@cairnkeep/cli 2.16.0 → 2.17.1
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 +34 -0
- package/README.md +1 -0
- package/docs/README.md +8 -0
- package/docs/agents.md +20 -0
- package/docs/context-intelligence.md +143 -0
- package/docs/context-packs.md +33 -0
- package/docs/domain-knowledge.md +29 -0
- package/docs/ecosystem.md +0 -1
- package/docs/learning/CURRICULUM-MAP.md +3 -1
- package/docs/learning/FEATURE-GUIDE.md +22 -3
- package/docs/learning/README.md +1 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/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 +2 -2
- package/docs/learning/lessons/L23-guided-setup.md +1 -1
- package/docs/learning/lessons/L24-okf-exchange.md +1 -1
- package/docs/learning/lessons/L25-playbooks.md +1 -1
- package/docs/learning/lessons/L26-context-intelligence.md +180 -0
- package/docs/learning/tracks/evidence-and-evaluation.md +1 -0
- package/docs/learning/tracks/operator.md +1 -0
- package/docs/learning/tracks/practitioner.md +1 -0
- package/docs/learning/video-scripts/V26-context-intelligence.md +144 -0
- package/docs/operating.md +44 -0
- package/docs/privacy-and-data-flow.md +22 -0
- package/docs/storage.md +21 -0
- package/docs/work-evidence.md +14 -0
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/context-pack-cli.js +3 -1
- package/mcp-memory-server/dist/context-pack-retrieval.js +413 -0
- package/mcp-memory-server/dist/context-pack.js +171 -3
- package/mcp-memory-server/dist/domain-retrieval-provider.js +257 -0
- package/mcp-memory-server/dist/index.js +73 -387
- package/mcp-memory-server/dist/mcp-tool-catalog.js +2 -0
- package/mcp-memory-server/dist/memory-extraction.js +87 -0
- package/mcp-memory-server/dist/memory-proposal-cli.js +110 -0
- package/mcp-memory-server/dist/memory-proposal-schema.js +78 -0
- package/mcp-memory-server/dist/memory-proposal-store.js +273 -0
- package/mcp-memory-server/dist/platform-security.js +32 -26
- package/mcp-memory-server/dist/retrieval-benchmark.js +199 -0
- package/mcp-memory-server/dist/reviewed-memory-store.js +281 -0
- package/mcp-memory-server/dist/work-evidence-schema.js +17 -0
- package/mcp-memory-server/dist/work-evidence-store.js +57 -1
- package/mcp-memory-server/package.json +8 -1
- package/package.json +2 -2
- package/schemas/context-usage-receipt.schema.json +41 -0
- package/schemas/memory-proposal.schema.json +65 -0
- package/schemas/retrieval-benchmark.schema.json +64 -0
- package/scripts/cairn-cli.mjs +2 -0
- package/scripts/completion.sh +17 -6
- package/scripts/doctor.sh +4 -2
- package/scripts/terminal-prompts.mjs +5 -3
- package/scripts/test-completion.sh +8 -1
- package/scripts/test-doctor.sh +21 -0
- package/scripts/test-release-sbom.sh +4 -1
- package/scripts/test-setup-preflight.mjs +13 -0
- package/scripts/test-windows-native.mjs +30 -0
- package/scripts/windows-platform.mjs +8 -5
- package/templates/PROJECT-INSTRUCTIONS.md.template +0 -1
|
@@ -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
|
+
}
|
package/scripts/cairn-cli.mjs
CHANGED
|
@@ -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>
|
package/scripts/completion.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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"
|
|
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
|
|
@@ -37,7 +37,6 @@ async function runKeypressPrompt({ input, output, draw, accept }) {
|
|
|
37
37
|
let settled = false;
|
|
38
38
|
let keypressHandler = null;
|
|
39
39
|
let rawChanged = false;
|
|
40
|
-
let resumed = false;
|
|
41
40
|
let cursorHidden = false;
|
|
42
41
|
|
|
43
42
|
const render = () => {
|
|
@@ -59,7 +58,6 @@ async function runKeypressPrompt({ input, output, draw, accept }) {
|
|
|
59
58
|
}
|
|
60
59
|
if (wasPaused && typeof input.resume === "function") {
|
|
61
60
|
input.resume();
|
|
62
|
-
resumed = true;
|
|
63
61
|
}
|
|
64
62
|
output.write("\x1b[?25l");
|
|
65
63
|
cursorHidden = true;
|
|
@@ -92,7 +90,11 @@ async function runKeypressPrompt({ input, output, draw, accept }) {
|
|
|
92
90
|
clear();
|
|
93
91
|
if (cursorHidden) output.write("\x1b[?25h");
|
|
94
92
|
if (rawChanged) input.setRawMode(false);
|
|
95
|
-
|
|
93
|
+
// A TTY can report itself as already flowing before the first prompt (for
|
|
94
|
+
// example through an SSH pseudo-terminal). Leaving it flowing keeps Node's
|
|
95
|
+
// event loop alive after setup has printed its final result. Always pause
|
|
96
|
+
// prompt input after removing our listener; the next selector resumes it.
|
|
97
|
+
if (typeof input.pause === "function") input.pause();
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -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"
|
package/scripts/test-doctor.sh
CHANGED
|
@@ -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'
|
|
@@ -19,7 +19,7 @@ const manifest = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
|
19
19
|
const lock = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
|
|
20
20
|
const candidateWorkflow = fs.readFileSync(process.argv[4], "utf8");
|
|
21
21
|
const publishWorkflow = fs.readFileSync(process.argv[5], "utf8");
|
|
22
|
-
const expected = "6.0.
|
|
22
|
+
const expected = "6.0.1";
|
|
23
23
|
|
|
24
24
|
if (manifest.devDependencies?.["@cyclonedx/cyclonedx-npm"] !== expected) {
|
|
25
25
|
throw new Error(`package.json must pin @cyclonedx/cyclonedx-npm exactly to ${expected}`);
|
|
@@ -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
|
}
|
|
@@ -300,6 +300,19 @@ async function testTerminalSelectors(prompts) {
|
|
|
300
300
|
await assert.rejects(cancelled, (error) => error?.status === 130 && /cancelled/i.test(error.message));
|
|
301
301
|
assert.equal(input.isRaw, false);
|
|
302
302
|
assert.equal(input.isPaused(), true);
|
|
303
|
+
|
|
304
|
+
const flowingInput = new FakeTTY();
|
|
305
|
+
flowingInput.paused = false;
|
|
306
|
+
const flowing = prompts.selectOnePrompt({
|
|
307
|
+
message: "Release flowing input",
|
|
308
|
+
choices: [{ value: "done", label: "Done" }],
|
|
309
|
+
input: flowingInput,
|
|
310
|
+
output: output.stream,
|
|
311
|
+
});
|
|
312
|
+
setImmediate(() => flowingInput.emit("keypress", "", { name: "return" }));
|
|
313
|
+
assert.equal(await flowing, "done");
|
|
314
|
+
assert.equal(flowingInput.isRaw, false);
|
|
315
|
+
assert.equal(flowingInput.isPaused(), true, "terminal prompt must release flowing stdin so the CLI can exit");
|
|
303
316
|
}
|
|
304
317
|
|
|
305
318
|
async function testStructuredSetupSelectors(setup, fixture) {
|
|
@@ -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
|
-
|
|
456
|
-
|
|
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
|
-
$
|
|
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
|
}
|
|
@@ -38,5 +38,4 @@ Scopes defined in `.opencode/memory.json`:
|
|
|
38
38
|
## Tool Roles
|
|
39
39
|
- AgentFS is the durable structured memory layer.
|
|
40
40
|
- AnythingLLM is the long-form document retrieval layer.
|
|
41
|
-
- `lean-ctx` is the default context layer (reads, search, exploration), not durable project memory.
|
|
42
41
|
- RTK is a recommended companion for shell-heavy sessions (git, listings, failing builds); Token Savior is not default.
|