cclaw-cli 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,6 +22,12 @@ function stageActivityPath() {
22
22
  function snapshotPath() {
23
23
  return `${RUNTIME_ROOT}/state/flow-state.snapshot.json`;
24
24
  }
25
+ function harnessGapsPath() {
26
+ return `${RUNTIME_ROOT}/state/harness-gaps.json`;
27
+ }
28
+ function retroArtifactPath() {
29
+ return `${RUNTIME_ROOT}/artifacts/09-retro.md`;
30
+ }
25
31
  /**
26
32
  * Command contract for /cc-view status — a read-only snapshot command.
27
33
  * Does not mutate state. Always safe to run.
@@ -34,7 +40,8 @@ export function statusCommandContract() {
34
40
  ## Purpose
35
41
 
36
42
  **Read-only visual snapshot of the cclaw run.** Shows progress bar, current stage,
37
- gate coverage, delegation status, stale markers, and top knowledge highlights.
43
+ gate coverage, delegation status with fulfillmentMode, closeout substate after
44
+ ship, harness parity fallback, stale markers, and top knowledge highlights.
38
45
 
39
46
  This command **never mutates state**. Use it at session start to orient, or at any
40
47
  time to answer "where are we?" without advancing the flow.
@@ -49,9 +56,10 @@ time to answer "where are we?" without advancing the flow.
49
56
  ## Algorithm
50
57
 
51
58
  1. Read **\`${flowPath}\`** — capture \`track\`, \`currentStage\`, \`completedStages\`,
52
- \`skippedStages\`, and per-stage gate catalog.
53
- 2. Read **\`${delegationPath}\`** — count delegated / completed / waived / pending entries
54
- for the current stage's \`mandatoryDelegations\`.
59
+ \`skippedStages\`, \`staleStages\`, per-stage gate catalog, and **\`closeout\`**
60
+ (shipSubstate + retro/compound flags).
61
+ 2. Read **\`${delegationPath}\`** — for each mandatory agent of the current stage,
62
+ capture \`status\`, \`fulfillmentMode\`, and whether \`evidenceRefs\` are present.
55
63
  3. Read **\`${contextModePath()}\`** — surface \`activeMode\` (default if missing).
56
64
  4. Compute **time in current stage** from the most recent stage-entry signal:
57
65
  - Prefer \`${checkpointPath()}\`'s \`timestamp\` when its \`stage\` matches \`currentStage\`.
@@ -60,25 +68,37 @@ time to answer "where are we?" without advancing the flow.
60
68
  - If no signal exists, render \`(unknown)\`.
61
69
  5. Optionally read **\`${snapshotPath()}\`** to compute gate delta versus prior baseline:
62
70
  - If missing or invalid, render \`delta: (baseline unavailable; run /cc-view diff)\`.
63
- 6. Read the top of **\`${knowledgePath}\`** surface up to 3 most recent entries
71
+ 6. Read **\`${harnessGapsPath()}\`** (schemaVersion 2). For every installed harness
72
+ capture \`tier\`, \`subagentFallback\`, and \`playbookPath\` for the harness row.
73
+ 7. Read the top of **\`${knowledgePath()}\`** — surface up to 3 most recent entries
64
74
  (by trailing timestamp or source marker).
65
- 7. Emit the visual status block described below. Do **not** load any stage skill.
75
+ 8. Detect **closeout artifacts**: check whether \`${retroArtifactPath()}\` exists on
76
+ disk and annotate the closeout row accordingly.
77
+ 9. Emit the visual status block described below. Do **not** load any stage skill.
66
78
 
67
79
  ## Visual markers
68
80
 
69
81
  Default UTF markers: \`✓\` passed, \`▶\` current, \`○\` pending, \`⊘\` skipped, \`⏸\` stale, \`✗\` blocked.
70
82
  ASCII fallback (no UTF locale): \`[x]\`, \`[>]\`, \`[ ]\`, \`[-]\`, \`[=]\`, \`[!]\`.
71
83
 
84
+ Delegation markers: \`✓\` completed, \`◎\` completed-no-evidence (role-switch
85
+ harness; **blocks stage**), \`○\` scheduled/pending, \`⊘\` waived, \`✗\` failed.
86
+
72
87
  ## Status Block Format
73
88
 
74
89
  \`\`\`
75
90
  cclaw status
76
- flow: <track> · run=<runId> · feature=<feature-id>
77
- stage: <stage> (<N>/<total>) · time <Xd|XhYm|Xm|unknown> · mode <activeMode>
78
- bar: [✓ brainstorm] [✓ scope] [▶ design] [○ spec] [○ plan] [○ tdd] [○ review] [○ ship]
79
- gates: now <passed>/<required> · blocked <count> · delta <summary or baseline-unavailable>
80
- delegations: [✓ <role>] [○ <role>] ...
81
- stale: <list or none>
91
+ flow: <track> · run=<runId> · feature=<feature-id>
92
+ stage: <stage> (<N>/<total>) · time <Xd|XhYm|Xm|unknown> · mode <activeMode>
93
+ bar: [✓ brainstorm] [✓ scope] [▶ design] [○ spec] [○ plan] [○ tdd] [○ review] [○ ship]
94
+ gates: now <passed>/<required> · blocked <count> · delta <summary or baseline-unavailable>
95
+ delegations (<expectedMode>):
96
+ - planner ✓ completed mode=<isolated|generic-dispatch|role-switch>
97
+ - reviewer ○ pending
98
+ - test-author ◎ missing-evidence (role-switch; add evidenceRefs)
99
+ closeout: <shipSubstate> · retro=<drafted|accepted|skipped|—> · compound=<N promoted|skipped|—>
100
+ harness: <id>=<tier>/<fallback>, ... · playbooks: <M>/<N>
101
+ stale: <list or none>
82
102
  knowledge:
83
103
  - <latest entry summary>
84
104
  - <second entry summary>
@@ -86,12 +106,20 @@ cclaw status
86
106
  next: /cc-next · /cc-view tree · /cc-view diff
87
107
  \`\`\`
88
108
 
109
+ - Omit the \`closeout:\` row when \`currentStage !== "ship"\` and \`shipSubstate === "idle"\`.
110
+ - Omit \`delegations\` line when the current stage has zero mandatory delegations.
111
+ - Omit \`harness\` line only when \`${harnessGapsPath()}\` is missing or invalid
112
+ (render \`harness: (report unavailable; run cclaw upgrade)\`).
113
+
89
114
  ## Anti-patterns
90
115
 
91
116
  - Inventing gate status without reading \`${flowPath}\`.
92
117
  - Reporting delegations as satisfied when the log says \`pending\`.
118
+ - Treating a \`completed\` role-switch delegation without \`evidenceRefs\` as green
119
+ — it must surface as \`◎ missing-evidence\`.
93
120
  - Advancing the stage from \`/cc-view status\` — progression belongs to \`/cc-next\`.
94
- - Hiding stale stages; stale markers must be surfaced directly in the status line.
121
+ - Hiding the closeout substate after ship; retro/compound/archive progress must
122
+ be visible so \`/cc-next\` resumes at the right step.
95
123
 
96
124
  ## Primary skill
97
125
 
@@ -106,7 +134,7 @@ export function statusCommandSkillMarkdown() {
106
134
  const delegationPath = delegationLogPath();
107
135
  return `---
108
136
  name: ${STATUS_SKILL_NAME}
109
- description: "Read-only visual snapshot of the cclaw flow with progress bar, gate delta, delegations, and stale markers."
137
+ description: "Read-only visual snapshot of the cclaw flow with progress bar, gate delta, delegations (fulfillmentMode + evidence), closeout substate, and harness parity row."
110
138
  ---
111
139
 
112
140
  # /cc-view status — Flow Status Snapshot
@@ -114,7 +142,14 @@ description: "Read-only visual snapshot of the cclaw flow with progress bar, gat
114
142
  ## Overview
115
143
 
116
144
  \`/cc-view status\` is the quickest way to answer "where are we in the flow?" without
117
- advancing or mutating anything. Safe to run at any point.
145
+ advancing or mutating anything. Safe to run at any point. The snapshot reflects:
146
+
147
+ - progress across stages with per-stage markers,
148
+ - gate coverage and delta vs. baseline,
149
+ - mandatory delegations with **fulfillmentMode** (isolated / generic-dispatch /
150
+ role-switch / harness-waiver) and evidence gate,
151
+ - **closeout substate** after ship (retro → compound → archive),
152
+ - **harness parity row** (tier + fallback) for the active harness set.
118
153
 
119
154
  ## HARD-GATE
120
155
 
@@ -133,22 +168,45 @@ a read-only command. Do **not** update \`${snapshotPath()}\` here.
133
168
  5. Try reading \`${snapshotPath()}\` for gate delta:
134
169
  - If available, compare current stage \`passed\` / \`blocked\` sets against baseline.
135
170
  - If unavailable, render \`delta: (baseline unavailable; run /cc-view diff)\`.
136
- 6. Read \`${RUNTIME_ROOT}/knowledge.jsonl\`. If missing or empty → knowledge highlights are \`(none recorded)\`. Parse each line as JSON and surface its \`trigger\`/\`action\`.
137
- 7. For each gate in \`stageGateCatalog[currentStage].required\`:
171
+ 6. Read \`${harnessGapsPath()}\`:
172
+ - If \`schemaVersion === 2\`, for each entry render \`<harness>=<tier>/<subagentFallback>\`.
173
+ - Count existing \`playbookPath\` files on disk to print \`playbooks: <M>/<N>\`.
174
+ - If the file is missing or has an older schema, render
175
+ \`harness: (report unavailable; run cclaw upgrade)\`.
176
+ 7. Read \`${RUNTIME_ROOT}/knowledge.jsonl\`. If missing or empty → knowledge highlights are \`(none recorded)\`. Parse each line as JSON and surface its \`trigger\`/\`action\`.
177
+ 8. For each gate in \`stageGateCatalog[currentStage].required\`:
138
178
  - Satisfied if present in \`passed\` and absent from \`blocked\`.
139
- 8. Build and print the visual status block:
140
- - stage header
141
- - one-line progress bar with per-stage markers
142
- - gate summary + delta
143
- - delegation row
144
- - stale stage row
145
- 9. Suggest the next action:
146
- - If current stage has unmet gates → \`/cc-next\` to resume.
147
- - If current stage is complete → \`/cc-next\` to advance (or report "Flow complete" if terminal).
179
+ 9. For each mandatory delegation of the current stage, evaluate:
180
+ - \`✓ completed\` when \`status === "completed"\` and (harness is not role-switch
181
+ **or** \`evidenceRefs.length >= 1\`).
182
+ - \`◎ missing-evidence\` when \`status === "completed"\`, harness declares
183
+ \`role-switch\`, and \`evidenceRefs\` is empty or absent.
184
+ - \`○ <status>\` for \`scheduled\` / pending.
185
+ - \`⊘ waived\` when \`status === "waived"\`.
186
+ - \`✗ failed\` when \`status === "failed"\`.
187
+ 10. Compute **closeout row** when \`currentStage === "ship"\` or
188
+ \`closeout.shipSubstate !== "idle"\`:
189
+ - \`shipSubstate\` verbatim,
190
+ - \`retro=drafted|accepted|skipped|—\` derived from \`closeout.retroDraftedAt\`,
191
+ \`closeout.retroAcceptedAt\`, \`closeout.retroSkipped\`,
192
+ - \`compound=<N promoted>|skipped|—\` from
193
+ \`closeout.compoundPromoted\` / \`closeout.compoundSkipped\`.
194
+ 11. Build and print the visual status block:
195
+ - stage header
196
+ - one-line progress bar with per-stage markers
197
+ - gate summary + delta
198
+ - delegation rows (per mandatory agent)
199
+ - closeout row (when active)
200
+ - harness row
201
+ - stale stage row
202
+ 12. Suggest the next action:
203
+ - If current stage has unmet gates → \`/cc-next\` to resume.
204
+ - If closeout substate is non-idle → \`/cc-next\` to continue the chain.
205
+ - If current stage is complete → \`/cc-next\` to advance (or report "Flow complete" if terminal).
148
206
 
149
207
  ## Output Guidelines
150
208
 
151
- - Keep output compact (≤ 30 lines) — status, not narrative.
209
+ - Keep output compact (≤ 40 lines) — status, not narrative.
152
210
  - Report counts, not full artifact contents.
153
211
  - If any data source is missing or corrupt, say so explicitly rather than guessing.
154
212
  - Include \`/cc-view tree\` for deep structure and \`/cc-view diff\` for before/after map in the final line.
@@ -157,6 +215,10 @@ a read-only command. Do **not** update \`${snapshotPath()}\` here.
157
215
 
158
216
  - Rebuilding trace-matrix or running doctor from \`/cc-view status\` — those belong to dedicated tools.
159
217
  - Treating absence of delegation log as "all delegations complete".
218
+ - Collapsing \`◎ missing-evidence\` into \`✓ completed\` — role-switch gaps must stay
219
+ visible so the stage cannot advance silently.
220
+ - Omitting the closeout row when \`shipSubstate !== "idle"\`; it is the only signal
221
+ that tells the user why \`/cc-next\` is about to run retro/compound/archive.
160
222
  - Mutating state to "clean up" during a status check.
161
223
  `;
162
224
  }
@@ -43,14 +43,20 @@ Human input remains mandatory only at explicit approval gates (plan approval, us
43
43
 
44
44
  ### Harness routing
45
45
 
46
- | Harness | Delegation tool | Structured ask tool | Routing note |
47
- |---|---|---|---|
48
- | Claude | Task/delegate | AskUserQuestion | Preferred for rich multi-step delegation + explicit approvals. |
49
- | Cursor | Task | AskQuestion | Use option-based asks for mode/waiver decisions; keep subagent payloads concise. |
50
- | Codex | Task (if available) | None native | Use numbered choices in chat for approvals; keep prompts fully self-contained. |
51
- | OpenCode | Task (if available) | None native | Log delegation outcomes in artifacts/state explicitly; do not assume built-in ask workflows. |
52
-
53
- If delegation tooling is unavailable in the active harness, run the same controller protocol in-thread and record a delegation waiver with reason \`harness_limitation\`.
46
+ | Harness | Fallback | Delegation tool | Structured ask | Parity playbook |
47
+ |---|---|---|---|---|
48
+ | Claude | \`native\` | Task (named subagent_type) | AskUserQuestion | \`.cclaw/references/harnesses/claude-playbook.md\` |
49
+ | Cursor | \`generic-dispatch\` | Task (generic subagent_type: explore/generalPurpose/…) | AskQuestion | \`.cclaw/references/harnesses/cursor-playbook.md\` |
50
+ | OpenCode | \`role-switch\` | plugin dispatch _or_ in-session role-switch | plain-text options | \`.cclaw/references/harnesses/opencode-playbook.md\` |
51
+ | Codex | \`role-switch\` | in-session role-switch (mandatory evidenceRefs) | plain-text options | \`.cclaw/references/harnesses/codex-playbook.md\` |
52
+
53
+ **Dispatch rules driven by \`subagentFallback\`:**
54
+
55
+ - \`native\` — use the harness's own named subagent primitive; delegation entry uses \`fulfillmentMode: "isolated"\`.
56
+ - \`generic-dispatch\` — map each cclaw agent onto the generic dispatcher via the harness playbook; delegation entry uses \`fulfillmentMode: "generic-dispatch"\`.
57
+ - \`role-switch\` — announce the role in-session, perform the work, append a delegation row with \`fulfillmentMode: "role-switch"\` and ≥1 \`evidenceRef\`. Without evidenceRefs the \`delegation:mandatory:current_stage\` check reports \`missingEvidence\` and blocks stage completion.
58
+
59
+ The only time a \`harness_limitation\` waiver fires automatically is when every installed harness declares \`subagentFallback: "waiver"\`. cclaw 0.33 no longer maps Codex onto auto-waiver — the agent must role-switch with evidence.
54
60
 
55
61
  ### Model routing
56
62
 
@@ -13,13 +13,17 @@ function artifactsPath() {
13
13
  function rewindLogPath() {
14
14
  return `${RUNTIME_ROOT}/state/rewind-log.jsonl`;
15
15
  }
16
+ function harnessPlaybooksDir() {
17
+ return `${RUNTIME_ROOT}/references/harnesses`;
18
+ }
16
19
  export function treeCommandContract() {
17
20
  return `# /cc-view tree
18
21
 
19
22
  ## Purpose
20
23
 
21
- Render a visual flow tree for quick orientation across stages, gates, delegations,
22
- stale markers, and artifact presence.
24
+ Render a visual flow tree for quick orientation across stages, gates, delegations
25
+ (with fulfillmentMode), ship closeout substate, stale markers, artifact presence,
26
+ and per-harness playbook availability.
23
27
 
24
28
  ## HARD-GATE
25
29
 
@@ -30,13 +34,21 @@ stale markers, and artifact presence.
30
34
 
31
35
  1. Read \`${flowStatePath()}\`.
32
36
  2. Read \`${delegationLogPath()}\` (if missing, treat current-stage delegations as pending).
33
- 3. Detect artifact files in \`${artifactsPath()}\`.
37
+ 3. Detect artifact files in \`${artifactsPath()}\` (\`01-brainstorm.md\` …
38
+ \`08-ship.md\` plus \`09-retro.md\`).
34
39
  4. Read rewind records from \`${rewindLogPath()}\` when present for stale-stage context.
35
- 5. Render the tree using stage order from active track:
40
+ 5. Inspect \`${harnessPlaybooksDir()}\` to confirm per-harness playbooks exist
41
+ for the installed harness set.
42
+ 6. Render the tree using stage order from active track:
36
43
  - stage node marker: passed/current/pending/skipped/stale
37
44
  - gate summary: \`passed/required\`
38
- - delegation summary for current stage
45
+ - delegation summary for current stage (each agent carries its
46
+ \`fulfillmentMode\` label)
39
47
  - artifact marker per stage (exists / stale copy / missing)
48
+ 7. When \`currentStage === "ship"\` or \`closeout.shipSubstate !== "idle"\`,
49
+ append a closeout sub-tree under ship with substate and retro/compound flags.
50
+ 8. Append a final \`harnesses\` branch summarising tier + fallback +
51
+ playbook-present for each installed harness.
40
52
 
41
53
  ## Tree Format
42
54
 
@@ -45,12 +57,31 @@ cclaw flow tree (track=<track>, run=<runId>)
45
57
  ├─ [✓] brainstorm gates 6/6 artifact 01-brainstorm.md
46
58
  ├─ [✓] scope gates 5/5 artifact 02-scope.md
47
59
  ├─ [▶] design gates 2/7 artifact 03-design.md
48
- │ ├─ delegations: [✓] planner [○] reviewer
60
+ │ ├─ delegations:
61
+ │ │ ├─ planner ✓ completed mode=isolated
62
+ │ │ └─ reviewer ○ pending
49
63
  │ └─ stale: none
50
64
  ├─ [○] spec gates - artifact missing
51
65
  └─ [○] plan gates - artifact missing
66
+
67
+ closeout (shipSubstate=retro_review):
68
+ ├─ retro: drafted 09-retro.md · awaiting accept/edit/skip
69
+ ├─ compound: —
70
+ └─ archive: pending
71
+
72
+ harnesses:
73
+ ├─ claude tier=tier1 fallback=native playbook ✓
74
+ ├─ cursor tier=tier2 fallback=generic-dispatch playbook ✓
75
+ ├─ opencode tier=tier2 fallback=role-switch playbook ✓
76
+ └─ codex tier=tier2 fallback=role-switch playbook ✓
52
77
  \`\`\`
53
78
 
79
+ - Closeout sub-tree is **omitted** when \`currentStage !== "ship"\` and
80
+ \`shipSubstate === "idle"\`.
81
+ - Delegations sub-branch is omitted when the stage has no mandatory agents.
82
+ - Playbook marker is \`✗ missing\` when the file under
83
+ \`${harnessPlaybooksDir()}/<harness>-playbook.md\` is absent.
84
+
54
85
  Use UTF markers by default, ASCII fallback when terminal cannot render UTF.
55
86
 
56
87
  ## Primary skill
@@ -61,7 +92,7 @@ Use UTF markers by default, ASCII fallback when terminal cannot render UTF.
61
92
  export function treeCommandSkillMarkdown() {
62
93
  return `---
63
94
  name: ${TREE_SKILL_NAME}
64
- description: "Render a visual flow tree for stages, gates, delegations, and artifacts."
95
+ description: "Render a visual flow tree for stages, gates, delegations (fulfillmentMode), ship closeout substate, artifacts, and per-harness playbooks."
65
96
  ---
66
97
 
67
98
  # /cc-view tree
@@ -72,20 +103,39 @@ Do not modify state in this command. It is a pure read/render operation.
72
103
 
73
104
  ## Protocol
74
105
 
75
- 1. Read \`${flowStatePath()}\` as source of truth.
76
- 2. Read \`${delegationLogPath()}\` for current-stage delegation status.
77
- 3. Inspect \`${artifactsPath()}\` for per-stage artifact presence and stale copies.
78
- 4. Render one compact tree:
79
- - stage marker: passed/current/pending/skipped/stale
80
- - gates summary
81
- - artifact summary
82
- - delegation branch for current stage
83
- 5. If rewind records exist in \`${rewindLogPath()}\`, include latest rewind note in footer.
106
+ 1. Read \`${flowStatePath()}\` as source of truth (including \`closeout\`).
107
+ 2. Read \`${delegationLogPath()}\` for current-stage delegation status plus
108
+ \`fulfillmentMode\` / \`evidenceRefs\`.
109
+ 3. Inspect \`${artifactsPath()}\` for per-stage artifact presence and stale copies,
110
+ and for the retro artifact \`09-retro.md\`.
111
+ 4. Inspect \`${harnessPlaybooksDir()}\` for \`<harness>-playbook.md\` files.
112
+ 5. Render one compact tree:
113
+ - stage marker: passed/current/pending/skipped/stale,
114
+ - gates summary,
115
+ - artifact summary,
116
+ - delegation branch for current stage with fulfillmentMode labels,
117
+ 6. When \`closeout.shipSubstate !== "idle"\` or \`currentStage === "ship"\`, add
118
+ a closeout sub-tree:
119
+ - \`retro:\` line derived from \`closeout.retroDraftedAt\` /
120
+ \`closeout.retroAcceptedAt\` / \`closeout.retroSkipped\` and artifact presence,
121
+ - \`compound:\` line derived from \`closeout.compoundPromoted\` /
122
+ \`closeout.compoundSkipped\` / \`closeout.compoundCompletedAt\`,
123
+ - \`archive:\` line — \`pending\` until \`shipSubstate === "ready_to_archive"\`,
124
+ then \`next\`; the transient \`archived\` substate surfaces only if the
125
+ archive step failed mid-run.
126
+ 7. Append a \`harnesses:\` branch. For each installed harness derive the tier
127
+ from the harness-gaps report and mark \`playbook ✓/✗ missing\` based on
128
+ \`${harnessPlaybooksDir()}/<harness>-playbook.md\` existence.
129
+ 8. If rewind records exist in \`${rewindLogPath()}\`, include latest rewind note in footer.
84
130
 
85
131
  ## Validation
86
132
 
87
133
  - Output must mention the active \`track\` and \`currentStage\`.
88
134
  - Exactly one stage is marked current.
89
135
  - Missing files are reported explicitly; never guessed as complete.
136
+ - Delegation rows always carry a fulfillmentMode label (or \`mode=?\` when the
137
+ ledger entry is legacy and the mode is inferred).
138
+ - Closeout sub-tree is present iff ship is reached; it cannot be omitted while
139
+ \`shipSubstate !== "idle"\`.
90
140
  `;
91
141
  }
@@ -1,6 +1,17 @@
1
+ import { type SubagentFallback } from "./harness-adapters.js";
1
2
  import type { FlowStage } from "./types.js";
2
3
  export type DelegationMode = "mandatory" | "proactive" | "conditional";
3
4
  export type DelegationStatus = "scheduled" | "completed" | "failed" | "waived";
5
+ /**
6
+ * How a delegation was actually fulfilled. Advisory — mirrors the harness
7
+ * `subagentFallback` that was in effect when the entry was recorded.
8
+ *
9
+ * - `isolated` — Claude-style isolated subagent worker.
10
+ * - `generic-dispatch` — Cursor-style Task dispatch mapped to a named role.
11
+ * - `role-switch` — performed in-session with explicit role announce.
12
+ * - `harness-waiver` — auto-waived due to missing dispatch capability.
13
+ */
14
+ export type DelegationFulfillmentMode = "isolated" | "generic-dispatch" | "role-switch" | "harness-waiver";
4
15
  export interface DelegationTokenUsage {
5
16
  input: number;
6
17
  output: number;
@@ -45,6 +56,12 @@ export type DelegationEntry = {
45
56
  retryCount?: number;
46
57
  /** Optional references to evidence anchors in artifacts. */
47
58
  evidenceRefs?: string[];
59
+ /**
60
+ * Fulfillment mode this entry was executed under. Omitted on legacy rows
61
+ * (treated as `"isolated"` for Claude, otherwise inferred from the active
62
+ * harness).
63
+ */
64
+ fulfillmentMode?: DelegationFulfillmentMode;
48
65
  /** Schema version marker for span-compatible delegation logs. */
49
66
  schemaVersion?: 1;
50
67
  };
@@ -54,10 +71,21 @@ export type DelegationLedger = {
54
71
  };
55
72
  export declare function readDelegationLedger(projectRoot: string): Promise<DelegationLedger>;
56
73
  export declare function appendDelegation(projectRoot: string, entry: DelegationEntry): Promise<void>;
74
+ /**
75
+ * Aggregate the fulfillment mode cclaw expects for the active harness set.
76
+ * Priority native > generic-dispatch > role-switch > waiver — the best
77
+ * available mode wins so mixed installs (e.g. claude + codex) inherit the
78
+ * strongest guarantee.
79
+ */
80
+ export declare function expectedFulfillmentMode(fallbacks: SubagentFallback[]): DelegationFulfillmentMode;
57
81
  export declare function checkMandatoryDelegations(projectRoot: string, stage: FlowStage): Promise<{
58
82
  satisfied: boolean;
59
83
  missing: string[];
60
84
  waived: string[];
61
85
  autoWaived: string[];
62
86
  staleIgnored: string[];
87
+ /** Delegation rows missing required evidence under a role-switch fallback. */
88
+ missingEvidence: string[];
89
+ /** Expected fulfillment mode for the active harness set. */
90
+ expectedMode: DelegationFulfillmentMode;
63
91
  }>;
@@ -54,6 +54,11 @@ function isDelegationEntry(value) {
54
54
  (o.taskId === undefined || typeof o.taskId === "string") &&
55
55
  (o.waiverReason === undefined || typeof o.waiverReason === "string") &&
56
56
  (o.runId === undefined || typeof o.runId === "string") &&
57
+ (o.fulfillmentMode === undefined ||
58
+ o.fulfillmentMode === "isolated" ||
59
+ o.fulfillmentMode === "generic-dispatch" ||
60
+ o.fulfillmentMode === "role-switch" ||
61
+ o.fulfillmentMode === "harness-waiver") &&
57
62
  (o.conditionTrigger === undefined || typeof o.conditionTrigger === "string") &&
58
63
  (o.tokens === undefined || isDelegationTokenUsage(o.tokens)) &&
59
64
  retryOk &&
@@ -128,6 +133,23 @@ export async function appendDelegation(projectRoot, entry) {
128
133
  await writeFileSafe(filePath, `${JSON.stringify(ledger, null, 2)}\n`);
129
134
  });
130
135
  }
136
+ /**
137
+ * Aggregate the fulfillment mode cclaw expects for the active harness set.
138
+ * Priority native > generic-dispatch > role-switch > waiver — the best
139
+ * available mode wins so mixed installs (e.g. claude + codex) inherit the
140
+ * strongest guarantee.
141
+ */
142
+ export function expectedFulfillmentMode(fallbacks) {
143
+ if (fallbacks.length === 0)
144
+ return "isolated";
145
+ if (fallbacks.some((f) => f === "native"))
146
+ return "isolated";
147
+ if (fallbacks.some((f) => f === "generic-dispatch"))
148
+ return "generic-dispatch";
149
+ if (fallbacks.some((f) => f === "role-switch"))
150
+ return "role-switch";
151
+ return "harness-waiver";
152
+ }
131
153
  export async function checkMandatoryDelegations(projectRoot, stage) {
132
154
  const mandatory = stageSchema(stage).mandatoryDelegations;
133
155
  const { activeRunId } = await readFlowState(projectRoot);
@@ -140,15 +162,21 @@ export async function checkMandatoryDelegations(projectRoot, stage) {
140
162
  const missing = [];
141
163
  const waived = [];
142
164
  const autoWaived = [];
165
+ const missingEvidence = [];
143
166
  const config = await readConfig(projectRoot).catch(() => null);
144
167
  const harnesses = config?.harnesses ?? [];
145
- const nativeDelegationUnavailable = harnesses.length > 0 &&
146
- harnesses.every((harness) => HARNESS_ADAPTERS[harness].capabilities.nativeSubagentDispatch === "none");
168
+ const fallbacks = harnesses.map((h) => HARNESS_ADAPTERS[h].capabilities.subagentFallback);
169
+ const expectedMode = expectedFulfillmentMode(fallbacks);
170
+ const onlyWaiverFallback = harnesses.length > 0 && fallbacks.every((f) => f === "waiver");
147
171
  for (const agent of mandatory) {
148
172
  const rows = forRun.filter((e) => e.agent === agent);
149
- const ok = rows.some((e) => e.status === "completed" || e.status === "waived");
173
+ const completedRows = rows.filter((e) => e.status === "completed");
174
+ const waivedRows = rows.filter((e) => e.status === "waived");
175
+ const hasCompleted = completedRows.length > 0;
176
+ const hasWaived = waivedRows.length > 0;
177
+ const ok = hasCompleted || hasWaived;
150
178
  if (!ok) {
151
- if (nativeDelegationUnavailable) {
179
+ if (onlyWaiverFallback) {
152
180
  const existingHarnessWaiver = rows.some((e) => e.status === "waived" && e.waiverReason === "harness_limitation");
153
181
  if (!existingHarnessWaiver) {
154
182
  await appendDelegation(projectRoot, {
@@ -157,6 +185,7 @@ export async function checkMandatoryDelegations(projectRoot, stage) {
157
185
  mode: "mandatory",
158
186
  status: "waived",
159
187
  waiverReason: "harness_limitation",
188
+ fulfillmentMode: "harness-waiver",
160
189
  ts: new Date().toISOString(),
161
190
  runId: activeRunId
162
191
  });
@@ -167,16 +196,27 @@ export async function checkMandatoryDelegations(projectRoot, stage) {
167
196
  else {
168
197
  missing.push(agent);
169
198
  }
199
+ continue;
170
200
  }
171
- else if (rows.some((e) => e.status === "waived")) {
201
+ if (hasWaived) {
172
202
  waived.push(agent);
173
203
  }
204
+ // Under role-switch fallback, a `completed` row is only credible if it
205
+ // carries at least one evidenceRef — otherwise the agent might have
206
+ // claimed role-switch satisfaction without showing its work.
207
+ if (hasCompleted &&
208
+ expectedMode === "role-switch" &&
209
+ !completedRows.some((e) => Array.isArray(e.evidenceRefs) && e.evidenceRefs.length > 0)) {
210
+ missingEvidence.push(agent);
211
+ }
174
212
  }
175
213
  return {
176
- satisfied: missing.length === 0,
214
+ satisfied: missing.length === 0 && missingEvidence.length === 0,
177
215
  missing,
178
216
  waived,
179
217
  autoWaived,
180
- staleIgnored
218
+ staleIgnored,
219
+ missingEvidence,
220
+ expectedMode
181
221
  };
182
222
  }
package/dist/doctor.js CHANGED
@@ -23,6 +23,7 @@ import { doctorCheckMetadata } from "./doctor-registry.js";
23
23
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
24
24
  import { CONTEXT_MODES, DEFAULT_CONTEXT_MODE } from "./content/contexts.js";
25
25
  import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
26
+ import { HARNESS_PLAYBOOKS_DIR, harnessPlaybookFileName } from "./content/harness-playbooks.js";
26
27
  import { validateHookDocument } from "./hook-schema.js";
27
28
  const execFileAsync = promisify(execFile);
28
29
  async function isGitRepo(projectRoot) {
@@ -375,6 +376,12 @@ export async function doctorChecks(projectRoot, options = {}) {
375
376
  ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "references", "harnesses.md")),
376
377
  details: `${RUNTIME_ROOT}/references/harnesses.md`
377
378
  });
379
+ const playbookDir = path.join(projectRoot, RUNTIME_ROOT, ...HARNESS_PLAYBOOKS_DIR.split("/"));
380
+ checks.push({
381
+ name: "harness_ref:playbooks_index",
382
+ ok: await exists(path.join(playbookDir, "README.md")),
383
+ details: `${RUNTIME_ROOT}/${HARNESS_PLAYBOOKS_DIR}/README.md`
384
+ });
378
385
  const doctorRefDir = path.join(projectRoot, RUNTIME_ROOT, "references", "doctor");
379
386
  for (const fileName of Object.keys(DOCTOR_REFERENCE_MARKDOWN)) {
380
387
  const refPath = path.join(doctorRefDir, fileName);
@@ -475,6 +482,12 @@ export async function doctorChecks(projectRoot, options = {}) {
475
482
  details: shimPath
476
483
  });
477
484
  }
485
+ const playbookFile = path.join(projectRoot, RUNTIME_ROOT, ...HARNESS_PLAYBOOKS_DIR.split("/"), harnessPlaybookFileName(harness));
486
+ checks.push({
487
+ name: `harness_ref:playbook:${harness}`,
488
+ ok: await exists(playbookFile),
489
+ details: `${RUNTIME_ROOT}/${HARNESS_PLAYBOOKS_DIR}/${harnessPlaybookFileName(harness)}`
490
+ });
478
491
  }
479
492
  const agentsFile = path.join(projectRoot, "AGENTS.md");
480
493
  let agentsBlockOk = false;
@@ -1298,12 +1311,15 @@ export async function doctorChecks(projectRoot, options = {}) {
1298
1311
  details: `${RUNTIME_ROOT}/runs must exist for archived feature snapshots`
1299
1312
  });
1300
1313
  const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
1314
+ const missingEvidenceNote = delegation.missingEvidence && delegation.missingEvidence.length > 0
1315
+ ? ` (role-switch rows without evidenceRefs: ${delegation.missingEvidence.join(", ")})`
1316
+ : "";
1301
1317
  checks.push({
1302
1318
  name: "delegation:mandatory:current_stage",
1303
1319
  ok: delegation.satisfied,
1304
1320
  details: delegation.satisfied
1305
- ? `All mandatory delegations satisfied for stage "${flowState.currentStage}"`
1306
- : `Missing mandatory delegations for stage "${flowState.currentStage}": ${delegation.missing.join(", ")}`
1321
+ ? `All mandatory delegations satisfied for stage "${flowState.currentStage}" (mode: ${delegation.expectedMode})`
1322
+ : `Missing mandatory delegations for stage "${flowState.currentStage}": ${delegation.missing.join(", ")}${missingEvidenceNote}`
1307
1323
  });
1308
1324
  checks.push({
1309
1325
  name: "warning:delegation:waived",
@@ -1,19 +1,58 @@
1
1
  import type { HarnessId } from "./types.js";
2
2
  export declare const CCLAW_MARKER_START = "<!-- cclaw-start -->";
3
3
  export declare const CCLAW_MARKER_END = "<!-- cclaw-end -->";
4
+ export type SubagentFallback =
5
+ /** Harness has real, isolated subagent dispatch; no fallback needed. */
6
+ "native"
7
+ /**
8
+ * Harness has generic dispatch (e.g. Cursor's Task tool with
9
+ * `subagent_type`) but not user-defined named subagents; cclaw maps each
10
+ * named agent to the generic dispatcher with a structured role prompt.
11
+ */
12
+ | "generic-dispatch"
13
+ /**
14
+ * No isolated dispatch — the agent performs the named subagent's role
15
+ * in-session with an explicit role announce + delegation-log entry
16
+ * carrying evidenceRefs. Accepted as `completed` in delegation checks.
17
+ */
18
+ | "role-switch"
19
+ /**
20
+ * No meaningful fallback — mandatory delegations can only be waived
21
+ * under `waiverReason: "harness_limitation"`.
22
+ */
23
+ | "waiver";
4
24
  export interface HarnessAdapter {
5
25
  id: HarnessId;
6
26
  commandDir: string;
7
27
  capabilities: {
8
- nativeSubagentDispatch: "full" | "partial" | "none";
28
+ /**
29
+ * Level of native subagent dispatch:
30
+ * - `full` — isolated workers + user-defined named subagents (Claude).
31
+ * - `generic` — generic dispatcher (Task) without named agents (Cursor).
32
+ * - `partial` — plugin-based dispatch, not a first-class primitive
33
+ * (OpenCode).
34
+ * - `none` — no dispatch primitive at all (Codex).
35
+ */
36
+ nativeSubagentDispatch: "full" | "generic" | "partial" | "none";
9
37
  hookSurface: "full" | "plugin" | "limited" | "none";
10
38
  structuredAsk: "AskUserQuestion" | "AskQuestion" | "plain-text";
39
+ /**
40
+ * Declared fallback pattern used when the harness cannot satisfy a
41
+ * mandatory delegation natively. Drives `checkMandatoryDelegations`
42
+ * and the generated playbook per harness.
43
+ */
44
+ subagentFallback: SubagentFallback;
11
45
  };
12
46
  }
13
47
  export declare function harnessShimFileNames(): string[];
14
48
  export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
15
49
  export type HarnessTier = "tier1" | "tier2" | "tier3";
16
50
  export declare function harnessTier(harnessId: HarnessId): HarnessTier;
51
+ /**
52
+ * Harness IDs ordered from best (tier1) to least-capable. Stable sort — same
53
+ * tier preserves declaration order.
54
+ */
55
+ export declare function harnessesByTier(): HarnessId[];
17
56
  /** Removes the cclaw AGENTS.md block. */
18
57
  export declare function stripCclawBlock(content: string): string;
19
58
  export declare function removeCclawFromAgentsMd(projectRoot: string): Promise<void>;