@cairnkeep/cli 2.3.0 → 2.4.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/CHANGELOG.md +78 -0
- package/README.md +221 -6
- package/bin/cairn +43 -2
- package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
- package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
- package/claude/hooks/compaction-capture.sh +34 -0
- package/claude/hooks/memory-capture.sh +47 -5
- package/claude/hooks/memory-wakeup.sh +55 -2
- package/docs/building-an-overlay.md +1 -1
- package/docs/design/meta-agent.md +439 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/operating.md +531 -4
- package/docs/privacy-and-data-flow.md +417 -0
- package/docs/storage.md +252 -0
- package/examples/eval/README.md +42 -0
- package/examples/eval/adapter.json +12 -0
- package/examples/eval/bundled-fake.json +1 -0
- package/examples/eval/task-set.json +1 -0
- package/mcp-memory-server/dist/artifact-cli.js +184 -0
- package/mcp-memory-server/dist/artifact-schema.js +218 -0
- package/mcp-memory-server/dist/artifact-store.js +812 -0
- package/mcp-memory-server/dist/capability-adapter.js +286 -0
- package/mcp-memory-server/dist/capability-cli.js +361 -0
- package/mcp-memory-server/dist/capability-config.js +310 -0
- package/mcp-memory-server/dist/capability-harness.js +496 -0
- package/mcp-memory-server/dist/capability-registry.js +123 -0
- package/mcp-memory-server/dist/capability-schema.js +65 -0
- package/mcp-memory-server/dist/capability-store.js +431 -0
- package/mcp-memory-server/dist/compaction-normalize.js +267 -0
- package/mcp-memory-server/dist/eval-cli.js +523 -0
- package/mcp-memory-server/dist/eval-plan.js +346 -0
- package/mcp-memory-server/dist/eval-process.js +273 -0
- package/mcp-memory-server/dist/eval-report.js +609 -0
- package/mcp-memory-server/dist/eval-runner.js +827 -0
- package/mcp-memory-server/dist/eval-schema.js +368 -0
- package/mcp-memory-server/dist/eval-statistics.js +161 -0
- package/mcp-memory-server/dist/eval-workspace.js +313 -0
- package/mcp-memory-server/dist/failure-signature.js +141 -0
- package/mcp-memory-server/dist/index.js +600 -55
- package/mcp-memory-server/dist/node-cli.js +84 -0
- package/mcp-memory-server/dist/node-schema.js +170 -0
- package/mcp-memory-server/dist/node-store.js +455 -0
- package/mcp-memory-server/dist/note-cli.js +235 -0
- package/mcp-memory-server/dist/note-distiller.js +177 -0
- package/mcp-memory-server/dist/note-enrichment.js +93 -0
- package/mcp-memory-server/dist/note-schema.js +4 -0
- package/mcp-memory-server/dist/note-store.js +985 -0
- package/mcp-memory-server/dist/trajectory-cli.js +141 -0
- package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
- package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
- package/mcp-memory-server/dist/trajectory-schema.js +73 -0
- package/mcp-memory-server/dist/trajectory-store.js +339 -0
- package/mcp-memory-server/package.json +24 -2
- package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
- package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
- package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
- package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
- package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
- package/opencode/capability-contract/plugins/capability-command.ts +319 -0
- package/opencode/command/graphify.md +25 -14
- package/opencode/plugins/memory-capture.ts +76 -10
- package/opencode/plugins/memory-wakeup.ts +58 -6
- package/package.json +9 -4
- package/pi/extensions/cairnkeep-trajectory.ts +55 -0
- package/schemas/artifact.schema.json +133 -0
- package/schemas/capability-callback.schema.json +68 -0
- package/schemas/capability-contract.schema.json +126 -0
- package/schemas/eval-adapter.schema.json +43 -0
- package/schemas/eval-protocol.schema.json +114 -0
- package/schemas/eval-report.schema.json +423 -0
- package/schemas/eval-task-set.schema.json +114 -0
- package/schemas/memory-node.schema.json +90 -0
- package/schemas/note.schema.json +112 -0
- package/schemas/trajectory-redaction.schema.json +25 -0
- package/schemas/trajectory.schema.json +82 -0
- package/scripts/bootstrap.sh +4 -0
- package/scripts/completion.sh +119 -5
- package/scripts/doctor.sh +284 -0
- package/scripts/fake-eval-adapter.mjs +120 -0
- package/scripts/fixtures/capability-harness-contracts.json +408 -0
- package/scripts/install-audit-timer.sh +3 -1
- package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
- package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
- package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
- package/scripts/memory-wiki-audit.sh +49 -0
- package/scripts/smoke-bash32.sh +9 -0
- package/scripts/sync-claude-assets.sh +99 -6
- package/scripts/sync-opencode-graphify-assets.sh +24 -3
- package/scripts/sync-opencode-plugin-assets.sh +31 -5
- package/scripts/sync-opencode-security-assets.sh +24 -3
- package/scripts/sync-opencode-wiki-assets.sh +24 -3
- package/scripts/sync-pi-assets.sh +72 -0
- package/scripts/test-audit-timer.sh +98 -0
- package/scripts/test-bootstrap-untracked.sh +48 -0
- package/scripts/test-cli-dispatch.sh +41 -2
- package/scripts/test-compaction-hooks.sh +495 -0
- package/scripts/test-completion.sh +77 -0
- package/scripts/test-doctor.sh +348 -1
- package/scripts/test-eval-cli.sh +175 -0
- package/scripts/test-launcher-hooks.sh +602 -3
- package/scripts/test-notes-cli.sh +114 -0
- package/scripts/test-package-install.sh +251 -0
- package/scripts/test-phase16-node-lifecycle.sh +50 -0
- package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
- package/scripts/test-phase18-capability-lifecycle.sh +504 -0
- package/scripts/test-phase18-harness-boundary.sh +1060 -0
- package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
- package/scripts/test-trajectory-cli.sh +87 -0
- package/scripts/test-trajectory-hooks.sh +142 -0
- package/scripts/test-uninstall.sh +231 -5
- package/scripts/uninstall.sh +37 -5
- package/scripts/verify-dependency-security.mjs +121 -0
- package/scripts/verify-docs-parity.sh +527 -1
- package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
- package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
- package/templates/capabilities.json.template +7 -0
- package/templates/env.example.template +51 -0
- package/templates/memory-wiki-audit.service.template +1 -1
- package/templates/start-claude.sh.template +36 -0
- package/templates/start-opencode.sh.template +47 -5
- package/templates/start-pi.sh.template +30 -0
- package/templates/trajectory-redaction.json.template +4 -0
package/scripts/doctor.sh
CHANGED
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
set -uo pipefail
|
|
10
10
|
|
|
11
11
|
CAIRN_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
12
|
+
REPAIR_STORES=0
|
|
13
|
+
while [[ $# -gt 0 ]]; do
|
|
14
|
+
case "$1" in
|
|
15
|
+
--repair) REPAIR_STORES=1; shift ;;
|
|
16
|
+
-h|--help) echo "Usage: cairn doctor [--repair] # repair derived trajectory/artifact/typed metadata, indexes, and interrupted note transactions"; exit 0 ;;
|
|
17
|
+
*) echo "Unknown doctor option: $1" >&2; echo "Usage: cairn doctor [--repair]" >&2; exit 2 ;;
|
|
18
|
+
esac
|
|
19
|
+
done
|
|
12
20
|
|
|
13
21
|
# Load the project's .ai/.env if present (does not override already-set env).
|
|
14
22
|
if [[ -f "$PWD/.ai/.env" ]]; then
|
|
@@ -116,6 +124,282 @@ else
|
|
|
116
124
|
warn "sqlite3 not found — memory export unavailable (runtime and import unaffected)"
|
|
117
125
|
fi
|
|
118
126
|
|
|
127
|
+
# 7. Project-local trajectory store. Absence is healthy because capture is
|
|
128
|
+
# opt-in. Existing stores must pass SQLite, schema and index checks. Repair
|
|
129
|
+
# is explicit and only reconstructs metadata/indexes from valid full records.
|
|
130
|
+
trajectory_cli="$CAIRN_ROOT/mcp-memory-server/dist/trajectory-cli.js"
|
|
131
|
+
trajectory_dir="$PWD/.agentfs"
|
|
132
|
+
if [[ -d "$trajectory_dir" && ! -w "$trajectory_dir" ]]; then
|
|
133
|
+
fail "trajectory store directory is not writable: $trajectory_dir"
|
|
134
|
+
elif [[ ! -d "$trajectory_dir" && ! -w "$PWD" ]]; then
|
|
135
|
+
fail "trajectory store parent is not writable: $PWD"
|
|
136
|
+
elif [[ ! -f "$trajectory_cli" ]]; then
|
|
137
|
+
fail "trajectory diagnostics unavailable — rebuild mcp-memory-server"
|
|
138
|
+
else
|
|
139
|
+
trajectory_args=(doctor --json)
|
|
140
|
+
[[ $REPAIR_STORES -eq 1 ]] && trajectory_args+=(--repair)
|
|
141
|
+
trajectory_json=$(node "$trajectory_cli" "${trajectory_args[@]}" 2>/dev/null)
|
|
142
|
+
trajectory_status=$?
|
|
143
|
+
trajectory_state=$(node -e '
|
|
144
|
+
try {
|
|
145
|
+
const value = JSON.parse(process.argv[1])
|
|
146
|
+
if (!value.exists) process.stdout.write("absent")
|
|
147
|
+
else if (value.ok && value.repaired) process.stdout.write("repaired")
|
|
148
|
+
else if (value.ok) process.stdout.write("ok")
|
|
149
|
+
else process.stdout.write("broken")
|
|
150
|
+
} catch { process.stdout.write("invalid") }
|
|
151
|
+
' "$trajectory_json" 2>/dev/null)
|
|
152
|
+
case "$trajectory_state" in
|
|
153
|
+
absent) skip "trajectory store (not present — capture is opt-in)" ;;
|
|
154
|
+
repaired) pass "trajectory store repaired (metadata/indexes rebuilt; full records preserved)" ;;
|
|
155
|
+
ok) pass "trajectory store integrity, schema and indexes are valid" ;;
|
|
156
|
+
*)
|
|
157
|
+
if [[ $REPAIR_STORES -eq 1 ]]; then
|
|
158
|
+
fail "trajectory store could not be repaired safely; preserve .agentfs/trajectory.db and inspect it manually"
|
|
159
|
+
else
|
|
160
|
+
fail "trajectory store metadata/schema/index check failed — run: cairn doctor --repair"
|
|
161
|
+
fi
|
|
162
|
+
;;
|
|
163
|
+
esac
|
|
164
|
+
[[ $trajectory_status -eq 0 || "$trajectory_state" == "broken" ]] || true
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# 8. Project-local artifact store. Absence is healthy because compaction
|
|
168
|
+
# continuity and explicit artifact access are both opt-in. Repair may only
|
|
169
|
+
# rebuild derived indexes, dedupe rows, and latest pointers from valid full
|
|
170
|
+
# records; authoritative schema, digest, full-record, or SQLite corruption
|
|
171
|
+
# is never modified automatically.
|
|
172
|
+
artifact_cli="$CAIRN_ROOT/mcp-memory-server/dist/artifact-cli.js"
|
|
173
|
+
if [[ ! -f "$artifact_cli" ]]; then
|
|
174
|
+
fail "artifact diagnostics unavailable — rebuild mcp-memory-server"
|
|
175
|
+
else
|
|
176
|
+
artifact_args=(doctor --json)
|
|
177
|
+
[[ $REPAIR_STORES -eq 1 ]] && artifact_args+=(--repair)
|
|
178
|
+
artifact_json=$(node "$artifact_cli" "${artifact_args[@]}" 2>/dev/null)
|
|
179
|
+
artifact_status=$?
|
|
180
|
+
artifact_state=$(node -e '
|
|
181
|
+
try {
|
|
182
|
+
const value = JSON.parse(process.argv[1])
|
|
183
|
+
if (!value.exists) process.stdout.write("absent")
|
|
184
|
+
else if (value.ok && value.repaired) process.stdout.write("repaired")
|
|
185
|
+
else if (value.ok) process.stdout.write("ok")
|
|
186
|
+
else process.stdout.write("broken")
|
|
187
|
+
} catch { process.stdout.write("invalid") }
|
|
188
|
+
' "$artifact_json" 2>/dev/null)
|
|
189
|
+
case "$artifact_state" in
|
|
190
|
+
absent) skip "artifact store (not present — compaction capture and artifact access are opt-in)" ;;
|
|
191
|
+
repaired) pass "artifact store repaired (derived indexes, dedupe rows, and pointers rebuilt; authoritative records preserved)" ;;
|
|
192
|
+
ok) pass "artifact store integrity, schema, digests, indexes, pointers, and retention state are valid" ;;
|
|
193
|
+
*)
|
|
194
|
+
if [[ $REPAIR_STORES -eq 1 ]]; then
|
|
195
|
+
fail "artifact store could not be repaired safely; preserve .agentfs/artifacts.db and inspect authoritative corruption manually"
|
|
196
|
+
else
|
|
197
|
+
fail "artifact store authoritative/schema/digest/index check failed — preserve .agentfs/artifacts.db, then run: cairn doctor --repair"
|
|
198
|
+
fi
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
[[ $artifact_status -eq 0 || "$artifact_state" == "broken" ]] || true
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# 9. Typed metadata overlays and canonical-note transaction state. The internal
|
|
205
|
+
# JSON seam enumerates only contained local databases and the one notes root;
|
|
206
|
+
# raw KV cells and unmarked Markdown bytes are never repair inputs.
|
|
207
|
+
node_cli="$CAIRN_ROOT/mcp-memory-server/dist/node-cli.js"
|
|
208
|
+
if [[ ! -f "$node_cli" ]]; then
|
|
209
|
+
fail "typed-node diagnostics unavailable — rebuild mcp-memory-server"
|
|
210
|
+
else
|
|
211
|
+
node_args=(doctor --project-root "$PWD")
|
|
212
|
+
[[ $REPAIR_STORES -eq 1 ]] && node_args+=(--repair)
|
|
213
|
+
node_json=$(node "$node_cli" "${node_args[@]}" 2>/dev/null)
|
|
214
|
+
node_status=$?
|
|
215
|
+
node_state=$(node -e '
|
|
216
|
+
try {
|
|
217
|
+
const value = JSON.parse(process.argv[1])
|
|
218
|
+
if (!value.exists) process.stdout.write("absent")
|
|
219
|
+
else if (value.ok && value.repaired) process.stdout.write("repaired")
|
|
220
|
+
else if (value.ok) process.stdout.write("ok")
|
|
221
|
+
else process.stdout.write("broken")
|
|
222
|
+
} catch { process.stdout.write("invalid") }
|
|
223
|
+
' "$node_json" 2>/dev/null)
|
|
224
|
+
case "$node_state" in
|
|
225
|
+
absent) skip "typed metadata and note transaction state (not present)" ;;
|
|
226
|
+
repaired) pass "typed metadata/indexes and note transactions repaired safely" ;;
|
|
227
|
+
ok) pass "typed metadata, replay state, and note transactions are valid" ;;
|
|
228
|
+
*)
|
|
229
|
+
if [[ $REPAIR_STORES -eq 1 ]]; then
|
|
230
|
+
fail "typed/note state could not be repaired safely; preserve the memory store and recovery journals before manual inspection"
|
|
231
|
+
else
|
|
232
|
+
fail "typed metadata or note transaction check failed — run: cairn doctor --repair"
|
|
233
|
+
fi
|
|
234
|
+
;;
|
|
235
|
+
esac
|
|
236
|
+
[[ $node_status -eq 0 || "$node_state" == "broken" ]] || true
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
# 10. Managed capability configuration and the independent callback namespace.
|
|
240
|
+
# The TypeScript doctor owns all config/database reads. This shell layer accepts
|
|
241
|
+
# only its fixed states, codes, canonical IDs, and setting name; raw values,
|
|
242
|
+
# records, paths, parser errors, and SQLite details never cross the boundary.
|
|
243
|
+
capability_cli="$CAIRN_ROOT/mcp-memory-server/dist/capability-cli.js"
|
|
244
|
+
if [[ ! -f "$capability_cli" ]]; then
|
|
245
|
+
fail "capability diagnostics unavailable — rebuild mcp-memory-server"
|
|
246
|
+
else
|
|
247
|
+
capability_json=$(node "$capability_cli" doctor --json 2>/dev/null)
|
|
248
|
+
capability_status=$?
|
|
249
|
+
if ! capability_mapped=$(node - "$capability_json" <<'NODE'
|
|
250
|
+
const ids = new Set([
|
|
251
|
+
"memory.write", "memory.search", "notes.distill", "wiki",
|
|
252
|
+
"graph", "security.audit", "route.check", "context.explore",
|
|
253
|
+
]);
|
|
254
|
+
const configCodes = new Set([
|
|
255
|
+
"unknown-capability", "invalid-capability-value", "invalid-logging-value",
|
|
256
|
+
"invalid-config", "unsafe-permissions",
|
|
257
|
+
]);
|
|
258
|
+
const callbackCodes = new Set([
|
|
259
|
+
"sqlite-integrity-failed", "schema-missing", "schema-unsupported",
|
|
260
|
+
"invalid-record", "diagnostic-failed",
|
|
261
|
+
]);
|
|
262
|
+
try {
|
|
263
|
+
const value = JSON.parse(process.argv[2]);
|
|
264
|
+
const configuration = value.configuration;
|
|
265
|
+
const callbacks = value.callbacks;
|
|
266
|
+
if (value.schema_version !== 1
|
|
267
|
+
|| !configuration || configuration.name !== ".ai/capabilities.json"
|
|
268
|
+
|| !callbacks || callbacks.name !== ".agentfs/trajectory.db"
|
|
269
|
+
|| typeof configuration.exists !== "boolean"
|
|
270
|
+
|| typeof callbacks.exists !== "boolean"
|
|
271
|
+
|| !["PASS", "WARN", "FAIL"].includes(configuration.state)
|
|
272
|
+
|| !["PASS", "FAIL"].includes(callbacks.state)
|
|
273
|
+
|| !Array.isArray(configuration.issues)
|
|
274
|
+
|| !Array.isArray(callbacks.issues)) throw new Error("invalid");
|
|
275
|
+
|
|
276
|
+
const configRows = configuration.issues.map((issue) => {
|
|
277
|
+
if (!issue || typeof issue !== "object" || !configCodes.has(issue.code)) throw new Error("invalid");
|
|
278
|
+
if (issue.code === "invalid-capability-value") {
|
|
279
|
+
if (!ids.has(issue.capability_id) || issue.setting !== undefined) throw new Error("invalid");
|
|
280
|
+
return [issue.code, issue.capability_id];
|
|
281
|
+
}
|
|
282
|
+
if (issue.code === "invalid-logging-value") {
|
|
283
|
+
if (issue.setting !== "logging.callbacks" || issue.capability_id !== undefined) throw new Error("invalid");
|
|
284
|
+
return [issue.code, issue.setting];
|
|
285
|
+
}
|
|
286
|
+
if (issue.capability_id !== undefined || issue.setting !== undefined) throw new Error("invalid");
|
|
287
|
+
return [issue.code, "-"];
|
|
288
|
+
});
|
|
289
|
+
if ((configuration.state === "PASS") !== (configRows.length === 0)) throw new Error("invalid");
|
|
290
|
+
if (configuration.state === "WARN" && configRows.some(([code]) => code === "invalid-config" || code === "unsafe-permissions")) throw new Error("invalid");
|
|
291
|
+
if (configuration.state === "FAIL" && !configRows.some(([code]) => code === "invalid-config" || code === "unsafe-permissions")) throw new Error("invalid");
|
|
292
|
+
|
|
293
|
+
const callbackRows = callbacks.issues.map((code) => {
|
|
294
|
+
if (typeof code !== "string" || !callbackCodes.has(code)) throw new Error("invalid");
|
|
295
|
+
return code;
|
|
296
|
+
});
|
|
297
|
+
if ((callbacks.state === "PASS") !== (callbackRows.length === 0)) throw new Error("invalid");
|
|
298
|
+
|
|
299
|
+
console.log(["configuration", configuration.state, configuration.exists ? "present" : "absent"].join("\t"));
|
|
300
|
+
for (const [code, subject] of configRows) console.log(["configuration-issue", code, subject].join("\t"));
|
|
301
|
+
console.log(["callbacks", callbacks.state, callbacks.exists ? "present" : "absent"].join("\t"));
|
|
302
|
+
for (const code of callbackRows) console.log(["callback-issue", code, "-"].join("\t"));
|
|
303
|
+
} catch {
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
NODE
|
|
307
|
+
); then
|
|
308
|
+
fail "capability diagnostics returned an invalid value-free response"
|
|
309
|
+
else
|
|
310
|
+
while IFS=$'\t' read -r area state detail; do
|
|
311
|
+
case "$area:$state:$detail" in
|
|
312
|
+
configuration:PASS:absent)
|
|
313
|
+
pass "capability configuration (not present — managed overrides are unused)"
|
|
314
|
+
;;
|
|
315
|
+
configuration:PASS:present)
|
|
316
|
+
pass "capability configuration schema and permissions are valid"
|
|
317
|
+
;;
|
|
318
|
+
configuration:WARN:*|configuration:FAIL:*)
|
|
319
|
+
;;
|
|
320
|
+
configuration-issue:unknown-capability:-)
|
|
321
|
+
warn "capability configuration contains an unknown override ID"
|
|
322
|
+
;;
|
|
323
|
+
configuration-issue:invalid-capability-value:*)
|
|
324
|
+
warn "capability configuration has an invalid value for $detail"
|
|
325
|
+
;;
|
|
326
|
+
configuration-issue:invalid-logging-value:logging.callbacks)
|
|
327
|
+
warn "capability configuration has an invalid value for logging.callbacks"
|
|
328
|
+
;;
|
|
329
|
+
configuration-issue:invalid-config:-)
|
|
330
|
+
fail "capability configuration is invalid or uses an unsupported schema; preserve .ai/capabilities.json before reset"
|
|
331
|
+
;;
|
|
332
|
+
configuration-issue:unsafe-permissions:-)
|
|
333
|
+
fail "capability configuration type or permissions are unsafe; preserve .ai/capabilities.json before recovery"
|
|
334
|
+
;;
|
|
335
|
+
callbacks:PASS:absent)
|
|
336
|
+
pass "capability callback namespace (not present — callback logging is unused)"
|
|
337
|
+
;;
|
|
338
|
+
callbacks:PASS:present)
|
|
339
|
+
pass "capability callback namespace schema and SQLite integrity are valid"
|
|
340
|
+
;;
|
|
341
|
+
callbacks:FAIL:*)
|
|
342
|
+
;;
|
|
343
|
+
callback-issue:sqlite-integrity-failed:-)
|
|
344
|
+
fail "capability callback namespace SQLite integrity failed; preserve .agentfs/trajectory.db before recovery"
|
|
345
|
+
;;
|
|
346
|
+
callback-issue:schema-missing:-)
|
|
347
|
+
fail "capability callback namespace schema marker is missing; preserve .agentfs/trajectory.db before recovery"
|
|
348
|
+
;;
|
|
349
|
+
callback-issue:schema-unsupported:-)
|
|
350
|
+
fail "capability callback namespace schema is unsupported; preserve .agentfs/trajectory.db before recovery"
|
|
351
|
+
;;
|
|
352
|
+
callback-issue:invalid-record:-)
|
|
353
|
+
fail "capability callback namespace contains invalid records; preserve .agentfs/trajectory.db before recovery"
|
|
354
|
+
;;
|
|
355
|
+
callback-issue:diagnostic-failed:-)
|
|
356
|
+
fail "capability callback namespace could not be diagnosed safely; preserve .agentfs/trajectory.db before recovery"
|
|
357
|
+
;;
|
|
358
|
+
*)
|
|
359
|
+
fail "capability diagnostics returned an invalid value-free response"
|
|
360
|
+
;;
|
|
361
|
+
esac
|
|
362
|
+
done <<< "$capability_mapped"
|
|
363
|
+
[[ $capability_status -eq 0 || "$capability_mapped" == *$'\tFAIL\t'* ]] ||
|
|
364
|
+
fail "capability diagnostics exited unexpectedly"
|
|
365
|
+
fi
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# 11. Evaluation reports. The private compiled operation resolves the owned
|
|
369
|
+
# project-local report root and returns one bounded, value-free state. The
|
|
370
|
+
# shell never reads report bytes and treats malformed output as unsafe.
|
|
371
|
+
eval_cli="$CAIRN_ROOT/mcp-memory-server/dist/eval-cli.js"
|
|
372
|
+
eval_state="unsafe"
|
|
373
|
+
if [[ -f "$eval_cli" ]]; then
|
|
374
|
+
eval_json=$(node "$eval_cli" doctor-diagnosis --root "$PWD" --json 2>/dev/null | head -c 257)
|
|
375
|
+
if parsed_eval_state=$(node - "$eval_json" <<'NODE'
|
|
376
|
+
const raw = process.argv[2];
|
|
377
|
+
try {
|
|
378
|
+
if (Buffer.byteLength(raw, "utf8") > 64) throw new Error("invalid");
|
|
379
|
+
const value = JSON.parse(raw);
|
|
380
|
+
const keys = Object.keys(value).sort();
|
|
381
|
+
if (JSON.stringify(keys) !== JSON.stringify(["diagnosis", "schema_version"])
|
|
382
|
+
|| value.schema_version !== 1
|
|
383
|
+
|| !["absent", "ok", "partial", "tampered", "unsafe"].includes(value.diagnosis)) {
|
|
384
|
+
throw new Error("invalid");
|
|
385
|
+
}
|
|
386
|
+
process.stdout.write(value.diagnosis);
|
|
387
|
+
} catch {
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
NODE
|
|
391
|
+
); then
|
|
392
|
+
eval_state="$parsed_eval_state"
|
|
393
|
+
fi
|
|
394
|
+
fi
|
|
395
|
+
case "$eval_state" in
|
|
396
|
+
absent) skip "evaluation reports (not present — evaluation is opt-in)" ;;
|
|
397
|
+
ok) pass "evaluation reports are complete and valid" ;;
|
|
398
|
+
partial) warn "evaluation reports are partial" ;;
|
|
399
|
+
tampered) warn "evaluation report integrity check failed" ;;
|
|
400
|
+
unsafe) warn "evaluation report storage is unsafe" ;;
|
|
401
|
+
esac
|
|
402
|
+
|
|
119
403
|
echo
|
|
120
404
|
if [[ "$fails" -gt 0 ]]; then
|
|
121
405
|
echo "cairn doctor: $fails configured dependency check(s) failed."
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Deterministic offline-framework adapter. It is fixture code, never live evidence.
|
|
3
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
|
|
7
|
+
const MAX_REQUEST_BYTES = 1024 * 1024;
|
|
8
|
+
const chunks = [];
|
|
9
|
+
let requestBytes = 0;
|
|
10
|
+
for await (const chunk of process.stdin) {
|
|
11
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
12
|
+
requestBytes += bytes.byteLength;
|
|
13
|
+
if (requestBytes > MAX_REQUEST_BYTES) {
|
|
14
|
+
process.stderr.write("offline-framework: request limit exceeded\n");
|
|
15
|
+
process.exit(2);
|
|
16
|
+
}
|
|
17
|
+
chunks.push(bytes);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let request;
|
|
21
|
+
try {
|
|
22
|
+
request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
23
|
+
} catch {
|
|
24
|
+
process.stderr.write("offline-framework: invalid request\n");
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const required = [
|
|
29
|
+
"schema_version", "experiment_id", "task_id", "arm", "repetition", "pass",
|
|
30
|
+
"workspace_path", "notes_path", "input", "limits", "seed",
|
|
31
|
+
"expected_capability_digest", "output_path",
|
|
32
|
+
];
|
|
33
|
+
if (!request || typeof request !== "object"
|
|
34
|
+
|| Object.keys(request).sort().join("\0") !== [...required].sort().join("\0")) {
|
|
35
|
+
process.stderr.write("offline-framework: unsupported request shape\n");
|
|
36
|
+
process.exit(2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const workspace = resolve(process.cwd(), request.workspace_path);
|
|
40
|
+
const sessionId = `${request.arm}-r${request.repetition}-${request.pass}-${request.task_id}`;
|
|
41
|
+
if (process.env.CAIRN_FAKE_PID_FILE) writeFileSync(process.env.CAIRN_FAKE_PID_FILE, `${process.pid}\n`, { mode: 0o600 });
|
|
42
|
+
|
|
43
|
+
function trajectory(events) {
|
|
44
|
+
const agentfs = join(workspace, ".agentfs");
|
|
45
|
+
mkdirSync(agentfs, { recursive: true, mode: 0o700 });
|
|
46
|
+
const database = new DatabaseSync(join(agentfs, "trajectory.db"));
|
|
47
|
+
database.exec("CREATE TABLE kv_store (key TEXT PRIMARY KEY, value TEXT NOT NULL, created_at INTEGER DEFAULT (unixepoch ()), updated_at INTEGER DEFAULT (unixepoch ()))");
|
|
48
|
+
const startedAt = "2026-01-01T00:00:00.000Z";
|
|
49
|
+
const endedAt = "2026-01-01T00:00:01.000Z";
|
|
50
|
+
const session = {
|
|
51
|
+
schema_version: 1,
|
|
52
|
+
session_id: sessionId,
|
|
53
|
+
harness: "pi",
|
|
54
|
+
project_root: workspace,
|
|
55
|
+
started_at: startedAt,
|
|
56
|
+
ended_at: endedAt,
|
|
57
|
+
events,
|
|
58
|
+
capture: {
|
|
59
|
+
captured_at: endedAt,
|
|
60
|
+
omitted_reasoning_blocks: 0,
|
|
61
|
+
omitted_unknown_records: 0,
|
|
62
|
+
truncated: false,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const index = {
|
|
66
|
+
schema_version: 1,
|
|
67
|
+
session_id: sessionId,
|
|
68
|
+
harness: "pi",
|
|
69
|
+
started_at: startedAt,
|
|
70
|
+
ended_at: endedAt,
|
|
71
|
+
event_count: events.length,
|
|
72
|
+
logical_bytes: Buffer.byteLength(JSON.stringify(session)),
|
|
73
|
+
full_key: `trajectory/session/${sessionId}`,
|
|
74
|
+
};
|
|
75
|
+
const insert = database.prepare("INSERT INTO kv_store(key,value) VALUES(?,?)");
|
|
76
|
+
insert.run("trajectory/meta/schema-version", "1");
|
|
77
|
+
insert.run(`trajectory/session/${sessionId}`, JSON.stringify(session));
|
|
78
|
+
insert.run(`trajectory/index/${String(Date.parse(endedAt)).padStart(16, "0")}/${sessionId}`, JSON.stringify(index));
|
|
79
|
+
database.close();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (process.env.CAIRN_FAKE_CANCEL_ALL === "1"
|
|
83
|
+
|| request.task_id === "offline-timeout"
|
|
84
|
+
|| (request.task_id === "offline-cancellation-control" && process.env.CAIRN_FAKE_CANCEL === "1")) {
|
|
85
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 60_000));
|
|
86
|
+
}
|
|
87
|
+
if (request.task_id === "offline-invalid-result") {
|
|
88
|
+
process.stdout.write('{"schema_version":1,"status":"completed","unexpected":true}');
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
if (request.task_id === "offline-adapter-error") {
|
|
92
|
+
process.stdout.write(JSON.stringify({ schema_version: 1, status: "adapter_error", error_code: "designed_adapter_error" }));
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
writeFileSync(join(workspace, "adapter-result.txt"), `${request.task_id}:${request.pass}\n`, { mode: 0o600 });
|
|
97
|
+
if (request.task_id === "offline-distillation-failure") {
|
|
98
|
+
const agentfs = join(workspace, ".agentfs");
|
|
99
|
+
mkdirSync(agentfs, { recursive: true, mode: 0o700 });
|
|
100
|
+
writeFileSync(join(agentfs, "trajectory.db"), "designed-invalid-store", { mode: 0o600 });
|
|
101
|
+
} else if (request.task_id !== "offline-skipped-notes") {
|
|
102
|
+
const events = request.task_id === "offline-pass-note" ? [
|
|
103
|
+
{ sequence: 0, kind: "tool_invocation", payload: { call_id: "fixture-call", tool_name: "fixture-check", input: { task: request.task_id } } },
|
|
104
|
+
{ sequence: 1, kind: "tool_result", payload: { call_id: "fixture-call", is_error: true, error: "Deterministic fixture failure" } },
|
|
105
|
+
] : [];
|
|
106
|
+
trajectory(events);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const result = {
|
|
110
|
+
schema_version: 1,
|
|
111
|
+
status: "completed",
|
|
112
|
+
turns: { value: request.pass === "run1" ? 2 : 1, semantics: "offline-fixture-turn" },
|
|
113
|
+
harness: { id: "cairn-offline-fake" },
|
|
114
|
+
adapter: { id: "cairn-offline-fake" },
|
|
115
|
+
model: { id: "deterministic-fixture" },
|
|
116
|
+
observed_capability_digest: request.expected_capability_digest,
|
|
117
|
+
...(request.task_id === "offline-skipped-notes" ? {} : { trajectory_ref: sessionId }),
|
|
118
|
+
...(request.task_id === "offline-missing-tokens" ? {} : { usage: { total_tokens: request.pass === "run1" ? 20 : 10 } }),
|
|
119
|
+
};
|
|
120
|
+
process.stdout.write(JSON.stringify(result));
|