@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,84 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, lstatSync, readdirSync } from "node:fs";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ import { doctorNoteStore } from "./note-store.js";
5
+ import { doctorTypedNodeStore } from "./node-store.js";
6
+ function baseDirectory() {
7
+ const value = process.env.CAIRN_AGENTFS_BASE_DIR?.trim();
8
+ if (!value || value === "~")
9
+ return join(process.env.HOME ?? "", ".cairnkeep");
10
+ if (value.startsWith("~/"))
11
+ return join(process.env.HOME ?? "", value.slice(2));
12
+ return resolve(value);
13
+ }
14
+ function databasePaths(projectRoot) {
15
+ const base = baseDirectory();
16
+ const candidates = [];
17
+ const addFiles = (directory) => {
18
+ if (!existsSync(directory) || lstatSync(directory).isSymbolicLink())
19
+ return;
20
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
21
+ if (candidates.length >= 1024)
22
+ throw new Error("Database enumeration exceeded the 1024-file safety bound.");
23
+ if (entry.isFile() && !entry.isSymbolicLink() && entry.name.endsWith(".db"))
24
+ candidates.push(join(directory, entry.name));
25
+ }
26
+ };
27
+ addFiles(base);
28
+ addFiles(join(base, "projects"));
29
+ const local = join(resolve(projectRoot), ".agentfs", "project.db");
30
+ if (existsSync(local) && !lstatSync(local).isSymbolicLink())
31
+ candidates.push(local);
32
+ return [...new Set(candidates)].sort();
33
+ }
34
+ export async function doctorNodes(options = {}) {
35
+ const scopes = [];
36
+ const issues = [];
37
+ try {
38
+ for (const path of databasePaths(options.projectRoot ?? process.cwd())) {
39
+ const scope = basename(path, ".db");
40
+ const result = await doctorTypedNodeStore({ scope, baseDir: dirname(path), repair: options.repair });
41
+ scopes.push({ scope, path, ...result });
42
+ issues.push(...result.issues.map((issue) => `${scope}: ${issue}`));
43
+ }
44
+ }
45
+ catch (error) {
46
+ issues.push(error instanceof Error ? error.message : String(error));
47
+ }
48
+ const notes = doctorNoteStore(options.repair ?? false);
49
+ issues.push(...notes.issues.map((issue) => `notes: ${issue}`));
50
+ const exists = scopes.length > 0 || notes.exists;
51
+ return {
52
+ schema_version: 1,
53
+ exists,
54
+ ok: issues.length === 0 && scopes.every((scope) => scope.ok) && notes.ok,
55
+ repaired: scopes.some((scope) => scope.repaired) || notes.repaired,
56
+ scopes,
57
+ notes,
58
+ issues,
59
+ };
60
+ }
61
+ async function main() {
62
+ const [command, ...args] = process.argv.slice(2);
63
+ if (command !== "doctor")
64
+ throw new Error("Usage: node node-cli.js doctor [--repair] [--project-root PATH]");
65
+ let repair = false;
66
+ let projectRoot = process.cwd();
67
+ for (let index = 0; index < args.length; index += 1) {
68
+ if (args[index] === "--repair")
69
+ repair = true;
70
+ else if (args[index] === "--project-root" && args[index + 1])
71
+ projectRoot = args[++index];
72
+ else
73
+ throw new Error(`Unknown node doctor option: ${args[index]}`);
74
+ }
75
+ const result = await doctorNodes({ repair, projectRoot });
76
+ process.stdout.write(`${JSON.stringify(result)}\n`);
77
+ process.exitCode = result.ok ? 0 : 1;
78
+ }
79
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname)) {
80
+ main().catch((error) => {
81
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
82
+ process.exitCode = 1;
83
+ });
84
+ }
@@ -0,0 +1,170 @@
1
+ import { z } from "zod";
2
+ export const NODE_SCHEMA_VERSION = 1;
3
+ export const MAX_IMPORT_NODES = 256;
4
+ export const MAX_NODE_VALUE_BYTES = 256 * 1024;
5
+ export const MAX_IMPORT_VALUE_BYTES = 5 * 1024 * 1024;
6
+ const truthyPattern = /^(?:1|true|yes|on)$/i;
7
+ export const coreNodeTypeSchema = z.enum(["memory", "knowledge", "hindsight", "shared", "provenance"]);
8
+ export const noteNodeTypeSchema = z.enum(["hindsight", "knowledge", "shared", "provenance"]);
9
+ const extensionNodeTypeSchema = z.string().regex(/^[a-z][a-z0-9-]{0,31}:[a-z0-9][a-z0-9-]{0,63}$/);
10
+ export const nodeTypeSchema = z.union([coreNodeTypeSchema, extensionNodeTypeSchema]);
11
+ function normalizeTag(value) {
12
+ return value.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/-+/g, "-");
13
+ }
14
+ export const canonicalTagSchema = z.string().transform(normalizeTag).pipe(z.string().regex(/^[a-z0-9][a-z0-9-]{0,127}$/));
15
+ export const canonicalTagsSchema = z.array(canonicalTagSchema).max(64).transform((values) => [...new Set(values)].sort());
16
+ export const nodePathSchema = z.string().min(1).max(1024).superRefine((value, context) => {
17
+ if (value.startsWith("/") || value.endsWith("/") || value.includes("\\") || /[\u0000-\u001f\u007f]/.test(value)) {
18
+ context.addIssue({ code: "custom", message: "Node path must be a relative slash-separated path." });
19
+ return;
20
+ }
21
+ const segments = value.split("/");
22
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
23
+ context.addIssue({ code: "custom", message: "Node path contains an invalid segment." });
24
+ }
25
+ if (segments[0]?.startsWith("__") || segments[0]?.startsWith(".cairnkeep")) {
26
+ context.addIssue({ code: "custom", message: "Node path uses a reserved namespace." });
27
+ }
28
+ });
29
+ export const nodeMetadataSchema = z.object({
30
+ schema_version: z.literal(NODE_SCHEMA_VERSION),
31
+ node_type: nodeTypeSchema,
32
+ tags: canonicalTagsSchema,
33
+ }).strict();
34
+ export const nodeSummarySchema = z.object({
35
+ schema_version: z.literal(NODE_SCHEMA_VERSION),
36
+ address_space: z.enum(["memory", "project-notes", "shared-notes"]),
37
+ scope: z.string(),
38
+ key: nodePathSchema,
39
+ node_type: nodeTypeSchema,
40
+ tags: canonicalTagsSchema,
41
+ }).strict();
42
+ export const failureFrameSchema = z.object({
43
+ function: z.string().min(1).max(512),
44
+ file: z.string().min(1).max(1024),
45
+ }).strict();
46
+ export const failureLookupKeysSchema = z.object({
47
+ full: z.string().regex(/^v1:full:[a-f0-9]{64}$/),
48
+ message_stack: z.string().regex(/^v1:message-stack:[a-f0-9]{64}$/),
49
+ message_component: z.string().regex(/^v1:message-component:[a-f0-9]{64}$/),
50
+ message: z.string().regex(/^v1:message:[a-f0-9]{64}$/),
51
+ }).strict();
52
+ export const failureSignatureSchema = z.object({
53
+ signature_version: z.literal(NODE_SCHEMA_VERSION),
54
+ family: z.string().regex(/^[a-z0-9][a-z0-9-]{0,127}$/),
55
+ normalized_message: z.string().min(1).max(4096),
56
+ stack_digest: z.union([z.literal(""), z.string().regex(/^[a-f0-9]{64}$/)]),
57
+ component: z.string().max(1024),
58
+ frames: z.array(failureFrameSchema).max(16),
59
+ lookup_keys: failureLookupKeysSchema,
60
+ fingerprint: z.string().regex(/^[a-f0-9]{64}$/),
61
+ }).strict();
62
+ export const noteOccurrenceSchema = z.object({
63
+ session_id: z.string().min(1).max(256),
64
+ session_digest: z.string().regex(/^[a-f0-9]{64}$/),
65
+ ended_at: z.iso.datetime(),
66
+ sequence: z.number().int().nonnegative(),
67
+ outcome: z.enum(["failure", "resolution", "abandonment"]),
68
+ tool_name: z.string().min(1).max(256).optional(),
69
+ validation_key: z.string().regex(/^[a-f0-9]{64}$/).optional(),
70
+ evidence: z.string().min(1).max(4096),
71
+ }).strict();
72
+ export const noteEnrichmentContentSchema = z.object({
73
+ summary: z.string().min(1).max(4096),
74
+ lessons: z.array(z.string().min(1).max(1024)).max(16),
75
+ caveats: z.array(z.string().min(1).max(1024)).max(16),
76
+ }).strict();
77
+ export const noteNodeSchema = z.object({
78
+ schema_version: z.literal(NODE_SCHEMA_VERSION),
79
+ id: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,255}$/),
80
+ title: z.string().min(1).max(512),
81
+ description: z.string().min(1).max(4096),
82
+ keywords: z.array(z.string().min(1).max(128)).max(64),
83
+ node_type: noteNodeTypeSchema,
84
+ tags: canonicalTagsSchema,
85
+ project_id: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,255}$/).optional(),
86
+ canonical_id: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,255}$/).optional(),
87
+ status: z.enum(["unresolved", "resolved", "abandoned"]).optional(),
88
+ signature: failureSignatureSchema.optional(),
89
+ enrichment: noteEnrichmentContentSchema.optional(),
90
+ occurrences: z.array(noteOccurrenceSchema).max(1024),
91
+ created_at: z.iso.datetime(),
92
+ updated_at: z.iso.datetime(),
93
+ }).strict().superRefine((note, context) => {
94
+ if (new Set(note.keywords).size !== note.keywords.length) {
95
+ context.addIssue({ code: "custom", path: ["keywords"], message: "Keywords must be unique." });
96
+ }
97
+ if (["hindsight", "provenance"].includes(note.node_type) && (!note.project_id || !note.signature || !note.status)) {
98
+ context.addIssue({ code: "custom", message: "Project hindsight and provenance notes require project_id, signature, and status." });
99
+ }
100
+ if (note.node_type === "provenance" && !note.canonical_id) {
101
+ context.addIssue({ code: "custom", message: "Provenance notes require canonical_id." });
102
+ }
103
+ if (note.node_type === "shared" && (!note.signature || !note.status)) {
104
+ context.addIssue({ code: "custom", message: "Shared hindsight notes require signature and status." });
105
+ }
106
+ });
107
+ export const memoryImportNodeSchema = z.object({
108
+ key: nodePathSchema,
109
+ value: z.string().refine((value) => Buffer.byteLength(value, "utf8") <= MAX_NODE_VALUE_BYTES, "Node value exceeds 256 KiB."),
110
+ node_type: nodeTypeSchema,
111
+ tags: canonicalTagsSchema,
112
+ note: noteNodeSchema.optional(),
113
+ }).strict();
114
+ export const memoryImportEnvelopeSchema = z.object({
115
+ schema_version: z.literal(NODE_SCHEMA_VERSION),
116
+ scope: z.string().min(1).refine((value) => value !== "all", 'Import scope must be concrete; "all" is read-only.'),
117
+ address_space: z.enum(["memory", "project-notes", "shared-notes"]).default("memory"),
118
+ import_id: z.string().max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/).optional(),
119
+ conflict_policy: z.enum(["reject", "supersede"]).default("reject"),
120
+ dry_run: z.boolean().default(false),
121
+ nodes: z.array(memoryImportNodeSchema).min(1).max(MAX_IMPORT_NODES),
122
+ }).strict().superRefine((envelope, context) => {
123
+ const keys = new Set();
124
+ let totalBytes = 0;
125
+ envelope.nodes.forEach((node, index) => {
126
+ totalBytes += Buffer.byteLength(node.value, "utf8");
127
+ if (keys.has(node.key))
128
+ context.addIssue({ code: "custom", path: ["nodes", index, "key"], message: "Import keys must be unique." });
129
+ keys.add(node.key);
130
+ if (envelope.address_space !== "memory") {
131
+ const noteRequired = ["hindsight", "shared", "provenance"].includes(node.node_type);
132
+ if (noteRequired && !node.note)
133
+ context.addIssue({ code: "custom", path: ["nodes", index, "note"], message: "This note type requires a complete note record." });
134
+ if (node.note && (node.note.node_type !== node.node_type || JSON.stringify(node.note.tags) !== JSON.stringify(node.tags))) {
135
+ context.addIssue({ code: "custom", path: ["nodes", index], message: "Import envelope type/tags must agree with note front matter." });
136
+ }
137
+ }
138
+ });
139
+ if (totalBytes > MAX_IMPORT_VALUE_BYTES)
140
+ context.addIssue({ code: "custom", path: ["nodes"], message: "Aggregate node values exceed 5 MiB." });
141
+ });
142
+ export const memoryImportActionSchema = z.object({
143
+ key: nodePathSchema,
144
+ action: z.enum(["would_create", "would_replace", "created", "replaced", "unchanged", "rejected"]),
145
+ code: z.string().optional(),
146
+ }).strict();
147
+ export const memoryImportCountsSchema = z.object({
148
+ created: z.number().int().nonnegative().optional(),
149
+ replaced: z.number().int().nonnegative().optional(),
150
+ would_create: z.number().int().nonnegative().optional(),
151
+ would_replace: z.number().int().nonnegative().optional(),
152
+ unchanged: z.number().int().nonnegative(),
153
+ rejected: z.number().int().nonnegative(),
154
+ }).strict();
155
+ export const memoryImportResultSchema = z.object({
156
+ schema_version: z.literal(NODE_SCHEMA_VERSION),
157
+ scope: z.string(),
158
+ address_space: z.enum(["memory", "project-notes", "shared-notes"]),
159
+ batch_digest: z.string().regex(/^[a-f0-9]{64}$/),
160
+ import_id: z.string().optional(),
161
+ dry_run: z.boolean(),
162
+ conflict_policy: z.enum(["reject", "supersede"]),
163
+ committed: z.boolean(),
164
+ replayed: z.boolean().optional(),
165
+ counts: memoryImportCountsSchema,
166
+ actions: z.array(memoryImportActionSchema).max(MAX_IMPORT_NODES),
167
+ }).strict();
168
+ export function isTypedMemoryNodesEnabled(value = process.env.CAIRN_TYPED_MEMORY_NODES) {
169
+ return truthyPattern.test(value?.trim() ?? "");
170
+ }