aiden-runtime 4.8.1 → 4.9.1
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 +88 -1
- package/dist/cli/v4/aidenCLI.js +37 -6
- package/dist/cli/v4/chatSession.js +53 -13
- package/dist/cli/v4/commands/daemon.js +53 -3
- package/dist/cli/v4/commands/daemonDoctor.js +212 -0
- package/dist/cli/v4/commands/daemonStatus.js +45 -26
- package/dist/cli/v4/commands/help.js +5 -0
- package/dist/cli/v4/commands/hooks.js +466 -0
- package/dist/cli/v4/commands/hooksSlash.js +33 -0
- package/dist/cli/v4/commands/index.js +13 -1
- package/dist/cli/v4/commands/mcp.js +89 -1
- package/dist/cli/v4/commands/mcpClientInstall.js +359 -0
- package/dist/cli/v4/commands/memory.js +707 -0
- package/dist/cli/v4/commands/memorySlash.js +38 -0
- package/dist/cli/v4/commands/recovery.js +1 -1
- package/dist/cli/v4/commands/skin.js +7 -0
- package/dist/cli/v4/commands/theme.js +217 -0
- package/dist/cli/v4/commands/trigger.js +1 -1
- package/dist/cli/v4/design/tokens.js +52 -4
- package/dist/cli/v4/display.js +39 -26
- package/dist/cli/v4/replyRenderer.js +6 -5
- package/dist/cli/v4/skinEngine.js +67 -0
- package/dist/cli/v4/ui/progressBar.js +179 -0
- package/dist/cli/v4/util/closestAction.js +48 -0
- package/dist/core/v4/aidenAgent.js +45 -2
- package/dist/core/v4/daemon/api/runs.js +131 -0
- package/dist/core/v4/daemon/bootstrap.js +368 -13
- package/dist/core/v4/daemon/db/migrations.js +169 -0
- package/dist/core/v4/daemon/idempotency/runIdempotencyStore.js +128 -0
- package/dist/core/v4/daemon/incarnationStore.js +47 -0
- package/dist/core/v4/daemon/runs/attemptStore.js +67 -0
- package/dist/core/v4/daemon/runs/reclaim.js +88 -0
- package/dist/core/v4/daemon/runs/retryPolicy.js +73 -0
- package/dist/core/v4/daemon/runs/runWithRetry.js +80 -0
- package/dist/core/v4/daemon/runs/stuckAttemptWatchdog.js +72 -0
- package/dist/core/v4/daemon/spans/spanHelpers.js +272 -0
- package/dist/core/v4/daemon/spans/spanStore.js +113 -0
- package/dist/core/v4/daemon/triggerBus.js +50 -19
- package/dist/core/v4/hooks/auditQuery.js +67 -0
- package/dist/core/v4/hooks/dispatcher.js +286 -0
- package/dist/core/v4/hooks/index.js +46 -0
- package/dist/core/v4/hooks/lifecycle.js +27 -0
- package/dist/core/v4/hooks/manifest.js +142 -0
- package/dist/core/v4/hooks/registry.js +149 -0
- package/dist/core/v4/hooks/runtime/subprocessRunner.js +188 -0
- package/dist/core/v4/hooks/toolHookGate.js +76 -0
- package/dist/core/v4/hooks/trust.js +14 -0
- package/dist/core/v4/identity/contextManager.js +83 -0
- package/dist/core/v4/identity/daemonId.js +85 -0
- package/dist/core/v4/identity/enforcement.js +103 -0
- package/dist/core/v4/identity/executionContext.js +153 -0
- package/dist/core/v4/identity/hookExecution.js +62 -0
- package/dist/core/v4/identity/httpContext.js +68 -0
- package/dist/core/v4/identity/ids.js +185 -0
- package/dist/core/v4/identity/index.js +60 -0
- package/dist/core/v4/identity/subprocessContext.js +98 -0
- package/dist/core/v4/identity/traceparent.js +114 -0
- package/dist/core/v4/logger/index.js +3 -1
- package/dist/core/v4/logger/logger.js +28 -1
- package/dist/core/v4/logger/redact.js +149 -0
- package/dist/core/v4/logger/sinks/fileSink.js +13 -0
- package/dist/core/v4/logger/sinks/stdSink.js +19 -1
- package/dist/core/v4/mcp/install/backup.js +78 -0
- package/dist/core/v4/mcp/install/clientPaths.js +90 -0
- package/dist/core/v4/mcp/install/clients.js +203 -0
- package/dist/core/v4/mcp/install/healthCheck.js +83 -0
- package/dist/core/v4/mcp/install/jsoncMerge.js +174 -0
- package/dist/core/v4/mcp/install/profiles.js +109 -0
- package/dist/core/v4/mcp/install/wslDetect.js +62 -0
- package/dist/core/v4/memory/namespaceRegistry.js +117 -0
- package/dist/core/v4/memory/projectRoot.js +76 -0
- package/dist/core/v4/memory/reviewer/index.js +162 -0
- package/dist/core/v4/memory/reviewer/pendingStore.js +136 -0
- package/dist/core/v4/memory/reviewer/prompt.js +105 -0
- package/dist/core/v4/memory/reviewer/skipRules.js +92 -0
- package/dist/core/v4/memoryManager.js +57 -10
- package/dist/core/v4/paths.js +2 -0
- package/dist/core/v4/subagent/spawnSubAgent.js +20 -7
- package/dist/core/v4/theme/bundledThemes.js +106 -0
- package/dist/core/v4/theme/themeLoader.js +160 -0
- package/dist/core/v4/theme/themeRegistry.js +97 -0
- package/dist/core/v4/theme/themeWatcher.js +95 -0
- package/dist/core/v4/toolRegistry.js +71 -8
- package/dist/core/v4/update/depWarningFilter.js +76 -0
- package/dist/core/v4/update/executeInstall.js +41 -35
- package/dist/core/v4/update/platformInstructions.js +128 -0
- package/dist/moat/approvalEngine.js +4 -0
- package/dist/moat/memoryGuard.js +8 -1
- package/dist/providers/v4/anthropicAdapter.js +10 -4
- package/dist/tools/v4/backends/local.js +19 -2
- package/dist/tools/v4/sessions/recallSession.js +6 -1
- package/package.json +3 -1
- package/plugins/aiden-plugin-chatgpt-plus/index.js +10 -1
- package/themes/default.yaml +52 -0
- package/themes/dracula.yaml +32 -0
- package/themes/light.yaml +32 -0
- package/themes/monochrome.yaml +31 -0
- package/themes/tokyo-night.yaml +32 -0
- package/dist/core/pluginSystem.js +0 -121
- package/dist/tools/v4/ui/_uiSmokeTool.js +0 -60
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/commands/memory.ts — v4.9.0 Slice 9.
|
|
10
|
+
*
|
|
11
|
+
* User-facing CLI for the existing `MemoryManager` + `MemoryGuard`. Does
|
|
12
|
+
* NOT introduce a new memory engine; just exposes what's already there
|
|
13
|
+
* with substrate-aware spans, atomic backups, and a `--json` surface.
|
|
14
|
+
*
|
|
15
|
+
* aiden memory alias for `list`
|
|
16
|
+
* aiden memory list both files + char counts
|
|
17
|
+
* aiden memory show <memory|user> cat with line numbers
|
|
18
|
+
* aiden memory add <memory|user> "<text>" append entry
|
|
19
|
+
* aiden memory remove <memory|user> --match "<substr>" remove first match
|
|
20
|
+
* aiden memory backup snapshot to memory-backups/<ts>/
|
|
21
|
+
* aiden memory restore <timestamp> restore from snapshot
|
|
22
|
+
* aiden memory edit <memory|user> print path (user opens in editor)
|
|
23
|
+
* aiden memory diff current vs most-recent backup
|
|
24
|
+
*
|
|
25
|
+
* Flags: `--json` (machine-parseable) ; `--yes` (skip confirms).
|
|
26
|
+
*/
|
|
27
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
30
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
31
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
32
|
+
}
|
|
33
|
+
Object.defineProperty(o, k2, desc);
|
|
34
|
+
}) : (function(o, m, k, k2) {
|
|
35
|
+
if (k2 === undefined) k2 = k;
|
|
36
|
+
o[k2] = m[k];
|
|
37
|
+
}));
|
|
38
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
39
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
40
|
+
}) : function(o, v) {
|
|
41
|
+
o["default"] = v;
|
|
42
|
+
});
|
|
43
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
44
|
+
var ownKeys = function(o) {
|
|
45
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
46
|
+
var ar = [];
|
|
47
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
48
|
+
return ar;
|
|
49
|
+
};
|
|
50
|
+
return ownKeys(o);
|
|
51
|
+
};
|
|
52
|
+
return function (mod) {
|
|
53
|
+
if (mod && mod.__esModule) return mod;
|
|
54
|
+
var result = {};
|
|
55
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
56
|
+
__setModuleDefault(result, mod);
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
})();
|
|
60
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
61
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
62
|
+
};
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.runMemorySubcommand = runMemorySubcommand;
|
|
65
|
+
const node_fs_1 = require("node:fs");
|
|
66
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
67
|
+
const node_crypto_1 = require("node:crypto");
|
|
68
|
+
const paths_1 = require("../../../core/v4/paths");
|
|
69
|
+
const memoryManager_1 = require("../../../core/v4/memoryManager");
|
|
70
|
+
const memoryGuard_1 = require("../../../moat/memoryGuard");
|
|
71
|
+
// v4.9.0 Slice 11 — namespace registry + project root detection.
|
|
72
|
+
const namespaceRegistry_1 = require("../../../core/v4/memory/namespaceRegistry");
|
|
73
|
+
const projectRoot_1 = require("../../../core/v4/memory/projectRoot");
|
|
74
|
+
const identity_1 = require("../../../core/v4/identity");
|
|
75
|
+
const spanHelpers_1 = require("../../../core/v4/daemon/spans/spanHelpers");
|
|
76
|
+
const bootstrap_1 = require("../../../core/v4/daemon/bootstrap");
|
|
77
|
+
const noopOut = (s) => { process.stdout.write(s); };
|
|
78
|
+
const noopErr = (s) => { process.stderr.write(s); };
|
|
79
|
+
/**
|
|
80
|
+
* v4.9.0 Slice 11 — validate against the dynamic namespace registry
|
|
81
|
+
* rather than the hard-coded `'memory' | 'user'` pair. Return type
|
|
82
|
+
* widened to `string` so the caller can pass any registered name
|
|
83
|
+
* through MemoryManager / MemoryGuard (whose method signatures were
|
|
84
|
+
* widened in the same slice).
|
|
85
|
+
*/
|
|
86
|
+
function parseFile(raw) {
|
|
87
|
+
if (!raw)
|
|
88
|
+
return null;
|
|
89
|
+
return (0, namespaceRegistry_1.hasNamespace)(raw) ? raw : null;
|
|
90
|
+
}
|
|
91
|
+
function limitFor(f) {
|
|
92
|
+
if ((0, namespaceRegistry_1.hasNamespace)(f))
|
|
93
|
+
return (0, namespaceRegistry_1.getNamespace)(f).charLimit;
|
|
94
|
+
// Defensive fallback — should never hit in practice.
|
|
95
|
+
return f === 'user' ? memoryManager_1.USER_CHAR_LIMIT : memoryManager_1.MEMORY_CHAR_LIMIT;
|
|
96
|
+
}
|
|
97
|
+
/** Build a minimal ExecutionContext for a one-shot CLI invocation. */
|
|
98
|
+
function buildCliCtx(opName) {
|
|
99
|
+
return {
|
|
100
|
+
daemonId: (0, bootstrap_1.getCurrentDaemonId)() ?? 'dmn_cli_oneshot00000000000000000000',
|
|
101
|
+
incarnationId: (0, bootstrap_1.getCurrentIncarnationId)() ?? 'inc_cli_oneshot00000000000000000000',
|
|
102
|
+
runId: (0, identity_1.newRunId)(),
|
|
103
|
+
traceId: (0, identity_1.newTraceId)(),
|
|
104
|
+
spanId: (0, identity_1.newSpanId)(),
|
|
105
|
+
source: 'cli',
|
|
106
|
+
attempt: 1,
|
|
107
|
+
baggage: { op: opName },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Wrap a memory mutation in a span; return the span_id (`mem_...` synonym).
|
|
111
|
+
* v4.9.0 Slice 11 — `file` widened to `string` to accept any namespace. */
|
|
112
|
+
async function withMemorySpan(ctx, opName, file, fn) {
|
|
113
|
+
const memoryId = (0, identity_1.newMemoryId)();
|
|
114
|
+
const db = (0, bootstrap_1.getCurrentDaemonDb)();
|
|
115
|
+
if (!db) {
|
|
116
|
+
// Daemon not booted (common for one-shot CLI). Skip the span and
|
|
117
|
+
// still return the mem_ id so the response shape is stable.
|
|
118
|
+
const value = await fn(memoryId);
|
|
119
|
+
return { memoryId, value };
|
|
120
|
+
}
|
|
121
|
+
const value = await (0, identity_1.runWithContext)(ctx, () => (0, spanHelpers_1.withSpan)(db, { kind: 'memory', name: `memory_${opName}`, attrs: { file, memory_id: memoryId } }, async () => fn(memoryId)));
|
|
122
|
+
return { memoryId, value };
|
|
123
|
+
}
|
|
124
|
+
/** Entry — `aiden memory <action> [args...]`. Returns desired process exit. */
|
|
125
|
+
async function runMemorySubcommand(action, args, opts = {}) {
|
|
126
|
+
const out = opts.writeOut ?? noopOut;
|
|
127
|
+
const err = opts.writeErr ?? noopErr;
|
|
128
|
+
const json = args.includes('--json');
|
|
129
|
+
const yes = args.includes('--yes');
|
|
130
|
+
// Strip flags from positional args.
|
|
131
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
132
|
+
const paths = (0, paths_1.resolveAidenPaths)(opts.rootDir ? { rootOverride: opts.rootDir } : {});
|
|
133
|
+
// v4.9.0 Slice 11 — detect project root from cwd so the `project`
|
|
134
|
+
// namespace can resolve its file path. `null` is fine — the
|
|
135
|
+
// namespace's resolver throws on access, the CLI catches + surfaces
|
|
136
|
+
// a helpful message instead of routing the error to the user.
|
|
137
|
+
const projectRoot = (0, projectRoot_1.findProjectRoot)(process.cwd());
|
|
138
|
+
const mgr = new memoryManager_1.MemoryManager({ paths, projectRoot });
|
|
139
|
+
const guard = new memoryGuard_1.MemoryGuard(mgr);
|
|
140
|
+
const effective = action || 'list';
|
|
141
|
+
switch (effective) {
|
|
142
|
+
case 'list': return cmdList(mgr, paths, out, json);
|
|
143
|
+
case 'show': return cmdShow(positional[0], paths, out, err, json);
|
|
144
|
+
case 'add': return cmdAdd(positional[0], positional[1], guard, mgr, paths, out, err, json);
|
|
145
|
+
case 'remove': return cmdRemove(positional[0], args, guard, out, err, json);
|
|
146
|
+
case 'edit': return cmdEdit(positional[0], paths, out, err);
|
|
147
|
+
case 'backup': return cmdBackup(paths, out, err, json);
|
|
148
|
+
case 'restore': return cmdRestore(positional[0], paths, out, err, json, yes);
|
|
149
|
+
case 'diff': return cmdDiff(paths, out, err);
|
|
150
|
+
// v4.9.0 Slice 10 — post-turn reviewer surface.
|
|
151
|
+
case 'namespaces': return cmdNamespaces(paths, projectRoot, out, json);
|
|
152
|
+
case 'pending': return cmdPending(paths, out, json);
|
|
153
|
+
case 'approve': return cmdApprove(positional[0], args, paths, guard, mgr, out, err, json);
|
|
154
|
+
case 'reject': return cmdReject(positional[0], args, paths, out, err, json);
|
|
155
|
+
case 'review': return cmdReview(args, paths, opts, out, err, json);
|
|
156
|
+
case '--help':
|
|
157
|
+
case 'help': return cmdHelp(out);
|
|
158
|
+
default: {
|
|
159
|
+
err(`Unknown memory action: ${effective}\n`);
|
|
160
|
+
const { closestAction } = await Promise.resolve().then(() => __importStar(require('../util/closestAction')));
|
|
161
|
+
const m = closestAction(effective, ['list', 'show', 'add', 'remove', 'edit', 'backup', 'restore', 'diff', 'namespaces', 'pending', 'approve', 'reject', 'review']);
|
|
162
|
+
if (m)
|
|
163
|
+
err(`Did you mean: ${m}?\n\n`);
|
|
164
|
+
cmdHelp(err);
|
|
165
|
+
return 2;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function cmdHelp(write) {
|
|
170
|
+
write('Usage: aiden memory <action> [args...] [--json] [--yes]\n\n' +
|
|
171
|
+
'Manage Aiden\'s MEMORY.md (~2200 chars) and USER.md (~1375 chars).\n\n' +
|
|
172
|
+
'Actions:\n' +
|
|
173
|
+
' list Show both files with char counts (default).\n' +
|
|
174
|
+
' show <memory|user> Cat the file with line numbers.\n' +
|
|
175
|
+
' add <memory|user> "<text>" Append an entry (MemoryGuard verified).\n' +
|
|
176
|
+
' remove <memory|user> --match "<s>" Remove the unique entry containing <s>.\n' +
|
|
177
|
+
' edit <memory|user> Print path so you can open in $EDITOR.\n' +
|
|
178
|
+
' backup Snapshot both files to memory-backups/<ts>/.\n' +
|
|
179
|
+
' restore <timestamp> Restore both files from a snapshot.\n' +
|
|
180
|
+
' diff Diff current state against latest backup.\n' +
|
|
181
|
+
' namespaces List registered memory namespaces.\n' +
|
|
182
|
+
' pending List candidates from `## Pending review` blocks.\n' +
|
|
183
|
+
' approve <mem_id> | --all Promote a pending candidate to a live entry.\n' +
|
|
184
|
+
' reject <mem_id> | --all Discard a pending candidate (logged).\n' +
|
|
185
|
+
' review --now | --status Run / inspect the post-turn memory reviewer.\n');
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
async function cmdList(mgr, paths, out, json) {
|
|
189
|
+
// v4.9.0 Slice 11 — iterate every registered namespace via the
|
|
190
|
+
// snapshot's `files` map. Namespaces requiring a project root (e.g.
|
|
191
|
+
// `project`) that aren't reachable from cwd are silently absent —
|
|
192
|
+
// matches `loadSnapshot` behaviour.
|
|
193
|
+
const snap = await mgr.loadSnapshot();
|
|
194
|
+
const files = snap.files ?? {};
|
|
195
|
+
if (json) {
|
|
196
|
+
const payload = {};
|
|
197
|
+
for (const ns of (0, namespaceRegistry_1.listNamespaces)()) {
|
|
198
|
+
const f = files[ns.name];
|
|
199
|
+
if (!f)
|
|
200
|
+
continue;
|
|
201
|
+
payload[ns.name] = { path: f.path, chars: f.charCount, limit: f.charLimit, entries: splitEntries(f.content) };
|
|
202
|
+
}
|
|
203
|
+
out(JSON.stringify(payload, null, 2) + '\n');
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
// Human format: one summary line per available namespace. Slice 9
|
|
207
|
+
// formatted as "memory: ...", "user: ..." with the value column
|
|
208
|
+
// at offset 8. Match that, with min 1 space for longer names.
|
|
209
|
+
for (const ns of (0, namespaceRegistry_1.listNamespaces)()) {
|
|
210
|
+
const f = files[ns.name];
|
|
211
|
+
if (!f)
|
|
212
|
+
continue;
|
|
213
|
+
const pad = ' '.repeat(Math.max(1, 8 - (ns.name.length + 1)));
|
|
214
|
+
out(`${ns.name}:${pad}${f.charCount} / ${f.charLimit} chars (${f.path})\n`);
|
|
215
|
+
}
|
|
216
|
+
for (const ns of (0, namespaceRegistry_1.listNamespaces)()) {
|
|
217
|
+
const f = files[ns.name];
|
|
218
|
+
if (!f)
|
|
219
|
+
continue;
|
|
220
|
+
const entries = splitEntries(f.content);
|
|
221
|
+
if (entries.length > 0) {
|
|
222
|
+
out(`\n--- ${ns.name} ---\n`);
|
|
223
|
+
entries.forEach((e, i) => out(` ${i + 1}. ${e}\n`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
async function cmdNamespaces(paths, projectRoot, out, json) {
|
|
229
|
+
const rows = (0, namespaceRegistry_1.listNamespaces)().map((ns) => {
|
|
230
|
+
let resolvedPath = null;
|
|
231
|
+
let available = true;
|
|
232
|
+
let reason;
|
|
233
|
+
try {
|
|
234
|
+
resolvedPath = ns.resolve(paths, projectRoot);
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
available = false;
|
|
238
|
+
reason = e instanceof Error ? e.message : String(e);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
name: ns.name, label: ns.label, description: ns.description,
|
|
242
|
+
charLimit: ns.charLimit, requiresProject: !!ns.requiresProject,
|
|
243
|
+
injectIntoPrompt: ns.injectIntoPrompt, available, path: resolvedPath, reason,
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
if (json) {
|
|
247
|
+
out(JSON.stringify({ projectRoot, namespaces: rows }, null, 2) + '\n');
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
out(`project root: ${projectRoot ?? '(none detected)'}\n\n`);
|
|
251
|
+
for (const r of rows) {
|
|
252
|
+
const status = r.available ? `ok path=${r.path}` : `requires project root — ${r.reason}`;
|
|
253
|
+
out(` ${r.name.padEnd(8)} (${r.charLimit} chars) ${status}\n ${r.description}\n`);
|
|
254
|
+
}
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
async function cmdShow(fileRaw, paths, out, err, json) {
|
|
258
|
+
const file = parseFile(fileRaw);
|
|
259
|
+
if (!file) {
|
|
260
|
+
err('show: pass `memory` or `user`\n');
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
// v4.9.0 Slice 11 — resolve via registry (supports project namespace).
|
|
264
|
+
let target;
|
|
265
|
+
try {
|
|
266
|
+
target = (0, namespaceRegistry_1.getNamespace)(file).resolve(paths, (0, projectRoot_1.findProjectRoot)(process.cwd()));
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
err(`${file}: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
270
|
+
return 1;
|
|
271
|
+
}
|
|
272
|
+
let text = '';
|
|
273
|
+
try {
|
|
274
|
+
text = await node_fs_1.promises.readFile(target, 'utf8');
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
if (e.code !== 'ENOENT')
|
|
278
|
+
throw e;
|
|
279
|
+
}
|
|
280
|
+
if (json) {
|
|
281
|
+
out(JSON.stringify({ file, path: target, content: text }, null, 2) + '\n');
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
out(`# ${target}\n`);
|
|
285
|
+
text.split('\n').forEach((line, i) => out(`${String(i + 1).padStart(4, ' ')} | ${line}\n`));
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
async function cmdAdd(fileRaw, content, guard, mgr, paths, out, err, json) {
|
|
289
|
+
const file = parseFile(fileRaw);
|
|
290
|
+
if (!file) {
|
|
291
|
+
err('add: pass `memory` or `user`\n');
|
|
292
|
+
return 2;
|
|
293
|
+
}
|
|
294
|
+
if (!content) {
|
|
295
|
+
err('add: missing entry text\n');
|
|
296
|
+
return 2;
|
|
297
|
+
}
|
|
298
|
+
const ctx = buildCliCtx('memory_add');
|
|
299
|
+
// Ensure parent dir exists (first-run convenience).
|
|
300
|
+
// v4.9.0 Slice 11 — ensure parent dir exists via registry-resolved path.
|
|
301
|
+
let nsPath;
|
|
302
|
+
try {
|
|
303
|
+
nsPath = (0, namespaceRegistry_1.getNamespace)(file).resolve(paths, (0, projectRoot_1.findProjectRoot)(process.cwd()));
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
err(`${file}: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
307
|
+
return 1;
|
|
308
|
+
}
|
|
309
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(nsPath), { recursive: true });
|
|
310
|
+
const { memoryId, value: result } = await withMemorySpan(ctx, 'add', file, async () => guard.guardedAdd(file, content));
|
|
311
|
+
if (!result.ok || !result.verified) {
|
|
312
|
+
if (json) {
|
|
313
|
+
out(JSON.stringify({ ok: false, reason: result.reason, mem_id: memoryId }) + '\n');
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
err(`add failed: ${result.reason ?? 'unknown'}\n`);
|
|
317
|
+
}
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
const snap = await mgr.loadSnapshot();
|
|
321
|
+
// v4.9.0 Slice 11 — read post-write length from the generalized
|
|
322
|
+
// files map (`memory`/`user` keys still populated for back-compat).
|
|
323
|
+
const len = (snap.files?.[file]?.content
|
|
324
|
+
?? (file === 'user' ? snap.userMd : snap.memoryMd) ?? '').length;
|
|
325
|
+
if (json) {
|
|
326
|
+
out(JSON.stringify({ ok: true, file, mem_id: memoryId, chars: len, limit: limitFor(file) }) + '\n');
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
out(`added to ${file} (now ${len} / ${limitFor(file)} chars) mem_id=${memoryId}\n`);
|
|
330
|
+
}
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
async function cmdRemove(fileRaw, args, guard, out, err, json) {
|
|
334
|
+
const file = parseFile(fileRaw);
|
|
335
|
+
if (!file) {
|
|
336
|
+
err('remove: pass `memory` or `user`\n');
|
|
337
|
+
return 2;
|
|
338
|
+
}
|
|
339
|
+
const matchIdx = args.findIndex((a) => a === '--match');
|
|
340
|
+
if (matchIdx < 0 || matchIdx + 1 >= args.length) {
|
|
341
|
+
err('remove: pass --match "<substring>"\n');
|
|
342
|
+
return 2;
|
|
343
|
+
}
|
|
344
|
+
const target = args[matchIdx + 1];
|
|
345
|
+
const ctx = buildCliCtx('memory_remove');
|
|
346
|
+
const { memoryId, value: result } = await withMemorySpan(ctx, 'remove', file, async () => guard.guardedRemove(file, target));
|
|
347
|
+
if (!result.ok || !result.verified) {
|
|
348
|
+
if (json) {
|
|
349
|
+
out(JSON.stringify({ ok: false, reason: result.reason, mem_id: memoryId }) + '\n');
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
err(`remove failed: ${result.reason ?? 'unknown'}\n`);
|
|
353
|
+
}
|
|
354
|
+
return 1;
|
|
355
|
+
}
|
|
356
|
+
if (json) {
|
|
357
|
+
out(JSON.stringify({ ok: true, file, mem_id: memoryId, match: target }) + '\n');
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
out(`removed entry containing "${target}" from ${file} mem_id=${memoryId}\n`);
|
|
361
|
+
}
|
|
362
|
+
return 0;
|
|
363
|
+
}
|
|
364
|
+
async function cmdEdit(fileRaw, paths, out, err) {
|
|
365
|
+
const file = parseFile(fileRaw);
|
|
366
|
+
if (!file) {
|
|
367
|
+
err('edit: pass `memory` or `user`\n');
|
|
368
|
+
return 2;
|
|
369
|
+
}
|
|
370
|
+
// v4.9.0 Slice 11 — resolve via registry (supports project namespace).
|
|
371
|
+
let target;
|
|
372
|
+
try {
|
|
373
|
+
target = (0, namespaceRegistry_1.getNamespace)(file).resolve(paths, (0, projectRoot_1.findProjectRoot)(process.cwd()));
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
err(`${file}: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
377
|
+
return 1;
|
|
378
|
+
}
|
|
379
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(target), { recursive: true });
|
|
380
|
+
try {
|
|
381
|
+
await node_fs_1.promises.access(target);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
await node_fs_1.promises.writeFile(target, '', 'utf8');
|
|
385
|
+
}
|
|
386
|
+
out(`${target}\n`);
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
function sha256(s) {
|
|
390
|
+
return (0, node_crypto_1.createHash)('sha256').update(s).digest('hex');
|
|
391
|
+
}
|
|
392
|
+
function ts() {
|
|
393
|
+
const d = new Date();
|
|
394
|
+
const pad = (n, w = 2) => String(n).padStart(w, '0');
|
|
395
|
+
return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}-${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}`;
|
|
396
|
+
}
|
|
397
|
+
async function cmdBackup(paths, out, _err, json) {
|
|
398
|
+
const ctx = buildCliCtx('memory_backup');
|
|
399
|
+
const stamp = ts();
|
|
400
|
+
const dir = node_path_1.default.join(paths.memoryBackupsDir, stamp);
|
|
401
|
+
await node_fs_1.promises.mkdir(dir, { recursive: true });
|
|
402
|
+
// v4.9.0 Slice 11 — snapshot every reachable namespace.
|
|
403
|
+
const projectRoot = (0, projectRoot_1.findProjectRoot)(process.cwd());
|
|
404
|
+
const snapshots = [];
|
|
405
|
+
const { memoryId } = await withMemorySpan(ctx, 'backup', 'memory', async () => {
|
|
406
|
+
for (const ns of (0, namespaceRegistry_1.listNamespaces)()) {
|
|
407
|
+
let srcPath;
|
|
408
|
+
try {
|
|
409
|
+
srcPath = ns.resolve(paths, projectRoot);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
continue; /* requiresProject + no root → skip */
|
|
413
|
+
}
|
|
414
|
+
const text = await node_fs_1.promises.readFile(srcPath, 'utf8').catch(() => '');
|
|
415
|
+
const outName = `${ns.name}.md`;
|
|
416
|
+
await node_fs_1.promises.writeFile(node_path_1.default.join(dir, outName), text, 'utf8');
|
|
417
|
+
snapshots.push({ name: outName, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256(text) });
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
});
|
|
421
|
+
const manifest = {
|
|
422
|
+
timestamp: stamp,
|
|
423
|
+
daemonId: (0, bootstrap_1.getCurrentDaemonId)(),
|
|
424
|
+
incarnationId: (0, bootstrap_1.getCurrentIncarnationId)(),
|
|
425
|
+
spanId: memoryId,
|
|
426
|
+
files: snapshots,
|
|
427
|
+
};
|
|
428
|
+
await node_fs_1.promises.writeFile(node_path_1.default.join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
429
|
+
if (json) {
|
|
430
|
+
out(JSON.stringify({ ok: true, dir, manifest }, null, 2) + '\n');
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
out(`backup: ${dir}\n`);
|
|
434
|
+
for (const f of manifest.files)
|
|
435
|
+
out(` ${f.name.padEnd(12)} ${f.bytes} B\n`);
|
|
436
|
+
out(` mem_id=${memoryId}\n`);
|
|
437
|
+
}
|
|
438
|
+
return 0;
|
|
439
|
+
}
|
|
440
|
+
async function cmdRestore(stampRaw, paths, out, err, json, _yes) {
|
|
441
|
+
if (!stampRaw) {
|
|
442
|
+
err('restore: pass <timestamp> (use `aiden memory backup` first, then `ls memory-backups/`)\n');
|
|
443
|
+
return 2;
|
|
444
|
+
}
|
|
445
|
+
const dir = node_path_1.default.join(paths.memoryBackupsDir, stampRaw);
|
|
446
|
+
try {
|
|
447
|
+
await node_fs_1.promises.access(dir);
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
err(`restore: backup not found: ${dir}\n`);
|
|
451
|
+
return 1;
|
|
452
|
+
}
|
|
453
|
+
const ctx = buildCliCtx('memory_restore');
|
|
454
|
+
// v4.9.0 Slice 11 — restore every namespace file that exists in the
|
|
455
|
+
// backup. Namespace files missing from the backup are skipped (not
|
|
456
|
+
// an error — older snapshots may pre-date the namespace).
|
|
457
|
+
const projectRoot = (0, projectRoot_1.findProjectRoot)(process.cwd());
|
|
458
|
+
const restored = [];
|
|
459
|
+
const { memoryId } = await withMemorySpan(ctx, 'restore', 'memory', async () => {
|
|
460
|
+
for (const ns of (0, namespaceRegistry_1.listNamespaces)()) {
|
|
461
|
+
let destPath;
|
|
462
|
+
try {
|
|
463
|
+
destPath = ns.resolve(paths, projectRoot);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
continue; /* requiresProject + no root → skip */
|
|
467
|
+
}
|
|
468
|
+
const src = node_path_1.default.join(dir, `${ns.name}.md`);
|
|
469
|
+
const text = await node_fs_1.promises.readFile(src, 'utf8').catch(() => null);
|
|
470
|
+
if (text === null)
|
|
471
|
+
continue;
|
|
472
|
+
await node_fs_1.promises.mkdir(node_path_1.default.dirname(destPath), { recursive: true });
|
|
473
|
+
await node_fs_1.promises.writeFile(destPath, text, 'utf8');
|
|
474
|
+
restored.push({ name: ns.name, chars: text.length });
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
});
|
|
478
|
+
if (json) {
|
|
479
|
+
out(JSON.stringify({ ok: true, restored_from: dir, restored, mem_id: memoryId }) + '\n');
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
out(`restored from ${dir}\n`);
|
|
483
|
+
for (const r of restored)
|
|
484
|
+
out(` ${r.name.padEnd(10)} ${r.chars} chars\n`);
|
|
485
|
+
out(` mem_id=${memoryId}\n`);
|
|
486
|
+
}
|
|
487
|
+
return 0;
|
|
488
|
+
}
|
|
489
|
+
async function cmdDiff(paths, out, err) {
|
|
490
|
+
let entries;
|
|
491
|
+
try {
|
|
492
|
+
entries = await node_fs_1.promises.readdir(paths.memoryBackupsDir);
|
|
493
|
+
}
|
|
494
|
+
catch {
|
|
495
|
+
err('diff: no backups exist yet (run `aiden memory backup` first)\n');
|
|
496
|
+
return 1;
|
|
497
|
+
}
|
|
498
|
+
const sorted = entries.filter((e) => /^\d{8}-\d{6}$/.test(e)).sort();
|
|
499
|
+
if (sorted.length === 0) {
|
|
500
|
+
err('diff: no backups found\n');
|
|
501
|
+
return 1;
|
|
502
|
+
}
|
|
503
|
+
const latest = sorted[sorted.length - 1];
|
|
504
|
+
const dir = node_path_1.default.join(paths.memoryBackupsDir, latest);
|
|
505
|
+
const memBefore = await node_fs_1.promises.readFile(node_path_1.default.join(dir, 'memory.md'), 'utf8').catch(() => '');
|
|
506
|
+
const usrBefore = await node_fs_1.promises.readFile(node_path_1.default.join(dir, 'user.md'), 'utf8').catch(() => '');
|
|
507
|
+
const memNow = await node_fs_1.promises.readFile(paths.memoryMd, 'utf8').catch(() => '');
|
|
508
|
+
const usrNow = await node_fs_1.promises.readFile(paths.userMd, 'utf8').catch(() => '');
|
|
509
|
+
out(`diff vs backup ${latest}\n`);
|
|
510
|
+
out(diffSummary('memory.md', memBefore, memNow));
|
|
511
|
+
out(diffSummary('user.md', usrBefore, usrNow));
|
|
512
|
+
return 0;
|
|
513
|
+
}
|
|
514
|
+
function diffSummary(label, before, after) {
|
|
515
|
+
if (before === after)
|
|
516
|
+
return ` ${label}: unchanged\n`;
|
|
517
|
+
const b = splitEntries(before), a = splitEntries(after);
|
|
518
|
+
const bs = new Set(b), as = new Set(a);
|
|
519
|
+
const added = a.filter((e) => !bs.has(e));
|
|
520
|
+
const removed = b.filter((e) => !as.has(e));
|
|
521
|
+
let s = ` ${label}: ${added.length} added, ${removed.length} removed\n`;
|
|
522
|
+
for (const e of added)
|
|
523
|
+
s += ` + ${e}\n`;
|
|
524
|
+
for (const e of removed)
|
|
525
|
+
s += ` - ${e}\n`;
|
|
526
|
+
return s;
|
|
527
|
+
}
|
|
528
|
+
function splitEntries(raw) {
|
|
529
|
+
if (!raw.trim())
|
|
530
|
+
return [];
|
|
531
|
+
return raw.split(memoryManager_1.ENTRY_SEPARATOR).map((e) => e.trim()).filter((e) => e.length > 0);
|
|
532
|
+
}
|
|
533
|
+
// ── v4.9.0 Slice 10 — post-turn reviewer surface ────────────────────────
|
|
534
|
+
const reviewer_1 = require("../../../core/v4/memory/reviewer");
|
|
535
|
+
const pendingStore_1 = require("../../../core/v4/memory/reviewer/pendingStore");
|
|
536
|
+
async function cmdPending(paths, out, json) {
|
|
537
|
+
const pending = await (0, pendingStore_1.listAllPending)(paths.memoryMd, paths.userMd);
|
|
538
|
+
if (json) {
|
|
539
|
+
out(JSON.stringify({ pending }, null, 2) + '\n');
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
if (pending.length === 0) {
|
|
543
|
+
out('no pending candidates\n');
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
out(`${pending.length} pending candidate(s):\n`);
|
|
547
|
+
for (const c of pending) {
|
|
548
|
+
out(` ${c.memId} [${c.file}] ${c.text}\n ↳ ${c.rationale} (proposed ${c.proposedAt})\n`);
|
|
549
|
+
}
|
|
550
|
+
return 0;
|
|
551
|
+
}
|
|
552
|
+
async function cmdApprove(memIdRaw, args, paths, guard, mgr, out, err, json) {
|
|
553
|
+
const all = args.includes('--all');
|
|
554
|
+
const pending = await (0, pendingStore_1.listAllPending)(paths.memoryMd, paths.userMd);
|
|
555
|
+
const targets = all
|
|
556
|
+
? pending
|
|
557
|
+
: (memIdRaw ? pending.filter((c) => c.memId === memIdRaw) : []);
|
|
558
|
+
if (targets.length === 0) {
|
|
559
|
+
if (json) {
|
|
560
|
+
out(JSON.stringify({ ok: false, reason: 'no_match' }) + '\n');
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
err('approve: no pending candidate matched (use `--all` for batch, or pass a mem_id)\n');
|
|
564
|
+
}
|
|
565
|
+
return 1;
|
|
566
|
+
}
|
|
567
|
+
let approved = 0;
|
|
568
|
+
void mgr;
|
|
569
|
+
for (const c of targets) {
|
|
570
|
+
const ctx = buildCliCtx('memory_approve');
|
|
571
|
+
const filePath = c.file === 'user' ? paths.userMd : paths.memoryMd;
|
|
572
|
+
// v4.9.0 Slice 10 — DROP the pending candidate row BEFORE calling
|
|
573
|
+
// guardedAdd. Otherwise MemoryManager.add's substring-dedup sees
|
|
574
|
+
// the candidate text inside the pending block and short-circuits
|
|
575
|
+
// to `deduped: true` without appending a live entry.
|
|
576
|
+
await (0, pendingStore_1.dropCandidate)(filePath, c.memId);
|
|
577
|
+
await withMemorySpan(ctx, 'approve', c.file, async () => guard.guardedAdd(c.file, c.text));
|
|
578
|
+
approved += 1;
|
|
579
|
+
}
|
|
580
|
+
if (json) {
|
|
581
|
+
out(JSON.stringify({ ok: true, approved }) + '\n');
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
out(`approved ${approved} candidate(s)\n`);
|
|
585
|
+
}
|
|
586
|
+
return 0;
|
|
587
|
+
}
|
|
588
|
+
async function cmdReject(memIdRaw, args, paths, out, err, json) {
|
|
589
|
+
const all = args.includes('--all');
|
|
590
|
+
const pending = await (0, pendingStore_1.listAllPending)(paths.memoryMd, paths.userMd);
|
|
591
|
+
const targets = all ? pending : (memIdRaw ? pending.filter((c) => c.memId === memIdRaw) : []);
|
|
592
|
+
if (targets.length === 0) {
|
|
593
|
+
if (json) {
|
|
594
|
+
out(JSON.stringify({ ok: false, reason: 'no_match' }) + '\n');
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
err('reject: no pending candidate matched\n');
|
|
598
|
+
}
|
|
599
|
+
return 1;
|
|
600
|
+
}
|
|
601
|
+
let rejected = 0;
|
|
602
|
+
for (const c of targets) {
|
|
603
|
+
const ctx = buildCliCtx('memory_reject');
|
|
604
|
+
await withMemorySpan(ctx, 'reject', c.file, async () => {
|
|
605
|
+
await (0, pendingStore_1.dropCandidate)(c.file === 'user' ? paths.userMd : paths.memoryMd, c.memId);
|
|
606
|
+
return null;
|
|
607
|
+
});
|
|
608
|
+
rejected += 1;
|
|
609
|
+
}
|
|
610
|
+
if (json) {
|
|
611
|
+
out(JSON.stringify({ ok: true, rejected }) + '\n');
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
out(`rejected ${rejected} candidate(s)\n`);
|
|
615
|
+
}
|
|
616
|
+
return 0;
|
|
617
|
+
}
|
|
618
|
+
async function cmdReview(args, paths, opts, out, err, json) {
|
|
619
|
+
const cfg = {
|
|
620
|
+
enabled: opts.reviewerConfig?.enabled ?? true,
|
|
621
|
+
mode: opts.reviewerConfig?.mode ?? 'on_quit',
|
|
622
|
+
timeoutMs: opts.reviewerConfig?.timeoutMs ?? 30000,
|
|
623
|
+
maxCandidates: opts.reviewerConfig?.maxCandidates ?? 5,
|
|
624
|
+
};
|
|
625
|
+
if (args.includes('--status')) {
|
|
626
|
+
if (json) {
|
|
627
|
+
out(JSON.stringify({ enabled: cfg.enabled, mode: cfg.mode, last_review: null, pending: (await (0, pendingStore_1.listAllPending)(paths.memoryMd, paths.userMd)).length }, null, 2) + '\n');
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const pendingCount = (await (0, pendingStore_1.listAllPending)(paths.memoryMd, paths.userMd)).length;
|
|
631
|
+
out(`review enabled: ${cfg.enabled}, mode: ${cfg.mode}, last review: never, pending: ${pendingCount}\n`);
|
|
632
|
+
}
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
if (!args.includes('--now')) {
|
|
636
|
+
err('review: pass --now to trigger a pass, or --status to inspect config\n');
|
|
637
|
+
return 2;
|
|
638
|
+
}
|
|
639
|
+
if (!cfg.enabled || cfg.mode === 'off') {
|
|
640
|
+
if (json) {
|
|
641
|
+
out(JSON.stringify({ outcome: 'disabled', reason: cfg.mode === 'off' ? 'mode_off' : 'config_disabled' }) + '\n');
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
out('review disabled (config: memory.review.enabled=false or mode=off)\n');
|
|
645
|
+
}
|
|
646
|
+
return 0;
|
|
647
|
+
}
|
|
648
|
+
if (!opts.reviewerCallLLM) {
|
|
649
|
+
if (json) {
|
|
650
|
+
out(JSON.stringify({ outcome: 'no_llm_configured' }) + '\n');
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
err('review --now: no LLM callback wired (the CLI binding must inject reviewerCallLLM)\n');
|
|
654
|
+
}
|
|
655
|
+
return 1;
|
|
656
|
+
}
|
|
657
|
+
const recentTurns = opts.reviewerRecentTurns
|
|
658
|
+
? await opts.reviewerRecentTurns()
|
|
659
|
+
: [];
|
|
660
|
+
const liveMemoryRaw = await Promise.resolve().then(() => __importStar(require('node:fs'))).then((m) => m.promises.readFile(paths.memoryMd, 'utf8').catch(() => ''));
|
|
661
|
+
const liveUserRaw = await Promise.resolve().then(() => __importStar(require('node:fs'))).then((m) => m.promises.readFile(paths.userMd, 'utf8').catch(() => ''));
|
|
662
|
+
// Wrap the review in a memory span so doctor / spans table track it.
|
|
663
|
+
const ctx = buildCliCtx('memory_review');
|
|
664
|
+
let outcome = null;
|
|
665
|
+
await withMemorySpan(ctx, 'review', 'memory', async () => {
|
|
666
|
+
outcome = await (0, reviewer_1.runReview)({
|
|
667
|
+
recentTurns, liveMemoryRaw, liveUserRaw,
|
|
668
|
+
memoryPath: paths.memoryMd, userPath: paths.userMd,
|
|
669
|
+
// v4.9.0 Slice 11 — pass paths + projectRoot so the reviewer
|
|
670
|
+
// can route `project`-namespace candidates to the right file
|
|
671
|
+
// when a project root is detected.
|
|
672
|
+
paths, projectRoot: (0, projectRoot_1.findProjectRoot)(process.cwd()),
|
|
673
|
+
callLLM: opts.reviewerCallLLM,
|
|
674
|
+
maxCandidates: cfg.maxCandidates,
|
|
675
|
+
timeoutMs: cfg.timeoutMs,
|
|
676
|
+
log: (level, msg) => {
|
|
677
|
+
if (level === 'error')
|
|
678
|
+
err(msg + '\n');
|
|
679
|
+
else
|
|
680
|
+
out(msg + '\n');
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
return outcome;
|
|
684
|
+
});
|
|
685
|
+
if (json) {
|
|
686
|
+
out(JSON.stringify(outcome, null, 2) + '\n');
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
const o = outcome;
|
|
690
|
+
if (o.outcome === 'ok') {
|
|
691
|
+
out(`review ok: proposed=${o.candidatesProposed.length} duration=${o.durationMs}ms\n`);
|
|
692
|
+
for (const c of o.candidatesProposed) {
|
|
693
|
+
out(` ${c.memId} [${c.file}] ${c.text}\n`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else if (o.outcome === 'timeout') {
|
|
697
|
+
out(`review timed out after ${o.durationMs}ms (no candidates produced — user unaffected)\n`);
|
|
698
|
+
}
|
|
699
|
+
else if (o.outcome === 'error') {
|
|
700
|
+
out(`review error (fail-open): ${o.error} (no candidates produced)\n`);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
out(`review disabled: ${o.reason ?? ''}\n`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return 0;
|
|
707
|
+
}
|