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.
- package/.codex-plugin/plugin.json +8 -13
- package/README.de.md +2 -0
- package/README.es.md +2 -0
- package/README.fr.md +2 -0
- package/README.ja.md +2 -0
- package/README.ko.md +3 -1
- package/README.md +3 -1
- package/README.pt.md +2 -0
- package/README.zh.md +2 -0
- package/agents/explorer.md +2 -0
- package/agents/worker.md +2 -0
- package/commands/cairn-uninstall.md +3 -1
- package/docs/model-guidance/README.md +10 -0
- package/docs/model-guidance/codex.md +2 -0
- package/package.json +1 -1
- package/scripts/cairn-goal.mjs +34 -8
- package/scripts/cairn-lifecycle.mjs +76 -12
- package/scripts/cairn-state.mjs +44 -23
- package/skills/cairn-plan/SKILL.md +4 -0
- package/skills/cairn-review/SKILL.md +2 -0
- package/skills/cairn-work/SKILL.md +4 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cairn",
|
|
3
|
-
"version": "0.2.
|
|
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
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
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 항목만
|
|
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
|
|
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
|
package/agents/explorer.md
CHANGED
|
@@ -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
|
-
|
|
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
package/scripts/cairn-goal.mjs
CHANGED
|
@@ -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 =
|
|
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
|
|
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 &&
|
|
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"
|
|
287
|
-
|
|
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}` :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
362
|
+
const result = await operation();
|
|
363
|
+
operationSucceeded = true;
|
|
364
|
+
return result;
|
|
353
365
|
} finally {
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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 });
|
package/scripts/cairn-state.mjs
CHANGED
|
@@ -117,7 +117,7 @@ const planTemplateKo = `# PLAN
|
|
|
117
117
|
|
|
118
118
|
const contextLimits = {
|
|
119
119
|
goalTitle: 64,
|
|
120
|
-
|
|
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))
|
|
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
|
-
? " 구현/계속
|
|
229
|
-
: "
|
|
230
|
-
:
|
|
231
|
-
|
|
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
|
|
242
|
+
const planId = state.goal.planId;
|
|
243
|
+
const phase = task ? "cairn-work" : "cairn-review";
|
|
236
244
|
const activeHeader = ko
|
|
237
|
-
?
|
|
238
|
-
:
|
|
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 =
|
|
253
|
+
const taskId = task.id;
|
|
246
254
|
const taskTitle = clipText(task.title, contextLimits.taskTitle);
|
|
247
|
-
const continuation =
|
|
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
|
-
?
|
|
253
|
-
: `
|
|
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.
|
|
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 -
|
|
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
|
-
|
|
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
|