@cairnkeep/cli 2.16.0 → 2.17.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 (59) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -0
  3. package/docs/README.md +8 -0
  4. package/docs/agents.md +20 -0
  5. package/docs/context-intelligence.md +143 -0
  6. package/docs/context-packs.md +33 -0
  7. package/docs/domain-knowledge.md +29 -0
  8. package/docs/learning/CURRICULUM-MAP.md +3 -1
  9. package/docs/learning/FEATURE-GUIDE.md +22 -3
  10. package/docs/learning/README.md +1 -0
  11. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  12. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  13. package/docs/learning/lessons/L02-installation.md +2 -2
  14. package/docs/learning/lessons/L03-first-project.md +1 -1
  15. package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
  16. package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
  17. package/docs/learning/lessons/L21-context-packs.md +1 -1
  18. package/docs/learning/lessons/L22-native-windows.md +2 -2
  19. package/docs/learning/lessons/L23-guided-setup.md +1 -1
  20. package/docs/learning/lessons/L24-okf-exchange.md +1 -1
  21. package/docs/learning/lessons/L25-playbooks.md +1 -1
  22. package/docs/learning/lessons/L26-context-intelligence.md +180 -0
  23. package/docs/learning/tracks/evidence-and-evaluation.md +1 -0
  24. package/docs/learning/tracks/operator.md +1 -0
  25. package/docs/learning/tracks/practitioner.md +1 -0
  26. package/docs/learning/video-scripts/V26-context-intelligence.md +144 -0
  27. package/docs/operating.md +44 -0
  28. package/docs/privacy-and-data-flow.md +22 -0
  29. package/docs/storage.md +21 -0
  30. package/docs/work-evidence.md +14 -0
  31. package/examples/eval/bundled-fake.json +1 -1
  32. package/mcp-memory-server/dist/context-pack-cli.js +3 -1
  33. package/mcp-memory-server/dist/context-pack-retrieval.js +413 -0
  34. package/mcp-memory-server/dist/context-pack.js +171 -3
  35. package/mcp-memory-server/dist/domain-retrieval-provider.js +257 -0
  36. package/mcp-memory-server/dist/index.js +73 -387
  37. package/mcp-memory-server/dist/mcp-tool-catalog.js +2 -0
  38. package/mcp-memory-server/dist/memory-extraction.js +87 -0
  39. package/mcp-memory-server/dist/memory-proposal-cli.js +110 -0
  40. package/mcp-memory-server/dist/memory-proposal-schema.js +78 -0
  41. package/mcp-memory-server/dist/memory-proposal-store.js +273 -0
  42. package/mcp-memory-server/dist/platform-security.js +32 -26
  43. package/mcp-memory-server/dist/retrieval-benchmark.js +199 -0
  44. package/mcp-memory-server/dist/reviewed-memory-store.js +281 -0
  45. package/mcp-memory-server/dist/work-evidence-schema.js +17 -0
  46. package/mcp-memory-server/dist/work-evidence-store.js +57 -1
  47. package/mcp-memory-server/package.json +8 -1
  48. package/package.json +1 -1
  49. package/schemas/context-usage-receipt.schema.json +41 -0
  50. package/schemas/memory-proposal.schema.json +65 -0
  51. package/schemas/retrieval-benchmark.schema.json +64 -0
  52. package/scripts/cairn-cli.mjs +2 -0
  53. package/scripts/completion.sh +17 -6
  54. package/scripts/doctor.sh +4 -2
  55. package/scripts/test-completion.sh +8 -1
  56. package/scripts/test-doctor.sh +21 -0
  57. package/scripts/test-release-sbom.sh +3 -0
  58. package/scripts/test-windows-native.mjs +30 -0
  59. package/scripts/windows-platform.mjs +8 -5
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/cairnkeep/cairnkeep/main/schemas/context-usage-receipt.schema.json",
4
+ "title": "Cairnkeep context usage receipt v1",
5
+ "description": "A privacy-minimal link from existing work evidence to a retrieved result. Receipts contain identifiers, digests, outcome labels and a timestamp; never retrieved content, queries, prompts or model output.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schema_version",
10
+ "link_id",
11
+ "evidence_id",
12
+ "kind",
13
+ "created_at",
14
+ "receipt_id",
15
+ "task_digest",
16
+ "result_digest",
17
+ "outcome"
18
+ ],
19
+ "properties": {
20
+ "schema_version": { "const": 1 },
21
+ "link_id": {
22
+ "description": "Equal to receipt_id for context usage links.",
23
+ "type": "string",
24
+ "pattern": "^[a-f0-9]{64}$"
25
+ },
26
+ "evidence_id": {
27
+ "type": "string",
28
+ "pattern": "^wev_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
29
+ },
30
+ "kind": { "const": "context_usage" },
31
+ "created_at": { "type": "string", "format": "date-time" },
32
+ "receipt_id": {
33
+ "description": "SHA-256 of canonical evidence_id, task_digest and result_digest fields. The outcome is deliberately excluded so a changed outcome is a conflict, not a second receipt.",
34
+ "type": "string",
35
+ "pattern": "^[a-f0-9]{64}$"
36
+ },
37
+ "task_digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
38
+ "result_digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
39
+ "outcome": { "enum": ["used", "unused", "unknown"] }
40
+ }
41
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/cairnkeep/cairnkeep/main/schemas/memory-proposal.schema.json",
4
+ "title": "Cairnkeep review-gated memory proposal",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "created_at", "project_root", "scope", "source", "extraction", "candidates", "digest"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "created_at": { "type": "string", "format": "date-time" },
11
+ "project_root": { "type": "string", "minLength": 1 },
12
+ "scope": { "type": "string", "pattern": "^(project|[a-z0-9][a-z0-9-]*)$" },
13
+ "source": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["kind", "session_id", "digest"],
17
+ "properties": {
18
+ "kind": { "const": "trajectory" },
19
+ "session_id": { "type": "string", "minLength": 1, "maxLength": 256 },
20
+ "digest": { "$ref": "#/$defs/digest" }
21
+ }
22
+ },
23
+ "extraction": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["model"],
27
+ "properties": { "model": { "type": "string", "minLength": 1, "maxLength": 256 } }
28
+ },
29
+ "candidates": {
30
+ "type": "array",
31
+ "minItems": 1,
32
+ "maxItems": 128,
33
+ "items": { "$ref": "#/$defs/candidate" }
34
+ },
35
+ "digest": { "$ref": "#/$defs/digest" }
36
+ },
37
+ "$defs": {
38
+ "digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
39
+ "candidate": {
40
+ "type": "object",
41
+ "additionalProperties": false,
42
+ "required": ["key", "value", "operation", "base_hash", "value_hash"],
43
+ "$comment": "Runtime verification also requires value_hash to equal SHA-256(value), noop base_hash to equal value_hash, and update base_hash to differ from value_hash.",
44
+ "properties": {
45
+ "key": { "type": "string", "minLength": 1, "maxLength": 512 },
46
+ "value": { "type": "string", "maxLength": 65536 },
47
+ "category": { "type": "string", "maxLength": 64 },
48
+ "importance": { "type": "number", "minimum": 0, "maximum": 1 },
49
+ "operation": { "enum": ["create", "update", "noop"] },
50
+ "base_hash": { "oneOf": [{ "$ref": "#/$defs/digest" }, { "type": "null" }] },
51
+ "value_hash": { "$ref": "#/$defs/digest" }
52
+ },
53
+ "allOf": [
54
+ {
55
+ "if": { "properties": { "operation": { "const": "create" } }, "required": ["operation"] },
56
+ "then": { "properties": { "base_hash": { "type": "null" } } }
57
+ },
58
+ {
59
+ "if": { "properties": { "operation": { "enum": ["update", "noop"] } }, "required": ["operation"] },
60
+ "then": { "properties": { "base_hash": { "$ref": "#/$defs/digest" } } }
61
+ }
62
+ ]
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cairnkeep.dev/schemas/retrieval-benchmark.schema.json",
4
+ "title": "Cairnkeep retrieval benchmark suite",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "id", "description", "packs", "cases"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
11
+ "description": { "type": "string", "minLength": 1 },
12
+ "packs": {
13
+ "type": "array",
14
+ "minItems": 1,
15
+ "maxItems": 32,
16
+ "items": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "required": ["id", "version", "title", "enabled", "files"],
20
+ "properties": {
21
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
22
+ "version": { "type": "string", "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$" },
23
+ "title": { "type": "string", "minLength": 1 },
24
+ "enabled": { "type": "boolean" },
25
+ "files": {
26
+ "type": "array",
27
+ "minItems": 1,
28
+ "maxItems": 1024,
29
+ "items": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["path", "kind", "title", "description", "keywords", "content"],
33
+ "properties": {
34
+ "path": { "type": "string", "minLength": 1, "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" },
35
+ "kind": { "enum": ["document", "skill"] },
36
+ "title": { "type": "string", "minLength": 1 },
37
+ "description": { "type": "string" },
38
+ "keywords": { "type": "array", "items": { "type": "string" } },
39
+ "content": { "type": "string" }
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }
45
+ },
46
+ "cases": {
47
+ "type": "array",
48
+ "minItems": 1,
49
+ "maxItems": 1024,
50
+ "items": {
51
+ "type": "object",
52
+ "additionalProperties": false,
53
+ "required": ["id", "query", "relevant", "forbidden", "limit"],
54
+ "properties": {
55
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
56
+ "query": { "type": "string", "minLength": 1 },
57
+ "relevant": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
58
+ "forbidden": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
59
+ "limit": { "type": "integer", "minimum": 1, "maximum": 100 }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
@@ -9,6 +9,7 @@ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
9
  const NODE_COMMANDS = new Map([
10
10
  ["memory-server", "index.js"],
11
11
  ["trajectory", "trajectory-cli.js"],
12
+ ["proposals", "memory-proposal-cli.js"],
12
13
  ["artifact", "artifact-cli.js"],
13
14
  ["capabilities", "capability-cli.js"],
14
15
  ["mcp-tools", "mcp-tool-cli.js"],
@@ -45,6 +46,7 @@ Usage:
45
46
  cairn sync-kimi [--check|--apply] [--live-root DIR]
46
47
  cairn doctor [--repair]
47
48
  cairn trajectory <list|show|prune>
49
+ cairn proposals <create|list|show|apply|doctor>
48
50
  cairn artifact <list|show|delete|prune>
49
51
  cairn capabilities <list|status|enable|disable|reset|logging>
50
52
  cairn mcp-tools <list|status|set|reset>
@@ -13,7 +13,7 @@ _cairn_complete() {
13
13
  COMPREPLY=()
14
14
  current=${COMP_WORDS[COMP_CWORD]}
15
15
  previous=${COMP_WORDS[COMP_CWORD-1]:-}
16
- commands="bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help"
16
+ commands="bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack proposals notes eval skill graph memory audit-timer uninstall completion version help"
17
17
  if (( COMP_CWORD == 1 )); then
18
18
  COMPREPLY=( $(compgen -W "$commands" -- "$current") )
19
19
  return
@@ -57,7 +57,8 @@ _cairn_complete() {
57
57
  esac
58
58
  ;;
59
59
  mcp-tools) COMPREPLY=( $(compgen -W "list status set reset full read-only custom --tool --project --json" -- "$current") ) ;;
60
- pack) COMPREPLY=( $(compgen -W "init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill --id --version --title --description --license --ref --project --project-id --output --file --note --check --apply --confirm --json" -- "$current") ) ;;
60
+ pack) COMPREPLY=( $(compgen -W "init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill doctor --id --version --title --description --license --ref --project --project-id --output --file --note --check --apply --confirm --repair --json" -- "$current") ) ;;
61
+ proposals) COMPREPLY=( $(compgen -W "create list show apply doctor --session --scope --model --category --project --json" -- "$current") ) ;;
61
62
  notes) COMPREPLY=( $(compgen -W "distill search-error promote doctor --project --session --all-projects --para-root --text --component --with --confirm --repair --json" -- "$current") ) ;;
62
63
  eval)
63
64
  case "${COMP_WORDS[2]:-}" in
@@ -101,6 +102,7 @@ _cairn() {
101
102
  'capabilities:inspect and manage project capability state'
102
103
  'mcp-tools:inspect and restrict MCP tool exposure'
103
104
  'pack:manage immutable context packs and skill approvals'
105
+ 'proposals:review and apply session memory proposals'
104
106
  'notes:distill and search local hindsight notes'
105
107
  'eval:run and inspect default-off local evaluations'
106
108
  'skill:review and evaluate evidence-backed skill improvements'
@@ -136,7 +138,8 @@ _cairn() {
136
138
  evidence) _values 'work evidence command' list show delete prune doctor '--status[filter records]:status:(pending complete)' '--dry-run[report without mutation]' '--repair[remove safe temporary remnants]' '--json[emit JSON]' ;;
137
139
  playbook) _values 'playbook command' list status init set enable disable reset check record receipts instructions doctor minimal balanced strict must should may off start finish install remove context.recall context.explore work.plan verify.tests review.repository review.security docs.update learning.capture '--project[project root]:directory:_files -/' '--json[emit JSON]' '--enforce[fail when must evidence is missing]' '--changed[changed project path]:file:_files' '--change-type[change type]:type:(code tests docs config dependencies security)' '--complexity[task complexity]:level:(trivial standard complex)' '--familiarity[context familiarity]:level:(known mixed unfamiliar)' '--risk[task risk]:level:(low normal high security)' '--public-change[public behavior changed]' '--completed[completed action]:action:' '--skipped[skipped action and reason]:evidence:' '--failed[failed action and reason]:evidence:' '--actor[local actor label]:actor:' '--actor-kind[local actor kind]:kind:(user agent service)' '--session[session label]:session:' '--policy[policy digest]:digest:' '--decision[decision digest]:digest:' '--event[lifecycle event]:event:(start check finish)' '--action[canonical action]:action:(context.recall context.explore work.plan verify.tests review.repository review.security docs.update learning.capture)' '--outcome[outcome]:outcome:(completed skipped failed)' '--reason[bounded reason]:reason:' ;;
138
140
  mcp-tools) _values 'MCP tool profile command' list status set reset full read-only custom '--tool[allow an exact tool]:tool:' '--project[project root]:directory:_files -/' '--json[emit JSON]' ;;
139
- pack) _values 'context pack command' init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill '--ref[pinned Git ref]:ref:' '--project[project root]:directory:_files -/' '--project-id[remote project ID]:project ID:' '--output[export output directory]:directory:_files -/' '--file[explicit project Markdown file]:file:_files' '--note[promoted shared note ID]:note ID:' '--check[inspect update or export]' '--apply[apply update or export]' '--confirm[confirm digest]:digest:' '--json[emit JSON]' ;;
141
+ pack) _values 'context pack command' init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill doctor '--ref[pinned Git ref]:ref:' '--project[project root]:directory:_files -/' '--project-id[remote project ID]:project ID:' '--output[export output directory]:directory:_files -/' '--file[explicit project Markdown file]:file:_files' '--note[promoted shared note ID]:note ID:' '--check[inspect update or export]' '--apply[apply update or export]' '--confirm[confirm digest]:digest:' '--repair[remove only invalid derived context cache state]' '--json[emit JSON]' ;;
142
+ proposals) _values 'memory proposal command' create list show apply doctor '--session[trajectory session ID]:session:' '--scope[memory scope]:scope:' '--model[extraction model]:model:' '--category[extraction category]:category:' '--project[project root]:directory:_files -/' '--json[emit JSON]' ;;
140
143
  capabilities)
141
144
  case $words[3] in
142
145
  list|status) _arguments '--json[emit JSON]' ;;
@@ -185,8 +188,8 @@ EOF
185
188
  fish)
186
189
  sed "s/__CAIRN_HARNESSES__/$harnesses/g" <<'EOF'
187
190
  complete -c cairn -f
188
- for command in bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help
189
- complete -c cairn -n "not __fish_seen_subcommand_from bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack notes eval skill graph memory audit-timer uninstall completion version help" -a $command
191
+ for command in bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack proposals notes eval skill graph memory audit-timer uninstall completion version help
192
+ complete -c cairn -n "not __fish_seen_subcommand_from bootstrap setup memory-server sync sync-pi sync-kimi doctor trajectory artifact evidence playbook capabilities mcp-tools pack proposals notes eval skill graph memory audit-timer uninstall completion version help" -a $command
190
193
  end
191
194
  complete -c cairn -n "__fish_seen_subcommand_from sync" -l apply
192
195
  complete -c cairn -n "__fish_seen_subcommand_from sync" -l live-root -r
@@ -243,12 +246,20 @@ complete -c cairn -n "__fish_seen_subcommand_from mcp-tools; and __fish_seen_sub
243
246
  complete -c cairn -n "__fish_seen_subcommand_from mcp-tools" -l project -r
244
247
  complete -c cairn -n "__fish_seen_subcommand_from mcp-tools" -l tool -r
245
248
  complete -c cairn -n "__fish_seen_subcommand_from mcp-tools" -l json
246
- complete -c cairn -n "__fish_seen_subcommand_from pack" -a "init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill"
249
+ complete -c cairn -n "__fish_seen_subcommand_from pack" -a "init lock validate install import-okf validate-okf export-okf list show remove enable disable update skills approve-skill revoke-skill doctor"
247
250
  complete -c cairn -n "__fish_seen_subcommand_from pack" -l project -r
248
251
  complete -c cairn -n "__fish_seen_subcommand_from pack" -l project-id -r
249
252
  complete -c cairn -n "__fish_seen_subcommand_from pack" -l ref -r
250
253
  complete -c cairn -n "__fish_seen_subcommand_from pack" -l confirm -r
251
254
  complete -c cairn -n "__fish_seen_subcommand_from pack" -l json
255
+ complete -c cairn -n "__fish_seen_subcommand_from pack; and __fish_seen_subcommand_from doctor" -l repair
256
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -a "create list show apply doctor"
257
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -l session -r
258
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -l scope -r
259
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -l model -r
260
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -l category -r
261
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -l project -r
262
+ complete -c cairn -n "__fish_seen_subcommand_from proposals" -l json
252
263
  complete -c cairn -n "__fish_seen_subcommand_from notes" -a "distill search-error promote doctor"
253
264
  complete -c cairn -n "__fish_seen_subcommand_from eval; and not __fish_seen_subcommand_from validate run ablate report prune delete" -a "validate run ablate report prune delete"
254
265
  complete -c cairn -n "__fish_seen_subcommand_from eval; and __fish_seen_subcommand_from validate run ablate" -l task-set -r
package/scripts/doctor.sh CHANGED
@@ -13,7 +13,7 @@ REPAIR_STORES=0
13
13
  while [[ $# -gt 0 ]]; do
14
14
  case "$1" in
15
15
  --repair) REPAIR_STORES=1; shift ;;
16
- -h|--help) echo "Usage: cairn doctor [--repair] # repair derived trajectory/artifact/typed metadata, indexes, and interrupted note transactions"; exit 0 ;;
16
+ -h|--help) echo "Usage: cairn doctor [--repair] # repair safe derived pack caches, typed metadata/indexes, and interrupted note transactions"; exit 0 ;;
17
17
  *) echo "Unknown doctor option: $1" >&2; echo "Usage: cairn doctor [--repair]" >&2; exit 2 ;;
18
18
  esac
19
19
  done
@@ -470,11 +470,13 @@ fi
470
470
  pack_cli="$CAIRN_ROOT/mcp-memory-server/dist/context-pack-cli.js"
471
471
  pack_base="${CAIRN_PACK_BASE_DIR:-$HOME/.cairnkeep/packs}"
472
472
  pack_base="${pack_base/#\~/$HOME}"
473
+ pack_args=(doctor --json)
474
+ if [[ "$REPAIR_STORES" == 1 ]]; then pack_args+=(--repair); fi
473
475
  if [[ ! -e "$pack_base" ]]; then
474
476
  skip "context packs (not installed)"
475
477
  elif [[ ! -f "$pack_cli" ]]; then
476
478
  fail "context-pack checker is missing from the installed package"
477
- elif pack_json=$(node "$pack_cli" doctor --json 2>/dev/null) && node -e 'const v=JSON.parse(process.argv[1]); if(v.ok!==true)process.exit(1)' "$pack_json"; then
479
+ elif pack_json=$(node "$pack_cli" "${pack_args[@]}" 2>/dev/null) && node -e 'const v=JSON.parse(process.argv[1]); if(v.ok!==true)process.exit(1)' "$pack_json"; then
478
480
  pack_counts=$(node -e 'const v=JSON.parse(process.argv[1]); process.stdout.write(`${v.objects} object(s), ${v.projects} project pointer(s)`)' "$pack_json")
479
481
  pass "context packs healthy ($pack_counts)"
480
482
  else
@@ -46,7 +46,13 @@ for shell in bash zsh fish; do
46
46
  grep -q 'mcp-tools' "$tmp/$shell"
47
47
  grep -q 'full.*read-only.*custom\|full read-only custom' "$tmp/$shell"
48
48
  grep -q 'pack' "$tmp/$shell"
49
- grep -q 'init.*lock.*validate.*install.*list.*show.*remove.*enable.*disable.*update.*skills.*approve-skill.*revoke-skill\|init lock validate install list show remove enable disable update skills approve-skill revoke-skill' "$tmp/$shell"
49
+ grep -q 'init.*lock.*validate.*install.*list.*show.*remove.*enable.*disable.*update.*skills.*approve-skill.*revoke-skill.*doctor\|init lock validate install list show remove enable disable update skills approve-skill revoke-skill doctor' "$tmp/$shell"
50
+ grep -q -- '--repair\|-l repair' "$tmp/$shell"
51
+ grep -q 'proposals' "$tmp/$shell"
52
+ grep -q 'create.*list.*show.*apply.*doctor\|create list show apply doctor' "$tmp/$shell"
53
+ for proposal_flag in --session --scope --model --category --project --json; do
54
+ grep -q -- "$proposal_flag\|-l ${proposal_flag#--}" "$tmp/$shell"
55
+ done
50
56
  grep -q -- '--purge-packs\|-l purge-packs' "$tmp/$shell"
51
57
  grep -q 'evidence' "$tmp/$shell"
52
58
  grep -q 'list.*show.*delete.*prune.*doctor\|list show delete prune doctor' "$tmp/$shell"
@@ -77,6 +83,7 @@ fi
77
83
  grep -qF 'cairn skill <harvest|list|show|review|propose|evaluate|apply|rollback>' "$tmp/root-help"
78
84
  grep -qF 'cairn evidence <list|show|delete|prune|doctor>' "$tmp/root-help"
79
85
  grep -qF 'cairn playbook <list|status|init|set|enable|disable|reset|check|record|receipts|instructions|doctor>' "$tmp/root-help"
86
+ grep -qF 'cairn proposals <create|list|show|apply|doctor>' "$tmp/root-help"
80
87
  node "$ROOT/mcp-memory-server/dist/skill-cli.js" --help >"$tmp/skill-help"
81
88
  for command in $skill_commands; do
82
89
  grep -q "cairn skill $command" "$tmp/skill-help"
@@ -30,6 +30,27 @@ grep -q "capability configuration (not present — managed overrides are unused)
30
30
  grep -q "capability callback namespace (not present — callback logging is unused)" "$tmp/out1" ||
31
31
  fail "expected absent capability callback diagnostics"
32
32
 
33
+ # Top-level repair must reach the context-pack doctor; the pack CLI itself
34
+ # owns the safe derived-cache deletion policy.
35
+ real_node=$(command -v node)
36
+ mkdir -p "$tmp/doctor-node-shim" "$HOME/.cairnkeep/packs"
37
+ cat >"$tmp/doctor-node-shim/node" <<'EOF'
38
+ #!/usr/bin/env bash
39
+ if [[ "${1:-}" == */context-pack-cli.js && "${2:-}" == doctor ]]; then
40
+ printf '%s\n' "$@" >"$CAIRN_TEST_PACK_ARGS"
41
+ printf '%s\n' '{"ok":true,"objects":0,"projects":0,"issues":[],"temporary_remnants":[]}'
42
+ exit 0
43
+ fi
44
+ exec "$CAIRN_TEST_REAL_NODE" "$@"
45
+ EOF
46
+ chmod 755 "$tmp/doctor-node-shim/node"
47
+ ( cd "$proj" && PATH="$tmp/doctor-node-shim:$PATH" CAIRN_TEST_REAL_NODE="$real_node" \
48
+ CAIRN_TEST_PACK_ARGS="$tmp/context-pack-doctor.args" "$doctor" --repair ) >"$tmp/out-pack-repair" 2>&1 ||
49
+ fail "doctor --repair failed while forwarding context-pack repair:\n$(cat "$tmp/out-pack-repair")"
50
+ grep -qx -- '--repair' "$tmp/context-pack-doctor.args" ||
51
+ fail "top-level doctor --repair did not reach the context-pack doctor"
52
+ rm -rf "$HOME/.cairnkeep/packs"
53
+
33
54
  # 2. An unsupported runtime is diagnosed before the server probe.
34
55
  mkdir -p "$tmp/old-node"
35
56
  cat > "$tmp/old-node/node" <<'EOF'
@@ -51,6 +51,9 @@ if (publishStep < 0 || attachStep < 0 || publishStep >= attachStep) {
51
51
  if (!publishWorkflow.includes('npm publish "./artifacts/cairnkeep-cli-$VERSION.tgz"')) {
52
52
  throw new Error("npm publication must use an explicit relative tarball path");
53
53
  }
54
+ if (!publishWorkflow.includes('gh release upload "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY"')) {
55
+ throw new Error("release attachment must name the repository outside a checkout");
56
+ }
54
57
  if (publishWorkflow.includes("check:public")) {
55
58
  throw new Error("release workflow must promote the verified candidate without repeating the public suite");
56
59
  }
@@ -21,6 +21,7 @@ import { HARNESS_IDS } from "./harness-registry.mjs";
21
21
  const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
22
22
  const sandbox = mkdtempSync(join(tmpdir(), "cairn-windows-native-"));
23
23
  const originalBase = process.env.CAIRN_AGENTFS_BASE_DIR;
24
+ const originalPackBase = process.env.CAIRN_PACK_BASE_DIR;
24
25
  const originalHome = process.env.HOME;
25
26
  const originalProfile = process.env.USERPROFILE;
26
27
  try {
@@ -69,6 +70,33 @@ try {
69
70
  assert.match(powershellCompletion(), /playbook/);
70
71
  assert.match(powershellCompletion(), /review\.security/);
71
72
  assert.match(powershellCompletion(), /--enforce/);
73
+ assert.match(powershellCompletion(), /doctor/);
74
+ assert.match(powershellCompletion(), /--repair/);
75
+ assert.match(powershellCompletion(), /proposals/);
76
+ assert.match(powershellCompletion(), /'create','list','show','apply','doctor'/);
77
+ assert.match(powershellCompletion(), /--category/);
78
+
79
+ const packBase = join(sandbox, "Context Packs");
80
+ const invalidDerivedPack = join(packBase, "cache", "context", "f".repeat(64));
81
+ mkdirSync(invalidDerivedPack, { recursive: true });
82
+ writeFileSync(join(invalidDerivedPack, `${"e".repeat(64)}.json`), "{\"corrupt\":true}\n");
83
+ process.env.CAIRN_PACK_BASE_DIR = packBase;
84
+ const inheritedLlmUrl = process.env.CAIRN_LLM_API_URL;
85
+ const exitCodeBeforeDoctor = process.exitCode;
86
+ const doctorWrite = process.stdout.write.bind(process.stdout);
87
+ let doctorOutput = "";
88
+ delete process.env.CAIRN_LLM_API_URL;
89
+ process.stdout.write = ((chunk) => { doctorOutput += String(chunk); return true; });
90
+ try {
91
+ await runWindowsCommand({ command: "doctor", args: ["--repair"], root });
92
+ } finally {
93
+ process.stdout.write = doctorWrite;
94
+ process.exitCode = exitCodeBeforeDoctor;
95
+ if (inheritedLlmUrl === undefined) delete process.env.CAIRN_LLM_API_URL;
96
+ else process.env.CAIRN_LLM_API_URL = inheritedLlmUrl;
97
+ }
98
+ assert.equal(existsSync(invalidDerivedPack), false, "native Windows doctor forwards safe context-pack cache repair");
99
+ assert.match(doctorOutput, /\[PASS\] context pack diagnostics passed/);
72
100
 
73
101
  let containerArgs = [];
74
102
  runNativeContainer(["stdio", "--image", "example/windows:1", "--volume", "windows-data"], root, (_engine, args) => { containerArgs = args; });
@@ -167,6 +195,8 @@ try {
167
195
  } finally {
168
196
  if (originalBase === undefined) delete process.env.CAIRN_AGENTFS_BASE_DIR;
169
197
  else process.env.CAIRN_AGENTFS_BASE_DIR = originalBase;
198
+ if (originalPackBase === undefined) delete process.env.CAIRN_PACK_BASE_DIR;
199
+ else process.env.CAIRN_PACK_BASE_DIR = originalPackBase;
170
200
  if (originalHome === undefined) delete process.env.HOME; else process.env.HOME = originalHome;
171
201
  if (originalProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = originalProfile;
172
202
  rmSync(sandbox, { recursive: true, force: true });
@@ -452,8 +452,9 @@ async function doctorWindows(root, args) {
452
452
  accessSync(store, fsConstants.W_OK);
453
453
  line("PASS", `memory store is writable: ${store}`);
454
454
  } catch { line("FAIL", `memory store is not writable: ${store}`); }
455
- const sqlite = run("where.exe", ["sqlite3.exe"]);
456
- line(sqlite.status === 0 ? "PASS" : "WARN", sqlite.status === 0
455
+ let sqliteStatus = null;
456
+ try { sqliteStatus = run("where.exe", ["sqlite3.exe"]).status; } catch {}
457
+ line(sqliteStatus === 0 ? "PASS" : "WARN", sqliteStatus === 0
457
458
  ? "sqlite3 is available for WAL-safe memory export"
458
459
  : "sqlite3 is not installed; runtime works, but memory export is unavailable");
459
460
  for (const [label, file, doctorArgs] of [
@@ -462,7 +463,7 @@ async function doctorWindows(root, args) {
462
463
  ["work evidence", "work-evidence-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
463
464
  ["typed memory", "node-cli.js", ["doctor", "--project-root", process.cwd(), ...(repair ? ["--repair"] : [])]],
464
465
  ["capability", "capability-cli.js", ["doctor", "--json"]],
465
- ["context pack", "context-pack-cli.js", ["doctor", "--json"]],
466
+ ["context pack", "context-pack-cli.js", ["doctor", "--json", ...(repair ? ["--repair"] : [])]],
466
467
  ["playbook", "playbook-cli.js", ["doctor", "--json"]],
467
468
  ]) {
468
469
  const entry = join(root, "mcp-memory-server", "dist", file);
@@ -728,10 +729,12 @@ export function powershellCompletion() {
728
729
  const harnesses = HARNESS_IDS.map((id) => `'${id}'`).join(",");
729
730
  return `Register-ArgumentCompleter -Native -CommandName cairn -ScriptBlock {
730
731
  param($wordToComplete, $commandAst, $cursorPosition)
731
- $commands = 'bootstrap','setup','memory-server','sync','sync-pi','sync-kimi','doctor','trajectory','artifact','evidence','playbook','capabilities','mcp-tools','pack','notes','eval','skill','graph','memory','audit-timer','uninstall','completion','version','help'
732
+ $commands = 'bootstrap','setup','memory-server','sync','sync-pi','sync-kimi','doctor','trajectory','artifact','evidence','playbook','capabilities','mcp-tools','pack','proposals','notes','eval','skill','graph','memory','audit-timer','uninstall','completion','version','help'
732
733
  $setup = '--git','--harness','--memory','--policy','--yes','--json','init','existing','none',${harnesses},'local'
733
734
  $playbook = 'list','status','init','set','enable','disable','reset','check','record','receipts','instructions','doctor','minimal','balanced','strict','must','should','may','off','start','finish','install','remove','context.recall','context.explore','work.plan','verify.tests','review.repository','review.security','docs.update','learning.capture','--project','--json','--enforce','--complexity','--familiarity','--risk','--public-change','--changed','--change-type','--completed','--skipped','--failed','--actor','--actor-kind','--session','--policy','--decision','--event','--action','--outcome','--reason'
734
- $candidates = if ($commandAst.ToString() -match '^\\s*cairn\\s+setup(?:\\s|$)') { $setup } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+playbook(?:\\s|$)') { $playbook } else { $commands }
735
+ $pack = 'init','lock','validate','install','import-okf','validate-okf','export-okf','list','show','remove','enable','disable','update','skills','approve-skill','revoke-skill','doctor','--repair','--project','--project-id','--json'
736
+ $proposals = 'create','list','show','apply','doctor','--session','--scope','--model','--category','--project','--json'
737
+ $candidates = if ($commandAst.ToString() -match '^\\s*cairn\\s+setup(?:\\s|$)') { $setup } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+playbook(?:\\s|$)') { $playbook } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+pack(?:\\s|$)') { $pack } elseif ($commandAst.ToString() -match '^\\s*cairn\\s+proposals(?:\\s|$)') { $proposals } else { $commands }
735
738
  $candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
736
739
  [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
737
740
  }