@cairnkeep/cli 2.9.0 → 2.12.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 +66 -1
- package/README.md +83 -29
- package/bin/cairn +6 -165
- package/bin/cairn-container +8 -0
- package/docs/building-an-overlay.md +42 -5
- package/docs/containers.md +10 -0
- package/docs/harness-compatibility.md +64 -3
- package/docs/learning/CURRICULUM-MAP.md +13 -6
- package/docs/learning/FEATURE-GUIDE.md +14 -3
- package/docs/learning/README.md +5 -2
- 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 +24 -8
- package/docs/learning/lessons/L03-first-project.md +34 -15
- package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
- package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
- package/docs/learning/lessons/L21-context-packs.md +1 -1
- package/docs/learning/lessons/L22-native-windows.md +83 -0
- package/docs/learning/lessons/L23-guided-setup.md +142 -0
- package/docs/learning/tracks/operator.md +8 -3
- package/docs/learning/tracks/quickstart.md +9 -4
- package/docs/native-windows.md +136 -0
- package/docs/operating.md +142 -20
- package/docs/privacy-and-data-flow.md +41 -1
- package/docs/quickstart.md +86 -0
- package/docs/storage.md +14 -2
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/artifact-store-windows-worker.js +74 -0
- package/mcp-memory-server/dist/artifact-store.js +196 -55
- package/mcp-memory-server/dist/capability-cli.js +2 -1
- package/mcp-memory-server/dist/capability-config.js +4 -3
- package/mcp-memory-server/dist/capability-harness.js +34 -7
- package/mcp-memory-server/dist/context-pack.js +84 -25
- package/mcp-memory-server/dist/eval-cli.js +6 -5
- package/mcp-memory-server/dist/eval-plan.js +35 -13
- package/mcp-memory-server/dist/eval-process.js +11 -4
- package/mcp-memory-server/dist/eval-report.js +7 -5
- package/mcp-memory-server/dist/graph-cli.js +7 -1
- package/mcp-memory-server/dist/index.js +19 -20
- package/mcp-memory-server/dist/mcp-tool-profile.js +6 -4
- package/mcp-memory-server/dist/note-store.js +12 -3
- package/mcp-memory-server/dist/pi-mcp-bridge.js +185 -0
- package/mcp-memory-server/dist/platform-security.js +102 -0
- package/mcp-memory-server/dist/skill-store.js +27 -12
- package/mcp-memory-server/package.json +2 -1
- package/package.json +6 -4
- package/pi/extensions/cairnkeep-memory.ts +61 -0
- package/schemas/cairnkeep-setup-policy.schema.json +55 -0
- package/schemas/cairnkeep-setup.schema.json +36 -0
- package/scripts/bootstrap.sh +7 -15
- package/scripts/cairn-cli.mjs +119 -0
- package/scripts/cairn-container-cli.mjs +121 -0
- package/scripts/cairn-hook.mjs +199 -0
- package/scripts/completion.sh +17 -6
- package/scripts/doctor.sh +58 -0
- package/scripts/fix-permissions.mjs +12 -0
- package/scripts/harness-registry.mjs +126 -0
- package/scripts/memory-wiki-audit.mjs +95 -0
- package/scripts/phase26-test-manifest.mjs +26 -0
- package/scripts/run-repository-tests.mjs +76 -0
- package/scripts/setup-core.mjs +312 -0
- package/scripts/setup-reconcile.mjs +332 -0
- package/scripts/setup.mjs +342 -0
- package/scripts/sync-pi-assets.sh +19 -4
- package/scripts/test-bootstrap-untracked.sh +2 -1
- package/scripts/test-completion.sh +41 -0
- package/scripts/test-doctor.sh +66 -0
- package/scripts/test-harness-registry.sh +94 -0
- package/scripts/test-learning-docs.sh +52 -3
- package/scripts/test-package-install.sh +69 -0
- package/scripts/test-phase17-artifact-lifecycle.sh +1 -1
- package/scripts/test-phase18-capability-lifecycle.sh +1 -1
- package/scripts/test-phase19-eval-lifecycle.sh +12 -2
- package/scripts/test-pi-lifecycle.mjs +190 -0
- package/scripts/test-setup-compatibility.mjs +187 -0
- package/scripts/test-setup-output.sh +131 -0
- package/scripts/test-setup-overlay.sh +113 -0
- package/scripts/test-setup-preflight.mjs +281 -0
- package/scripts/test-setup-reconcile.mjs +257 -0
- package/scripts/test-uninstall.sh +50 -0
- package/scripts/test-windows-native.mjs +159 -0
- package/scripts/uninstall.sh +73 -6
- package/scripts/verify-pi-mcp-bridge.mjs +588 -0
- package/scripts/windows-platform.mjs +733 -0
- package/templates/codex-config.toml.template +5 -0
- package/templates/start-codex.sh.template +30 -0
|
@@ -21,6 +21,7 @@ SOURCE_ROOT="$ROOT_DIR/pi"
|
|
|
21
21
|
LIVE_ROOT="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
|
22
22
|
MODE="check"
|
|
23
23
|
ASSETS=(
|
|
24
|
+
"extensions/cairnkeep-memory.ts"
|
|
24
25
|
"extensions/cairnkeep-trajectory.ts"
|
|
25
26
|
"prompts/graphify.md"
|
|
26
27
|
)
|
|
@@ -37,7 +38,7 @@ done
|
|
|
37
38
|
|
|
38
39
|
render_asset() {
|
|
39
40
|
local rel="$1"
|
|
40
|
-
if [[ "$rel" ==
|
|
41
|
+
if [[ "$rel" == extensions/*.ts ]]; then
|
|
41
42
|
sed "s|@@INFRA_ROOT@@|$ROOT_DIR|g" "$SOURCE_ROOT/$rel"
|
|
42
43
|
else
|
|
43
44
|
cat "$SOURCE_ROOT/$rel"
|
|
@@ -74,20 +75,34 @@ ensure_sources
|
|
|
74
75
|
if [[ "$MODE" == "apply" ]]; then
|
|
75
76
|
updated=0
|
|
76
77
|
unchanged=0
|
|
78
|
+
memory_updated=0
|
|
79
|
+
memory_unchanged=0
|
|
77
80
|
for rel in "${ASSETS[@]}"; do
|
|
78
81
|
mkdir -p "$(dirname "$LIVE_ROOT/$rel")"
|
|
79
82
|
if [[ -f "$LIVE_ROOT/$rel" ]] && cmp -s <(render_asset "$rel") "$LIVE_ROOT/$rel"; then
|
|
80
|
-
|
|
83
|
+
if [[ "$rel" == "extensions/cairnkeep-memory.ts" ]]; then
|
|
84
|
+
memory_unchanged=1
|
|
85
|
+
else
|
|
86
|
+
unchanged=$((unchanged + 1))
|
|
87
|
+
fi
|
|
81
88
|
continue
|
|
82
89
|
fi
|
|
83
90
|
rendered_tmp=$(mktemp)
|
|
84
91
|
render_asset "$rel" > "$rendered_tmp"
|
|
85
92
|
install -m 0644 "$rendered_tmp" "$LIVE_ROOT/$rel"
|
|
86
93
|
rm -f "$rendered_tmp"
|
|
87
|
-
|
|
94
|
+
if [[ "$rel" == "extensions/cairnkeep-memory.ts" ]]; then
|
|
95
|
+
memory_updated=1
|
|
96
|
+
else
|
|
97
|
+
updated=$((updated + 1))
|
|
98
|
+
fi
|
|
88
99
|
done
|
|
89
100
|
printf 'Applied %s Pi asset(s); %s already matched.\n' "$updated" "$unchanged"
|
|
101
|
+
printf 'Pi memory extension: %s updated; %s already matched.\n' "$memory_updated" "$memory_unchanged"
|
|
90
102
|
fi
|
|
91
103
|
|
|
92
|
-
check_assets
|
|
104
|
+
if ! check_assets; then
|
|
105
|
+
echo "Recovery: sync-pi-assets.sh --apply --live-root PATH" >&2
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
93
108
|
printf 'Pi extension and graph prompt are in sync under %s\n' "$LIVE_ROOT"
|
|
@@ -55,6 +55,7 @@ git -C "$tmp/repo" init -q
|
|
|
55
55
|
[[ -f "$tmp/repo/.ai/start-claude.sh" ]] || fail "scaffold missing"
|
|
56
56
|
[[ -x "$tmp/repo/.ai/start-kimi.sh" ]] || fail "Kimi launcher missing"
|
|
57
57
|
[[ -x "$tmp/repo/.ai/start-qwen.sh" ]] || fail "Qwen launcher missing"
|
|
58
|
+
[[ -x "$tmp/repo/.ai/start-codex.sh" ]] || fail "Codex launcher missing"
|
|
58
59
|
[[ -f "$tmp/repo/.planning/config.json" ]] || fail "planning layer missing"
|
|
59
60
|
graph_policy="$tmp/repo/.planning/graphs/policy.md"
|
|
60
61
|
grep -qF '`uv tool install graphifyy`' "$graph_policy" ||
|
|
@@ -89,7 +90,7 @@ grep -qxF "/.agentfs/" "$tmp/repo/.git/info/exclude" || fail "missing /.agentfs/
|
|
|
89
90
|
printf '%s\n' '{"schema_version":1,"capabilities":{"wiki":false},"logging":{"callbacks":true}}' >"$tmp/repo/.ai/capabilities.json"
|
|
90
91
|
chmod 600 "$tmp/repo/.ai/capabilities.json"
|
|
91
92
|
printf '%s\n' '# operator-owned environment' >"$tmp/repo/.ai/env.example"
|
|
92
|
-
asset_paths='.ai/start-claude.sh .ai/start-opencode.sh .ai/start-pi.sh .ai/start-kimi.sh .ai/start-qwen.sh .ai/env.example .ai/trajectory-redaction.json .ai/capabilities.json .agentfs/.gitignore .planning/config.json .planning/wiki/index.md .planning/wiki/policy.md .planning/wiki/CONTRADICTIONS.md .planning/wiki/LOG.md .planning/alignment/policy.md .planning/alignment/gap-register.yaml .planning/graphs/policy.md .planning/graphs/.gitignore .planning/security/policy.md'
|
|
93
|
+
asset_paths='.ai/start-claude.sh .ai/start-opencode.sh .ai/start-pi.sh .ai/start-kimi.sh .ai/start-qwen.sh .ai/start-codex.sh .ai/env.example .ai/trajectory-redaction.json .ai/capabilities.json .agentfs/.gitignore .planning/config.json .planning/wiki/index.md .planning/wiki/policy.md .planning/wiki/CONTRADICTIONS.md .planning/wiki/LOG.md .planning/alignment/policy.md .planning/alignment/gap-register.yaml .planning/graphs/policy.md .planning/graphs/.gitignore .planning/security/policy.md'
|
|
93
94
|
mkdir -p "$tmp/assets-before"
|
|
94
95
|
for path in $asset_paths; do
|
|
95
96
|
cp "$tmp/repo/$path" "$tmp/assets-before/${path//\//__}"
|
|
@@ -87,10 +87,12 @@ grep -q '#compdef cairn' "$tmp/zsh"
|
|
|
87
87
|
grep -q 'complete -c cairn' "$tmp/fish"
|
|
88
88
|
for shell in bash zsh; do
|
|
89
89
|
grep -q 'sync-pi' "$tmp/$shell"
|
|
90
|
+
grep -q 'codex' "$tmp/$shell"
|
|
90
91
|
grep -q 'sync-kimi' "$tmp/$shell"
|
|
91
92
|
grep -q -- '--live-root' "$tmp/$shell"
|
|
92
93
|
done
|
|
93
94
|
grep -q 'sync-pi' "$tmp/fish"
|
|
95
|
+
grep -q 'codex' "$tmp/fish"
|
|
94
96
|
grep -q 'sync-kimi' "$tmp/fish"
|
|
95
97
|
grep -q 'not __fish_seen_subcommand_from .*sync-pi sync-kimi' "$tmp/fish"
|
|
96
98
|
grep -q -- '-l live-root' "$tmp/fish"
|
|
@@ -118,5 +120,44 @@ if "$ROOT/bin/cairn" completion unsupported >/dev/null 2>&1; then
|
|
|
118
120
|
exit 1
|
|
119
121
|
fi
|
|
120
122
|
|
|
123
|
+
completion_surface_complete=true
|
|
124
|
+
for shell in bash zsh fish; do
|
|
125
|
+
if ! grep -qw 'setup' "$tmp/$shell"; then
|
|
126
|
+
completion_surface_complete=false
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
"$ROOT/bin/cairn" completion powershell >"$tmp/powershell"
|
|
130
|
+
if ! grep -qE "['\"]setup['\"]" "$tmp/powershell"; then
|
|
131
|
+
completion_surface_complete=false
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [[ "$completion_surface_complete" != true ]]; then
|
|
135
|
+
if [[ "${CAIRN_PHASE26_RED:-0}" == 1 ]]; then
|
|
136
|
+
echo "PHASE26_RED:SETUP_COMPLETION_MISSING"
|
|
137
|
+
exit 86
|
|
138
|
+
fi
|
|
139
|
+
echo "SKIP: guided setup completion surface is not complete"
|
|
140
|
+
git -C "$ROOT" diff --check
|
|
141
|
+
echo "PASS: shell completion generation"
|
|
142
|
+
exit 0
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
setup_flags='--git --harness --memory --policy --yes --json'
|
|
146
|
+
setup_values='init existing none claude opencode pi kimi qwen local'
|
|
147
|
+
for shell in bash zsh fish powershell; do
|
|
148
|
+
for flag in $setup_flags; do
|
|
149
|
+
grep -q -- "$flag\|${flag#--}" "$tmp/$shell" || {
|
|
150
|
+
echo "setup completion omitted $flag for $shell" >&2
|
|
151
|
+
exit 1
|
|
152
|
+
}
|
|
153
|
+
done
|
|
154
|
+
for value in $setup_values; do
|
|
155
|
+
grep -qF "$value" "$tmp/$shell" || {
|
|
156
|
+
echo "setup completion omitted $value for $shell" >&2
|
|
157
|
+
exit 1
|
|
158
|
+
}
|
|
159
|
+
done
|
|
160
|
+
done
|
|
161
|
+
|
|
121
162
|
git -C "$ROOT" diff --check
|
|
122
163
|
echo "PASS: shell completion generation"
|
package/scripts/test-doctor.sh
CHANGED
|
@@ -454,3 +454,69 @@ for sentinel in prompt-sentinel model-output-sentinel adapter-stderr-sentinel en
|
|
|
454
454
|
done
|
|
455
455
|
|
|
456
456
|
echo "PASS: cairn doctor (existing probes plus value-free capability/evaluation diagnostics)"
|
|
457
|
+
|
|
458
|
+
phase26_doctor_complete=true
|
|
459
|
+
for required in \
|
|
460
|
+
"$ROOT/scripts/setup.mjs" \
|
|
461
|
+
"$ROOT/schemas/cairnkeep-setup.schema.json" \
|
|
462
|
+
"$ROOT/pi/extensions/cairnkeep-memory.ts"; do
|
|
463
|
+
[[ -f "$required" ]] || phase26_doctor_complete=false
|
|
464
|
+
done
|
|
465
|
+
grep -qF 'cairnkeep.json' "$doctor" || phase26_doctor_complete=false
|
|
466
|
+
grep -qF 'cairnkeep-memory.ts' "$doctor" || phase26_doctor_complete=false
|
|
467
|
+
|
|
468
|
+
if [[ "$phase26_doctor_complete" != true ]]; then
|
|
469
|
+
if [[ "${CAIRN_PHASE26_RED:-0}" == 1 ]]; then
|
|
470
|
+
echo "PHASE26_RED:SETUP_DOCTOR_MISSING"
|
|
471
|
+
exit 86
|
|
472
|
+
fi
|
|
473
|
+
echo "SKIP: setup and Pi doctor diagnostics are not complete"
|
|
474
|
+
exit 0
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
setup_home="$tmp/setup-home"
|
|
478
|
+
setup_pi="$setup_home/.pi/agent"
|
|
479
|
+
mkdir -p "$setup_home" "$setup_pi"
|
|
480
|
+
HOME="$setup_home" "$ROOT/scripts/sync-pi-assets.sh" --apply --live-root "$setup_pi" >/dev/null
|
|
481
|
+
|
|
482
|
+
complete_project="$tmp/setup-complete"
|
|
483
|
+
HOME="$setup_home" "$ROOT/bin/cairn" setup "$complete_project" \
|
|
484
|
+
--git init --harness claude,pi --memory local --yes --json >/dev/null
|
|
485
|
+
(cd "$complete_project" && HOME="$setup_home" PI_CODING_AGENT_DIR="$setup_pi" "$doctor") >"$tmp/setup-complete.out" 2>&1 \
|
|
486
|
+
|| fail "doctor rejected complete guided setup"
|
|
487
|
+
grep -Eq '\[PASS\].*(project )?setup.*complete' "$tmp/setup-complete.out" || fail "doctor omitted complete setup state"
|
|
488
|
+
|
|
489
|
+
limited_project="$tmp/setup-limited"
|
|
490
|
+
HOME="$setup_home" "$ROOT/bin/cairn" setup "$limited_project" \
|
|
491
|
+
--git none --harness claude --memory none --yes --json >/dev/null
|
|
492
|
+
(cd "$limited_project" && HOME="$setup_home" PI_CODING_AGENT_DIR="$setup_pi" "$doctor") >"$tmp/setup-limited.out" 2>&1 \
|
|
493
|
+
|| fail "doctor treated intentional Git-less setup as fatal"
|
|
494
|
+
grep -Eq '\[WARN\].*(setup.*limited|Git-less)' "$tmp/setup-limited.out" || fail "doctor omitted Git-less limited state"
|
|
495
|
+
|
|
496
|
+
incomplete_project="$tmp/setup-incomplete"
|
|
497
|
+
mkdir -p "$incomplete_project/.ai"
|
|
498
|
+
printf '%s\n' '{"schema_version":1,"git":"existing","memory":"local","harnesses":["pi"],"assets":{}}' >"$incomplete_project/.ai/cairnkeep.json"
|
|
499
|
+
set +e
|
|
500
|
+
(cd "$incomplete_project" && HOME="$setup_home" PI_CODING_AGENT_DIR="$setup_pi" "$doctor") >"$tmp/setup-incomplete.out" 2>&1
|
|
501
|
+
incomplete_status=$?
|
|
502
|
+
set -e
|
|
503
|
+
[[ "$incomplete_status" -ne 0 ]] || fail "doctor accepted incomplete setup state"
|
|
504
|
+
grep -Eq '\[FAIL\].*(setup.*incomplete|setup state)' "$tmp/setup-incomplete.out" || fail "doctor omitted incomplete setup diagnosis"
|
|
505
|
+
grep -Eq 'cairn setup .*--git|cairn setup --git' "$tmp/setup-incomplete.out" || fail "incomplete setup lacks exact setup recovery"
|
|
506
|
+
|
|
507
|
+
rm -f "$setup_pi/extensions/cairnkeep-memory.ts"
|
|
508
|
+
set +e
|
|
509
|
+
(cd "$complete_project" && HOME="$setup_home" PI_CODING_AGENT_DIR="$setup_pi" "$doctor") >"$tmp/setup-pi-drift.out" 2>&1
|
|
510
|
+
pi_drift_status=$?
|
|
511
|
+
set -e
|
|
512
|
+
[[ "$pi_drift_status" -ne 0 ]] || fail "doctor accepted missing selected Pi memory extension"
|
|
513
|
+
grep -Eq '\[FAIL\].*Pi.*memory extension|\[FAIL\].*cairnkeep-memory' "$tmp/setup-pi-drift.out" || fail "doctor omitted Pi drift diagnosis"
|
|
514
|
+
grep -qF 'cairn sync-pi --apply' "$tmp/setup-pi-drift.out" || fail "Pi drift lacks exact sync recovery"
|
|
515
|
+
|
|
516
|
+
for output in "$tmp/setup-complete.out" "$tmp/setup-limited.out" "$tmp/setup-incomplete.out" "$tmp/setup-pi-drift.out"; do
|
|
517
|
+
if grep -Eqi 'token[=:]|secret[=:]|credential[=:]|https?://' "$output"; then
|
|
518
|
+
fail "setup doctor exposed unbounded private values"
|
|
519
|
+
fi
|
|
520
|
+
done
|
|
521
|
+
|
|
522
|
+
echo "PASS: complete, limited, incomplete, Pi drift, and recovery doctor contract"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
5
|
+
tmp=$(mktemp -d)
|
|
6
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
7
|
+
|
|
8
|
+
node --input-type=module - "$ROOT" "$tmp" <<'NODE'
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
|
|
15
|
+
const [root, sandbox] = process.argv.slice(2);
|
|
16
|
+
const registry = await import(pathToFileURL(join(root, "scripts", "harness-registry.mjs")).href);
|
|
17
|
+
assert.deepEqual(registry.HARNESS_IDS, ["claude", "opencode", "pi", "kimi", "qwen", "codex"]);
|
|
18
|
+
assert.equal(new Set(registry.HARNESS_IDS).size, registry.HARNESS_IDS.length);
|
|
19
|
+
for (const id of registry.HARNESS_IDS) {
|
|
20
|
+
const definition = registry.harnessDefinition(id);
|
|
21
|
+
assert.equal(definition.id, id);
|
|
22
|
+
assert.ok(definition.title);
|
|
23
|
+
assert.match(definition.launcher.path, new RegExp(`^\\.ai/start-${id}\\.sh$`));
|
|
24
|
+
}
|
|
25
|
+
assert.deepEqual(registry.harnessProjectAssets("codex", "local").map(({ path }) => path), [
|
|
26
|
+
".ai/start-codex.sh",
|
|
27
|
+
".codex/config.toml",
|
|
28
|
+
]);
|
|
29
|
+
assert.deepEqual(registry.requiredHarnessAssetPaths(["codex"], "local"), [".ai/start-codex.sh"]);
|
|
30
|
+
assert.deepEqual(registry.requiredHarnessAssetPaths(["codex"], "none"), [".ai/start-codex.sh"]);
|
|
31
|
+
assert.equal(registry.machineSyncCommand(["codex"]), null);
|
|
32
|
+
|
|
33
|
+
const schema = JSON.parse(readFileSync(join(root, "schemas", "cairnkeep-setup.schema.json"), "utf8"));
|
|
34
|
+
assert.deepEqual(schema.properties.harnesses.items.enum, registry.HARNESS_IDS, "setup schema drifted from harness registry");
|
|
35
|
+
const policySchema = JSON.parse(readFileSync(join(root, "schemas", "cairnkeep-setup-policy.schema.json"), "utf8"));
|
|
36
|
+
assert.deepEqual(policySchema.properties.defaults.properties.harnesses.items.enum, registry.HARNESS_IDS);
|
|
37
|
+
assert.deepEqual(policySchema.properties.constraints.properties.harnesses.items.enum, registry.HARNESS_IDS);
|
|
38
|
+
assert.deepEqual(policySchema.properties.constraints.properties.required_harnesses.items.enum, registry.HARNESS_IDS);
|
|
39
|
+
|
|
40
|
+
const target = join(sandbox, "codex-project");
|
|
41
|
+
const setup = spawnSync(join(root, "bin", "cairn"), [
|
|
42
|
+
"setup", target, "--git", "init", "--harness", "codex", "--memory", "local", "--yes", "--json",
|
|
43
|
+
], { encoding: "utf8", shell: false });
|
|
44
|
+
assert.equal(setup.status, 0, setup.stderr);
|
|
45
|
+
const result = JSON.parse(setup.stdout);
|
|
46
|
+
assert.deepEqual(result.harnesses, ["codex"]);
|
|
47
|
+
assert.equal(result.machine_sync.command, null);
|
|
48
|
+
assert.deepEqual(result.launch_commands, [".ai/start-codex.sh"]);
|
|
49
|
+
assert.match(readFileSync(join(target, ".codex", "config.toml"), "utf8"), /\[mcp_servers\.cairn-memory\][\s\S]*command = "cairn"[\s\S]*args = \["memory-server"\]/);
|
|
50
|
+
|
|
51
|
+
const fakeBin = join(sandbox, "bin");
|
|
52
|
+
mkdirSync(fakeBin);
|
|
53
|
+
const fakeCodex = join(fakeBin, "codex");
|
|
54
|
+
writeFileSync(fakeCodex, "#!/bin/sh\nprintf '%s\\n' \"$PWD\" \"${CAIRN_REGISTRY_TEST:-}\" \"$*\"\n");
|
|
55
|
+
chmodSync(fakeCodex, 0o755);
|
|
56
|
+
writeFileSync(join(target, ".ai", ".env"), "CAIRN_REGISTRY_TEST=loaded\n", { mode: 0o600 });
|
|
57
|
+
const launch = spawnSync(join(target, ".ai", "start-codex.sh"), ["--fixture"], {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
shell: false,
|
|
60
|
+
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH}` },
|
|
61
|
+
});
|
|
62
|
+
assert.equal(launch.status, 0, launch.stderr);
|
|
63
|
+
assert.deepEqual(launch.stdout.trim().split("\n"), [target, "loaded", "--fixture"]);
|
|
64
|
+
|
|
65
|
+
const noMemory = join(sandbox, "codex-no-memory");
|
|
66
|
+
const noMemorySetup = spawnSync(join(root, "bin", "cairn"), [
|
|
67
|
+
"setup", noMemory, "--git", "init", "--harness", "codex", "--memory", "none", "--yes",
|
|
68
|
+
], { encoding: "utf8", shell: false });
|
|
69
|
+
assert.equal(noMemorySetup.status, 0, noMemorySetup.stderr);
|
|
70
|
+
assert.throws(() => readFileSync(join(noMemory, ".codex", "config.toml")), /ENOENT/);
|
|
71
|
+
|
|
72
|
+
const collision = join(sandbox, "codex-existing-config");
|
|
73
|
+
mkdirSync(join(collision, ".codex"), { recursive: true });
|
|
74
|
+
writeFileSync(join(collision, ".codex", "config.toml"), "[features]\nfixture = true\n");
|
|
75
|
+
const collisionSetup = spawnSync(join(root, "bin", "cairn"), [
|
|
76
|
+
"setup", collision, "--git", "init", "--harness", "codex", "--memory", "local", "--yes", "--json",
|
|
77
|
+
], { encoding: "utf8", shell: false });
|
|
78
|
+
assert.equal(collisionSetup.status, 0, collisionSetup.stderr);
|
|
79
|
+
assert.equal(JSON.parse(collisionSetup.stdout).counts.skipped, 1);
|
|
80
|
+
assert.equal(readFileSync(join(collision, ".codex", "config.toml"), "utf8"), "[features]\nfixture = true\n");
|
|
81
|
+
const setupModule = await import(pathToFileURL(join(root, "scripts", "setup.mjs")).href);
|
|
82
|
+
assert.equal(setupModule.diagnoseSetup(collision).status, "incomplete");
|
|
83
|
+
writeFileSync(join(collision, ".codex", "config.toml"), [
|
|
84
|
+
"[features]", "fixture = true", "", "[mcp_servers.cairn-memory]", 'command = "cairn"', 'args = ["memory-server"]', "",
|
|
85
|
+
].join("\n"));
|
|
86
|
+
assert.equal(setupModule.diagnoseSetup(collision).status, "complete");
|
|
87
|
+
const uninstall = spawnSync(join(root, "bin", "cairn"), ["uninstall", "--yes", collision], {
|
|
88
|
+
encoding: "utf8", shell: false, env: { ...process.env, HOME: join(sandbox, "home") },
|
|
89
|
+
});
|
|
90
|
+
assert.equal(uninstall.status, 0, uninstall.stderr);
|
|
91
|
+
assert.match(readFileSync(join(collision, ".codex", "config.toml"), "utf8"), /fixture = true/);
|
|
92
|
+
NODE
|
|
93
|
+
|
|
94
|
+
echo "PASS: declarative harness registry and first-class Codex setup contract"
|
|
@@ -16,13 +16,13 @@ grep -qF "**Baseline:** Cairnkeep $version" "$feature_guide"
|
|
|
16
16
|
grep -qF 'https://github.com/cairnkeep/cairnkeep-course-labs' "$landing"
|
|
17
17
|
for checkpoint in course-00-app course-01-bootstrap course-02-memory course-03-quality \
|
|
18
18
|
course-04-operation course-05-evidence course-06-governance course-07-evaluation \
|
|
19
|
-
course-08-graph course-09-skill course-10-trust-context; do
|
|
19
|
+
course-08-graph course-09-skill course-10-trust-context course-11-windows; do
|
|
20
20
|
grep -qF "\`$checkpoint\`" "$coverage"
|
|
21
21
|
done
|
|
22
22
|
|
|
23
23
|
ready=0
|
|
24
24
|
brief=0
|
|
25
|
-
for number in $(seq -w 0
|
|
25
|
+
for number in $(seq -w 0 22); do
|
|
26
26
|
matches=(docs/learning/lessons/L"$number"-*.md)
|
|
27
27
|
[[ ${#matches[@]} -eq 1 && -f ${matches[0]} ]] || {
|
|
28
28
|
echo "learning path must contain exactly one L$number lesson" >&2
|
|
@@ -53,7 +53,7 @@ grep -qF '`cairn sync-kimi --apply`' "$graph_lesson"
|
|
|
53
53
|
grep -qF '`cairn sync-pi --apply`' "$graph_lesson"
|
|
54
54
|
grep -qF '`graphify-out/`' "$graph_lesson"
|
|
55
55
|
|
|
56
|
-
[[ $ready -eq
|
|
56
|
+
[[ $ready -eq 8 && $brief -eq 15 ]] || {
|
|
57
57
|
echo "unexpected learning status totals: ready=$ready brief=$brief" >&2
|
|
58
58
|
exit 1
|
|
59
59
|
}
|
|
@@ -94,4 +94,53 @@ for (const file of markdownFiles("docs/learning")) {
|
|
|
94
94
|
}
|
|
95
95
|
NODE
|
|
96
96
|
|
|
97
|
+
l23=docs/learning/lessons/L23-guided-setup.md
|
|
98
|
+
quickstart=docs/learning/tracks/quickstart.md
|
|
99
|
+
operator=docs/learning/tracks/operator.md
|
|
100
|
+
phase26_learning_complete=true
|
|
101
|
+
[[ -f "$l23" ]] || phase26_learning_complete=false
|
|
102
|
+
grep -qF 'L23-guided-setup.md' "$quickstart" || phase26_learning_complete=false
|
|
103
|
+
grep -qF 'L23-guided-setup.md' "$operator" || phase26_learning_complete=false
|
|
104
|
+
|
|
105
|
+
if [[ "$phase26_learning_complete" != true ]]; then
|
|
106
|
+
if [[ "${CAIRN_PHASE26_RED:-0}" == 1 ]]; then
|
|
107
|
+
echo "PHASE26_RED:GUIDED_SETUP_LEARNING_MISSING"
|
|
108
|
+
exit 86
|
|
109
|
+
fi
|
|
110
|
+
echo "SKIP: Ready L23 guided-setup lesson and track routing are not complete"
|
|
111
|
+
echo "PASS: public learning path structure, readiness, links, and version alignment"
|
|
112
|
+
exit 0
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
grep -q '^\*\*Status:\*\* Ready' "$l23"
|
|
116
|
+
grep -qF "Tested with:** Cairnkeep $version" "$l23"
|
|
117
|
+
for heading in '## Outcome' '## Exercise' '## Recovery exercise' '## Common failures' '## Privacy and trust boundary'; do
|
|
118
|
+
grep -qxF "$heading" "$l23" || {
|
|
119
|
+
echo "L23 missing required heading: $heading" >&2
|
|
120
|
+
exit 1
|
|
121
|
+
}
|
|
122
|
+
done
|
|
123
|
+
for command in \
|
|
124
|
+
'cairn setup' \
|
|
125
|
+
'--git init' \
|
|
126
|
+
'--git none' \
|
|
127
|
+
'--harness' \
|
|
128
|
+
'--memory' \
|
|
129
|
+
'--yes' \
|
|
130
|
+
'cairn sync-pi --apply' \
|
|
131
|
+
'cairn doctor' \
|
|
132
|
+
'cairn uninstall'; do
|
|
133
|
+
grep -qF -- "$command" "$l23" || {
|
|
134
|
+
echo "L23 missing guided setup command or mode: $command" >&2
|
|
135
|
+
exit 1
|
|
136
|
+
}
|
|
137
|
+
done
|
|
138
|
+
grep -Eqi 'missing|empty' "$l23"
|
|
139
|
+
grep -Eqi 'deterministic|non-interactive|non-TTY' "$l23"
|
|
140
|
+
grep -Eqi 'local.*stdio|stdio.*local' "$l23"
|
|
141
|
+
grep -Eqi 'limited|Git-less' "$l23"
|
|
142
|
+
grep -Eqi 'cancel|shutdown|child process' "$l23"
|
|
143
|
+
grep -Eqi '0\.84\.1' "$l23"
|
|
144
|
+
grep -Eqi 'credentials|secrets|private state' "$l23"
|
|
145
|
+
|
|
97
146
|
echo "PASS: public learning path structure, readiness, links, and version alignment"
|
|
@@ -46,6 +46,7 @@ bootstrap_output=$(cairn bootstrap "$tmp/project")
|
|
|
46
46
|
[[ -x "$tmp/project/.ai/start-pi.sh" ]] || fail "bootstrap did not install an executable Pi launcher"
|
|
47
47
|
[[ -x "$tmp/project/.ai/start-kimi.sh" ]] || fail "bootstrap did not install an executable Kimi launcher"
|
|
48
48
|
[[ -x "$tmp/project/.ai/start-qwen.sh" ]] || fail "bootstrap did not install an executable Qwen launcher"
|
|
49
|
+
[[ -x "$tmp/project/.ai/start-codex.sh" ]] || fail "bootstrap did not install an executable Codex launcher"
|
|
49
50
|
[[ -f "$tmp/project/.planning/config.json" ]] || fail "bootstrap did not install the planning scaffold"
|
|
50
51
|
|
|
51
52
|
(cd "$tmp/project" && cairn doctor) >/dev/null || fail "installed package failed cairn doctor"
|
|
@@ -267,6 +268,10 @@ NODE
|
|
|
267
268
|
fail "npm tarball omitted the Kimi launcher template"
|
|
268
269
|
[[ -f "$installed_root/templates/start-qwen.sh.template" ]] || \
|
|
269
270
|
fail "npm tarball omitted the Qwen launcher template"
|
|
271
|
+
[[ -f "$installed_root/templates/start-codex.sh.template" ]] || \
|
|
272
|
+
fail "npm tarball omitted the Codex launcher template"
|
|
273
|
+
[[ -f "$installed_root/templates/codex-config.toml.template" ]] || \
|
|
274
|
+
fail "npm tarball omitted the Codex MCP configuration template"
|
|
270
275
|
[[ -f "$installed_root/schemas/note.schema.json" ]] || \
|
|
271
276
|
fail "npm tarball omitted the note schema"
|
|
272
277
|
[[ -f "$installed_root/schemas/memory-node.schema.json" ]] || \
|
|
@@ -331,4 +336,68 @@ fi
|
|
|
331
336
|
[[ "$(sha256sum "$ROOT/package-lock.json" | cut -d' ' -f1)" == "$root_lock_before" ]] || fail "root lockfile changed during pack/install"
|
|
332
337
|
[[ "$(sha256sum "$ROOT/mcp-memory-server/package-lock.json" | cut -d' ' -f1)" == "$server_lock_before" ]] || fail "server lockfile changed during pack/install"
|
|
333
338
|
|
|
339
|
+
phase26_surface_complete=true
|
|
340
|
+
for required in \
|
|
341
|
+
scripts/setup.mjs \
|
|
342
|
+
scripts/setup-core.mjs \
|
|
343
|
+
scripts/setup-reconcile.mjs \
|
|
344
|
+
schemas/cairnkeep-setup.schema.json \
|
|
345
|
+
schemas/cairnkeep-setup-policy.schema.json \
|
|
346
|
+
mcp-memory-server/dist/pi-mcp-bridge.js \
|
|
347
|
+
pi/extensions/cairnkeep-memory.ts \
|
|
348
|
+
docs/operating.md \
|
|
349
|
+
docs/harness-compatibility.md \
|
|
350
|
+
docs/native-windows.md \
|
|
351
|
+
docs/building-an-overlay.md
|
|
352
|
+
do
|
|
353
|
+
[[ -f "$ROOT/$required" ]] || phase26_surface_complete=false
|
|
354
|
+
done
|
|
355
|
+
|
|
356
|
+
if [[ "$phase26_surface_complete" != true ]]; then
|
|
357
|
+
if [[ "${CAIRN_PHASE26_RED:-0}" == 1 ]]; then
|
|
358
|
+
echo "PHASE26_RED:SETUP_PACKAGE_MISSING"
|
|
359
|
+
exit 86
|
|
360
|
+
fi
|
|
361
|
+
echo "SKIP: packaged guided setup and Pi bridge surface is not complete"
|
|
362
|
+
echo "PASS: npm tarball installs a self-contained CLI and MCP server"
|
|
363
|
+
exit 0
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
for required in \
|
|
367
|
+
scripts/setup.mjs \
|
|
368
|
+
scripts/setup-core.mjs \
|
|
369
|
+
scripts/setup-reconcile.mjs \
|
|
370
|
+
schemas/cairnkeep-setup.schema.json \
|
|
371
|
+
schemas/cairnkeep-setup-policy.schema.json \
|
|
372
|
+
mcp-memory-server/dist/pi-mcp-bridge.js \
|
|
373
|
+
pi/extensions/cairnkeep-memory.ts \
|
|
374
|
+
docs/operating.md \
|
|
375
|
+
docs/harness-compatibility.md \
|
|
376
|
+
docs/native-windows.md \
|
|
377
|
+
docs/building-an-overlay.md
|
|
378
|
+
do
|
|
379
|
+
[[ -f "$installed_root/$required" ]] || fail "npm tarball omitted Phase 26 asset $required"
|
|
380
|
+
done
|
|
381
|
+
|
|
382
|
+
installed_setup_target="$tmp/Installed Setup – Unicode"
|
|
383
|
+
(cd "$tmp" && cairn setup "$installed_setup_target" \
|
|
384
|
+
--git none --harness claude,pi --memory local --yes --json) >"$tmp/installed-setup.json" \
|
|
385
|
+
|| fail "installed cairn setup failed outside the source checkout"
|
|
386
|
+
node - "$tmp/installed-setup.json" "$installed_setup_target" <<'NODE'
|
|
387
|
+
const fs = require("fs");
|
|
388
|
+
const [resultPath, target] = process.argv.slice(2);
|
|
389
|
+
const result = JSON.parse(fs.readFileSync(resultPath, "utf8"));
|
|
390
|
+
if (result.schema_version !== 1 || result.status !== "limited") process.exit(1);
|
|
391
|
+
if (result.git !== "none" || result.memory !== "local") process.exit(1);
|
|
392
|
+
if (JSON.stringify(result.harnesses) !== JSON.stringify(["claude", "pi"])) process.exit(1);
|
|
393
|
+
if (!fs.existsSync(`${target}/.ai/cairnkeep.json`)) process.exit(1);
|
|
394
|
+
NODE
|
|
395
|
+
|
|
396
|
+
if find "$installed_root" -type f \( \
|
|
397
|
+
-name '.env' -o -name '*.tmp' -o -name '*.bak' -o -name 'cairnkeep.json' \
|
|
398
|
+
-o -path '*/.planning/*' -o -path '*/.agentfs/*' \
|
|
399
|
+
\) | grep -q .; then
|
|
400
|
+
fail "npm tarball included temporary or project-private setup state"
|
|
401
|
+
fi
|
|
402
|
+
|
|
334
403
|
echo "PASS: npm tarball installs a self-contained CLI and MCP server"
|
|
@@ -48,7 +48,7 @@ run_shell_syntax_baseline() {
|
|
|
48
48
|
local script
|
|
49
49
|
while IFS= read -r script; do
|
|
50
50
|
bash -n "$script" || return 1
|
|
51
|
-
done < <(find "$ROOT/bin" "$ROOT/scripts" -type f
|
|
51
|
+
done < <(find "$ROOT/bin" "$ROOT/scripts" -type f -name '*.sh' | sort)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
run_baseline() {
|
|
@@ -142,7 +142,7 @@ run_baseline() {
|
|
|
142
142
|
run_baseline_check "$ROOT/scripts/test-completion.sh"
|
|
143
143
|
run_baseline_check "$ROOT/scripts/test-package-install.sh"
|
|
144
144
|
run_baseline_check "$ROOT/scripts/test-uninstall.sh"
|
|
145
|
-
run_baseline_check
|
|
145
|
+
run_baseline_check node --check "$ROOT/bin/cairn"
|
|
146
146
|
run_master_off_identity
|
|
147
147
|
echo "PASS: Phase 18 master-off CLI/package/lifecycle identity baseline"
|
|
148
148
|
}
|
|
@@ -336,8 +336,18 @@ const cancelTask={...tasks[0],id:"task-cancel",input:"cancel"};
|
|
|
336
336
|
const cancelPlan=makePlan([cancelTask]);
|
|
337
337
|
const cancelStore=await reports.createEvalReportStore({root:join(fixtureRoot,"reports"),experiment_id:"cancel-fixture"});
|
|
338
338
|
const controller=new AbortController();
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
const cancellation=runner.runTwoPassExperiment({plan:cancelPlan,report_store:cancelStore,temporary_root:fixtureRoot,signal:controller.signal,distill_command:{program:process.execPath,args:[distillerScript]}});
|
|
340
|
+
let adapterStarted=false;
|
|
341
|
+
for (let attempt=0; attempt<200; attempt+=1) {
|
|
342
|
+
await new Promise((resolve)=>setTimeout(resolve,25));
|
|
343
|
+
if (existsSync(invocationLog) && readFileSync(invocationLog,"utf8").includes("task-cancel:run1")) {
|
|
344
|
+
adapterStarted=true;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
assert.equal(adapterStarted,true,"cancellation fixture adapter did not start");
|
|
349
|
+
controller.abort();
|
|
350
|
+
const cancelled=await cancellation;
|
|
341
351
|
assert.equal(cancelled.report.status,"partial");
|
|
342
352
|
assert.deepEqual(cancelled.report.observations.map(({terminal_state})=>terminal_state),["cancelled"]);
|
|
343
353
|
assert.equal(cancelled.report.observations[0].process.cleanup!=="pending",true);
|