cclaw-cli 0.32.0 → 0.33.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.
- package/README.md +46 -18
- package/dist/content/harness-playbooks.d.ts +24 -0
- package/dist/content/harness-playbooks.js +292 -0
- package/dist/content/harnesses-doc.js +13 -3
- package/dist/content/subagents.js +14 -8
- package/dist/delegation.d.ts +28 -0
- package/dist/delegation.js +47 -7
- package/dist/doctor.js +18 -2
- package/dist/harness-adapters.d.ts +40 -1
- package/dist/harness-adapters.js +24 -5
- package/dist/install.js +36 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -277,9 +277,8 @@ The `tdd` stage is not prose guidance. It requires:
|
|
|
277
277
|
- optional **REFACTOR** pass with coverage preservation
|
|
278
278
|
|
|
279
279
|
`/cc-next` will not advance past `tdd` until the delegation log shows the
|
|
280
|
-
subagent as `completed` or
|
|
281
|
-
|
|
282
|
-
[Harness support](#harness-support)).
|
|
280
|
+
subagent as `completed` (or, on Codex / OpenCode, role-switched with
|
|
281
|
+
`evidenceRefs` — see [Harness support](#harness-support)).
|
|
283
282
|
|
|
284
283
|
---
|
|
285
284
|
|
|
@@ -320,21 +319,50 @@ to: `/cc-next` is the only command.
|
|
|
320
319
|
|
|
321
320
|
## Harness support
|
|
322
321
|
|
|
323
|
-
cclaw is honest about
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
|
330
|
-
|
|
|
331
|
-
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
native
|
|
336
|
-
|
|
337
|
-
|
|
322
|
+
cclaw is honest about what each harness can and cannot do, and it
|
|
323
|
+
closes every real gap with a documented fallback — not a silent waiver.
|
|
324
|
+
|
|
325
|
+
| Harness | Dispatch | Fallback | Hook surface | Structured ask | Playbook |
|
|
326
|
+
|---|---|---|---|---|---|
|
|
327
|
+
| Claude Code | full (named subagents) | `native` | full | `AskUserQuestion` | [`claude-playbook.md`](./src/content/harness-playbooks.ts) |
|
|
328
|
+
| Cursor | generic Task dispatcher | `generic-dispatch` | full | `AskQuestion` | `cursor-playbook.md` |
|
|
329
|
+
| OpenCode | plugin / in-session | `role-switch` | plugin | plain-text | `opencode-playbook.md` |
|
|
330
|
+
| OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | full | plain-text | `codex-playbook.md` |
|
|
331
|
+
|
|
332
|
+
What the fallbacks mean:
|
|
333
|
+
|
|
334
|
+
- `native` — Claude runs mandatory delegations in isolated subagent
|
|
335
|
+
workers; cclaw records them with `fulfillmentMode: "isolated"`.
|
|
336
|
+
- `generic-dispatch` — Cursor has a real Task tool with a fixed
|
|
337
|
+
vocabulary of `subagent_type`s (`explore`, `generalPurpose`, …).
|
|
338
|
+
cclaw maps each named agent (planner / reviewer / test-author /
|
|
339
|
+
security-reviewer / doc-updater) onto the generic dispatcher with a
|
|
340
|
+
structured role prompt. Per-agent mapping lives in the Cursor
|
|
341
|
+
playbook.
|
|
342
|
+
- `role-switch` — OpenCode and Codex lack an isolated worker primitive.
|
|
343
|
+
The agent announces the role in-session, performs the work, and
|
|
344
|
+
records a delegation row with `fulfillmentMode: "role-switch"` and at
|
|
345
|
+
least one `evidenceRef` pointing at the artifact section that
|
|
346
|
+
captures the output. Under role-switch, a `completed` row **without**
|
|
347
|
+
evidenceRefs is classified as `missingEvidence` by `cclaw doctor` and
|
|
348
|
+
blocks stage completion.
|
|
349
|
+
- `waiver` — reserved. Only fires auto-waivers if every installed
|
|
350
|
+
harness declares it. Currently unused — v0.33 removed the old
|
|
351
|
+
Codex-only auto-waiver path.
|
|
352
|
+
|
|
353
|
+
The full capability matrix lives in
|
|
354
|
+
[`docs/harnesses.md`](./docs/harnesses.md). Per-harness playbooks are
|
|
355
|
+
generated into `.cclaw/references/harnesses/` on every install and
|
|
356
|
+
upgrade; stage skills cite them by path.
|
|
357
|
+
|
|
358
|
+
Runtime state:
|
|
359
|
+
|
|
360
|
+
- `.cclaw/state/harness-gaps.json` (schema v2) — per-harness list of
|
|
361
|
+
missing capabilities, missing hook events, the declared fallback, the
|
|
362
|
+
playbook path, and a `remediation[]` list you can act on.
|
|
363
|
+
- `cclaw doctor` — asserts every installed harness has its playbook on
|
|
364
|
+
disk and surfaces the expected fulfillment mode inside the
|
|
365
|
+
`delegation:mandatory:current_stage` check.
|
|
338
366
|
|
|
339
367
|
---
|
|
340
368
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-harness parity playbooks.
|
|
3
|
+
*
|
|
4
|
+
* cclaw's subagent contracts (planner / reviewer / security-reviewer /
|
|
5
|
+
* test-author / doc-updater) assume Claude-style isolated workers. On
|
|
6
|
+
* harnesses without that primitive, the agent has to fulfil the role via a
|
|
7
|
+
* documented fallback (generic Task dispatch, role-switch in-session, …).
|
|
8
|
+
*
|
|
9
|
+
* Each playbook is:
|
|
10
|
+
* 1. short (≤ ~150 lines markdown),
|
|
11
|
+
* 2. executable — reproducible by an agent without reading the whole repo,
|
|
12
|
+
* 3. evidence-first — always records a delegation-log entry with
|
|
13
|
+
* `fulfillmentMode` and `evidenceRefs` so `cclaw doctor` can tell the
|
|
14
|
+
* role was actually performed.
|
|
15
|
+
*
|
|
16
|
+
* Playbooks are materialised at
|
|
17
|
+
* `.cclaw/references/harnesses/<harness>-playbook.md` by install/sync/upgrade.
|
|
18
|
+
*/
|
|
19
|
+
import type { HarnessId } from "../types.js";
|
|
20
|
+
export declare const HARNESS_PLAYBOOKS_DIR = "references/harnesses";
|
|
21
|
+
export declare function harnessPlaybookRelativePath(harness: HarnessId): string;
|
|
22
|
+
export declare function harnessPlaybookFileName(harness: HarnessId): string;
|
|
23
|
+
export declare function harnessPlaybookMarkdown(harness: HarnessId): string;
|
|
24
|
+
export declare function harnessPlaybooksIndexMarkdown(): string;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-harness parity playbooks.
|
|
3
|
+
*
|
|
4
|
+
* cclaw's subagent contracts (planner / reviewer / security-reviewer /
|
|
5
|
+
* test-author / doc-updater) assume Claude-style isolated workers. On
|
|
6
|
+
* harnesses without that primitive, the agent has to fulfil the role via a
|
|
7
|
+
* documented fallback (generic Task dispatch, role-switch in-session, …).
|
|
8
|
+
*
|
|
9
|
+
* Each playbook is:
|
|
10
|
+
* 1. short (≤ ~150 lines markdown),
|
|
11
|
+
* 2. executable — reproducible by an agent without reading the whole repo,
|
|
12
|
+
* 3. evidence-first — always records a delegation-log entry with
|
|
13
|
+
* `fulfillmentMode` and `evidenceRefs` so `cclaw doctor` can tell the
|
|
14
|
+
* role was actually performed.
|
|
15
|
+
*
|
|
16
|
+
* Playbooks are materialised at
|
|
17
|
+
* `.cclaw/references/harnesses/<harness>-playbook.md` by install/sync/upgrade.
|
|
18
|
+
*/
|
|
19
|
+
import { HARNESS_ADAPTERS } from "../harness-adapters.js";
|
|
20
|
+
export const HARNESS_PLAYBOOKS_DIR = "references/harnesses";
|
|
21
|
+
export function harnessPlaybookRelativePath(harness) {
|
|
22
|
+
return `${HARNESS_PLAYBOOKS_DIR}/${harness}-playbook.md`;
|
|
23
|
+
}
|
|
24
|
+
export function harnessPlaybookFileName(harness) {
|
|
25
|
+
return `${harness}-playbook.md`;
|
|
26
|
+
}
|
|
27
|
+
const CLAUDE_PLAYBOOK = `---
|
|
28
|
+
harness: claude
|
|
29
|
+
fallback: native
|
|
30
|
+
description: "Claude Code has real isolated subagent workers with user-defined named types. No fallback required — this playbook is reference-only."
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
# Claude Code — Parity Playbook
|
|
34
|
+
|
|
35
|
+
**Status: native.** Claude Code supports isolated subagent workers via the
|
|
36
|
+
\`Task\` tool with user-defined \`subagent_type\` (\`planner\`, \`reviewer\`,
|
|
37
|
+
\`security-reviewer\`, \`test-author\`, \`doc-updater\`). Each dispatch runs in
|
|
38
|
+
its own context and produces a return message visible only to the parent
|
|
39
|
+
agent.
|
|
40
|
+
|
|
41
|
+
This playbook exists so the harness matrix has one reference shape; Claude
|
|
42
|
+
itself has no parity gap to close.
|
|
43
|
+
|
|
44
|
+
## Dispatch pattern
|
|
45
|
+
|
|
46
|
+
1. Pick the \`subagent_type\` matching the cclaw agent (e.g. \`reviewer\`).
|
|
47
|
+
2. Provide a specific, self-contained \`prompt\` — the subagent cannot see
|
|
48
|
+
prior assistant turns.
|
|
49
|
+
3. Record a delegation entry before dispatch:
|
|
50
|
+
|
|
51
|
+
\`\`\`json
|
|
52
|
+
{
|
|
53
|
+
"stage": "review",
|
|
54
|
+
"agent": "reviewer",
|
|
55
|
+
"mode": "mandatory",
|
|
56
|
+
"status": "scheduled",
|
|
57
|
+
"fulfillmentMode": "isolated",
|
|
58
|
+
"spanId": "dspan-..."
|
|
59
|
+
}
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
4. After the subagent returns, update the entry to \`status: "completed"\`
|
|
63
|
+
and attach \`evidenceRefs\` pointing at the artifact section that
|
|
64
|
+
captures the subagent's output.
|
|
65
|
+
|
|
66
|
+
## Verification
|
|
67
|
+
|
|
68
|
+
\`cclaw doctor\` will pass the \`delegation:mandatory:current_stage\` check
|
|
69
|
+
when each mandatory agent has a \`completed\` row for the active run.
|
|
70
|
+
`;
|
|
71
|
+
const CURSOR_PLAYBOOK = `---
|
|
72
|
+
harness: cursor
|
|
73
|
+
fallback: generic-dispatch
|
|
74
|
+
description: "Cursor has a generic Task dispatcher with subagent_type (generalPurpose, explore, shell, …) but no user-defined named subagents. cclaw maps planner/reviewer/test-author/… onto generic dispatch with a structured role prompt."
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# Cursor — Parity Playbook
|
|
78
|
+
|
|
79
|
+
**Fallback: generic-dispatch.** Cursor's \`Task\` tool supports
|
|
80
|
+
\`subagent_type\` from a fixed vocabulary (\`generalPurpose\`, \`explore\`,
|
|
81
|
+
\`shell\`, \`browser-use\`, …). Real isolation, but no user-defined agent
|
|
82
|
+
names. cclaw closes the gap by mapping each named cclaw agent onto the
|
|
83
|
+
generic dispatcher with a strict role prompt.
|
|
84
|
+
|
|
85
|
+
## Named-agent → Cursor subagent_type map
|
|
86
|
+
|
|
87
|
+
| cclaw agent | Cursor \`subagent_type\` | Readonly? | Rationale |
|
|
88
|
+
|----------------------|-------------------------|-----------|-----------|
|
|
89
|
+
| \`planner\` | \`explore\` | yes | Pure research, no writes. |
|
|
90
|
+
| \`reviewer\` | \`explore\` | yes | Reads diff + context, emits findings. |
|
|
91
|
+
| \`security-reviewer\`| \`explore\` | yes | Reads code, produces report; no fixes. |
|
|
92
|
+
| \`test-author\` | \`generalPurpose\` | no | Writes tests, runs them, iterates. |
|
|
93
|
+
| \`doc-updater\` | \`generalPurpose\` | no | Edits docs, re-runs build. |
|
|
94
|
+
|
|
95
|
+
## Dispatch pattern
|
|
96
|
+
|
|
97
|
+
1. Pick the mapped \`subagent_type\` from the table above.
|
|
98
|
+
2. Build the \`prompt\` from the cclaw agent contract in
|
|
99
|
+
\`.cclaw/agents/<agent>.md\`, prefaced with a single line naming the
|
|
100
|
+
cclaw role (\`You are the cclaw <agent>. Follow the contract below.\`).
|
|
101
|
+
3. Set \`readonly: true\` when the table says yes — Cursor enforces it.
|
|
102
|
+
4. Before dispatch, append a delegation row:
|
|
103
|
+
|
|
104
|
+
\`\`\`json
|
|
105
|
+
{
|
|
106
|
+
"stage": "tdd",
|
|
107
|
+
"agent": "test-author",
|
|
108
|
+
"mode": "mandatory",
|
|
109
|
+
"status": "scheduled",
|
|
110
|
+
"fulfillmentMode": "generic-dispatch",
|
|
111
|
+
"spanId": "dspan-..."
|
|
112
|
+
}
|
|
113
|
+
\`\`\`
|
|
114
|
+
|
|
115
|
+
5. After dispatch returns, transition the row to \`completed\` with
|
|
116
|
+
\`evidenceRefs\` citing the artifact anchor where the result landed.
|
|
117
|
+
|
|
118
|
+
## Why not upgrade Cursor to a full tier-1?
|
|
119
|
+
|
|
120
|
+
Cursor has dispatch + hooks + \`AskQuestion\`. The missing piece is
|
|
121
|
+
**user-defined named subagents**. Semantically this is the difference
|
|
122
|
+
between Claude's \`test-author\` (a distinct runtime worker registered by
|
|
123
|
+
cclaw) and Cursor's \`generalPurpose\` worker that cclaw *asks* to act as a
|
|
124
|
+
test-author. Good enough for parity; different enough to keep the labels
|
|
125
|
+
honest.
|
|
126
|
+
|
|
127
|
+
## Verification
|
|
128
|
+
|
|
129
|
+
\`cclaw doctor\` passes when the delegation row exists with
|
|
130
|
+
\`fulfillmentMode: "generic-dispatch"\` (or \`completed\` rows for the
|
|
131
|
+
mandatory agents in general). No evidenceRef requirement applies here —
|
|
132
|
+
Cursor dispatch is real isolation.
|
|
133
|
+
`;
|
|
134
|
+
const OPENCODE_PLAYBOOK = `---
|
|
135
|
+
harness: opencode
|
|
136
|
+
fallback: role-switch
|
|
137
|
+
description: "OpenCode has plugin-based dispatch hooks but no isolated subagent worker primitive. cclaw uses an in-session role-switch with a delegation-log entry + evidenceRefs."
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
# OpenCode — Parity Playbook
|
|
141
|
+
|
|
142
|
+
**Fallback: role-switch.** OpenCode exposes tool/session event hooks via a
|
|
143
|
+
plugin but does not provide an isolated subagent worker. cclaw closes the
|
|
144
|
+
delegation gate by role-switching inside the same session: the agent
|
|
145
|
+
announces the role, performs the work against the contract, and records
|
|
146
|
+
evidence.
|
|
147
|
+
|
|
148
|
+
## Role-switch protocol
|
|
149
|
+
|
|
150
|
+
1. Announce the role explicitly in a single message:
|
|
151
|
+
|
|
152
|
+
> Acting as cclaw **<agent>** per \`.cclaw/agents/<agent>.md\`. No other
|
|
153
|
+
> role may be assumed until the delegation row is closed.
|
|
154
|
+
|
|
155
|
+
2. Execute the role's contract. Do NOT interleave other roles' work.
|
|
156
|
+
3. Write the result into the stage artifact (e.g. TDD work lands in
|
|
157
|
+
\`.cclaw/artifacts/06-tdd.md\`).
|
|
158
|
+
4. Append a delegation row:
|
|
159
|
+
|
|
160
|
+
\`\`\`json
|
|
161
|
+
{
|
|
162
|
+
"stage": "tdd",
|
|
163
|
+
"agent": "test-author",
|
|
164
|
+
"mode": "mandatory",
|
|
165
|
+
"status": "completed",
|
|
166
|
+
"fulfillmentMode": "role-switch",
|
|
167
|
+
"evidenceRefs": [
|
|
168
|
+
".cclaw/artifacts/06-tdd.md#red-run",
|
|
169
|
+
".cclaw/artifacts/06-tdd.md#green-run"
|
|
170
|
+
],
|
|
171
|
+
"spanId": "dspan-..."
|
|
172
|
+
}
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
5. \`evidenceRefs\` **must** point at concrete artifact anchors — not
|
|
176
|
+
placeholder text. \`cclaw doctor\` will report \`missingEvidence\` if
|
|
177
|
+
the array is empty under a role-switch fallback.
|
|
178
|
+
|
|
179
|
+
## Exception: OpenCode plugin dispatch
|
|
180
|
+
|
|
181
|
+
If the project configures a plugin-based dispatch path (e.g. a tool that
|
|
182
|
+
spawns a worker process), set \`fulfillmentMode: "generic-dispatch"\`
|
|
183
|
+
instead of \`role-switch\` and omit the role-announce step. evidenceRefs
|
|
184
|
+
remain optional but recommended.
|
|
185
|
+
|
|
186
|
+
## Verification
|
|
187
|
+
|
|
188
|
+
\`cclaw doctor\` passes when every mandatory agent for the active stage
|
|
189
|
+
has either a \`completed\` row with evidenceRefs (role-switch) or a
|
|
190
|
+
\`completed\` row under plugin dispatch.
|
|
191
|
+
`;
|
|
192
|
+
const CODEX_PLAYBOOK = `---
|
|
193
|
+
harness: codex
|
|
194
|
+
fallback: role-switch
|
|
195
|
+
description: "OpenAI Codex has no subagent dispatch primitive. cclaw uses role-switch with evidenceRefs; silent auto-waiver is explicitly disabled."
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
# OpenAI Codex — Parity Playbook
|
|
199
|
+
|
|
200
|
+
**Fallback: role-switch.** Codex has no subagent dispatch — neither named
|
|
201
|
+
nor generic. cclaw used to silently auto-waive mandatory delegations on
|
|
202
|
+
Codex; v0.33 disables that shortcut. The agent must role-switch in-session
|
|
203
|
+
and record evidence, or the delegation gate blocks stage completion.
|
|
204
|
+
|
|
205
|
+
## Role-switch protocol
|
|
206
|
+
|
|
207
|
+
Identical to OpenCode. Key requirements:
|
|
208
|
+
|
|
209
|
+
1. **Explicit announce.** Before performing the role, emit a single
|
|
210
|
+
message naming the role and citing \`.cclaw/agents/<agent>.md\`.
|
|
211
|
+
2. **No role interleaving.** Do not mix, for example, reviewer and
|
|
212
|
+
test-author work into the same turn — close one delegation before
|
|
213
|
+
opening another.
|
|
214
|
+
3. **EvidenceRefs are mandatory.** Under Codex's role-switch fallback a
|
|
215
|
+
\`completed\` row without \`evidenceRefs\` is treated as
|
|
216
|
+
\`missingEvidence\` by \`cclaw doctor\` and blocks the gate.
|
|
217
|
+
|
|
218
|
+
## Stage-specific role maps
|
|
219
|
+
|
|
220
|
+
| Stage | Mandatory roles | Artifact to cite in evidenceRefs |
|
|
221
|
+
|------------|----------------------------------|--------------------------------------|
|
|
222
|
+
| scope | \`planner\` | \`.cclaw/artifacts/02-scope.md\` |
|
|
223
|
+
| design | \`planner\` | \`.cclaw/artifacts/03-design.md\` |
|
|
224
|
+
| plan | \`planner\` | \`.cclaw/artifacts/05-plan.md\` |
|
|
225
|
+
| tdd | \`test-author\` | \`.cclaw/artifacts/06-tdd.md\` |
|
|
226
|
+
| review | \`reviewer\`, \`security-reviewer\` | \`.cclaw/artifacts/07-review.md\` |
|
|
227
|
+
| ship | \`doc-updater\` | \`.cclaw/artifacts/08-ship.md\` |
|
|
228
|
+
|
|
229
|
+
## Why no auto-waiver anymore?
|
|
230
|
+
|
|
231
|
+
Silent auto-waiver on Codex let entire stages complete without any
|
|
232
|
+
reviewer or test-author work. That defeats cclaw's hard gates. v0.33
|
|
233
|
+
replaces it with an explicit role-switch obligation: the agent still gets
|
|
234
|
+
a path forward, but the path is visible in the delegation log.
|
|
235
|
+
|
|
236
|
+
If a team genuinely wants to skip a delegation on Codex, they must
|
|
237
|
+
manually append a \`status: "waived"\` row with a one-line
|
|
238
|
+
\`waiverReason\` — the same audit trail any Claude/Cursor install would
|
|
239
|
+
need.
|
|
240
|
+
|
|
241
|
+
## Verification
|
|
242
|
+
|
|
243
|
+
\`cclaw doctor\` passes when every mandatory agent for the active stage
|
|
244
|
+
has a \`completed\` row with \`fulfillmentMode: "role-switch"\` and at
|
|
245
|
+
least one \`evidenceRef\`.
|
|
246
|
+
`;
|
|
247
|
+
const PLAYBOOK_BY_HARNESS = {
|
|
248
|
+
claude: CLAUDE_PLAYBOOK,
|
|
249
|
+
cursor: CURSOR_PLAYBOOK,
|
|
250
|
+
opencode: OPENCODE_PLAYBOOK,
|
|
251
|
+
codex: CODEX_PLAYBOOK
|
|
252
|
+
};
|
|
253
|
+
export function harnessPlaybookMarkdown(harness) {
|
|
254
|
+
const body = PLAYBOOK_BY_HARNESS[harness];
|
|
255
|
+
if (!body) {
|
|
256
|
+
throw new Error(`No playbook defined for harness "${harness}".`);
|
|
257
|
+
}
|
|
258
|
+
return body;
|
|
259
|
+
}
|
|
260
|
+
export function harnessPlaybooksIndexMarkdown() {
|
|
261
|
+
const rows = Object.keys(HARNESS_ADAPTERS)
|
|
262
|
+
.map((h) => {
|
|
263
|
+
const fallback = HARNESS_ADAPTERS[h].capabilities.subagentFallback;
|
|
264
|
+
return `| \`${h}\` | ${fallback} | [\`${harnessPlaybookFileName(h)}\`](./${harnessPlaybookFileName(h)}) |`;
|
|
265
|
+
})
|
|
266
|
+
.join("\n");
|
|
267
|
+
return `# Harness parity playbooks
|
|
268
|
+
|
|
269
|
+
Each playbook describes the concrete pattern cclaw expects when the
|
|
270
|
+
harness does not natively satisfy a mandatory delegation contract.
|
|
271
|
+
|
|
272
|
+
| Harness | Fallback | Playbook |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
${rows}
|
|
275
|
+
|
|
276
|
+
## How cclaw uses these files
|
|
277
|
+
|
|
278
|
+
- \`cclaw doctor\` verifies that every installed harness has its playbook
|
|
279
|
+
present under \`.cclaw/references/harnesses/\`.
|
|
280
|
+
- Stage skills (TDD, review, ship) cite the active harness's playbook
|
|
281
|
+
instead of inlining the fallback pattern.
|
|
282
|
+
- The \`delegation:mandatory:current_stage\` check expects
|
|
283
|
+
\`fulfillmentMode\` to match the harness's declared \`subagentFallback\`
|
|
284
|
+
(\`isolated\`, \`generic-dispatch\`, or \`role-switch\`).
|
|
285
|
+
|
|
286
|
+
## When to edit
|
|
287
|
+
|
|
288
|
+
Playbooks are generated by \`cclaw upgrade\`. Local edits are overwritten.
|
|
289
|
+
To customise the parity pattern for a specific repository, override the
|
|
290
|
+
skill that cites the playbook, not the playbook itself.
|
|
291
|
+
`;
|
|
292
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HARNESS_ADAPTERS, harnessTier } from "../harness-adapters.js";
|
|
2
2
|
import { HOOK_EVENTS_BY_HARNESS, HOOK_SEMANTIC_EVENTS } from "./hook-events.js";
|
|
3
|
+
import { HARNESS_PLAYBOOKS_DIR, harnessPlaybookFileName } from "./harness-playbooks.js";
|
|
3
4
|
function harnessTitle(harness) {
|
|
4
5
|
switch (harness) {
|
|
5
6
|
case "claude":
|
|
@@ -25,7 +26,9 @@ export function harnessIntegrationDocMarkdown() {
|
|
|
25
26
|
.map((harness) => {
|
|
26
27
|
const adapter = HARNESS_ADAPTERS[harness];
|
|
27
28
|
const tier = harnessTier(harness);
|
|
28
|
-
|
|
29
|
+
const caps = adapter.capabilities;
|
|
30
|
+
const playbook = `\`${HARNESS_PLAYBOOKS_DIR}/${harnessPlaybookFileName(harness)}\``;
|
|
31
|
+
return `| ${harnessTitle(harness)} | \`${harness}\` | \`${tier}\` (${tierDescription(tier)}) | ${caps.nativeSubagentDispatch} | ${caps.subagentFallback} | ${caps.hookSurface} | ${caps.structuredAsk} | ${playbook} |`;
|
|
29
32
|
})
|
|
30
33
|
.join("\n");
|
|
31
34
|
const hookRows = HOOK_SEMANTIC_EVENTS.map((eventName) => {
|
|
@@ -43,10 +46,17 @@ Generated from \`src/harness-adapters.ts\` capabilities and hook event mappings.
|
|
|
43
46
|
|
|
44
47
|
## Capability tiers
|
|
45
48
|
|
|
46
|
-
| Harness | ID | Tier | Native
|
|
47
|
-
|
|
49
|
+
| Harness | ID | Tier | Native dispatch | Fallback | Hook surface | Structured ask | Playbook |
|
|
50
|
+
|---|---|---|---|---|---|---|---|
|
|
48
51
|
${capabilityRows}
|
|
49
52
|
|
|
53
|
+
Fallback legend:
|
|
54
|
+
|
|
55
|
+
- \`native\` — first-class named subagent dispatch (Claude).
|
|
56
|
+
- \`generic-dispatch\` — generic Task dispatcher mapped to cclaw roles (Cursor).
|
|
57
|
+
- \`role-switch\` — in-session role announce + delegation-log entry with evidenceRefs (OpenCode, Codex).
|
|
58
|
+
- \`waiver\` — no parity path; reserved for harnesses that cannot role-switch (none shipped).
|
|
59
|
+
|
|
50
60
|
## Semantic hook event coverage
|
|
51
61
|
|
|
52
62
|
| Event | Claude | Cursor | OpenCode | Codex |
|
|
@@ -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
|
|
47
|
-
|
|
48
|
-
| Claude | Task
|
|
49
|
-
| Cursor |
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
package/dist/delegation.d.ts
CHANGED
|
@@ -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
|
}>;
|
package/dist/delegation.js
CHANGED
|
@@ -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
|
|
146
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|
package/dist/harness-adapters.js
CHANGED
|
@@ -54,16 +54,22 @@ export const HARNESS_ADAPTERS = {
|
|
|
54
54
|
capabilities: {
|
|
55
55
|
nativeSubagentDispatch: "full",
|
|
56
56
|
hookSurface: "full",
|
|
57
|
-
structuredAsk: "AskUserQuestion"
|
|
57
|
+
structuredAsk: "AskUserQuestion",
|
|
58
|
+
subagentFallback: "native"
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
61
|
cursor: {
|
|
61
62
|
id: "cursor",
|
|
62
63
|
commandDir: ".cursor/commands",
|
|
63
64
|
capabilities: {
|
|
64
|
-
|
|
65
|
+
// Cursor has a real Task tool with subagent_type (generalPurpose,
|
|
66
|
+
// explore, shell, browser-use, …) but no user-defined named
|
|
67
|
+
// subagents. cclaw maps each named agent (planner/reviewer/…) onto
|
|
68
|
+
// generic dispatch with a role prompt — see the cursor playbook.
|
|
69
|
+
nativeSubagentDispatch: "generic",
|
|
65
70
|
hookSurface: "full",
|
|
66
|
-
structuredAsk: "AskQuestion"
|
|
71
|
+
structuredAsk: "AskQuestion",
|
|
72
|
+
subagentFallback: "generic-dispatch"
|
|
67
73
|
}
|
|
68
74
|
},
|
|
69
75
|
opencode: {
|
|
@@ -72,7 +78,8 @@ export const HARNESS_ADAPTERS = {
|
|
|
72
78
|
capabilities: {
|
|
73
79
|
nativeSubagentDispatch: "partial",
|
|
74
80
|
hookSurface: "plugin",
|
|
75
|
-
structuredAsk: "plain-text"
|
|
81
|
+
structuredAsk: "plain-text",
|
|
82
|
+
subagentFallback: "role-switch"
|
|
76
83
|
}
|
|
77
84
|
},
|
|
78
85
|
codex: {
|
|
@@ -81,7 +88,8 @@ export const HARNESS_ADAPTERS = {
|
|
|
81
88
|
capabilities: {
|
|
82
89
|
nativeSubagentDispatch: "none",
|
|
83
90
|
hookSurface: "full",
|
|
84
|
-
structuredAsk: "plain-text"
|
|
91
|
+
structuredAsk: "plain-text",
|
|
92
|
+
subagentFallback: "role-switch"
|
|
85
93
|
}
|
|
86
94
|
}
|
|
87
95
|
};
|
|
@@ -94,11 +102,22 @@ export function harnessTier(harnessId) {
|
|
|
94
102
|
}
|
|
95
103
|
if (capabilities.hookSurface === "full" ||
|
|
96
104
|
capabilities.hookSurface === "plugin" ||
|
|
105
|
+
capabilities.nativeSubagentDispatch === "generic" ||
|
|
97
106
|
capabilities.nativeSubagentDispatch === "partial") {
|
|
98
107
|
return "tier2";
|
|
99
108
|
}
|
|
100
109
|
return "tier3";
|
|
101
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Harness IDs ordered from best (tier1) to least-capable. Stable sort — same
|
|
113
|
+
* tier preserves declaration order.
|
|
114
|
+
*/
|
|
115
|
+
export function harnessesByTier() {
|
|
116
|
+
return Object.keys(HARNESS_ADAPTERS).sort((a, b) => {
|
|
117
|
+
const tierOrder = { tier1: 0, tier2: 1, tier3: 2 };
|
|
118
|
+
return tierOrder[harnessTier(a)] - tierOrder[harnessTier(b)];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
102
121
|
function agentsMdBlock() {
|
|
103
122
|
return `${CCLAW_MARKER_START}
|
|
104
123
|
## Cclaw — Workflow Adapter
|
package/dist/install.js
CHANGED
|
@@ -37,6 +37,7 @@ import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
|
|
|
37
37
|
import { HARNESS_TOOL_REFS_DIR, HARNESS_TOOL_REFS_INDEX_MD, harnessToolRefMarkdown } from "./content/harness-tool-refs.js";
|
|
38
38
|
import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
|
|
39
39
|
import { harnessIntegrationDocMarkdown } from "./content/harnesses-doc.js";
|
|
40
|
+
import { HARNESS_PLAYBOOKS_DIR, harnessPlaybookFileName, harnessPlaybookMarkdown, harnessPlaybooksIndexMarkdown } from "./content/harness-playbooks.js";
|
|
40
41
|
import { HOOK_EVENTS_BY_HARNESS, HOOK_SEMANTIC_EVENTS } from "./content/hook-events.js";
|
|
41
42
|
import { createInitialFlowState } from "./flow-state.js";
|
|
42
43
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
@@ -293,6 +294,15 @@ async function writeSkills(projectRoot, config) {
|
|
|
293
294
|
await writeFileSafe(runtimePath(projectRoot, ...doctorRefsDir, fileName), markdown);
|
|
294
295
|
}
|
|
295
296
|
await writeFileSafe(runtimePath(projectRoot, "references", "harnesses.md"), harnessIntegrationDocMarkdown());
|
|
297
|
+
// Per-harness parity playbooks. Generated for every supported harness
|
|
298
|
+
// regardless of which harnesses the project installed — the index always
|
|
299
|
+
// resolves, and doctor only asserts presence of the installed harnesses'
|
|
300
|
+
// playbooks (see runtime-integrity checks).
|
|
301
|
+
const playbookDirSegments = HARNESS_PLAYBOOKS_DIR.split("/");
|
|
302
|
+
await writeFileSafe(runtimePath(projectRoot, ...playbookDirSegments, "README.md"), harnessPlaybooksIndexMarkdown());
|
|
303
|
+
for (const harness of harnessIds) {
|
|
304
|
+
await writeFileSafe(runtimePath(projectRoot, ...playbookDirSegments, harnessPlaybookFileName(harness)), harnessPlaybookMarkdown(harness));
|
|
305
|
+
}
|
|
296
306
|
}
|
|
297
307
|
async function writeUtilityCommands(projectRoot) {
|
|
298
308
|
await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
|
|
@@ -948,15 +958,40 @@ async function writeHarnessGapsState(projectRoot, harnesses) {
|
|
|
948
958
|
if (capabilities.structuredAsk === "plain-text") {
|
|
949
959
|
missingCapabilities.push("structuredAsk:none");
|
|
950
960
|
}
|
|
961
|
+
const remediation = [];
|
|
962
|
+
switch (capabilities.subagentFallback) {
|
|
963
|
+
case "native":
|
|
964
|
+
// nothing to remediate — harness has first-class dispatch
|
|
965
|
+
break;
|
|
966
|
+
case "generic-dispatch":
|
|
967
|
+
remediation.push(`subagent dispatch → map named cclaw agents onto generic Task subagent_type per ${HARNESS_PLAYBOOKS_DIR}/${harness}-playbook.md`);
|
|
968
|
+
break;
|
|
969
|
+
case "role-switch":
|
|
970
|
+
remediation.push(`subagent dispatch → role-switch in-session with evidenceRefs per ${HARNESS_PLAYBOOKS_DIR}/${harness}-playbook.md`);
|
|
971
|
+
break;
|
|
972
|
+
case "waiver":
|
|
973
|
+
remediation.push(`subagent dispatch → record explicit harness_limitation waiver; no parity path available`);
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
if (capabilities.structuredAsk === "plain-text") {
|
|
977
|
+
remediation.push("structured ask → fall back to a numbered plain-text list; first option is default");
|
|
978
|
+
}
|
|
979
|
+
for (const event of missingHookEvents) {
|
|
980
|
+
remediation.push(`hook event ${event} → schedule the corresponding script manually or accept reduced observability`);
|
|
981
|
+
}
|
|
951
982
|
return {
|
|
952
983
|
harness,
|
|
953
984
|
tier: harnessTier(harness),
|
|
985
|
+
subagentFallback: capabilities.subagentFallback,
|
|
986
|
+
playbookPath: `${RUNTIME_ROOT}/${HARNESS_PLAYBOOKS_DIR}/${harness}-playbook.md`,
|
|
954
987
|
missingCapabilities,
|
|
955
|
-
missingHookEvents
|
|
988
|
+
missingHookEvents,
|
|
989
|
+
remediation
|
|
956
990
|
};
|
|
957
991
|
});
|
|
958
992
|
await writeFileSafe(runtimePath(projectRoot, "state", "harness-gaps.json"), `${JSON.stringify({
|
|
959
993
|
generatedAt: new Date().toISOString(),
|
|
994
|
+
schemaVersion: 2,
|
|
960
995
|
harnesses: report
|
|
961
996
|
}, null, 2)}\n`);
|
|
962
997
|
}
|