@cairnkeep/cli 2.7.0 → 2.9.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 (60) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +51 -2
  3. package/bin/cairn +19 -1
  4. package/docs/context-packs.md +94 -0
  5. package/docs/design/meta-agent.md +39 -35
  6. package/docs/harness-compatibility.md +7 -1
  7. package/docs/learning/CURRICULUM-MAP.md +14 -4
  8. package/docs/learning/FEATURE-GUIDE.md +11 -4
  9. package/docs/learning/README.md +8 -2
  10. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  11. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  12. package/docs/learning/lessons/L02-installation.md +2 -2
  13. package/docs/learning/lessons/L03-first-project.md +1 -1
  14. package/docs/learning/lessons/L17-agent-boundary.md +12 -8
  15. package/docs/learning/lessons/L18-local-code-graph.md +97 -0
  16. package/docs/learning/lessons/L19-validated-skill-improvement.md +118 -0
  17. package/docs/learning/lessons/L20-mcp-tool-profiles.md +67 -0
  18. package/docs/learning/lessons/L21-context-packs.md +79 -0
  19. package/docs/learning/tracks/evidence-and-evaluation.md +5 -3
  20. package/docs/learning/tracks/operator.md +4 -2
  21. package/docs/learning/tracks/practitioner.md +5 -4
  22. package/docs/mcp-tool-profiles.md +43 -0
  23. package/docs/operating.md +61 -0
  24. package/docs/privacy-and-data-flow.md +59 -1
  25. package/docs/skill-improvement.md +124 -0
  26. package/docs/storage.md +42 -0
  27. package/examples/eval/bundled-fake.json +1 -1
  28. package/mcp-memory-server/dist/context-pack-cli.js +185 -0
  29. package/mcp-memory-server/dist/context-pack.js +830 -0
  30. package/mcp-memory-server/dist/eval-plan.js +22 -5
  31. package/mcp-memory-server/dist/eval-process.js +31 -0
  32. package/mcp-memory-server/dist/eval-report.js +1 -0
  33. package/mcp-memory-server/dist/eval-runner.js +12 -4
  34. package/mcp-memory-server/dist/eval-schema.js +2 -1
  35. package/mcp-memory-server/dist/eval-workspace.js +50 -1
  36. package/mcp-memory-server/dist/index.js +68 -17
  37. package/mcp-memory-server/dist/mcp-tool-catalog.js +47 -0
  38. package/mcp-memory-server/dist/mcp-tool-cli.js +85 -0
  39. package/mcp-memory-server/dist/mcp-tool-profile.js +162 -0
  40. package/mcp-memory-server/dist/skill-cli.js +236 -0
  41. package/mcp-memory-server/dist/skill-evaluation.js +244 -0
  42. package/mcp-memory-server/dist/skill-schema.js +164 -0
  43. package/mcp-memory-server/dist/skill-store.js +583 -0
  44. package/mcp-memory-server/package.json +4 -1
  45. package/package.json +1 -1
  46. package/schemas/context-pack.schema.json +33 -0
  47. package/schemas/eval-report.schema.json +2 -1
  48. package/schemas/skill-adapter.schema.json +39 -0
  49. package/schemas/skill-proposal-protocol.schema.json +97 -0
  50. package/scripts/completion.sh +62 -5
  51. package/scripts/doctor.sh +39 -0
  52. package/scripts/test-cli-dispatch.sh +6 -0
  53. package/scripts/test-completion.sh +16 -0
  54. package/scripts/test-learning-docs.sh +13 -3
  55. package/scripts/test-package-install.sh +35 -0
  56. package/scripts/test-skill-cli.sh +30 -0
  57. package/scripts/test-uninstall.sh +21 -0
  58. package/scripts/uninstall.sh +26 -1
  59. package/templates/env.example.template +11 -0
  60. package/templates/graph-policy.md.template +3 -1
@@ -16,7 +16,10 @@ request unless the corresponding endpoint and credential are configured.
16
16
  | `domain_knowledge_sync` | Files selected by the sync configuration | `ANYTHINGLLM_BASE_URL` |
17
17
  | `route_check` | A health request, with no memory or prompt content | `CAIRN_ROUTE_ENDPOINT` |
18
18
  | `context_explore` | Repository path and query are passed to the configured local executable | `CAIRN_EXPLORE_BINARY`; any further data flow is controlled by that tool |
19
- | Optional Graphify workflow | The repository path, exact query/symbol names, and local published graph path are passed to the operator-installed `graphify` executable | Local Graphify subprocess with a minimal environment and no provider credentials; managed build uses code-only `update`, never semantic document extraction |
19
+ | Local context-pack install/list/read/substring search | None | Verified immutable files and project pointers under `CAIRN_PACK_BASE_DIR` |
20
+ | Embedding-ranked context-pack search | Enabled file chunks not already cached, plus the query | The explicitly configured embedding endpoint; failure falls back locally |
21
+ | Authenticated HTTP context-pack retrieval | MCP requests and enabled document/approved-skill content | The explicit Cairnkeep HTTP server; requires separate pack HTTP consent |
22
+ | Optional Graphify workflow | The repository path, exact query/symbol names, root `graphify-out/` work directory, and local `.planning/graphs/` publication are passed to or produced by the operator-installed `graphify` executable | Local Graphify subprocess with a minimal environment and no provider credentials; managed build uses code-only `update`, never semantic document extraction |
20
23
  | Remote HTTP memory | MCP requests and responses, including memory content | The explicitly registered Cairnkeep HTTP server |
21
24
  | Opt-in trajectory capture | None | Local `<project>/.agentfs/trajectory.db` only; no model or HTTP path exists |
22
25
  | Opt-in capability callback records | None | Payload-free final metadata in local `<project>/.agentfs/trajectory.db`; HTTP transport is always skipped |
@@ -175,6 +178,47 @@ the full committed population and note-eligible subset with paired counts and
175
178
  seeded uncertainty; they do not convert framework output into a causal,
176
179
  significance, quality, or efficiency claim.
177
180
 
181
+ ## Validated skill improvement flow
182
+
183
+ Harvest and review are local-only operations over project hindsight notes.
184
+ Before approval, no proposal subprocess is started and no evidence crosses a
185
+ process boundary. After explicit approval, the configured proposal adapter
186
+ receives exactly one bounded JSON request containing the approved candidate,
187
+ candidate digest, selected target path, current target content and digest, and
188
+ edit budget.
189
+
190
+ The proposal adapter receives an isolated private HOME and TMPDIR, a minimal
191
+ PATH and locale, and only environment variables named in its strict
192
+ `environment_allowlist`. It does not inherit the rest of the parent
193
+ environment. HOME, PATH, temporary and XDG roots, and Node runtime injection
194
+ variables are isolation-controlled and cannot be allowlisted. Its stdout must
195
+ be one strict bounded response containing status
196
+ and bounded edits; arbitrary stderr is live operator diagnostics and is not
197
+ stored. Cairnkeep records the adapter executable digest and rejects a change
198
+ during the invocation. The adapter may perform network access according to the
199
+ executable and host policy, but Cairnkeep provides no endpoint, credential, or
200
+ network default. Operators must treat approval as consent to send those exact
201
+ evidence and target fields to that adapter.
202
+
203
+ The reduced environment is not an operating-system sandbox. The explicit
204
+ adapter executable retains the invoking user's filesystem and network
205
+ authority, so operators must trust it or wrap it in a separately enforced host
206
+ sandbox.
207
+
208
+ Evaluation sends neither candidate evidence nor proposal rationales to the
209
+ harness adapter. It overlays baseline or candidate skill bytes into separate
210
+ fresh worktrees and otherwise uses the existing evaluation request contract.
211
+ Exploration and confirmation use disjoint task IDs and definitions. Confirmation
212
+ is not opened unless exploration passes, and its results cannot be supplied
213
+ back to proposal generation by this workflow. Both stages bind one evaluation
214
+ adapter executable digest and reject a change during either stage.
215
+
216
+ Apply and rollback invoke no model and make no network request. They operate on
217
+ one contained regular target and private local artifacts, require exact
218
+ digests, reject symlinks, and fail closed on concurrent target changes. See
219
+ [storage](storage.md#validated-skill-storage) for retained bytes and
220
+ [Validated skill improvement](skill-improvement.md) for the operator sequence.
221
+
178
222
  ## Compaction and artifact flows
179
223
 
180
224
  Both `CAIRN_COMPACTION_CAPTURE` and `CAIRN_ARTIFACT_STORE` are default-off and
@@ -471,3 +515,17 @@ provide tenant isolation or per-scope authorization.
471
515
  Before sharing diagnostics, remove credentials, private endpoints, database
472
516
  files, memory values, local paths, and project names. Report vulnerabilities
473
517
  through the private channel in [SECURITY.md](../SECURITY.md).
518
+
519
+ ## Context-pack trust boundary
520
+
521
+ Pack validation proves that installed bytes match the manifest and immutable
522
+ digest. It does not authenticate a publisher. Review the source, commit, license,
523
+ and content before enabling a digest; signatures are not yet supported. Local
524
+ directory installation, listing, reading, and substring search make no network
525
+ request. Git installation/update contacts only the operator-supplied source and
526
+ is never scheduled in the background.
527
+
528
+ All enabled documents carry pack and file provenance in MCP results. Skill files
529
+ are excluded until the current project approves the exact pack digest, path, and
530
+ file digest. Updating the pack invalidates that approval. Approval only makes a
531
+ skill readable; Cairnkeep never copies it into a harness or executes it.
@@ -0,0 +1,124 @@
1
+ # Validated skill improvement
2
+
3
+ `cairn skill` turns recurring project hindsight into a proposed change to one
4
+ existing skill file, then requires independent evidence before that change can
5
+ be applied. It is an operator-controlled lifecycle, not an autonomous rewrite
6
+ loop.
7
+
8
+ ## Safety contract
9
+
10
+ - Harvest reads only project hindsight notes and requires failures from at
11
+ least two distinct sessions by default.
12
+ - Evidence remains local until `review --approve` records explicit approval.
13
+ - Proposal generation invokes only the configured executable, sends one strict
14
+ bounded JSON request, exposes only allowlisted environment variables, and
15
+ accepts a bounded edit list rather than an arbitrary rewritten tree.
16
+ - Proposal and evaluation artifacts bind the adapter executable digest;
17
+ evaluation rechecks it after exploration and confirmation.
18
+ - Proposal and evaluation use candidate content without modifying the live
19
+ target.
20
+ - Evaluation requires repository-root tasks in two different committed task
21
+ sets with at least two tasks each. Task IDs and exact task definitions cannot
22
+ overlap, and both sets must bind the same immutable source revision.
23
+ - Confirmation runs only after exploration passes. Confirmation results never
24
+ return to proposal generation.
25
+ - Apply requires an eligible exact evaluation, an unchanged target, and the
26
+ full proposal digest. It creates a private backup before atomic replacement.
27
+ - Rollback refuses to overwrite a target changed after application.
28
+
29
+ ## Prepare a target and proposal adapter
30
+
31
+ The target is one existing project-relative regular file named `SKILL.md`, for example
32
+ `.claude/skills/generated-client-review/SKILL.md`. It cannot be a symlink and
33
+ is limited to 256 KiB.
34
+
35
+ The proposal adapter configuration is strict JSON:
36
+
37
+ ```json
38
+ {
39
+ "schema_version": 1,
40
+ "id": "local-skill-proposer",
41
+ "command": {
42
+ "program": "/absolute/path/to/proposal-adapter",
43
+ "args": []
44
+ },
45
+ "environment_allowlist": ["PROPOSAL_PROVIDER_TOKEN"],
46
+ "limits": {
47
+ "elapsed_ms": 120000,
48
+ "stdout_bytes": 1048576
49
+ }
50
+ }
51
+ ```
52
+
53
+ The executable receives the approved candidate, its digest, current target
54
+ content and digest, and edit budget on stdin. It returns `add`, `replace`, or
55
+ `delete` edits with exact anchors and rationales. The executable path must be
56
+ absolute, executable, and not a symlink. Cairnkeep does not provide a model,
57
+ endpoint, credential, or network default. The machine-readable contracts are
58
+ [`schemas/skill-adapter.schema.json`](../schemas/skill-adapter.schema.json) and
59
+ [`schemas/skill-proposal-protocol.schema.json`](../schemas/skill-proposal-protocol.schema.json).
60
+ Process-isolation variables such as `HOME`, `PATH`, temporary directories,
61
+ XDG roots, and Node runtime injection settings cannot be allowlisted.
62
+ This environment boundary is not an operating-system sandbox: the configured
63
+ program runs with the invoking user's filesystem and network authority. Use
64
+ only a trusted adapter or add a separate host sandbox.
65
+
66
+ ## Run the lifecycle
67
+
68
+ ```bash
69
+ # 1. Find recurring hindsight evidence. This performs no model call.
70
+ cairn skill harvest --project . --json
71
+ cairn skill show --kind candidate --id CANDIDATE_ID
72
+
73
+ # 2. Approve the exact evidence that may cross the adapter boundary.
74
+ cairn skill review --candidate CANDIDATE_ID --approve --json
75
+
76
+ # 3. Generate a bounded proposal. The live target remains unchanged.
77
+ cairn skill propose --candidate CANDIDATE_ID \
78
+ --target .claude/skills/generated-client-review/SKILL.md \
79
+ --adapter ./private/proposal-adapter.json --edit-budget 4 --json
80
+
81
+ # 4. Measure baseline versus candidate in fresh isolated worktrees.
82
+ export CAIRN_EVAL=1
83
+ cairn skill evaluate --proposal PROPOSAL_ID \
84
+ --exploration-task-set eval/skill-exploration.json \
85
+ --confirmation-task-set eval/skill-confirmation.json \
86
+ --adapter eval/harness-adapter.json \
87
+ --repetitions 2 --minimum-improvement 1 --yes --json
88
+
89
+ # 5. Inspect artifacts and apply only the exact eligible proposal.
90
+ cairn skill show --kind proposal --id PROPOSAL_ID
91
+ cairn skill show --kind evaluation --id EVALUATION_ID
92
+ cairn skill apply --proposal PROPOSAL_ID --evaluation EVALUATION_ID \
93
+ --confirm FULL_PROPOSAL_DIGEST --json
94
+
95
+ # 6. Revert if later evidence warrants it.
96
+ cairn skill rollback --application APPLICATION_ID --confirm --json
97
+ ```
98
+
99
+ `propose --json` includes `proposal_digest`, which is the value required by
100
+ `apply --confirm`. `list` shows every artifact class; use `--kind candidate`,
101
+ `proposal`, `evaluation`, or `application` to narrow it.
102
+
103
+ ## Evaluation semantics
104
+
105
+ Each task runs twice in each arm. The baseline arm overlays the exact unchanged
106
+ target content; the treatment arm overlays the exact candidate content. Every
107
+ observation must complete, return the expected capability digest, and receive a
108
+ known result from the independent verifier. A task with any candidate-only pass
109
+ counts once as improved; a task with any baseline-only pass counts once as
110
+ regressed and rejects the candidate.
111
+
112
+ Exploration must have no unknown pairs, no regression, and at least
113
+ `--minimum-improvement` improvements. Otherwise evaluation stops without
114
+ opening the confirmation set. Confirmation must independently satisfy the same
115
+ gate. This is deliberately strict and suited to small, high-value task sets;
116
+ it is not a statistical proof that a skill is universally better.
117
+
118
+ ## What this does not do
119
+
120
+ It does not scrape native harness transcripts, schedule periodic self-edits,
121
+ invent task sets, approve evidence, choose an inference provider, or publish a
122
+ changed skill. These remain explicit operator responsibilities. Start with a
123
+ small target and representative tasks; expand only when the stored evaluation
124
+ artifacts make the decision auditable.
package/docs/storage.md CHANGED
@@ -21,6 +21,9 @@ registration, memory remains on that computer.
21
21
  | Opt-in session trajectories and capability callback state | `<harness project root>/.agentfs/trajectory.db` |
22
22
  | Opt-in local artifacts and compaction revisions | `<project root>/.agentfs/artifacts.db` |
23
23
  | Opt-in evaluation reports and note snapshots | `<project root>/.agentfs/eval/experiments/<experiment-id>/` |
24
+ | Immutable context-pack objects and source records | `${CAIRN_PACK_BASE_DIR:-~/.cairnkeep/packs}/objects/` and `sources/` |
25
+ | Digest-pinned context-pack project pointers and approvals | `${CAIRN_PACK_BASE_DIR:-~/.cairnkeep/packs}/projects/` |
26
+ | Optional context-pack embedding cache | `${CAIRN_PACK_BASE_DIR:-~/.cairnkeep/packs}/cache/` (outside immutable objects) |
24
27
  | Opt-in derived hindsight notes | `${CAIRN_AGENTFS_BASE_DIR:-~/.cairnkeep}/notes/` |
25
28
  | Any named/global scope, such as `identity` or `work` | `${CAIRN_AGENTFS_BASE_DIR:-~/.cairnkeep}/<scope>.db` |
26
29
 
@@ -213,6 +216,31 @@ the process stores databases below `/data`, normally backed by the
213
216
  removing the volume removes the databases. See [Containers](containers.md) for
214
217
  the exact paths and backup boundary.
215
218
 
219
+ ## Validated skill storage
220
+
221
+ The skill-improvement lifecycle stores private JSON artifacts below
222
+ `<project>/.agentfs/skills/`: `candidates/`, `proposals/`, `evaluations/`, and
223
+ `applications/`. Pre-application target backups live in `backups/`; isolated
224
+ proposal-adapter homes are created temporarily in `adapter-tmp/` and removed
225
+ after every invocation. Directories are mode `0700`, JSON and backup files are
226
+ mode `0600`, artifacts are bounded to 2 MiB, and candidate target content is
227
+ bounded to 256 KiB.
228
+
229
+ Candidate and proposal artifacts can contain hindsight excerpts and full skill
230
+ content, so the entire directory is sensitive. Evaluation reports and
231
+ experiment-owned snapshots remain in `.agentfs/eval/experiments/` under the
232
+ existing evaluation retention rules. No skill artifact is written to global or
233
+ remote memory, and remote MCP configuration does not redirect this project
234
+ state.
235
+
236
+ `apply` writes the original target to a unique private backup before atomically
237
+ replacing the target. If replacement, digest verification, or ledger storage
238
+ fails, Cairnkeep restores the original and removes the incomplete backup.
239
+ `rollback` restores only when both the current target and backup match the
240
+ application ledger, preventing an unrelated later edit from being overwritten.
241
+ Default uninstall retains `.agentfs/`; explicit purge uses the existing
242
+ backup-first project boundary.
243
+
216
244
  ## Remote HTTP mode
217
245
 
218
246
  Remote storage is explicit. An operator starts Cairnkeep in HTTP mode on a
@@ -468,3 +496,17 @@ bearer_token_env_var = "CAIRN_MEMORY_HTTP_TOKEN"
468
496
  "X-Cairn-Scopes" = "identity,personal,project"
469
497
  "X-Cairn-AnythingLLM-Workspaces" = "engineering-patterns"
470
498
  ```
499
+
500
+ ## Context-pack storage
501
+
502
+ Context-pack objects are content-addressed, immutable directories. Installation
503
+ copies verified content through a temporary directory and atomically publishes
504
+ the digest; a failed or concurrent install never changes project enablement.
505
+ Project pointers are separate atomic mode-`0600` files keyed by canonical local
506
+ project identity or a validated remote project ID. Updates retain old objects
507
+ and switch only the named project after exact-digest confirmation.
508
+
509
+ `cairn doctor` verifies objects, pointers, and interrupted-install remnants.
510
+ It never chooses a pack version or grants a skill approval. `cairn pack remove`
511
+ refuses any referenced digest. Ordinary uninstall retains this directory even
512
+ when memory is removed; `--purge-packs` is the separate backup-first consent.
@@ -1 +1 @@
1
- {"identifier":"cairn-offline-fake-v1","package_version":"2.7.0","schema_version":1,"task_set_digest":"13e95379f35839593047ec34521a65a8fa3ab92bd3993bca9dc8bf5c4a9ff36f"}
1
+ {"identifier":"cairn-offline-fake-v1","package_version":"2.9.0","schema_version":1,"task_set_digest":"13e95379f35839593047ec34521a65a8fa3ab92bd3993bca9dc8bf5c4a9ff36f"}
@@ -0,0 +1,185 @@
1
+ import { existsSync } from "node:fs";
2
+ import { rm } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { applyContextPackUpdate, approvePackSkill, contextPackBaseDirectory, disableContextPack, doctorContextPacks, enableContextPack, initializeContextPack, inspectContextPackUpdate, installContextPack, listInstalledContextPacks, listPackSkills, lockContextPack, readProjectPointer, removeContextPack, resolveInstalledPack, revokePackSkill, validateContextPack, } from "./context-pack.js";
5
+ function usage() {
6
+ process.stderr.write(`Usage:
7
+ cairn pack init [DIRECTORY] [--id ID] [--version VERSION] [--title TITLE] [--description TEXT] [--license LICENSE]
8
+ cairn pack lock|validate [DIRECTORY]
9
+ cairn pack install SOURCE [--ref REF]
10
+ cairn pack list [--json]
11
+ cairn pack show SELECTOR [--json]
12
+ cairn pack remove SELECTOR
13
+ cairn pack enable|disable SELECTOR [--project PATH | --project-id ID]
14
+ cairn pack update SELECTOR --check [--project PATH | --project-id ID]
15
+ cairn pack update SELECTOR --apply --confirm DIGEST [--project PATH | --project-id ID]
16
+ cairn pack skills [--project PATH | --project-id ID] [--json]
17
+ cairn pack approve-skill SELECTOR PATH --confirm FILE_DIGEST [--project PATH | --project-id ID]
18
+ cairn pack revoke-skill SELECTOR PATH [--project PATH | --project-id ID]
19
+ `);
20
+ process.exit(2);
21
+ }
22
+ function take(args, name) {
23
+ const index = args.indexOf(name);
24
+ if (index < 0)
25
+ return undefined;
26
+ const value = args[index + 1];
27
+ if (!value || value.startsWith("--"))
28
+ usage();
29
+ args.splice(index, 2);
30
+ return value;
31
+ }
32
+ function flag(args, name) {
33
+ const index = args.indexOf(name);
34
+ if (index < 0)
35
+ return false;
36
+ args.splice(index, 1);
37
+ return true;
38
+ }
39
+ function projectOptions(args) {
40
+ const projectRoot = take(args, "--project");
41
+ const projectId = take(args, "--project-id");
42
+ if (projectRoot && projectId)
43
+ usage();
44
+ return projectId ? { projectId } : { projectRoot: projectRoot ?? process.cwd() };
45
+ }
46
+ function output(value, json, human) {
47
+ process.stdout.write(`${json ? JSON.stringify(value) : human ?? JSON.stringify(value, null, 2)}\n`);
48
+ }
49
+ async function main() {
50
+ const args = process.argv.slice(2);
51
+ const command = args.shift();
52
+ const json = flag(args, "--json");
53
+ if (command === "init") {
54
+ const directory = args[0] && !args[0].startsWith("--") ? args.shift() : process.cwd();
55
+ const manifest = await initializeContextPack(directory, {
56
+ id: take(args, "--id"), version: take(args, "--version"), title: take(args, "--title"),
57
+ description: take(args, "--description"), license: take(args, "--license"),
58
+ });
59
+ if (args.length)
60
+ usage();
61
+ output(manifest, json, `Initialized ${resolve(directory)}/context-pack.json`);
62
+ return;
63
+ }
64
+ if (command === "lock" || command === "validate") {
65
+ const directory = args.shift() ?? process.cwd();
66
+ if (args.length)
67
+ usage();
68
+ const result = command === "lock" ? await lockContextPack(directory) : await validateContextPack(directory);
69
+ output(result, json, command === "lock" ? `Locked ${resolve(directory)}/context-pack.json` : `Valid context pack: ${result.digest}`);
70
+ return;
71
+ }
72
+ if (command === "install") {
73
+ const source = args.shift();
74
+ if (!source)
75
+ usage();
76
+ const ref = take(args, "--ref");
77
+ if (args.length)
78
+ usage();
79
+ const result = await installContextPack(source, { ref });
80
+ output({ id: result.pack.manifest.id, version: result.pack.manifest.version, digest: result.pack.digest, source: result.source, existing: result.existing }, json, `${result.existing ? "Already installed" : "Installed"} ${result.pack.manifest.id}@${result.pack.manifest.version} ${result.pack.digest}`);
81
+ return;
82
+ }
83
+ if (command === "list") {
84
+ if (args.length)
85
+ usage();
86
+ const packs = await listInstalledContextPacks();
87
+ output({ schema_version: 1, packs }, json, packs.length ? packs.map((pack) => `${pack.id}@${pack.version}\t${pack.digest}`).join("\n") : "No context packs installed.");
88
+ return;
89
+ }
90
+ if (command === "show") {
91
+ const selector = args.shift();
92
+ if (!selector || args.length)
93
+ usage();
94
+ const pack = await resolveInstalledPack(selector);
95
+ output({ ...pack.manifest, digest: pack.digest, total_bytes: pack.total_bytes }, json);
96
+ return;
97
+ }
98
+ if (command === "remove") {
99
+ const selector = args.shift();
100
+ if (!selector || args.length)
101
+ usage();
102
+ const digest = await removeContextPack(selector);
103
+ output({ removed: true, digest }, json, `Removed context pack ${digest}.`);
104
+ return;
105
+ }
106
+ if (command === "enable" || command === "disable") {
107
+ const selector = args.shift();
108
+ if (!selector)
109
+ usage();
110
+ const project = projectOptions(args);
111
+ if (args.length)
112
+ usage();
113
+ const pointer = command === "enable" ? await enableContextPack(selector, project) : await disableContextPack(selector, project);
114
+ output(pointer, json, `${command === "enable" ? "Enabled" : "Disabled"} context pack ${selector}.`);
115
+ return;
116
+ }
117
+ if (command === "update") {
118
+ const selector = args.shift();
119
+ if (!selector)
120
+ usage();
121
+ const check = flag(args, "--check");
122
+ const apply = flag(args, "--apply");
123
+ const confirm = take(args, "--confirm");
124
+ const project = projectOptions(args);
125
+ if (args.length || check === apply || (apply && !confirm) || (check && confirm))
126
+ usage();
127
+ const result = check ? await inspectContextPackUpdate(selector, project) : await applyContextPackUpdate(selector, confirm, project);
128
+ output(result, json, check ? `${result.changed ? "Update available" : "Up to date"}: ${result.candidate_digest}` : `Applied context pack update ${result.candidate_digest}.`);
129
+ return;
130
+ }
131
+ if (command === "skills") {
132
+ const project = projectOptions(args);
133
+ if (args.length)
134
+ usage();
135
+ const skills = await listPackSkills(project);
136
+ output({ schema_version: 1, skills }, json, skills.length ? skills.map((row) => `${row.approved ? "approved" : "unapproved"}\t${row.pack_id}\t${row.path}\t${row.file_digest}`).join("\n") : "No context pack skills.");
137
+ return;
138
+ }
139
+ if (command === "approve-skill" || command === "revoke-skill") {
140
+ const selector = args.shift();
141
+ const path = args.shift();
142
+ if (!selector || !path)
143
+ usage();
144
+ const confirm = take(args, "--confirm");
145
+ const project = projectOptions(args);
146
+ if (args.length || (command === "approve-skill" && !confirm) || (command === "revoke-skill" && confirm))
147
+ usage();
148
+ const pointer = command === "approve-skill"
149
+ ? await approvePackSkill(selector, path, confirm, project)
150
+ : await revokePackSkill(selector, path, project);
151
+ output(pointer, json, `${command === "approve-skill" ? "Approved" : "Revoked"} ${path}.`);
152
+ return;
153
+ }
154
+ if (command === "doctor") {
155
+ if (args.length)
156
+ usage();
157
+ const result = await doctorContextPacks();
158
+ output(result, json, result.ok ? `Context packs healthy (${result.objects} objects, ${result.projects} projects).` : `Context pack problems: ${[...result.issues, ...result.temporary_remnants].join("; ")}`);
159
+ if (!result.ok)
160
+ process.exitCode = 1;
161
+ return;
162
+ }
163
+ if (command === "purge") {
164
+ if (!flag(args, "--yes") || args.length)
165
+ usage();
166
+ const base = contextPackBaseDirectory();
167
+ if (existsSync(base))
168
+ await rm(base, { recursive: true, force: true });
169
+ output({ purged: true, path: base }, json, `Purged context pack data at ${base}.`);
170
+ return;
171
+ }
172
+ if (command === "status") {
173
+ const project = projectOptions(args);
174
+ if (args.length)
175
+ usage();
176
+ const pointer = readProjectPointer(project);
177
+ output(pointer, json);
178
+ return;
179
+ }
180
+ usage();
181
+ }
182
+ main().catch((error) => {
183
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
184
+ process.exitCode = 1;
185
+ });