cairn-ai 0.2.5 → 0.2.7

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 (69) hide show
  1. package/.agents/workflows/cairn-memory.md +2 -2
  2. package/.agents/workflows/cairn-plan.md +7 -3
  3. package/.agents/workflows/cairn-review.md +2 -2
  4. package/.agents/workflows/cairn-work.md +7 -3
  5. package/.claude/agents/explorer.md +2 -2
  6. package/.claude/agents/worker.md +2 -2
  7. package/.claude/commands/cairn-memory.md +1 -1
  8. package/.claude/commands/cairn-plan.md +1 -1
  9. package/.claude/commands/cairn-review.md +1 -1
  10. package/.claude/commands/cairn-work.md +1 -1
  11. package/.codex-plugin/plugin.json +4 -4
  12. package/README.de.md +3 -2
  13. package/README.es.md +3 -2
  14. package/README.fr.md +3 -2
  15. package/README.ja.md +3 -2
  16. package/README.ko.md +8 -5
  17. package/README.md +10 -7
  18. package/README.pt.md +3 -2
  19. package/README.zh.md +3 -2
  20. package/agents/explorer.md +2 -2
  21. package/agents/worker.md +6 -2
  22. package/commands/cairn-install.md +2 -1
  23. package/commands/cairn-memory.md +2 -2
  24. package/commands/cairn-plan.md +8 -4
  25. package/commands/cairn-review.md +2 -2
  26. package/commands/cairn-uninstall.md +1 -1
  27. package/commands/cairn-work.md +7 -3
  28. package/docs/model-guidance/README.md +16 -4
  29. package/docs/model-guidance/claude.md +2 -0
  30. package/docs/model-guidance/codex.md +4 -2
  31. package/package.json +5 -3
  32. package/scripts/cairn-cleanup.mjs +67 -0
  33. package/scripts/cairn-goal-cli.mjs +233 -0
  34. package/scripts/cairn-goal-evidence.mjs +179 -0
  35. package/scripts/cairn-goal-operations-evidence.mjs +124 -0
  36. package/scripts/cairn-goal-operations-mutations.mjs +63 -0
  37. package/scripts/cairn-goal-operations.mjs +177 -0
  38. package/scripts/cairn-goal-policy.mjs +33 -0
  39. package/scripts/cairn-goal-state-validation.mjs +199 -0
  40. package/scripts/cairn-goal-state.mjs +89 -0
  41. package/scripts/cairn-goal.mjs +34 -961
  42. package/scripts/cairn-lifecycle-commands.mjs +200 -0
  43. package/scripts/cairn-lifecycle-config.mjs +149 -0
  44. package/scripts/cairn-lifecycle-integrity.mjs +218 -0
  45. package/scripts/cairn-lifecycle-legacy.mjs +142 -0
  46. package/scripts/cairn-lifecycle-lock.mjs +205 -0
  47. package/scripts/cairn-lifecycle-mirror.mjs +91 -0
  48. package/scripts/cairn-lifecycle-staging.mjs +152 -0
  49. package/scripts/cairn-lifecycle-transaction-journal.mjs +210 -0
  50. package/scripts/cairn-lifecycle-transaction-targets.mjs +119 -0
  51. package/scripts/cairn-lifecycle-transaction.mjs +12 -0
  52. package/scripts/cairn-lifecycle.mjs +103 -1185
  53. package/scripts/cairn-paths.mjs +82 -6
  54. package/scripts/cairn-safe-fs.mjs +61 -35
  55. package/scripts/cairn-state-context.mjs +148 -0
  56. package/scripts/cairn-state-hook.mjs +24 -0
  57. package/scripts/cairn-state-init.mjs +137 -0
  58. package/scripts/cairn-state.mjs +15 -269
  59. package/scripts/cairn-toolcheck-detect.mjs +229 -0
  60. package/scripts/cairn-toolcheck-runtime.mjs +178 -0
  61. package/scripts/cairn-toolcheck.mjs +79 -424
  62. package/scripts/cairn.mjs +49 -27
  63. package/skills/cairn-memory/SKILL.md +4 -4
  64. package/skills/cairn-plan/SKILL.md +29 -10
  65. package/skills/cairn-review/SKILL.md +5 -5
  66. package/skills/cairn-work/SKILL.md +35 -22
  67. package/templates/PLAN.md +3 -1
  68. package/templates/work-plan.md +21 -0
  69. package/scripts/cairn-state.sh +0 -167
@@ -2,13 +2,13 @@
2
2
 
3
3
  Use the `cairn-review` skill.
4
4
 
5
- Prerequisite: resolve Cairn from its installed runtime locator. If `cairn doctor` fails, restore it with the published/global lifecycle command. Never look for Cairn runtime files in the target repository.
5
+ Prerequisite: resolve Cairn from its installed runtime locator. If the shared runtime or locator is missing, unreadable, or inconsistent, stop every dependent mirror; run published/global `cairn doctor`, then `cairn upgrade` to recover it. Never repair through another mirror or look for Cairn runtime files in the target repository.
6
6
 
7
7
  Goal: review completed work against plan, memory, policy, and evidence.
8
8
 
9
9
  Procedure:
10
10
 
11
- 1. Every agent must read the project-root `MEMORY.md` for domain knowledge and repository policy before review work, then read the plan task and memory inputs.
11
+ 1. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before review work, then reads the plan task and available memory inputs.
12
12
  2. Check changed files, tool readiness evidence, and verification evidence.
13
13
  3. Delegate independent read-only review to `explorer` when it materially improves speed or quality; use `worker` only for scoped verification reruns or QA artifacts. When subagent tools are available, each agent may recursively delegate bounded sub-tasks to subagents. Tell every delegated agent and child subagent to read the project-root `MEMORY.md` before work, keep scope, and preserve others' edits.
14
14
  4. Report findings first, ordered by severity, with file and line references. If no issues are found, say so clearly and mention remaining test gaps or residual risk.
@@ -19,4 +19,4 @@ Managed items:
19
19
 
20
20
  After the transaction commits, Cairn removes empty managed scaffold directories bottom-up and removes the custom marketplace root only after releasing its own lifecycle lock. A directory containing any unmanaged child is preserved; pruning never uses recursive deletion.
21
21
 
22
- The source work folder and repository `MEMORY.md`, `PLAN.md`, `docs/`, and `.cairn/state.json` are not deleted. The globally installed `cairn-ai` package, package-manager download caches, legacy backup files, and legacy shared settings not recorded by the current ownership manifest are also outside this command's scope and require a separate, explicit cleanup decision.
22
+ The source work folder, repository `MEMORY.md`, `PLAN.md`, `docs/`, and user-home Cairn goal state are not deleted. The globally installed `cairn-ai` package, package-manager download caches, legacy backup files, and legacy shared settings not recorded by the current ownership manifest are also outside this command's scope and require a separate, explicit cleanup decision.
@@ -2,13 +2,17 @@
2
2
 
3
3
  Use the `cairn-work` skill.
4
4
 
5
- Prerequisite: resolve the installed Cairn runtime from the plugin or skill locator. If `cairn doctor` fails, restore it with the published/global lifecycle command. Never look for Cairn scripts or model guidance in the target repository.
5
+ Prerequisite: resolve the installed Cairn runtime from the plugin or skill locator. If the shared runtime or locator is missing, unreadable, or inconsistent, stop every dependent mirror; run published/global `cairn doctor`, then `cairn upgrade` to recover it. Never repair through another mirror or look for Cairn scripts or model guidance in the target repository.
6
6
 
7
7
  Goal: execute the active goal's current task without bypassing complexity triage, then keep advancing tasks until goal-level final review completes or the goal is explicitly paused, blocked, or cancelled.
8
8
 
9
+ Require three complexity records: the provisional request checkpoint, post-exploration planning checkpoint, and code checkpoint after exact file/caller/test inspection immediately before the first edit. Before editing, evidence may change either route. Every change must synchronize the plan artifact, repository goal task roadmap through `goal replan`, and native UI plan, including reviews and required evidence. After editing begins, a new Heavy Path signal promotes Light Path to Heavy Path: stop further edits, mark affected evidence stale, synchronize all three roadmaps, and repeat the code checkpoint.
10
+
11
+ Models always inherit. Route reasoning effort per task: Light planning/implementation/verification=`medium`; Heavy planning/review/implementation=`high`; final verification/review=`xhigh`. Requested is Cairn's desired profile; Effective records the dispatch result. Effective=`inherited` means no task override was confirmed, not that the host is incapable. Claim host capability only from its exposed interface or dispatch response, and distinguish Cairn non-integration from host non-support. Only a new task/worker may receive requested effort through a supported host-native option; never change model/global config. Route changes synchronize the plan artifact, repository goal task roadmap, native UI plan, and reasoning effort profile; preserve completed profiles and recalculate incomplete profiles.
12
+
9
13
  Procedure:
10
14
 
11
- 1. Every agent must read the project-root `MEMORY.md` for domain knowledge and repository policy before selecting or executing a task.
15
+ 1. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before selecting or executing a task.
12
16
  2. Read `PLAN.md`, the detailed plan, and relevant memory notes.
13
17
  3. Resolve and read the `cairn://` Codex or Claude model guidance recorded in the plan through the installed runtime.
14
18
  4. Read Cairn goal status and select only its current task. If implementation was requested but no goal exists, create it from the decision-complete plan.
@@ -19,7 +23,7 @@ Procedure:
19
23
  9. Give implementation the test contract, failing evidence, exact file ownership, and constraints. Require minimal implementation and no repeated broad discovery. Preserve status, final-report, close-and-review, recursive bounded delegation, and main-agent fallback rules.
20
24
  10. Treat tool exit codes and bounded summaries as authoritative. Keep success output concise; expand only the failing test and related code context.
21
25
  11. For Heavy Path, run the recorded automated test command and record explicit `Tests:` evidence.
22
- 12. Run focused module acceptance, then one final surface integration check through `goal verify -- ...`. Verification defaults to 600,000 ms (10 minutes), may be shortened with `--timeout-ms`, and is capped at 3,600,000 ms (1 hour). The command runs without holding the state lock; evidence is recorded only if the starting goal/task identity and the pre/post watched fingerprint still match.
26
+ 12. Run focused module acceptance, then one final surface integration check through `goal verify -- ...`. Verification defaults to 600,000 ms (10 minutes), may be shortened with `--timeout-ms`, and is capped at 3,600,000 ms (1 hour). The command runs without holding the state lock; evidence is recorded only if the starting goal/task identity and the pre/post watched fingerprint still match. The default Git fingerprint excludes ignored files, so explicitly `--watch` every ignored artifact needed by the claim.
23
27
  13. Before package verification, inspect package lifecycle scripts. Run normal `npm pack --dry-run` by default. Content-producing or unknown lifecycle scripts must never use `--ignore-scripts`; only absent or proven content-neutral scripts may use it when the prior full check remains fresh.
24
28
  14. Record evidence at the semantic boundary that proves the claimed behavior, bound to goal/task/plan. Missing, failed, skipped, stale, or placeholder evidence never completes work; relevant mutation invalidates affected evidence.
25
29
  15. Complete the task and continue the goal. Use `--quiet` on successful state mutations; workers report only their assigned task and never select the next one.
@@ -7,18 +7,20 @@ The goal is not to make prompts long by model. The goal is to stabilize the same
7
7
  ## Application Order
8
8
 
9
9
  1. Identify the active model name. If the model name is not directly exposed, infer it from the user's default model setting and agent role frontmatter.
10
- 2. Resolve this installed guidance root from the active skill's `references/cairn-runtime.json` or the source skill's location. Never look for Cairn model guidance in the target repository.
10
+ 2. Resolve this installed guidance root from the active skill's `references/cairn-runtime.json` or the source skill's location. Never look for Cairn model guidance in the target repository. If the shared runtime or locator is missing, unreadable, or inconsistent, stop using every dependent mirror; run published/global `cairn doctor`, then `cairn upgrade` for recovery, rather than repairing through another mirror.
11
11
  3. For Claude-family models, apply `cairn://docs/model-guidance/claude.md`.
12
12
  4. For Codex-family models, apply `cairn://docs/model-guidance/codex.md`.
13
13
  5. If the model is not clearly classified, apply only the common rules and skip model-specific adjustment.
14
14
 
15
15
  ## Common Rules
16
16
 
17
- - On every fresh entry, compaction recovery, restart, delegation, or handoff, restore context in this order: root `MEMORY.md` → current phase skill → active plan → current-task references → model guidance recorded by the plan. If persisted state or an assignment requires a reference that is missing, unreadable, or inconsistent, fail closed: do not edit, delegate, approve, or complete; report or record a blocker.
17
+ - On every fresh entry, compaction recovery, restart, delegation, or handoff, restore context in this order: optional root `MEMORY.md` → current phase skill → active plan → current-task references → model guidance recorded by the plan. If `MEMORY.md` is absent, continue without repository memory and do not invoke another memory service. If persisted state or an assignment requires another reference that is missing, unreadable, or inconsistent, fail closed: do not edit, delegate, approve, or complete; report or record a blocker.
18
18
  - Keep root `MEMORY.md` and `PLAN.md` as indexes only.
19
19
  - Keep repository-specific judgment in `docs/memory/` and `docs/plan/`. Keep Cairn's model guidance in the installed plugin and reference it with `cairn://docs/model-guidance/...`.
20
+ - Treat the user-home `.cairn/projects/<project-id>/worktrees/<worktree-id>/state.json` as a worktree-bound active-work slot. State without `worktreeId` and state copied into another worktree are stale and are removed on the next hook/goal start. Successful completion or cancellation removes the owning copy after plan evidence is recorded; only paused or blocked work remains for resumption.
20
21
  - Preserve proper nouns, file names, variable names, service names, alert names, MCP tool names, and agent names exactly as written.
21
22
  - Select Light Path or Heavy Path before implementation.
23
+ - Treat complexity as three checkpoints: a provisional request checkpoint, a post-exploration planning checkpoint, and a code checkpoint after exact file/caller/test inspection immediately before the first edit. Before editing, evidence may change either route. Every change must synchronize the plan artifact, repository goal task roadmap through `goal replan`, and native UI plan, including reviews and required evidence. After editing begins, a new Heavy signal promotes Light Path to Heavy Path: stop further edits, mark affected evidence stale, synchronize all three roadmaps, and repeat the code checkpoint.
22
24
  - Treat the user-called/main agent as the orchestrator: it plans, assigns, verifies, and records evidence.
23
25
  - Delegate actual implementation edits to `worker` subagents whenever subagent tools are available, regardless of Light Path or Heavy Path.
24
26
  - When the subagent tool provides a progress-reporting channel, require subagents to report status to the orchestrator when starting work, when deciding or confirming direction, during periodic progress, and when finishing; the orchestrator must immediately relay received status events to the user. If no mid-run reporting channel exists, the orchestrator relays observable events such as assignment, waiting, and final completion.
@@ -26,11 +28,11 @@ The goal is not to make prompts long by model. The goal is to stabilize the same
26
28
  - If subagent tools are unavailable, the main agent takes over implementation directly and records that takeover in evidence.
27
29
  - Detect required LSP, typecheck, lint, dry-run, and verification tools before implementation.
28
30
  - Missing required tools must be reported with a proposed install. Installation requires explicit user approval and a pinned/supported installer.
29
- - Treat every implementation or continued-execution request as authorization to bind active work to a persisted Cairn goal and stable task IDs, even when the user does not mention a goal. After MEMORY.md, first write an initial plan that makes triage the active task. Before exploration or triage, synchronize its roadmap to Codex `update_plan` and `create_goal` when available and to the repository Cairn goal. Update both plans after triage, and implement only from the decision-complete revision. Exclude consultation, explanation, and plan-only requests.
31
+ - Treat non-trivial implementation or continuation of planned work as authorization to bind active work to a persisted Cairn goal and stable task IDs, even when the user does not mention a goal. Routine known-target Git/GitHub operations (status, fetch, checkout, branch, merge, push, PR management) stay plan/goal-free unless they require code edits, conflict resolution, destructive recovery, release/deploy, or design. For planned work, after MEMORY.md, first write an initial plan that makes triage the active task, synchronize it before exploration, and implement only from the decision-complete revision. Exclude consultation, explanation, and plan-only requests.
30
32
  - Treat missing, failed, skipped, stale, or placeholder evidence as failure. Task evidence records must be bound to the current goal, task, and plan.
31
33
  - Treat the repository Cairn state as the fail-closed transition authority. Advance the matching Codex UI plan step only after the evidence-gated repository task transition succeeds.
32
34
  - Spend reasoning on a focused executable test contract before implementation: requirements, invariants, boundaries, and failure modes. Give implementation only that contract, failing evidence, file scope, and constraints; require the minimum passing change.
33
- - Run verification through `goal verify -- ...`; its default timeout is 600,000 ms and its maximum is 3,600,000 ms. Treat tool exit codes and bounded machine summaries as authoritative. Record evidence only when the starting goal/task identity and pre/post watched fingerprint still match, and choose the watch set and command at the semantic boundary that proves the claim. Compress success output and expand context only for failing tests and related code.
35
+ - Run verification through `goal verify -- ...`; its default timeout is 600,000 ms and its maximum is 3,600,000 ms. Treat tool exit codes and bounded machine summaries as authoritative. Record evidence only when the starting goal/task identity and pre/post watched fingerprint still match, and choose the watch set and command at the semantic boundary that proves the claim. The default Git fingerprint excludes ignored files; explicitly pass every ignored artifact needed by the claim with `--watch`. Compress success output and expand context only for failing tests and related code.
34
36
  - Use a verification ladder: focused contract tests, one full check after the final change, then package dry-run while the full check remains fresh. Inspect package lifecycle scripts first and run normal `npm pack --dry-run` by default. Content-producing or unknown lifecycle scripts must never use `--ignore-scripts`; only absent or proven content-neutral scripts may use it.
35
37
  - Every implementation task must pass module acceptance verification and surface integration verification.
36
38
  - Before mutating external state, run the closest available dry-run, check, plan, diff, validate, or equivalent command.
@@ -46,6 +48,16 @@ The goal is not to make prompts long by model. The goal is to stabilize the same
46
48
  - Character budgets are regression proxies for cache-friendly prompt shape, not measurements of provider cache hits or cost. Cairn does not require provider cache keys, breakpoints, or live API telemetry.
47
49
  - Do not add read receipts that claim model attention. Restore references on every re-entry and use readable state plus fresh bound evidence as the enforceable contract.
48
50
 
51
+ ## Reasoning Effort Routing
52
+
53
+ - Models always inherit the host or user default; Cairn never selects or overrides a model.
54
+ - Light Path planning, implementation, and verification request `medium`.
55
+ - Heavy Path planning, review, and implementation request `high`; final verification and review request `xhigh`.
56
+ - Every plan task records `Requested reasoning effort` and `Effective reasoning effort`. Requested is Cairn's desired profile; Effective is the value confirmed by the dispatch result.
57
+ - Only a newly dispatched task or worker may receive the requested value, and only through a host-exposed reasoning-effort option or host-native equivalent. Omit every model override.
58
+ - For an unsupported host or value, use `Effective reasoning effort: inherited`; this means no task-level override was confirmed and does not prove the host lacks the capability. Describe host capability only from an exposed interface or dispatch result, and distinguish Cairn's choice not to integrate or use an option from host non-support. Do not change the model or global config and do not silently choose a nearby value.
59
+ - A route change synchronizes the plan artifact, repository goal task roadmap through `goal replan`, native UI plan, and reasoning effort profile before edits resume. Preserve completed task profiles as audit history and recalculate incomplete task profiles for the new path.
60
+
49
61
  ## Delegation Defaults
50
62
 
51
63
  - `explorer`: use for read-only codebase discovery, impact analysis, pattern searches, and read-only verification when available.
@@ -18,7 +18,9 @@ Claude-family models are strong at long-context retention, policy interpretation
18
18
  - Do not skip the two verification gates even on Light Path.
19
19
  - Do not approve a plan that lacks dry-run or check evidence for external-state-changing work.
20
20
  - Do not allow open-ended verification loops. Require a blocker or sub-task split after two failed passes.
21
+ - Keep the model inherited. `Requested reasoning effort` is Cairn's desired profile; `Effective reasoning effort` is what the dispatch response confirms. Apply the requested value only to a newly dispatched task or worker through a host-native task/subagent option, never a model override. For an unsupported host or value, record Effective `inherited`; this means no task override was confirmed, not proof of missing host capability. Infer capability only from the exposed interface or dispatch response, distinguish Cairn non-integration from host non-support, and make no model/global configuration change.
21
22
  - On Heavy Path, check plan gaps before implementation.
23
+ - Review all three complexity records: the provisional request checkpoint, post-exploration planning checkpoint, and code checkpoint after exact file/caller/test inspection immediately before the first edit. Before editing, evidence may change either route. Every change must synchronize the plan artifact, repository goal task roadmap through `goal replan`, and native UI plan, including reviews and required evidence. After editing begins, a new Heavy signal promotes Light Path to Heavy Path: stop further edits, mark affected evidence stale, synchronize all three roadmaps, and repeat the code checkpoint.
22
24
  - Before asking the user, use repository evidence and available `explorer`/`worker` delegation when it materially improves speed or quality.
23
25
  - Write user-visible responses and generated or updated documentation, plans, and memory artifacts in the OS locale unless the user asks for another language.
24
26
 
@@ -17,8 +17,10 @@ Codex-family models are strong at small implementation tasks, explicit tool use,
17
17
 
18
18
  ## Adjustment Rules
19
19
 
20
- - On every fresh entry, compaction recovery, restart, delegation, or handoff, restore root `MEMORY.md` → current phase skill → active plan → current-task references → recorded model guidance. If persisted state or the assignment requires a missing, unreadable, or inconsistent reference, fail closed and report a blocker before editing, delegating, approving, or completing.
20
+ - On every fresh entry, compaction recovery, restart, delegation, or handoff, restore optional root `MEMORY.md` → current phase skill → active plan → current-task references → recorded model guidance. Missing `MEMORY.md` never blocks and must not trigger another memory service. If persisted state or the assignment requires another missing, unreadable, or inconsistent reference, fail closed and report a blocker before editing, delegating, approving, or completing.
21
21
  - Do not start implementation without a plan.
22
+ - Keep the model inherited. `Requested reasoning effort` is Cairn's desired profile; `Effective reasoning effort` is what the dispatch response confirms. Only a newly dispatched task or worker may receive the requested effort through the host's task/subagent option, with no model override. For an unsupported host or value, record Effective `inherited`; this means no task override was confirmed, not proof of missing host capability. Infer capability only from the exposed interface or dispatch response, distinguish Cairn non-integration from host non-support, and leave model/global configuration unchanged.
23
+ - Do not treat complexity as a one-time choice. Require the provisional request checkpoint, post-exploration planning checkpoint, and code checkpoint after exact file/caller/test inspection immediately before the first edit. Before editing, evidence may change either route. Every change must synchronize the plan artifact, repository goal task roadmap through `goal replan`, and native UI plan, including reviews and required evidence. After editing begins, a new Heavy signal promotes Light Path to Heavy Path: stop further edits, mark affected evidence stale, synchronize all three roadmaps, and repeat the code checkpoint.
22
24
  - Do not stop before local implementation solely because subagent tools are unavailable; the main agent takes over implementation directly and records that takeover in evidence.
23
25
  - Keep work units small. Split large changes into smaller plan tasks.
24
26
  - If `apply_patch` is unstable in the environment, use an editing tool or a clear file update path.
@@ -44,7 +46,7 @@ Codex-family models are strong at small implementation tasks, explicit tool use,
44
46
  ### Implementation
45
47
 
46
48
  - Invest Codex reasoning in executable test design first, then hand the bounded failing contract to implementation and require the minimum passing patch.
47
- - Run verification through `goal verify -- ...`. Its default timeout is 600,000 ms (10 minutes), capped at 3,600,000 ms (1 hour). On success, retain exact argv, tool exit code, pass count, output digest, and watched-workspace fingerprint; commit evidence only when the starting goal/task identity and pre/post watched fingerprint match. Choose the watch set and command at the semantic boundary that proves the claim. Expand diagnostics only for the failing test and its related file/symbol scope; reject stale evidence after related changes.
49
+ - Run verification through `goal verify -- ...`. Its default timeout is 600,000 ms (10 minutes), capped at 3,600,000 ms (1 hour). On success, retain exact argv, tool exit code, pass count, output digest, and watched-workspace fingerprint; commit evidence only when the starting goal/task identity and pre/post watched fingerprint match. Choose the watch set and command at the semantic boundary that proves the claim. The default Git fingerprint excludes ignored files; explicitly `--watch` every ignored artifact needed by the claim. Expand diagnostics only for the failing test and its related file/symbol scope; reject stale evidence after related changes.
48
50
  - If acting as the user-called/main agent, orchestrate the work and delegate actual implementation edits to `worker` whenever subagent tools are available, regardless of Light Path or Heavy Path.
49
51
  - If acting as the user-called/main agent, immediately relay received subagent status events to the user. If no mid-run reporting channel exists, relay observable events such as assignment, waiting, and final completion.
50
52
  - If acting as the user-called/main agent, close or release completed subagents after capturing their final report and evidence, then review the final report and evidence before marking the work complete.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn-ai",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Token-efficient agent harness with tool readiness for Codex, Claude Code, and Antigravity",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -32,14 +32,16 @@
32
32
  "commands/",
33
33
  "docs/model-guidance/",
34
34
  "hooks/",
35
- "scripts/",
35
+ "scripts/cairn",
36
+ "scripts/*.mjs",
37
+ "scripts/release-integrity-*.json",
36
38
  "skills/",
37
39
  "templates/",
38
40
  "README.md",
39
41
  "README.*.md"
40
42
  ],
41
43
  "scripts": {
42
- "check": "node --check scripts/cairn.mjs && node --check scripts/cairn-paths.mjs && node --check scripts/cairn-safe-fs.mjs && node --check scripts/cairn-goal.mjs && node --check scripts/cairn-state.mjs && node --check scripts/cairn-lifecycle.mjs && node --check scripts/cairn-toolcheck.mjs && node -e \"JSON.parse(require('node:fs').readFileSync('package.json','utf8')); JSON.parse(require('node:fs').readFileSync('.codex-plugin/plugin.json','utf8')); JSON.parse(require('node:fs').readFileSync('hooks/hooks.json','utf8')); JSON.parse(require('node:fs').readFileSync('scripts/release-integrity-0.2.2.json','utf8'))\" && npm test",
44
+ "check": "node --check scripts/cairn.mjs && node --check scripts/cairn-paths.mjs && node --check scripts/cairn-safe-fs.mjs && node --check scripts/cairn-goal.mjs && node --check scripts/cairn-state.mjs && node --check scripts/cairn-cleanup.mjs && node --check scripts/cairn-lifecycle.mjs && node --check scripts/cairn-toolcheck.mjs && node -e \"JSON.parse(require('node:fs').readFileSync('package.json','utf8')); JSON.parse(require('node:fs').readFileSync('.codex-plugin/plugin.json','utf8')); JSON.parse(require('node:fs').readFileSync('hooks/hooks.json','utf8')); JSON.parse(require('node:fs').readFileSync('scripts/release-integrity-0.2.2.json','utf8'))\" && npm test",
43
45
  "test": "node --test",
44
46
  "prepack": "npm run check"
45
47
  },
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import { lstat, readdir, rm, rmdir } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { reconcileWorktreeState } from "./cairn-goal.mjs";
6
+ import { parseRootArgs } from "./cairn-paths.mjs";
7
+
8
+ const staleLockName = /^state\.lock\.stale\..+\.[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
9
+
10
+ export async function cleanupProjectCairn({ root = process.cwd(), apply = false } = {}) {
11
+ const repoRoot = resolve(root);
12
+ const legacyRoot = join(repoRoot, ".cairn");
13
+ let rootStat;
14
+ try {
15
+ rootStat = await lstat(legacyRoot);
16
+ } catch (error) {
17
+ if (error?.code === "ENOENT") return { root: repoRoot, apply, known: [], unknown: [], removed: [], projectDirectoryRemoved: false };
18
+ throw error;
19
+ }
20
+ if (rootStat.isSymbolicLink()) throw new Error(`Legacy Cairn root must not be a symbolic link: ${legacyRoot}`);
21
+ if (!rootStat.isDirectory()) throw new Error(`Legacy Cairn root is not a directory: ${legacyRoot}`);
22
+
23
+ const entries = await readdir(legacyRoot, { withFileTypes: true });
24
+ const known = [];
25
+ const unknown = [];
26
+ for (const entry of entries) {
27
+ const isKnownFile = entry.isFile() && (entry.name === "state.json" || entry.name === "state.lock" || staleLockName.test(entry.name));
28
+ const isKnownDirectory = entry.isDirectory() && entry.name === "tools";
29
+ (isKnownFile || isKnownDirectory ? known : unknown).push(entry.name);
30
+ }
31
+ if (!apply) return { root: repoRoot, apply, known: known.sort(), unknown: unknown.sort(), removed: [], projectDirectoryRemoved: false };
32
+
33
+ if (known.includes("state.json")) await reconcileWorktreeState({ root: repoRoot });
34
+ const removed = [];
35
+ for (const name of known) {
36
+ const path = join(legacyRoot, name);
37
+ try {
38
+ const stat = await lstat(path);
39
+ const expectedType = name === "tools" ? stat.isDirectory() : stat.isFile();
40
+ if (!expectedType) continue;
41
+ await rm(path, { recursive: name === "tools", force: true });
42
+ removed.push(name);
43
+ } catch (error) {
44
+ if (error?.code !== "ENOENT") throw error;
45
+ }
46
+ }
47
+ let projectDirectoryRemoved = false;
48
+ try {
49
+ await rmdir(legacyRoot);
50
+ projectDirectoryRemoved = true;
51
+ } catch (error) {
52
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error?.code)) throw error;
53
+ }
54
+ return { root: repoRoot, apply, known: known.sort(), unknown: unknown.sort(), removed: removed.sort(), projectDirectoryRemoved };
55
+ }
56
+
57
+ async function main() {
58
+ const { args, repoRoot } = parseRootArgs(process.argv.slice(2));
59
+ const apply = args.includes("--yes");
60
+ const unsupported = args.filter((value) => value !== "--yes" && value !== "--check");
61
+ if (unsupported.length > 0) throw new Error(`Unknown cleanup option: ${unsupported[0]}`);
62
+ console.log(JSON.stringify(await cleanupProjectCairn({ root: repoRoot, apply }), null, 2));
63
+ }
64
+
65
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
66
+ await main();
67
+ }
@@ -0,0 +1,233 @@
1
+ export async function runGoalCli(args, {
2
+ stdout = console.log,
3
+ defaultGoalEvidence,
4
+ operations,
5
+ }) {
6
+ const [firstCommand = "help", ...remaining] = args;
7
+ const [command = "help", ...rest] = firstCommand === "goal" ? remaining : [firstCommand, ...remaining];
8
+ const { options, positional, passthrough } = parseArgs(rest, command);
9
+ const root = options.root ?? process.cwd();
10
+ const emit = options.quiet ? () => {} : stdout;
11
+ if (command === "start") {
12
+ const tasks = parseTasks(options.tasks ?? positional.slice(0));
13
+ const state = await operations.startGoal({
14
+ root,
15
+ goal: options.goal,
16
+ planId: options.plan,
17
+ tasks,
18
+ completionCriteria: splitValues(options.criteria),
19
+ requiredEvidence: options.requiredEvidence ? splitValues(options.requiredEvidence) : defaultGoalEvidence,
20
+ ownerSessionId: options.session,
21
+ evidencePolicy: options.evidencePolicy ?? options["evidence-policy"] ?? "tool-bound",
22
+ });
23
+ emit(JSON.stringify(state));
24
+ return state;
25
+ }
26
+ if (command === "status") {
27
+ const state = await operations.readGoalState({ root });
28
+ stdout(JSON.stringify(state));
29
+ return state;
30
+ }
31
+ if (command === "task") {
32
+ if (positional[0] === "status" && options.status === undefined) {
33
+ const state = await operations.readGoalState({ root });
34
+ if (!state) {
35
+ stdout(JSON.stringify(null));
36
+ return null;
37
+ }
38
+ const requestedTaskId = options.task ?? positional[1];
39
+ const task = requestedTaskId === undefined
40
+ ? state.tasks.find((candidate) => candidate.status === "active") ?? null
41
+ : state.tasks.find((candidate) => candidate.id === requestedTaskId);
42
+ if (requestedTaskId !== undefined && task === undefined) throw new Error(`Unknown task: ${requestedTaskId}`);
43
+ stdout(JSON.stringify(task));
44
+ return task;
45
+ }
46
+ const taskAction = taskStatusFromAction(positional[0]);
47
+ const state = await operations.setTaskStatus({
48
+ root,
49
+ taskId: options.task ?? (taskAction ? positional[1] : positional[0]),
50
+ status: options.status ?? taskAction ?? positional[1],
51
+ blocker: options.reason,
52
+ });
53
+ emit(JSON.stringify(state));
54
+ return state;
55
+ }
56
+ if (command === "replan") {
57
+ const state = await operations.replanGoal({ root, tasks: parseTasks(options.tasks ?? positional.slice(0)) });
58
+ emit(JSON.stringify(state));
59
+ return state;
60
+ }
61
+ if (command === "assign") {
62
+ const state = await operations.assignTask({
63
+ root,
64
+ taskId: options.task ?? positional[0],
65
+ agentId: options.agent ?? positional[1] ?? null,
66
+ });
67
+ emit(JSON.stringify(state));
68
+ return state;
69
+ }
70
+ if (command === "receipt") {
71
+ const state = await operations.recordReceipt({
72
+ root,
73
+ taskId: options.task ?? positional[0],
74
+ kind: options.kind,
75
+ scope: options.scope,
76
+ command: options.command,
77
+ exitCode: numberOption(options.exitCode ?? options["exit-code"] ?? options.exit),
78
+ timestamp: options.timestamp,
79
+ goalId: options.goalId ?? options["goal-id"],
80
+ planId: options.plan,
81
+ });
82
+ emit(JSON.stringify(state));
83
+ return state;
84
+ }
85
+ if (command === "verify") {
86
+ const result = await operations.verifyAndRecord({
87
+ root,
88
+ taskId: options.task ?? positional[0],
89
+ kind: options.kind,
90
+ scope: options.scope,
91
+ watchPaths: splitValues(options.watch),
92
+ argv: passthrough,
93
+ timeoutMs: options["timeout-ms"] === undefined
94
+ ? 600_000
95
+ : positiveIntegerOption(options["timeout-ms"], "timeout-ms"),
96
+ });
97
+ emit(JSON.stringify({
98
+ id: result.evidence.id,
99
+ scope: result.evidence.scope,
100
+ kind: result.evidence.kind,
101
+ exitCode: result.evidence.exitCode,
102
+ outputDigest: result.evidence.outputDigest,
103
+ workspaceFingerprint: result.evidence.workspaceFingerprint,
104
+ }));
105
+ return result.state;
106
+ }
107
+ if (["pause", "resume", "block", "cancel", "complete"].includes(command)) {
108
+ const state = await operations.transitionGoal({
109
+ root,
110
+ status: { pause: "paused", resume: "active", block: "blocked", cancel: "cancelled", complete: "completed" }[command],
111
+ blocker: options.reason,
112
+ });
113
+ emit(JSON.stringify(state));
114
+ return state;
115
+ }
116
+ throw new Error("Usage: cairn-goal start|status|task|replan|assign|receipt|verify|pause|resume|block|cancel|complete [--root PATH]");
117
+ }
118
+
119
+ function parseArgs(args, command) {
120
+ const options = {};
121
+ const positional = [];
122
+ const passthrough = [];
123
+ const booleanOptions = new Set(["quiet"]);
124
+ const allowed = allowedOptions(command);
125
+ const seenCanonicalOptions = new Set();
126
+ for (let index = 0; index < args.length; index += 1) {
127
+ const value = args[index];
128
+ if (value === "--") {
129
+ passthrough.push(...args.slice(index + 1));
130
+ break;
131
+ }
132
+ if (!value.startsWith("--")) {
133
+ positional.push(value);
134
+ continue;
135
+ }
136
+ const option = value.slice(2);
137
+ const equalsIndex = option.indexOf("=");
138
+ const key = equalsIndex === -1 ? option : option.slice(0, equalsIndex);
139
+ const inline = equalsIndex === -1 ? undefined : option.slice(equalsIndex + 1);
140
+ if (!allowed.has(key)) throw new Error(`Unknown option for ${command}: --${key}`);
141
+ const canonicalKey = canonicalOptionKey(key);
142
+ if (seenCanonicalOptions.has(canonicalKey)) throw new Error(`--${canonicalKey} may only be provided once`);
143
+ seenCanonicalOptions.add(canonicalKey);
144
+ if (inline !== undefined) {
145
+ if (!booleanOptions.has(key) && inline.length === 0) throw new Error(`--${key} requires a value`);
146
+ if (booleanOptions.has(key) && !["true", "false"].includes(inline)) throw new Error(`--${key} must be true or false`);
147
+ options[key] = booleanOptions.has(key) ? inline !== "false" : inline;
148
+ } else if (booleanOptions.has(key)) {
149
+ options[key] = true;
150
+ } else {
151
+ const next = args[index + 1];
152
+ if (next === undefined || next === "--" || next.startsWith("--")) throw new Error(`--${key} requires a value`);
153
+ options[key] = next;
154
+ index += 1;
155
+ }
156
+ }
157
+ return { options, positional, passthrough };
158
+ }
159
+
160
+ function canonicalOptionKey(key) {
161
+ return {
162
+ exitCode: "exit-code",
163
+ exit: "exit-code",
164
+ goalId: "goal-id",
165
+ evidencePolicy: "evidence-policy",
166
+ }[key] ?? key;
167
+ }
168
+
169
+ function allowedOptions(command) {
170
+ const common = ["root", "quiet"];
171
+ const byCommand = {
172
+ start: ["goal", "plan", "tasks", "criteria", "requiredEvidence", "session", "evidencePolicy", "evidence-policy"],
173
+ status: [],
174
+ task: ["task", "status", "reason"],
175
+ replan: ["tasks"],
176
+ assign: ["task", "agent"],
177
+ receipt: ["task", "kind", "scope", "command", "exitCode", "exit-code", "exit", "timestamp", "goalId", "goal-id", "plan"],
178
+ verify: ["task", "kind", "scope", "watch", "timeout-ms"],
179
+ pause: [],
180
+ resume: [],
181
+ block: ["reason"],
182
+ cancel: [],
183
+ complete: [],
184
+ help: [],
185
+ };
186
+ return new Set([...common, ...(byCommand[command] ?? [])]);
187
+ }
188
+
189
+ function parseTasks(value) {
190
+ if (Array.isArray(value)) return value;
191
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error("Provide tasks with --tasks JSON or positional task titles");
192
+ try {
193
+ const parsed = JSON.parse(value);
194
+ if (!Array.isArray(parsed)) throw new Error("--tasks JSON must be an array");
195
+ return parsed;
196
+ } catch (error) {
197
+ if (error instanceof SyntaxError) return value.split("|").map((item) => item.trim()).filter(Boolean);
198
+ throw error;
199
+ }
200
+ }
201
+
202
+ function splitValues(value) {
203
+ if (!value) return [];
204
+ return String(value).split("|").map((item) => item.trim()).filter(Boolean);
205
+ }
206
+
207
+ function numberOption(value) {
208
+ if (value === undefined) return undefined;
209
+ const result = Number(value);
210
+ if (!Number.isInteger(result)) throw new Error("exitCode must be an integer");
211
+ return result;
212
+ }
213
+
214
+ function positiveIntegerOption(value, label) {
215
+ const result = Number(value);
216
+ if (!Number.isSafeInteger(result) || result <= 0 || result > 3_600_000) {
217
+ throw new Error(`${label} must be an integer between 1 and 3600000`);
218
+ }
219
+ return result;
220
+ }
221
+
222
+ function taskStatusFromAction(value) {
223
+ return {
224
+ start: "active",
225
+ activate: "active",
226
+ active: "active",
227
+ complete: "completed",
228
+ completed: "completed",
229
+ block: "blocked",
230
+ blocked: "blocked",
231
+ pending: "pending",
232
+ }[value] ?? null;
233
+ }