@cairnkeep/cli 2.3.1 → 2.5.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +232 -6
  3. package/bin/cairn +43 -2
  4. package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
  5. package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
  6. package/claude/hooks/compaction-capture.sh +34 -0
  7. package/claude/hooks/memory-capture.sh +47 -5
  8. package/claude/hooks/memory-wakeup.sh +55 -2
  9. package/docs/building-an-overlay.md +4 -3
  10. package/docs/design/meta-agent.md +439 -0
  11. package/docs/harness-compatibility.md +117 -0
  12. package/docs/learning/CURRICULUM-MAP.md +91 -0
  13. package/docs/learning/FEATURE-GUIDE.md +86 -0
  14. package/docs/learning/PRODUCTION-PLAN.md +45 -14
  15. package/docs/learning/README.md +20 -1
  16. package/docs/learning/instructor-guide.md +6 -0
  17. package/docs/learning/lessons/L00-why-cairnkeep.md +20 -8
  18. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  19. package/docs/learning/lessons/L02-installation.md +23 -5
  20. package/docs/learning/lessons/L03-first-project.md +7 -2
  21. package/docs/learning/lessons/L04-memory-fundamentals.md +4 -0
  22. package/docs/learning/lessons/L05-daily-workflow.md +4 -0
  23. package/docs/learning/lessons/L07-privacy-and-storage.md +3 -0
  24. package/docs/learning/lessons/L10-context-exploration.md +4 -2
  25. package/docs/learning/lessons/L12-managed-overlays.md +2 -0
  26. package/docs/learning/lessons/L13-session-evidence.md +71 -0
  27. package/docs/learning/lessons/L14-typed-memory.md +51 -0
  28. package/docs/learning/lessons/L15-capability-governance.md +56 -0
  29. package/docs/learning/lessons/L16-evaluation.md +67 -0
  30. package/docs/learning/lessons/L17-agent-boundary.md +55 -0
  31. package/docs/learning/templates/lesson-template.md +4 -1
  32. package/docs/learning/tracks/evidence-and-evaluation.md +20 -0
  33. package/docs/learning/tracks/operator.md +8 -2
  34. package/docs/learning/tracks/practitioner.md +6 -1
  35. package/docs/learning/tracks/quickstart.md +3 -0
  36. package/docs/learning/video-scripts/L00-why-cairnkeep.md +21 -17
  37. package/docs/learning/video-scripts/L02-installation.md +9 -2
  38. package/docs/learning/video-scripts/L03-first-project.md +3 -0
  39. package/docs/operating.md +567 -7
  40. package/docs/privacy-and-data-flow.md +417 -0
  41. package/docs/storage.md +252 -0
  42. package/examples/eval/README.md +42 -0
  43. package/examples/eval/adapter.json +12 -0
  44. package/examples/eval/bundled-fake.json +1 -0
  45. package/examples/eval/task-set.json +1 -0
  46. package/mcp-memory-server/dist/artifact-cli.js +184 -0
  47. package/mcp-memory-server/dist/artifact-schema.js +218 -0
  48. package/mcp-memory-server/dist/artifact-store.js +812 -0
  49. package/mcp-memory-server/dist/capability-adapter.js +286 -0
  50. package/mcp-memory-server/dist/capability-cli.js +361 -0
  51. package/mcp-memory-server/dist/capability-config.js +310 -0
  52. package/mcp-memory-server/dist/capability-harness.js +496 -0
  53. package/mcp-memory-server/dist/capability-registry.js +123 -0
  54. package/mcp-memory-server/dist/capability-schema.js +65 -0
  55. package/mcp-memory-server/dist/capability-store.js +431 -0
  56. package/mcp-memory-server/dist/compaction-normalize.js +267 -0
  57. package/mcp-memory-server/dist/eval-cli.js +523 -0
  58. package/mcp-memory-server/dist/eval-plan.js +346 -0
  59. package/mcp-memory-server/dist/eval-process.js +273 -0
  60. package/mcp-memory-server/dist/eval-report.js +609 -0
  61. package/mcp-memory-server/dist/eval-runner.js +827 -0
  62. package/mcp-memory-server/dist/eval-schema.js +368 -0
  63. package/mcp-memory-server/dist/eval-statistics.js +161 -0
  64. package/mcp-memory-server/dist/eval-workspace.js +313 -0
  65. package/mcp-memory-server/dist/failure-signature.js +141 -0
  66. package/mcp-memory-server/dist/index.js +600 -55
  67. package/mcp-memory-server/dist/node-cli.js +84 -0
  68. package/mcp-memory-server/dist/node-schema.js +170 -0
  69. package/mcp-memory-server/dist/node-store.js +455 -0
  70. package/mcp-memory-server/dist/note-cli.js +235 -0
  71. package/mcp-memory-server/dist/note-distiller.js +177 -0
  72. package/mcp-memory-server/dist/note-enrichment.js +93 -0
  73. package/mcp-memory-server/dist/note-schema.js +4 -0
  74. package/mcp-memory-server/dist/note-store.js +985 -0
  75. package/mcp-memory-server/dist/trajectory-cli.js +141 -0
  76. package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
  77. package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
  78. package/mcp-memory-server/dist/trajectory-schema.js +73 -0
  79. package/mcp-memory-server/dist/trajectory-store.js +339 -0
  80. package/mcp-memory-server/package.json +24 -2
  81. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
  82. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
  83. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
  84. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
  85. package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
  86. package/opencode/capability-contract/plugins/capability-command.ts +319 -0
  87. package/opencode/command/graphify.md +25 -14
  88. package/opencode/plugins/memory-capture.ts +76 -10
  89. package/opencode/plugins/memory-wakeup.ts +58 -6
  90. package/package.json +8 -4
  91. package/pi/extensions/cairnkeep-trajectory.ts +55 -0
  92. package/schemas/artifact.schema.json +133 -0
  93. package/schemas/capability-callback.schema.json +68 -0
  94. package/schemas/capability-contract.schema.json +126 -0
  95. package/schemas/eval-adapter.schema.json +43 -0
  96. package/schemas/eval-protocol.schema.json +114 -0
  97. package/schemas/eval-report.schema.json +423 -0
  98. package/schemas/eval-task-set.schema.json +114 -0
  99. package/schemas/memory-node.schema.json +90 -0
  100. package/schemas/note.schema.json +112 -0
  101. package/schemas/trajectory-redaction.schema.json +25 -0
  102. package/schemas/trajectory.schema.json +82 -0
  103. package/scripts/bootstrap.sh +6 -0
  104. package/scripts/completion.sh +119 -5
  105. package/scripts/doctor.sh +284 -0
  106. package/scripts/fake-eval-adapter.mjs +120 -0
  107. package/scripts/fixtures/capability-harness-contracts.json +408 -0
  108. package/scripts/install-audit-timer.sh +3 -1
  109. package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
  110. package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
  111. package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
  112. package/scripts/memory-wiki-audit.sh +49 -0
  113. package/scripts/smoke-bash32.sh +9 -0
  114. package/scripts/sync-claude-assets.sh +99 -6
  115. package/scripts/sync-opencode-graphify-assets.sh +24 -3
  116. package/scripts/sync-opencode-plugin-assets.sh +31 -5
  117. package/scripts/sync-opencode-security-assets.sh +24 -3
  118. package/scripts/sync-opencode-wiki-assets.sh +24 -3
  119. package/scripts/sync-pi-assets.sh +72 -0
  120. package/scripts/test-audit-timer.sh +98 -0
  121. package/scripts/test-bootstrap-untracked.sh +49 -0
  122. package/scripts/test-cli-dispatch.sh +41 -2
  123. package/scripts/test-compaction-hooks.sh +495 -0
  124. package/scripts/test-completion.sh +77 -0
  125. package/scripts/test-doctor.sh +348 -1
  126. package/scripts/test-eval-cli.sh +175 -0
  127. package/scripts/test-launcher-hooks.sh +630 -3
  128. package/scripts/test-learning-docs.sh +20 -4
  129. package/scripts/test-notes-cli.sh +114 -0
  130. package/scripts/test-package-install.sh +254 -0
  131. package/scripts/test-phase16-node-lifecycle.sh +50 -0
  132. package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
  133. package/scripts/test-phase18-capability-lifecycle.sh +504 -0
  134. package/scripts/test-phase18-harness-boundary.sh +1060 -0
  135. package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
  136. package/scripts/test-trajectory-cli.sh +87 -0
  137. package/scripts/test-trajectory-hooks.sh +142 -0
  138. package/scripts/test-uninstall.sh +231 -5
  139. package/scripts/uninstall.sh +38 -5
  140. package/scripts/verify-dependency-security.mjs +121 -0
  141. package/scripts/verify-docs-parity.sh +527 -1
  142. package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
  143. package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
  144. package/templates/capabilities.json.template +7 -0
  145. package/templates/env.example.template +51 -0
  146. package/templates/memory-wiki-audit.service.template +1 -1
  147. package/templates/start-claude.sh.template +36 -0
  148. package/templates/start-kimi.sh.template +39 -0
  149. package/templates/start-opencode.sh.template +47 -5
  150. package/templates/start-pi.sh.template +30 -0
  151. package/templates/trajectory-redaction.json.template +4 -0
@@ -0,0 +1,177 @@
1
+ import { createHash } from "node:crypto";
2
+ import { buildFailureSignature } from "./failure-signature.js";
3
+ import { enrichNoteEvidence } from "./note-enrichment.js";
4
+ import { isNoteDistillationEnabled } from "./note-schema.js";
5
+ import { applyDistilledSession, applyNoteEnrichment, getNoteEnrichmentEvidence, } from "./note-store.js";
6
+ import { listTrajectories, showTrajectory } from "./trajectory-store.js";
7
+ function object(value) {
8
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
9
+ }
10
+ function string(value) {
11
+ return typeof value === "string" && value.length > 0 ? value : undefined;
12
+ }
13
+ function stableValue(value) {
14
+ if (Array.isArray(value))
15
+ return value.map(stableValue);
16
+ const record = object(value);
17
+ if (!record)
18
+ return value;
19
+ return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
20
+ }
21
+ function hash(domain, value) {
22
+ return createHash("sha256").update(`cairnkeep:notes:v1:${domain}\0${JSON.stringify(stableValue(value))}`).digest("hex");
23
+ }
24
+ function validationKey(toolName, input) {
25
+ return hash("validation", { tool_name: toolName.trim().toLowerCase(), input });
26
+ }
27
+ function evidenceText(payload) {
28
+ for (const key of ["output", "message", "error", "stderr"]) {
29
+ const value = string(payload[key]);
30
+ if (value)
31
+ return value.slice(0, 32768);
32
+ if (payload[key] !== undefined && payload[key] !== null)
33
+ return JSON.stringify(stableValue(payload[key])).slice(0, 32768);
34
+ }
35
+ return "Unknown structured failure";
36
+ }
37
+ function explicitFailure(payload) {
38
+ if (payload.is_error === true)
39
+ return true;
40
+ for (const key of ["exit_code", "status", "status_code"]) {
41
+ const value = payload[key];
42
+ if (typeof value === "number" && Number.isFinite(value) && value !== 0)
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ function explicitSuccess(payload) {
48
+ if (payload.is_error === false)
49
+ return true;
50
+ for (const key of ["exit_code", "status", "status_code"])
51
+ if (payload[key] === 0)
52
+ return true;
53
+ return false;
54
+ }
55
+ function occurrence(session, sessionDigest, event, outcome, evidence, toolName, key) {
56
+ return {
57
+ session_id: session.session_id,
58
+ session_digest: sessionDigest,
59
+ ended_at: session.ended_at,
60
+ sequence: event.sequence,
61
+ outcome,
62
+ ...(toolName ? { tool_name: toolName.slice(0, 256) } : {}),
63
+ ...(key ? { validation_key: key } : {}),
64
+ evidence: evidence.replace(/\s+/g, " ").trim().slice(0, 4096) || "Structured event",
65
+ };
66
+ }
67
+ function extractSession(session) {
68
+ const sessionDigest = hash("trajectory", session);
69
+ const invocations = new Map();
70
+ const failures = [];
71
+ const successes = [];
72
+ for (const event of [...session.events].sort((a, b) => a.sequence - b.sequence)) {
73
+ const payload = object(event.payload) ?? {};
74
+ if (event.kind === "tool_invocation") {
75
+ const callId = string(payload.call_id);
76
+ const toolName = string(payload.tool_name);
77
+ if (callId && toolName)
78
+ invocations.set(callId, { toolName, input: payload.input, key: validationKey(toolName, payload.input) });
79
+ continue;
80
+ }
81
+ if (event.kind === "tool_result") {
82
+ const invocation = invocations.get(string(payload.call_id) ?? "");
83
+ if (!invocation)
84
+ continue;
85
+ if (explicitFailure(payload)) {
86
+ const raw = evidenceText(payload);
87
+ const signature = buildFailureSignature(raw, { root: session.project_root });
88
+ failures.push({
89
+ signature,
90
+ occurrence: occurrence(session, sessionDigest, event, "failure", signature.normalized_message, invocation.toolName, invocation.key),
91
+ sequence: event.sequence,
92
+ });
93
+ }
94
+ else if (explicitSuccess(payload)) {
95
+ successes.push({
96
+ validation_key: invocation.key,
97
+ occurrence: occurrence(session, sessionDigest, event, "resolution", "Equivalent validation invocation succeeded.", invocation.toolName, invocation.key),
98
+ });
99
+ }
100
+ continue;
101
+ }
102
+ if (event.kind === "system_event" && payload.event === "model_error") {
103
+ const raw = evidenceText(payload);
104
+ const signature = buildFailureSignature(raw, { root: session.project_root, component: "model" });
105
+ failures.push({
106
+ signature,
107
+ occurrence: occurrence(session, sessionDigest, event, "failure", signature.normalized_message),
108
+ sequence: event.sequence,
109
+ });
110
+ continue;
111
+ }
112
+ if (event.kind === "model_output") {
113
+ const text = string(payload.text)?.trim();
114
+ if (!text || !/^(?:I am abandoning this approach|I will not pursue this strategy)\b/i.test(text))
115
+ continue;
116
+ const latest = [...failures].reverse().find((failure) => failure.sequence < event.sequence && !failure.abandonment);
117
+ if (latest)
118
+ latest.abandonment = occurrence(session, sessionDigest, event, "abandonment", text);
119
+ }
120
+ }
121
+ return { digest: sessionDigest, failures: failures.map(({ sequence: _sequence, ...failure }) => failure), successes };
122
+ }
123
+ export async function distillProject(options) {
124
+ if (!isNoteDistillationEnabled()) {
125
+ return {
126
+ schema_version: 1,
127
+ enabled: false,
128
+ created: [],
129
+ updated: [],
130
+ already_processed: [],
131
+ enrichment_skipped: [],
132
+ enrichment_failed: [],
133
+ failed: [],
134
+ };
135
+ }
136
+ const sessions = options.sessionId
137
+ ? [await showTrajectory(options.sessionId, options.projectRoot)]
138
+ : await (async () => {
139
+ const listed = await listTrajectories(options.projectRoot);
140
+ const selected = await Promise.all(listed.sessions.map((entry) => showTrajectory(entry.session_id, options.projectRoot)));
141
+ return selected.sort((left, right) => left.ended_at.localeCompare(right.ended_at) || left.session_id.localeCompare(right.session_id));
142
+ })();
143
+ const result = {
144
+ schema_version: 1,
145
+ enabled: true,
146
+ created: [],
147
+ updated: [],
148
+ already_processed: [],
149
+ enrichment_skipped: [],
150
+ enrichment_failed: [],
151
+ failed: [],
152
+ };
153
+ for (const session of sessions) {
154
+ const extracted = extractSession(session);
155
+ const stored = applyDistilledSession({
156
+ projectRoot: options.projectRoot,
157
+ sessionId: session.session_id,
158
+ sessionDigest: extracted.digest,
159
+ failures: extracted.failures,
160
+ successes: extracted.successes,
161
+ });
162
+ result.created.push(...stored.created);
163
+ result.updated.push(...stored.updated);
164
+ result.already_processed.push(...stored.already_processed);
165
+ for (const note of [...stored.created, ...stored.updated]) {
166
+ const enriched = await enrichNoteEvidence(getNoteEnrichmentEvidence(note.id));
167
+ if (enriched.status === "enriched")
168
+ applyNoteEnrichment(note.id, enriched.enrichment);
169
+ else if (enriched.status === "enrichment_skipped")
170
+ result.enrichment_skipped.push({ id: note.id, reason: enriched.reason });
171
+ else
172
+ result.enrichment_failed.push({ id: note.id, error: enriched.error });
173
+ }
174
+ result.project_id ??= stored.created[0]?.project_id ?? stored.updated[0]?.project_id;
175
+ }
176
+ return result;
177
+ }
@@ -0,0 +1,93 @@
1
+ import { z } from "zod";
2
+ import { isNoteDistillationEnabled, noteEnrichmentContentSchema } from "./note-schema.js";
3
+ const DEFAULT_TIMEOUT_MS = 15_000;
4
+ const MAX_RESPONSE_BYTES = 64 * 1024;
5
+ const evidenceSchema = z.object({
6
+ id: z.string().min(1).max(256),
7
+ normalized_error: z.string().min(1).max(4096),
8
+ component: z.string().max(1024),
9
+ status: z.enum(["unresolved", "resolved", "abandoned"]),
10
+ attempts: z.array(z.string().min(1).max(4096)).max(64),
11
+ }).strict();
12
+ function truthy(value) {
13
+ return /^(?:1|true|yes|on)$/i.test(value?.trim() ?? "");
14
+ }
15
+ function config() {
16
+ if (!isNoteDistillationEnabled() || !truthy(process.env.CAIRN_NOTE_ENRICHMENT))
17
+ return null;
18
+ const apiKey = process.env.CAIRN_LLM_API_KEY?.trim();
19
+ const rawUrl = process.env.CAIRN_LLM_API_URL?.trim();
20
+ const model = process.env.CAIRN_NOTE_ENRICHMENT_MODEL?.trim();
21
+ if (!apiKey || !rawUrl || !model)
22
+ return null;
23
+ const url = new URL(rawUrl);
24
+ if (!['http:', 'https:'].includes(url.protocol))
25
+ throw new Error("CAIRN_LLM_API_URL must use http or https.");
26
+ const rawTimeout = Number(process.env.CAIRN_NOTE_ENRICHMENT_TIMEOUT_MS);
27
+ const timeoutMs = Number.isFinite(rawTimeout) && rawTimeout >= 100 && rawTimeout <= 120_000
28
+ ? Math.floor(rawTimeout)
29
+ : DEFAULT_TIMEOUT_MS;
30
+ return { apiUrl: rawUrl.replace(/\/+$/, ""), apiKey, model, timeoutMs };
31
+ }
32
+ function parseContent(value) {
33
+ const stripped = value.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
34
+ return noteEnrichmentContentSchema.parse(JSON.parse(stripped));
35
+ }
36
+ async function request(configured, evidence) {
37
+ const response = await fetch(`${configured.apiUrl}/chat/completions`, {
38
+ method: "POST",
39
+ headers: {
40
+ Authorization: `Bearer ${configured.apiKey}`,
41
+ "Content-Type": "application/json",
42
+ },
43
+ body: JSON.stringify({
44
+ model: configured.model,
45
+ temperature: 0,
46
+ max_tokens: 1000,
47
+ response_format: { type: "json_object" },
48
+ messages: [
49
+ {
50
+ role: "system",
51
+ content: "Summarize the supplied already-redacted failure evidence. Return strict JSON with summary, lessons, and caveats. Do not infer failure identity, lifecycle, or provenance.",
52
+ },
53
+ { role: "user", content: JSON.stringify(evidence) },
54
+ ],
55
+ }),
56
+ signal: AbortSignal.timeout(configured.timeoutMs),
57
+ });
58
+ if (!response.ok)
59
+ throw new Error(`provider returned HTTP ${response.status}`);
60
+ const raw = await response.text();
61
+ if (Buffer.byteLength(raw, "utf8") > MAX_RESPONSE_BYTES)
62
+ throw new Error("provider response exceeded the local size limit");
63
+ const outer = z.object({
64
+ choices: z.array(z.object({ message: z.object({ content: z.string() }).passthrough() }).passthrough()).min(1),
65
+ }).passthrough().parse(JSON.parse(raw));
66
+ return parseContent(outer.choices[0].message.content);
67
+ }
68
+ export async function enrichNoteEvidence(rawEvidence) {
69
+ let configured;
70
+ try {
71
+ configured = config();
72
+ }
73
+ catch (error) {
74
+ return { status: "enrichment_failed", error: error instanceof Error ? error.message : "invalid enrichment configuration" };
75
+ }
76
+ if (!configured)
77
+ return { status: "enrichment_skipped", reason: "optional enrichment is disabled or incompletely configured" };
78
+ const parsed = evidenceSchema.safeParse(rawEvidence);
79
+ if (!parsed.success)
80
+ return { status: "enrichment_failed", error: "enrichment evidence failed local validation" };
81
+ for (let attempt = 0; attempt < 2; attempt += 1) {
82
+ try {
83
+ return { status: "enriched", enrichment: await request(configured, parsed.data) };
84
+ }
85
+ catch (error) {
86
+ if (attempt === 1) {
87
+ const name = error instanceof Error ? error.name : "Error";
88
+ return { status: "enrichment_failed", error: `optional enrichment failed locally (${name})` };
89
+ }
90
+ }
91
+ }
92
+ return { status: "enrichment_failed", error: "optional enrichment failed locally" };
93
+ }
@@ -0,0 +1,4 @@
1
+ export { NODE_SCHEMA_VERSION as NOTE_SCHEMA_VERSION, failureFrameSchema, failureLookupKeysSchema, failureSignatureSchema, noteEnrichmentContentSchema, noteNodeSchema, noteOccurrenceSchema, } from "./node-schema.js";
2
+ export function isNoteDistillationEnabled(value = process.env.CAIRN_NOTE_DISTILLATION) {
3
+ return /^(?:1|true|yes|on)$/i.test(value?.trim() ?? "");
4
+ }