cairn-ai 0.2.7 → 0.2.9
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/.agents/workflows/cairn-memory.md +1 -1
- package/.agents/workflows/cairn-plan.md +1 -1
- package/.agents/workflows/cairn-review.md +1 -1
- package/.agents/workflows/cairn-work.md +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/README.ko.md +13 -4
- package/README.md +13 -4
- package/agents/worker.md +2 -0
- package/commands/cairn-install.md +1 -0
- package/commands/cairn-memory.md +1 -1
- package/commands/cairn-plan.md +1 -1
- package/commands/cairn-review.md +1 -1
- package/commands/cairn-work.md +2 -2
- package/docs/model-guidance/README.md +1 -1
- package/docs/model-guidance/codex.md +1 -1
- package/package.json +1 -1
- package/scripts/cairn-goal-cli.mjs +7 -1
- package/scripts/cairn-goal-evidence.mjs +8 -1
- package/scripts/cairn-goal-operations.mjs +11 -1
- package/scripts/cairn-goal-policy.mjs +4 -1
- package/scripts/cairn-goal-state.mjs +35 -0
- package/scripts/cairn-goal.mjs +3 -2
- package/scripts/cairn-lifecycle-claude.mjs +142 -0
- package/scripts/cairn-lifecycle-commands.mjs +41 -3
- package/scripts/cairn-lifecycle-config.mjs +7 -3
- package/scripts/cairn-lifecycle-integrity.mjs +15 -2
- package/scripts/cairn-lifecycle-staging.mjs +9 -2
- package/scripts/cairn-lifecycle.mjs +5 -2
- package/scripts/cairn-state-context.mjs +20 -12
- package/scripts/cairn-state-hook.mjs +8 -6
- package/scripts/cairn-state.mjs +30 -4
- package/skills/cairn-memory/SKILL.md +2 -1
- package/skills/cairn-plan/SKILL.md +4 -1
- package/skills/cairn-review/SKILL.md +3 -3
- package/skills/cairn-work/SKILL.md +19 -3
|
@@ -12,6 +12,6 @@ Procedure:
|
|
|
12
12
|
2. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before assigned discovery or update work.
|
|
13
13
|
3. Explore repository facts with focused tools.
|
|
14
14
|
4. Use `explorer` for read-only domain discovery and contradiction checks when available; use `worker` for exact references, command checks, and bounded evidence capture. 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.
|
|
15
|
-
5. Write details to `docs/memory/<domain>.md`.
|
|
15
|
+
5. Write details to `docs/memory/<domain>.md`. When a lesson or correction recurs and is machine-checkable, encode it as a test, lint rule, script, or hook instead of more prose; memory keeps judgment rules and a pointer to the enforcing check.
|
|
16
16
|
6. Keep `MEMORY.md` as a short index.
|
|
17
17
|
7. Use the OS locale for user-visible responses and generated or updated documentation, plans, and memory artifacts unless the user asks for another language.
|
|
@@ -14,7 +14,7 @@ Procedure:
|
|
|
14
14
|
|
|
15
15
|
1. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before repository exploration, tool selection, work, or delegation.
|
|
16
16
|
2. Keep routine known-target Git/GitHub operations plan/goal-free unless they require code edits, conflict resolution, destructive recovery, release/deploy, or design. For non-trivial implementation or continuation of planned work, write the initial repository plan and `PLAN.md` entry from the request and `MEMORY.md`. Include a planned `triage-plan` task and anticipated execution/verification stages, then start or attach the repository Cairn goal before exploration.
|
|
17
|
-
3. When the surface exposes native plan/goal UI tools, synchronize the same roadmap there before exploration; otherwise record that those tools are unavailable.
|
|
17
|
+
3. When the surface exposes native plan/goal UI tools, synchronize the same roadmap there before exploration; otherwise record that those tools are unavailable. If an unfinished repository goal already exists when a new implementation request, steer, or drift arrives, do not cancel it to start another. Fold the new work into it with `goal replan` (completed tasks and their evidence are preserved; resume a paused or blocked goal first), synchronize the plan artifact and native UI roadmap, and keep advancing until goal final review. Start a separate goal only when the user explicitly switches work. When a hook reports that another session owns the goal and this session continues that same work, take ownership with `goal claim --session <id>` using the ID shown by the hook.
|
|
18
18
|
4. Read relevant `docs/memory/*.md` and perform toolcheck, exploration, and Light/Heavy Path triage according to the initial task.
|
|
19
19
|
5. Update the same plan to a decision-complete implementation revision before implementation.
|
|
20
20
|
6. If the active or assigned model is Claude-family, read `claude.md` under the locator's `resources.modelGuidance`; if Codex-family, read `codex.md` there.
|
|
@@ -9,7 +9,7 @@ Goal: review completed work against plan, memory, policy, and evidence.
|
|
|
9
9
|
Procedure:
|
|
10
10
|
|
|
11
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
|
-
2. Check changed files, tool readiness evidence, and verification evidence.
|
|
12
|
+
2. Check changed files, tool readiness evidence, and verification evidence. Inspect test, fixture, and verification-command diffs and reject any weakening without a recorded plan reason. For defect fixes, confirm the same-rule sweep was performed and its result recorded.
|
|
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.
|
|
15
15
|
5. Record review evidence in `docs/plan/<topic>.md`.
|
|
@@ -15,7 +15,7 @@ Procedure:
|
|
|
15
15
|
1. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before selecting or executing a task.
|
|
16
16
|
2. Read `PLAN.md`, the detailed plan, and relevant memory notes.
|
|
17
17
|
3. Read the Codex or Claude model guidance recorded in the plan.
|
|
18
|
-
4. Select one small module task.
|
|
18
|
+
4. Select one small module task. Read it with `goal task status` (full `goal status` only when receipts are needed). Fold a new request or steer that arrives during an unfinished goal into it with `goal replan` instead of cancelling it, and keep advancing to goal final review.
|
|
19
19
|
5. Confirm required tool readiness by invoking Node directly with the locator's absolute `entrypoints.cli`, `toolcheck`, and `--root` for the target repository. Follow the installed toolcheck policy before any explicitly approved installation attempt.
|
|
20
20
|
6. Confirm the complexity triage and selected Light/Heavy Path recorded in the plan. If missing, update the plan before mutating files.
|
|
21
21
|
7. Spend reasoning on a focused executable test contract before implementation: requirements, invariants, boundaries, and failure modes. Confirm the intended failure, then require only the minimum implementation that passes.
|
|
@@ -23,7 +23,7 @@ Procedure:
|
|
|
23
23
|
9. For Heavy Path, follow the plan's staged implementation and review gates without role-mapping shortcuts.
|
|
24
24
|
10. Give implementation only the test contract, failing evidence, exact file ownership, and constraints. Delegate actual implementation edits to `worker` on both paths whenever subagent tools are available. 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; immediately relay received status events to the user. If no mid-run reporting channel exists, relay observable events such as assignment, waiting, and final completion. Require delegated subagents to provide a final report before leaving; after capturing the final report and evidence, close or release the completed subagent, then review the final report and evidence before marking the work complete. 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. If subagent tools are unavailable, the main agent takes over implementation directly and records that takeover in evidence.
|
|
25
25
|
11. Treat tool exit codes and bounded machine summaries as authoritative. Keep success concise; expand context only around failing tests.
|
|
26
|
-
12. Run focused tests, then one final full repository check through `goal verify -- ...`. Verification defaults to 600,000 ms (10 minutes), is capped at 3,600,000 ms (1 hour), and records evidence only when the starting goal/task identity and pre/post watched fingerprint still match. Select a semantic boundary that proves the claim. The default Git fingerprint excludes ignored files; explicitly `--watch` every ignored artifact needed by the claim. Any relevant later mutation makes affected evidence stale.
|
|
26
|
+
12. Run focused tests, then one final full repository check through `goal verify -- ...`. Verification defaults to 600,000 ms (10 minutes), is capped at 3,600,000 ms (1 hour), and records evidence only when the starting goal/task identity and pre/post watched fingerprint still match. Select a semantic boundary that proves the claim. The default Git fingerprint excludes ignored files; explicitly `--watch` every ignored artifact needed by the claim. Any relevant later mutation makes affected evidence stale. Never edit, weaken, skip, or delete existing tests, fixtures, or verification commands to make a gate pass unless the plan records the contract change. Reproduce reported defects before fixing, test one hypothesis at a time with a measurement, and after a fix search for other implementations of the same rule or pattern.
|
|
27
27
|
13. Inspect package lifecycle scripts before package verification 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 while the full-check evidence remains fresh.
|
|
28
28
|
14. Record tool readiness and verification evidence in `docs/plan/<topic>.md` and update `PLAN.md`.
|
|
29
29
|
15. If the user asks a side question, status question, or narrow clarification while this task is still active, answer it briefly and then resume the previous active work unless the user explicitly asks to pause, stop, or switch tasks.
|
package/README.ko.md
CHANGED
|
@@ -58,6 +58,13 @@ cairn toolcheck --install --yes --root .
|
|
|
58
58
|
- 검증 게이트가 실패하면 Cairn은 한 번 진단하고 작업을 줄이거나 sub-task로 나눈 뒤 두 게이트를 다시 실행합니다.
|
|
59
59
|
- 같은 작업에서 두 번의 검증 패스가 실패하면 반복 루프를 계속하지 않고 `docs/plan/<topic>.md`에 blocker를 기록합니다.
|
|
60
60
|
|
|
61
|
+
## Verification Integrity
|
|
62
|
+
|
|
63
|
+
- 구현자는 판정 기준을 바꿀 수 없습니다. 기존 test, fixture, snapshot, 기록된 검증 명령은 계획에 계약 변경 사유를 먼저 기록하지 않는 한 게이트 통과를 위해 수정·약화·skip·삭제하지 않습니다. 리뷰는 이 diff를 검사하고 사유 없는 약화를 거부합니다.
|
|
64
|
+
- 보고된 결함은 수정 전에 도구로 먼저 재현합니다. 원인이 불명확한 실패는 가설을 하나씩 세우고 측정으로 닫으며, 가능 여부 질문은 검증 없는 "불가능"이 아니라 제한된 실험으로 답합니다.
|
|
65
|
+
- 결함을 고친 뒤에는 같은 규칙이나 패턴의 다른 구현을 저장소 전체에서 찾아 범위 안에서 고치거나 후속 작업으로 기록합니다.
|
|
66
|
+
- 반복되고 기계적으로 확인 가능한 교훈은 메모리 문장을 늘리는 대신 test, lint rule, script, hook으로 옮깁니다.
|
|
67
|
+
|
|
61
68
|
## Model Guidance
|
|
62
69
|
|
|
63
70
|
Cairn은 Claude-family와 Codex-family 모델에만 모델별 조정을 적용합니다.
|
|
@@ -102,13 +109,13 @@ cairn uninstall
|
|
|
102
109
|
cairn toolcheck
|
|
103
110
|
```
|
|
104
111
|
|
|
105
|
-
- `cairn install`: custom marketplace source, versioned Codex runtime cache, Cairn-owned config section, Claude Code mirror
|
|
112
|
+
- `cairn install`: custom marketplace source, versioned Codex runtime cache, Cairn-owned config section, Claude Code mirror와 플러그인, 현재 Antigravity IDE/CLI skill 표면을 transaction으로 설치합니다.
|
|
106
113
|
- `cairn upgrade`: ownership manifest의 installed digest와 현재 값이 같은 항목만 교체하며, staged validation과 역순 rollback을 적용합니다.
|
|
107
114
|
- `cairn doctor`: ownership manifest, managed digest, 유효 Codex feature, 실제 plugin installed/enabled/version, mirror/runtime locator를 수정 없이 진단합니다.
|
|
108
|
-
- `cairn uninstall`:
|
|
115
|
+
- `cairn uninstall`: 먼저 config 외 모든 ownership manifest 항목을 설치 digest와 대조하고, 수정되거나 삭제된 managed artifact가 하나라도 있으면 아무것도 바꾸지 않고 uninstall 전체를 거부합니다. 그렇지 않으면 managed 항목을 제거·복원하고 빈 managed cache scaffold를 정리하며, unmanaged target과 그 상위 디렉터리는 보존하고 commit 전 실패는 rollback합니다.
|
|
109
116
|
- `cairn toolcheck`: 저장소 스택과 필요한 LSP·검증 도구를 확인하며, 승인된 지원 installer만 실행합니다.
|
|
110
117
|
- `cairn cleanup --root <repo>`: 기본값은 legacy 프로젝트 로컬 `.cairn` 잔여물을 읽기 전용으로 보고합니다. `--yes`는 유효한 활성 상태를 사용자 홈 저장소로 조정·이관하고 알려진 상태/lock/tool 잔여물만 제거하며, 알 수 없는 파일은 보존하고 비었을 때만 디렉터리를 정리합니다.
|
|
111
|
-
- `cairn goal ...`: 저장소의 영속 goal을 시작·조회·일시정지·재개·차단·취소·완료합니다. 기본 tool-bound 정책은 `goal verify -- <argv>`가 명령을 직접 실행해 성공 증거를 기록합니다. `goal receipt`는 기존 선언 증거를 가져오는 호환 명령입니다.
|
|
118
|
+
- `cairn goal ...`: 저장소의 영속 goal을 시작·조회·일시정지·재개·차단·취소·완료합니다. `goal claim --session <id>`는 같은 작업을 이어받는 session(재개·압축·handoff·fork)에 미완료 goal의 소유권을 넘기며, 미완료 goal 중에 들어온 새 작업은 goal을 취소하지 않고 `goal replan`으로 합칩니다. 기본 tool-bound 정책은 `goal verify -- <argv>`가 명령을 직접 실행해 성공 증거를 기록합니다. `goal receipt`는 기존 선언 증거를 가져오는 호환 명령입니다.
|
|
112
119
|
- `cairn-memory`: 도메인 지식을 탐색하고 `MEMORY.md`를 갱신합니다.
|
|
113
120
|
- `cairn-plan`: `docs/plan/` 아래에 decision-complete plan을 만듭니다.
|
|
114
121
|
- `cairn-work`: 현재 `PLAN.md`의 다음 모듈 작업을 실행하고 두 검증 게이트를 확보합니다.
|
|
@@ -122,7 +129,9 @@ cairn toolcheck
|
|
|
122
129
|
|
|
123
130
|
Clean uninstall은 모든 managed scaffold가 비었을 때만 recursive 삭제 없이 빈 디렉터리 제거로 Cairn marketplace cache root를 정리합니다. 소스 저장소와 저장소의 `MEMORY.md`/`PLAN.md`, 사용자 홈 Cairn goal 상태, 전역 `cairn-ai` package, package-manager download cache, legacy backup, 현재 ownership manifest 밖의 legacy shared setting은 지우지 않으며 별도의 명시적 정리 판단이 필요합니다.
|
|
124
131
|
|
|
125
|
-
Codex는 `skills/`와 `commands/`를 사용합니다. Claude Code는
|
|
132
|
+
Codex는 `skills/`와 `commands/`를 사용합니다. Claude Code는 `~/.claude/` 아래의 mirror command·agent definition과 Claude Code 플러그인 `cairn@cairn`을 사용합니다. Antigravity는 `.agents/workflows`와 global skills mirror를 사용합니다.
|
|
133
|
+
|
|
134
|
+
Claude Code 플러그인은 Cairn이 소유한 로컬 marketplace `~/.codex/plugins/cache/cairn/claude`에 생성되며, Cairn skill 4개와 SessionStart·UserPromptSubmit·PostToolUse·Stop·SubagentStop hook을 담습니다. hook은 설치된 버전 런타임을 절대경로로 호출합니다. 파일 commit 뒤 `cairn install`/`upgrade`가 `claude plugin marketplace add`와 `claude plugin install cairn@cairn --scope user`로 등록하므로 다음 세션부터 Claude Code 터미널·데스크톱 앱·IDE 플러그인 목록에 표시됩니다. `claude` CLI가 없으면 등록을 건너뛰고 수동 명령을 안내하며, 이미 `cairn`이라는 다른 marketplace가 있으면 건드리지 않습니다. `cairn uninstall`은 파일 제거 전에 Cairn 소유 marketplace만 등록 해제하고, uninstall이 rollback되면 다시 등록합니다. `cairn doctor`는 `Claude plugin files`, `Claude plugin registered`, `Claude plugin enabled`를 보고합니다.
|
|
126
135
|
|
|
127
136
|
## Antigravity Compatibility
|
|
128
137
|
|
package/README.md
CHANGED
|
@@ -66,6 +66,13 @@ cairn toolcheck --install --yes --root .
|
|
|
66
66
|
- If a verification gate fails, Cairn diagnoses once, shrinks the task or splits it into sub-tasks, and reruns both gates.
|
|
67
67
|
- After two failed verification passes for the same task, Cairn records the blocker in `docs/plan/<topic>.md` instead of continuing a repeated loop.
|
|
68
68
|
|
|
69
|
+
## Verification Integrity
|
|
70
|
+
|
|
71
|
+
- The implementer must not change the judge: existing tests, fixtures, snapshots, and recorded verification commands are never edited, weakened, skipped, or deleted to make a gate pass unless the plan records the contract change first. Review inspects those diffs and rejects unexplained weakening.
|
|
72
|
+
- Reported defects are reproduced with tools before fixing. Unclear failures are narrowed one hypothesis at a time, each closed by a measurement, and feasibility questions are answered with a bounded experiment rather than an unverified "not possible".
|
|
73
|
+
- After a defect fix, the repository is searched for other implementations of the same rule or pattern; they are fixed in scope or recorded as follow-up.
|
|
74
|
+
- Recurring, machine-checkable lessons are encoded as tests, lint rules, scripts, or hooks instead of more memory prose.
|
|
75
|
+
|
|
69
76
|
## Model Guidance
|
|
70
77
|
|
|
71
78
|
Cairn only applies model-specific adjustment to Claude-family and Codex-family models.
|
|
@@ -110,13 +117,13 @@ cairn uninstall
|
|
|
110
117
|
cairn toolcheck
|
|
111
118
|
```
|
|
112
119
|
|
|
113
|
-
- `cairn install`: transactionally installs the custom marketplace source, versioned Codex runtime cache, Cairn-owned config sections, Claude Code mirrors, and current Antigravity IDE/CLI skill surfaces.
|
|
120
|
+
- `cairn install`: transactionally installs the custom marketplace source, versioned Codex runtime cache, Cairn-owned config sections, Claude Code mirrors and plugin, and current Antigravity IDE/CLI skill surfaces.
|
|
114
121
|
- `cairn upgrade`: replaces only ownership-manifest entries whose installed digests still match, with staged validation and reverse-order rollback on failure.
|
|
115
122
|
- `cairn doctor`: validates the ownership manifest, managed digests, effective Codex features, actual installed/enabled/version plugin state, and mirror/runtime locators without repairing them.
|
|
116
|
-
- `cairn uninstall`:
|
|
123
|
+
- `cairn uninstall`: verifies every non-config ownership-manifest entry against its installed digest first and refuses the whole uninstall, changing nothing, if any managed artifact was modified or removed. Otherwise it removes or restores the managed entries, prunes empty managed cache scaffolds, preserves unmanaged targets and their parent directories, and rolls back pre-commit failures.
|
|
117
124
|
- `cairn toolcheck`: detects repository stacks and checks required LSP and verification tools; installation runs only for an approved supported installer.
|
|
118
125
|
- `cairn cleanup --root <repo>`: reports legacy project-local `.cairn` residue without changing it; `--yes` migrates/reconciles valid active state into the user-home store, removes only known Cairn state/lock/tool residue, preserves unknown files, and prunes the directory only when empty.
|
|
119
|
-
- `cairn goal ...`: starts, inspects, pauses, resumes, blocks, cancels, and completes a persisted repository goal. State lives under user-home `.cairn`, separated by repository and Git worktree; state without `worktreeId` and state copied from another worktree are stale and removed on the next hook/goal start. Successful completion or cancellation removes active state because completed evidence already lives in the plan document, while paused/blocked state remains resumable. The default tool-bound policy records successful evidence by executing `goal verify -- <argv>`. The legacy `goal receipt` command imports declared evidence only.
|
|
126
|
+
- `cairn goal ...`: starts, inspects, pauses, resumes, blocks, cancels, and completes a persisted repository goal. `goal claim --session <id>` hands an unfinished goal to the session that continues the same work (resume, compaction, handoff, fork); new work that arrives during an unfinished goal is folded in with `goal replan` instead of cancelling it. State lives under user-home `.cairn`, separated by repository and Git worktree; state without `worktreeId` and state copied from another worktree are stale and removed on the next hook/goal start. Successful completion or cancellation removes active state because completed evidence already lives in the plan document, while paused/blocked state remains resumable. The default tool-bound policy records successful evidence by executing `goal verify -- <argv>`. The legacy `goal receipt` command imports declared evidence only.
|
|
120
127
|
- `cairn-memory`: explores domain knowledge and updates `MEMORY.md`.
|
|
121
128
|
- `cairn-plan`: creates a decision-complete plan under `docs/plan/`.
|
|
122
129
|
- `cairn-work`: executes the next module task in the current `PLAN.md` with two verification gates.
|
|
@@ -130,7 +137,9 @@ Install and upgrade keep the custom marketplace lifecycle; they do not invoke `c
|
|
|
130
137
|
|
|
131
138
|
Clean uninstall removes the Cairn marketplace cache root only when every managed scaffold is empty, using empty-directory removal rather than recursive deletion. It does not delete the source repository, repository `MEMORY.md`/`PLAN.md`, user-home Cairn goal state, the global `cairn-ai` package, package-manager download caches, legacy backups, or legacy shared settings outside the current ownership manifest; those require a separate explicit cleanup decision.
|
|
132
139
|
|
|
133
|
-
Codex uses `skills/` and `commands/`. Claude Code uses mirrored commands and agent definitions under
|
|
140
|
+
Codex uses `skills/` and `commands/`. Claude Code uses mirrored commands and agent definitions under `~/.claude/` plus a Claude Code plugin, `cairn@cairn`. Antigravity uses `.agents/workflows` and global skills mirrors.
|
|
141
|
+
|
|
142
|
+
The Claude Code plugin is generated in a Cairn-owned local marketplace at `~/.codex/plugins/cache/cairn/claude` and carries the four Cairn skills and the SessionStart, UserPromptSubmit, PostToolUse, Stop, and SubagentStop hooks, which call the installed versioned runtime by absolute path. After the files commit, `cairn install`/`upgrade` registers it through `claude plugin marketplace add` and `claude plugin install cairn@cairn --scope user`, so it appears in the Claude Code terminal, desktop app, and IDE plugin lists from the next session. Registration is skipped with a manual command when the `claude` CLI is missing, and a different marketplace already named `cairn` is never touched. `cairn uninstall` unregisters only Cairn's own marketplace before removing files, and re-registers it if the uninstall rolls back. `cairn doctor` reports `Claude plugin files`, `Claude plugin registered`, and `Claude plugin enabled`.
|
|
134
143
|
|
|
135
144
|
## Antigravity Compatibility
|
|
136
145
|
|
package/agents/worker.md
CHANGED
|
@@ -25,6 +25,8 @@ Use for:
|
|
|
25
25
|
|
|
26
26
|
Do not make architecture decisions. Do not ask the user. Do not delegate vague or unbounded work. Do not treat a missing tool as unavailable until install or bootstrap has been attempted. Report only important paths, commands, and outputs accurately.
|
|
27
27
|
|
|
28
|
+
Never edit, weaken, skip, or delete existing tests, fixtures, or verification commands to make a gate pass unless the plan records the contract change. Reproduce a reported defect before fixing it, and after the fix search for other implementations of the same rule or pattern; report them in the handoff.
|
|
29
|
+
|
|
28
30
|
Before the first edit, inspect the assigned exact files, callers, and tests and report evidence for the plan's code checkpoint. If you discover a new Heavy Path signal before or during implementation, stop further edits and report it to the orchestrator so the plan artifact, repository goal task roadmap, native UI plan, review requirements, and stale evidence can be updated. Resume only after a new code checkpoint is recorded.
|
|
29
31
|
|
|
30
32
|
Keep the model inherited. Read the task's `Requested reasoning effort` and `Effective reasoning effort`: Requested is Cairn's desired profile; Effective is what the dispatch response confirms. A newly delegated task or new worker may receive requested effort only through a host-exposed task/subagent reasoning-effort option or host-native equivalent; omit model overrides. For an unsupported host or value, report Effective `inherited`; this means no task override was confirmed, not proof that the host lacks capability. Describe capability only from the exposed interface or dispatch response, distinguish Cairn non-integration from host non-support, and do not alter model/global config.
|
|
@@ -16,6 +16,7 @@ Actions:
|
|
|
16
16
|
- Add the `hooks/hooks.json` link to the installed manifest.
|
|
17
17
|
- Register only Cairn-owned marketplace, plugin, and hook trust sections in `~/.codex/config.toml`; do not rewrite public feature or agent settings.
|
|
18
18
|
- Install `cairn-*` commands and agents mirror files for Claude Code.
|
|
19
|
+
- Generate the Claude Code plugin `cairn@cairn` (four skills plus hooks that call the installed runtime) in the Cairn-owned local marketplace, then register it with `claude plugin marketplace add` and `claude plugin install cairn@cairn --scope user` after the files commit. Skip with a manual command when the `claude` CLI is missing, and never touch a different marketplace named `cairn`.
|
|
19
20
|
- Install `cairn-*` skills and workflows mirror files for Antigravity IDE/CLI.
|
|
20
21
|
- Write runtime locators so all mirrors resolve scripts, templates, and model guidance from the installed plugin root.
|
|
21
22
|
- Record every current managed destination and digest in the ownership manifest under the custom marketplace, and rollback committed phases in reverse order on failure.
|
package/commands/cairn-memory.md
CHANGED
|
@@ -12,6 +12,6 @@ Procedure:
|
|
|
12
12
|
2. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before assigned discovery or update work.
|
|
13
13
|
3. Explore repository facts with focused tools.
|
|
14
14
|
4. Use `explorer` for read-only domain discovery and contradiction checks when available; use `worker` for exact references, command checks, and bounded evidence capture. 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.
|
|
15
|
-
5. Write details to `docs/memory/<domain>.md`.
|
|
15
|
+
5. Write details to `docs/memory/<domain>.md`. When a lesson or correction recurs and is machine-checkable, encode it as a test, lint rule, script, or hook instead of more prose; memory keeps judgment rules and a pointer to the enforcing check.
|
|
16
16
|
6. Keep `MEMORY.md` as a short index.
|
|
17
17
|
7. Use the OS locale for user-visible responses and generated or updated documentation, plans, and memory artifacts unless the user asks for another language.
|
package/commands/cairn-plan.md
CHANGED
|
@@ -14,7 +14,7 @@ Procedure:
|
|
|
14
14
|
|
|
15
15
|
1. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before repository exploration, tool selection, work, or delegation.
|
|
16
16
|
2. Classify routine known-target Git/GitHub operations separately: status, fetch, checkout, branch, merge, push, and PR management stay plan/goal-free unless they require code edits, conflict resolution, destructive recovery, release/deploy, or design. For non-trivial implementation or continuation of planned work, write an initial `docs/plan/<topic>.md` and `PLAN.md` entry from the request and `MEMORY.md`. Make `triage-plan` the first active task and specify its investigation scope, path-decision criteria, plan-update condition, and anticipated implementation/verification stages. This initial plan must be decision-complete for triage and explicitly not implementation-ready.
|
|
17
|
-
3. Before repository exploration, toolcheck, complexity triage, delegation, or a non-blocking user question for planned work, call Codex `update_plan` and then `create_goal` when those UI tools are available. Mirror the same roadmap into the repository Cairn goal. Record unavailable UI tools instead of silently implying UI synchronization. Skip goal activation for routine known-target Git/GitHub operations, consultation, explanation, and plan-only requests.
|
|
17
|
+
3. Before repository exploration, toolcheck, complexity triage, delegation, or a non-blocking user question for planned work, call Codex `update_plan` and then `create_goal` when those UI tools are available. Mirror the same roadmap into the repository Cairn goal. Record unavailable UI tools instead of silently implying UI synchronization. Skip goal activation for routine known-target Git/GitHub operations, consultation, explanation, and plan-only requests. If an unfinished repository goal already exists when a new implementation request, steer, or drift arrives, do not cancel it to start another. Fold the new work into it with `goal replan` (completed tasks and their evidence are preserved; resume a paused or blocked goal first), synchronize the plan artifact and native UI roadmap, and keep advancing until goal final review. Start a separate goal only when the user explicitly switches work. When a hook reports that another session owns the goal and this session continues that same work, take ownership with `goal claim --session <id>` using the ID shown by the hook.
|
|
18
18
|
4. Read relevant `docs/memory/*.md` as directed by the initial triage task.
|
|
19
19
|
5. Read the applicable installed model guidance.
|
|
20
20
|
6. Run the installed runtime's `toolcheck --root <repoRoot>` and handle missing tools through the explicit approval policy.
|
package/commands/cairn-review.md
CHANGED
|
@@ -9,7 +9,7 @@ Goal: review completed work against plan, memory, policy, and evidence.
|
|
|
9
9
|
Procedure:
|
|
10
10
|
|
|
11
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
|
-
2. Check changed files, tool readiness evidence, and verification evidence.
|
|
12
|
+
2. Check changed files, tool readiness evidence, and verification evidence. Inspect test, fixture, and verification-command diffs and reject any weakening without a recorded plan reason. For defect fixes, confirm the same-rule sweep was performed and its result recorded.
|
|
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.
|
|
15
15
|
5. Record review evidence in `docs/plan/<topic>.md` and as the goal-level final review evidence record before completing the goal.
|
package/commands/cairn-work.md
CHANGED
|
@@ -15,7 +15,7 @@ Procedure:
|
|
|
15
15
|
1. Every agent reads project-root `MEMORY.md` when present and continues without repository memory when absent before selecting or executing a task.
|
|
16
16
|
2. Read `PLAN.md`, the detailed plan, and relevant memory notes.
|
|
17
17
|
3. Resolve and read the `cairn://` Codex or Claude model guidance recorded in the plan through the installed runtime.
|
|
18
|
-
4. Read
|
|
18
|
+
4. Read the current task with `goal task status` (full `goal status` only when receipts are needed) and select only that task. Fold a new request or steer that arrives during an unfinished goal into it with `goal replan` instead of cancelling it, and keep advancing to goal final review. If implementation was requested but no goal exists, create it from the decision-complete plan.
|
|
19
19
|
5. Confirm required tool readiness. If a required LSP/check tool is missing, report it and obtain explicit user approval before `toolcheck --install --yes` or a repository-native install command.
|
|
20
20
|
6. Confirm the complexity triage and selected Light/Heavy Path recorded in the plan. If missing, update the plan before mutating files.
|
|
21
21
|
7. Before implementation, spend reasoning on a focused executable test contract covering requirements, invariants, boundaries, and failure modes. Confirm the intended failure, then constrain implementation to the minimum change that passes it.
|
|
@@ -23,7 +23,7 @@ Procedure:
|
|
|
23
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.
|
|
24
24
|
10. Treat tool exit codes and bounded summaries as authoritative. Keep success output concise; expand only the failing test and related code context.
|
|
25
25
|
11. For Heavy Path, run the recorded automated test command and record explicit `Tests:` evidence.
|
|
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.
|
|
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. Never edit, weaken, skip, or delete existing tests, fixtures, or verification commands to make a gate pass unless the plan records the contract change. Reproduce reported defects before fixing, test one hypothesis at a time with a measurement, and after a fix search for other implementations of the same rule or pattern.
|
|
27
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.
|
|
28
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.
|
|
29
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.
|
|
@@ -14,7 +14,7 @@ The goal is not to make prompts long by model. The goal is to stabilize the same
|
|
|
14
14
|
|
|
15
15
|
## Common Rules
|
|
16
16
|
|
|
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.
|
|
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. Delegation means the delegated agent restores on its own entry; the orchestrator does not re-read this chain after dispatching or collecting a subagent, and within one unbroken context it rereads only references that changed, which keeps the cached prompt prefix stable.
|
|
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
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.
|
|
@@ -17,7 +17,7 @@ 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 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.
|
|
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. Delegation means the delegated agent restores on its own entry; the orchestrator does not re-read this chain after dispatching or collecting a subagent, and within one unbroken context it rereads only references that changed, which keeps the cached prompt prefix stable.
|
|
21
21
|
- Do not start implementation without a plan.
|
|
22
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
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.
|
package/package.json
CHANGED
|
@@ -58,6 +58,11 @@ export async function runGoalCli(args, {
|
|
|
58
58
|
emit(JSON.stringify(state));
|
|
59
59
|
return state;
|
|
60
60
|
}
|
|
61
|
+
if (command === "claim") {
|
|
62
|
+
const state = await operations.claimGoal({ root, sessionId: options.session });
|
|
63
|
+
emit(JSON.stringify(state));
|
|
64
|
+
return state;
|
|
65
|
+
}
|
|
61
66
|
if (command === "assign") {
|
|
62
67
|
const state = await operations.assignTask({
|
|
63
68
|
root,
|
|
@@ -113,7 +118,7 @@ export async function runGoalCli(args, {
|
|
|
113
118
|
emit(JSON.stringify(state));
|
|
114
119
|
return state;
|
|
115
120
|
}
|
|
116
|
-
throw new Error("Usage: cairn-goal start|status|task|replan|assign|receipt|verify|pause|resume|block|cancel|complete [--root PATH]");
|
|
121
|
+
throw new Error("Usage: cairn-goal start|status|task|replan|claim|assign|receipt|verify|pause|resume|block|cancel|complete [--root PATH]");
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
function parseArgs(args, command) {
|
|
@@ -173,6 +178,7 @@ function allowedOptions(command) {
|
|
|
173
178
|
status: [],
|
|
174
179
|
task: ["task", "status", "reason"],
|
|
175
180
|
replan: ["tasks"],
|
|
181
|
+
claim: ["session"],
|
|
176
182
|
assign: ["task", "agent"],
|
|
177
183
|
receipt: ["task", "kind", "scope", "command", "exitCode", "exit-code", "exit", "timestamp", "goalId", "goal-id", "plan"],
|
|
178
184
|
verify: ["task", "kind", "scope", "watch", "timeout-ms"],
|
|
@@ -82,7 +82,14 @@ function listGitWorkspacePaths(root) {
|
|
|
82
82
|
maxBuffer: 16 * 1024 * 1024,
|
|
83
83
|
});
|
|
84
84
|
if (result.status !== 0 || result.error) return null;
|
|
85
|
-
return result.stdout.split("\0").filter(Boolean)
|
|
85
|
+
return result.stdout.split("\0").filter(Boolean)
|
|
86
|
+
.filter((path) => !isInternalStatePath(path) && !isCairnRecordPath(path));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Cairn records evidence in these artifacts after verification; including them in the default fingerprint would
|
|
90
|
+
// make every recorded receipt stale. Pass --watch explicitly when a claim is about these files.
|
|
91
|
+
function isCairnRecordPath(path) {
|
|
92
|
+
return path === "PLAN.md" || path === "MEMORY.md" || path.startsWith("docs/plan/") || path.startsWith("docs/memory/");
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
function listFilesystemPaths(root, watchPaths) {
|
|
@@ -41,7 +41,7 @@ export async function startGoal({ root = process.cwd(), goal, planId, tasks, com
|
|
|
41
41
|
existing = null;
|
|
42
42
|
}
|
|
43
43
|
if (existing && !terminalGoalStatuses.has(existing.goal.status)) {
|
|
44
|
-
throw new Error(`An
|
|
44
|
+
throw new Error(`An unfinished Cairn goal already exists (${existing.goal.id}, ${existing.goal.status}); add the new work to it with goal replan (resume it first if paused or blocked), or cancel or complete it before starting another goal`);
|
|
45
45
|
}
|
|
46
46
|
const now = timestamp();
|
|
47
47
|
const normalizedTasks = tasks.map((task, index) => normalizeTask(task, index, index === 0 ? "active" : "pending"));
|
|
@@ -163,6 +163,16 @@ export async function replanGoal({ root = process.cwd(), tasks } = {}) {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
export async function claimGoal({ root = process.cwd(), sessionId } = {}) {
|
|
167
|
+
const normalizedSessionId = requiredText(sessionId, "sessionId");
|
|
168
|
+
return mutateGoal(root, (state) => {
|
|
169
|
+
assertGoalMutable(state);
|
|
170
|
+
state.goal.ownerSessionId = optionalText(normalizedSessionId, "ownerSessionId");
|
|
171
|
+
state.goal.updatedAt = timestamp();
|
|
172
|
+
return state;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
export async function assignTask({ root = process.cwd(), taskId, agentId = null } = {}) {
|
|
167
177
|
const normalizedTaskId = requiredText(taskId, "taskId");
|
|
168
178
|
const normalizedAgentId = agentId === null ? null : requiredText(agentId, "agentId");
|
|
@@ -7,7 +7,10 @@ export function currentTask(state) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function isGoalOwnedBySession(state, sessionId) {
|
|
10
|
-
|
|
10
|
+
if (!state?.goal?.ownerSessionId) return true;
|
|
11
|
+
// A payload without a session id cannot prove another owner; treating it as foreign would silently halt the goal.
|
|
12
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return true;
|
|
13
|
+
return state.goal.ownerSessionId === sessionId;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export function evaluateStop(state, { subagent = false, agentId } = {}) {
|
|
@@ -12,9 +12,44 @@ export function goalStatePath(root = process.cwd()) {
|
|
|
12
12
|
export async function readGoalState({ root = process.cwd() } = {}) {
|
|
13
13
|
const state = await readCurrentGoalState(root);
|
|
14
14
|
if (state) return state;
|
|
15
|
+
// Read-only callers (hooks) must not create user-home state directories when there is nothing to migrate.
|
|
16
|
+
if (!(await legacyGoalStateExists(root))) return null;
|
|
15
17
|
return withGoalStateLock(root, () => readGoalStateUnlocked(root));
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
export function stopGuardPath(root = process.cwd()) {
|
|
21
|
+
return join(goalStateDirectory(root), "stop-guard.json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function readStopGuard(root = process.cwd()) {
|
|
25
|
+
try {
|
|
26
|
+
const path = stopGuardPath(root);
|
|
27
|
+
await assertNoSymlinkComponents(cairnHome(), path);
|
|
28
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
29
|
+
return value && typeof value === "object" ? value : null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function writeStopGuard(root = process.cwd(), value) {
|
|
36
|
+
try {
|
|
37
|
+
await safeWriteFile(cairnHome(), stopGuardPath(root), `${JSON.stringify(value)}\n`, "utf8");
|
|
38
|
+
} catch {
|
|
39
|
+
// The guard only prevents no-progress continuation loops; failing to persist it must not break the stop hook.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function legacyGoalStateExists(root) {
|
|
44
|
+
try {
|
|
45
|
+
await lstat(join(resolve(root), ".cairn", "state.json"));
|
|
46
|
+
return true;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") return false;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
18
53
|
export async function writeGoalState({ root = process.cwd(), state } = {}) {
|
|
19
54
|
return withGoalStateLock(root, () => writeGoalStateUnlocked({ root, state }));
|
|
20
55
|
}
|
package/scripts/cairn-goal.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import { runGoalCli } from "./cairn-goal-cli.mjs";
|
|
5
|
-
import { assignTask, replanGoal, setTaskStatus, startGoal, transitionGoal } from "./cairn-goal-operations.mjs";
|
|
5
|
+
import { assignTask, claimGoal, replanGoal, setTaskStatus, startGoal, transitionGoal } from "./cairn-goal-operations.mjs";
|
|
6
6
|
import { recordReceipt, verifyAndRecord } from "./cairn-goal-operations-evidence.mjs";
|
|
7
7
|
import { readGoalState } from "./cairn-goal-state.mjs";
|
|
8
8
|
import { defaultGoalEvidence } from "./cairn-goal-state-validation.mjs";
|
|
@@ -19,7 +19,7 @@ export {
|
|
|
19
19
|
TASK_STATUSES,
|
|
20
20
|
} from "./cairn-goal-state-validation.mjs";
|
|
21
21
|
export { goalStatePath, readGoalState, writeGoalState } from "./cairn-goal-state.mjs";
|
|
22
|
-
export { assignTask, reconcileWorktreeState, replanGoal, setTaskStatus, startGoal, transitionGoal } from "./cairn-goal-operations.mjs";
|
|
22
|
+
export { assignTask, claimGoal, reconcileWorktreeState, replanGoal, setTaskStatus, startGoal, transitionGoal } from "./cairn-goal-operations.mjs";
|
|
23
23
|
export { recordReceipt, verifyAndRecord } from "./cairn-goal-operations-evidence.mjs";
|
|
24
24
|
export { currentTask, evaluateStop, isGoalOwnedBySession } from "./cairn-goal-policy.mjs";
|
|
25
25
|
|
|
@@ -29,6 +29,7 @@ export async function handleGoalCli(args = process.argv.slice(2), { stdout = con
|
|
|
29
29
|
defaultGoalEvidence,
|
|
30
30
|
operations: {
|
|
31
31
|
assignTask,
|
|
32
|
+
claimGoal,
|
|
32
33
|
readGoalState,
|
|
33
34
|
recordReceipt,
|
|
34
35
|
replanGoal,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { quoteShellArg, renderMirrorFile, writeLocator } from "./cairn-lifecycle-mirror.mjs";
|
|
5
|
+
|
|
6
|
+
// Claude Code loads a plugin's default hooks/hooks.json and merges any manifest hooks, and it only expands
|
|
7
|
+
// ${CLAUDE_PLUGIN_ROOT}. The shared package hooks use Codex's ${PLUGIN_ROOT}, so Claude gets a separate thin
|
|
8
|
+
// plugin whose hooks call the installed versioned runtime by absolute path. Commands and agents stay in the
|
|
9
|
+
// existing ~/.claude mirrors so they are not listed twice.
|
|
10
|
+
export async function stageClaudePlugin({
|
|
11
|
+
stageRoot, runtimeStage, destinationRoot, versionedPluginRoot, marketplaceName, pluginName,
|
|
12
|
+
releaseVersion, skillNames, platform = process.platform,
|
|
13
|
+
}) {
|
|
14
|
+
const staged = join(stageRoot, "claude-plugin");
|
|
15
|
+
const pluginDir = join(staged, pluginName);
|
|
16
|
+
const packageJson = JSON.parse(await readFile(join(runtimeStage, "package.json"), "utf8"));
|
|
17
|
+
const description = packageJson.description ?? "Cairn agent harness";
|
|
18
|
+
|
|
19
|
+
await writeJson(join(staged, ".claude-plugin", "marketplace.json"), {
|
|
20
|
+
name: marketplaceName,
|
|
21
|
+
description: "Local Cairn marketplace managed by `cairn install`",
|
|
22
|
+
owner: { name: packageJson.author?.name ?? "Cairn" },
|
|
23
|
+
plugins: [{ name: pluginName, source: `./${pluginName}`, description, version: releaseVersion }],
|
|
24
|
+
});
|
|
25
|
+
await writeJson(join(pluginDir, ".claude-plugin", "plugin.json"), {
|
|
26
|
+
name: pluginName,
|
|
27
|
+
version: releaseVersion,
|
|
28
|
+
description,
|
|
29
|
+
author: { name: packageJson.author?.name ?? "Cairn" },
|
|
30
|
+
...(packageJson.homepage ? { homepage: packageJson.homepage } : {}),
|
|
31
|
+
...(packageJson.license ? { license: packageJson.license } : {}),
|
|
32
|
+
});
|
|
33
|
+
await writeJson(join(pluginDir, "hooks", "hooks.json"), await claudeHooks(runtimeStage, versionedPluginRoot, platform));
|
|
34
|
+
|
|
35
|
+
for (const name of skillNames) {
|
|
36
|
+
const skillStage = join(pluginDir, "skills", name);
|
|
37
|
+
await cp(join(runtimeStage, "skills", name), skillStage, { recursive: true });
|
|
38
|
+
const locatorPath = join(destinationRoot, pluginName, "skills", name, "references", "cairn-runtime.json");
|
|
39
|
+
await renderMirrorFile(join(runtimeStage, "skills", name, "SKILL.md"), join(skillStage, "SKILL.md"), {
|
|
40
|
+
locatorPath, runtimeRoot: versionedPluginRoot, includeLocatorNotice: true,
|
|
41
|
+
});
|
|
42
|
+
await writeLocator(join(skillStage, "references", "cairn-runtime.json"), versionedPluginRoot);
|
|
43
|
+
}
|
|
44
|
+
return staged;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function claudeHooks(runtimeStage, versionedPluginRoot, platform) {
|
|
48
|
+
const source = JSON.parse(await readFile(join(runtimeStage, "hooks", "hooks.json"), "utf8"));
|
|
49
|
+
const stateScript = quoteShellArg(join(versionedPluginRoot, "scripts", "cairn-state.mjs"), platform);
|
|
50
|
+
const hooks = {};
|
|
51
|
+
for (const [eventName, groups] of Object.entries(source.hooks ?? {})) {
|
|
52
|
+
hooks[eventName] = groups.map((group) => ({
|
|
53
|
+
...group,
|
|
54
|
+
hooks: (group.hooks ?? []).map((handler) => {
|
|
55
|
+
const event = String(handler.command ?? "").match(/cairn-state\.mjs"?\s+([a-z-]+)\s*$/)?.[1];
|
|
56
|
+
if (!event) throw new Error(`Unsupported Cairn hook command for Claude Code: ${handler.command}`);
|
|
57
|
+
return { ...handler, command: `node ${stateScript} ${event}` };
|
|
58
|
+
}),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
return { hooks };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function writeJson(path, value) {
|
|
65
|
+
await mkdir(dirname(path), { recursive: true });
|
|
66
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Registration goes through the documented `claude plugin` CLI. CLAUDE_CONFIG_DIR is only set when the caller
|
|
70
|
+
// overrode the Claude home, because setting it for the default home would move Claude's global config file.
|
|
71
|
+
export function createClaudeRegistration({ claudeHome, claudeHomeOverridden, marketplaceRoot, marketplaceName, pluginName }) {
|
|
72
|
+
const cli = process.env.CAIRN_CLAUDE_CLI ?? "claude";
|
|
73
|
+
const pluginId = `${pluginName}@${marketplaceName}`;
|
|
74
|
+
const env = claudeHomeOverridden ? { ...process.env, CLAUDE_CONFIG_DIR: claudeHome } : process.env;
|
|
75
|
+
|
|
76
|
+
// A .mjs/.js override runs through the current Node binary so tests can supply a portable stand-in CLI.
|
|
77
|
+
function run(args) {
|
|
78
|
+
if (/\.m?js$/.test(cli)) return spawnSync(process.execPath, [cli, ...args], { encoding: "utf8", env, timeout: 120_000 });
|
|
79
|
+
return spawnSync(cli, args, { encoding: "utf8", env, timeout: 120_000, shell: process.platform === "win32" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function available() {
|
|
83
|
+
if (cli === "none") return false;
|
|
84
|
+
const result = run(["--version"]);
|
|
85
|
+
return !result.error && result.status === 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function json(args) {
|
|
89
|
+
const result = run([...args, "--json"]);
|
|
90
|
+
if (result.error || result.status !== 0) {
|
|
91
|
+
throw new Error(`claude ${args.join(" ")} failed: ${(result.stderr || result.stdout || result.error?.message || "").trim().split("\n").at(-1)}`);
|
|
92
|
+
}
|
|
93
|
+
const text = result.stdout.trim();
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
} catch {
|
|
97
|
+
return JSON.parse(text.split("\n").filter(Boolean).at(-1));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const ownsMarketplace = (entry) => entry?.name === marketplaceName
|
|
102
|
+
&& typeof entry.path === "string" && resolve(entry.path) === resolve(marketplaceRoot);
|
|
103
|
+
const ownedPlugins = (list) => (Array.isArray(list) ? list : [])
|
|
104
|
+
.filter((entry) => entry?.id === pluginId && entry.scope === "user");
|
|
105
|
+
|
|
106
|
+
function status({ checked = false } = {}) {
|
|
107
|
+
if (!checked && !available()) return { available: false, marketplace: false, installed: false, enabled: false };
|
|
108
|
+
const marketplace = json(["plugin", "marketplace", "list"]).find((entry) => entry?.name === marketplaceName) ?? null;
|
|
109
|
+
const plugin = ownedPlugins(json(["plugin", "list"]))[0] ?? null;
|
|
110
|
+
return {
|
|
111
|
+
available: true,
|
|
112
|
+
marketplace: ownsMarketplace(marketplace),
|
|
113
|
+
foreignMarketplace: Boolean(marketplace) && !ownsMarketplace(marketplace),
|
|
114
|
+
installed: Boolean(plugin),
|
|
115
|
+
enabled: Boolean(plugin?.enabled),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function register() {
|
|
120
|
+
if (!available()) return { registered: false, reason: "claude CLI not found" };
|
|
121
|
+
const current = status({ checked: true });
|
|
122
|
+
if (current.foreignMarketplace) {
|
|
123
|
+
return { registered: false, reason: `a different Claude Code marketplace named "${marketplaceName}" already exists` };
|
|
124
|
+
}
|
|
125
|
+
if (!current.marketplace) json(["plugin", "marketplace", "add", marketplaceRoot, "--scope", "user"]);
|
|
126
|
+
if (!current.installed) json(["plugin", "install", pluginId, "--scope", "user"]);
|
|
127
|
+
else if (!current.enabled) json(["plugin", "enable", pluginId, "--scope", "user"]);
|
|
128
|
+
return { registered: true };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function unregister() {
|
|
132
|
+
if (!available()) return { unregistered: false, reason: "claude CLI not found" };
|
|
133
|
+
const current = status({ checked: true });
|
|
134
|
+
// Only remove a registration that points at Cairn's own marketplace directory.
|
|
135
|
+
if (!current.marketplace) return { unregistered: false, reason: "no Cairn-owned Claude Code marketplace is registered" };
|
|
136
|
+
if (current.installed) json(["plugin", "uninstall", pluginId, "--scope", "user"]);
|
|
137
|
+
json(["plugin", "marketplace", "remove", marketplaceName]);
|
|
138
|
+
return { unregistered: true };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { available, register, status, unregister, pluginId };
|
|
142
|
+
}
|
|
@@ -5,11 +5,12 @@ import { hasSetting, readSharedConfigSnapshot, removeCairnConfig, splitSections
|
|
|
5
5
|
import {
|
|
6
6
|
installedSkillLocatorPath, validRuntimeLocator, validateAntigravitySkills,
|
|
7
7
|
} from "./cairn-lifecycle-mirror.mjs";
|
|
8
|
+
import { createClaudeRegistration } from "./cairn-lifecycle-claude.mjs";
|
|
8
9
|
|
|
9
10
|
export function createCommands(context) {
|
|
10
11
|
const {
|
|
11
|
-
antigravityCliHome, antigravityHome,
|
|
12
|
-
codexHome, configPath, installedPluginRoot, installedRuntimeLocatorPath,
|
|
12
|
+
antigravityCliHome, antigravityHome, claudeHome, claudeHomeOverridden, claudePluginMarketplaceRoot,
|
|
13
|
+
claudeRuntimeLocatorPath, codexHome, marketplaceName, configPath, installedPluginRoot, installedRuntimeLocatorPath,
|
|
13
14
|
marketplaceJsonPath, ownershipPath, phases, pluginName, pluginRoot,
|
|
14
15
|
pruneUninstallScaffolds, releaseVersion, samePath, skillNames, t, transaction,
|
|
15
16
|
versionedPluginRoot, staging, integrity,
|
|
@@ -19,6 +20,9 @@ export function createCommands(context) {
|
|
|
19
20
|
removeManagedTarget, replaceTarget, rollback,
|
|
20
21
|
} = transaction;
|
|
21
22
|
const { legacyMigrationTargets, stageInstall } = staging;
|
|
23
|
+
const claudeRegistration = createClaudeRegistration({
|
|
24
|
+
claudeHome, claudeHomeOverridden, marketplaceRoot: claudePluginMarketplaceRoot, marketplaceName, pluginName,
|
|
25
|
+
});
|
|
22
26
|
const {
|
|
23
27
|
adoptLegacy, anyManagedArtifactExists, assertOwnershipDigests,
|
|
24
28
|
assertTargetsReplaceable, ownershipDigestsValid, readOwnership, targetDigest,
|
|
@@ -80,6 +84,24 @@ async function install(mode) {
|
|
|
80
84
|
if (transaction.state !== "committed") await rollback(transaction);
|
|
81
85
|
throw error;
|
|
82
86
|
}
|
|
87
|
+
reportClaudeRegistration();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Files are committed first; registering with Claude Code is idempotent, and a failure leaves the files
|
|
91
|
+
// installed with a manual command to finish, instead of rolling back a working Codex/Antigravity install.
|
|
92
|
+
function reportClaudeRegistration() {
|
|
93
|
+
const manual = `claude plugin marketplace add ${JSON.stringify(claudePluginMarketplaceRoot)} && claude plugin install ${claudeRegistration.pluginId} --scope user`;
|
|
94
|
+
try {
|
|
95
|
+
const result = claudeRegistration.register();
|
|
96
|
+
if (result.registered) {
|
|
97
|
+
console.log(`Claude Code plugin: ${claudeRegistration.pluginId} (${claudePluginMarketplaceRoot})`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(`Claude Code plugin not registered: ${result.reason}. To register: ${manual}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(`Claude Code plugin registration failed: ${error.message}. To register: ${manual}`);
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
83
105
|
}
|
|
84
106
|
|
|
85
107
|
async function uninstall() {
|
|
@@ -92,6 +114,8 @@ async function uninstall() {
|
|
|
92
114
|
if (record.type !== "config" && current !== record.installedDigest) conflicts.push(record.path);
|
|
93
115
|
}
|
|
94
116
|
if (conflicts.length > 0) throw new Error(`Refusing to uninstall modified managed artifacts: ${conflicts.join(", ")}`);
|
|
117
|
+
// Unregister before removing files so Claude Code never points at a deleted plugin; a failure aborts with nothing changed.
|
|
118
|
+
const unregistered = claudeRegistration.unregister().unregistered;
|
|
95
119
|
const transaction = await createTransaction();
|
|
96
120
|
try {
|
|
97
121
|
const configRecord = ownership.targets.find((record) => record.type === "config");
|
|
@@ -114,7 +138,13 @@ async function uninstall() {
|
|
|
114
138
|
await finishTransaction(transaction);
|
|
115
139
|
injectFailure("uninstall-after-commit");
|
|
116
140
|
} catch (error) {
|
|
117
|
-
if (transaction.state !== "committed")
|
|
141
|
+
if (transaction.state !== "committed") {
|
|
142
|
+
await rollback(transaction);
|
|
143
|
+
// The rollback restored the plugin files, so restore the Claude Code registration that preceded them.
|
|
144
|
+
if (unregistered) {
|
|
145
|
+
try { claudeRegistration.register(); } catch (registrationError) { console.error(`Claude Code plugin re-registration failed: ${registrationError.message}`); }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
118
148
|
throw error;
|
|
119
149
|
}
|
|
120
150
|
await pruneUninstallScaffolds();
|
|
@@ -141,6 +171,14 @@ async function doctor() {
|
|
|
141
171
|
checks.push(["config marketplace", splitSections(config).some((section) => section.header === "marketplaces.cairn")]);
|
|
142
172
|
checks.push(["config plugin enabled", hasSetting(config, 'plugins."cairn@cairn"', "enabled", "true")]);
|
|
143
173
|
checks.push(["Claude runtime locator", await validRuntimeLocator(claudeRuntimeLocatorPath, versionedPluginRoot)]);
|
|
174
|
+
const claudePluginManifest = await readJson(join(claudePluginMarketplaceRoot, pluginName, ".claude-plugin", "plugin.json"));
|
|
175
|
+
checks.push(["Claude plugin files", claudePluginManifest?.version === releaseVersion
|
|
176
|
+
&& await exists(join(claudePluginMarketplaceRoot, ".claude-plugin", "marketplace.json"))
|
|
177
|
+
&& await exists(join(claudePluginMarketplaceRoot, pluginName, "hooks", "hooks.json"))]);
|
|
178
|
+
let claudeStatus = null;
|
|
179
|
+
try { claudeStatus = claudeRegistration.status(); } catch { claudeStatus = null; }
|
|
180
|
+
checks.push(["Claude plugin registered", Boolean(claudeStatus?.marketplace && claudeStatus.installed)]);
|
|
181
|
+
checks.push(["Claude plugin enabled", Boolean(claudeStatus?.enabled)]);
|
|
144
182
|
checks.push(["Antigravity IDE skills", await exists(join(antigravityHome, "skills", "cairn-plan", "SKILL.md"))]);
|
|
145
183
|
checks.push(["Antigravity CLI flat skills", await exists(join(antigravityCliHome, "skills", "cairn-plan.md"))]);
|
|
146
184
|
|
|
@@ -99,10 +99,14 @@ export function splitSections(config) {
|
|
|
99
99
|
}
|
|
100
100
|
const doubleCount = delimiterCount('"""');
|
|
101
101
|
const singleCount = delimiterCount("'''");
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
// Array-of-tables and headers with trailing comments start a new section too; otherwise a foreign section
|
|
103
|
+
// after Cairn's would be absorbed into it and deleted on upgrade/uninstall.
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
const arrayMatch = trimmed.match(/^\[\[([^\[\]]+)\]\]\s*(?:#.*)?$/);
|
|
106
|
+
const match = arrayMatch ? null : trimmed.match(/^\[([^\[\]]+)\]\s*(?:#.*)?$/);
|
|
107
|
+
if (arrayMatch || match) {
|
|
104
108
|
if (current.text.length > 0) result.push(current);
|
|
105
|
-
current = { header: match[1], text: line };
|
|
109
|
+
current = { header: arrayMatch ? `[${arrayMatch[1].trim()}]` : match[1].trim(), text: line };
|
|
106
110
|
} else current.text += line;
|
|
107
111
|
if (doubleCount % 2 === 1) multiline = '"""';
|
|
108
112
|
else if (singleCount % 2 === 1) multiline = "'''";
|
|
@@ -7,7 +7,7 @@ import { antigravityRuntimeLocatorPath } from "./cairn-lifecycle-mirror.mjs";
|
|
|
7
7
|
|
|
8
8
|
export function createIntegrity(context) {
|
|
9
9
|
const {
|
|
10
|
-
antigravityCliHome, antigravityHome, claudeHome, claudeRuntimeLocatorPath,
|
|
10
|
+
antigravityCliHome, antigravityHome, claudeHome, claudePluginMarketplaceRoot, claudeRuntimeLocatorPath,
|
|
11
11
|
commandNames, configPath, installedPluginRoot, legacyAntigravityHome,
|
|
12
12
|
marketplaceJsonPath, marketplaceRoot, ownershipPath, phases, pluginName,
|
|
13
13
|
releaseVersion, skillNames,
|
|
@@ -97,13 +97,25 @@ function validateOwnershipShape(ownership, { required = false } = {}) {
|
|
|
97
97
|
paths.add(record.path);
|
|
98
98
|
}
|
|
99
99
|
const requiredTargets = requiredOwnershipTargets(ownership.version);
|
|
100
|
-
|
|
100
|
+
const optionalIds = new Set(requiredTargets.has("claude-plugin") ? [] : ["claude-plugin"]);
|
|
101
|
+
if ([...ids].some((id) => !requiredTargets.has(id) && !optionalIds.has(id)) || [...requiredTargets.keys()].some((id) => !ids.has(id))) {
|
|
101
102
|
throw new Error("Ownership manifest does not contain the complete managed target set.");
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
const CLAUDE_PLUGIN_SINCE = "0.2.9";
|
|
107
|
+
|
|
108
|
+
function compareVersions(left, right) {
|
|
109
|
+
const parse = (value) => String(value).split("-")[0].split(".").map(Number);
|
|
110
|
+
const [a, b] = [parse(left), parse(right)];
|
|
111
|
+
for (let index = 0; index < 3; index += 1) if (a[index] !== b[index]) return a[index] - b[index];
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
function requiredOwnershipTargets(version = releaseVersion) {
|
|
106
116
|
const ids = ["codex-source", "codex-runtime", "marketplace", "claude-runtime", "ide-runtime", "cli-runtime", "codex-config"];
|
|
117
|
+
// Manifests written before the Claude Code plugin existed must stay valid so those installs can upgrade.
|
|
118
|
+
if (compareVersions(version, CLAUDE_PLUGIN_SINCE) >= 0) ids.push("claude-plugin");
|
|
107
119
|
for (const name of commandNames()) ids.push(`claude-command-${name}`, `ide-workflow-${name}`, `cli-workflow-${name}`);
|
|
108
120
|
for (const name of ["explorer", "worker"]) ids.push(`claude-agent-${name}`);
|
|
109
121
|
for (const name of skillNames()) ids.push(`ide-skill-${name}`, `cli-skill-${name}`);
|
|
@@ -116,6 +128,7 @@ function expectedManagedTarget(id, version = releaseVersion) {
|
|
|
116
128
|
"codex-runtime": { phase: "codex", path: versionedRuntimeRoot(version), type: "tree" },
|
|
117
129
|
marketplace: { phase: "codex", path: marketplaceJsonPath, type: "file" },
|
|
118
130
|
"claude-runtime": { phase: "claude", path: claudeRuntimeLocatorPath, type: "file" },
|
|
131
|
+
"claude-plugin": { phase: "claude", path: claudePluginMarketplaceRoot, type: "tree" },
|
|
119
132
|
"ide-runtime": { phase: "antigravity", path: antigravityRuntimeLocatorPath(antigravityHome), type: "file" },
|
|
120
133
|
"cli-runtime": { phase: "antigravity", path: antigravityRuntimeLocatorPath(antigravityCliHome), type: "file" },
|
|
121
134
|
"legacy-ide-runtime": { phase: "antigravity", path: antigravityRuntimeLocatorPath(legacyAntigravityHome), type: "file" },
|
|
@@ -4,11 +4,12 @@ import { readSharedConfigSnapshot, trustedHookStates } from "./cairn-lifecycle-c
|
|
|
4
4
|
import {
|
|
5
5
|
antigravityRuntimeLocatorPath, antigravitySkillLocatorPath, renderMirrorFile, writeLocator,
|
|
6
6
|
} from "./cairn-lifecycle-mirror.mjs";
|
|
7
|
+
import { stageClaudePlugin } from "./cairn-lifecycle-claude.mjs";
|
|
7
8
|
|
|
8
9
|
export function createStaging(context) {
|
|
9
10
|
const {
|
|
10
|
-
agentNames, antigravityCliHome, antigravityHome, claudeHome,
|
|
11
|
-
claudeRuntimeLocatorPath, commandNames, configPath, installedPluginRoot,
|
|
11
|
+
agentNames, antigravityCliHome, antigravityHome, claudeHome, claudePluginMarketplaceRoot,
|
|
12
|
+
claudeRuntimeLocatorPath, commandNames, releaseVersion, configPath, installedPluginRoot,
|
|
12
13
|
legacyAntigravityHome, marketplaceJsonPath, marketplaceName, pluginName,
|
|
13
14
|
pluginRoot, skillNames, updateConfig, versionedPluginRoot, assertRegularTree,
|
|
14
15
|
} = context;
|
|
@@ -44,6 +45,12 @@ async function stageInstall(stageRoot) {
|
|
|
44
45
|
add(`claude-agent-${name}`, "claude", join(claudeHome, "agents", `cairn-${name}.md`), staged, "file");
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
const claudePluginStage = await stageClaudePlugin({
|
|
49
|
+
stageRoot, runtimeStage, destinationRoot: claudePluginMarketplaceRoot, versionedPluginRoot,
|
|
50
|
+
marketplaceName, pluginName, releaseVersion, skillNames: skillNames(),
|
|
51
|
+
});
|
|
52
|
+
add("claude-plugin", "claude", claudePluginMarketplaceRoot, claudePluginStage);
|
|
53
|
+
|
|
47
54
|
await stageAntigravity(targets, stageRoot, runtimeStage, antigravityHome, "ide", false);
|
|
48
55
|
await stageAntigravity(targets, stageRoot, runtimeStage, antigravityCliHome, "cli", true);
|
|
49
56
|
|
|
@@ -30,6 +30,7 @@ export {
|
|
|
30
30
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
31
31
|
const homeRoot = process.env.HOME ?? process.env.USERPROFILE ?? homedir() ?? ".";
|
|
32
32
|
const codexHome = resolve(process.env.CODEX_HOME ?? join(homeRoot, ".codex"));
|
|
33
|
+
const claudeHomeOverridden = typeof process.env.CLAUDE_HOME === "string" && process.env.CLAUDE_HOME.length > 0;
|
|
33
34
|
const claudeHome = resolve(process.env.CLAUDE_HOME ?? join(homeRoot, ".claude"));
|
|
34
35
|
const antigravityHome = resolve(process.env.ANTIGRAVITY_HOME ?? join(homeRoot, ".gemini", "config"));
|
|
35
36
|
const legacyAntigravityHome = resolve(process.env.CAIRN_LEGACY_ANTIGRAVITY_HOME ?? join(homeRoot, ".agents"));
|
|
@@ -45,6 +46,7 @@ const transactionPath = join(marketplaceRoot, ".cairn", "transaction.json");
|
|
|
45
46
|
const transactionRoot = join(marketplaceRoot, ".cairn", "transactions");
|
|
46
47
|
const lifecycleLockPath = join(marketplaceRoot, ".cairn", "lifecycle.lock");
|
|
47
48
|
const claudeRuntimeLocatorPath = join(claudeHome, "cairn", "runtime.json");
|
|
49
|
+
const claudePluginMarketplaceRoot = join(marketplaceRoot, "claude");
|
|
48
50
|
const sourceManifest = JSON.parse(await readFile(join(pluginRoot, ".codex-plugin", "plugin.json"), "utf8"));
|
|
49
51
|
const releaseVersion = sourceManifest.version;
|
|
50
52
|
const versionedPluginRoot = join(marketplaceRoot, pluginName, releaseVersion);
|
|
@@ -53,7 +55,8 @@ const phases = ["codex", "claude", "antigravity", "config", "cleanup"];
|
|
|
53
55
|
const MAX_LIFECYCLE_LOCK_TIMEOUT_MS = 2_147_483_647;
|
|
54
56
|
|
|
55
57
|
const lifecycleContext = {
|
|
56
|
-
agentNames, antigravityCliHome, antigravityHome, claudeHome,
|
|
58
|
+
agentNames, antigravityCliHome, antigravityHome, claudeHome, claudeHomeOverridden,
|
|
59
|
+
claudePluginMarketplaceRoot, claudeRuntimeLocatorPath,
|
|
57
60
|
codexHome, commandNames, configPath, installedPluginRoot, installedRuntimeLocatorPath,
|
|
58
61
|
legacyAntigravityHome, lifecycleLockPath, marketplaceJsonPath, marketplaceName,
|
|
59
62
|
marketplaceRoot, ownershipPath, phases, pluginName, pluginRoot, releaseVersion,
|
|
@@ -105,7 +108,7 @@ function lifecycleLockTimeout() {
|
|
|
105
108
|
async function assertLifecycleMutationPaths() {
|
|
106
109
|
const roots = [
|
|
107
110
|
[codexHome, lifecycleLockPath], [codexHome, installedPluginRoot], [codexHome, versionedPluginRoot],
|
|
108
|
-
[codexHome, marketplaceJsonPath], [isInside(codexHome, configPath) ? codexHome : dirname(configPath), configPath],
|
|
111
|
+
[codexHome, marketplaceJsonPath], [codexHome, claudePluginMarketplaceRoot], [isInside(codexHome, configPath) ? codexHome : dirname(configPath), configPath],
|
|
109
112
|
[claudeHome, join(claudeHome, "commands")], [claudeHome, join(claudeHome, "agents")],
|
|
110
113
|
[claudeHome, claudeRuntimeLocatorPath], [antigravityHome, join(antigravityHome, "skills")],
|
|
111
114
|
...skillNames().map((name) => [antigravityHome, join(antigravityHome, "skills", name)]),
|
|
@@ -43,8 +43,8 @@ export function contextResult({ ko, state, event }) {
|
|
|
43
43
|
const planId = state.goal.planId;
|
|
44
44
|
const phase = task ? "cairn-work" : "cairn-review";
|
|
45
45
|
const activeHeader = ko
|
|
46
|
-
? `${base} ${phase}를 로드하고 정확한 plan ${planId}${task ? "과 아래 current task" : "의 완료 기준"}를 복원하세요. 압축·재시작·handoff
|
|
47
|
-
: `${base} Load ${phase}; restore exact plan ${planId}${task ? " and the current task below" : " completion criteria"}. Re-read after compaction/restart/handoff
|
|
46
|
+
? `${base} ${phase}를 로드하고 정확한 plan ${planId}${task ? "과 아래 current task" : "의 완료 기준"}를 복원하세요. 압축·재시작·handoff 뒤 다시 읽으세요.${failClosed}`
|
|
47
|
+
: `${base} Load ${phase}; restore exact plan ${planId}${task ? " and the current task below" : " completion criteria"}. Re-read after compaction/restart/handoff.${failClosed}`;
|
|
48
48
|
if (!task) {
|
|
49
49
|
return contextHookResult({
|
|
50
50
|
event,
|
|
@@ -63,11 +63,11 @@ export function contextResult({ ko, state, event }) {
|
|
|
63
63
|
const resume = event === "user-prompt-submit"
|
|
64
64
|
? (taskId.length > 48
|
|
65
65
|
? (ko
|
|
66
|
-
? "곁가지
|
|
67
|
-
: "
|
|
66
|
+
? "곁가지 질문·조향 뒤 위의 정확한 task를 재개하고 최종 검토까지 진행하세요(일시정지·중단·전환 요청 제외)."
|
|
67
|
+
: "After side questions or steers, resume that exact task; advance to final review unless paused, stopped, or switched.")
|
|
68
68
|
: (ko
|
|
69
|
-
? `곁가지
|
|
70
|
-
: `After a side question, resume ${taskId} unless asked to pause, stop, or switch.`))
|
|
69
|
+
? `곁가지 질문·조향 뒤 ${taskId}를 재개하고 남은 task를 최종 검토까지 진행하세요(일시정지·중단·전환 요청 제외).`
|
|
70
|
+
: `After a side question or steer, resume ${taskId}, then advance remaining tasks to final review unless asked to pause, stop, or switch.`))
|
|
71
71
|
: "";
|
|
72
72
|
return contextHookResult({ event, message: [activeHeader, taskRoadmap(ko, state), continuation, resume].filter(Boolean).join("\n") });
|
|
73
73
|
}
|
|
@@ -87,18 +87,23 @@ function interruptedContextResult(event, ko, state) {
|
|
|
87
87
|
: (ko ? "현재 task: 없음." : "Current task: none.");
|
|
88
88
|
const blocker = state.goal.blocker ?? task?.blocker ?? (ko ? "없음" : "none");
|
|
89
89
|
const recovery = ko
|
|
90
|
-
? "자동으로
|
|
91
|
-
: "Do not
|
|
90
|
+
? "goal 상태를 자동으로 바꾸지 마세요. 정확한 context를 복원하고 원인을 처리한 뒤 goal resume을 명시적으로 실행하고 현재 task를 이어가세요."
|
|
91
|
+
: "Do not change goal status automatically. Restore this exact context, address the interruption, then run goal resume explicitly and continue the current task.";
|
|
92
92
|
const message = ko
|
|
93
93
|
? `${kernelBase(ko)} 정확한 plan: ${state.goal.planId}. ${taskContext} Goal 상태: ${state.goal.status}. Blocker: ${blocker}. ${recovery}`
|
|
94
94
|
: `${kernelBase(ko)} Exact plan: ${state.goal.planId}. ${taskContext} Goal status: ${state.goal.status}. Blocker: ${blocker}. ${recovery}`;
|
|
95
95
|
return contextHookResult({ event, message });
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export function foreignSessionContextResult({ ko, event }) {
|
|
98
|
+
export function foreignSessionContextResult({ ko, event, sessionId }) {
|
|
99
|
+
const claim = typeof sessionId === "string" && sessionId.length > 0
|
|
100
|
+
? (ko
|
|
101
|
+
? ` 이 session이 같은 작업을 이어받는 경우(재개·압축·handoff·fork)에만 cairn goal claim --session ${JSON.stringify(sessionId)} --root <repoRoot>로 소유권을 넘겨받고 이어가세요.`
|
|
102
|
+
: ` Only if this session continues that same work (resume, compaction, handoff, fork), take ownership with cairn goal claim --session ${JSON.stringify(sessionId)} --root <repoRoot>, then continue.`)
|
|
103
|
+
: "";
|
|
99
104
|
const message = ko
|
|
100
|
-
?
|
|
101
|
-
:
|
|
105
|
+
? `Cairn kernel: 루트 MEMORY.md는 선택 사항이며, 있으면 읽으세요. 저장소 goal을 다른 session이 소유합니다. cairn goal status로 상태를 확인하고 소유권이 해결될 때까지 Cairn 작업을 시작·수정·위임·완료하지 마세요.${claim} 이 capsule은 goal, plan, task 상세를 노출하지 않습니다.`
|
|
106
|
+
: `Cairn kernel: root MEMORY.md is optional; read it when present. A repository goal is owned by another session. Inspect cairn goal status; until ownership is resolved, do not start, edit, delegate, or complete Cairn work.${claim} This capsule does not expose goal, plan, or task details.`;
|
|
102
107
|
return contextHookResult({ event, message });
|
|
103
108
|
}
|
|
104
109
|
|
|
@@ -117,7 +122,10 @@ function taskRoadmap(ko, state) {
|
|
|
117
122
|
: `Work steps (${state.tasks.length}; ${countSummary})`;
|
|
118
123
|
const omission = ko ? `… ${omitted}개 단계 생략` : `… ${omitted} steps omitted`;
|
|
119
124
|
const currentPosition = currentIndex + 1;
|
|
120
|
-
|
|
125
|
+
const upcoming = state.tasks.slice(currentIndex + 1).filter((task) => task.status === "pending").slice(0, 2)
|
|
126
|
+
.map((task) => clipText(task.id, contextLimits.roadmapTaskId));
|
|
127
|
+
const next = upcoming.length > 0 ? `; ${ko ? "다음" : "next"} ${upcoming.join(", ")}` : "";
|
|
128
|
+
return `${header}. ${ko ? `현재 위치 ${currentPosition}` : `Current position ${currentPosition}`}; ${omission}${next}`;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
function contextHookResult({ event, message }) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
export function continuationReason(reason, payload) {
|
|
2
|
-
|
|
3
|
-
return `${reason}
|
|
1
|
+
export function continuationReason(reason, payload, { progressed = false } = {}) {
|
|
2
|
+
if (!payload?.stop_hook_active || progressed) return reason;
|
|
3
|
+
return `${reason} This turn was already continued without new Cairn state progress, so the loop guard allows this stop. The goal is still active and incomplete: resume the current task on the next prompt; do not treat it as finished or deferred.`;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// stop_hook_active alone would allow every second stop. Block again whenever the goal state advanced
|
|
7
|
+
// since the previous continuation, so a steered turn keeps driving the goal; allow only a no-progress repeat.
|
|
8
|
+
export function stopHookOutput(gate, payload, { progressed = false } = {}) {
|
|
7
9
|
if (!gate.block) return { continue: true };
|
|
8
|
-
const reason = continuationReason(gate.reason, payload);
|
|
9
|
-
if (payload?.stop_hook_active) {
|
|
10
|
+
const reason = continuationReason(gate.reason, payload, { progressed });
|
|
11
|
+
if (payload?.stop_hook_active && !progressed) {
|
|
10
12
|
return { continue: true, systemMessage: reason };
|
|
11
13
|
}
|
|
12
14
|
return { decision: "block", reason };
|
package/scripts/cairn-state.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { contextResult, foreignSessionContextResult, prependContext } from "./ca
|
|
|
5
5
|
import { continuationReason, stopAllowedMessage, stopHookOutput } from "./cairn-state-hook.mjs";
|
|
6
6
|
import { initializeProject } from "./cairn-state-init.mjs";
|
|
7
7
|
import { evaluateStop, isGoalOwnedBySession, reconcileWorktreeState } from "./cairn-goal.mjs";
|
|
8
|
+
import { readStopGuard, writeStopGuard } from "./cairn-goal-state.mjs";
|
|
8
9
|
import { resolveRepoRoot } from "./cairn-paths.mjs";
|
|
9
10
|
|
|
10
11
|
const recoverableGoalStatuses = new Set(["active", "paused", "blocked"]);
|
|
@@ -12,7 +13,9 @@ const recoverableGoalStatuses = new Set(["active", "paused", "blocked"]);
|
|
|
12
13
|
if (isCliEntry()) {
|
|
13
14
|
const event = process.argv[2] ?? "manual";
|
|
14
15
|
const payload = await readHookInput();
|
|
15
|
-
const result =
|
|
16
|
+
const result = isHookEvent(event)
|
|
17
|
+
? await runStateResult(event, { payload }).catch((error) => hookFailureResult(event, error))
|
|
18
|
+
: await runStateResult(event, { payload });
|
|
16
19
|
if (isHookEvent(event)) {
|
|
17
20
|
console.log(JSON.stringify(result.hookOutput ?? {}));
|
|
18
21
|
} else {
|
|
@@ -47,7 +50,7 @@ export async function runStateResult(event = "manual", {
|
|
|
47
50
|
const reconciled = await reconcileWorktreeState({ root: resolvedRoot });
|
|
48
51
|
const state = reconciled.state;
|
|
49
52
|
if (recoverableGoalStatuses.has(state?.goal?.status) && !isGoalOwnedBySession(state, payload?.session_id)) {
|
|
50
|
-
return foreignSessionContextResult({ ko, event });
|
|
53
|
+
return foreignSessionContextResult({ ko, event, sessionId: payload?.session_id, root: resolvedRoot });
|
|
51
54
|
}
|
|
52
55
|
const result = contextResult({ ko, state, event });
|
|
53
56
|
if (!reconciled.removed) return result;
|
|
@@ -58,6 +61,12 @@ export async function runStateResult(event = "manual", {
|
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
if (event === "post-tool-use") {
|
|
64
|
+
// Only remind while an owned goal is active: an unconditional reminder after every edit accumulates in the
|
|
65
|
+
// conversation prefix, raising cached-read cost and bringing compaction (a full cache reset) sooner.
|
|
66
|
+
const { state } = await reconcileWorktreeState({ root: resolvedRoot });
|
|
67
|
+
if (state?.goal?.status !== "active" || !isGoalOwnedBySession(state, payload?.session_id)) {
|
|
68
|
+
return { status: 0, message: "", hookOutput: {} };
|
|
69
|
+
}
|
|
61
70
|
const message = ko
|
|
62
71
|
? "Cairn 점검: 외부 상태 변경에는 dry-run/check 증거 기록이 필요합니다. 활성 목표가 있다면 현재 task에 성공 증거를 기록하세요."
|
|
63
72
|
: "Cairn check: external-state changes need dry-run/check evidence. When a goal is active, record successful evidence for its current task.";
|
|
@@ -80,10 +89,11 @@ export async function runStateResult(event = "manual", {
|
|
|
80
89
|
subagent: event === "subagent-stop",
|
|
81
90
|
agentId: payload?.agent_id,
|
|
82
91
|
});
|
|
83
|
-
const
|
|
92
|
+
const progressed = gate.block ? await recordStopProgress(resolvedRoot, state, event, payload) : false;
|
|
93
|
+
const hookOutput = stopHookOutput(gate, payload, { progressed });
|
|
84
94
|
return {
|
|
85
95
|
status: 0,
|
|
86
|
-
message: gate.block ? continuationReason(gate.reason, payload) : stopAllowedMessage({ ko, state, event }),
|
|
96
|
+
message: gate.block ? continuationReason(gate.reason, payload, { progressed }) : stopAllowedMessage({ ko, state, event }),
|
|
87
97
|
hookOutput,
|
|
88
98
|
};
|
|
89
99
|
}
|
|
@@ -94,6 +104,22 @@ export async function runStateResult(event = "manual", {
|
|
|
94
104
|
};
|
|
95
105
|
}
|
|
96
106
|
|
|
107
|
+
async function recordStopProgress(root, state, event, payload) {
|
|
108
|
+
const scope = event === "subagent-stop" ? `subagent:${payload?.agent_id ?? ""}` : "turn";
|
|
109
|
+
const previous = await readStopGuard(root);
|
|
110
|
+
const progressed = previous?.goalId !== state.goal.id
|
|
111
|
+
|| previous?.scope !== scope
|
|
112
|
+
|| previous?.revision !== state.revision;
|
|
113
|
+
if (progressed) await writeStopGuard(root, { goalId: state.goal.id, scope, revision: state.revision });
|
|
114
|
+
return progressed;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hookFailureResult(event, error) {
|
|
118
|
+
const detail = String(error?.message ?? error).split("\n")[0].slice(0, 300);
|
|
119
|
+
const message = `Cairn ${event} hook could not read goal state: ${detail}. Run cairn goal status to inspect it; do not treat an active goal as finished.`;
|
|
120
|
+
return { status: 0, message, hookOutput: { systemMessage: message } };
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
function resolveRoot(root, payload) {
|
|
98
124
|
return resolveRepoRoot({
|
|
99
125
|
explicitRoot: root ?? null,
|
|
@@ -31,7 +31,8 @@ Keep the target repository root separate. Pass it explicitly with `--root <repoR
|
|
|
31
31
|
- Recursive subagent delegation is allowed only for bounded sub-tasks when the current surface supports it.
|
|
32
32
|
4. Write detailed facts to `docs/memory/<domain>.md`.
|
|
33
33
|
5. Keep only one-line links and summaries in `MEMORY.md`.
|
|
34
|
-
6.
|
|
34
|
+
6. When a lesson or correction recurs and is machine-checkable, encode it as a test, lint rule, script, or hook instead of more prose; memory keeps judgment rules and a pointer to the enforcing check.
|
|
35
|
+
7. Write user-visible responses and generated or updated documentation, plans, and memory artifacts in the OS locale unless the user asks for another language.
|
|
35
36
|
|
|
36
37
|
## Delegation Prompt Format
|
|
37
38
|
|
|
@@ -33,7 +33,10 @@ For a non-trivial implementation request or continuation of already planned work
|
|
|
33
33
|
node "<pluginRoot>/scripts/cairn.mjs" goal start --root "<repoRoot>" --goal "<objective>" --plan "docs/plan/<topic>.md" --tasks '[{"id":"triage-plan","title":"Run planned triage and finalize the plan","requiredEvidence":["planArtifact","triageDecision"]},{"id":"implement","title":"Implement the finalized plan"},{"id":"verify","title":"Verify and review the result"}]' --criteria "<completion criterion>" --session "<current session_id>"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Pass `--session` only with a session ID the surface actually exposes to the model; never guess or invent one, because a mismatched owner makes every later hook treat the goal as foreign. If no stable session ID is exposed, omit `--session` and record that the goal is repository-scoped.
|
|
37
|
+
|
|
38
|
+
If an unfinished repository goal already exists when a new implementation request, steer, or drift arrives, do not cancel it to start another. Fold the new work into it with `goal replan` (completed tasks and their evidence are preserved; resume a paused or blocked goal first), synchronize the plan artifact and native UI roadmap, and keep advancing until goal final review. Start a separate goal only when the user explicitly switches work. When a hook reports that another session owns the goal and this session continues that same work, take ownership with `goal claim --session <id>` using the ID shown by the hook.
|
|
39
|
+
If Codex UI plan/goal tools are unavailable, record that limitation and continue with the repository plan and goal; never claim UI synchronization that did not occur.
|
|
37
40
|
|
|
38
41
|
## Procedure
|
|
39
42
|
|
|
@@ -13,7 +13,7 @@ Review is not another implementation loop. Confirm that the completed task satis
|
|
|
13
13
|
|
|
14
14
|
Every review run and delegated review agent reads project-root `MEMORY.md` when present. If it is absent, continue without repository memory and do not invoke another memory service.
|
|
15
15
|
|
|
16
|
-
At the start and after compaction, restart, delegation, or handoff, restore context in this order: optional root `MEMORY.md` → `cairn-review` → the active plan → completed/current-task references → model guidance recorded by the plan. Missing `MEMORY.md` is not an inconsistency. Confirm the remaining references agree with persisted goal state before reviewing. If any required reference is missing, unreadable, or inconsistent, do not approve, delegate, record final review, or complete the goal; report a blocker. Do not use a read-receipt field as evidence that a model understood a reference.
|
|
16
|
+
At the start and after compaction, restart, delegation, or handoff, restore context in this order: optional root `MEMORY.md` → `cairn-review` → the active plan → completed/current-task references → model guidance recorded by the plan. Missing `MEMORY.md` is not an inconsistency. Confirm the remaining references agree with persisted goal state before reviewing. If any required reference is missing, unreadable, or inconsistent, do not approve, delegate, record final review, or complete the goal; report a blocker. Do not use a read-receipt field as evidence that a model understood a reference. Delegation means the delegated agent restores on its own entry; the orchestrator does not re-read this chain after dispatching or collecting a subagent, and within one unbroken context it rereads only references that changed, which keeps the cached prompt prefix stable.
|
|
17
17
|
|
|
18
18
|
When subagent tools are available, each agent may recursively delegate bounded review sub-tasks to subagents. Every child subagent reads project-root `MEMORY.md` when present and continues when absent, keeps the assigned scope, and preserves others' edits.
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ Resolve any `cairn://...` resource through `references/cairn-runtime.json` next
|
|
|
23
23
|
|
|
24
24
|
1. Read the completed task in `docs/plan/<topic>.md`.
|
|
25
25
|
2. Read `MEMORY.md` when present and relevant `docs/memory/*.md`; continue when repository memory is absent.
|
|
26
|
-
3. Check changed files, tool readiness evidence, and evidence paths.
|
|
26
|
+
3. Check changed files, tool readiness evidence, and evidence paths. Inspect test, fixture, and verification-command diffs and reject any weakening without a recorded plan reason. For defect fixes, confirm the same-rule sweep was performed and its result recorded.
|
|
27
27
|
4. Delegate independent review when it materially improves speed or quality and the current tool policy allows it.
|
|
28
28
|
- Use `explorer` for read-only impact analysis, pattern checks, and independent diff inspection.
|
|
29
29
|
- Use `worker` for scoped verification command re-runs or QA artifact capture.
|
|
@@ -39,7 +39,7 @@ TASK: Review correctness, scope, and evidence for task <task-id>.
|
|
|
39
39
|
EXPECTED OUTCOME: Findings ordered by severity with file and line references, or an explicit no-issue result with residual risk.
|
|
40
40
|
REQUIRED TOOLS: Read-only diff inspection, tool readiness checks, verification command execution, plan/memory reading.
|
|
41
41
|
MUST DO: Read the project-root `MEMORY.md` before doing assigned work. Check tool readiness and install-attempt evidence. Check dry-run or check evidence when external state could change. Check both module evidence and surface evidence. Preserve proper nouns exactly. Use the OS locale for user-visible responses and generated or updated documentation, plans, and memory artifacts.
|
|
42
|
-
MUST NOT DO: Edit files. Ask the user. Approve skipped LSP/typecheck/lint without install evidence. Approve missing evidence or open-ended verification loops.
|
|
42
|
+
MUST NOT DO: Edit files. Ask the user. Approve skipped LSP/typecheck/lint without install evidence. Approve weakened, skipped, or deleted tests or verification commands without a recorded plan reason. Approve missing evidence or open-ended verification loops.
|
|
43
43
|
CONTEXT: docs/plan/<topic>.md, MEMORY.md, relevant docs/memory notes, changed files.
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -23,7 +23,7 @@ If the user asks a side question, status question, or narrow clarification while
|
|
|
23
23
|
|
|
24
24
|
## Re-entry and Required References
|
|
25
25
|
|
|
26
|
-
At the start and after compaction, restart, delegation, or handoff, restore context in this order: optional root `MEMORY.md` → `cairn-work` → the active plan → current-task references → model guidance recorded by the plan. Confirm that the plan task, persisted current task, and worker assignment agree before acting. Missing `MEMORY.md` is not an inconsistency. If state, skill, plan, assignment, or another required reference is missing, unreadable, or inconsistent, do not edit, delegate, record completion evidence, or complete work; report a blocker to the orchestrator. Do not introduce read receipts as proof of model attention: current state, readable references, and fresh tool-bound evidence are the authority.
|
|
26
|
+
At the start and after compaction, restart, delegation, or handoff, restore context in this order: optional root `MEMORY.md` → `cairn-work` → the active plan → current-task references → model guidance recorded by the plan. Confirm that the plan task, persisted current task, and worker assignment agree before acting. Missing `MEMORY.md` is not an inconsistency. If state, skill, plan, assignment, or another required reference is missing, unreadable, or inconsistent, do not edit, delegate, record completion evidence, or complete work; report a blocker to the orchestrator. Do not introduce read receipts as proof of model attention: current state, readable references, and fresh tool-bound evidence are the authority. Delegation means the delegated agent restores on its own entry; the orchestrator does not re-read this chain after dispatching or collecting a subagent, and within one unbroken context it rereads only references that changed, which keeps the cached prompt prefix stable.
|
|
27
27
|
|
|
28
28
|
When subagent tools are available, each agent may recursively delegate bounded sub-tasks to subagents. Every child subagent reads project-root `MEMORY.md` when present and continues when absent, keeps the assigned scope, and preserves others' edits.
|
|
29
29
|
|
|
@@ -34,7 +34,9 @@ Resolve Cairn's read-only runtime from `references/cairn-runtime.json` next to t
|
|
|
34
34
|
Use the installed CLI for durable state. Important forms are:
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
+
node "<pluginRoot>/scripts/cairn.mjs" goal task status --root "<repoRoot>"
|
|
37
38
|
node "<pluginRoot>/scripts/cairn.mjs" goal status --root "<repoRoot>"
|
|
39
|
+
node "<pluginRoot>/scripts/cairn.mjs" goal claim --quiet --root "<repoRoot>" --session "<session-id-shown-by-hook>"
|
|
38
40
|
node "<pluginRoot>/scripts/cairn.mjs" goal replan --quiet --root "<repoRoot>" --tasks '[{"id":"<task-id>","title":"<task-title>","requiredEvidence":["moduleAcceptance","surfaceIntegration"]}]'
|
|
39
41
|
node "<pluginRoot>/scripts/cairn.mjs" goal assign --quiet --root "<repoRoot>" --task "<task-id>" --agent "<agent-id>"
|
|
40
42
|
node "<pluginRoot>/scripts/cairn.mjs" goal verify --quiet --root "<repoRoot>" --task "<task-id>" --kind moduleAcceptance --watch "<path|path>" -- <tool> <arg> ...
|
|
@@ -65,6 +67,18 @@ Use this verification ladder:
|
|
|
65
67
|
5. Run normal `npm pack --dry-run` by default. Content-producing or unknown lifecycle scripts must never be skipped with `--ignore-scripts`; their generated contents are part of the package contract.
|
|
66
68
|
6. Only when lifecycle scripts are absent or proven content-neutral, the full check is still fresh, and the dry-run is only checking the file list may package verification use `npm pack --dry-run --ignore-scripts`.
|
|
67
69
|
|
|
70
|
+
## Verification Integrity
|
|
71
|
+
|
|
72
|
+
- The implementer must not be able to change the judge. Never edit, weaken, skip, or delete existing tests, fixtures, snapshots, or recorded verification commands to make a gate pass. When a contract genuinely changes, record the reason in `docs/plan/<topic>.md` before that edit; review rejects any judge change without a recorded reason.
|
|
73
|
+
- Prefer executable checks (test, lint, script, hook) for machine-checkable rules; keep prose only for rules that need judgment.
|
|
74
|
+
|
|
75
|
+
## Defect Investigation
|
|
76
|
+
|
|
77
|
+
- Reproduce a reported defect with repository tools before fixing it; do not wait for more detail when a local reproduction is possible.
|
|
78
|
+
- For unclear or non-reproducible failures, test one hypothesis at a time and close it with a measurement before changing code. Never fix on speculation.
|
|
79
|
+
- Answer feasibility questions with a bounded experiment or measurement instead of an unverified "not possible". Stop and ask only when the experiment would bypass a security policy or mutate external state without approval.
|
|
80
|
+
- After fixing a defect, search the repository for other implementations of the same rule or pattern. Fix them within the assigned scope or record them as follow-up in the plan.
|
|
81
|
+
|
|
68
82
|
Any relevant mutation after a gate makes that result stale evidence. Rerun the affected focused gate and, when integration could change, the full gate. Use `--quiet` for successful Cairn state mutations so full state JSON does not consume conversation tokens. `goal status` is read-only and always returns state, including when `--quiet` is supplied.
|
|
69
83
|
|
|
70
84
|
## Reasoning Effort Routing
|
|
@@ -81,7 +95,7 @@ Any relevant mutation after a gate makes that result stale evidence. Rerun the a
|
|
|
81
95
|
1. Read project-root `MEMORY.md` when present; continue without repository memory when absent.
|
|
82
96
|
2. Read `PLAN.md`, the selected `docs/plan/<topic>.md`, and relevant memory notes.
|
|
83
97
|
3. Read the `cairn://docs/model-guidance/*.md` inputs recorded in the plan through the installed runtime.
|
|
84
|
-
4. Read
|
|
98
|
+
4. Read the current task through `cairn goal task status --root "<repoRoot>"`; the hook capsule already carries the roadmap. Read full `cairn goal status` only when receipts or the complete roadmap are needed, because its output grows with every receipt. Select only the active goal's current task. If a new implementation request or steer arrives while a goal is unfinished, add the work through `goal replan` instead of cancelling the goal, then keep advancing to goal final review. If implementation was requested but no goal exists, create one from the decision-complete plan before editing.
|
|
85
99
|
5. Confirm that required LSP, typecheck, lint, dry-run, and verification tools are available from the plan's tool readiness section.
|
|
86
100
|
6. If a required tool is missing, do not install it implicitly. Obtain explicit user approval, then run the installed runtime's `toolcheck --install --yes --root "<repoRoot>"` only for pinned/supported installers or run the approved repository-native command. Otherwise record a blocker.
|
|
87
101
|
7. Confirm the selected Light Path or Heavy Path in the plan. If the plan lacks complexity triage, stop and update the plan before mutating files.
|
|
@@ -110,7 +124,7 @@ Any relevant mutation after a gate makes that result stale evidence. Rerun the a
|
|
|
110
124
|
TASK: Implement task <task-id> for <topic>.
|
|
111
125
|
EXPECTED OUTCOME: Minimal code or documentation change satisfying the task contract.
|
|
112
126
|
REQUIRED TOOLS: Scoped file editing, tool readiness checks, existing test/build commands, surface QA commands.
|
|
113
|
-
MUST DO: Read the project-root `MEMORY.md` before doing assigned work and follow its policy. Stay within the listed file scope and ownership. Assume you are not alone in the codebase; do not revert others' edits. Treat the user-called/main agent as the orchestrator; delegate actual implementation edits to `worker` whenever subagent tools are available, regardless of Light Path or Heavy Path. 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, relay observable events such as assignment, waiting, and final completion. Require delegated subagents to provide a final report before leaving; after capturing final report and evidence, close or release the completed subagent, then review the final report and evidence before marking the work complete. If subagent tools are unavailable, the main agent takes over implementation directly and records that takeover in evidence. When subagent tools are available, recursively delegate only bounded sub-tasks and pass these same constraints to every child subagent. Obtain explicit user approval before installing missing tools. Run dry-run or check mode before external-state mutation when available. Provide module evidence, surface evidence, and a structured handoff scoped to the assigned goal/task. Use the OS locale for user-visible responses and generated or updated documentation, plans, and memory artifacts.
|
|
127
|
+
MUST DO: Read the project-root `MEMORY.md` before doing assigned work and follow its policy. Stay within the listed file scope and ownership. Assume you are not alone in the codebase; do not revert others' edits. Treat the user-called/main agent as the orchestrator; delegate actual implementation edits to `worker` whenever subagent tools are available, regardless of Light Path or Heavy Path. 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, relay observable events such as assignment, waiting, and final completion. Require delegated subagents to provide a final report before leaving; after capturing final report and evidence, close or release the completed subagent, then review the final report and evidence before marking the work complete. If subagent tools are unavailable, the main agent takes over implementation directly and records that takeover in evidence. When subagent tools are available, recursively delegate only bounded sub-tasks and pass these same constraints to every child subagent. Obtain explicit user approval before installing missing tools. Run dry-run or check mode before external-state mutation when available. Provide module evidence, surface evidence, and a structured handoff scoped to the assigned goal/task. Never edit, weaken, skip, or delete existing tests, fixtures, or verification commands to pass a gate without a reason recorded in the plan. After a defect fix, search for other implementations of the same rule or pattern and report them. Use the OS locale for user-visible responses and generated or updated documentation, plans, and memory artifacts.
|
|
114
128
|
MUST NOT DO: Ask the user. Skip LSP/typecheck/lint because a tool is missing without attempting installation. Refactor unrelated code. Continue an open-ended verification loop. Treat a side question as permission to abandon the active task. Delegate vague or unbounded work. Claim completion without evidence. Modify files outside the assigned ownership.
|
|
115
129
|
CONTEXT: docs/plan/<topic>.md task, relevant docs/memory notes, applied model guidance, exact files, verification commands.
|
|
116
130
|
```
|
|
@@ -156,5 +170,7 @@ Run the closest available dry-run or check mode before commands that mutate exte
|
|
|
156
170
|
- Dry-run or check evidence exists when the task could mutate external state, or the plan records why no dry-run exists.
|
|
157
171
|
- The required Light Path or Heavy Path flow was followed.
|
|
158
172
|
- The model guidance recorded in the plan was applied.
|
|
173
|
+
- No existing test, fixture, or verification command was weakened without a recorded plan reason.
|
|
174
|
+
- Defect fixes record the same-rule sweep result.
|
|
159
175
|
- Evidence was recorded in `docs/plan/<topic>.md`.
|
|
160
176
|
- `PLAN.md` status was updated only after evidence existed.
|