@cachly-dev/mcp-server 0.10.116 → 0.10.118
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 +8 -3
- package/dist/src/ambient-cli.d.ts +85 -0
- package/dist/src/ambient-cli.js +199 -0
- package/dist/src/ambient-cli.js.map +1 -0
- package/dist/src/ambient-hooks.d.ts +128 -0
- package/dist/src/ambient-hooks.js +269 -0
- package/dist/src/ambient-hooks.js.map +1 -0
- package/dist/src/ambient-ledger.d.ts +19 -0
- package/dist/src/ambient-ledger.js +95 -0
- package/dist/src/ambient-ledger.js.map +1 -0
- package/dist/src/ambient-recall.d.ts +59 -0
- package/dist/src/ambient-recall.js +96 -0
- package/dist/src/ambient-recall.js.map +1 -0
- package/dist/src/doctor.d.ts +47 -0
- package/dist/src/doctor.js +247 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/first-contact.d.ts +56 -0
- package/dist/src/first-contact.js +108 -0
- package/dist/src/first-contact.js.map +1 -0
- package/dist/src/handlers/brain.d.ts +1 -0
- package/dist/src/handlers/brain.js +37 -7
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/fedbrain.js +48 -16
- package/dist/src/handlers/fedbrain.js.map +1 -1
- package/dist/src/handlers/syndicate.js +91 -1
- package/dist/src/handlers/syndicate.js.map +1 -1
- package/dist/src/handlers/team.d.ts +10 -0
- package/dist/src/handlers/team.js +45 -1
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/index.d.ts +8 -5
- package/dist/src/index.js +213 -33
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +5 -196
- package/dist/src/tools.js +12 -179
- package/dist/src/tools.js.map +1 -1
- package/dist/src/toolspecs.d.ts +1 -1
- package/dist/src/toolspecs.js +1 -1
- package/glama.json +1 -1
- package/package.json +14 -4
- package/server.json +4 -4
- package/dist/src/handlers/tco.d.ts +0 -16
- package/dist/src/handlers/tco.js +0 -304
- package/dist/src/handlers/tco.js.map +0 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// Ambient Recall (Phase 4) — Claude Code hook-script generators.
|
|
2
|
+
//
|
|
3
|
+
// Builds the SessionStart + UserPromptSubmit hook scripts (and the
|
|
4
|
+
// .claude/settings.json fragment that wires them) so recall is PUSHED into
|
|
5
|
+
// context automatically, without the agent having to remember to call it
|
|
6
|
+
// (roadmap §6.1). This is the packaging that turns cachly's existing recall
|
|
7
|
+
// into "memory that is just there".
|
|
8
|
+
//
|
|
9
|
+
// Design mirrors cls-hook.ts:
|
|
10
|
+
// • Pure string builders — exhaustively unit-tested.
|
|
11
|
+
// • No untrusted input is interpolated into the script; the hook payload
|
|
12
|
+
// (which contains the user's prompt) is piped to the CLI on stdin, never
|
|
13
|
+
// spliced into shell/JS source.
|
|
14
|
+
// • Versioned so installers can upgrade old hooks in place.
|
|
15
|
+
// • Graceful: a crashing hook must NEVER block the agent — every path exits 0
|
|
16
|
+
// with no output, so Claude Code simply proceeds without the extra context
|
|
17
|
+
// (§6.3 guardrail 5).
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import { readFile, writeFile, chmod, mkdir } from 'node:fs/promises';
|
|
20
|
+
import { resolve } from 'node:path';
|
|
21
|
+
/**
|
|
22
|
+
* Bumped whenever a hook script changes so installers can upgrade old hooks.
|
|
23
|
+
* v3: hooks became Node scripts (.mjs) invoked as `node "<path>"` — the
|
|
24
|
+
* cross-platform shape from the Claude Code hooks guide. The v1/v2 POSIX shell
|
|
25
|
+
* scripts silently never ran on native Windows (no /bin/sh); Node is already a
|
|
26
|
+
* hard requirement of this package, so the hook now runs identically on
|
|
27
|
+
* Windows, macOS and Linux.
|
|
28
|
+
*/
|
|
29
|
+
export const AMBIENT_HOOK_VERSION = 'v3';
|
|
30
|
+
/**
|
|
31
|
+
* The CLI subcommand the hooks pipe their payload to. It reads the hook JSON on
|
|
32
|
+
* stdin, runs smart_recall through the relevance gate (ambient-recall.ts), and
|
|
33
|
+
* prints the `hookSpecificOutput` JSON Claude Code injects as additionalContext.
|
|
34
|
+
* The CLI self-limits its latency budget (§6.3 guardrail 4). See ambient-cli.ts.
|
|
35
|
+
*/
|
|
36
|
+
export const AMBIENT_CLI_SUBCOMMAND = 'ambient-recall';
|
|
37
|
+
const DEFAULT_CLI = `npx @cachly-dev/mcp-server@latest ${AMBIENT_CLI_SUBCOMMAND}`;
|
|
38
|
+
/** Escape a value for embedding inside a single-quoted JS string literal. */
|
|
39
|
+
function jsString(s) {
|
|
40
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
41
|
+
}
|
|
42
|
+
function buildHook(event, opts) {
|
|
43
|
+
const cli = opts.cliCommand ?? DEFAULT_CLI;
|
|
44
|
+
return [
|
|
45
|
+
`#!/usr/bin/env node`,
|
|
46
|
+
`// cachly Ambient Recall — ${event} ${AMBIENT_HOOK_VERSION}`,
|
|
47
|
+
`// Pushes relevant memory into context automatically. Cross-platform Node hook`,
|
|
48
|
+
`// (no shell script — runs identically on Windows/macOS/Linux). Never blocks`,
|
|
49
|
+
`// the agent: every failure path exits 0 with no output (graceful degrade).`,
|
|
50
|
+
`import { spawn } from 'node:child_process';`,
|
|
51
|
+
`process.env.CACHLY_BRAIN_INSTANCE_ID = '${jsString(opts.instanceId)}';`,
|
|
52
|
+
...(opts.apiKey ? [`process.env.CACHLY_JWT = '${jsString(opts.apiKey)}';`] : []),
|
|
53
|
+
`process.env.CACHLY_HOOK_EVENT = '${jsString(event)}';`,
|
|
54
|
+
`try {`,
|
|
55
|
+
// Claude Code delivers the hook payload as JSON on stdin; stdio 'inherit'
|
|
56
|
+
// hands it verbatim to the CLI (no re-parsing of prompt content), and the
|
|
57
|
+
// CLI's hookSpecificOutput JSON flows straight back to Claude Code on
|
|
58
|
+
// stdout. shell:true resolves npx/npx.cmd on every platform; stderr is
|
|
59
|
+
// discarded so a noisy npm can never corrupt the hook protocol.
|
|
60
|
+
` const child = spawn('${jsString(cli)}', { shell: true, stdio: ['inherit', 'inherit', 'ignore'] });`,
|
|
61
|
+
` child.on('error', () => process.exit(0));`,
|
|
62
|
+
` child.on('close', () => process.exit(0));`,
|
|
63
|
+
`} catch {`,
|
|
64
|
+
` process.exit(0);`,
|
|
65
|
+
`}`,
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* SessionStart hook: emits the session briefing as additionalContext at the
|
|
70
|
+
* start of every session — the automatic replacement for "the agent should call
|
|
71
|
+
* session_start".
|
|
72
|
+
*/
|
|
73
|
+
export function buildSessionStartHook(opts) {
|
|
74
|
+
return buildHook('SessionStart', opts);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* UserPromptSubmit hook — the core of Ambient Recall. Runs before every user
|
|
78
|
+
* message, recalls on the prompt through the relevance gate, and pushes gated
|
|
79
|
+
* hits into context. This is the most-forgotten call, made automatic.
|
|
80
|
+
*/
|
|
81
|
+
export function buildUserPromptSubmitHook(opts) {
|
|
82
|
+
return buildHook('UserPromptSubmit', opts);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* PreToolUse hook (Ausbau) — file-open briefing. Fires before Edit/Write-class
|
|
86
|
+
* tools (settings matcher `Edit|Write|MultiEdit`); the CLI recalls file-scoped
|
|
87
|
+
* lessons and injects them as additionalContext — the automatic "prüf mal, ob's
|
|
88
|
+
* zu dieser Datei Lessons gibt" (roadmap §6.1).
|
|
89
|
+
*/
|
|
90
|
+
export function buildPreToolUseHook(opts) {
|
|
91
|
+
return buildHook('PreToolUse', opts);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Stop hook (Ausbau) — auto-learn. After a turn whose final message carries a
|
|
95
|
+
* clear fix signal, the CLI feeds one observation to auto_learn_session — the
|
|
96
|
+
* automatic replacement for the forgotten `learn_from_attempts` call.
|
|
97
|
+
*/
|
|
98
|
+
export function buildStopHook(opts) {
|
|
99
|
+
return buildHook('Stop', opts);
|
|
100
|
+
}
|
|
101
|
+
// Per-event latency budgets (seconds). The CLI self-limits recall to 3s; these
|
|
102
|
+
// are the outer safety net so a wedged npx can never stall a turn for long.
|
|
103
|
+
const EVENT_TIMEOUTS = {
|
|
104
|
+
SessionStart: 30,
|
|
105
|
+
UserPromptSubmit: 10,
|
|
106
|
+
PreToolUse: 10,
|
|
107
|
+
Stop: 60, // auto-learn may do a real write; Stop is not latency-critical
|
|
108
|
+
};
|
|
109
|
+
/** Matcher for the PreToolUse briefing: only file-mutating tools. */
|
|
110
|
+
export const PRE_TOOL_USE_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';
|
|
111
|
+
/**
|
|
112
|
+
* Build the `.claude/settings.json` `hooks` fragment that wires the scripts.
|
|
113
|
+
* The caller merges this into the user's existing settings (never overwrites).
|
|
114
|
+
* Shape matches Claude Code's hooks config: an array of matcher groups, each
|
|
115
|
+
* with a list of `{ type: "command", command, timeout }` entries.
|
|
116
|
+
*/
|
|
117
|
+
export function buildAmbientSettingsHooks(paths) {
|
|
118
|
+
// v3: the command is `node "<script>"` — a plain string that both /bin/sh
|
|
119
|
+
// (macOS/Linux, Windows+Git-Bash) and PowerShell (native Windows fallback)
|
|
120
|
+
// execute identically, so one settings shape covers every platform.
|
|
121
|
+
const entry = (event, scriptPath, matcher) => ({
|
|
122
|
+
...(matcher ? { matcher } : {}),
|
|
123
|
+
hooks: [{ type: 'command', command: `node "${scriptPath}"`, timeout: EVENT_TIMEOUTS[event] }],
|
|
124
|
+
});
|
|
125
|
+
const frag = {
|
|
126
|
+
SessionStart: [entry('SessionStart', paths.sessionStart)],
|
|
127
|
+
UserPromptSubmit: [entry('UserPromptSubmit', paths.userPromptSubmit)],
|
|
128
|
+
};
|
|
129
|
+
if (paths.preToolUse)
|
|
130
|
+
frag.PreToolUse = [entry('PreToolUse', paths.preToolUse, PRE_TOOL_USE_MATCHER)];
|
|
131
|
+
if (paths.stop)
|
|
132
|
+
frag.Stop = [entry('Stop', paths.stop)];
|
|
133
|
+
return frag;
|
|
134
|
+
}
|
|
135
|
+
// ── Installer ────────────────────────────────────────────────────────────────
|
|
136
|
+
// Writes the two hook scripts into `.claude/hooks/` and merges the settings
|
|
137
|
+
// fragment into `.claude/settings.json`. Idempotent and non-destructive:
|
|
138
|
+
// • re-running upgrades the scripts in place (version marker) and never
|
|
139
|
+
// duplicates a settings entry that already points at our script,
|
|
140
|
+
// • foreign hooks in the user's settings are preserved (we append, never
|
|
141
|
+
// overwrite the arrays).
|
|
142
|
+
// Never throws — returns a status the caller can log.
|
|
143
|
+
const HOOK_DIR = '.claude/hooks';
|
|
144
|
+
const SETTINGS_FILE = '.claude/settings.json';
|
|
145
|
+
/** Script filename per event — shared marker `cachly-ambient-` drives upgrades. */
|
|
146
|
+
const SCRIPT_NAMES = {
|
|
147
|
+
SessionStart: 'cachly-ambient-session-start.mjs',
|
|
148
|
+
UserPromptSubmit: 'cachly-ambient-prompt-submit.mjs',
|
|
149
|
+
PreToolUse: 'cachly-ambient-pre-tool.mjs',
|
|
150
|
+
Stop: 'cachly-ambient-stop.mjs',
|
|
151
|
+
};
|
|
152
|
+
const AMBIENT_SCRIPT_MARKER = '.claude/hooks/cachly-ambient-';
|
|
153
|
+
/** True when this matcher group only wires cachly-ambient scripts. */
|
|
154
|
+
function isAmbientGroup(g, currentPaths) {
|
|
155
|
+
const hooks = g.hooks ?? [];
|
|
156
|
+
return (hooks.length > 0 &&
|
|
157
|
+
hooks.every((h) => {
|
|
158
|
+
const cmd = h.command ?? '';
|
|
159
|
+
// Matches v1/v2 entries (bare script path), v3 entries (`node "<path>"`),
|
|
160
|
+
// and marker-less test/custom paths passed as the current fragment.
|
|
161
|
+
return (cmd.includes(AMBIENT_SCRIPT_MARKER) ||
|
|
162
|
+
currentPaths.has(cmd) ||
|
|
163
|
+
[...currentPaths].some((p) => cmd.includes(`"${p}"`)));
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Merge our hook entries into an existing settings object without disturbing
|
|
168
|
+
* anything else. Idempotent AND upgrade-safe: existing cachly-ambient groups
|
|
169
|
+
* (from any prior version/paths) are replaced by the current fragment rather
|
|
170
|
+
* than accumulated; foreign groups are always preserved. Pure — unit-tested.
|
|
171
|
+
*/
|
|
172
|
+
export function mergeAmbientSettings(existing, paths) {
|
|
173
|
+
const next = { ...existing, hooks: { ...(existing.hooks ?? {}) } };
|
|
174
|
+
const hooks = next.hooks;
|
|
175
|
+
const fragment = buildAmbientSettingsHooks(paths);
|
|
176
|
+
const currentPaths = new Set([paths.sessionStart, paths.userPromptSubmit, paths.preToolUse, paths.stop].filter((p) => !!p));
|
|
177
|
+
for (const [event, ourGroups] of Object.entries(fragment)) {
|
|
178
|
+
const foreign = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((g) => !isAmbientGroup(g, currentPaths));
|
|
179
|
+
hooks[event] = [...foreign, ...ourGroups];
|
|
180
|
+
}
|
|
181
|
+
const changed = JSON.stringify(next) !== JSON.stringify(existing);
|
|
182
|
+
return { settings: next, changed };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Install/upgrade the Ambient Recall hooks in `projectDir`. Best-effort:
|
|
186
|
+
* a filesystem error surfaces as a thrown error only for the top-level caller,
|
|
187
|
+
* which wraps it in try/catch (the git-hook feature is non-critical).
|
|
188
|
+
*/
|
|
189
|
+
export async function installAmbientHooks(projectDir, instanceId, apiKey, options = {}) {
|
|
190
|
+
const hookDir = resolve(projectDir, HOOK_DIR);
|
|
191
|
+
await mkdir(hookDir, { recursive: true });
|
|
192
|
+
const opts = { instanceId, apiKey };
|
|
193
|
+
const events = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'Stop'];
|
|
194
|
+
const scriptFor = {
|
|
195
|
+
SessionStart: buildSessionStartHook(opts),
|
|
196
|
+
UserPromptSubmit: buildUserPromptSubmitHook(opts),
|
|
197
|
+
PreToolUse: buildPreToolUseHook(opts),
|
|
198
|
+
Stop: buildStopHook(opts),
|
|
199
|
+
};
|
|
200
|
+
const pathFor = (e) => resolve(hookDir, SCRIPT_NAMES[e]);
|
|
201
|
+
const readIf = async (p) => {
|
|
202
|
+
try {
|
|
203
|
+
return await readFile(p, 'utf-8');
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
let anyExisting = false;
|
|
210
|
+
let allCurrent = true;
|
|
211
|
+
for (const e of events) {
|
|
212
|
+
const prev = await readIf(pathFor(e));
|
|
213
|
+
if (prev)
|
|
214
|
+
anyExisting = true;
|
|
215
|
+
if (prev !== scriptFor[e] + '\n')
|
|
216
|
+
allCurrent = false;
|
|
217
|
+
}
|
|
218
|
+
const scripts = allCurrent ? 'unchanged' : anyExisting ? 'upgraded' : 'written';
|
|
219
|
+
if (scripts !== 'unchanged') {
|
|
220
|
+
for (const e of events) {
|
|
221
|
+
await writeFile(pathFor(e), scriptFor[e] + '\n', 'utf-8');
|
|
222
|
+
await chmod(pathFor(e), 0o755).catch(() => { });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Merge the settings fragment (upgrade-safe: prior ambient groups replaced).
|
|
226
|
+
const settingsPath = resolve(projectDir, SETTINGS_FILE);
|
|
227
|
+
const settingsExisted = existsSync(settingsPath);
|
|
228
|
+
let existingSettings = {};
|
|
229
|
+
if (settingsExisted) {
|
|
230
|
+
try {
|
|
231
|
+
existingSettings = JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
existingSettings = {};
|
|
235
|
+
} // corrupt/empty file → start fresh (still non-destructive to hooks we add)
|
|
236
|
+
}
|
|
237
|
+
// Portable installs (committed hooks) reference $CLAUDE_PROJECT_DIR so the
|
|
238
|
+
// settings work at any checkout location; local installs use absolute paths.
|
|
239
|
+
// These are SCRIPT PATHS — buildAmbientSettingsHooks wraps them as
|
|
240
|
+
// `node "<path>"`, so the portable var sits inside the double quotes and is
|
|
241
|
+
// expanded by the shell at hook time.
|
|
242
|
+
const commandFor = (e) => options.portable ? `$CLAUDE_PROJECT_DIR/${HOOK_DIR}/${SCRIPT_NAMES[e]}` : pathFor(e);
|
|
243
|
+
const { settings, changed } = mergeAmbientSettings(existingSettings, {
|
|
244
|
+
sessionStart: commandFor('SessionStart'),
|
|
245
|
+
userPromptSubmit: commandFor('UserPromptSubmit'),
|
|
246
|
+
preToolUse: commandFor('PreToolUse'),
|
|
247
|
+
stop: commandFor('Stop'),
|
|
248
|
+
});
|
|
249
|
+
let settingsStatus;
|
|
250
|
+
if (!settingsExisted)
|
|
251
|
+
settingsStatus = 'written';
|
|
252
|
+
else if (!changed)
|
|
253
|
+
settingsStatus = 'unchanged';
|
|
254
|
+
else
|
|
255
|
+
settingsStatus = 'merged';
|
|
256
|
+
if (settingsStatus !== 'unchanged') {
|
|
257
|
+
await mkdir(resolve(projectDir, '.claude'), { recursive: true });
|
|
258
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
sessionStartPath: pathFor('SessionStart'),
|
|
262
|
+
promptSubmitPath: pathFor('UserPromptSubmit'),
|
|
263
|
+
preToolUsePath: pathFor('PreToolUse'),
|
|
264
|
+
stopPath: pathFor('Stop'),
|
|
265
|
+
scripts,
|
|
266
|
+
settings: settingsStatus,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=ambient-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ambient-hooks.js","sourceRoot":"","sources":["../../src/ambient-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TurnRecord } from './ambient-recall.js';
|
|
2
|
+
export interface LedgerEntry extends TurnRecord {
|
|
3
|
+
/** ISO timestamp of the turn. */
|
|
4
|
+
ts: string;
|
|
5
|
+
/** Hook event (SessionStart/UserPromptSubmit/PreToolUse) or 'credit'. */
|
|
6
|
+
event: string;
|
|
7
|
+
/** Optional free-text note (e.g. what the credit was for). */
|
|
8
|
+
note?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Default ledger location; overridable for tests via explicit path args. */
|
|
11
|
+
export declare function defaultLedgerPath(): string;
|
|
12
|
+
/** One JSONL line for an entry. Pure. */
|
|
13
|
+
export declare function serializeEntry(e: LedgerEntry): string;
|
|
14
|
+
/** Parse ledger text into entries, silently skipping corrupt lines. Pure. */
|
|
15
|
+
export declare function parseLedger(text: string): LedgerEntry[];
|
|
16
|
+
/** Append an entry; auto-creates the directory, auto-rotates. Never throws. */
|
|
17
|
+
export declare function appendLedgerEntry(entry: LedgerEntry, path?: string): Promise<void>;
|
|
18
|
+
/** Read all entries; missing/corrupt file → []. Never throws. */
|
|
19
|
+
export declare function readLedger(path?: string): Promise<LedgerEntry[]>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Ambient Recall (Phase 4 Ausbau) — the net-token ledger.
|
|
2
|
+
//
|
|
3
|
+
// Roadmap §6.2: the whole feature is only honest if injected tokens (cost, paid
|
|
4
|
+
// now) are weighed against prevented tokens (a wrong path a lesson averted).
|
|
5
|
+
// This module persists per-turn records as JSONL (~/.cachly/ambient-ledger.jsonl)
|
|
6
|
+
// so `ambient-stats` can show the NET number — even when it is negative — and
|
|
7
|
+
// the CLI can auto-backoff (§6.3 guardrail 3) when the recent window is net-red.
|
|
8
|
+
//
|
|
9
|
+
// Prevented-credit in v1 is agent-reported: the injected context carries a tiny
|
|
10
|
+
// footer inviting the agent to run `ambient-credit <tokens>` when a recalled
|
|
11
|
+
// lesson actually changed its path. Self-reported ≠ perfect, but it is the only
|
|
12
|
+
// client-side signal that exists before the server-side dashboard (v2) — and it
|
|
13
|
+
// is what makes auto-backoff meaningful instead of permanently red.
|
|
14
|
+
//
|
|
15
|
+
// Everything here is best-effort: a broken ledger must never break a hook.
|
|
16
|
+
import { appendFile, readFile, rename, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { mkdir } from 'node:fs/promises';
|
|
18
|
+
import { dirname, resolve } from 'node:path';
|
|
19
|
+
/** Default ledger location; overridable for tests via explicit path args. */
|
|
20
|
+
export function defaultLedgerPath() {
|
|
21
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? '.';
|
|
22
|
+
return resolve(home, '.cachly', 'ambient-ledger.jsonl');
|
|
23
|
+
}
|
|
24
|
+
/** One JSONL line for an entry. Pure. */
|
|
25
|
+
export function serializeEntry(e) {
|
|
26
|
+
return JSON.stringify({
|
|
27
|
+
ts: e.ts,
|
|
28
|
+
event: e.event,
|
|
29
|
+
injected: Math.max(0, Math.round(e.injected)),
|
|
30
|
+
prevented: Math.max(0, Math.round(e.prevented)),
|
|
31
|
+
...(e.note ? { note: e.note.slice(0, 200) } : {}),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Parse ledger text into entries, silently skipping corrupt lines. Pure. */
|
|
35
|
+
export function parseLedger(text) {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const line of text.split('\n')) {
|
|
38
|
+
const t = line.trim();
|
|
39
|
+
if (!t)
|
|
40
|
+
continue;
|
|
41
|
+
try {
|
|
42
|
+
const e = JSON.parse(t);
|
|
43
|
+
if (typeof e !== 'object' || e === null)
|
|
44
|
+
continue;
|
|
45
|
+
const injected = Number(e.injected);
|
|
46
|
+
const prevented = Number(e.prevented);
|
|
47
|
+
if (!Number.isFinite(injected) || !Number.isFinite(prevented))
|
|
48
|
+
continue;
|
|
49
|
+
out.push({
|
|
50
|
+
ts: typeof e.ts === 'string' ? e.ts : '',
|
|
51
|
+
event: typeof e.event === 'string' ? e.event : 'unknown',
|
|
52
|
+
injected: Math.max(0, injected),
|
|
53
|
+
prevented: Math.max(0, prevented),
|
|
54
|
+
...(typeof e.note === 'string' ? { note: e.note } : {}),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// corrupt line (partial write, manual edit) → skip, never throw
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
// Rotation keeps the ledger bounded: hooks run on every prompt, and an
|
|
64
|
+
// unbounded append-only file would eventually slow every turn down.
|
|
65
|
+
const MAX_LEDGER_BYTES = 256 * 1024;
|
|
66
|
+
const KEEP_LINES_ON_ROTATE = 400;
|
|
67
|
+
/** Append an entry; auto-creates the directory, auto-rotates. Never throws. */
|
|
68
|
+
export async function appendLedgerEntry(entry, path = defaultLedgerPath()) {
|
|
69
|
+
try {
|
|
70
|
+
await mkdir(dirname(path), { recursive: true });
|
|
71
|
+
await appendFile(path, serializeEntry(entry) + '\n', 'utf-8');
|
|
72
|
+
const text = await readFile(path, 'utf-8');
|
|
73
|
+
if (Buffer.byteLength(text, 'utf-8') > MAX_LEDGER_BYTES) {
|
|
74
|
+
const lines = text.split('\n').filter(Boolean);
|
|
75
|
+
const kept = lines.slice(-KEEP_LINES_ON_ROTATE).join('\n') + '\n';
|
|
76
|
+
// Write-then-rename so a crash mid-rotation never truncates to nothing.
|
|
77
|
+
const tmp = path + '.tmp';
|
|
78
|
+
await writeFile(tmp, kept, 'utf-8');
|
|
79
|
+
await rename(tmp, path);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ledger is telemetry — never let it break a hook or a commit.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Read all entries; missing/corrupt file → []. Never throws. */
|
|
87
|
+
export async function readLedger(path = defaultLedgerPath()) {
|
|
88
|
+
try {
|
|
89
|
+
return parseLedger(await readFile(path, 'utf-8'));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=ambient-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ambient-ledger.js","sourceRoot":"","sources":["../../src/ambient-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface LessonCandidate {
|
|
2
|
+
id: string;
|
|
3
|
+
/** The text that would be injected into context. */
|
|
4
|
+
summary: string;
|
|
5
|
+
/** Brain confidence in the lesson, [0,1]. */
|
|
6
|
+
confidence: number;
|
|
7
|
+
/** Semantic similarity of the lesson to the current prompt, [0,1]. */
|
|
8
|
+
score: number;
|
|
9
|
+
severity?: 'critical' | 'major' | 'minor';
|
|
10
|
+
}
|
|
11
|
+
export interface GateOptions {
|
|
12
|
+
/** Minimum brain confidence to be eligible. */
|
|
13
|
+
minConfidence: number;
|
|
14
|
+
/** Minimum semantic similarity to the prompt. */
|
|
15
|
+
minScore: number;
|
|
16
|
+
/** Hard cap on how many lessons may be injected in one turn. */
|
|
17
|
+
topK: number;
|
|
18
|
+
/** Hard per-turn injection budget, in estimated tokens. */
|
|
19
|
+
maxTokens: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const DEFAULT_GATE: GateOptions;
|
|
22
|
+
/** Rough token estimate (~4 chars/token) — good enough for a per-turn budget. */
|
|
23
|
+
export declare function estimateTokens(text: string): number;
|
|
24
|
+
/**
|
|
25
|
+
* A trivial prompt has ~zero expected wrong-path savings (§6.3 guardrail 2):
|
|
26
|
+
* too short to carry risk, a pure greeting, or chit-chat with no code signal.
|
|
27
|
+
* Injecting there is pure waste, so we skip recall entirely.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isTrivialPrompt(prompt: string): boolean;
|
|
30
|
+
export type GateReason = 'trivial-skip' | 'no-candidate-passed-gate' | 'injected';
|
|
31
|
+
export interface GateDecision {
|
|
32
|
+
inject: boolean;
|
|
33
|
+
reason: GateReason;
|
|
34
|
+
selected: LessonCandidate[];
|
|
35
|
+
/** Estimated tokens the selection would inject (0 when nothing is injected). */
|
|
36
|
+
tokens: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The §6.3 relevance gate: decide what (if anything) to push into context for a
|
|
40
|
+
* given prompt. Applies trivial-skip, a confidence AND semantic-score floor,
|
|
41
|
+
* a top-K cap, and a hard per-turn token budget — strongest candidates first.
|
|
42
|
+
*/
|
|
43
|
+
export declare function selectInjectable(prompt: string, candidates: LessonCandidate[], opts?: Partial<GateOptions>): GateDecision;
|
|
44
|
+
export interface TurnRecord {
|
|
45
|
+
injected: number;
|
|
46
|
+
prevented: number;
|
|
47
|
+
}
|
|
48
|
+
export interface NetBalance {
|
|
49
|
+
injected: number;
|
|
50
|
+
prevented: number;
|
|
51
|
+
net: number;
|
|
52
|
+
}
|
|
53
|
+
export declare function netBalance(records: TurnRecord[]): NetBalance;
|
|
54
|
+
/**
|
|
55
|
+
* Auto-backoff (§6.3 guardrail 3): if, over the recent window, Ambient Recall is
|
|
56
|
+
* net-negative, tell the caller to tighten the gate (or pause). Only fires once
|
|
57
|
+
* there is enough signal (>= minTurns) so early noise doesn't trip it.
|
|
58
|
+
*/
|
|
59
|
+
export declare function shouldBackoff(records: TurnRecord[], windowN?: number, minTurns?: number): boolean;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Ambient Recall (Phase 4) — the relevance gate + net-token accounting that make
|
|
2
|
+
// push-based memory economically honest.
|
|
3
|
+
//
|
|
4
|
+
// Background (docs/make_cachly_great_again.md §6.2/§6.3): pushing recall into
|
|
5
|
+
// every prompt COSTS tokens; it only pays off when the injected lesson prevents
|
|
6
|
+
// a wrong path that would have cost far more. So the whole feature hinges on a
|
|
7
|
+
// sharp relevance gate (only inject high-signal, few, small) plus honest net
|
|
8
|
+
// accounting (injected vs prevented) with auto-backoff when it goes net-negative.
|
|
9
|
+
//
|
|
10
|
+
// This module is pure and dependency-free so it is exhaustively unit-tested; the
|
|
11
|
+
// hook scripts + telemetry layer build on top of it.
|
|
12
|
+
// Deliberately conservative defaults: it is far cheaper to skip a marginal
|
|
13
|
+
// lesson than to erode the "−60% tokens" credibility by injecting noise.
|
|
14
|
+
export const DEFAULT_GATE = {
|
|
15
|
+
minConfidence: 0.6,
|
|
16
|
+
minScore: 0.72,
|
|
17
|
+
topK: 3,
|
|
18
|
+
maxTokens: 240,
|
|
19
|
+
};
|
|
20
|
+
/** Rough token estimate (~4 chars/token) — good enough for a per-turn budget. */
|
|
21
|
+
export function estimateTokens(text) {
|
|
22
|
+
return Math.ceil(text.trim().length / 4);
|
|
23
|
+
}
|
|
24
|
+
// Pure conversational openers with no engineering payload.
|
|
25
|
+
const TRIVIAL_RE = /^(hi|hello|hey|thanks|thank you|ok|okay|yes|no|yep|nope|ty|lol|danke|hallo|servus)\b/i;
|
|
26
|
+
// Signals that a prompt is about code/ops, where a wrong path is expensive and a
|
|
27
|
+
// relevant lesson can pay for itself. Calibrated against the golden vectors in
|
|
28
|
+
// docs/spec/ambient-gate-vectors.json (ambient-gate-vectors.test.ts): the v1
|
|
29
|
+
// list false-skipped short real requests like "curl returns 403 via the proxy"
|
|
30
|
+
// or "rename the Settings page to Preferences" (substantive-recall 0.67), so
|
|
31
|
+
// v2 adds engineering verbs, infra nouns, HTTP status codes and German
|
|
32
|
+
// question words. Stems with \w* also match inflections ("migration",
|
|
33
|
+
// "updated", "evicted"). Substantive-recall on the vector set: 1.0, with
|
|
34
|
+
// trivial-precision unchanged at 1.0.
|
|
35
|
+
const CODEY_RE = /[/\\._{}()<>;=]|\b\d{3}\b|\b(fix|bug|error|deploy|migrat\w*|test\w*|build|refactor|auth|api|db|schema|race|crash|fail\w*|revert|rollback|why|how|debug|config|hook|token|cache|add|creat\w*|implement\w*|updat\w*|remov\w*|delet\w*|renam\w*|install\w*|upgrad\w*|write|script|component|page|button|css|style\w*|log\w*|pod\w*|node\w*|docker|k8s|kubernetes|proxy|curl|http\w*|readme|lint\w*|latenc\w*|latenz|timeout\w*|evict\w*|warum|wieso|weshalb)\b/i;
|
|
36
|
+
/**
|
|
37
|
+
* A trivial prompt has ~zero expected wrong-path savings (§6.3 guardrail 2):
|
|
38
|
+
* too short to carry risk, a pure greeting, or chit-chat with no code signal.
|
|
39
|
+
* Injecting there is pure waste, so we skip recall entirely.
|
|
40
|
+
*/
|
|
41
|
+
export function isTrivialPrompt(prompt) {
|
|
42
|
+
const p = prompt.trim();
|
|
43
|
+
if (p.length < 12)
|
|
44
|
+
return true;
|
|
45
|
+
if (TRIVIAL_RE.test(p) && p.length < 40)
|
|
46
|
+
return true;
|
|
47
|
+
if (!CODEY_RE.test(p) && p.length < 60)
|
|
48
|
+
return true;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The §6.3 relevance gate: decide what (if anything) to push into context for a
|
|
53
|
+
* given prompt. Applies trivial-skip, a confidence AND semantic-score floor,
|
|
54
|
+
* a top-K cap, and a hard per-turn token budget — strongest candidates first.
|
|
55
|
+
*/
|
|
56
|
+
export function selectInjectable(prompt, candidates, opts = {}) {
|
|
57
|
+
const o = { ...DEFAULT_GATE, ...opts };
|
|
58
|
+
if (isTrivialPrompt(prompt)) {
|
|
59
|
+
return { inject: false, reason: 'trivial-skip', selected: [], tokens: 0 };
|
|
60
|
+
}
|
|
61
|
+
const passed = candidates
|
|
62
|
+
.filter((c) => c.confidence >= o.minConfidence && c.score >= o.minScore)
|
|
63
|
+
.sort((a, b) => b.score - a.score || b.confidence - a.confidence);
|
|
64
|
+
const selected = [];
|
|
65
|
+
let tokens = 0;
|
|
66
|
+
for (const c of passed) {
|
|
67
|
+
if (selected.length >= o.topK)
|
|
68
|
+
break;
|
|
69
|
+
const t = estimateTokens(c.summary);
|
|
70
|
+
if (tokens + t > o.maxTokens)
|
|
71
|
+
break; // hard per-turn cap — never overshoot
|
|
72
|
+
selected.push(c);
|
|
73
|
+
tokens += t;
|
|
74
|
+
}
|
|
75
|
+
if (selected.length === 0) {
|
|
76
|
+
return { inject: false, reason: 'no-candidate-passed-gate', selected: [], tokens: 0 };
|
|
77
|
+
}
|
|
78
|
+
return { inject: true, reason: 'injected', selected, tokens };
|
|
79
|
+
}
|
|
80
|
+
export function netBalance(records) {
|
|
81
|
+
const injected = records.reduce((s, r) => s + r.injected, 0);
|
|
82
|
+
const prevented = records.reduce((s, r) => s + r.prevented, 0);
|
|
83
|
+
return { injected, prevented, net: prevented - injected };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Auto-backoff (§6.3 guardrail 3): if, over the recent window, Ambient Recall is
|
|
87
|
+
* net-negative, tell the caller to tighten the gate (or pause). Only fires once
|
|
88
|
+
* there is enough signal (>= minTurns) so early noise doesn't trip it.
|
|
89
|
+
*/
|
|
90
|
+
export function shouldBackoff(records, windowN = 20, minTurns = 8) {
|
|
91
|
+
const recent = records.slice(-windowN);
|
|
92
|
+
if (recent.length < minTurns)
|
|
93
|
+
return false;
|
|
94
|
+
return netBalance(recent).net < 0;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=ambient-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ambient-recall.js","sourceRoot":"","sources":["../../src/ambient-recall.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type TurnRecord } from './ambient-recall.js';
|
|
2
|
+
export type DoctorStatus = 'ok' | 'warn' | 'fail';
|
|
3
|
+
export interface DoctorCheck {
|
|
4
|
+
/** Short stable label, e.g. "Node.js", "Credential", "Hooks". */
|
|
5
|
+
name: string;
|
|
6
|
+
status: DoctorStatus;
|
|
7
|
+
/** One line of what was found. */
|
|
8
|
+
detail: string;
|
|
9
|
+
/** Actionable next step — only set when status is not ok. */
|
|
10
|
+
hint?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Node runtime new enough for fetch/AbortSignal (>= 18). */
|
|
13
|
+
export declare function checkNodeVersion(version?: string): DoctorCheck;
|
|
14
|
+
/** Credential present and roughly the right shape (cky_… API key or JWT). */
|
|
15
|
+
export declare function checkCredential(jwt: string): DoctorCheck;
|
|
16
|
+
type FetchLike = (url: string, init?: {
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}) => Promise<{
|
|
19
|
+
ok: boolean;
|
|
20
|
+
status: number;
|
|
21
|
+
}>;
|
|
22
|
+
/** API reachable at all (public /health — no auth involved). */
|
|
23
|
+
export declare function checkApiReachable(apiUrl: string, fetchFn?: FetchLike): Promise<DoctorCheck>;
|
|
24
|
+
/** Credential actually accepted by the API (authenticated list call). */
|
|
25
|
+
export declare function checkAuthAccepted(apiUrl: string, jwt: string, fetchFn?: FetchLike): Promise<DoctorCheck>;
|
|
26
|
+
/** Brain instance configured (env or auto-provisioned default). */
|
|
27
|
+
export declare function checkInstance(instanceId: string | undefined): DoctorCheck;
|
|
28
|
+
export interface HookInspection {
|
|
29
|
+
settingsExists: boolean;
|
|
30
|
+
wiredEvents: string[];
|
|
31
|
+
scriptFiles: {
|
|
32
|
+
path: string;
|
|
33
|
+
exists: boolean;
|
|
34
|
+
version: 'current' | 'stale' | 'unknown';
|
|
35
|
+
}[];
|
|
36
|
+
}
|
|
37
|
+
/** Read-only look at .claude/ in projectDir — never throws. */
|
|
38
|
+
export declare function inspectAmbientHooks(projectDir: string): HookInspection;
|
|
39
|
+
/** Ambient hooks wired, scripts present, version current. */
|
|
40
|
+
export declare function checkHooks(inspection: HookInspection): DoctorCheck;
|
|
41
|
+
/** Net-token ledger readable + backoff state. */
|
|
42
|
+
export declare function checkLedger(entries: TurnRecord[], ledgerPath: string): DoctorCheck;
|
|
43
|
+
/** Render the report; deterministic, no colors (CI-friendly). */
|
|
44
|
+
export declare function renderDoctorReport(checks: DoctorCheck[]): string;
|
|
45
|
+
/** Exit code contract: 1 only on hard failures (warnings stay 0 for CI use). */
|
|
46
|
+
export declare function doctorExitCode(checks: DoctorCheck[]): 0 | 1;
|
|
47
|
+
export {};
|