cclaw-cli 0.51.29 → 0.55.2
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 +22 -16
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +245 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +323 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +162 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +65 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +115 -0
- package/dist/artifact-linter/shared.d.ts +246 -0
- package/dist/artifact-linter/shared.js +1488 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +46 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +108 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +124 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +2 -18
- package/dist/cli.js +8 -246
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +9 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.js +13 -10
- package/dist/content/core-agents.d.ts +18 -0
- package/dist/content/core-agents.js +51 -7
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- package/dist/content/examples.js +6 -6
- package/dist/content/harness-doc.js +20 -2
- package/dist/content/hook-inline-snippets.d.ts +17 -4
- package/dist/content/hook-inline-snippets.js +218 -5
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea-command.d.ts +8 -0
- package/dist/content/{ideate-command.js → idea-command.js} +57 -50
- package/dist/content/idea-frames.d.ts +31 -0
- package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
- package/dist/content/idea-ranking.d.ts +25 -0
- package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.js +1 -1
- package/dist/content/meta-skill.js +11 -13
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +45 -11
- package/dist/content/opencode-plugin.js +3 -3
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.js +19 -7
- package/dist/content/stage-command.js +1 -1
- package/dist/content/stage-schema.js +44 -2
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +9 -6
- package/dist/content/stages/review.js +4 -4
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +2 -2
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.d.ts +2 -2
- package/dist/content/start-command.js +24 -21
- package/dist/content/status-command.js +8 -8
- package/dist/content/subagents.js +61 -7
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +104 -152
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +2 -2
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +1 -1
- package/dist/flow-state.js +1 -1
- package/dist/gate-evidence.d.ts +8 -0
- package/dist/gate-evidence.js +141 -5
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +54 -122
- package/dist/harness-selection.d.ts +31 -0
- package/dist/harness-selection.js +214 -0
- package/dist/install.js +166 -38
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +480 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +170 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +3 -8
- package/dist/knowledge-store.js +16 -29
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +5 -7
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +16 -16
- package/dist/run-persistence.js +112 -12
- package/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +1 -1
- package/dist/content/finish-command.d.ts +0 -2
- package/dist/content/finish-command.js +0 -26
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2206
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
package/dist/harness-adapters.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { RUNTIME_ROOT
|
|
3
|
+
import { RUNTIME_ROOT } from "./constants.js";
|
|
4
4
|
import { conversationLanguagePolicyMarkdown } from "./content/language-policy.js";
|
|
5
5
|
import { CCLAW_AGENTS, agentMarkdown } from "./content/core-agents.js";
|
|
6
|
-
import {
|
|
6
|
+
import { IRON_LAWS } from "./content/iron-laws.js";
|
|
7
7
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
8
|
-
import { FLOW_STAGES } from "./types.js";
|
|
9
8
|
export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
|
|
10
9
|
export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
|
|
11
10
|
function escapeRegExp(value) {
|
|
@@ -16,32 +15,11 @@ const RUNTIME_AGENTS_BLOCK_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "u"
|
|
|
16
15
|
const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "gu");
|
|
17
16
|
const UTILITY_SHIMS = [
|
|
18
17
|
{
|
|
19
|
-
fileName: "cc-
|
|
20
|
-
skillName: "cc-
|
|
21
|
-
command: "
|
|
22
|
-
skillFolder: "flow-
|
|
23
|
-
commandFile: "
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
fileName: "cc-ideate.md",
|
|
27
|
-
skillName: "cc-ideate",
|
|
28
|
-
command: "ideate",
|
|
29
|
-
skillFolder: "flow-ideate",
|
|
30
|
-
commandFile: "ideate.md"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
fileName: "cc-view.md",
|
|
34
|
-
skillName: "cc-view",
|
|
35
|
-
command: "view",
|
|
36
|
-
skillFolder: "flow-view",
|
|
37
|
-
commandFile: "view.md"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
fileName: "cc-finish.md",
|
|
41
|
-
skillName: "cc-finish",
|
|
42
|
-
command: "finish",
|
|
43
|
-
skillFolder: "flow-finish",
|
|
44
|
-
commandFile: "finish.md"
|
|
18
|
+
fileName: "cc-idea.md",
|
|
19
|
+
skillName: "cc-idea",
|
|
20
|
+
command: "idea",
|
|
21
|
+
skillFolder: "flow-idea",
|
|
22
|
+
commandFile: "idea.md"
|
|
45
23
|
},
|
|
46
24
|
{
|
|
47
25
|
fileName: "cc-cancel.md",
|
|
@@ -61,25 +39,17 @@ const LEGACY_CODEX_SKILL_PREFIX = "cclaw-cc";
|
|
|
61
39
|
* harness command directories so `/cc-learn` etc. do not linger.
|
|
62
40
|
*/
|
|
63
41
|
const LEGACY_HARNESS_SHIMS = ["cc-learn.md"];
|
|
64
|
-
function stageShimFileName(stage) {
|
|
65
|
-
return `cc-${stage}.md`;
|
|
66
|
-
}
|
|
67
|
-
function stageShimSkillName(stage) {
|
|
68
|
-
return `cc-${stage}`;
|
|
69
|
-
}
|
|
70
42
|
export function harnessShimFileNames() {
|
|
71
43
|
return [
|
|
72
44
|
"cc.md",
|
|
73
|
-
...UTILITY_SHIMS.map((shim) => shim.fileName)
|
|
74
|
-
...FLOW_STAGES.map((stage) => stageShimFileName(stage))
|
|
45
|
+
...UTILITY_SHIMS.map((shim) => shim.fileName)
|
|
75
46
|
];
|
|
76
47
|
}
|
|
77
48
|
/** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
|
|
78
49
|
export function harnessShimSkillNames() {
|
|
79
50
|
return [
|
|
80
51
|
ENTRY_SHIM_SKILL_NAME,
|
|
81
|
-
...UTILITY_SHIMS.map((shim) => shim.skillName)
|
|
82
|
-
...FLOW_STAGES.map((stage) => stageShimSkillName(stage))
|
|
52
|
+
...UTILITY_SHIMS.map((shim) => shim.skillName)
|
|
83
53
|
];
|
|
84
54
|
}
|
|
85
55
|
export const HARNESS_ADAPTERS = {
|
|
@@ -193,9 +163,9 @@ export function harnessDispatchSurface(harnessId) {
|
|
|
193
163
|
case "cursor":
|
|
194
164
|
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.";
|
|
195
165
|
case "opencode":
|
|
196
|
-
return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `cclaw
|
|
166
|
+
return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `npx cclaw-cli sync`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
|
|
197
167
|
case "codex":
|
|
198
|
-
return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `cclaw
|
|
168
|
+
return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `npx cclaw-cli sync`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
|
|
199
169
|
}
|
|
200
170
|
}
|
|
201
171
|
/**
|
|
@@ -322,6 +292,35 @@ export function harnessesByTier() {
|
|
|
322
292
|
return tierOrder[harnessTier(a)] - tierOrder[harnessTier(b)];
|
|
323
293
|
});
|
|
324
294
|
}
|
|
295
|
+
function ironLawsAgentsMdBlock() {
|
|
296
|
+
const enforcedLawIds = new Set([
|
|
297
|
+
"stop-clean-or-handoff",
|
|
298
|
+
"review-coverage-complete-before-ship"
|
|
299
|
+
]);
|
|
300
|
+
const enforcedRows = IRON_LAWS
|
|
301
|
+
.filter((law) => enforcedLawIds.has(law.id))
|
|
302
|
+
.map((law) => `| \`${law.id}\` | ${law.rule} | ${law.enforcement} |`)
|
|
303
|
+
.join("\n");
|
|
304
|
+
const advisoryRows = IRON_LAWS
|
|
305
|
+
.filter((law) => !enforcedLawIds.has(law.id))
|
|
306
|
+
.map((law) => {
|
|
307
|
+
const appliesTo = law.appliesTo === "all" ? "all stages" : law.appliesTo.join(", ");
|
|
308
|
+
return `- \`${law.id}\` (applies to: ${appliesTo})`;
|
|
309
|
+
})
|
|
310
|
+
.join("\n");
|
|
311
|
+
return `### Iron Laws
|
|
312
|
+
|
|
313
|
+
These rules are always-on. The hook-enforced runtime laws are:
|
|
314
|
+
|
|
315
|
+
| ID | Rule | Enforced by |
|
|
316
|
+
|---|---|---|
|
|
317
|
+
${enforcedRows}
|
|
318
|
+
|
|
319
|
+
Advisory laws are stage-owned through each stage's HARD-GATE block:
|
|
320
|
+
|
|
321
|
+
${advisoryRows}
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
325
324
|
function agentsMdBlock() {
|
|
326
325
|
return `${CCLAW_MARKER_START}
|
|
327
326
|
## Cclaw — Workflow Adapter
|
|
@@ -343,7 +342,7 @@ Treat quality as a hard requirement, not style preference:
|
|
|
343
342
|
|
|
344
343
|
Before responding to a coding request:
|
|
345
344
|
1. Read \`.cclaw/state/flow-state.json\` for the current stage.
|
|
346
|
-
2. Use \`/cc\` to start or
|
|
345
|
+
2. Use \`/cc\` to start, resume, or continue the flow.
|
|
347
346
|
3. If no stage applies, respond normally.
|
|
348
347
|
|
|
349
348
|
${ironLawsAgentsMdBlock()}
|
|
@@ -372,11 +371,8 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
|
|
|
372
371
|
|
|
373
372
|
| Command | Purpose |
|
|
374
373
|
|---|---|
|
|
375
|
-
| \`/cc\` | **Entry point.** No args = resume current
|
|
376
|
-
| \`/cc-
|
|
377
|
-
| \`/cc-ideate\` | **Ideate mode.** Generates a ranked repo-improvement backlog before implementation. |
|
|
378
|
-
| \`/cc-view\` | **Read-only router.** Unified entry for status/tree/diff views. |
|
|
379
|
-
| \`/cc-finish\` | **Successful closeout.** Archives a completed run after strict ship closeout gates. |
|
|
374
|
+
| \`/cc\` | **Entry point.** No args = resume or progress current flow. With prompt = classify task and start the right flow. |
|
|
375
|
+
| \`/cc-idea\` | **Idea mode.** Generates a ranked repo-improvement backlog before implementation. |
|
|
380
376
|
| \`/cc-cancel\` | **Non-completion closeout.** Archives a cancelled/abandoned run with a required reason. |
|
|
381
377
|
|
|
382
378
|
Knowledge capture and curation run automatically as part of stage completion
|
|
@@ -384,8 +380,7 @@ protocols via the internal \`learnings\` skill — no user-facing command.
|
|
|
384
380
|
Reusable entries land in \`.cclaw/knowledge.jsonl\` as strict JSONL with
|
|
385
381
|
\`type\`, \`trigger\`, \`action\`, and \`origin_run\` metadata.
|
|
386
382
|
|
|
387
|
-
**Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship, then closeout: retro > compound > archive. Use \`/cc
|
|
388
|
-
\`/cc-next\` loads the right stage skill automatically and also drives post-ship closeout. Gates must pass before handoff.
|
|
383
|
+
**Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship, then closeout: retro > compound > archive. Use \`/cc\` to keep moving through normal work and post-ship closeout; use \`/cc-cancel\` for cancelled/abandoned runs. Gates must pass before handoff.
|
|
389
384
|
|
|
390
385
|
### Verification Discipline
|
|
391
386
|
|
|
@@ -406,12 +401,11 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
406
401
|
### Codex users
|
|
407
402
|
|
|
408
403
|
OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
|
|
409
|
-
were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-
|
|
410
|
-
\`/cc-
|
|
411
|
-
tokens above describe intent — in Codex they map onto skills cclaw installs at
|
|
404
|
+
were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-idea\`, and
|
|
405
|
+
\`/cc-cancel\` tokens above describe intent — in Codex they map onto skills cclaw installs at
|
|
412
406
|
\`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
|
|
413
407
|
|
|
414
|
-
- Type \`/use cc\` (or \`cc-
|
|
408
|
+
- Type \`/use cc\` (or \`cc-idea\` / \`cc-cancel\`) at Codex's prompt.
|
|
415
409
|
- Type \`/cc …\` as plain text — Codex matches the skill \`description\`
|
|
416
410
|
frontmatter (which spells out the token verbatim) and loads the right
|
|
417
411
|
skill body automatically.
|
|
@@ -420,9 +414,9 @@ Codex CLI v0.114+ (Mar 2026) **does** expose lifecycle hooks via
|
|
|
420
414
|
\`.codex/hooks.json\`, gated by the \`[features] codex_hooks = true\` flag
|
|
421
415
|
in \`~/.codex/config.toml\`. cclaw generates \`.codex/hooks.json\` on
|
|
422
416
|
sync; if the feature flag is off, hooks are inert and cclaw's
|
|
423
|
-
session-start rehydration simply does not fire. Run \`cclaw
|
|
417
|
+
session-start rehydration simply does not fire. Run \`npx cclaw-cli sync\` to
|
|
424
418
|
see if the flag is missing. \`.codex/commands/*\` is still unused by
|
|
425
|
-
Codex CLI and is removed on every sync. Run \`cclaw
|
|
419
|
+
Codex CLI and is removed on every sync. Run \`npx cclaw-cli sync\` for
|
|
426
420
|
hook coverage details (Bash-only \`PreToolUse\`/\`PostToolUse\`; other events are full).
|
|
427
421
|
${CCLAW_MARKER_END}`;
|
|
428
422
|
}
|
|
@@ -484,14 +478,8 @@ function utilityShimBehavior(command) {
|
|
|
484
478
|
switch (command) {
|
|
485
479
|
case "cc":
|
|
486
480
|
return "This is the entry command, not a flow stage. It may initialize or resume flow state after confirmation.";
|
|
487
|
-
case "
|
|
488
|
-
return "This is the progression command, not a flow stage. It may advance stages and post-ship closeout through managed helpers.";
|
|
489
|
-
case "ideate":
|
|
481
|
+
case "idea":
|
|
490
482
|
return "This is an ideation command, not a flow stage. It may write ideation artifacts/seeds but does not advance flow state.";
|
|
491
|
-
case "view":
|
|
492
|
-
return "This is a read-only view command, not a flow stage. It never mutates flow state.";
|
|
493
|
-
case "finish":
|
|
494
|
-
return "This is a successful closeout utility, not a flow stage. It archives a completed run after ship closeout gates pass and records completed disposition semantics.";
|
|
495
483
|
case "cancel":
|
|
496
484
|
return "This is a non-completion closeout utility, not a flow stage. It requires a reason and archives cancelled or abandoned work without presenting it as completed.";
|
|
497
485
|
default:
|
|
@@ -515,50 +503,6 @@ Load and execute:
|
|
|
515
503
|
${utilityShimBehavior(command)}
|
|
516
504
|
`;
|
|
517
505
|
}
|
|
518
|
-
function stageShimContent(harness, stage) {
|
|
519
|
-
const shimName = stageShimSkillName(stage);
|
|
520
|
-
const skillPath = `${RUNTIME_ROOT}/skills/${STAGE_TO_SKILL_FOLDER[stage]}/SKILL.md`;
|
|
521
|
-
return `---
|
|
522
|
-
name: ${shimName}
|
|
523
|
-
description: Generated shim for ${harness}. Flow stage pointer; normal advancement uses /cc-next.
|
|
524
|
-
source: generated-by-cclaw
|
|
525
|
-
---
|
|
526
|
-
|
|
527
|
-
# cclaw ${stage}
|
|
528
|
-
|
|
529
|
-
This is a thin compatibility shim for the \`${stage}\` flow stage.
|
|
530
|
-
|
|
531
|
-
Load and follow the authoritative stage skill:
|
|
532
|
-
|
|
533
|
-
- \`${skillPath}\`
|
|
534
|
-
|
|
535
|
-
Normal stage resume and advancement uses \`/cc-next\`. Use \`/cc-next\` to read
|
|
536
|
-
\`.cclaw/state/flow-state.json\`, select the active stage, and advance only after
|
|
537
|
-
that stage's gates pass. Do not duplicate the stage protocol here.
|
|
538
|
-
`;
|
|
539
|
-
}
|
|
540
|
-
function codexStageSkillMarkdown(stage) {
|
|
541
|
-
const skillName = stageShimSkillName(stage);
|
|
542
|
-
const skillPath = `${RUNTIME_ROOT}/skills/${STAGE_TO_SKILL_FOLDER[stage]}/SKILL.md`;
|
|
543
|
-
return `---
|
|
544
|
-
name: ${skillName}
|
|
545
|
-
description: Thin cclaw stage shim for /cc-${stage}. Load ${skillPath}; normal stage resume and advancement uses /cc-next.
|
|
546
|
-
source: generated-by-cclaw
|
|
547
|
-
---
|
|
548
|
-
|
|
549
|
-
# cclaw /cc-${stage} (Codex adapter)
|
|
550
|
-
|
|
551
|
-
This is a thin compatibility shim for the \`${stage}\` flow stage.
|
|
552
|
-
|
|
553
|
-
Load and follow the authoritative stage skill:
|
|
554
|
-
|
|
555
|
-
- \`${skillPath}\`
|
|
556
|
-
|
|
557
|
-
Normal stage resume and advancement uses \`/cc-next\`. Use \`/cc-next\` to read
|
|
558
|
-
\`.cclaw/state/flow-state.json\`, select the active stage, and advance only after
|
|
559
|
-
that stage's gates pass. Do not duplicate the stage protocol here.
|
|
560
|
-
`;
|
|
561
|
-
}
|
|
562
506
|
/**
|
|
563
507
|
* Frontmatter `description` that triggers the skill when the user types any
|
|
564
508
|
* of the classic cclaw slash-tokens. Codex's skill matcher runs on the skill
|
|
@@ -569,14 +513,8 @@ function codexSkillDescription(command) {
|
|
|
569
513
|
switch (command) {
|
|
570
514
|
case "cc":
|
|
571
515
|
return `Entry point for the cclaw track-aware workflow ending in ship plus auto-closeout (retro → compound → archive). Use whenever the user types \`/cc\`, \`/cclaw\`, or asks to "start the flow", "begin cclaw", "kick off the workflow", "classify this task", or wants to start/resume a non-trivial software change. No args = resume the active stage from \`.cclaw/state/flow-state.json\`. With a prompt = classify and pick a track (quick/medium/standard).`;
|
|
572
|
-
case "
|
|
573
|
-
return `
|
|
574
|
-
case "ideate":
|
|
575
|
-
return `Read-only repo-improvement ideate mode for cclaw. Use when the user types \`/cc-ideate\` or asks to "ideate", "scan the repo for TODOs/tech debt", "generate a backlog", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
|
|
576
|
-
case "view":
|
|
577
|
-
return `Read-only router for cclaw flow views. Use when the user types \`/cc-view\`, \`/cc-view status\`, \`/cc-view tree\`, \`/cc-view diff\`, or asks to "show cclaw status", "show the flow tree", "diff flow state", or wants a snapshot without mutation.`;
|
|
578
|
-
case "finish":
|
|
579
|
-
return `Finish a completed cclaw run. Use when the user types \`/cc-finish\` or asks to finish, complete, close out, or archive a successful run. Runs cclaw archive with completed disposition after strict ship closeout gates.`;
|
|
516
|
+
case "idea":
|
|
517
|
+
return `Read-only repo-improvement idea mode for cclaw. Use when the user types \`/cc-idea\` or asks to "scan the repo for TODOs/tech debt", "generate a backlog", "brainstorm improvement ideas", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
|
|
580
518
|
case "cancel":
|
|
581
519
|
return `Cancel or abandon the active cclaw run. Use when the user types \`/cc-cancel\` or asks to cancel, abandon, stop, discard, or reset an unfinished run. Requires a reason and archives with cancelled/abandoned disposition.`;
|
|
582
520
|
default:
|
|
@@ -609,7 +547,7 @@ under \`.agents/skills/${skillSlug}/\` so the user can either:
|
|
|
609
547
|
|
|
610
548
|
Lifecycle hooks **are** available in Codex CLI v0.114+ (behind the
|
|
611
549
|
\`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`) and
|
|
612
|
-
cclaw installs a matching \`.codex/hooks.json\`; run \`cclaw
|
|
550
|
+
cclaw installs a matching \`.codex/hooks.json\`; run \`npx cclaw-cli sync\`
|
|
613
551
|
for the current hook surface and limitations.
|
|
614
552
|
|
|
615
553
|
## Protocol
|
|
@@ -633,7 +571,7 @@ for the current hook surface and limitations.
|
|
|
633
571
|
unavailable or disabled, and then include non-empty \`evidenceRefs\`.
|
|
634
572
|
- Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
|
|
635
573
|
the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
|
|
636
|
-
are **not** gated by hooks — use \`cclaw
|
|
574
|
+
are **not** gated by hooks — use \`npx cclaw-cli sync\` for what cclaw
|
|
637
575
|
substitutes with in-turn agent steps for those call classes.
|
|
638
576
|
- Codex's \`SessionStart\` matcher only supports \`startup|resume\`. Claude
|
|
639
577
|
and Cursor also fire on \`clear\` and \`compact\`, so mid-session
|
|
@@ -660,9 +598,6 @@ async function writeCommandKindShims(commandDir, harness) {
|
|
|
660
598
|
for (const shim of UTILITY_SHIMS) {
|
|
661
599
|
await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
|
|
662
600
|
}
|
|
663
|
-
for (const stage of FLOW_STAGES) {
|
|
664
|
-
await writeFileSafe(path.join(commandDir, stageShimFileName(stage)), stageShimContent(harness, stage));
|
|
665
|
-
}
|
|
666
601
|
for (const legacy of LEGACY_HARNESS_SHIMS) {
|
|
667
602
|
const legacyPath = path.join(commandDir, legacy);
|
|
668
603
|
try {
|
|
@@ -679,9 +614,6 @@ async function writeSkillKindShims(commandDir) {
|
|
|
679
614
|
for (const shim of UTILITY_SHIMS) {
|
|
680
615
|
await writeFileSafe(path.join(commandDir, shim.skillName, "SKILL.md"), codexSkillMarkdown(shim.command, shim.skillName, shim.skillFolder, shim.commandFile));
|
|
681
616
|
}
|
|
682
|
-
for (const stage of FLOW_STAGES) {
|
|
683
|
-
await writeFileSafe(path.join(commandDir, stageShimSkillName(stage), "SKILL.md"), codexStageSkillMarkdown(stage));
|
|
684
|
-
}
|
|
685
617
|
}
|
|
686
618
|
/**
|
|
687
619
|
* Legacy codex surfaces cclaw wrote before v0.39.0 that Codex CLI never
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CliContext, type HarnessId } from "./types.js";
|
|
2
|
+
export type HarnessSelectionAnswer = {
|
|
3
|
+
kind: "accept";
|
|
4
|
+
} | {
|
|
5
|
+
kind: "all";
|
|
6
|
+
} | {
|
|
7
|
+
kind: "toggle";
|
|
8
|
+
indexes: number[];
|
|
9
|
+
} | {
|
|
10
|
+
kind: "invalid";
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
export interface HarnessChecklistState {
|
|
14
|
+
choices: readonly HarnessId[];
|
|
15
|
+
selected: readonly HarnessId[];
|
|
16
|
+
cursor: number;
|
|
17
|
+
message?: string;
|
|
18
|
+
}
|
|
19
|
+
export type HarnessChecklistOutcome = "confirm" | "cancel";
|
|
20
|
+
export interface HarnessChecklistUpdate {
|
|
21
|
+
state: HarnessChecklistState;
|
|
22
|
+
outcome?: HarnessChecklistOutcome;
|
|
23
|
+
}
|
|
24
|
+
export declare function parseHarnessSelectionAnswer(raw: string, total?: 4): HarnessSelectionAnswer;
|
|
25
|
+
export declare function createHarnessChecklistState(selected: readonly HarnessId[], choices?: readonly HarnessId[]): HarnessChecklistState;
|
|
26
|
+
export declare function updateHarnessChecklistState(state: HarnessChecklistState, key: string): HarnessChecklistUpdate;
|
|
27
|
+
export declare function promptHarnessSelectionChecklist(defaults: {
|
|
28
|
+
harnesses: HarnessId[];
|
|
29
|
+
detectedHarnesses?: HarnessId[];
|
|
30
|
+
currentHarnesses?: HarnessId[];
|
|
31
|
+
}, ctx: CliContext, label?: string): Promise<HarnessId[]>;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
4
|
+
import { HARNESS_IDS } from "./types.js";
|
|
5
|
+
export function parseHarnessSelectionAnswer(raw, total = HARNESS_IDS.length) {
|
|
6
|
+
const answer = raw.trim().toLowerCase();
|
|
7
|
+
if (answer.length === 0)
|
|
8
|
+
return { kind: "accept" };
|
|
9
|
+
if (answer === "all")
|
|
10
|
+
return { kind: "all" };
|
|
11
|
+
if (answer === "none") {
|
|
12
|
+
return { kind: "invalid", message: "Zero harnesses is not supported. Select at least one harness." };
|
|
13
|
+
}
|
|
14
|
+
const parts = answer.split(",").map((part) => part.trim()).filter(Boolean);
|
|
15
|
+
const indexes = parts.map((part) => Number.parseInt(part, 10));
|
|
16
|
+
if (indexes.some((value) => !Number.isInteger(value) || value < 1 || value > total)) {
|
|
17
|
+
return { kind: "invalid", message: `Invalid selection. Use numbers 1-${total}, comma-separated.` };
|
|
18
|
+
}
|
|
19
|
+
return { kind: "toggle", indexes };
|
|
20
|
+
}
|
|
21
|
+
export function createHarnessChecklistState(selected, choices = HARNESS_IDS) {
|
|
22
|
+
const validSelected = choices.filter((harness) => selected.includes(harness));
|
|
23
|
+
return {
|
|
24
|
+
choices,
|
|
25
|
+
selected: validSelected.length > 0 ? validSelected : choices.slice(),
|
|
26
|
+
cursor: 0
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function moveCursor(state, delta) {
|
|
30
|
+
const next = (state.cursor + delta + state.choices.length) % state.choices.length;
|
|
31
|
+
return { ...state, cursor: next, message: undefined };
|
|
32
|
+
}
|
|
33
|
+
function toggleCurrent(state) {
|
|
34
|
+
const current = state.choices[state.cursor];
|
|
35
|
+
if (!current)
|
|
36
|
+
return state;
|
|
37
|
+
const selected = state.selected.includes(current)
|
|
38
|
+
? state.selected.filter((harness) => harness !== current)
|
|
39
|
+
: [...state.selected, current];
|
|
40
|
+
return { ...state, selected: state.choices.filter((harness) => selected.includes(harness)), message: undefined };
|
|
41
|
+
}
|
|
42
|
+
export function updateHarnessChecklistState(state, key) {
|
|
43
|
+
if (key === "\u0003" || key === "\u001b") {
|
|
44
|
+
return { state: { ...state, message: "Cancelled." }, outcome: "cancel" };
|
|
45
|
+
}
|
|
46
|
+
if (key === "\u001b[A" || key === "k" || key === "K") {
|
|
47
|
+
return { state: moveCursor(state, -1) };
|
|
48
|
+
}
|
|
49
|
+
if (key === "\u001b[B" || key === "j" || key === "J") {
|
|
50
|
+
return { state: moveCursor(state, 1) };
|
|
51
|
+
}
|
|
52
|
+
if (key === " ") {
|
|
53
|
+
return { state: toggleCurrent(state) };
|
|
54
|
+
}
|
|
55
|
+
if (key === "a" || key === "A") {
|
|
56
|
+
return { state: { ...state, selected: state.choices.slice(), message: undefined } };
|
|
57
|
+
}
|
|
58
|
+
if (key === "\r" || key === "\n") {
|
|
59
|
+
if (state.selected.length === 0) {
|
|
60
|
+
return { state: { ...state, message: "Select at least one harness." } };
|
|
61
|
+
}
|
|
62
|
+
return { state, outcome: "confirm" };
|
|
63
|
+
}
|
|
64
|
+
return { state };
|
|
65
|
+
}
|
|
66
|
+
function selectedHarnessPreview(harnesses) {
|
|
67
|
+
return harnesses.length > 0 ? harnesses.join(", ") : "none";
|
|
68
|
+
}
|
|
69
|
+
function harnessLabel(harness) {
|
|
70
|
+
const adapter = HARNESS_ADAPTERS[harness];
|
|
71
|
+
const tier = adapter ? `${adapter.reality.declaredSupport}, ${adapter.capabilities.hookSurface} hooks` : "supported";
|
|
72
|
+
return `${harness} (${tier})`;
|
|
73
|
+
}
|
|
74
|
+
function renderChecklist(state, defaults, ctx, label) {
|
|
75
|
+
const detected = new Set(defaults.detectedHarnesses ?? []);
|
|
76
|
+
const current = new Set(defaults.currentHarnesses ?? []);
|
|
77
|
+
const defaultSet = new Set(defaults.defaultHarnesses ?? []);
|
|
78
|
+
ctx.stdout.write("\x1b[2J\x1b[H");
|
|
79
|
+
ctx.stdout.write(`${label}\n`);
|
|
80
|
+
ctx.stdout.write(`Detected: ${selectedHarnessPreview(defaults.detectedHarnesses ?? [])}\n`);
|
|
81
|
+
ctx.stdout.write(`Current: ${selectedHarnessPreview(defaults.currentHarnesses ?? [])}\n`);
|
|
82
|
+
ctx.stdout.write("Use Up/Down or k/j to move, Space to toggle, a to select all, Enter to confirm, Esc to cancel.\n\n");
|
|
83
|
+
state.choices.forEach((harness, index) => {
|
|
84
|
+
const adapter = HARNESS_ADAPTERS[harness];
|
|
85
|
+
const markers = [
|
|
86
|
+
detected.has(harness) ? "detected" : "",
|
|
87
|
+
current.has(harness) ? "current" : "",
|
|
88
|
+
defaultSet.has(harness) ? "default" : ""
|
|
89
|
+
].filter(Boolean).join(", ");
|
|
90
|
+
const pointer = index === state.cursor ? ">" : " ";
|
|
91
|
+
const checked = state.selected.includes(harness) ? "x" : " ";
|
|
92
|
+
ctx.stdout.write(`${pointer} [${checked}] ${harnessLabel(harness)} -> ${adapter.commandDir}${markers ? ` (${markers})` : ""}\n`);
|
|
93
|
+
});
|
|
94
|
+
if (state.message) {
|
|
95
|
+
ctx.stdout.write(`\n${state.message}\n`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function rawModeAvailable(ctx) {
|
|
99
|
+
return Boolean(process.stdin.isTTY &&
|
|
100
|
+
ctx.stdout.isTTY &&
|
|
101
|
+
typeof process.stdin.setRawMode === "function");
|
|
102
|
+
}
|
|
103
|
+
async function promptHarnessSelectionRaw(defaults, ctx, label) {
|
|
104
|
+
let state = createHarnessChecklistState(defaults.harnesses);
|
|
105
|
+
const input = process.stdin;
|
|
106
|
+
const wasRaw = Boolean(input.isRaw);
|
|
107
|
+
let settle;
|
|
108
|
+
let rejectSelection;
|
|
109
|
+
const done = new Promise((resolve, reject) => {
|
|
110
|
+
settle = resolve;
|
|
111
|
+
rejectSelection = reject;
|
|
112
|
+
});
|
|
113
|
+
const onData = (chunk) => {
|
|
114
|
+
const key = chunk.toString("utf8");
|
|
115
|
+
const update = updateHarnessChecklistState(state, key);
|
|
116
|
+
state = update.state;
|
|
117
|
+
renderChecklist(state, {
|
|
118
|
+
detectedHarnesses: defaults.detectedHarnesses,
|
|
119
|
+
currentHarnesses: defaults.currentHarnesses,
|
|
120
|
+
defaultHarnesses: defaults.harnesses
|
|
121
|
+
}, ctx, label);
|
|
122
|
+
if (update.outcome === "confirm") {
|
|
123
|
+
settle?.(HARNESS_IDS.filter((harness) => state.selected.includes(harness)));
|
|
124
|
+
}
|
|
125
|
+
else if (update.outcome === "cancel") {
|
|
126
|
+
rejectSelection?.(new Error("Harness selection cancelled."));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
input.setRawMode?.(true);
|
|
131
|
+
input.resume();
|
|
132
|
+
input.on("data", onData);
|
|
133
|
+
renderChecklist(state, {
|
|
134
|
+
detectedHarnesses: defaults.detectedHarnesses,
|
|
135
|
+
currentHarnesses: defaults.currentHarnesses,
|
|
136
|
+
defaultHarnesses: defaults.harnesses
|
|
137
|
+
}, ctx, label);
|
|
138
|
+
return await done;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
input.off("data", onData);
|
|
142
|
+
input.setRawMode?.(wasRaw);
|
|
143
|
+
if (!wasRaw)
|
|
144
|
+
input.pause();
|
|
145
|
+
ctx.stdout.write("\n");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function promptHarnessSelectionText(defaults, ctx, label) {
|
|
149
|
+
const rl = createInterface({
|
|
150
|
+
input: process.stdin,
|
|
151
|
+
output: ctx.stdout
|
|
152
|
+
});
|
|
153
|
+
const defaultSet = new Set(defaults.harnesses);
|
|
154
|
+
const selected = new Set(defaults.harnesses.length > 0 ? defaults.harnesses : HARNESS_IDS);
|
|
155
|
+
const detected = new Set(defaults.detectedHarnesses ?? []);
|
|
156
|
+
const current = new Set(defaults.currentHarnesses ?? []);
|
|
157
|
+
const printMenu = () => {
|
|
158
|
+
ctx.stdout.write(`\n${label}\n`);
|
|
159
|
+
ctx.stdout.write(`Detected: ${selectedHarnessPreview(defaults.detectedHarnesses ?? [])}\n`);
|
|
160
|
+
ctx.stdout.write(`Current: ${selectedHarnessPreview(defaults.currentHarnesses ?? [])}\n`);
|
|
161
|
+
ctx.stdout.write("Supported harnesses and target paths:\n");
|
|
162
|
+
HARNESS_IDS.forEach((harness, index) => {
|
|
163
|
+
const adapter = HARNESS_ADAPTERS[harness];
|
|
164
|
+
const markers = [
|
|
165
|
+
detected.has(harness) ? "detected" : "",
|
|
166
|
+
current.has(harness) ? "current" : "",
|
|
167
|
+
defaultSet.has(harness) ? "default" : ""
|
|
168
|
+
].filter(Boolean).join(", ");
|
|
169
|
+
const checked = selected.has(harness) ? "x" : " ";
|
|
170
|
+
ctx.stdout.write(` ${index + 1}. [${checked}] ${harnessLabel(harness)} -> ${adapter.commandDir}${markers ? ` (${markers})` : ""}\n`);
|
|
171
|
+
});
|
|
172
|
+
ctx.stdout.write("Enter numbers to toggle (for example 1,3), 'all', or press Enter to accept.\n");
|
|
173
|
+
};
|
|
174
|
+
try {
|
|
175
|
+
while (true) {
|
|
176
|
+
printMenu();
|
|
177
|
+
const answer = await rl.question(`Selected [${[...selected].join(",") || "select at least one"}]: `);
|
|
178
|
+
const parsedAnswer = parseHarnessSelectionAnswer(answer);
|
|
179
|
+
if (parsedAnswer.kind === "accept") {
|
|
180
|
+
if (selected.size === 0) {
|
|
181
|
+
ctx.stdout.write("Select at least one harness.\n");
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
return HARNESS_IDS.filter((harness) => selected.has(harness));
|
|
185
|
+
}
|
|
186
|
+
if (parsedAnswer.kind === "all") {
|
|
187
|
+
HARNESS_IDS.forEach((harness) => selected.add(harness));
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (parsedAnswer.kind === "invalid") {
|
|
191
|
+
ctx.stdout.write(`${parsedAnswer.message}\n`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
for (const index of parsedAnswer.indexes) {
|
|
195
|
+
const harness = HARNESS_IDS[index - 1];
|
|
196
|
+
if (!harness)
|
|
197
|
+
continue;
|
|
198
|
+
if (selected.has(harness))
|
|
199
|
+
selected.delete(harness);
|
|
200
|
+
else
|
|
201
|
+
selected.add(harness);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
rl.close();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export async function promptHarnessSelectionChecklist(defaults, ctx, label = "Harness selection") {
|
|
210
|
+
if (rawModeAvailable(ctx)) {
|
|
211
|
+
return promptHarnessSelectionRaw(defaults, ctx, label);
|
|
212
|
+
}
|
|
213
|
+
return promptHarnessSelectionText(defaults, ctx, label);
|
|
214
|
+
}
|