cairn-ai 0.2.3 → 0.2.4

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Token-efficient agent harness with tool readiness for Codex, Claude Code, and Antigravity.",
5
5
  "author": {
6
6
  "name": "WkNam"
@@ -21,18 +21,13 @@
21
21
  "Tool Readiness"
22
22
  ],
23
23
  "defaultPrompt": [
24
- "Use Cairn to create MEMORY.md and PLAN.md for this repository.",
25
- "Every agent must read the project-root MEMORY.md before planning, implementation, review, or delegation. Keep MEMORY.md and PLAN.md as short indexes; open only current-task details.",
26
- "Resolve Cairn runtime resources only through the installed locator, keep repoRoot separate, and run toolcheck before implementation planning. Tool installation requires explicit approval.",
27
- "Run cairn-plan before non-trivial changes. Understand the whole work, split it into executable tasks/sub-tasks, record Light/Heavy Path triage, and finalize a decision-complete implementation plan before editing.",
28
- "The user-called/main agent is the orchestrator. Delegate bounded implementation edits to worker subagents when allowed; otherwise the main agent implements and records takeover evidence.",
29
- "With a progress-reporting channel, subagents report start, direction, periodic progress, and finish; relay those events immediately. Otherwise relay assignment, waiting, and final completion.",
30
- "Delegated agents read MEMORY.md, preserve others' edits, return a final report, and may recursively delegate only bounded work. Capture evidence, close/release the agent, then review its report before completion.",
31
- "Implementation/continue requests authorize an active Cairn goal even without the word goal. After MEMORY.md, write an initial plan with triage-plan active; before exploration call update_plan, then create_goal, then start/attach the repository goal. Triage finalizes the repository and UI plans before implementation. Keep roadmap/current-task recovery context; consultation, explanation, and plan-only requests stay goal-free.",
32
- "Use a test contract first: spend reasoning on requirements, invariants, boundaries, and failure modes; design executable tests before implementation; make the minimum implementation that passes. Treat tool exit codes and machine summaries as authoritative, and expand context only for failing tests.",
33
- "Missing, failed, skipped, stale, or placeholder evidence never completes work. Advance a UI step only after the matching repository task passes bound evidence; workers report only assigned tasks; active is not complete.",
34
- "Use goal verify -- <argv>, quiet state mutations, and concise success summaries. Run focused tests and one final full check. Inspect package lifecycle scripts before packaging: run normal npm pack --dry-run by default; never skip content-producing or unknown scripts with --ignore-scripts, and use it only for absent or proven content-neutral scripts while evidence is fresh.",
35
- "If blocked, pause/block/cancel explicitly. Answer side questions briefly, then resume the current task unless asked to pause, stop, or switch."
24
+ "Use Cairn's repository-local MEMORY.md, PLAN.md, and persisted goal state as the recovery spine; every agent reads root MEMORY.md before planning, implementation, review, or delegation.",
25
+ "For non-trivial work, load the phase skill (cairn-plan, cairn-work, or cairn-review), then restore the active plan and current-task references before acting.",
26
+ "After compaction, restart, handoff, or delegation, restore MEMORY.md → phase skill → active plan → current task references; do not rely on conversation recall.",
27
+ "If state, skill, plan, or a required reference is missing, unreadable, or inconsistent, fail closed: do not edit, delegate, or complete work; report or block the discrepancy.",
28
+ "Missing, failed, skipped, stale, or placeholder evidence never completes work; advance UI state only after bound repository evidence passes.",
29
+ "Before external-state changes, run dry-run/check and bind the resulting evidence to the current task.",
30
+ "Answer side questions briefly, then resume the current task unless asked to pause, stop, or switch."
36
31
  ]
37
32
  }
38
33
  }
package/README.de.md CHANGED
@@ -108,6 +108,8 @@ cairn toolcheck
108
108
 
109
109
  Der custom marketplace lifecycle hält source und versioned runtime getrennt. Staged validation geht jedem commit voraus, das ownership manifest bindet alle managed digests, und ein Fehler führt zu reverse-order rollback. Modified oder unmanaged artifacts werden preserved und als conflict gemeldet. Cairn ändert nur eigene TOML sections und erzwingt keine öffentlichen feature/agent settings.
110
110
 
111
+ Clean uninstall entfernt den Cairn marketplace cache root nur, wenn alle managed scaffold directories leer sind; unmanaged Inhalte bleiben erhalten. Source repository und repository state, das globale `cairn-ai` package, package-manager caches sowie legacy backups/settings außerhalb des aktuellen ownership manifest werden nicht automatisch gelöscht.
112
+
111
113
  Codex verwendet `skills/` und `commands/`. Claude Code verwendet gespiegelte commands und agent definitions unter `.claude/`. Antigravity verwendet `.agents/workflows` und globale skills mirrors.
112
114
 
113
115
  ## Antigravity Compatibility
package/README.es.md CHANGED
@@ -108,6 +108,8 @@ cairn toolcheck
108
108
 
109
109
  El custom marketplace lifecycle mantiene separados source y versioned runtime. Staged validation precede cada commit, el ownership manifest vincula todos los managed digests y un fallo aplica reverse-order rollback. Los artifacts modified o unmanaged se preservan y se reportan como conflict. Cairn modifica solo sus propias TOML sections y no fuerza feature/agent settings públicos.
110
110
 
111
+ Clean uninstall elimina el Cairn marketplace cache root solo cuando todos los managed scaffold directories están vacíos; cualquier contenido unmanaged se conserva. No elimina el source repository ni su state, el paquete global `cairn-ai`, los package-manager caches ni legacy backups/settings fuera del ownership manifest actual.
112
+
111
113
  Codex usa `skills/` y `commands/`. Claude Code usa comandos reflejados y definiciones de agentes bajo `.claude/`. Antigravity usa `.agents/workflows` y mirrors globales de skills.
112
114
 
113
115
  ## Antigravity Compatibility
package/README.fr.md CHANGED
@@ -108,6 +108,8 @@ cairn toolcheck
108
108
 
109
109
  Le custom marketplace lifecycle sépare source et versioned runtime. Staged validation précède chaque commit, le ownership manifest lie tous les managed digests et un échec déclenche un reverse-order rollback. Les artifacts modified ou unmanaged sont preserved et signalés comme conflict. Cairn ne modifie que ses propres TOML sections et ne force aucun feature/agent setting public.
110
110
 
111
+ Clean uninstall supprime le Cairn marketplace cache root uniquement lorsque tous les managed scaffold directories sont vides ; tout contenu unmanaged est conservé. Il ne supprime ni le source repository et son state, ni le package global `cairn-ai`, les package-manager caches ou les legacy backups/settings hors du ownership manifest courant.
112
+
111
113
  Codex utilise `skills/` et `commands/`. Claude Code utilise les commandes miroir et définitions d'agents sous `.claude/`. Antigravity utilise `.agents/workflows` et les global skills mirrors.
112
114
 
113
115
  ## Antigravity Compatibility
package/README.ja.md CHANGED
@@ -108,6 +108,8 @@ cairn toolcheck
108
108
 
109
109
  custom marketplace lifecycle は source と versioned runtime を分離します。各 commit 前に staged validation を行い、ownership manifest が全 managed digest を結び、失敗時は reverse-order rollback します。modified または unmanaged artifacts は preserve して conflict として報告します。Cairn-owned TOML sections だけを変更し、public feature/agent settings は強制しません。
110
110
 
111
+ Clean uninstall は、すべての managed scaffold directory が空の場合に限り Cairn marketplace cache root を削除し、unmanaged content は保持します。source repository と repository state、global `cairn-ai` package、package-manager cache、現在の ownership manifest 外の legacy backup/settings は自動削除しません。
112
+
111
113
  Codex は `skills/` と `commands/` を使います。Claude Code は `.claude/` 配下の mirrored commands と agent definitions を使います。Antigravity は `.agents/workflows` と global skills mirrors を使います。
112
114
 
113
115
  ## Antigravity Compatibility
package/README.ko.md CHANGED
@@ -103,7 +103,7 @@ cairn toolcheck
103
103
  - `cairn install`: custom marketplace source, versioned Codex runtime cache, Cairn-owned config section, Claude Code mirror, 현재 Antigravity IDE/CLI skill 표면을 transaction으로 설치합니다.
104
104
  - `cairn upgrade`: ownership manifest의 installed digest와 현재 값이 같은 항목만 교체하며, staged validation과 역순 rollback을 적용합니다.
105
105
  - `cairn doctor`: ownership manifest, managed digest, 유효 Codex feature, 실제 plugin installed/enabled/version, mirror/runtime locator를 수정 없이 진단합니다.
106
- - `cairn uninstall`: 수정되지 않은 ownership manifest 항목만 제거·복원하고, modified/unmanaged target은 conflict로 보존하며 실패 시 rollback합니다.
106
+ - `cairn uninstall`: 수정되지 않은 ownership manifest 항목만 제거·복원한 뒤 빈 managed cache scaffold를 정리합니다. modified/unmanaged target과 그 상위 디렉터리는 보존하고 commit 전 실패는 rollback합니다.
107
107
  - `cairn toolcheck`: 저장소 스택과 필요한 LSP·검증 도구를 확인하며, 승인된 지원 installer만 실행합니다.
108
108
  - `cairn goal ...`: 저장소의 영속 goal을 시작·조회·일시정지·재개·차단·취소·완료합니다. 기본 tool-bound 정책은 `goal verify -- <argv>`가 명령을 직접 실행해 성공 증거를 기록합니다. `goal receipt`는 기존 선언 증거를 가져오는 호환 명령입니다.
109
109
  - `cairn-memory`: 도메인 지식을 탐색하고 `MEMORY.md`를 갱신합니다.
@@ -117,6 +117,8 @@ cairn toolcheck
117
117
 
118
118
  `install`과 `upgrade`는 custom marketplace lifecycle을 유지하며 `codex plugin add`를 호출하지 않습니다. candidate를 staging에서 검증한 뒤 commit하고, 모든 managed destination과 digest를 ownership manifest에 기록하며 실패 시 완료된 phase를 역순 rollback합니다. 수정되지 않은 지원 legacy 설치만 release-integrity 검증 뒤 인수하고, 수정되거나 알 수 없는 artifact는 보존한 채 거부합니다. Cairn-owned TOML section만 편집하고 public feature/agent 설정은 강제하지 않습니다. lifecycle command는 교체 대상 cache copy가 아니라 게시/전역 package에서 실행해야 합니다.
119
119
 
120
+ Clean uninstall은 모든 managed scaffold가 비었을 때만 recursive 삭제 없이 빈 디렉터리 제거로 Cairn marketplace cache root를 정리합니다. 소스 저장소와 저장소의 `MEMORY.md`/`PLAN.md`/`.cairn` 상태, 전역 `cairn-ai` package, package-manager download cache, legacy backup, 현재 ownership manifest 밖의 legacy shared setting은 지우지 않으며 별도의 명시적 정리 판단이 필요합니다.
121
+
120
122
  Codex는 `skills/`와 `commands/`를 사용합니다. Claude Code는 `.claude/` 아래의 mirror command와 agent definition을 사용합니다. Antigravity는 `.agents/workflows`와 global skills mirror를 사용합니다.
121
123
 
122
124
  ## Antigravity Compatibility
package/README.md CHANGED
@@ -111,7 +111,7 @@ cairn toolcheck
111
111
  - `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.
112
112
  - `cairn upgrade`: replaces only ownership-manifest entries whose installed digests still match, with staged validation and reverse-order rollback on failure.
113
113
  - `cairn doctor`: validates the ownership manifest, managed digests, effective Codex features, actual installed/enabled/version plugin state, and mirror/runtime locators without repairing them.
114
- - `cairn uninstall`: removes or restores only ownership-manifest entries that remain unmodified; modified or unmanaged targets are preserved as conflicts and failures roll back.
114
+ - `cairn uninstall`: removes or restores only ownership-manifest entries that remain unmodified, then prunes empty managed cache scaffolds; modified or unmanaged targets and their parent directories are preserved, and pre-commit failures roll back.
115
115
  - `cairn toolcheck`: detects repository stacks and checks required LSP and verification tools; installation runs only for an approved supported installer.
116
116
  - `cairn goal ...`: starts, inspects, pauses, resumes, blocks, cancels, and completes a persisted repository goal; the default tool-bound policy records successful evidence by executing `goal verify -- <argv>`. The legacy `goal receipt` command imports declared evidence only.
117
117
  - `cairn-memory`: explores domain knowledge and updates `MEMORY.md`.
@@ -125,6 +125,8 @@ For token-efficient execution, Cairn puts reasoning into a focused executable te
125
125
 
126
126
  Install and upgrade keep the custom marketplace lifecycle; they do not invoke `codex plugin add`. Candidates are staged and validated before commit, every managed destination and digest is recorded in an ownership manifest, and committed phases roll back in reverse order on failure. An exact unmodified supported legacy installation can be adopted after release-integrity validation; modified or unknown artifacts are preserved and rejected. Cairn edits only its own TOML sections and never forces public feature or agent settings. The source plugin manifest stays validator-friendly; only the installed cache copy gets a `hooks` field. Lifecycle commands must run from the published/global package, not from the cached copy that they replace.
127
127
 
128
+ 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 or repository `MEMORY.md`/`PLAN.md`/`.cairn` 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.
129
+
128
130
  Codex uses `skills/` and `commands/`. Claude Code uses mirrored commands and agent definitions under `.claude/`. Antigravity uses `.agents/workflows` and global skills mirrors.
129
131
 
130
132
  ## Antigravity Compatibility
package/README.pt.md CHANGED
@@ -108,6 +108,8 @@ cairn toolcheck
108
108
 
109
109
  O custom marketplace lifecycle separa source e versioned runtime. Staged validation precede cada commit, o ownership manifest vincula todos os managed digests e uma falha aciona reverse-order rollback. Artifacts modified ou unmanaged sao preserved e relatados como conflict. O Cairn altera apenas suas TOML sections e nao forca feature/agent settings publicos.
110
110
 
111
+ Clean uninstall remove o Cairn marketplace cache root somente quando todos os managed scaffold directories estao vazios; qualquer conteudo unmanaged e preservado. O source repository e seu state, o package global `cairn-ai`, package-manager caches e legacy backups/settings fora do ownership manifest atual nao sao removidos automaticamente.
112
+
111
113
  Codex usa `skills/` e `commands/`. Claude Code usa comandos espelhados e agent definitions em `.claude/`. Antigravity usa `.agents/workflows` e global skills mirrors.
112
114
 
113
115
  ## Antigravity Compatibility
package/README.zh.md CHANGED
@@ -108,6 +108,8 @@ cairn toolcheck
108
108
 
109
109
  custom marketplace lifecycle 将 source 与 versioned runtime 分离。每次 commit 前执行 staged validation,ownership manifest 绑定全部 managed digests,失败时执行 reverse-order rollback。modified 或 unmanaged artifacts 会 preserve 并报告 conflict。Cairn 只修改自身 TOML sections,不强制 public feature/agent settings。
110
110
 
111
+ Clean uninstall 仅在所有 managed scaffold directory 均为空时删除 Cairn marketplace cache root,并保留任何 unmanaged content。它不会自动删除 source repository 及其 state、全局 `cairn-ai` package、package-manager cache,或当前 ownership manifest 之外的 legacy backup/settings。
112
+
111
113
  Codex 使用 `skills/` 和 `commands/`。Claude Code 使用 `.claude/` 下的 mirrored commands 和 agent definitions。Antigravity 使用 `.agents/workflows` 和 global skills mirrors。
112
114
 
113
115
  ## Antigravity Compatibility
@@ -10,6 +10,8 @@ Handle read-only discovery and verification tasks.
10
10
 
11
11
  Before doing any assigned task, read the project-root `MEMORY.md`. Treat it as the domain knowledge and repository policy index, then read only the relevant `docs/memory/*.md` notes needed for the task.
12
12
 
13
+ At the start and after compaction, restart, or handoff, restore context in this order: root `MEMORY.md` → the phase skill named by the assignment → the active plan → current-task references → model guidance recorded by that plan. The assignment must identify the plan and current task whenever persisted work is active. If a required reference is missing, unreadable, or inconsistent with the assignment or goal state, report a blocker and do not perform the assigned discovery or delegate it.
14
+
13
15
  When subagent tools are available, you may recursively delegate bounded read-only sub-tasks to subagents. Every child subagent must read the project-root `MEMORY.md`, keep the assigned scope, and preserve others' edits.
14
16
 
15
17
  Use for:
package/agents/worker.md CHANGED
@@ -10,6 +10,8 @@ Handle narrow, focused tasks.
10
10
 
11
11
  Before doing any assigned task, read the project-root `MEMORY.md`. Treat it as the domain knowledge and repository policy index, then read only the relevant `docs/memory/*.md` notes needed for the task.
12
12
 
13
+ At the start and after compaction, restart, or handoff, restore context in this order: root `MEMORY.md` → the phase skill named by the assignment → the active plan → current-task references → model guidance recorded by that plan. The assignment must identify the plan and current task whenever persisted work is active. If a required reference is missing, unreadable, or inconsistent with the assignment or goal state, report a blocker and do not edit, delegate, or claim completion.
14
+
13
15
  When subagent tools are available, you may recursively delegate bounded sub-tasks to subagents. Every child subagent must read the project-root `MEMORY.md`, keep the assigned scope, and preserve others' edits.
14
16
 
15
17
  Use for:
@@ -17,4 +17,6 @@ Managed items:
17
17
  - Claude Code `cairn-*` commands and agents mirror files.
18
18
  - Antigravity IDE/CLI `cairn-*` skills and workflows mirror files.
19
19
 
20
- The source work folder is not deleted. The custom marketplace root is removed last, after external mirrors and config have committed successfully.
20
+ After the transaction commits, Cairn removes empty managed scaffold directories bottom-up and removes the custom marketplace root only after releasing its own lifecycle lock. A directory containing any unmanaged child is preserved; pruning never uses recursive deletion.
21
+
22
+ The source work folder and repository `MEMORY.md`, `PLAN.md`, `docs/`, and `.cairn/state.json` are not deleted. The globally installed `cairn-ai` package, package-manager download caches, legacy backup files, and legacy shared settings not recorded by the current ownership manifest are also outside this command's scope and require a separate, explicit cleanup decision.
@@ -14,6 +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: root `MEMORY.md` → current phase skill → active plan → current-task references → model guidance recorded by the plan. If persisted state or an assignment requires a reference that is missing, unreadable, or inconsistent, fail closed: do not edit, delegate, approve, or complete; report or record a blocker.
17
18
  - Keep root `MEMORY.md` and `PLAN.md` as indexes only.
18
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/...`.
19
20
  - Preserve proper nouns, file names, variable names, service names, alert names, MCP tool names, and agent names exactly as written.
@@ -36,6 +37,15 @@ The goal is not to make prompts long by model. The goal is to stabilize the same
36
37
  - Limit each task to two verification passes by default. After two failed passes, record the blocker or split it into sub-tasks instead of continuing a repeated loop.
37
38
  - User-visible responses and generated or updated documentation, plans, and memory artifacts must follow the OS locale unless the user asks for another language.
38
39
 
40
+ ## Prompt Cache Shape
41
+
42
+ - Keep `.codex-plugin/plugin.json` `defaultPrompt` as a small, stable policy and recovery kernel. Put detailed bootstrap, delegation, test, package, and verification procedures in the phase skills and this installed guidance.
43
+ - Put static instructions before dynamic goal data. Hook capsules must be deterministic for the same locale, event, and persisted state; prompt text and turn IDs must not change their content.
44
+ - Idle capsules reference root `MEMORY.md` and `cairn-plan`; active-work capsules reference the active plan, current task, and `cairn-work`; all-tasks-complete capsules reference `cairn-review`.
45
+ - A foreign session receives only a generic ownership-conflict capsule on session/prompt events. It must not expose goal, plan, or task details, and it must not make stop hooks block work owned by another session.
46
+ - Character budgets are regression proxies for cache-friendly prompt shape, not measurements of provider cache hits or cost. Cairn does not require provider cache keys, breakpoints, or live API telemetry.
47
+ - Do not add read receipts that claim model attention. Restore references on every re-entry and use readable state plus fresh bound evidence as the enforceable contract.
48
+
39
49
  ## Delegation Defaults
40
50
 
41
51
  - `explorer`: use for read-only codebase discovery, impact analysis, pattern searches, and read-only verification when available.
@@ -17,6 +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 root `MEMORY.md` → current phase skill → active plan → current-task references → recorded model guidance. If persisted state or the assignment requires a missing, unreadable, or inconsistent reference, fail closed and report a blocker before editing, delegating, approving, or completing.
20
21
  - Do not start implementation without a plan.
21
22
  - Do not stop before local implementation solely because subagent tools are unavailable; the main agent takes over implementation directly and records that takeover in evidence.
22
23
  - Keep work units small. Split large changes into smaller plan tasks.
@@ -26,6 +27,7 @@ Codex-family models are strong at small implementation tasks, explicit tool use,
26
27
  - Do not skip LSP, typecheck, lint, dry-run, or verification because a tool is missing. Try repository-local installation or record the failed install attempt as a blocker.
27
28
  - Do not continue an open-ended verification loop. Use at most two verification passes per task unless the plan records a risk-based exception.
28
29
  - Write user-visible responses and generated or updated documentation, plans, and memory artifacts in the OS locale unless the user asks for another language.
30
+ - Preserve the stable prompt prefix: keep detailed workflow in phase skills/model guidance, and do not vary a hook capsule with prompt text or turn ID when persisted state is unchanged.
29
31
 
30
32
  ## Path Guidance
31
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn-ai",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Token-efficient agent harness with tool readiness for Codex, Claude Code, and Antigravity",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -10,6 +10,9 @@ import { assertNoSymlinkComponents, safeMkdir, safeWriteFile, withStateLock } fr
10
10
  export const GOAL_STATE_VERSION = 2;
11
11
  export const GOAL_STATUSES = new Set(["active", "paused", "blocked", "cancelled", "completed"]);
12
12
  export const TASK_STATUSES = new Set(["pending", "active", "blocked", "completed"]);
13
+ export const PLAN_ID_MAX_LENGTH = 128;
14
+ export const TASK_ID_MAX_LENGTH = 64;
15
+ export const RECOVERY_REFERENCE_MAX_LENGTH = 160;
13
16
 
14
17
  const terminalGoalStatuses = new Set(["cancelled", "completed"]);
15
18
  const evidencePolicies = new Set(["declared", "tool-bound"]);
@@ -48,7 +51,7 @@ export async function readGoalState({ root = process.cwd() } = {}) {
48
51
 
49
52
  export async function startGoal({ root = process.cwd(), goal, planId, tasks, completionCriteria = [], requiredEvidence = defaultGoalEvidence, ownerSessionId = null, evidencePolicy = "tool-bound" } = {}) {
50
53
  const normalizedGoal = requiredText(goal, "goal");
51
- const normalizedPlanId = requiredText(planId, "planId");
54
+ const normalizedPlanId = boundedRequiredText(planId, "planId", PLAN_ID_MAX_LENGTH);
52
55
  if (!Array.isArray(tasks) || tasks.length === 0) throw new Error("tasks must contain at least one task");
53
56
 
54
57
  return withStateLock(root, async () => {
@@ -60,6 +63,7 @@ export async function startGoal({ root = process.cwd(), goal, planId, tasks, com
60
63
  const now = timestamp();
61
64
  const goalId = `goal-${randomUUID()}`;
62
65
  const normalizedTasks = tasks.map((task, index) => normalizeTask(task, index, index === 0 ? "active" : "pending"));
66
+ for (const task of normalizedTasks) assertRecoveryReferenceBudget(normalizedPlanId, task.id);
63
67
  const state = {
64
68
  schemaVersion: GOAL_STATE_VERSION,
65
69
  revision: 1,
@@ -206,7 +210,9 @@ async function recordEvidence({
206
210
  || state.goal.planId !== expectedIdentity.planId
207
211
  )) throw new Error("Goal identity changed during verification; evidence was not recorded");
208
212
  if (expectedIdentity && normalizedScope === "task" && (
209
- task?.id !== expectedIdentity.taskId || task?.status !== expectedIdentity.taskStatus
213
+ task?.id !== expectedIdentity.taskId
214
+ || task?.status !== expectedIdentity.taskStatus
215
+ || task?.assignedAgentId !== expectedIdentity.taskAgentId
210
216
  )) throw new Error("Verification task changed during verification; evidence was not recorded");
211
217
  if (expectedFingerprint && workspaceFingerprint(root, watchPaths) !== expectedFingerprint) {
212
218
  throw new Error("Watched workspace changed during verification; evidence was not recorded");
@@ -252,12 +258,15 @@ export async function verifyAndRecord({
252
258
  assertGoalMutable(initialState);
253
259
  const normalizedTaskId = normalizedScope === "task" ? requiredText(taskId, "taskId") : undefined;
254
260
  const initialTask = normalizedScope === "task" ? taskById(initialState, normalizedTaskId) : null;
255
- if (initialTask && initialTask.status !== "active") throw new Error(`Verification task must be active: ${initialTask.id}`);
261
+ if (initialTask && !["active", "completed"].includes(initialTask.status)) {
262
+ throw new Error(`Verification task must be active or completed: ${initialTask.id}`);
263
+ }
256
264
  const identity = {
257
265
  goalId: initialState.goal.id,
258
266
  planId: initialState.goal.planId,
259
267
  taskId: normalizedTaskId,
260
268
  taskStatus: initialTask?.status,
269
+ taskAgentId: initialTask?.assignedAgentId,
261
270
  };
262
271
  const initialFingerprint = workspaceFingerprint(root, normalizedWatchPaths);
263
272
  const result = runner(normalizedArgv[0], normalizedArgv.slice(1), {
@@ -283,8 +292,11 @@ export async function verifyAndRecord({
283
292
  if (!postState || postState.goal.id !== identity.goalId || postState.goal.planId !== identity.planId) {
284
293
  throw new Error("Goal identity changed during verification; evidence was not recorded");
285
294
  }
286
- if (normalizedScope === "task" && taskById(postState, normalizedTaskId).status !== identity.taskStatus) {
287
- throw new Error("Verification task is no longer active; evidence was not recorded");
295
+ if (normalizedScope === "task") {
296
+ const postTask = taskById(postState, normalizedTaskId);
297
+ if (postTask.status !== identity.taskStatus || postTask.assignedAgentId !== identity.taskAgentId) {
298
+ throw new Error("Verification task changed during verification; evidence was not recorded");
299
+ }
288
300
  }
289
301
  const command = normalizedArgv.map((value) => JSON.stringify(value)).join(" ");
290
302
  const state = await recordEvidence({
@@ -457,7 +469,7 @@ async function mutateGoal(root, mutate) {
457
469
  function normalizeTask(task, index, defaultStatus) {
458
470
  const source = typeof task === "string" ? { title: task } : task;
459
471
  if (!source || typeof source !== "object") throw new Error(`tasks[${index}] must be a string or object`);
460
- const id = source.id === undefined ? `task-${index + 1}` : requiredText(source.id, `tasks[${index}].id`);
472
+ const id = source.id === undefined ? `task-${index + 1}` : boundedRequiredText(source.id, `tasks[${index}].id`, TASK_ID_MAX_LENGTH);
461
473
  const title = requiredText(source.title, `tasks[${index}].title`);
462
474
  return {
463
475
  id,
@@ -495,7 +507,7 @@ function validateState(value) {
495
507
  const goal = value.goal;
496
508
  requiredText(goal.id, "goal.id");
497
509
  requiredText(goal.title, "goal.title");
498
- requiredText(goal.planId, "goal.planId");
510
+ const planId = boundedRequiredText(goal.planId, "goal.planId", PLAN_ID_MAX_LENGTH);
499
511
  validGoalStatus(goal.status);
500
512
  validEvidencePolicy(goal.evidencePolicy);
501
513
  validTimestamp(goal.createdAt);
@@ -511,7 +523,8 @@ function validateState(value) {
511
523
  let activeTasks = 0;
512
524
  for (const task of value.tasks) {
513
525
  if (!task || typeof task !== "object") throw new Error("Each task must be an object");
514
- requiredText(task.id, "task.id");
526
+ const taskId = boundedRequiredText(task.id, "task.id", TASK_ID_MAX_LENGTH);
527
+ assertRecoveryReferenceBudget(planId, taskId);
515
528
  if (ids.has(task.id)) throw new Error(`Duplicate task id: ${task.id}`);
516
529
  ids.add(task.id);
517
530
  requiredText(task.title, "task.title");
@@ -622,6 +635,19 @@ function requiredText(value, label) {
622
635
  return value.trim();
623
636
  }
624
637
 
638
+ function boundedRequiredText(value, label, maxLength) {
639
+ const text = requiredText(value, label);
640
+ if (value !== text) throw new Error(`${label} must not have leading or trailing whitespace`);
641
+ if (text.length > maxLength) throw new Error(`${label} must be at most ${maxLength} characters`);
642
+ return text;
643
+ }
644
+
645
+ function assertRecoveryReferenceBudget(planId, taskId) {
646
+ if (planId.length + taskId.length > RECOVERY_REFERENCE_MAX_LENGTH) {
647
+ throw new Error(`planId and taskId must total at most ${RECOVERY_REFERENCE_MAX_LENGTH} characters for exact recovery context`);
648
+ }
649
+ }
650
+
625
651
  function validGoalStatus(value) {
626
652
  if (!GOAL_STATUSES.has(value)) throw new Error(`Invalid goal status: ${value}`);
627
653
  return value;
@@ -3,7 +3,7 @@ import { createHash, randomUUID } from "node:crypto";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { realpathSync } from "node:fs";
5
5
  import {
6
- copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, writeFile,
6
+ copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, rmdir, writeFile,
7
7
  } from "node:fs/promises";
8
8
  import { homedir, hostname, tmpdir } from "node:os";
9
9
  import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
@@ -47,7 +47,7 @@ if (isCliEntry()) {
47
47
  const command = process.argv[2] ?? "help";
48
48
  if (command === "install" || command === "upgrade") await withLifecycleLock(() => install(command));
49
49
  else if (command === "doctor") await doctor();
50
- else if (command === "uninstall") await withLifecycleLock(uninstall);
50
+ else if (command === "uninstall") await withLifecycleLock(uninstall, { afterRelease: pruneUninstallRoots });
51
51
  else help();
52
52
  } catch (error) {
53
53
  console.error(`Cairn lifecycle error: ${error.message}`);
@@ -107,7 +107,7 @@ async function install(mode) {
107
107
  console.log(`Codex versioned runtime: ${versionedPluginRoot}`);
108
108
  console.log(`Ownership manifest: ${ownershipPath}`);
109
109
  } catch (error) {
110
- await rollback(transaction);
110
+ if (transaction.state !== "committed") await rollback(transaction);
111
111
  throw error;
112
112
  }
113
113
  }
@@ -141,11 +141,13 @@ async function uninstall() {
141
141
  await removeManagedTarget({ id: "ownership", phase: "manifest", path: ownershipPath, type: "file" }, transaction);
142
142
  injectFailure("uninstall-after-ownership");
143
143
  await finishTransaction(transaction);
144
- console.log(t("uninstallComplete"));
144
+ injectFailure("uninstall-after-commit");
145
145
  } catch (error) {
146
- await rollback(transaction);
146
+ if (transaction.state !== "committed") await rollback(transaction);
147
147
  throw error;
148
148
  }
149
+ await pruneUninstallScaffolds();
150
+ console.log(t("uninstallComplete"));
149
151
  }
150
152
 
151
153
  async function doctor() {
@@ -328,8 +330,10 @@ async function withLifecycleLock(operation, {
328
330
  timeoutMs = Number(process.env.CAIRN_LIFECYCLE_LOCK_TIMEOUT_MS ?? DEFAULT_LIFECYCLE_LOCK_TIMEOUT_MS),
329
331
  retryMs = 25,
330
332
  malformedStaleMs = 30_000,
333
+ afterRelease,
331
334
  } = {}) {
332
335
  await mkdir(dirname(lifecycleLockPath), { recursive: true });
336
+ if (process.env.CAIRN_TEST_PRUNE_LOCK_PARENT_BEFORE_ACQUIRE === "1") await rmdir(dirname(lifecycleLockPath));
333
337
  const owner = { schemaVersion: 1, pid: process.pid, hostname: hostname(), nonce: randomUUID(), acquiredAt: new Date().toISOString() };
334
338
  const deadline = Date.now() + timeoutMs;
335
339
  while (true) {
@@ -342,21 +346,76 @@ async function withLifecycleLock(operation, {
342
346
  if (acquired?.nonce !== owner.nonce) throw new Error("Cairn lifecycle lock ownership changed after acquisition");
343
347
  break;
344
348
  } catch (error) {
349
+ if (error?.code === "ENOENT") {
350
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for Cairn lifecycle lock after ${timeoutMs}ms`);
351
+ await mkdir(dirname(lifecycleLockPath), { recursive: true });
352
+ continue;
353
+ }
345
354
  if (error?.code !== "EEXIST") throw error;
346
355
  if (await reclaimLifecycleLock(malformedStaleMs)) continue;
347
356
  if (Date.now() >= deadline) throw new Error(`Timed out waiting for Cairn lifecycle lock after ${timeoutMs}ms`);
348
357
  await delay(Math.min(retryMs, Math.max(1, deadline - Date.now())));
349
358
  }
350
359
  }
360
+ let operationSucceeded = false;
351
361
  try {
352
- return await operation();
362
+ const result = await operation();
363
+ operationSucceeded = true;
364
+ return result;
353
365
  } finally {
354
- try {
355
- const current = parseLifecycleLock(await readFile(lifecycleLockPath, "utf8"));
356
- if (current?.nonce === owner.nonce) await rm(lifecycleLockPath, { force: true });
357
- } catch (error) {
358
- if (error?.code !== "ENOENT") throw error;
366
+ if (process.env.CAIRN_TEST_REPLACE_LOCK_BEFORE_RELEASE === "1") {
367
+ await writeFile(lifecycleLockPath, `${JSON.stringify({ ...owner, nonce: randomUUID() })}\n`);
359
368
  }
369
+ const released = await releaseLifecycleLock(owner);
370
+ if (operationSucceeded && released && afterRelease) await afterRelease();
371
+ }
372
+ }
373
+
374
+ async function releaseLifecycleLock(owner) {
375
+ let current;
376
+ try {
377
+ current = parseLifecycleLock(await readFile(lifecycleLockPath, "utf8"));
378
+ } catch (error) {
379
+ if (error?.code === "ENOENT") return false;
380
+ throw error;
381
+ }
382
+ if (current?.nonce !== owner.nonce) return false;
383
+ const releasedPath = `${lifecycleLockPath}.released.${owner.nonce}`;
384
+ try {
385
+ await rename(lifecycleLockPath, releasedPath);
386
+ } catch (error) {
387
+ if (error?.code === "ENOENT") return false;
388
+ throw error;
389
+ }
390
+ const released = parseLifecycleLock(await readFile(releasedPath, "utf8"));
391
+ if (released?.nonce !== owner.nonce) {
392
+ if (!(await exists(lifecycleLockPath))) await rename(releasedPath, lifecycleLockPath);
393
+ return false;
394
+ }
395
+ await rm(releasedPath, { force: true });
396
+ return true;
397
+ }
398
+
399
+ async function pruneUninstallScaffolds() {
400
+ for (const path of [
401
+ join(marketplaceRoot, "plugins"),
402
+ join(marketplaceRoot, pluginName),
403
+ join(marketplaceRoot, ".agents", "plugins"),
404
+ join(marketplaceRoot, ".agents"),
405
+ transactionRoot,
406
+ ]) await removeEmptyDirectory(path);
407
+ }
408
+
409
+ async function pruneUninstallRoots() {
410
+ await removeEmptyDirectory(dirname(lifecycleLockPath));
411
+ await removeEmptyDirectory(marketplaceRoot);
412
+ }
413
+
414
+ async function removeEmptyDirectory(path) {
415
+ try {
416
+ await rmdir(path);
417
+ } catch (error) {
418
+ if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error?.code)) throw error;
360
419
  }
361
420
  }
362
421
 
@@ -462,7 +521,12 @@ async function rollback(transaction) {
462
521
 
463
522
  async function finishTransaction(transaction) {
464
523
  transaction.state = "committed";
465
- await writeTransactionJournal(transaction);
524
+ try {
525
+ await writeTransactionJournal(transaction);
526
+ } catch (error) {
527
+ transaction.state = "active";
528
+ throw error;
529
+ }
466
530
  await rm(transaction.root, { recursive: true, force: true });
467
531
  await rm(join(transactionRoot, transaction.id), { recursive: true, force: true });
468
532
  await rm(transactionPath, { force: true });
@@ -117,7 +117,7 @@ const planTemplateKo = `# PLAN
117
117
 
118
118
  const contextLimits = {
119
119
  goalTitle: 64,
120
- taskId: 32,
120
+ roadmapTaskId: 24,
121
121
  taskTitle: 40,
122
122
  roadmapWindow: 3,
123
123
  };
@@ -158,7 +158,9 @@ export async function runStateResult(event = "manual", {
158
158
 
159
159
  if (event === "session-start" || event === "user-prompt-submit") {
160
160
  const state = await readGoalState({ root: resolvedRoot });
161
- if (!isGoalOwnedBySession(state, payload?.session_id)) return { status: 0, message: "", hookOutput: {} };
161
+ if (state?.goal?.status === "active" && !isGoalOwnedBySession(state, payload?.session_id)) {
162
+ return foreignSessionContextResult({ ko, event });
163
+ }
162
164
  return contextResult({ ko, state, event });
163
165
  }
164
166
 
@@ -222,57 +224,76 @@ async function writeIfMissing(root, path, content) {
222
224
  }
223
225
 
224
226
  function contextResult({ ko, state, event }) {
225
- const base = ko ? "Cairn: 루트 MEMORY.md를 읽으세요." : "Cairn: read root MEMORY.md.";
227
+ const base = ko ? "Cairn kernel: 루트 MEMORY.md를 먼저 읽으세요." : "Cairn kernel: read root MEMORY.md first.";
226
228
  const idlePolicy = event === "user-prompt-submit"
227
229
  ? (ko
228
- ? " 구현/계속 실행: 초기 트리아지 계획 작성 → update_plan → create_goal → 탐색 전 저장소 goal 시작 → 트리아지 후 계획 확정 → 구현. 상담·설명·계획 전용은 goal 없이 처리하세요."
229
- : " Implementation/continue: write an initial triage plan; call update_plan, then create_goal; start the repository goal before exploration; finalize the plan after triage, then implement. Skip goals for consultation, explanation, or plan-only requests.")
230
- : "";
231
- if (!state || state.goal.status !== "active") return contextHookResult({ event, message: `${base}${idlePolicy}` });
230
+ ? " 구현/계속 실행이면 cairn-plan을 로드하고 active plan과 current task를 복원하거나 생성하세요. 상담·설명·계획 전용은 goal 없이 처리하세요."
231
+ : " For implementation/continue, load cairn-plan and restore or create the active plan and current task. Keep consultation, explanation, and plan-only requests goal-free.")
232
+ : (ko
233
+ ? " 중요 작업 전에 cairn-plan을 로드하고 active plan과 current task를 복원하세요."
234
+ : " Before non-trivial work, load cairn-plan and restore the active plan and current task.");
235
+ const failClosed = ko
236
+ ? " state, skill, plan 또는 required reference가 없거나 읽을 수 없거나 일치하지 않으면 수정·위임·완료하지 말고 보고하거나 차단하세요."
237
+ : " If state, skill, plan, or a required ref is missing, unreadable, or inconsistent, do not edit, delegate, or complete; report/block.";
238
+ if (!state || state.goal.status !== "active") return contextHookResult({ event, message: `${base}${idlePolicy}${failClosed}` });
232
239
  const task = state.tasks.find((item) => item.status === "active")
233
240
  ?? state.tasks.find((item) => item.status === "pending")
234
241
  ?? state.tasks.find((item) => item.status === "blocked");
235
- const goalTitle = clipText(state.goal.title, contextLimits.goalTitle);
242
+ const planId = state.goal.planId;
243
+ const phase = task ? "cairn-work" : "cairn-review";
236
244
  const activeHeader = ko
237
- ? `Cairn active: "${goalTitle}". ${base}`
238
- : `Cairn active: "${goalTitle}". ${base}`;
245
+ ? `${base} ${phase}를 로드하고 정확한 plan ${planId}${task ? "과 아래 current task" : "의 완료 기준"}를 복원하세요. 압축·재시작·handoff·위임 뒤 다시 읽으세요.${failClosed}`
246
+ : `${base} Load ${phase}; restore exact plan ${planId}${task ? " and the current task below" : " completion criteria"}. Re-read after compaction/restart/handoff/delegation.${failClosed}`;
239
247
  if (!task) {
240
248
  return contextHookResult({
241
249
  event,
242
250
  message: [activeHeader, taskRoadmap(ko, state), activeGoalCompletionMessage(ko, state)].join("\n"),
243
251
  });
244
252
  }
245
- const taskId = clipText(task.id, contextLimits.taskId);
253
+ const taskId = task.id;
246
254
  const taskTitle = clipText(task.title, contextLimits.taskTitle);
247
- const continuation = ko
248
- ? `현재: ${taskId} (${taskTitle}). 바인딩된 증거 뒤에만 완료하세요.`
249
- : `Current: ${taskId} (${taskTitle}). Complete only after bound evidence.`;
250
- const resume = event === "user-prompt-submit"
255
+ const continuation = taskId.length > 48
251
256
  ? (ko
252
- ? `곁가지 질문 뒤 일시정지·중단·전환 요청이 없으면 ${taskId}를 재개하세요.`
253
- : `After a side question, resume ${taskId} unless asked to pause, stop, or switch.`)
257
+ ? `정확한 current task: ${taskId}. 바인딩된 증거 뒤에만 완료하세요.`
258
+ : `Exact task: ${taskId}. Complete after bound evidence.`)
259
+ : (ko
260
+ ? `현재: ${taskId} (${taskTitle}). 바인딩된 증거 뒤에만 완료하세요.`
261
+ : `Current: ${taskId} (${taskTitle}). Complete only after bound evidence.`);
262
+ const resume = event === "user-prompt-submit"
263
+ ? (taskId.length > 48
264
+ ? (ko
265
+ ? "곁가지 질문 뒤 일시정지·중단·전환 요청이 없으면 위의 정확한 task를 재개하세요."
266
+ : "Resume that exact task after side questions unless paused, stopped, or switched.")
267
+ : (ko
268
+ ? `곁가지 질문 뒤 일시정지·중단·전환 요청이 없으면 ${taskId}를 재개하세요.`
269
+ : `After a side question, resume ${taskId} unless asked to pause, stop, or switch.`))
254
270
  : "";
255
271
  return contextHookResult({ event, message: [activeHeader, taskRoadmap(ko, state), continuation, resume].filter(Boolean).join("\n") });
256
272
  }
257
273
 
274
+ function foreignSessionContextResult({ ko, event }) {
275
+ const message = ko
276
+ ? "Cairn kernel: 루트 MEMORY.md를 먼저 읽으세요. 저장소 goal을 다른 session이 소유합니다. cairn goal status로 상태를 확인하고 소유권이 해결될 때까지 Cairn 작업을 시작·수정·위임·완료하지 마세요. 이 capsule은 goal, plan, task 상세를 노출하지 않습니다."
277
+ : "Cairn kernel: read root MEMORY.md first. 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. This capsule does not expose goal, plan, or task details.";
278
+ return contextHookResult({ event, message });
279
+ }
280
+
258
281
  function taskRoadmap(ko, state) {
259
- const formatTask = (task, index) => `${index + 1}. ${clipText(task.id, contextLimits.taskId)} [${task.status}] ${clipText(task.title, contextLimits.taskTitle)}`;
282
+ const formatTask = (task, index) => `${index + 1}. ${clipText(task.id, contextLimits.roadmapTaskId)} [${task.status}] ${clipText(task.title, contextLimits.taskTitle)}`;
260
283
  if (state.tasks.length <= contextLimits.roadmapWindow) {
261
284
  return `${ko ? "작업 단계" : "Work steps"}:\n${state.tasks.map(formatTask).join("\n")}`;
262
285
  }
263
286
 
264
287
  const currentIndex = Math.max(0, state.tasks.findIndex((task) => task.status === "active" || task.status === "pending"));
265
- const maxStart = state.tasks.length - contextLimits.roadmapWindow;
266
- const start = Math.min(Math.max(0, currentIndex - 1), maxStart);
267
- const visible = state.tasks.slice(start, start + contextLimits.roadmapWindow);
268
288
  const counts = Object.fromEntries([...TASK_STATUS_NAMES].map((status) => [status, state.tasks.filter((task) => task.status === status).length]));
269
289
  const countSummary = Object.entries(counts).filter(([, count]) => count > 0).map(([status, count]) => `${status} ${count}`).join(", ");
270
- const omitted = state.tasks.length - visible.length;
290
+ const omitted = state.tasks.length - 1;
271
291
  const header = ko
272
292
  ? `작업 단계 (${state.tasks.length}개; ${countSummary})`
273
293
  : `Work steps (${state.tasks.length}; ${countSummary})`;
274
294
  const omission = ko ? `… ${omitted}개 단계 생략` : `… ${omitted} steps omitted`;
275
- return `${header}:\n${visible.map((task, index) => formatTask(task, start + index)).join("\n")}\n${omission}`;
295
+ const currentPosition = currentIndex + 1;
296
+ return `${header}. ${ko ? `현재 위치 ${currentPosition}` : `Current position ${currentPosition}`}; ${omission}`;
276
297
  }
277
298
 
278
299
  function contextHookResult({ event, message }) {
@@ -17,6 +17,10 @@ Do not treat planning as optional for implementation work that is more than a na
17
17
 
18
18
  Plan from the whole work down. First understand the full requested outcome and affected surfaces, then classify the work into small executable tasks. If one task is still too broad to verify cleanly, split that task into sub-tasks.
19
19
 
20
+ ## Re-entry and Required References
21
+
22
+ At the start and after compaction, restart, or handoff, restore context in this order: root `MEMORY.md` → `cairn-plan` → the active plan → current-task references → model guidance recorded by the plan. A new implementation request with no persisted goal may create its initial plan through the procedure below. If persisted state names a plan or task and that reference is missing, unreadable, or inconsistent, do not explore, edit, delegate, or complete work; report or record a blocker first. Do not add a read-receipt field that claims the model read a file: filesystem/state consistency and bound tool evidence remain the fail-closed authority.
23
+
20
24
  ## Runtime Location
21
25
 
22
26
  Resolve Cairn's read-only runtime from `references/cairn-runtime.json` next to this `SKILL.md`. Its `pluginRoot` contains Cairn's `scripts/`, `templates/`, commands, agents, and model guidance. In a source checkout where the locator does not exist, resolve the plugin root from this `SKILL.md` location (`../..`). Never search the target repository for Cairn scripts, templates, or model guidance, and never derive the plugin root from the current working directory.
@@ -13,6 +13,8 @@ Review is not another implementation loop. Confirm that the completed task satis
13
13
 
14
14
  Every review run and every delegated review agent must read the project-root `MEMORY.md` before doing assigned work.
15
15
 
16
+ At the start and after compaction, restart, delegation, or handoff, restore context in this order: root `MEMORY.md` → `cairn-review` → the active plan → completed/current-task references → model guidance recorded by the plan. Confirm those 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.
17
+
16
18
  When subagent tools are available, each agent may recursively delegate bounded review sub-tasks to subagents. Every child subagent must read the project-root `MEMORY.md`, keep the assigned scope, and preserve others' edits.
17
19
 
18
20
  Resolve any `cairn://...` resource through `references/cairn-runtime.json` next to this `SKILL.md` (or this skill's `../..` source root during development). Never look for Cairn runtime resources in the target repository.
@@ -21,6 +21,10 @@ Delegated subagents must provide a final report before leaving. After the orches
21
21
 
22
22
  If the user asks a side question, status question, or narrow clarification while the selected task is still active, use the active goal's ordered task roadmap to preserve position, answer briefly, and then resume the current task unless the user explicitly asks to pause, stop, or switch tasks.
23
23
 
24
+ ## Re-entry and Required References
25
+
26
+ At the start and after compaction, restart, delegation, or handoff, restore context in this order: 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. If state, skill, plan, assignment, or a 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.
27
+
24
28
  When subagent tools are available, each agent may recursively delegate bounded sub-tasks to subagents. Every child subagent must read the project-root `MEMORY.md`, keep the assigned scope, and preserve others' edits.
25
29
 
26
30
  ## Runtime Location