cclaw-cli 0.51.22 → 0.51.23
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 +14 -13
- package/dist/content/examples.js +2 -2
- package/dist/content/hook-manifest.js +1 -4
- package/dist/content/learnings.js +5 -2
- package/dist/content/meta-skill.d.ts +1 -0
- package/dist/content/meta-skill.js +10 -1
- package/dist/content/node-hooks.js +1 -1
- package/dist/content/skills.js +14 -10
- package/dist/content/stage-command.d.ts +2 -0
- package/dist/content/stage-command.js +17 -0
- package/dist/content/stages/review.js +7 -7
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/tdd.js +10 -3
- package/dist/content/subagents.js +14 -4
- package/dist/content/templates.js +9 -9
- package/dist/content/track-render-context.js +7 -0
- package/dist/delegation.d.ts +2 -2
- package/dist/delegation.js +16 -9
- package/dist/doctor-registry.js +1 -1
- package/dist/doctor.js +75 -13
- package/dist/harness-adapters.d.ts +14 -11
- package/dist/harness-adapters.js +153 -17
- package/dist/install.js +29 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -344,8 +344,9 @@ The `tdd` stage is not prose guidance. It requires:
|
|
|
344
344
|
- optional **REFACTOR** pass with coverage preservation
|
|
345
345
|
|
|
346
346
|
`/cc-next` will not advance past `tdd` until the delegation log shows the
|
|
347
|
-
subagent as `completed
|
|
348
|
-
|
|
347
|
+
subagent as `completed`. Codex and OpenCode use generated native subagents
|
|
348
|
+
by default; a role-switch row is only a degraded fallback and must include
|
|
349
|
+
`evidenceRefs` — see [Harness support](#harness-support).
|
|
349
350
|
|
|
350
351
|
---
|
|
351
352
|
|
|
@@ -391,25 +392,25 @@ closes every real gap with a documented fallback — not a silent waiver.
|
|
|
391
392
|
|---|---|---|---|---|
|
|
392
393
|
| Claude Code | full (named subagents) | `native` | full | `AskUserQuestion` |
|
|
393
394
|
| Cursor | generic Task dispatcher | `generic-dispatch` | full | `AskQuestion` |
|
|
394
|
-
| OpenCode |
|
|
395
|
-
| OpenAI Codex |
|
|
395
|
+
| OpenCode | native subagents (`.opencode/agents`) | `native` | plugin | `question` (permission-gated; `permission.question: "allow"`) |
|
|
396
|
+
| OpenAI Codex | native parallel subagents (`.codex/agents`) | `native` | limited (Bash-only `PreToolUse`/`PostToolUse`; requires `codex_hooks` feature flag) | `request_user_input` (experimental; Plan / Collaboration mode) |
|
|
396
397
|
|
|
397
398
|
What the fallbacks mean:
|
|
398
399
|
|
|
399
|
-
- `native` — Claude
|
|
400
|
-
workers; cclaw records them with `fulfillmentMode: "isolated"`.
|
|
400
|
+
- `native` — Claude, OpenCode, and Codex run mandatory delegations in
|
|
401
|
+
isolated subagent workers; cclaw records them with `fulfillmentMode: "isolated"`.
|
|
401
402
|
- `generic-dispatch` — Cursor has a real Task tool with a fixed
|
|
402
403
|
vocabulary of `subagent_type`s (`explore`, `generalPurpose`, …).
|
|
403
404
|
cclaw maps each named agent (planner / reviewer / test-author /
|
|
404
405
|
security-reviewer / doc-updater) onto the generic dispatcher with a
|
|
405
406
|
structured role prompt.
|
|
406
|
-
- `role-switch` —
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
407
|
+
- `role-switch` — degraded fallback only when the active runtime cannot
|
|
408
|
+
expose its declared dispatch surface. The agent announces
|
|
409
|
+
`## cclaw role-switch: <stage>/<agent> (<mode>)`, loads
|
|
410
|
+
`.cclaw/agents/<agent>.md`, writes outputs to the stage artifact, and
|
|
411
|
+
records a delegation row with `fulfillmentMode: "role-switch"` plus at
|
|
412
|
+
least one `evidenceRef`. A role-switch `completed` row without
|
|
413
|
+
evidenceRefs is classified as `missingEvidence` by `cclaw doctor`.
|
|
413
414
|
- `waiver` — reserved. Only fires auto-waivers if every installed
|
|
414
415
|
harness declares it. Currently unused — v0.33 removed the old
|
|
415
416
|
Codex-only auto-waiver path.
|
package/dist/content/examples.js
CHANGED
|
@@ -726,8 +726,8 @@ const DOMAIN_LABELS = {
|
|
|
726
726
|
export const RESEARCH_FLEET_USAGE_EXAMPLE = [
|
|
727
727
|
"Before drafting `03-design.md`, run `research/research-fleet.md` once and",
|
|
728
728
|
"capture all four lenses in `.cclaw/artifacts/02a-research.md`.",
|
|
729
|
-
"Dispatch semantics by harness: Claude/
|
|
730
|
-
"
|
|
729
|
+
"Dispatch semantics by harness: Claude/OpenCode/Codex = native subagents;",
|
|
730
|
+
"Cursor = generic-dispatch Task mapping; role-switch only as degraded fallback.",
|
|
731
731
|
"Design must include a `Research Fleet Synthesis` section that maps each",
|
|
732
732
|
"lens to concrete architecture decisions and risks."
|
|
733
733
|
].join(" ");
|
|
@@ -74,10 +74,7 @@ export const HOOK_MANIFEST = [
|
|
|
74
74
|
bindings: {
|
|
75
75
|
claude: [{ event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash" }],
|
|
76
76
|
cursor: [{ event: "preToolUse", matcher: "*" }],
|
|
77
|
-
codex: [
|
|
78
|
-
{ event: "UserPromptSubmit" },
|
|
79
|
-
{ event: "PreToolUse", matcher: "Bash|bash" }
|
|
80
|
-
]
|
|
77
|
+
codex: [{ event: "PreToolUse", matcher: "Bash|bash" }]
|
|
81
78
|
}
|
|
82
79
|
},
|
|
83
80
|
{
|
|
@@ -78,7 +78,7 @@ Do not invent alternate stores (no markdown mirror, no SQLite, no per-stage file
|
|
|
78
78
|
|
|
79
79
|
Exactly one JSON object per line. Required fields must appear in the order:
|
|
80
80
|
\`type, trigger, action, confidence, domain, stage, origin_stage, origin_run, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`.
|
|
81
|
-
Optional fields \`source\` and \`
|
|
81
|
+
Optional fields \`source\`, \`severity\`, \`supersedes\`, and \`superseded_by\` may be appended after \`project\`.
|
|
82
82
|
|
|
83
83
|
\`\`\`json
|
|
84
84
|
{"type":"pattern","trigger":"when reviewing external payloads","action":"parse through zod before touching service layer","confidence":"high","domain":"api","stage":"review","origin_stage":"review","origin_run":"payload-hardening","frequency":1,"universality":"project","maturity":"raw","created":"2026-04-14T12:00:00Z","first_seen_ts":"2026-04-14T12:00:00Z","last_seen_ts":"2026-04-14T12:00:00Z","project":"cclaw"}
|
|
@@ -103,12 +103,15 @@ Optional fields \`source\` and \`severity\` may be appended after \`project\`.
|
|
|
103
103
|
| \`project\` | string \\| null | yes | Repo or scope name. Use \`null\` when the entry crosses projects. |
|
|
104
104
|
| \`source\` | \`"stage" \\| "retro" \\| "compound" \\| "ideate" \\| "manual" \\| null\` | no | Origin channel for the entry when known. |
|
|
105
105
|
| \`severity\` | \`"critical" \\| "important" \\| "suggestion"\` | no | Priority signal for compound lifts; \`critical\` enables single-hit override in compound readiness analysis. |
|
|
106
|
+
| \`supersedes\` | string[] | no | Non-empty IDs/slugs of older entries this entry refreshes. Use only for clear replacements discovered during compound closeout or curation. |
|
|
107
|
+
| \`superseded_by\` | string | no | Non-empty ID/slug of the newer entry that refreshes this one. Use only when marking stale guidance as replaced. |
|
|
106
108
|
|
|
107
109
|
Rules:
|
|
108
110
|
- No other fields beyond the table above. Extra keys are forbidden and MUST be rejected by any writer.
|
|
109
111
|
- Every required-null field must be emitted explicitly as \`null\` (not omitted). This keeps the file grep-friendly.
|
|
110
112
|
- Append-only: never rewrite or delete a historical line. Corrections are new
|
|
111
|
-
entries whose \`trigger\` clearly supersedes the earlier one
|
|
113
|
+
entries whose \`trigger\` clearly supersedes the earlier one; add \`supersedes\` /
|
|
114
|
+
\`superseded_by\` only when the replacement relationship is clear.
|
|
112
115
|
- Keep each entry one line. No pretty-printing. No trailing commas.
|
|
113
116
|
|
|
114
117
|
## Curation policy (target: ≤ 50 active entries)
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { conversationLanguagePolicyMarkdown } from "./language-policy.js";
|
|
2
2
|
import { CLOSEOUT_CHAIN, closeoutChainInline, closeoutFlowMapSentence, closeoutProtocolBehaviorSentence } from "./closeout-guidance.js";
|
|
3
3
|
export const META_SKILL_NAME = "using-cclaw";
|
|
4
|
+
export const META_SKILL_GENERATED_HELPER_SKILLS = [
|
|
5
|
+
"subagent-dev",
|
|
6
|
+
"parallel-dispatch",
|
|
7
|
+
"session",
|
|
8
|
+
"iron-laws"
|
|
9
|
+
];
|
|
10
|
+
function generatedHelperSkillList() {
|
|
11
|
+
return META_SKILL_GENERATED_HELPER_SKILLS.map((name) => `\`${name}\``).join(", ");
|
|
12
|
+
}
|
|
4
13
|
export function usingCclawSkillMarkdown() {
|
|
5
14
|
return `---
|
|
6
15
|
name: using-cclaw
|
|
@@ -25,7 +34,7 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
25
34
|
If \`.cclaw/state/flow-state.json\` exists and \`currentStage\` is set,
|
|
26
35
|
load the matching stage SKILL before producing **substantive** work
|
|
27
36
|
(artifact edits, code, structured clarifying questions). Do not improvise
|
|
28
|
-
from memory. Load only generated helper surfaces that actually exist in this install:
|
|
37
|
+
from memory. Load only generated helper surfaces that actually exist in this install: ${generatedHelperSkillList()}, research playbooks, review prompts, or enabled language rule packs under \`.cclaw/rules/lang/\`. Do not invent helper-skill names beyond those generated surfaces.
|
|
29
38
|
|
|
30
39
|
Substantive vs. non-substantive:
|
|
31
40
|
|
|
@@ -1636,7 +1636,7 @@ async function handleVerifyCurrentState(runtime) {
|
|
|
1636
1636
|
process.stderr.write(result.stderr.trim().length > 0
|
|
1637
1637
|
? result.stderr
|
|
1638
1638
|
: "[cclaw] hook: local Node runtime entrypoint is required for verify-current-state\\n");
|
|
1639
|
-
return 1;
|
|
1639
|
+
return mode === "strict" ? 1 : 0;
|
|
1640
1640
|
}
|
|
1641
1641
|
if (mode === "strict") {
|
|
1642
1642
|
if (result.code !== 0) {
|
package/dist/content/skills.js
CHANGED
|
@@ -61,23 +61,24 @@ function autoSubagentDispatchBlock(stage, track) {
|
|
|
61
61
|
const rules = stageAutoSubagentDispatch(stage);
|
|
62
62
|
if (rules.length === 0)
|
|
63
63
|
return "";
|
|
64
|
+
const schema = stageSchema(stage, track);
|
|
64
65
|
const rows = rules
|
|
65
66
|
.map((rule) => {
|
|
66
67
|
const userGate = rule.requiresUserGate ? "required" : "not required";
|
|
67
|
-
return `| ${rule.agent} | ${rule.mode} | ${userGate} | ${rule.when} |`;
|
|
68
|
+
return `| ${rule.agent} | ${rule.mode} | ${userGate} | ${rule.when} | ${rule.purpose} |`;
|
|
68
69
|
})
|
|
69
70
|
.join("\n");
|
|
70
|
-
const mandatory =
|
|
71
|
+
const mandatory = schema.mandatoryDelegations;
|
|
71
72
|
const mandatoryList = mandatory.length > 0 ? mandatory.map((a) => `\`${a}\``).join(", ") : "none";
|
|
72
73
|
const delegationLogRel = `${RUNTIME_ROOT}/state/delegation-log.json`;
|
|
74
|
+
const artifactRef = `${RUNTIME_ROOT}/artifacts/${schema.artifactRules.artifactFile}`;
|
|
73
75
|
return `## Automatic Subagent Dispatch
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|---|---|---|---|
|
|
76
|
+
| Agent | Mode | User Gate | Trigger | Purpose |
|
|
77
|
+
|---|---|---|---|---|
|
|
77
78
|
${rows}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
Mandatory: ${mandatoryList}. Record completion/waiver in \`${delegationLogRel}\` before completion.
|
|
80
|
+
### Harness Dispatch Contract
|
|
81
|
+
Use true harness dispatch: Claude native Task, Cursor generic dispatch, OpenCode \`.opencode/agents/<agent>.md\`, Codex \`.codex/agents/<agent>.toml\`. Run independent read-only/review agents in parallel where safe, write evidence into \`${artifactRef}\`, then append \`${delegationLogRel}\` rows with matching \`fulfillmentMode: "isolated"\` or \`"generic-dispatch"\`. Do not collapse OpenCode or Codex to role-switch by default; role-switch is degraded fallback and must carry non-empty \`evidenceRefs\`. Missing evidence blocks completion.
|
|
81
82
|
`;
|
|
82
83
|
}
|
|
83
84
|
function researchPlaybooksBlock(playbooks) {
|
|
@@ -160,15 +161,18 @@ function batchExecutionModeBlock(stage, track) {
|
|
|
160
161
|
const schema = stageSchema(stage, track);
|
|
161
162
|
if (!schema.batchExecutionAllowed)
|
|
162
163
|
return "";
|
|
164
|
+
const orderingGuidance = track === "quick"
|
|
165
|
+
? "Use spec acceptance items / bug reproduction slices for ordering."
|
|
166
|
+
: "Use plan slices for ordering.";
|
|
163
167
|
return `## Batch Execution Mode
|
|
164
168
|
|
|
165
169
|
Execute the current dependency batch task-by-task (RED -> GREEN -> REFACTOR).
|
|
166
170
|
Stop on BLOCKED status or when user input is required.
|
|
167
|
-
Apply concise turn announces: one announce per batch boundary (or when risk/
|
|
171
|
+
Apply concise turn announces: one announce per batch boundary (or when risk/source
|
|
168
172
|
changes materially), then execute tasks without repetitive boilerplate.
|
|
169
173
|
|
|
170
174
|
Detailed walkthrough:
|
|
171
|
-
|
|
175
|
+
${orderingGuidance} Keep RED -> GREEN -> REFACTOR evidence in the TDD artifact.
|
|
172
176
|
`;
|
|
173
177
|
}
|
|
174
178
|
function crossStageTraceBlock(trace) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
import { stageSkillFolder } from "./skills.js";
|
|
3
|
+
export function stageCommandShimMarkdown(stage) {
|
|
4
|
+
const skillPath = `${RUNTIME_ROOT}/skills/${stageSkillFolder(stage)}/SKILL.md`;
|
|
5
|
+
return `# /cc-${stage}
|
|
6
|
+
|
|
7
|
+
This is a thin compatibility shim for the \`${stage}\` flow stage.
|
|
8
|
+
|
|
9
|
+
Load and follow the authoritative stage skill:
|
|
10
|
+
|
|
11
|
+
- \`${skillPath}\`
|
|
12
|
+
|
|
13
|
+
Normal stage resume and advancement uses \`/cc-next\`. Use \`/cc-next\` to read
|
|
14
|
+
\`.cclaw/state/flow-state.json\`, select the active stage, and advance only after
|
|
15
|
+
that stage's gates pass. Do not duplicate the stage protocol here.
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
@@ -35,8 +35,8 @@ export const REVIEW = {
|
|
|
35
35
|
"Diff Scope — Run `git diff` against base branch. If no diff, exit early with APPROVED (no changes to review). Scope the review to changed files unless blast-radius analysis requires wider inspection.",
|
|
36
36
|
"Change-Size Check — ~100 lines = normal. ~300 lines = consider splitting. ~1000+ lines = strongly recommend stacked PRs. Flag large diffs to the user.",
|
|
37
37
|
"Risk-Based Second Opinion — compute changed-line count, files-touched count, and trust-boundary movement. Dispatch an adversarial reviewer only when trust boundaries changed, Critical/Important ambiguity remains, or the diff is both large and high-risk; otherwise record `not triggered`.",
|
|
38
|
-
"Load upstream evidence — read TDD artifact (RED + GREEN + REFACTOR), spec, and
|
|
39
|
-
"Run traceability matrix when
|
|
38
|
+
"Load upstream evidence — read TDD artifact (RED + GREEN + REFACTOR), spec, and the active track's upstream source items.",
|
|
39
|
+
"Run traceability matrix when the active track enforces it; otherwise confirm spec acceptance/reproduction slices are covered directly.",
|
|
40
40
|
"Layer 1: Spec Compliance — check every acceptance criterion against implementation. Verdict: pass/fail per criterion.",
|
|
41
41
|
"Layer 2: Integrated findings — one structured pass tagged by category: correctness, security, performance, architecture, external-safety.",
|
|
42
42
|
"Security sweep — mandatory dedicated security-reviewer pass across diff + touched modules. A zero-finding pass must include `NO_CHANGE_ATTESTATION` with rationale.",
|
|
@@ -72,12 +72,12 @@ export const REVIEW = {
|
|
|
72
72
|
{ id: "review_layer_coverage_complete", description: "Layer coverage map in 07-review-army.json confirms spec/correctness/security/performance/architecture/external-safety tags were considered." },
|
|
73
73
|
{ id: "review_criticals_resolved", description: "Normal APPROVED or APPROVED_WITH_CONCERNS path only: no unresolved critical blockers remain. BLOCKED routes use review_verdict_blocked instead." },
|
|
74
74
|
{ id: "review_army_json_valid", description: "07-review-army.json passes schema validation (validateReviewArmy)." },
|
|
75
|
-
{ id: "review_trace_matrix_clean", description: "Trace matrix has no orphaned
|
|
75
|
+
{ id: "review_trace_matrix_clean", description: "Trace matrix has no orphaned source items or test slices for the active run, and evidence cites a discovered real test command before ship handoff." }
|
|
76
76
|
],
|
|
77
77
|
requiredEvidence: [
|
|
78
78
|
"Artifact written to `.cclaw/artifacts/07-review.md`.",
|
|
79
79
|
"Artifact written to `.cclaw/artifacts/07-review-army.json`.",
|
|
80
|
-
"Traceability matrix run recorded (no orphaned
|
|
80
|
+
"Traceability matrix run recorded (no orphaned source items or tests for enforced tracks).",
|
|
81
81
|
"Layer 1 verdict captured with per-criterion pass/fail.",
|
|
82
82
|
"Layer 2 sections completed across correctness, security, performance, architecture, and external-safety findings.",
|
|
83
83
|
"Severity log includes critical/important/suggestion buckets.",
|
|
@@ -85,7 +85,7 @@ export const REVIEW = {
|
|
|
85
85
|
"Fresh verification command discovery recorded, and the command cited in `review_trace_matrix_clean` evidence before ship handoff.",
|
|
86
86
|
"If BLOCKED: include explicit remediation route (`ROUTE_BACK_TO_TDD`) with blocking finding IDs."
|
|
87
87
|
],
|
|
88
|
-
inputs: ["implementation diff", "
|
|
88
|
+
inputs: ["implementation diff", "upstream artifacts", "test/build evidence"],
|
|
89
89
|
requiredContext: ["spec criteria", "tdd artifact", "rulebook constraints"],
|
|
90
90
|
blockers: [
|
|
91
91
|
"layer 1 failed",
|
|
@@ -118,9 +118,9 @@ export const REVIEW = {
|
|
|
118
118
|
{ section: "Layer 2 Findings", required: false, validationRule: "Each finding has severity, description, and resolution status across correctness, security, performance, architecture, and external-safety. Security coverage must include either explicit security findings or `NO_CHANGE_ATTESTATION: <reason>` when no security-relevant changes were found." },
|
|
119
119
|
{ section: "Review Findings Contract", required: true, validationRule: "Structured findings in 07-review-army.json include id/severity/confidence/fingerprint/reportedBy/status and source tags from {spec, correctness, security, performance, architecture, external-safety} with dedup reconciliation summary." },
|
|
120
120
|
{ section: "Review Readiness Snapshot", required: false, validationRule: "Optional compact summary: completed checks, delegation-log status, staleness signal, open critical blockers, and ship recommendation." },
|
|
121
|
-
{ section: "Completeness Snapshot", required: false, validationRule: "Optional compact coverage summary for AC coverage,
|
|
121
|
+
{ section: "Completeness Snapshot", required: false, validationRule: "Optional compact coverage summary for AC coverage, source item coverage, test-slice coverage, and adversarial-review status when triggered." },
|
|
122
122
|
{ section: "Incoming Feedback Queue", required: false, validationRule: "When external review feedback exists, include a queue summary with per-item disposition (resolved / accepted-risk / rejected-with-evidence) and evidence refs." },
|
|
123
|
-
{ section: "Trace Matrix Check", required: false, validationRule: "Records
|
|
123
|
+
{ section: "Trace Matrix Check", required: false, validationRule: "Records source-item/test orphan counts (all zero on enforced tracks) with command output reference." },
|
|
124
124
|
{ section: "Blocked Route", required: false, validationRule: "When Final Verdict is BLOCKED: includes `ROUTE_BACK_TO_TDD`, rewind target `tdd`, and blocked finding IDs." },
|
|
125
125
|
{ section: "Severity Summary", required: true, validationRule: "Per-severity count lines for critical, important, and suggestion buckets." },
|
|
126
126
|
{ section: "Final Verdict", required: true, validationRule: "Exactly one of: APPROVED, APPROVED_WITH_CONCERNS, BLOCKED." }
|
|
@@ -65,7 +65,7 @@ export const SCOPE = {
|
|
|
65
65
|
"If the user says no but cannot name the change, offer concrete moves: keep scope, add one obvious adjacent capability, reduce to wedge, or re-open stack/product direction.",
|
|
66
66
|
`Before final approval, record outside-voice findings and a \`## Scope Outside Voice Loop\` table using ${reviewLoopPolicySummary("scope")}`,
|
|
67
67
|
"**STOP.** Wait for explicit user approval of the scope mode and scope contract before writing final approval language or advancing.",
|
|
68
|
-
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be completed or explicitly waived. If
|
|
68
|
+
"**STOP BEFORE ADVANCE.** Mandatory delegation `planner` must be completed or explicitly waived for a real blocker. If the active harness cannot isolate a planner, run a role-switch planner pass instead: announce `## cclaw role-switch: scope/planner (mandatory)`, write the planner output/evidence into the scope artifact, and append a completed delegation row with `fulfillmentMode: \"role-switch\"` plus non-empty `evidenceRefs`. Then close with `node .cclaw/hooks/stage-complete.mjs scope --passed=scope_mode_selected,scope_contract_written,scope_user_approved --evidence-json '{\"scope_mode_selected\":\"<user-approved mode + rationale>\",\"scope_contract_written\":\"<artifact path + sections>\",\"scope_user_approved\":\"<explicit user approval quote or summary>\"}'`. `scope_user_approved` must cite the user's approval; review-loop evidence alone is not approval."
|
|
69
69
|
],
|
|
70
70
|
process: [
|
|
71
71
|
"Run configured pre-scope audit only when enabled.",
|
|
@@ -241,7 +241,12 @@ function tddStageVariantForTrack(track) {
|
|
|
241
241
|
skillDescription: renderTrackTerminology(TDD.skillDescription, renderContext),
|
|
242
242
|
philosophy: {
|
|
243
243
|
...TDD.philosophy,
|
|
244
|
-
hardGate: renderTrackTerminology(TDD.philosophy.hardGate, renderContext)
|
|
244
|
+
hardGate: renderTrackTerminology(TDD.philosophy.hardGate, renderContext),
|
|
245
|
+
purpose: renderTrackTerminology(TDD.philosophy.purpose, renderContext),
|
|
246
|
+
whenToUse: TDD.philosophy.whenToUse.map((value) => renderTrackTerminology(value, renderContext)),
|
|
247
|
+
whenNotToUse: TDD.philosophy.whenNotToUse.map((value) => renderTrackTerminology(value, renderContext)),
|
|
248
|
+
commonRationalizations: TDD.philosophy.commonRationalizations
|
|
249
|
+
.map((value) => renderTrackTerminology(value, renderContext))
|
|
245
250
|
},
|
|
246
251
|
executionModel: {
|
|
247
252
|
...TDD.executionModel,
|
|
@@ -258,7 +263,9 @@ function tddStageVariantForTrack(track) {
|
|
|
258
263
|
requiredEvidence: TDD.executionModel.requiredEvidence
|
|
259
264
|
.map((value) => renderTrackTerminology(value, renderContext)),
|
|
260
265
|
inputs: TDD.executionModel.inputs.map((value) => renderTrackTerminology(value, renderContext)),
|
|
261
|
-
requiredContext: [renderContext.upstreamArtifactLabel, "existing test patterns", "affected contracts and state boundaries"]
|
|
266
|
+
requiredContext: [renderContext.upstreamArtifactLabel, "existing test patterns", "affected contracts and state boundaries"],
|
|
267
|
+
blockers: TDD.executionModel.blockers.map((value) => renderTrackTerminology(value, renderContext)),
|
|
268
|
+
exitCriteria: TDD.executionModel.exitCriteria.map((value) => renderTrackTerminology(value, renderContext))
|
|
262
269
|
},
|
|
263
270
|
reviewLens: {
|
|
264
271
|
...TDD.reviewLens,
|
|
@@ -286,7 +293,7 @@ function tddStageVariantForTrack(track) {
|
|
|
286
293
|
if (row.section === "Traceability") {
|
|
287
294
|
return {
|
|
288
295
|
...row,
|
|
289
|
-
validationRule: "Spec acceptance item IDs and, for bug fixes, reproduction slice IDs are linked to RED/GREEN evidence.
|
|
296
|
+
validationRule: "Spec acceptance item IDs and, for bug fixes, reproduction slice IDs are linked to RED/GREEN evidence."
|
|
290
297
|
};
|
|
291
298
|
}
|
|
292
299
|
return {
|
|
@@ -83,16 +83,26 @@ can enforce phase-appropriate write boundaries. Use separate workers only when t
|
|
|
83
83
|
|---|---|---|---|---|
|
|
84
84
|
| Claude | \`native\` | Task (named subagent_type) | AskUserQuestion | \`npx cclaw-cli doctor\` |
|
|
85
85
|
| Cursor | \`generic-dispatch\` | Task (generic subagent_type: explore/generalPurpose/…) | AskQuestion | \`npx cclaw-cli doctor\` |
|
|
86
|
-
| OpenCode | \`
|
|
87
|
-
| Codex | \`
|
|
86
|
+
| OpenCode | \`native\` | generated \`.opencode/agents/<agent>.md\` subagents via Task / \`@agent\` mention | \`question\` (permission-gated; \`permission.question: "allow"\`) | \`npx cclaw-cli doctor\` |
|
|
87
|
+
| Codex | \`native\` | generated \`.codex/agents/<agent>.toml\` custom agents via native parallel subagent spawning | \`request_user_input\` (experimental; Plan / Collaboration mode) | \`npx cclaw-cli doctor\` |
|
|
88
88
|
|
|
89
89
|
**Dispatch rules driven by \`subagentFallback\`:**
|
|
90
90
|
|
|
91
91
|
- \`native\` — use the harness's own named subagent primitive; delegation entry uses \`fulfillmentMode: "isolated"\`.
|
|
92
92
|
- \`generic-dispatch\` — map each cclaw agent onto the generic dispatcher with a role prompt; delegation entry uses \`fulfillmentMode: "generic-dispatch"\`.
|
|
93
|
-
- \`role-switch\` —
|
|
93
|
+
- \`role-switch\` — degraded fallback only when the active runtime cannot expose its declared dispatch surface. 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.
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
### Native dispatch contract
|
|
96
|
+
|
|
97
|
+
Use real harness subagents for OpenCode and Codex:
|
|
98
|
+
|
|
99
|
+
1. OpenCode: invoke the generated \`.opencode/agents/<agent>.md\` subagent via Task or \`@<agent>\`. Built-in \`general\` / \`explore\` remain fallback subagent types for ad hoc tasks, but cclaw's core roles are generated by name.
|
|
100
|
+
2. Codex: ask Codex to spawn the generated \`.codex/agents/<agent>.toml\` custom agent(s) by name; for review-style independent lanes, request parallel spawning and wait for all results before reconciliation.
|
|
101
|
+
3. Claude: use the native named Task subagent. Cursor: map the cclaw role onto the generic Task/Subagent surface with a self-contained prompt.
|
|
102
|
+
4. Produce stage output in the current artifact, with anchors suitable for \`evidenceRefs\`.
|
|
103
|
+
5. Append delegation ledger rows with \`stage\`, \`agent\`, \`mode\`, \`status: "completed"\`, and \`fulfillmentMode\` matching the dispatch mode (\`"isolated"\` for Claude/OpenCode/Codex, \`"generic-dispatch"\` for Cursor).
|
|
104
|
+
|
|
105
|
+
The only time a \`harness_limitation\` waiver fires automatically is when every installed harness declares \`subagentFallback: "waiver"\`. Do not map Codex or OpenCode onto auto-waiver or default role-switch; they have true subagent surfaces.
|
|
96
106
|
|
|
97
107
|
### Model routing
|
|
98
108
|
|
|
@@ -645,7 +645,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
645
645
|
# TDD Artifact
|
|
646
646
|
|
|
647
647
|
## Upstream Handoff
|
|
648
|
-
- Source artifacts: \`04-spec.md
|
|
648
|
+
- Source artifacts: \`04-spec.md\` plus the active track's upstream source item (plan slice on standard/medium, spec acceptance item or bug reproduction slice on quick).
|
|
649
649
|
- Decisions carried forward:
|
|
650
650
|
- Constraints carried forward:
|
|
651
651
|
- Open questions:
|
|
@@ -672,11 +672,11 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
672
672
|
| S-1 | | | |
|
|
673
673
|
|
|
674
674
|
## Acceptance Mapping
|
|
675
|
-
| Slice |
|
|
675
|
+
| Slice | Source item ID | Spec criterion ID |
|
|
676
676
|
|---|---|---|
|
|
677
|
-
| S-1 |
|
|
677
|
+
| S-1 | SRC-1 | AC-1 |
|
|
678
678
|
|
|
679
|
-
>
|
|
679
|
+
> Map each slice to the active track's source item: plan slice on standard/medium, or the \`Quick Reproduction Contract\` bug slice / spec acceptance item on quick.
|
|
680
680
|
|
|
681
681
|
## Failure Analysis
|
|
682
682
|
| Slice | Expected missing behavior | Actual failure reason |
|
|
@@ -693,7 +693,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
693
693
|
- Behavior preserved:
|
|
694
694
|
|
|
695
695
|
## Traceability
|
|
696
|
-
-
|
|
696
|
+
- Source item IDs:
|
|
697
697
|
- Spec criterion IDs:
|
|
698
698
|
|
|
699
699
|
|
|
@@ -729,7 +729,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
729
729
|
# Review Artifact
|
|
730
730
|
|
|
731
731
|
## Upstream Handoff
|
|
732
|
-
- Source artifacts: \`04-spec.md\`, \`06-tdd.md
|
|
732
|
+
- Source artifacts: \`04-spec.md\`, \`06-tdd.md\`, plus the active track's upstream source item when available.
|
|
733
733
|
- Decisions carried forward:
|
|
734
734
|
- Constraints carried forward:
|
|
735
735
|
- Open questions:
|
|
@@ -766,15 +766,15 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
766
766
|
|
|
767
767
|
## Completeness Snapshot
|
|
768
768
|
- AC coverage: <N>/<M> (<percent>%)
|
|
769
|
-
-
|
|
769
|
+
- Source item coverage (source items backed by ≥1 test slice): <N>/<M> or \`N/A - direct spec/reproduction coverage\`
|
|
770
770
|
- Slice coverage (slices linked to ≥1 AC or bug reproduction slice): <N>/<M>
|
|
771
771
|
- Adversarial review: not triggered | pass | fail
|
|
772
772
|
- Overall: complete | concerns | blocked
|
|
773
773
|
|
|
774
774
|
## Trace Matrix Check
|
|
775
|
-
- Command: \`cclaw internal trace-matrix\` when
|
|
775
|
+
- Command: \`cclaw internal trace-matrix\` when the active track enforces it; otherwise record direct AC/reproduction-slice coverage.
|
|
776
776
|
- Orphaned criteria: 0
|
|
777
|
-
- Orphaned
|
|
777
|
+
- Orphaned source items: 0 or \`N/A - direct spec/reproduction coverage\`
|
|
778
778
|
- Orphaned tests: 0
|
|
779
779
|
- Evidence ref:
|
|
780
780
|
|
|
@@ -34,8 +34,15 @@ export function renderTrackTerminology(value, context) {
|
|
|
34
34
|
}
|
|
35
35
|
return value
|
|
36
36
|
.replace(/\btask from the plan\b/giu, `${context.traceabilitySourceNoun} from the spec`)
|
|
37
|
+
.replace(/\bplan confirmation\b/giu, "spec approval")
|
|
38
|
+
.replace(/\bplan approval\b/giu, "spec approval")
|
|
39
|
+
.replace(/\bapproved plan slice\b/giu, `approved ${context.traceabilitySliceNoun}`)
|
|
40
|
+
.replace(/\bplanned slice\b/giu, context.traceabilitySliceNoun)
|
|
37
41
|
.replace(/\bplan task ID\b/giu, context.traceabilityIdNoun)
|
|
38
42
|
.replace(/\bplan task\b/giu, context.traceabilitySourceNoun)
|
|
43
|
+
.replace(/\bplan-task\b/giu, "acceptance-criterion")
|
|
44
|
+
.replace(/\btask coverage\b/giu, "source item coverage")
|
|
45
|
+
.replace(/\borphaned tasks\b/giu, "orphaned source items")
|
|
39
46
|
.replace(/\bplan row\b/giu, "acceptance row")
|
|
40
47
|
.replace(/\btraceable to plan slice\b/giu, `traceable to ${context.traceabilitySliceNoun}`)
|
|
41
48
|
.replace(/\bplan slice\b/giu, context.traceabilitySliceNoun)
|
package/dist/delegation.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ export type DelegationStatus = "scheduled" | "completed" | "failed" | "waived";
|
|
|
6
6
|
* How a delegation was actually fulfilled. Advisory — mirrors the harness
|
|
7
7
|
* `subagentFallback` that was in effect when the entry was recorded.
|
|
8
8
|
*
|
|
9
|
-
* - `isolated` —
|
|
10
|
-
* - `generic-dispatch` —
|
|
9
|
+
* - `isolated` — native isolated subagent worker (Claude/OpenCode/Codex).
|
|
10
|
+
* - `generic-dispatch` — generic Task/Subagent dispatch mapped to a named role.
|
|
11
11
|
* - `role-switch` — performed in-session with explicit role announce.
|
|
12
12
|
* - `harness-waiver` — auto-waived due to missing dispatch capability.
|
|
13
13
|
*/
|
package/dist/delegation.js
CHANGED
|
@@ -18,6 +18,13 @@ function delegationLockPath(projectRoot) {
|
|
|
18
18
|
function createSpanId() {
|
|
19
19
|
return `dspan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
20
20
|
}
|
|
21
|
+
function activeHarnessSubagentFallback() {
|
|
22
|
+
const activeHarness = process.env.CCLAW_ACTIVE_HARNESS;
|
|
23
|
+
if (!activeHarness)
|
|
24
|
+
return undefined;
|
|
25
|
+
return HARNESS_ADAPTERS[activeHarness]
|
|
26
|
+
?.capabilities.subagentFallback;
|
|
27
|
+
}
|
|
21
28
|
async function resolveReviewDiffBase(projectRoot) {
|
|
22
29
|
let head = "";
|
|
23
30
|
try {
|
|
@@ -229,10 +236,7 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
229
236
|
stamped.evidenceRefs = [];
|
|
230
237
|
}
|
|
231
238
|
if (stamped.status === "completed" && stamped.fulfillmentMode === undefined) {
|
|
232
|
-
const activeFallback =
|
|
233
|
-
? HARNESS_ADAPTERS[process.env.CCLAW_ACTIVE_HARNESS]
|
|
234
|
-
?.capabilities.subagentFallback
|
|
235
|
-
: undefined;
|
|
239
|
+
const activeFallback = activeHarnessSubagentFallback();
|
|
236
240
|
if (activeFallback) {
|
|
237
241
|
stamped.fulfillmentMode = expectedFulfillmentMode([activeFallback]);
|
|
238
242
|
}
|
|
@@ -291,8 +295,9 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
291
295
|
const missingEvidence = [];
|
|
292
296
|
const config = await readConfig(projectRoot).catch(() => null);
|
|
293
297
|
const harnesses = config?.harnesses ?? [];
|
|
294
|
-
const
|
|
295
|
-
const
|
|
298
|
+
const configuredFallbacks = harnesses.map((h) => HARNESS_ADAPTERS[h].capabilities.subagentFallback);
|
|
299
|
+
const activeFallback = activeHarnessSubagentFallback();
|
|
300
|
+
const expectedMode = expectedFulfillmentMode(activeFallback ? [activeFallback] : configuredFallbacks);
|
|
296
301
|
for (const agent of mandatory) {
|
|
297
302
|
const rows = forRun.filter((e) => e.agent === agent);
|
|
298
303
|
const completedRows = rows.filter((e) => e.status === "completed");
|
|
@@ -307,9 +312,11 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
307
312
|
if (hasWaived) {
|
|
308
313
|
waived.push(agent);
|
|
309
314
|
}
|
|
310
|
-
// Evidence is required for
|
|
311
|
-
//
|
|
312
|
-
|
|
315
|
+
// Evidence is required for non-isolated completions and for explicit
|
|
316
|
+
// degraded role-switch rows. Native OpenCode/Codex/Claude isolated
|
|
317
|
+
// dispatch is accepted as true subagent work; role-switch remains a
|
|
318
|
+
// fallback that must point at artifact evidence.
|
|
319
|
+
const evidenceRequired = expectedMode !== "isolated" || completedRows.some((e) => (e.fulfillmentMode ?? "isolated") !== "isolated");
|
|
313
320
|
if (hasCompleted &&
|
|
314
321
|
evidenceRequired &&
|
|
315
322
|
!completedRows.some((e) => Array.isArray(e.evidenceRefs) && e.evidenceRefs.length > 0)) {
|
package/dist/doctor-registry.js
CHANGED
|
@@ -39,7 +39,7 @@ const RULES = [
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
test: /^(dir:|command:|utility_command:|skill:|utility_skill:|agent:|harness_tool_ref:|harness_ref:|stage_examples_ref:|doctor_ref:)/,
|
|
42
|
+
test: /^(dir:|command:|utility_command:|stage_command:|skill:|utility_skill:|agent:|harness_tool_ref:|harness_ref:|stage_examples_ref:|doctor_ref:)/,
|
|
43
43
|
metadata: {
|
|
44
44
|
severity: "error",
|
|
45
45
|
summary: "Generated runtime surface presence check.",
|
package/dist/doctor.js
CHANGED
|
@@ -18,6 +18,7 @@ import { buildTraceMatrix } from "./trace-matrix.js";
|
|
|
18
18
|
import { classifyReconciliationNotices, reconcileAndWriteCurrentStageGateCatalog, readReconciliationNotices, RECONCILIATION_NOTICES_REL_PATH, verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "./gate-evidence.js";
|
|
19
19
|
import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
|
|
20
20
|
import { stageSkillFolder } from "./content/skills.js";
|
|
21
|
+
import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
21
22
|
import { doctorCheckMetadata } from "./doctor-registry.js";
|
|
22
23
|
import { resolveTrackFromPrompt } from "./track-heuristics.js";
|
|
23
24
|
import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
|
|
@@ -289,17 +290,23 @@ function normalizeOpenCodePluginEntry(entry) {
|
|
|
289
290
|
}
|
|
290
291
|
return null;
|
|
291
292
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
|
|
294
|
+
function opencodeConfigCandidates(projectRoot) {
|
|
295
|
+
return [
|
|
295
296
|
path.join(projectRoot, "opencode.json"),
|
|
296
297
|
path.join(projectRoot, "opencode.jsonc"),
|
|
297
298
|
path.join(projectRoot, ".opencode/opencode.json"),
|
|
298
299
|
path.join(projectRoot, ".opencode/opencode.jsonc")
|
|
299
300
|
];
|
|
301
|
+
}
|
|
302
|
+
function openCodeConfigRegistersPlugin(parsed) {
|
|
303
|
+
const plugins = Array.isArray(parsed.plugin) ? parsed.plugin : [];
|
|
304
|
+
return plugins.some((entry) => normalizeOpenCodePluginEntry(entry) === OPENCODE_PLUGIN_REL_PATH);
|
|
305
|
+
}
|
|
306
|
+
async function opencodeRegistrationCheck(projectRoot) {
|
|
300
307
|
const mismatches = [];
|
|
301
308
|
let foundAnyConfig = false;
|
|
302
|
-
for (const configPath of
|
|
309
|
+
for (const configPath of opencodeConfigCandidates(projectRoot)) {
|
|
303
310
|
if (!(await exists(configPath))) {
|
|
304
311
|
continue;
|
|
305
312
|
}
|
|
@@ -309,17 +316,49 @@ async function opencodeRegistrationCheck(projectRoot) {
|
|
|
309
316
|
mismatches.push(`${path.relative(projectRoot, configPath)} is unreadable or invalid JSON`);
|
|
310
317
|
continue;
|
|
311
318
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (registered) {
|
|
315
|
-
return { ok: true, details: `${path.relative(projectRoot, configPath)} registers ${expected}` };
|
|
319
|
+
if (openCodeConfigRegistersPlugin(parsed)) {
|
|
320
|
+
return { ok: true, details: `${path.relative(projectRoot, configPath)} registers ${OPENCODE_PLUGIN_REL_PATH}` };
|
|
316
321
|
}
|
|
317
|
-
mismatches.push(`${path.relative(projectRoot, configPath)} missing plugin ${
|
|
322
|
+
mismatches.push(`${path.relative(projectRoot, configPath)} missing plugin ${OPENCODE_PLUGIN_REL_PATH}`);
|
|
318
323
|
}
|
|
319
324
|
if (foundAnyConfig) {
|
|
320
325
|
return { ok: false, details: mismatches.join(" | ") };
|
|
321
326
|
}
|
|
322
|
-
return { ok: false, details: `No opencode.json/opencode.jsonc found with plugin ${
|
|
327
|
+
return { ok: false, details: `No opencode.json/opencode.jsonc found with plugin ${OPENCODE_PLUGIN_REL_PATH}` };
|
|
328
|
+
}
|
|
329
|
+
async function opencodeQuestionPermissionCheck(projectRoot) {
|
|
330
|
+
const mismatches = [];
|
|
331
|
+
for (const configPath of opencodeConfigCandidates(projectRoot)) {
|
|
332
|
+
if (!(await exists(configPath)))
|
|
333
|
+
continue;
|
|
334
|
+
const parsed = await readHookDocument(configPath);
|
|
335
|
+
if (!parsed || !openCodeConfigRegistersPlugin(parsed))
|
|
336
|
+
continue;
|
|
337
|
+
const permission = toObject(parsed.permission) ?? {};
|
|
338
|
+
if (permission.question === "allow") {
|
|
339
|
+
return {
|
|
340
|
+
ok: true,
|
|
341
|
+
details: `${path.relative(projectRoot, configPath)} sets permission.question to "allow" for structured questions`
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
mismatches.push(`${path.relative(projectRoot, configPath)} registers ${OPENCODE_PLUGIN_REL_PATH} but must set permission.question to "allow"`);
|
|
345
|
+
}
|
|
346
|
+
if (mismatches.length > 0) {
|
|
347
|
+
return { ok: false, details: mismatches.join(" | ") };
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
ok: false,
|
|
351
|
+
details: `No opencode config with ${OPENCODE_PLUGIN_REL_PATH} registration found; cannot verify permission.question = "allow"`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function opencodeQuestionEnvCheck() {
|
|
355
|
+
if (process.env.OPENCODE_ENABLE_QUESTION_TOOL === "1") {
|
|
356
|
+
return { ok: true, details: "OPENCODE_ENABLE_QUESTION_TOOL=1 is set for ACP question tooling" };
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
ok: false,
|
|
360
|
+
details: "Set OPENCODE_ENABLE_QUESTION_TOOL=1 for OpenCode ACP clients so permission-gated structured questions can use the question tool."
|
|
361
|
+
};
|
|
323
362
|
}
|
|
324
363
|
async function initRecoveryCheck(projectRoot) {
|
|
325
364
|
const sentinelPath = path.join(projectRoot, RUNTIME_ROOT, "state", ".init-in-progress");
|
|
@@ -667,7 +706,6 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
667
706
|
ok: agentsBlockOk,
|
|
668
707
|
details: `${agentsFile} must contain the managed cclaw marker block with routing, verification, and minimal detail pointer`
|
|
669
708
|
});
|
|
670
|
-
// User-facing entry commands only. Stage and view subcommands live in skills.
|
|
671
709
|
for (const cmd of ["start", "next", "ideate", "view"]) {
|
|
672
710
|
const cmdPath = path.join(projectRoot, RUNTIME_ROOT, "commands", `${cmd}.md`);
|
|
673
711
|
checks.push({
|
|
@@ -676,6 +714,19 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
676
714
|
details: cmdPath
|
|
677
715
|
});
|
|
678
716
|
}
|
|
717
|
+
for (const stage of FLOW_STAGES) {
|
|
718
|
+
const cmdPath = path.join(projectRoot, RUNTIME_ROOT, "commands", `${stage}.md`);
|
|
719
|
+
let stageCommandOk = false;
|
|
720
|
+
if (await exists(cmdPath)) {
|
|
721
|
+
const content = await fs.readFile(cmdPath, "utf8");
|
|
722
|
+
stageCommandOk = content === stageCommandShimMarkdown(stage);
|
|
723
|
+
}
|
|
724
|
+
checks.push({
|
|
725
|
+
name: `stage_command:${stage}`,
|
|
726
|
+
ok: stageCommandOk,
|
|
727
|
+
details: `${cmdPath} must be a thin shim to ${RUNTIME_ROOT}/skills/${stageSkillFolder(stage)}/SKILL.md and /cc-next`
|
|
728
|
+
});
|
|
729
|
+
}
|
|
679
730
|
// Utility skills
|
|
680
731
|
for (const [folder, label] of [
|
|
681
732
|
["learnings", "learnings"],
|
|
@@ -942,7 +993,6 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
942
993
|
const codexStopCmds = collectHookCommands(codexHooks.Stop);
|
|
943
994
|
const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start")) &&
|
|
944
995
|
codexUserPromptCmds.some((cmd) => cmd.includes("prompt-guard")) &&
|
|
945
|
-
codexUserPromptCmds.some((cmd) => cmd.includes("workflow-guard")) &&
|
|
946
996
|
codexUserPromptCmds.some((cmd) => cmd.includes("verify-current-state")) &&
|
|
947
997
|
codexPreCmds.some((cmd) => cmd.includes("prompt-guard")) &&
|
|
948
998
|
codexPreCmds.some((cmd) => cmd.includes("workflow-guard")) &&
|
|
@@ -951,7 +1001,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
951
1001
|
checks.push({
|
|
952
1002
|
name: "hook:wiring:codex",
|
|
953
1003
|
ok: codexWiringOk,
|
|
954
|
-
details: `${codexHooksFile} must wire SessionStart, UserPromptSubmit(prompt/
|
|
1004
|
+
details: `${codexHooksFile} must wire SessionStart, UserPromptSubmit(prompt/verify-current-state), Bash-only PreToolUse(prompt/workflow), Bash-only PostToolUse(context-monitor), and Stop(stop-handoff). Codex workflow-guard is intentionally strict Bash-only.`
|
|
955
1005
|
});
|
|
956
1006
|
// Feature flag warning: Codex ignores `.codex/hooks.json` unless the
|
|
957
1007
|
// user has `[features] codex_hooks = true` in `~/.codex/config.toml`.
|
|
@@ -1074,6 +1124,18 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1074
1124
|
ok: registration.ok,
|
|
1075
1125
|
details: registration.details
|
|
1076
1126
|
});
|
|
1127
|
+
const questionPermission = await opencodeQuestionPermissionCheck(projectRoot);
|
|
1128
|
+
checks.push({
|
|
1129
|
+
name: "hook:opencode:question_permission",
|
|
1130
|
+
ok: questionPermission.ok,
|
|
1131
|
+
details: questionPermission.details
|
|
1132
|
+
});
|
|
1133
|
+
const questionEnv = opencodeQuestionEnvCheck();
|
|
1134
|
+
checks.push({
|
|
1135
|
+
name: "warning:opencode:question_tool_env",
|
|
1136
|
+
ok: questionEnv.ok,
|
|
1137
|
+
details: questionEnv.details
|
|
1138
|
+
});
|
|
1077
1139
|
}
|
|
1078
1140
|
const nodeVersion = await commandVersion("node");
|
|
1079
1141
|
const nodeMajor = parseNodeMajor(nodeVersion.output);
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import type
|
|
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
4
|
export type SubagentFallback =
|
|
5
|
-
/** Harness has real, isolated subagent dispatch; no fallback needed. */
|
|
5
|
+
/** Harness has real, isolated named subagent dispatch; no fallback needed. */
|
|
6
6
|
"native"
|
|
7
7
|
/**
|
|
8
|
-
* Harness has
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Harness has a real dispatcher but not cclaw-named agents. cclaw maps each
|
|
9
|
+
* named role to the available built-in/generic subagent surface with a
|
|
10
|
+
* structured role prompt.
|
|
11
11
|
*/
|
|
12
12
|
| "generic-dispatch"
|
|
13
13
|
/**
|
|
14
14
|
* No isolated dispatch — the agent performs the named subagent's role
|
|
15
15
|
* in-session with an explicit role announce + delegation-log entry
|
|
16
|
-
* carrying evidenceRefs. Accepted as `completed`
|
|
16
|
+
* carrying evidenceRefs. Accepted as `completed` only when no true dispatch
|
|
17
|
+
* surface exists.
|
|
17
18
|
*/
|
|
18
19
|
| "role-switch"
|
|
19
20
|
/**
|
|
@@ -50,11 +51,11 @@ export interface HarnessAdapter {
|
|
|
50
51
|
capabilities: {
|
|
51
52
|
/**
|
|
52
53
|
* Level of native subagent dispatch:
|
|
53
|
-
* - `full` — isolated workers + user-defined named subagents (Claude
|
|
54
|
-
*
|
|
55
|
-
* - `
|
|
56
|
-
*
|
|
57
|
-
* - `none` — no dispatch primitive at all
|
|
54
|
+
* - `full` — isolated workers + user-defined named subagents (Claude,
|
|
55
|
+
* OpenCode, Codex custom agents).
|
|
56
|
+
* - `generic` — generic dispatcher without cclaw-named agents (Cursor).
|
|
57
|
+
* - `partial` — limited or plugin-only dispatch surface.
|
|
58
|
+
* - `none` — no dispatch primitive at all.
|
|
58
59
|
*/
|
|
59
60
|
nativeSubagentDispatch: "full" | "generic" | "partial" | "none";
|
|
60
61
|
hookSurface: "full" | "plugin" | "limited" | "none";
|
|
@@ -87,6 +88,8 @@ export declare function harnessShimFileNames(): string[];
|
|
|
87
88
|
/** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
|
|
88
89
|
export declare function harnessShimSkillNames(): string[];
|
|
89
90
|
export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
|
|
91
|
+
export declare function harnessDispatchSurface(harnessId: HarnessId): string;
|
|
92
|
+
export declare function harnessDispatchFallback(harnessId: HarnessId): string;
|
|
90
93
|
export type HarnessTier = "tier1" | "tier2" | "tier3";
|
|
91
94
|
export declare function harnessTier(harnessId: HarnessId): HarnessTier;
|
|
92
95
|
/**
|
package/dist/harness-adapters.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { RUNTIME_ROOT } from "./constants.js";
|
|
3
|
+
import { RUNTIME_ROOT, STAGE_TO_SKILL_FOLDER } from "./constants.js";
|
|
4
4
|
import { conversationLanguagePolicyMarkdown } from "./content/language-policy.js";
|
|
5
5
|
import { CCLAW_AGENTS, agentMarkdown } from "./content/core-agents.js";
|
|
6
6
|
import { ironLawsAgentsMdBlock } from "./content/iron-laws.js";
|
|
7
7
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
8
|
+
import { FLOW_STAGES } from "./types.js";
|
|
8
9
|
export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
|
|
9
10
|
export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
|
|
10
11
|
function escapeRegExp(value) {
|
|
@@ -46,12 +47,26 @@ const LEGACY_CODEX_SKILL_PREFIX = "cclaw-cc";
|
|
|
46
47
|
* harness command directories so `/cc-learn` etc. do not linger.
|
|
47
48
|
*/
|
|
48
49
|
const LEGACY_HARNESS_SHIMS = ["cc-learn.md"];
|
|
50
|
+
function stageShimFileName(stage) {
|
|
51
|
+
return `cc-${stage}.md`;
|
|
52
|
+
}
|
|
53
|
+
function stageShimSkillName(stage) {
|
|
54
|
+
return `cc-${stage}`;
|
|
55
|
+
}
|
|
49
56
|
export function harnessShimFileNames() {
|
|
50
|
-
return [
|
|
57
|
+
return [
|
|
58
|
+
"cc.md",
|
|
59
|
+
...UTILITY_SHIMS.map((shim) => shim.fileName),
|
|
60
|
+
...FLOW_STAGES.map((stage) => stageShimFileName(stage))
|
|
61
|
+
];
|
|
51
62
|
}
|
|
52
63
|
/** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
|
|
53
64
|
export function harnessShimSkillNames() {
|
|
54
|
-
return [
|
|
65
|
+
return [
|
|
66
|
+
ENTRY_SHIM_SKILL_NAME,
|
|
67
|
+
...UTILITY_SHIMS.map((shim) => shim.skillName),
|
|
68
|
+
...FLOW_STAGES.map((stage) => stageShimSkillName(stage))
|
|
69
|
+
];
|
|
55
70
|
}
|
|
56
71
|
export const HARNESS_ADAPTERS = {
|
|
57
72
|
claude: {
|
|
@@ -85,7 +100,11 @@ export const HARNESS_ADAPTERS = {
|
|
|
85
100
|
commandDir: ".opencode/commands",
|
|
86
101
|
shimKind: "command",
|
|
87
102
|
capabilities: {
|
|
88
|
-
|
|
103
|
+
// OpenCode supports project-local markdown subagents under
|
|
104
|
+
// `.opencode/agents/`; primary agents can invoke them via the Task
|
|
105
|
+
// tool or explicit `@agent` mention. cclaw materializes its core
|
|
106
|
+
// roster there, so mandatory delegations are real isolated subagents.
|
|
107
|
+
nativeSubagentDispatch: "full",
|
|
89
108
|
hookSurface: "plugin",
|
|
90
109
|
// OpenCode exposes a native `question` tool (header + options +
|
|
91
110
|
// custom-answer fallback, multi-question navigation). It is
|
|
@@ -95,7 +114,7 @@ export const HARNESS_ADAPTERS = {
|
|
|
95
114
|
// in generated harness guidance; skills fall back to the shared
|
|
96
115
|
// plain-text lettered list when the tool is denied or unavailable.
|
|
97
116
|
structuredAsk: "question",
|
|
98
|
-
subagentFallback: "
|
|
117
|
+
subagentFallback: "native"
|
|
99
118
|
}
|
|
100
119
|
},
|
|
101
120
|
codex: {
|
|
@@ -103,8 +122,10 @@ export const HARNESS_ADAPTERS = {
|
|
|
103
122
|
// Codex CLI reads skills from the universal `.agents/skills/` path
|
|
104
123
|
// (OpenAI Codex 0.89, Jan 2026). It does NOT have a native
|
|
105
124
|
// `.codex/commands/*` slash-command discovery — cclaw installs
|
|
106
|
-
// its entry points as skills here.
|
|
107
|
-
//
|
|
125
|
+
// its entry points as skills here. Current Codex releases also support
|
|
126
|
+
// native parallel subagents and project-local `.codex/agents/*.toml`
|
|
127
|
+
// custom agents; cclaw materializes its core roster there. Since v0.114
|
|
128
|
+
// (Mar 2026) Codex also exposes lifecycle hooks via `.codex/hooks.json`, behind
|
|
108
129
|
// the `[features] codex_hooks = true` feature flag in
|
|
109
130
|
// `~/.codex/config.toml`. cclaw writes that file on sync and
|
|
110
131
|
// `hookSurface: "limited"` records the reality: SessionStart /
|
|
@@ -113,7 +134,7 @@ export const HARNESS_ADAPTERS = {
|
|
|
113
134
|
commandDir: ".agents/skills",
|
|
114
135
|
shimKind: "skill",
|
|
115
136
|
capabilities: {
|
|
116
|
-
nativeSubagentDispatch: "
|
|
137
|
+
nativeSubagentDispatch: "full",
|
|
117
138
|
hookSurface: "limited",
|
|
118
139
|
// Codex CLI exposes `request_user_input` — an experimental tool
|
|
119
140
|
// that asks 1-3 short questions and returns the user's answers.
|
|
@@ -123,10 +144,29 @@ export const HARNESS_ADAPTERS = {
|
|
|
123
144
|
// it into generated harness guidance. The shared plain-text
|
|
124
145
|
// lettered list is the documented fallback when the tool is unavailable.
|
|
125
146
|
structuredAsk: "request_user_input",
|
|
126
|
-
subagentFallback: "
|
|
147
|
+
subagentFallback: "native"
|
|
127
148
|
}
|
|
128
149
|
}
|
|
129
150
|
};
|
|
151
|
+
export function harnessDispatchSurface(harnessId) {
|
|
152
|
+
switch (harnessId) {
|
|
153
|
+
case "claude":
|
|
154
|
+
return "Use Claude Code Task with the cclaw agent name as subagent_type; record fulfillmentMode: \"isolated\".";
|
|
155
|
+
case "cursor":
|
|
156
|
+
return "Use Cursor Subagent/Task with a generic subagent_type (explore for read-only mapping, generalPurpose for broader work, shell/browser-use when specifically needed) and paste the cclaw role prompt; record fulfillmentMode: \"generic-dispatch\" with evidenceRefs.";
|
|
157
|
+
case "opencode":
|
|
158
|
+
return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>, run independent agents in parallel when safe, then record fulfillmentMode: \"isolated\".";
|
|
159
|
+
case "codex":
|
|
160
|
+
return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name, wait for all results, then record fulfillmentMode: \"isolated\".";
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export function harnessDispatchFallback(harnessId) {
|
|
164
|
+
const adapter = HARNESS_ADAPTERS[harnessId];
|
|
165
|
+
if (adapter.capabilities.subagentFallback !== "role-switch") {
|
|
166
|
+
return "Role-switch is only a degradation path if the active runtime cannot expose the declared dispatch surface; include non-empty evidenceRefs when used.";
|
|
167
|
+
}
|
|
168
|
+
return "Use a visible role-switch pass with non-empty evidenceRefs because this harness has no true dispatch surface.";
|
|
169
|
+
}
|
|
130
170
|
export function harnessTier(harnessId) {
|
|
131
171
|
const capabilities = HARNESS_ADAPTERS[harnessId].capabilities;
|
|
132
172
|
if (capabilities.nativeSubagentDispatch === "full" &&
|
|
@@ -223,7 +263,7 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
223
263
|
### Detail Level
|
|
224
264
|
|
|
225
265
|
- This managed AGENTS block is intentionally minimal for cross-project use.
|
|
226
|
-
-
|
|
266
|
+
- Subagent dispatch coverage: Claude/OpenCode/Codex support native isolated workers; Cursor uses generic Task dispatch. Codex still has Bash-only tool hooks.
|
|
227
267
|
- Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
|
|
228
268
|
- Keep preambles brief; re-announce role/stage only when either changes.
|
|
229
269
|
- Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
|
|
@@ -336,6 +376,50 @@ Load and execute:
|
|
|
336
376
|
${utilityShimBehavior(command)}
|
|
337
377
|
`;
|
|
338
378
|
}
|
|
379
|
+
function stageShimContent(harness, stage) {
|
|
380
|
+
const shimName = stageShimSkillName(stage);
|
|
381
|
+
const skillPath = `${RUNTIME_ROOT}/skills/${STAGE_TO_SKILL_FOLDER[stage]}/SKILL.md`;
|
|
382
|
+
return `---
|
|
383
|
+
name: ${shimName}
|
|
384
|
+
description: Generated shim for ${harness}. Flow stage pointer; normal advancement uses /cc-next.
|
|
385
|
+
source: generated-by-cclaw
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
# cclaw ${stage}
|
|
389
|
+
|
|
390
|
+
This is a thin compatibility shim for the \`${stage}\` flow stage.
|
|
391
|
+
|
|
392
|
+
Load and follow the authoritative stage skill:
|
|
393
|
+
|
|
394
|
+
- \`${skillPath}\`
|
|
395
|
+
|
|
396
|
+
Normal stage resume and advancement uses \`/cc-next\`. Use \`/cc-next\` to read
|
|
397
|
+
\`.cclaw/state/flow-state.json\`, select the active stage, and advance only after
|
|
398
|
+
that stage's gates pass. Do not duplicate the stage protocol here.
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
401
|
+
function codexStageSkillMarkdown(stage) {
|
|
402
|
+
const skillName = stageShimSkillName(stage);
|
|
403
|
+
const skillPath = `${RUNTIME_ROOT}/skills/${STAGE_TO_SKILL_FOLDER[stage]}/SKILL.md`;
|
|
404
|
+
return `---
|
|
405
|
+
name: ${skillName}
|
|
406
|
+
description: Thin cclaw stage shim for /cc-${stage}. Load ${skillPath}; normal stage resume and advancement uses /cc-next.
|
|
407
|
+
source: generated-by-cclaw
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
# cclaw /cc-${stage} (Codex adapter)
|
|
411
|
+
|
|
412
|
+
This is a thin compatibility shim for the \`${stage}\` flow stage.
|
|
413
|
+
|
|
414
|
+
Load and follow the authoritative stage skill:
|
|
415
|
+
|
|
416
|
+
- \`${skillPath}\`
|
|
417
|
+
|
|
418
|
+
Normal stage resume and advancement uses \`/cc-next\`. Use \`/cc-next\` to read
|
|
419
|
+
\`.cclaw/state/flow-state.json\`, select the active stage, and advance only after
|
|
420
|
+
that stage's gates pass. Do not duplicate the stage protocol here.
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
339
423
|
/**
|
|
340
424
|
* Frontmatter `description` that triggers the skill when the user types any
|
|
341
425
|
* of the classic cclaw slash-tokens. Codex's skill matcher runs on the skill
|
|
@@ -398,11 +482,12 @@ for the current hook surface and limitations.
|
|
|
398
482
|
|
|
399
483
|
## Honest caveats
|
|
400
484
|
|
|
401
|
-
- Codex has
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
485
|
+
- Codex has native parallel subagents. cclaw writes project custom agents
|
|
486
|
+
under \`.codex/agents/*.toml\`; ask Codex to spawn the relevant cclaw
|
|
487
|
+
agent(s) by name, wait for their results, write evidence into the active
|
|
488
|
+
artifact, then append completed delegation rows with \`fulfillmentMode:
|
|
489
|
+
"isolated"\`. Use role-switch only if this Codex build has subagents
|
|
490
|
+
unavailable or disabled, and then include non-empty \`evidenceRefs\`.
|
|
406
491
|
- Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
|
|
407
492
|
the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
|
|
408
493
|
are **not** gated by hooks — use \`cclaw doctor --explain\` for what cclaw
|
|
@@ -432,6 +517,9 @@ async function writeCommandKindShims(commandDir, harness) {
|
|
|
432
517
|
for (const shim of UTILITY_SHIMS) {
|
|
433
518
|
await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
|
|
434
519
|
}
|
|
520
|
+
for (const stage of FLOW_STAGES) {
|
|
521
|
+
await writeFileSafe(path.join(commandDir, stageShimFileName(stage)), stageShimContent(harness, stage));
|
|
522
|
+
}
|
|
435
523
|
for (const legacy of LEGACY_HARNESS_SHIMS) {
|
|
436
524
|
const legacyPath = path.join(commandDir, legacy);
|
|
437
525
|
try {
|
|
@@ -448,6 +536,9 @@ async function writeSkillKindShims(commandDir) {
|
|
|
448
536
|
for (const shim of UTILITY_SHIMS) {
|
|
449
537
|
await writeFileSafe(path.join(commandDir, shim.skillName, "SKILL.md"), codexSkillMarkdown(shim.command, shim.skillName, shim.skillFolder, shim.commandFile));
|
|
450
538
|
}
|
|
539
|
+
for (const stage of FLOW_STAGES) {
|
|
540
|
+
await writeFileSafe(path.join(commandDir, stageShimSkillName(stage), "SKILL.md"), codexStageSkillMarkdown(stage));
|
|
541
|
+
}
|
|
451
542
|
}
|
|
452
543
|
/**
|
|
453
544
|
* Legacy codex surfaces cclaw wrote before v0.39.0 that Codex CLI never
|
|
@@ -505,12 +596,57 @@ async function cleanupLegacyCodexSurfaces(projectRoot) {
|
|
|
505
596
|
// directory absent or non-empty
|
|
506
597
|
}
|
|
507
598
|
}
|
|
508
|
-
|
|
599
|
+
function codexAgentToml(agent) {
|
|
600
|
+
const instructions = `${agent.body}\n\n${enhancedAgentInstruction(agent.name)}`.trim();
|
|
601
|
+
const sandboxMode = agent.tools.some((tool) => ["Write", "Edit", "Bash"].includes(tool))
|
|
602
|
+
? "workspace-write"
|
|
603
|
+
: "read-only";
|
|
604
|
+
return [
|
|
605
|
+
`name = ${JSON.stringify(agent.name)}`,
|
|
606
|
+
`description = ${JSON.stringify(agent.description)}`,
|
|
607
|
+
`sandbox_mode = ${JSON.stringify(sandboxMode)}`,
|
|
608
|
+
'developer_instructions = """',
|
|
609
|
+
instructions.replace(/"""/gu, '\"\"\"'),
|
|
610
|
+
'"""',
|
|
611
|
+
""
|
|
612
|
+
].join("\n");
|
|
613
|
+
}
|
|
614
|
+
function opencodeAgentMarkdown(agent) {
|
|
615
|
+
const editPermission = agent.tools.some((tool) => ["Write", "Edit"].includes(tool)) ? "ask" : "deny";
|
|
616
|
+
const bashPermission = agent.tools.includes("Bash") ? "ask" : "deny";
|
|
617
|
+
return `---
|
|
618
|
+
description: ${JSON.stringify(agent.description)}
|
|
619
|
+
mode: subagent
|
|
620
|
+
permission:
|
|
621
|
+
edit: ${editPermission}
|
|
622
|
+
bash: ${bashPermission}
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
${agentMarkdown(agent)}`;
|
|
626
|
+
}
|
|
627
|
+
function enhancedAgentInstruction(agentName) {
|
|
628
|
+
return `You are the cclaw ${agentName} subagent. Follow the parent prompt as the task boundary, produce evidence suitable for .cclaw/state/delegation-log.json, and do not recursively orchestrate other agents unless the parent explicitly asks.`;
|
|
629
|
+
}
|
|
630
|
+
async function syncAgentFiles(projectRoot, harnesses) {
|
|
509
631
|
const agentsDir = path.join(projectRoot, RUNTIME_ROOT, "agents");
|
|
510
632
|
await ensureDir(agentsDir);
|
|
511
633
|
for (const agent of CCLAW_AGENTS) {
|
|
512
634
|
await writeFileSafe(path.join(agentsDir, `${agent.name}.md`), agentMarkdown(agent));
|
|
513
635
|
}
|
|
636
|
+
if (harnesses.includes("opencode")) {
|
|
637
|
+
const opencodeAgentsDir = path.join(projectRoot, ".opencode/agents");
|
|
638
|
+
await ensureDir(opencodeAgentsDir);
|
|
639
|
+
for (const agent of CCLAW_AGENTS) {
|
|
640
|
+
await writeFileSafe(path.join(opencodeAgentsDir, `${agent.name}.md`), opencodeAgentMarkdown(agent));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (harnesses.includes("codex")) {
|
|
644
|
+
const codexAgentsDir = path.join(projectRoot, ".codex/agents");
|
|
645
|
+
await ensureDir(codexAgentsDir);
|
|
646
|
+
for (const agent of CCLAW_AGENTS) {
|
|
647
|
+
await writeFileSafe(path.join(codexAgentsDir, `${agent.name}.toml`), codexAgentToml(agent));
|
|
648
|
+
}
|
|
649
|
+
}
|
|
514
650
|
}
|
|
515
651
|
export async function syncHarnessShims(projectRoot, harnesses) {
|
|
516
652
|
// Legacy codex cleanup is unconditional — even installs that never enabled
|
|
@@ -529,6 +665,6 @@ export async function syncHarnessShims(projectRoot, harnesses) {
|
|
|
529
665
|
await writeCommandKindShims(commandDir, harness);
|
|
530
666
|
}
|
|
531
667
|
}
|
|
532
|
-
await syncAgentFiles(projectRoot);
|
|
668
|
+
await syncAgentFiles(projectRoot, harnesses);
|
|
533
669
|
await syncAgentsMd(projectRoot, harnesses);
|
|
534
670
|
}
|
package/dist/install.js
CHANGED
|
@@ -6,6 +6,7 @@ import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./cons
|
|
|
6
6
|
import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
|
|
7
7
|
import { learnSkillMarkdown } from "./content/learnings.js";
|
|
8
8
|
import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
|
|
9
|
+
import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
9
10
|
import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
|
|
10
11
|
import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
|
|
11
12
|
import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
|
|
@@ -443,6 +444,9 @@ async function writeEntryCommands(projectRoot) {
|
|
|
443
444
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
444
445
|
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
445
446
|
await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
|
|
447
|
+
for (const stage of FLOW_STAGES) {
|
|
448
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", `${stage}.md`), stageCommandShimMarkdown(stage));
|
|
449
|
+
}
|
|
446
450
|
}
|
|
447
451
|
function toObject(value) {
|
|
448
452
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -547,11 +551,20 @@ function mergeOpenCodePluginConfig(existingDoc, pluginRelPath) {
|
|
|
547
551
|
if (!normalized.has(pluginRelPath)) {
|
|
548
552
|
pluginsRaw.push(pluginRelPath);
|
|
549
553
|
}
|
|
550
|
-
const
|
|
554
|
+
const permission = toObject(root.permission) ?? {};
|
|
555
|
+
const permissionChanged = permission.question !== "allow";
|
|
556
|
+
const changed = !normalized.has(pluginRelPath) ||
|
|
557
|
+
!Array.isArray(root.plugin) ||
|
|
558
|
+
permissionChanged ||
|
|
559
|
+
!toObject(root.permission);
|
|
551
560
|
return {
|
|
552
561
|
merged: {
|
|
553
562
|
...root,
|
|
554
|
-
plugin: pluginsRaw
|
|
563
|
+
plugin: pluginsRaw,
|
|
564
|
+
permission: {
|
|
565
|
+
...permission,
|
|
566
|
+
question: "allow"
|
|
567
|
+
}
|
|
555
568
|
},
|
|
556
569
|
changed
|
|
557
570
|
};
|
|
@@ -933,7 +946,6 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
933
946
|
await removeBestEffort(legacyPlugin);
|
|
934
947
|
}
|
|
935
948
|
for (const legacyRuntimeFile of [
|
|
936
|
-
...FLOW_STAGES.map((stage) => runtimePath(projectRoot, "commands", `${stage}.md`)),
|
|
937
949
|
...DEPRECATED_COMMAND_FILES.map((file) => runtimePath(projectRoot, "commands", file)),
|
|
938
950
|
...DEPRECATED_SKILL_FILES.map((segments) => runtimePath(projectRoot, "skills", ...segments)),
|
|
939
951
|
...DEPRECATED_STATE_FILES.map((file) => runtimePath(projectRoot, "state", file)),
|
|
@@ -1248,7 +1260,7 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1248
1260
|
try {
|
|
1249
1261
|
const entries = await fs.readdir(codexSkillsRoot);
|
|
1250
1262
|
for (const entry of entries) {
|
|
1251
|
-
if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate))?$/u.test(entry)) {
|
|
1263
|
+
if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
|
|
1252
1264
|
await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
|
|
1253
1265
|
}
|
|
1254
1266
|
}
|
|
@@ -1258,6 +1270,17 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1258
1270
|
}
|
|
1259
1271
|
await removeIfEmpty(codexSkillsRoot);
|
|
1260
1272
|
await removeIfEmpty(path.join(projectRoot, ".agents"));
|
|
1273
|
+
const managedAgentNames = [
|
|
1274
|
+
"planner",
|
|
1275
|
+
"reviewer",
|
|
1276
|
+
"security-reviewer",
|
|
1277
|
+
"test-author",
|
|
1278
|
+
"doc-updater"
|
|
1279
|
+
];
|
|
1280
|
+
for (const agentName of managedAgentNames) {
|
|
1281
|
+
await removeBestEffort(path.join(projectRoot, ".opencode/agents", `${agentName}.md`));
|
|
1282
|
+
await removeBestEffort(path.join(projectRoot, ".codex/agents", `${agentName}.toml`));
|
|
1283
|
+
}
|
|
1261
1284
|
for (const pluginPath of [
|
|
1262
1285
|
path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
|
|
1263
1286
|
path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
|
|
@@ -1284,8 +1307,10 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1284
1307
|
".cursor/rules",
|
|
1285
1308
|
".cursor/commands",
|
|
1286
1309
|
".cursor",
|
|
1310
|
+
".codex/agents",
|
|
1287
1311
|
".codex/commands",
|
|
1288
1312
|
".codex",
|
|
1313
|
+
".opencode/agents",
|
|
1289
1314
|
".opencode/plugins",
|
|
1290
1315
|
".opencode/commands",
|
|
1291
1316
|
".opencode"
|