@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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { nodePathSchema } from "./node-schema.js";
|
|
5
|
+
export const ARTIFACT_SCHEMA_VERSION = 1;
|
|
6
|
+
export const ARTIFACT_KINDS = [
|
|
7
|
+
"compaction_summary",
|
|
8
|
+
"diff",
|
|
9
|
+
"test_output",
|
|
10
|
+
"generated_file",
|
|
11
|
+
];
|
|
12
|
+
export const ARTIFACT_DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
13
|
+
export const ARTIFACT_DEFAULT_SESSION_MAX_BYTES = 16 * 1024 * 1024;
|
|
14
|
+
export const ARTIFACT_DEFAULT_STORE_MAX_BYTES = 256 * 1024 * 1024;
|
|
15
|
+
export const ARTIFACT_DEFAULT_RETENTION_DAYS = 30;
|
|
16
|
+
export const COMPACTION_DEFAULT_MAX_REVISIONS = 8;
|
|
17
|
+
export const GENERATED_FILE_MAX_SNAPSHOT_BYTES = 256 * 1024;
|
|
18
|
+
const truthyPattern = /^(?:1|true|yes|on)$/i;
|
|
19
|
+
const safeIdentifierSchema = z.string().min(1).max(256).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
20
|
+
const boundedTextSchema = z.string().max(64 * 1024 * 1024);
|
|
21
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
22
|
+
const nonnegativeIntegerSchema = z.number().int().nonnegative();
|
|
23
|
+
export const artifactKindSchema = z.enum(ARTIFACT_KINDS);
|
|
24
|
+
export const artifactIdSchema = z.string().regex(/^art_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
25
|
+
export const artifactSessionRefSchema = safeIdentifierSchema;
|
|
26
|
+
export const artifactMediaTypeSchema = z.string().min(1).max(255).regex(/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+\/-]*$/);
|
|
27
|
+
export const artifactNodeRefSchema = z.object({
|
|
28
|
+
scope: z.string().min(1).max(256).refine((value) => value !== "all", 'Node scope must be concrete; "all" is read-only.'),
|
|
29
|
+
address_space: z.enum(["memory", "project-notes", "shared-notes"]),
|
|
30
|
+
key: nodePathSchema,
|
|
31
|
+
}).strict();
|
|
32
|
+
export const artifactProvenanceSchema = z.object({
|
|
33
|
+
producer: z.string().min(1).max(256).refine((value) => !/[\u0000-\u001f\u007f]/.test(value)),
|
|
34
|
+
source_event: z.string().min(1).max(256).optional(),
|
|
35
|
+
harness: z.enum(["claude-code", "opencode", "pi"]).optional(),
|
|
36
|
+
harness_version: z.string().min(1).max(128).optional(),
|
|
37
|
+
native_id: z.string().min(1).max(256).optional(),
|
|
38
|
+
}).strict();
|
|
39
|
+
export const compactionCompletenessSchema = z.object({
|
|
40
|
+
task_goals: z.enum(["complete", "partial", "missing"]),
|
|
41
|
+
decisions_made: z.enum(["complete", "partial", "missing"]),
|
|
42
|
+
open_todos: z.enum(["complete", "partial", "missing"]),
|
|
43
|
+
critical_error_traces: z.enum(["complete", "partial", "missing"]),
|
|
44
|
+
}).strict();
|
|
45
|
+
const compactionBase = {
|
|
46
|
+
raw_summary: boundedTextSchema,
|
|
47
|
+
task_goals: z.array(boundedTextSchema).max(256),
|
|
48
|
+
decisions_made: z.array(boundedTextSchema).max(256),
|
|
49
|
+
open_todos: z.array(boundedTextSchema).max(256),
|
|
50
|
+
critical_error_traces: z.array(boundedTextSchema).max(256),
|
|
51
|
+
completeness: compactionCompletenessSchema,
|
|
52
|
+
trigger: z.string().min(1).max(128),
|
|
53
|
+
};
|
|
54
|
+
export const compactionSummaryInputContentSchema = z.object(compactionBase).strict();
|
|
55
|
+
export const compactionSummaryContentSchema = z.object({
|
|
56
|
+
...compactionBase,
|
|
57
|
+
revision: z.number().int().positive(),
|
|
58
|
+
}).strict();
|
|
59
|
+
export const diffContentSchema = z.object({ text: boundedTextSchema }).strict();
|
|
60
|
+
export const testOutputContentSchema = z.object({
|
|
61
|
+
text: boundedTextSchema,
|
|
62
|
+
exit_code: z.number().int().optional(),
|
|
63
|
+
status: z.enum(["passed", "failed", "unknown"]).optional(),
|
|
64
|
+
}).strict();
|
|
65
|
+
export const generatedFilePathLabelSchema = z.string().min(1).max(1024).superRefine((value, context) => {
|
|
66
|
+
if (isAbsolute(value) || value.startsWith("/") || value.endsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
67
|
+
context.addIssue({ code: "custom", message: "Generated-file path label must be project-relative." });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const segments = value.split("/");
|
|
71
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
72
|
+
context.addIssue({ code: "custom", message: "Generated-file path label contains an invalid segment." });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
export const generatedFileInputContentSchema = z.object({
|
|
76
|
+
path_label: generatedFilePathLabelSchema,
|
|
77
|
+
file_digest: digestSchema,
|
|
78
|
+
logical_bytes: nonnegativeIntegerSchema,
|
|
79
|
+
binary: z.boolean(),
|
|
80
|
+
snapshot: boundedTextSchema.optional(),
|
|
81
|
+
}).strict();
|
|
82
|
+
export const generatedFileContentSchema = z.object({
|
|
83
|
+
path_label: generatedFilePathLabelSchema,
|
|
84
|
+
file_digest: digestSchema,
|
|
85
|
+
logical_bytes: nonnegativeIntegerSchema,
|
|
86
|
+
binary: z.boolean(),
|
|
87
|
+
snapshot: boundedTextSchema.optional(),
|
|
88
|
+
metadata_only: z.boolean().optional(),
|
|
89
|
+
}).strict();
|
|
90
|
+
const writeBase = {
|
|
91
|
+
session_ref: artifactSessionRefSchema,
|
|
92
|
+
node_ref: artifactNodeRefSchema.optional(),
|
|
93
|
+
media_type: artifactMediaTypeSchema,
|
|
94
|
+
provenance: artifactProvenanceSchema,
|
|
95
|
+
supersedes: artifactIdSchema.optional(),
|
|
96
|
+
};
|
|
97
|
+
export const artifactWriteInputSchema = z.discriminatedUnion("kind", [
|
|
98
|
+
z.object({ kind: z.literal("compaction_summary"), ...writeBase, content: compactionSummaryInputContentSchema }).strict(),
|
|
99
|
+
z.object({ kind: z.literal("diff"), ...writeBase, content: diffContentSchema }).strict(),
|
|
100
|
+
z.object({ kind: z.literal("test_output"), ...writeBase, content: testOutputContentSchema }).strict(),
|
|
101
|
+
z.object({ kind: z.literal("generated_file"), ...writeBase, content: generatedFileInputContentSchema }).strict(),
|
|
102
|
+
]);
|
|
103
|
+
export const artifactWriteSchema = artifactWriteInputSchema;
|
|
104
|
+
const envelopeBase = {
|
|
105
|
+
schema_version: z.literal(ARTIFACT_SCHEMA_VERSION),
|
|
106
|
+
artifact_id: artifactIdSchema,
|
|
107
|
+
created_at: z.iso.datetime(),
|
|
108
|
+
session_ref: artifactSessionRefSchema,
|
|
109
|
+
node_ref: artifactNodeRefSchema.optional(),
|
|
110
|
+
media_type: artifactMediaTypeSchema,
|
|
111
|
+
logical_bytes: nonnegativeIntegerSchema,
|
|
112
|
+
stored_bytes: nonnegativeIntegerSchema,
|
|
113
|
+
content_digest: digestSchema,
|
|
114
|
+
provenance: artifactProvenanceSchema,
|
|
115
|
+
redaction: z.object({ applied: z.boolean(), replacement_count: nonnegativeIntegerSchema }).strict(),
|
|
116
|
+
truncation: z.object({
|
|
117
|
+
truncated: z.boolean(),
|
|
118
|
+
reason: z.enum(["artifact_limit", "generated_file_metadata_only"]).optional(),
|
|
119
|
+
original_bytes: nonnegativeIntegerSchema,
|
|
120
|
+
stored_bytes: nonnegativeIntegerSchema,
|
|
121
|
+
}).strict(),
|
|
122
|
+
supersedes: artifactIdSchema.optional(),
|
|
123
|
+
};
|
|
124
|
+
export const artifactEnvelopeSchema = z.discriminatedUnion("kind", [
|
|
125
|
+
z.object({ kind: z.literal("compaction_summary"), ...envelopeBase, content: compactionSummaryContentSchema }).strict(),
|
|
126
|
+
z.object({ kind: z.literal("diff"), ...envelopeBase, content: diffContentSchema }).strict(),
|
|
127
|
+
z.object({ kind: z.literal("test_output"), ...envelopeBase, content: testOutputContentSchema }).strict(),
|
|
128
|
+
z.object({ kind: z.literal("generated_file"), ...envelopeBase, content: generatedFileContentSchema }).strict(),
|
|
129
|
+
]);
|
|
130
|
+
export const artifactListOutputSchema = z.object({
|
|
131
|
+
schema_version: z.literal(ARTIFACT_SCHEMA_VERSION),
|
|
132
|
+
artifacts: z.array(artifactEnvelopeSchema),
|
|
133
|
+
logical_bytes: nonnegativeIntegerSchema,
|
|
134
|
+
next_cursor: z.string().optional(),
|
|
135
|
+
}).strict();
|
|
136
|
+
export const artifactDoctorOutputSchema = z.object({
|
|
137
|
+
schema_version: z.literal(ARTIFACT_SCHEMA_VERSION),
|
|
138
|
+
ok: z.boolean(),
|
|
139
|
+
repaired: z.boolean(),
|
|
140
|
+
integrity: z.enum(["ok", "failed"]),
|
|
141
|
+
issues: z.array(z.string()),
|
|
142
|
+
}).strict();
|
|
143
|
+
function integerSetting(name, fallback, minimum) {
|
|
144
|
+
const raw = process.env[name]?.trim();
|
|
145
|
+
if (!raw)
|
|
146
|
+
return fallback;
|
|
147
|
+
const value = Number(raw);
|
|
148
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
149
|
+
throw new Error(`${name} must be an integer greater than or equal to ${minimum}.`);
|
|
150
|
+
}
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
export function getArtifactLimits() {
|
|
154
|
+
const artifactMaxBytes = integerSetting("CAIRN_ARTIFACT_MAX_BYTES", ARTIFACT_DEFAULT_MAX_BYTES, 1024);
|
|
155
|
+
const sessionMaxBytes = integerSetting("CAIRN_ARTIFACT_SESSION_MAX_BYTES", ARTIFACT_DEFAULT_SESSION_MAX_BYTES, 1024);
|
|
156
|
+
const storeMaxBytes = integerSetting("CAIRN_ARTIFACT_STORE_MAX_BYTES", ARTIFACT_DEFAULT_STORE_MAX_BYTES, 1024);
|
|
157
|
+
const retentionDays = integerSetting("CAIRN_ARTIFACT_RETENTION_DAYS", ARTIFACT_DEFAULT_RETENTION_DAYS, 0);
|
|
158
|
+
const compactionMaxRevisions = integerSetting("CAIRN_COMPACTION_MAX_REVISIONS", COMPACTION_DEFAULT_MAX_REVISIONS, 1);
|
|
159
|
+
const generatedFileSnapshotMaxBytes = integerSetting("CAIRN_ARTIFACT_GENERATED_FILE_SNAPSHOT_MAX_BYTES", GENERATED_FILE_MAX_SNAPSHOT_BYTES, 0);
|
|
160
|
+
if (sessionMaxBytes < artifactMaxBytes) {
|
|
161
|
+
throw new Error("Artifact session maximum must be at least the per-artifact maximum.");
|
|
162
|
+
}
|
|
163
|
+
if (storeMaxBytes < sessionMaxBytes) {
|
|
164
|
+
throw new Error("Artifact store maximum must be at least the per-session maximum.");
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
artifactMaxBytes,
|
|
168
|
+
sessionMaxBytes,
|
|
169
|
+
storeMaxBytes,
|
|
170
|
+
retentionDays,
|
|
171
|
+
compactionMaxRevisions,
|
|
172
|
+
generatedFileSnapshotMaxBytes: Math.min(generatedFileSnapshotMaxBytes, artifactMaxBytes),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export function isArtifactStoreEnabled(value = process.env.CAIRN_ARTIFACT_STORE) {
|
|
176
|
+
return truthyPattern.test(value?.trim() ?? "");
|
|
177
|
+
}
|
|
178
|
+
export function isArtifactHttpEnabled(value = process.env.CAIRN_ARTIFACT_HTTP) {
|
|
179
|
+
return truthyPattern.test(value?.trim() ?? "");
|
|
180
|
+
}
|
|
181
|
+
export function isCompactionCaptureEnabled(value = process.env.CAIRN_COMPACTION_CAPTURE) {
|
|
182
|
+
return truthyPattern.test(value?.trim() ?? "");
|
|
183
|
+
}
|
|
184
|
+
function sortCanonical(value) {
|
|
185
|
+
if (Array.isArray(value))
|
|
186
|
+
return value.map(sortCanonical);
|
|
187
|
+
if (value && typeof value === "object") {
|
|
188
|
+
return Object.fromEntries(Object.entries(value)
|
|
189
|
+
.sort(([left], [right]) => left.localeCompare(right, "en"))
|
|
190
|
+
.map(([key, child]) => [key, sortCanonical(child)]));
|
|
191
|
+
}
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
export function canonicalJson(value) {
|
|
195
|
+
return JSON.stringify(sortCanonical(value));
|
|
196
|
+
}
|
|
197
|
+
export function canonicalBytes(value) {
|
|
198
|
+
return Buffer.from(canonicalJson(value), "utf8");
|
|
199
|
+
}
|
|
200
|
+
export function sha256Hex(value) {
|
|
201
|
+
return createHash("sha256").update(value).digest("hex");
|
|
202
|
+
}
|
|
203
|
+
export function truncateUtf8(value, maxBytes) {
|
|
204
|
+
const originalBytes = Buffer.byteLength(value, "utf8");
|
|
205
|
+
if (originalBytes <= maxBytes) {
|
|
206
|
+
return { text: value, original_bytes: originalBytes, stored_bytes: originalBytes, truncated: false };
|
|
207
|
+
}
|
|
208
|
+
let storedBytes = 0;
|
|
209
|
+
let text = "";
|
|
210
|
+
for (const character of value) {
|
|
211
|
+
const bytes = Buffer.byteLength(character, "utf8");
|
|
212
|
+
if (storedBytes + bytes > maxBytes)
|
|
213
|
+
break;
|
|
214
|
+
text += character;
|
|
215
|
+
storedBytes += bytes;
|
|
216
|
+
}
|
|
217
|
+
return { text, original_bytes: originalBytes, stored_bytes: storedBytes, truncated: true };
|
|
218
|
+
}
|