@tznthou/ccrecall 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -16
- package/README_ZH.md +29 -16
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +25 -10
- package/dist/api/routes.js.map +1 -1
- package/dist/core/database.d.ts +62 -1
- package/dist/core/database.d.ts.map +1 -1
- package/dist/core/database.js +189 -26
- package/dist/core/database.js.map +1 -1
- package/dist/core/integrity-monitor.d.ts.map +1 -1
- package/dist/core/integrity-monitor.js +7 -3
- package/dist/core/integrity-monitor.js.map +1 -1
- package/dist/core/recall-telemetry.d.ts.map +1 -1
- package/dist/core/recall-telemetry.js +9 -3
- package/dist/core/recall-telemetry.js.map +1 -1
- package/dist/core/secure-dir.d.ts +7 -0
- package/dist/core/secure-dir.d.ts.map +1 -0
- package/dist/core/secure-dir.js +64 -0
- package/dist/core/secure-dir.js.map +1 -0
- package/dist/core/token-budget.d.ts +59 -3
- package/dist/core/token-budget.d.ts.map +1 -1
- package/dist/core/token-budget.js +115 -11
- package/dist/core/token-budget.js.map +1 -1
- package/dist/mcp/tools.d.ts +2 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +70 -12
- package/dist/mcp/tools.js.map +1 -1
- package/hooks/lib/secure-dir.mjs +52 -0
- package/hooks/session-start.mjs +20 -3
- package/package.json +3 -2
- package/scripts/extraction-prompt.md +52 -1
- package/scripts/l3-prescription-position.py +835 -0
- package/scripts/post-session-extract.sh +43 -7
package/hooks/session-start.mjs
CHANGED
|
@@ -13,11 +13,14 @@ import fs from 'node:fs'
|
|
|
13
13
|
import path from 'node:path'
|
|
14
14
|
import os from 'node:os'
|
|
15
15
|
import { resolveProjectId } from './lib/project-id.mjs'
|
|
16
|
+
import { ensureCcrecallDir, secureCcrecallFile, CCRECALL_FILE_MODE } from './lib/secure-dir.mjs'
|
|
16
17
|
|
|
17
18
|
const PORT = Number(process.env.CCRECALL_PORT ?? 7749)
|
|
18
19
|
const HOST = '127.0.0.1'
|
|
19
20
|
const TIMEOUT_MS = 2000
|
|
20
21
|
const MEMORY_LIMIT = 5
|
|
22
|
+
// Legacy strategy only. /memory/query treats an absent maxTokens as "no budget
|
|
23
|
+
// at all" and would emit whole memories, so this one still has to be sent.
|
|
21
24
|
const MAX_TOKENS = 300
|
|
22
25
|
// A key longer than this is not rendered at all rather than truncated: a clipped
|
|
23
26
|
// key is a handle that silently resolves to nothing. Observed keys run to 52
|
|
@@ -82,11 +85,16 @@ async function queryLegacy(query, projectId) {
|
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
async function queryStartupV1(query, projectId, sessionId) {
|
|
88
|
+
// No maxTokens: the daemon owns budget policy, same as the prompt hook. It
|
|
89
|
+
// prices the rendered line — prefix, confidence suffix, [key: …] handle,
|
|
90
|
+
// header and footer — and this file is the thing being priced, so it cannot
|
|
91
|
+
// be the one naming the number. It also means an upgrade to the service
|
|
92
|
+
// changes the budget without rewriting a hook already installed in the
|
|
93
|
+
// user's settings.json.
|
|
85
94
|
const params = new URLSearchParams({
|
|
86
95
|
project: projectId,
|
|
87
96
|
q: query,
|
|
88
97
|
limit: String(MEMORY_LIMIT),
|
|
89
|
-
maxTokens: String(MAX_TOKENS),
|
|
90
98
|
})
|
|
91
99
|
if (sessionId) params.set('sessionId', sessionId)
|
|
92
100
|
const result = await httpGet(`/memory/startup?${params.toString()}`)
|
|
@@ -101,8 +109,17 @@ async function queryStartupV1(query, projectId, sessionId) {
|
|
|
101
109
|
function writeTelemetry(record) {
|
|
102
110
|
if (TELEMETRY_OFF) return
|
|
103
111
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
// #95: this hook runs on every session start, which made it the creator
|
|
113
|
+
// that in practice decided ~/.ccrecall's mode — 0755 on the dogfood
|
|
114
|
+
// machine. secureCcrecallFile also repairs a log already sitting at 0644,
|
|
115
|
+
// which appendFileSync's own `mode` option would not: that applies only
|
|
116
|
+
// when the file is created.
|
|
117
|
+
ensureCcrecallDir(path.dirname(TELEMETRY_PATH))
|
|
118
|
+
fs.appendFileSync(TELEMETRY_PATH, JSON.stringify(record) + '\n', {
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
mode: CCRECALL_FILE_MODE,
|
|
121
|
+
})
|
|
122
|
+
secureCcrecallFile(TELEMETRY_PATH)
|
|
106
123
|
} catch (err) {
|
|
107
124
|
console.error(`[ccRecall] telemetry write failed: ${err.message}`)
|
|
108
125
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tznthou/ccrecall",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "AI memory service for Claude Code — on-demand context injection with metacognition",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"start": "node dist/index.js",
|
|
59
59
|
"test": "vitest run",
|
|
60
60
|
"lint": "eslint . --fix",
|
|
61
|
-
"typecheck": "tsc --noEmit"
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"eval:demo": "tsx evaluation/demo.ts"
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -21,13 +21,37 @@ NOT worth saving (noise):
|
|
|
21
21
|
- Anything already stored in auto-memory or CLAUDE.md
|
|
22
22
|
- Credentials, API keys, tokens, passwords
|
|
23
23
|
|
|
24
|
+
## Lead with the prescription (most important rule)
|
|
25
|
+
|
|
26
|
+
When this memory is injected into a future session, **only its first 149
|
|
27
|
+
characters survive** — the rest is truncated away. The reader sees the opening
|
|
28
|
+
and nothing else, and has to decide what to do from that alone.
|
|
29
|
+
|
|
30
|
+
So open with the thing a future reader should DO. Put the evidence, the war
|
|
31
|
+
story and the file names after it.
|
|
32
|
+
|
|
33
|
+
- BAD — the actionable half never arrives:
|
|
34
|
+
"Three file-cut attempts failed (M8 missing quote, M18 punctuation style, M20
|
|
35
|
+
paragraph detection) before realizing the patterns were wrong, not the data
|
|
36
|
+
missing. Mitigation: pre-verify patterns by running grep first."
|
|
37
|
+
- GOOD — survives the cut intact:
|
|
38
|
+
"Pre-verify a grep pattern against the live file (confirm >= 1 hit) before
|
|
39
|
+
encoding it into an assertion: zero-hits reads the same whether the pattern is
|
|
40
|
+
wrong or the data is absent. Hit three times — M8, M18, M20."
|
|
41
|
+
|
|
42
|
+
This is not a length limit. Write the full memory; just order it so the first
|
|
43
|
+
sentence carries the instruction rather than the narrative that produced it.
|
|
44
|
+
|
|
45
|
+
If a memory has no action attached — a pure fact, a decision record, a version
|
|
46
|
+
quirk — lead with the conclusion instead. Never lead with the investigation.
|
|
47
|
+
|
|
24
48
|
## How to write each memory
|
|
25
49
|
|
|
26
50
|
Every memory must be **self-contained** — a future reader in a different project
|
|
27
51
|
has zero context about this session.
|
|
28
52
|
|
|
29
53
|
Rules:
|
|
30
|
-
1. Include the WHY, not just the WHAT
|
|
54
|
+
1. Include the WHY, not just the WHAT — but after the prescription, not before it
|
|
31
55
|
2. Use concrete details: file paths (relative, not absolute), command names,
|
|
32
56
|
version numbers, error messages
|
|
33
57
|
3. Replace pronouns ("we", "it", "this") with specific nouns
|
|
@@ -72,6 +96,26 @@ memory is updated instead of duplicated. Choose keys that are:
|
|
|
72
96
|
- Stable enough that the same insight would get the same key next time
|
|
73
97
|
- Lowercase, hyphenated, no special characters
|
|
74
98
|
|
|
99
|
+
## Message provenance (messageId)
|
|
100
|
+
|
|
101
|
+
Every turn in the transcript is headed `--- human [<uuid>] ---` or
|
|
102
|
+
`--- assistant [<uuid>] ---`. That uuid identifies the message.
|
|
103
|
+
|
|
104
|
+
For each memory, pass the uuid of the ONE message that most directly produced
|
|
105
|
+
it — where the decision was stated, the root cause named, the preference
|
|
106
|
+
expressed. Copy it verbatim out of the header.
|
|
107
|
+
|
|
108
|
+
- **Never invent, reconstruct, or guess a uuid.** If no single message stands
|
|
109
|
+
out, omit `messageId` entirely. A missing citation costs nothing; a wrong one
|
|
110
|
+
makes a memory look sourced when it is not, which is worse than unsourced.
|
|
111
|
+
- A header printed without a uuid (`--- human ---`) cannot be cited. Omit.
|
|
112
|
+
- Citing a message is **not** a claim that the message proves the memory. It
|
|
113
|
+
records where you drew it from, so a human can go read that turn later. Pick
|
|
114
|
+
the most direct source; do not stretch to manufacture one.
|
|
115
|
+
- The save path checks the uuid against this session's real messages. One it
|
|
116
|
+
cannot find is dropped, the memory is saved without it, and the result says
|
|
117
|
+
so. Do not retry — the turn budget is better spent on saves not yet made.
|
|
118
|
+
|
|
75
119
|
## recall_save parameters
|
|
76
120
|
|
|
77
121
|
For each memory, call `recall_save` with:
|
|
@@ -82,6 +126,13 @@ For each memory, call `recall_save` with:
|
|
|
82
126
|
- `projectId`: include for project-specific, omit for cross-project
|
|
83
127
|
- `sessionId`: the Origin session ID given in the transcript header above —
|
|
84
128
|
pass it verbatim so each memory can be traced back to its origin session
|
|
129
|
+
- `messageId`: the uuid of the single message this memory came from, copied
|
|
130
|
+
from its `--- human [<uuid>] ---` header (see above). Omit when no one
|
|
131
|
+
message is the source — never guess one
|
|
132
|
+
- `origin`: always `"agent-inferred"`. You are reading a finished transcript
|
|
133
|
+
with nobody watching, so your saves must not overwrite a memory the user
|
|
134
|
+
wrote by hand under the same key. Omitting this marks your save as the
|
|
135
|
+
user's own and removes that protection.
|
|
85
136
|
|
|
86
137
|
## Output
|
|
87
138
|
|