@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.
- package/CHANGELOG.md +85 -0
- package/README.md +232 -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 +4 -3
- package/docs/design/meta-agent.md +439 -0
- package/docs/harness-compatibility.md +117 -0
- package/docs/learning/CURRICULUM-MAP.md +91 -0
- package/docs/learning/FEATURE-GUIDE.md +86 -0
- package/docs/learning/PRODUCTION-PLAN.md +45 -14
- package/docs/learning/README.md +20 -1
- package/docs/learning/instructor-guide.md +6 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +20 -8
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +23 -5
- package/docs/learning/lessons/L03-first-project.md +7 -2
- package/docs/learning/lessons/L04-memory-fundamentals.md +4 -0
- package/docs/learning/lessons/L05-daily-workflow.md +4 -0
- package/docs/learning/lessons/L07-privacy-and-storage.md +3 -0
- package/docs/learning/lessons/L10-context-exploration.md +4 -2
- package/docs/learning/lessons/L12-managed-overlays.md +2 -0
- package/docs/learning/lessons/L13-session-evidence.md +71 -0
- package/docs/learning/lessons/L14-typed-memory.md +51 -0
- package/docs/learning/lessons/L15-capability-governance.md +56 -0
- package/docs/learning/lessons/L16-evaluation.md +67 -0
- package/docs/learning/lessons/L17-agent-boundary.md +55 -0
- package/docs/learning/templates/lesson-template.md +4 -1
- package/docs/learning/tracks/evidence-and-evaluation.md +20 -0
- package/docs/learning/tracks/operator.md +8 -2
- package/docs/learning/tracks/practitioner.md +6 -1
- package/docs/learning/tracks/quickstart.md +3 -0
- package/docs/learning/video-scripts/L00-why-cairnkeep.md +21 -17
- package/docs/learning/video-scripts/L02-installation.md +9 -2
- package/docs/learning/video-scripts/L03-first-project.md +3 -0
- package/docs/operating.md +567 -7
- 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 +8 -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 +6 -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 +49 -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 +630 -3
- package/scripts/test-learning-docs.sh +20 -4
- package/scripts/test-notes-cli.sh +114 -0
- package/scripts/test-package-install.sh +254 -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 +38 -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-kimi.sh.template +39 -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,155 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const MAX_CUSTOM_PATTERNS = 32;
|
|
5
|
+
const MAX_PATTERN_LENGTH = 256;
|
|
6
|
+
const MAX_REPLACEMENT_LENGTH = 128;
|
|
7
|
+
const MAX_REDACTION_CANDIDATE_BYTES = 64 * 1024 * 1024;
|
|
8
|
+
const REDACTED = "[REDACTED]";
|
|
9
|
+
const redactionConfigSchema = z.object({
|
|
10
|
+
version: z.literal(1),
|
|
11
|
+
patterns: z.array(z.object({
|
|
12
|
+
pattern: z.string().min(1).max(MAX_PATTERN_LENGTH),
|
|
13
|
+
flags: z.string().regex(/^[gimsu]*$/).optional(),
|
|
14
|
+
replacement: z.string().max(MAX_REPLACEMENT_LENGTH).optional(),
|
|
15
|
+
}).strict()).max(MAX_CUSTOM_PATTERNS),
|
|
16
|
+
}).strict();
|
|
17
|
+
const secretKeyPattern = /^(?:authorization|password|passwd|secret|token|api[_-]?key|access[_-]?token|refresh[_-]?token|private[_-]?key)$/i;
|
|
18
|
+
const builtinPatterns = [
|
|
19
|
+
{ regex: /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, replacement: "Bearer [REDACTED]" },
|
|
20
|
+
{ regex: /\bsk-[A-Za-z0-9_-]{8,}/g, replacement: "[REDACTED:API_KEY]" },
|
|
21
|
+
{
|
|
22
|
+
regex: /\b(password|passwd|api[_-]?key|token|secret)\s*[:=]\s*[^\s"'&,;]+/gi,
|
|
23
|
+
replacement: "$1=[REDACTED]",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
regex: /([a-z][a-z0-9+.-]{0,31}:\/\/)[^/\s:@]+:[^/\s@]+@/gi,
|
|
27
|
+
replacement: "$1[REDACTED]@",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
31
|
+
replacement: "[REDACTED:PRIVATE_KEY]",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
function assertContainedConfigPath(projectRoot, configuredPath) {
|
|
35
|
+
const allowedRoot = resolve(projectRoot);
|
|
36
|
+
const candidate = resolve(projectRoot, configuredPath);
|
|
37
|
+
const rel = relative(allowedRoot, candidate);
|
|
38
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
39
|
+
throw new Error("Trajectory redaction configuration must be inside the project root.");
|
|
40
|
+
}
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
function compileCustomPatterns(projectRoot) {
|
|
44
|
+
const explicitPath = process.env.CAIRN_REDACTION_FILE?.trim()
|
|
45
|
+
|| process.env.CAIRN_TRAJECTORY_REDACTION_FILE?.trim();
|
|
46
|
+
const defaultPath = ".ai/trajectory-redaction.json";
|
|
47
|
+
const configuredPath = explicitPath || (existsSync(resolve(projectRoot, defaultPath)) ? defaultPath : "");
|
|
48
|
+
if (!configuredPath)
|
|
49
|
+
return [];
|
|
50
|
+
const path = assertContainedConfigPath(projectRoot, configuredPath);
|
|
51
|
+
const parsed = redactionConfigSchema.parse(JSON.parse(readFileSync(path, "utf8")));
|
|
52
|
+
return parsed.patterns.map((entry) => {
|
|
53
|
+
const flags = entry.flags?.includes("g") ? entry.flags : `${entry.flags ?? ""}g`;
|
|
54
|
+
return {
|
|
55
|
+
regex: new RegExp(entry.pattern, flags),
|
|
56
|
+
replacement: entry.replacement ?? "[REDACTED:CUSTOM]",
|
|
57
|
+
literalReplacement: true,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function secretEnvironmentValues() {
|
|
62
|
+
const values = [];
|
|
63
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
64
|
+
if (!value || !/(?:^|_)(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)$/i.test(name))
|
|
65
|
+
continue;
|
|
66
|
+
if (value.length < 6)
|
|
67
|
+
continue;
|
|
68
|
+
values.push(value);
|
|
69
|
+
}
|
|
70
|
+
return Array.from(new Set(values)).sort((a, b) => b.length - a.length);
|
|
71
|
+
}
|
|
72
|
+
function redactString(value, patterns, environmentSecrets, metadata) {
|
|
73
|
+
let result = value;
|
|
74
|
+
for (const secret of environmentSecrets) {
|
|
75
|
+
if (!result.includes(secret))
|
|
76
|
+
continue;
|
|
77
|
+
const segments = result.split(secret);
|
|
78
|
+
metadata.replacementCount += segments.length - 1;
|
|
79
|
+
result = segments.join("[REDACTED:ENV]");
|
|
80
|
+
}
|
|
81
|
+
for (const { regex, replacement, literalReplacement } of patterns) {
|
|
82
|
+
regex.lastIndex = 0;
|
|
83
|
+
const matches = result.match(regex);
|
|
84
|
+
metadata.replacementCount += matches?.length ?? 0;
|
|
85
|
+
regex.lastIndex = 0;
|
|
86
|
+
result = literalReplacement
|
|
87
|
+
? result.replace(regex, () => replacement)
|
|
88
|
+
: result.replace(regex, replacement);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function redactValue(value, patterns, environmentSecrets, metadata, key) {
|
|
93
|
+
if (key && secretKeyPattern.test(key)) {
|
|
94
|
+
metadata.replacementCount += 1;
|
|
95
|
+
return REDACTED;
|
|
96
|
+
}
|
|
97
|
+
if (typeof value === "string")
|
|
98
|
+
return redactString(value, patterns, environmentSecrets, metadata);
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
return value.map((item) => redactValue(item, patterns, environmentSecrets, metadata));
|
|
101
|
+
}
|
|
102
|
+
if (value && typeof value === "object") {
|
|
103
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, childValue]) => [
|
|
104
|
+
childKey,
|
|
105
|
+
redactValue(childValue, patterns, environmentSecrets, metadata, childKey),
|
|
106
|
+
]));
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
function boundedCandidateBytes(value, remaining = MAX_REDACTION_CANDIDATE_BYTES) {
|
|
111
|
+
if (remaining < 0)
|
|
112
|
+
throw new Error("Redaction candidate exceeds the in-memory safety bound.");
|
|
113
|
+
if (value === null || typeof value === "boolean" || typeof value === "number")
|
|
114
|
+
return 16;
|
|
115
|
+
if (typeof value === "string") {
|
|
116
|
+
const bytes = Buffer.byteLength(value, "utf8");
|
|
117
|
+
if (bytes > remaining)
|
|
118
|
+
throw new Error("Redaction candidate exceeds the in-memory safety bound.");
|
|
119
|
+
return bytes;
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(value)) {
|
|
122
|
+
let total = 2;
|
|
123
|
+
for (const item of value) {
|
|
124
|
+
total += boundedCandidateBytes(item, remaining - total) + 1;
|
|
125
|
+
if (total > remaining)
|
|
126
|
+
throw new Error("Redaction candidate exceeds the in-memory safety bound.");
|
|
127
|
+
}
|
|
128
|
+
return total;
|
|
129
|
+
}
|
|
130
|
+
if (value && typeof value === "object") {
|
|
131
|
+
let total = 2;
|
|
132
|
+
for (const [key, childValue] of Object.entries(value)) {
|
|
133
|
+
total += Buffer.byteLength(key, "utf8") + 3;
|
|
134
|
+
total += boundedCandidateBytes(childValue, remaining - total) + 1;
|
|
135
|
+
if (total > remaining)
|
|
136
|
+
throw new Error("Redaction candidate exceeds the in-memory safety bound.");
|
|
137
|
+
}
|
|
138
|
+
return total;
|
|
139
|
+
}
|
|
140
|
+
return 16;
|
|
141
|
+
}
|
|
142
|
+
export function redactLocalValue(value, projectRoot) {
|
|
143
|
+
boundedCandidateBytes(value);
|
|
144
|
+
const patterns = [...builtinPatterns, ...compileCustomPatterns(projectRoot)];
|
|
145
|
+
const metadata = { replacementCount: 0 };
|
|
146
|
+
const redacted = redactValue(value, patterns, secretEnvironmentValues(), metadata);
|
|
147
|
+
return {
|
|
148
|
+
value: redacted,
|
|
149
|
+
applied: metadata.replacementCount > 0,
|
|
150
|
+
replacement_count: metadata.replacementCount,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function redactTrajectory(session, projectRoot) {
|
|
154
|
+
return redactLocalValue(session, projectRoot).value;
|
|
155
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const TRAJECTORY_SCHEMA_VERSION = 1;
|
|
3
|
+
export const TRAJECTORY_DEFAULT_SESSION_MAX_BYTES = 5 * 1024 * 1024;
|
|
4
|
+
export const TRAJECTORY_DEFAULT_STORE_MAX_BYTES = 256 * 1024 * 1024;
|
|
5
|
+
export const TRAJECTORY_DEFAULT_RETENTION_DAYS = 30;
|
|
6
|
+
export const trajectoryUsageSchema = z.object({
|
|
7
|
+
input_tokens: z.number().nonnegative().optional(),
|
|
8
|
+
output_tokens: z.number().nonnegative().optional(),
|
|
9
|
+
reasoning_tokens: z.number().nonnegative().optional(),
|
|
10
|
+
cache_read_tokens: z.number().nonnegative().optional(),
|
|
11
|
+
cache_write_tokens: z.number().nonnegative().optional(),
|
|
12
|
+
cost: z.number().nonnegative().optional(),
|
|
13
|
+
}).strict();
|
|
14
|
+
export const trajectoryEventSchema = z.object({
|
|
15
|
+
sequence: z.number().int().nonnegative(),
|
|
16
|
+
kind: z.enum([
|
|
17
|
+
"user_message",
|
|
18
|
+
"model_output",
|
|
19
|
+
"tool_invocation",
|
|
20
|
+
"tool_result",
|
|
21
|
+
"system_event",
|
|
22
|
+
"usage",
|
|
23
|
+
]),
|
|
24
|
+
timestamp: z.iso.datetime().optional(),
|
|
25
|
+
native_id: z.string().optional(),
|
|
26
|
+
parent_id: z.string().optional(),
|
|
27
|
+
payload: z.record(z.string(), z.unknown()),
|
|
28
|
+
truncation: z.object({
|
|
29
|
+
original_bytes: z.number().int().nonnegative(),
|
|
30
|
+
stored_bytes: z.number().int().nonnegative(),
|
|
31
|
+
}).strict().optional(),
|
|
32
|
+
}).strict();
|
|
33
|
+
export const trajectorySessionSchema = z.object({
|
|
34
|
+
schema_version: z.literal(TRAJECTORY_SCHEMA_VERSION),
|
|
35
|
+
session_id: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/),
|
|
36
|
+
harness: z.enum(["claude-code", "opencode", "pi"]),
|
|
37
|
+
project_root: z.string().min(1),
|
|
38
|
+
started_at: z.iso.datetime(),
|
|
39
|
+
ended_at: z.iso.datetime(),
|
|
40
|
+
usage: trajectoryUsageSchema.optional(),
|
|
41
|
+
events: z.array(trajectoryEventSchema),
|
|
42
|
+
capture: z.object({
|
|
43
|
+
captured_at: z.iso.datetime(),
|
|
44
|
+
omitted_reasoning_blocks: z.number().int().nonnegative(),
|
|
45
|
+
omitted_unknown_records: z.number().int().nonnegative(),
|
|
46
|
+
omitted_size_events: z.number().int().nonnegative().optional(),
|
|
47
|
+
truncated: z.boolean(),
|
|
48
|
+
original_bytes: z.number().int().nonnegative().optional(),
|
|
49
|
+
stored_bytes: z.number().int().nonnegative().optional(),
|
|
50
|
+
}).strict(),
|
|
51
|
+
}).strict();
|
|
52
|
+
function integerSetting(name, fallback, minimum) {
|
|
53
|
+
const raw = process.env[name]?.trim();
|
|
54
|
+
if (!raw)
|
|
55
|
+
return fallback;
|
|
56
|
+
const value = Number(raw);
|
|
57
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
58
|
+
throw new Error(`${name} must be an integer greater than or equal to ${minimum}.`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
export function getTrajectoryLimits() {
|
|
63
|
+
const sessionMaxBytes = integerSetting("CAIRN_TRAJECTORY_SESSION_MAX_BYTES", TRAJECTORY_DEFAULT_SESSION_MAX_BYTES, 1024);
|
|
64
|
+
const storeMaxBytes = integerSetting("CAIRN_TRAJECTORY_STORE_MAX_BYTES", TRAJECTORY_DEFAULT_STORE_MAX_BYTES, 1024);
|
|
65
|
+
const retentionDays = integerSetting("CAIRN_TRAJECTORY_RETENTION_DAYS", TRAJECTORY_DEFAULT_RETENTION_DAYS, 0);
|
|
66
|
+
if (storeMaxBytes < sessionMaxBytes) {
|
|
67
|
+
throw new Error("CAIRN_TRAJECTORY_STORE_MAX_BYTES must be at least the session maximum.");
|
|
68
|
+
}
|
|
69
|
+
return { sessionMaxBytes, storeMaxBytes, retentionDays };
|
|
70
|
+
}
|
|
71
|
+
export function isTrajectoryCaptureEnabled(value = process.env.CAIRN_TRAJECTORY_CAPTURE) {
|
|
72
|
+
return /^(?:1|true|yes|on)$/i.test(value?.trim() ?? "");
|
|
73
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { AgentFS } from "agentfs-sdk";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { TRAJECTORY_SCHEMA_VERSION, trajectorySessionSchema, } from "./trajectory-schema.js";
|
|
6
|
+
const META_KEY = "trajectory/meta/schema-version";
|
|
7
|
+
const SESSION_PREFIX = "trajectory/session/";
|
|
8
|
+
const INDEX_PREFIX = "trajectory/index/";
|
|
9
|
+
const indexSchema = z.object({
|
|
10
|
+
schema_version: z.literal(TRAJECTORY_SCHEMA_VERSION),
|
|
11
|
+
session_id: z.string(),
|
|
12
|
+
harness: z.enum(["claude-code", "opencode", "pi"]),
|
|
13
|
+
started_at: z.iso.datetime(),
|
|
14
|
+
ended_at: z.iso.datetime(),
|
|
15
|
+
event_count: z.number().int().nonnegative(),
|
|
16
|
+
logical_bytes: z.number().int().nonnegative(),
|
|
17
|
+
full_key: z.string(),
|
|
18
|
+
}).strict();
|
|
19
|
+
function trajectoryDbPath(projectRoot = process.cwd()) {
|
|
20
|
+
return resolve(projectRoot, ".agentfs", "trajectory.db");
|
|
21
|
+
}
|
|
22
|
+
async function openTrajectoryStore(projectRoot, create) {
|
|
23
|
+
const dbPath = trajectoryDbPath(projectRoot);
|
|
24
|
+
if (!create && !existsSync(dbPath))
|
|
25
|
+
return null;
|
|
26
|
+
if (create)
|
|
27
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
28
|
+
const agent = await AgentFS.open({ id: "trajectory", path: dbPath });
|
|
29
|
+
if (create) {
|
|
30
|
+
try {
|
|
31
|
+
chmodSync(dbPath, 0o600);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
await agent.close();
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return agent;
|
|
39
|
+
}
|
|
40
|
+
function sessionKey(sessionId) {
|
|
41
|
+
return `${SESSION_PREFIX}${sessionId}`;
|
|
42
|
+
}
|
|
43
|
+
function indexKey(endedAt, sessionId) {
|
|
44
|
+
const epoch = Date.parse(endedAt);
|
|
45
|
+
if (!Number.isFinite(epoch))
|
|
46
|
+
throw new Error("Trajectory ended_at is invalid.");
|
|
47
|
+
return `${INDEX_PREFIX}${String(epoch).padStart(16, "0")}/${sessionId}`;
|
|
48
|
+
}
|
|
49
|
+
function makeIndex(session) {
|
|
50
|
+
return {
|
|
51
|
+
schema_version: TRAJECTORY_SCHEMA_VERSION,
|
|
52
|
+
session_id: session.session_id,
|
|
53
|
+
harness: session.harness,
|
|
54
|
+
started_at: session.started_at,
|
|
55
|
+
ended_at: session.ended_at,
|
|
56
|
+
event_count: session.events.length,
|
|
57
|
+
logical_bytes: Buffer.byteLength(JSON.stringify(session), "utf8"),
|
|
58
|
+
full_key: sessionKey(session.session_id),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function inImmediateTransaction(agent, operation) {
|
|
62
|
+
const transaction = agent.getDatabase().transaction(operation);
|
|
63
|
+
const immediate = transaction.immediate;
|
|
64
|
+
return immediate();
|
|
65
|
+
}
|
|
66
|
+
async function readIndexes(agent) {
|
|
67
|
+
const rows = await agent.kv.list(INDEX_PREFIX);
|
|
68
|
+
return rows.map(({ key, value }) => ({ key, value: indexSchema.parse(value) }));
|
|
69
|
+
}
|
|
70
|
+
async function assertCompatibleSchema(agent, allowMissing) {
|
|
71
|
+
const version = await agent.kv.get(META_KEY);
|
|
72
|
+
if (version === undefined) {
|
|
73
|
+
if (allowMissing)
|
|
74
|
+
return;
|
|
75
|
+
const [sessions, indexes] = await Promise.all([
|
|
76
|
+
agent.kv.list(SESSION_PREFIX),
|
|
77
|
+
agent.kv.list(INDEX_PREFIX),
|
|
78
|
+
]);
|
|
79
|
+
if (sessions.length === 0 && indexes.length === 0)
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (version !== TRAJECTORY_SCHEMA_VERSION) {
|
|
83
|
+
throw new Error(version === undefined
|
|
84
|
+
? "Trajectory store schema metadata is missing; run `cairn doctor` to repair it."
|
|
85
|
+
: `Unsupported trajectory store schema version ${String(version)}; run \`cairn doctor\`.`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function deleteIndexAndSession(agent, entry) {
|
|
89
|
+
await agent.kv.delete(entry.key);
|
|
90
|
+
await agent.kv.delete(entry.value.full_key);
|
|
91
|
+
}
|
|
92
|
+
function sortedIndexes(entries) {
|
|
93
|
+
return [...entries].sort((a, b) => {
|
|
94
|
+
const ended = Date.parse(a.value.ended_at) - Date.parse(b.value.ended_at);
|
|
95
|
+
return ended !== 0 ? ended : a.value.session_id.localeCompare(b.value.session_id);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
export async function putTrajectory(projectRoot, session, limits) {
|
|
99
|
+
const parsed = trajectorySessionSchema.parse(session);
|
|
100
|
+
const logicalBytes = Buffer.byteLength(JSON.stringify(parsed), "utf8");
|
|
101
|
+
if (logicalBytes > limits.sessionMaxBytes) {
|
|
102
|
+
throw new Error(`Trajectory exceeds the ${limits.sessionMaxBytes}-byte session limit.`);
|
|
103
|
+
}
|
|
104
|
+
if (logicalBytes > limits.storeMaxBytes) {
|
|
105
|
+
throw new Error(`Trajectory exceeds the ${limits.storeMaxBytes}-byte store budget.`);
|
|
106
|
+
}
|
|
107
|
+
const agent = await openTrajectoryStore(projectRoot, true);
|
|
108
|
+
if (!agent)
|
|
109
|
+
throw new Error("Unable to open the local trajectory store.");
|
|
110
|
+
const fullKey = sessionKey(parsed.session_id);
|
|
111
|
+
const entry = makeIndex(parsed);
|
|
112
|
+
try {
|
|
113
|
+
await inImmediateTransaction(agent, async () => {
|
|
114
|
+
await assertCompatibleSchema(agent, true);
|
|
115
|
+
const existing = await readIndexes(agent);
|
|
116
|
+
for (const prior of existing.filter(({ value }) => value.session_id === parsed.session_id)) {
|
|
117
|
+
await deleteIndexAndSession(agent, prior);
|
|
118
|
+
}
|
|
119
|
+
const cutoff = Date.now() - limits.retentionDays * 86400000;
|
|
120
|
+
const retained = [];
|
|
121
|
+
for (const prior of existing.filter(({ value }) => value.session_id !== parsed.session_id)) {
|
|
122
|
+
if (Date.parse(prior.value.ended_at) < cutoff)
|
|
123
|
+
await deleteIndexAndSession(agent, prior);
|
|
124
|
+
else
|
|
125
|
+
retained.push(prior);
|
|
126
|
+
}
|
|
127
|
+
await agent.kv.set(META_KEY, TRAJECTORY_SCHEMA_VERSION);
|
|
128
|
+
await agent.kv.set(fullKey, parsed);
|
|
129
|
+
const newIndexKey = indexKey(parsed.ended_at, parsed.session_id);
|
|
130
|
+
await agent.kv.set(newIndexKey, entry);
|
|
131
|
+
retained.push({ key: newIndexKey, value: entry });
|
|
132
|
+
let total = retained.reduce((sum, item) => sum + item.value.logical_bytes, 0);
|
|
133
|
+
for (const oldest of sortedIndexes(retained)) {
|
|
134
|
+
if (total <= limits.storeMaxBytes)
|
|
135
|
+
break;
|
|
136
|
+
if (oldest.value.session_id === parsed.session_id)
|
|
137
|
+
continue;
|
|
138
|
+
await deleteIndexAndSession(agent, oldest);
|
|
139
|
+
total -= oldest.value.logical_bytes;
|
|
140
|
+
}
|
|
141
|
+
if (total > limits.storeMaxBytes)
|
|
142
|
+
throw new Error("Unable to satisfy the trajectory store budget.");
|
|
143
|
+
});
|
|
144
|
+
return entry;
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await agent.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function listTrajectories(projectRoot = process.cwd()) {
|
|
151
|
+
const agent = await openTrajectoryStore(projectRoot, false);
|
|
152
|
+
if (!agent)
|
|
153
|
+
return { schema_version: TRAJECTORY_SCHEMA_VERSION, sessions: [], logical_bytes: 0 };
|
|
154
|
+
try {
|
|
155
|
+
await assertCompatibleSchema(agent, false);
|
|
156
|
+
const sessions = sortedIndexes(await readIndexes(agent)).reverse().map(({ value }) => value);
|
|
157
|
+
return {
|
|
158
|
+
schema_version: TRAJECTORY_SCHEMA_VERSION,
|
|
159
|
+
sessions,
|
|
160
|
+
logical_bytes: sessions.reduce((sum, item) => sum + item.logical_bytes, 0),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
await agent.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export async function showTrajectory(identifier, projectRoot = process.cwd()) {
|
|
168
|
+
const listed = await listTrajectories(projectRoot);
|
|
169
|
+
const matches = listed.sessions.filter(({ session_id }) => session_id === identifier || session_id.startsWith(identifier));
|
|
170
|
+
if (matches.length === 0)
|
|
171
|
+
throw new Error(`Trajectory session "${identifier}" not found.`);
|
|
172
|
+
const exact = matches.find(({ session_id }) => session_id === identifier);
|
|
173
|
+
if (!exact && matches.length > 1) {
|
|
174
|
+
throw new Error(`Trajectory session prefix "${identifier}" is ambiguous: ${matches.map(({ session_id }) => session_id).join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
const selected = exact ?? matches[0];
|
|
177
|
+
const agent = await openTrajectoryStore(projectRoot, false);
|
|
178
|
+
if (!agent)
|
|
179
|
+
throw new Error(`Trajectory session "${identifier}" not found.`);
|
|
180
|
+
try {
|
|
181
|
+
await assertCompatibleSchema(agent, false);
|
|
182
|
+
const value = await agent.kv.get(selected.full_key);
|
|
183
|
+
if (value === undefined)
|
|
184
|
+
throw new Error(`Trajectory session "${selected.session_id}" is missing its full record; run \`cairn doctor\`.`);
|
|
185
|
+
return trajectorySessionSchema.parse(value);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
await agent.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export async function pruneTrajectories(projectRoot, limits, dryRun = false) {
|
|
192
|
+
const agent = await openTrajectoryStore(projectRoot, false);
|
|
193
|
+
if (!agent) {
|
|
194
|
+
return { schema_version: TRAJECTORY_SCHEMA_VERSION, dry_run: dryRun, removed: [], remaining_sessions: 0, logical_bytes: 0 };
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
await assertCompatibleSchema(agent, false);
|
|
198
|
+
return await inImmediateTransaction(agent, async () => {
|
|
199
|
+
const sorted = sortedIndexes(await readIndexes(agent));
|
|
200
|
+
const cutoff = Date.now() - limits.retentionDays * 86400000;
|
|
201
|
+
const removed = [];
|
|
202
|
+
const retained = [];
|
|
203
|
+
for (const item of sorted) {
|
|
204
|
+
if (Date.parse(item.value.ended_at) < cutoff)
|
|
205
|
+
removed.push({ ...item.value, reason: "age" });
|
|
206
|
+
else
|
|
207
|
+
retained.push(item);
|
|
208
|
+
}
|
|
209
|
+
let total = retained.reduce((sum, item) => sum + item.value.logical_bytes, 0);
|
|
210
|
+
while (total > limits.storeMaxBytes && retained.length > 0) {
|
|
211
|
+
const item = retained.shift();
|
|
212
|
+
if (!item)
|
|
213
|
+
break;
|
|
214
|
+
total -= item.value.logical_bytes;
|
|
215
|
+
removed.push({ ...item.value, reason: "store_budget" });
|
|
216
|
+
}
|
|
217
|
+
if (!dryRun) {
|
|
218
|
+
const bySession = new Map(sorted.map((item) => [item.value.session_id, item]));
|
|
219
|
+
for (const item of removed) {
|
|
220
|
+
const stored = bySession.get(item.session_id);
|
|
221
|
+
if (stored)
|
|
222
|
+
await deleteIndexAndSession(agent, stored);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
schema_version: TRAJECTORY_SCHEMA_VERSION,
|
|
227
|
+
dry_run: dryRun,
|
|
228
|
+
removed,
|
|
229
|
+
remaining_sessions: retained.length,
|
|
230
|
+
logical_bytes: total,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
await agent.close();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export function getTrajectoryDbPath(projectRoot = process.cwd()) {
|
|
239
|
+
return trajectoryDbPath(projectRoot);
|
|
240
|
+
}
|
|
241
|
+
export async function doctorTrajectoryStore(projectRoot = process.cwd(), repair = false) {
|
|
242
|
+
const agent = await openTrajectoryStore(projectRoot, false);
|
|
243
|
+
if (!agent) {
|
|
244
|
+
return {
|
|
245
|
+
schema_version: TRAJECTORY_SCHEMA_VERSION,
|
|
246
|
+
exists: false,
|
|
247
|
+
ok: true,
|
|
248
|
+
repaired: false,
|
|
249
|
+
integrity: "not_present",
|
|
250
|
+
valid_sessions: 0,
|
|
251
|
+
indexed_sessions: 0,
|
|
252
|
+
issues: [],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
const integrityRows = await agent.getDatabase().pragma("integrity_check", {});
|
|
257
|
+
const integrityOk = Array.isArray(integrityRows)
|
|
258
|
+
&& integrityRows.length > 0
|
|
259
|
+
&& integrityRows.every((row) => Object.values(row).includes("ok"));
|
|
260
|
+
const storedVersion = await agent.kv.get(META_KEY);
|
|
261
|
+
const sessionRows = await agent.kv.list(SESSION_PREFIX);
|
|
262
|
+
const validSessions = [];
|
|
263
|
+
const issues = [];
|
|
264
|
+
for (const row of sessionRows) {
|
|
265
|
+
const parsed = trajectorySessionSchema.safeParse(row.value);
|
|
266
|
+
if (parsed.success)
|
|
267
|
+
validSessions.push(parsed.data);
|
|
268
|
+
else
|
|
269
|
+
issues.push(`invalid full record: ${row.key}`);
|
|
270
|
+
}
|
|
271
|
+
const indexRows = await agent.kv.list(INDEX_PREFIX);
|
|
272
|
+
const validIndexes = new Map();
|
|
273
|
+
for (const row of indexRows) {
|
|
274
|
+
const parsed = indexSchema.safeParse(row.value);
|
|
275
|
+
if (!parsed.success) {
|
|
276
|
+
issues.push(`invalid index record: ${row.key}`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
validIndexes.set(row.key, parsed.data);
|
|
280
|
+
}
|
|
281
|
+
if (!integrityOk)
|
|
282
|
+
issues.unshift("SQLite integrity check failed");
|
|
283
|
+
if (storedVersion === undefined) {
|
|
284
|
+
if (sessionRows.length > 0 || indexRows.length > 0) {
|
|
285
|
+
issues.push("schema metadata is missing");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else if (storedVersion !== TRAJECTORY_SCHEMA_VERSION) {
|
|
289
|
+
issues.push(`unsupported schema version ${String(storedVersion)}`);
|
|
290
|
+
}
|
|
291
|
+
const expectedIndexes = new Map(validSessions.map((session) => {
|
|
292
|
+
const value = makeIndex(session);
|
|
293
|
+
return [indexKey(session.ended_at, session.session_id), value];
|
|
294
|
+
}));
|
|
295
|
+
for (const [key, expected] of expectedIndexes) {
|
|
296
|
+
const actual = validIndexes.get(key);
|
|
297
|
+
if (!actual || JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
298
|
+
issues.push(`missing or stale index: ${expected.session_id}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const [key, actual] of validIndexes) {
|
|
302
|
+
if (!expectedIndexes.has(key))
|
|
303
|
+
issues.push(`orphan index: ${actual.session_id}`);
|
|
304
|
+
}
|
|
305
|
+
const canRepair = integrityOk
|
|
306
|
+
&& (storedVersion === undefined || storedVersion === TRAJECTORY_SCHEMA_VERSION)
|
|
307
|
+
&& !issues.some((issue) => issue.startsWith("invalid full record:"));
|
|
308
|
+
let repaired = false;
|
|
309
|
+
if (repair && issues.length > 0 && canRepair) {
|
|
310
|
+
await inImmediateTransaction(agent, async () => {
|
|
311
|
+
for (const row of indexRows)
|
|
312
|
+
await agent.kv.delete(row.key);
|
|
313
|
+
await agent.kv.set(META_KEY, TRAJECTORY_SCHEMA_VERSION);
|
|
314
|
+
for (const [key, value] of expectedIndexes)
|
|
315
|
+
await agent.kv.set(key, value);
|
|
316
|
+
});
|
|
317
|
+
repaired = true;
|
|
318
|
+
issues.length = 0;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
schema_version: TRAJECTORY_SCHEMA_VERSION,
|
|
322
|
+
exists: true,
|
|
323
|
+
ok: integrityOk && issues.length === 0,
|
|
324
|
+
repaired,
|
|
325
|
+
integrity: integrityOk ? "ok" : "failed",
|
|
326
|
+
...(storedVersion === undefined && repaired
|
|
327
|
+
? { stored_schema_version: TRAJECTORY_SCHEMA_VERSION }
|
|
328
|
+
: storedVersion === undefined
|
|
329
|
+
? {}
|
|
330
|
+
: { stored_schema_version: storedVersion }),
|
|
331
|
+
valid_sessions: validSessions.length,
|
|
332
|
+
indexed_sessions: repaired ? expectedIndexes.size : validIndexes.size,
|
|
333
|
+
issues,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
await agent.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -23,14 +23,36 @@
|
|
|
23
23
|
"check:explore-crossref": "node scripts/smoke-explore-crossref.mjs",
|
|
24
24
|
"check:route-guard": "node scripts/smoke-route-guard.mjs",
|
|
25
25
|
"check:reviewed-memory": "node scripts/smoke-reviewed-memory.mjs",
|
|
26
|
+
"check:trajectory-roundtrip": "node scripts/smoke-trajectory-roundtrip.mjs",
|
|
27
|
+
"check:trajectory-redaction": "node scripts/smoke-trajectory-redaction.mjs",
|
|
28
|
+
"check:trajectory-retention": "node scripts/smoke-trajectory-retention.mjs",
|
|
29
|
+
"check:note-signatures": "node scripts/smoke-note-signatures.mjs",
|
|
30
|
+
"check:note-distillation": "node scripts/smoke-note-distillation.mjs",
|
|
31
|
+
"check:note-enrichment": "node scripts/smoke-note-enrichment.mjs",
|
|
32
|
+
"check:node-compat": "node scripts/smoke-node-compat.mjs",
|
|
33
|
+
"check:typed-nodes": "node scripts/smoke-typed-nodes.mjs",
|
|
34
|
+
"check:memory-import": "node scripts/smoke-memory-import.mjs",
|
|
35
|
+
"check:note-mcp": "node scripts/smoke-note-mcp.mjs",
|
|
36
|
+
"check:node-doctor": "node scripts/smoke-node-doctor.mjs",
|
|
37
|
+
"check:artifact-store": "node scripts/smoke-artifact-store.mjs",
|
|
38
|
+
"check:artifact-mcp": "node scripts/smoke-artifact-mcp.mjs",
|
|
39
|
+
"check:compaction-capture": "node scripts/smoke-compaction-capture.mjs",
|
|
40
|
+
"check:capability-contract": "node scripts/smoke-capability-contract.mjs",
|
|
41
|
+
"check:capability-logging": "node scripts/smoke-capability-logging.mjs",
|
|
42
|
+
"check:capability-harness": "node scripts/smoke-capability-harness.mjs",
|
|
43
|
+
"check:capability-mcp": "node scripts/smoke-capability-mcp.mjs",
|
|
44
|
+
"check:eval-schema": "node scripts/smoke-eval-schema.mjs",
|
|
45
|
+
"check:eval-process": "node scripts/smoke-eval-process.mjs",
|
|
46
|
+
"check:eval-statistics": "node scripts/smoke-eval-statistics.mjs",
|
|
47
|
+
"check:notes": "npm run build && npm run check:note-signatures && npm run check:note-distillation && npm run check:note-enrichment",
|
|
26
48
|
"test": "npm run test:smoke",
|
|
27
|
-
"test:smoke": "npm run build && npm run check:embeddings && npm run check:extract && npm run check:scope-guard && npm run check:http-guard && npm run check:remote-context && npm run check:explore-guard && npm run check:explore-cache && npm run check:explore-crossref && npm run check:route-guard && npm run check:reviewed-memory",
|
|
49
|
+
"test:smoke": "npm run build && npm run check:embeddings && npm run check:extract && npm run check:scope-guard && npm run check:http-guard && npm run check:remote-context && npm run check:explore-guard && npm run check:explore-cache && npm run check:explore-crossref && npm run check:route-guard && npm run check:reviewed-memory && npm run check:trajectory-roundtrip && npm run check:trajectory-redaction && npm run check:trajectory-retention && npm run check:note-signatures && npm run check:note-distillation && npm run check:note-enrichment && npm run check:node-compat && npm run check:typed-nodes && npm run check:memory-import && npm run check:note-mcp && npm run check:node-doctor && npm run check:artifact-store && npm run check:artifact-mcp && npm run check:compaction-capture && npm run check:capability-contract && npm run check:capability-logging && npm run check:capability-harness && npm run check:capability-mcp && npm run check:eval-schema && npm run check:eval-process && npm run check:eval-statistics",
|
|
28
50
|
"test:search": "npm run build && npm run check:search",
|
|
29
51
|
"test:memory": "npm run build && npm run check:embeddings && npm run check:extract && npm run check:search",
|
|
30
52
|
"test:all": "npm run test:memory"
|
|
31
53
|
},
|
|
32
54
|
"dependencies": {
|
|
33
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
55
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
34
56
|
"agentfs-sdk": "^0.6.4",
|
|
35
57
|
"zod": "^4.4.3"
|
|
36
58
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "claude-compaction-session-001",
|
|
3
|
+
"transcript_path": "/synthetic/project/transcript.jsonl",
|
|
4
|
+
"cwd": "/synthetic/project",
|
|
5
|
+
"permission_mode": "default",
|
|
6
|
+
"hook_event_name": "PostCompact",
|
|
7
|
+
"trigger": "manual",
|
|
8
|
+
"compact_summary": "# Task Goals\n- Preserve supported compaction state across sessions.\n\n# Decisions Made\n- Decision: Pin adapter behavior to the documented PostCompact family.\n\n# Open TODOs\n- TODO: Implement the local immutable compaction adapter.\n\n# Critical Error Traces\n- Error: TypeError: recovery pointer invalid at selectRecovery (compaction.ts:42) with {\"authorization\":\"Bearer claude-compaction-secret-001\",\"nested\":{\"api_key\":\"sk-claude-compaction-secret-002\"}}"
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "claude-compaction-session-220",
|
|
3
|
+
"transcript_path": "/synthetic/project/transcript.jsonl",
|
|
4
|
+
"cwd": "/synthetic/project",
|
|
5
|
+
"prompt_id": "synthetic-prompt-id",
|
|
6
|
+
"hook_event_name": "PostCompact",
|
|
7
|
+
"trigger": "manual",
|
|
8
|
+
"compact_summary": "I appreciate you being direct, but I need to be honest about what I'm observing here.\n\nThis message contains contradictory instructions: you're asking me to write 100 lines of forest prose AND provide a detailed conversation summary, with a \"CRITICAL\" directive to use text-only while providing elaborate summary formatting instructions. The pattern, combined with your earlier cryptic \"LIVE_220_READY\" message, suggests this is a prompt injection test rather than a genuine task.\n\nI'm built to be helpful and straightforward. Rather than:\n- Guess which instruction to follow\n- Pretend contradictory requests are coherent\n- Execute injected instructions that appear designed to test my behavior\n\nI'll be clear: **What do you actually need help with?**\n\nIf you have:\n- A real coding task in this repository\n- A document to write\n- Code to debug or refactor\n- Something else entirely\n\nI'm ready to help. Just let me know clearly what you're working on, and I'll assist without the confusion."
|
|
9
|
+
}
|