cclaw-cli 0.51.29 → 0.51.30
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/dist/cli.d.ts +1 -12
- package/dist/cli.js +6 -89
- package/dist/content/closeout-guidance.js +3 -3
- package/dist/content/core-agents.js +5 -5
- package/dist/content/meta-skill.js +7 -9
- package/dist/content/next-command.d.ts +2 -2
- package/dist/content/next-command.js +15 -15
- package/dist/content/node-hooks.js +2 -2
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/stage-command.js +1 -1
- package/dist/content/stage-common-guidance.js +4 -4
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/review.js +1 -1
- package/dist/content/start-command.d.ts +2 -2
- package/dist/content/start-command.js +14 -12
- package/dist/content/status-command.js +5 -5
- package/dist/content/subagents.js +1 -1
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +2 -2
- package/dist/doctor.js +5 -10
- package/dist/flow-state.d.ts +1 -1
- package/dist/flow-state.js +1 -1
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +9 -106
- package/dist/harness-selection.d.ts +31 -0
- package/dist/harness-selection.js +214 -0
- package/dist/install.js +13 -9
- package/dist/internal/advance-stage.js +1 -1
- package/dist/policy.js +1 -1
- package/dist/run-archive.js +4 -4
- package/dist/run-persistence.js +1 -1
- package/package.json +1 -1
- package/dist/content/finish-command.d.ts +0 -2
- package/dist/content/finish-command.js +0 -26
|
@@ -1410,7 +1410,7 @@ alwaysApply: true
|
|
|
1410
1410
|
|
|
1411
1411
|
Before responding to coding work:
|
|
1412
1412
|
1. Read \`.cclaw/state/flow-state.json\`.
|
|
1413
|
-
2. Start with \`/cc\` or continue with \`/cc
|
|
1413
|
+
2. Start with \`/cc\` or continue with \`/cc\`.
|
|
1414
1414
|
3. If no software-stage flow applies, respond normally.
|
|
1415
1415
|
|
|
1416
1416
|
## Stage Order
|
|
@@ -1431,7 +1431,7 @@ Track-specific skips are allowed only when \`flow-state.track\` + \`skippedStage
|
|
|
1431
1431
|
## Command Surface
|
|
1432
1432
|
|
|
1433
1433
|
- \`/cc\` = entry and resume.
|
|
1434
|
-
- \`/cc
|
|
1434
|
+
- \`/cc\` = only progression path.
|
|
1435
1435
|
- Knowledge capture and recall use the \`learnings\` skill when requested.
|
|
1436
1436
|
|
|
1437
1437
|
## Verification Discipline
|
package/dist/doctor.js
CHANGED
|
@@ -982,21 +982,17 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
982
982
|
const content = await fs.readFile(agentsFile, "utf8");
|
|
983
983
|
const hasMarkers = content.includes(CCLAW_MARKER_START) && content.includes(CCLAW_MARKER_END);
|
|
984
984
|
const hasCcCommand = content.includes("/cc");
|
|
985
|
-
const hasCcNext = content.includes("/cc-next");
|
|
986
985
|
const hasCcIdeate = content.includes("/cc-ideate");
|
|
987
|
-
const hasCcView = content.includes("/cc-view");
|
|
988
|
-
const hasCcFinish = content.includes("/cc-finish");
|
|
989
986
|
const hasCcCancel = content.includes("/cc-cancel");
|
|
987
|
+
const omitsFinish = !content.includes("/cc-finish");
|
|
990
988
|
const hasVerification = content.includes("Verification Discipline");
|
|
991
989
|
const hasMinimalMarker = content.includes("intentionally minimal for cross-project use");
|
|
992
990
|
const hasMetaSkillPointer = content.includes(".cclaw/skills/using-cclaw/SKILL.md");
|
|
993
991
|
agentsBlockOk = hasMarkers
|
|
994
992
|
&& hasCcCommand
|
|
995
|
-
&& hasCcNext
|
|
996
993
|
&& hasCcIdeate
|
|
997
|
-
&& hasCcView
|
|
998
|
-
&& hasCcFinish
|
|
999
994
|
&& hasCcCancel
|
|
995
|
+
&& omitsFinish
|
|
1000
996
|
&& hasVerification
|
|
1001
997
|
&& hasMinimalMarker
|
|
1002
998
|
&& hasMetaSkillPointer;
|
|
@@ -1006,7 +1002,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1006
1002
|
ok: agentsBlockOk,
|
|
1007
1003
|
details: `${agentsFile} must contain the managed cclaw marker block with routing, verification, and minimal detail pointer`
|
|
1008
1004
|
});
|
|
1009
|
-
for (const cmd of ["start", "next", "ideate", "view", "
|
|
1005
|
+
for (const cmd of ["start", "next", "ideate", "view", "cancel"]) {
|
|
1010
1006
|
const cmdPath = path.join(projectRoot, RUNTIME_ROOT, "commands", `${cmd}.md`);
|
|
1011
1007
|
checks.push({
|
|
1012
1008
|
name: `utility_command:${cmd}`,
|
|
@@ -1024,7 +1020,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1024
1020
|
checks.push({
|
|
1025
1021
|
name: `stage_command:${stage}`,
|
|
1026
1022
|
ok: stageCommandOk,
|
|
1027
|
-
details: `${cmdPath} must be a thin shim to ${RUNTIME_ROOT}/skills/${stageSkillFolder(stage)}/SKILL.md and /cc
|
|
1023
|
+
details: `${cmdPath} must be a thin shim to ${RUNTIME_ROOT}/skills/${stageSkillFolder(stage)}/SKILL.md and /cc`
|
|
1028
1024
|
});
|
|
1029
1025
|
}
|
|
1030
1026
|
// Utility skills
|
|
@@ -1032,7 +1028,6 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1032
1028
|
["learnings", "learnings"],
|
|
1033
1029
|
["flow-ideate", "flow-ideate"],
|
|
1034
1030
|
["flow-view", "flow-view"],
|
|
1035
|
-
["flow-finish", "flow-finish"],
|
|
1036
1031
|
["flow-cancel", "flow-cancel"],
|
|
1037
1032
|
["subagent-dev", "sdd"],
|
|
1038
1033
|
["parallel-dispatch", "parallel-agents"],
|
|
@@ -1270,7 +1265,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1270
1265
|
cursorRuleOk =
|
|
1271
1266
|
content.includes("cclaw-managed-cursor-workflow-rule") &&
|
|
1272
1267
|
content.includes(".cclaw/state/flow-state.json") &&
|
|
1273
|
-
content.includes("/cc
|
|
1268
|
+
content.includes("/cc");
|
|
1274
1269
|
}
|
|
1275
1270
|
checks.push({
|
|
1276
1271
|
name: "rules:cursor:workflow",
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface RetroState {
|
|
|
33
33
|
* Ship closeout substate machine.
|
|
34
34
|
*
|
|
35
35
|
* After ship completes, cclaw auto-chains retro → compound → archive.
|
|
36
|
-
* Each step is interruptible: `/cc
|
|
36
|
+
* Each step is interruptible: `/cc` reads `shipSubstate` and resumes
|
|
37
37
|
* from the correct step even across sessions.
|
|
38
38
|
*
|
|
39
39
|
* - `idle` — ship not complete, or closeout not yet started.
|
package/dist/flow-state.js
CHANGED
|
@@ -6,7 +6,7 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
|
|
|
6
6
|
* Ship closeout substate machine.
|
|
7
7
|
*
|
|
8
8
|
* After ship completes, cclaw auto-chains retro → compound → archive.
|
|
9
|
-
* Each step is interruptible: `/cc
|
|
9
|
+
* Each step is interruptible: `/cc` reads `shipSubstate` and resumes
|
|
10
10
|
* from the correct step even across sessions.
|
|
11
11
|
*
|
|
12
12
|
* - `idle` — ship not complete, or closeout not yet started.
|
|
@@ -32,7 +32,7 @@ export type SubagentFallback =
|
|
|
32
32
|
* directories under a skills root (Codex CLI ≥0.89, Jan 2026). cclaw
|
|
33
33
|
* writes `<commandDir>/<skillName>/SKILL.md` and the agent invokes it
|
|
34
34
|
* either via `/use <skillName>` or via automatic description matching
|
|
35
|
-
* when the user's text mentions `/cc`, `/cc-
|
|
35
|
+
* when the user's text mentions `/cc`, `/cc-ideate`, or `/cc-cancel`.
|
|
36
36
|
*/
|
|
37
37
|
export type ShimKind = "command" | "skill";
|
|
38
38
|
export interface HarnessAdapter {
|
|
@@ -47,7 +47,7 @@ export interface HarnessAdapter {
|
|
|
47
47
|
* Root directory where cclaw writes `/cc*` entry points.
|
|
48
48
|
*
|
|
49
49
|
* - For `shimKind: "command"` this is the directory containing flat
|
|
50
|
-
* markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-
|
|
50
|
+
* markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-ideate.md`, …).
|
|
51
51
|
* - For `shimKind: "skill"` this is the skills root that contains
|
|
52
52
|
* per-skill subdirectories (`<commandDir>/<skillName>/SKILL.md`).
|
|
53
53
|
*/
|
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
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";
|
|
9
8
|
export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
|
|
10
9
|
export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
|
|
11
10
|
function escapeRegExp(value) {
|
|
@@ -15,13 +14,6 @@ const RUNTIME_AGENTS_BLOCK_SOURCE = `${escapeRegExp(CCLAW_MARKER_START)}[\\s\\S]
|
|
|
15
14
|
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
|
-
{
|
|
19
|
-
fileName: "cc-next.md",
|
|
20
|
-
skillName: "cc-next",
|
|
21
|
-
command: "next",
|
|
22
|
-
skillFolder: "flow-next-step",
|
|
23
|
-
commandFile: "next.md"
|
|
24
|
-
},
|
|
25
17
|
{
|
|
26
18
|
fileName: "cc-ideate.md",
|
|
27
19
|
skillName: "cc-ideate",
|
|
@@ -29,20 +21,6 @@ const UTILITY_SHIMS = [
|
|
|
29
21
|
skillFolder: "flow-ideate",
|
|
30
22
|
commandFile: "ideate.md"
|
|
31
23
|
},
|
|
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"
|
|
45
|
-
},
|
|
46
24
|
{
|
|
47
25
|
fileName: "cc-cancel.md",
|
|
48
26
|
skillName: "cc-cancel",
|
|
@@ -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 = {
|
|
@@ -343,7 +313,7 @@ Treat quality as a hard requirement, not style preference:
|
|
|
343
313
|
|
|
344
314
|
Before responding to a coding request:
|
|
345
315
|
1. Read \`.cclaw/state/flow-state.json\` for the current stage.
|
|
346
|
-
2. Use \`/cc\` to start or
|
|
316
|
+
2. Use \`/cc\` to start, resume, or continue the flow.
|
|
347
317
|
3. If no stage applies, respond normally.
|
|
348
318
|
|
|
349
319
|
${ironLawsAgentsMdBlock()}
|
|
@@ -372,11 +342,8 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
|
|
|
372
342
|
|
|
373
343
|
| Command | Purpose |
|
|
374
344
|
|---|---|
|
|
375
|
-
| \`/cc\` | **Entry point.** No args = resume current
|
|
376
|
-
| \`/cc-next\` | **Progression.** Advances to the next stage when current is complete. |
|
|
345
|
+
| \`/cc\` | **Entry point.** No args = resume or progress current flow. With prompt = classify task and start the right flow. |
|
|
377
346
|
| \`/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. |
|
|
380
347
|
| \`/cc-cancel\` | **Non-completion closeout.** Archives a cancelled/abandoned run with a required reason. |
|
|
381
348
|
|
|
382
349
|
Knowledge capture and curation run automatically as part of stage completion
|
|
@@ -384,8 +351,7 @@ protocols via the internal \`learnings\` skill — no user-facing command.
|
|
|
384
351
|
Reusable entries land in \`.cclaw/knowledge.jsonl\` as strict JSONL with
|
|
385
352
|
\`type\`, \`trigger\`, \`action\`, and \`origin_run\` metadata.
|
|
386
353
|
|
|
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.
|
|
354
|
+
**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
355
|
|
|
390
356
|
### Verification Discipline
|
|
391
357
|
|
|
@@ -406,12 +372,11 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
406
372
|
### Codex users
|
|
407
373
|
|
|
408
374
|
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
|
|
375
|
+
were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-ideate\`, and
|
|
376
|
+
\`/cc-cancel\` tokens above describe intent — in Codex they map onto skills cclaw installs at
|
|
412
377
|
\`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
|
|
413
378
|
|
|
414
|
-
- Type \`/use cc\` (or \`cc-
|
|
379
|
+
- Type \`/use cc\` (or \`cc-ideate\` / \`cc-cancel\`) at Codex's prompt.
|
|
415
380
|
- Type \`/cc …\` as plain text — Codex matches the skill \`description\`
|
|
416
381
|
frontmatter (which spells out the token verbatim) and loads the right
|
|
417
382
|
skill body automatically.
|
|
@@ -484,14 +449,8 @@ function utilityShimBehavior(command) {
|
|
|
484
449
|
switch (command) {
|
|
485
450
|
case "cc":
|
|
486
451
|
return "This is the entry command, not a flow stage. It may initialize or resume flow state after confirmation.";
|
|
487
|
-
case "next":
|
|
488
|
-
return "This is the progression command, not a flow stage. It may advance stages and post-ship closeout through managed helpers.";
|
|
489
452
|
case "ideate":
|
|
490
453
|
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
454
|
case "cancel":
|
|
496
455
|
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
456
|
default:
|
|
@@ -515,50 +474,6 @@ Load and execute:
|
|
|
515
474
|
${utilityShimBehavior(command)}
|
|
516
475
|
`;
|
|
517
476
|
}
|
|
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
477
|
/**
|
|
563
478
|
* Frontmatter `description` that triggers the skill when the user types any
|
|
564
479
|
* of the classic cclaw slash-tokens. Codex's skill matcher runs on the skill
|
|
@@ -569,14 +484,8 @@ function codexSkillDescription(command) {
|
|
|
569
484
|
switch (command) {
|
|
570
485
|
case "cc":
|
|
571
486
|
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 "next":
|
|
573
|
-
return `Advance the cclaw flow to the next stage or post-ship closeout substate. Use when the user types \`/cc-next\` or asks to "move to the next stage", "continue the flow", "advance cclaw", "progress the workflow", or when the current stage skill reports completion and gates have passed.`;
|
|
574
487
|
case "ideate":
|
|
575
488
|
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.`;
|
|
580
489
|
case "cancel":
|
|
581
490
|
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
491
|
default:
|
|
@@ -660,9 +569,6 @@ async function writeCommandKindShims(commandDir, harness) {
|
|
|
660
569
|
for (const shim of UTILITY_SHIMS) {
|
|
661
570
|
await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
|
|
662
571
|
}
|
|
663
|
-
for (const stage of FLOW_STAGES) {
|
|
664
|
-
await writeFileSafe(path.join(commandDir, stageShimFileName(stage)), stageShimContent(harness, stage));
|
|
665
|
-
}
|
|
666
572
|
for (const legacy of LEGACY_HARNESS_SHIMS) {
|
|
667
573
|
const legacyPath = path.join(commandDir, legacy);
|
|
668
574
|
try {
|
|
@@ -679,9 +585,6 @@ async function writeSkillKindShims(commandDir) {
|
|
|
679
585
|
for (const shim of UTILITY_SHIMS) {
|
|
680
586
|
await writeFileSafe(path.join(commandDir, shim.skillName, "SKILL.md"), codexSkillMarkdown(shim.command, shim.skillName, shim.skillFolder, shim.commandFile));
|
|
681
587
|
}
|
|
682
|
-
for (const stage of FLOW_STAGES) {
|
|
683
|
-
await writeFileSafe(path.join(commandDir, stageShimSkillName(stage), "SKILL.md"), codexStageSkillMarkdown(stage));
|
|
684
|
-
}
|
|
685
588
|
}
|
|
686
589
|
/**
|
|
687
590
|
* 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
|
+
}
|
package/dist/install.js
CHANGED
|
@@ -10,7 +10,6 @@ import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
|
10
10
|
import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
|
|
11
11
|
import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
|
|
12
12
|
import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
|
|
13
|
-
import { finishCommandContract, finishCommandSkillMarkdown } from "./content/finish-command.js";
|
|
14
13
|
import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
|
|
15
14
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
16
15
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
@@ -102,6 +101,7 @@ const DEPRECATED_AGENT_FILES = [
|
|
|
102
101
|
];
|
|
103
102
|
const DEPRECATED_COMMAND_FILES = [
|
|
104
103
|
"learn.md",
|
|
104
|
+
"finish.md",
|
|
105
105
|
"status.md",
|
|
106
106
|
"tree.md",
|
|
107
107
|
"diff.md",
|
|
@@ -114,6 +114,7 @@ const DEPRECATED_COMMAND_FILES = [
|
|
|
114
114
|
"rewind.md"
|
|
115
115
|
];
|
|
116
116
|
const DEPRECATED_SKILL_FILES = [
|
|
117
|
+
["flow-finish", "SKILL.md"],
|
|
117
118
|
["flow-ops", "SKILL.md"],
|
|
118
119
|
["tdd-cycle-log", "SKILL.md"],
|
|
119
120
|
["flow-retro", "SKILL.md"],
|
|
@@ -454,7 +455,6 @@ async function writeSkills(projectRoot, config) {
|
|
|
454
455
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ideate", "SKILL.md"), ideateCommandSkillMarkdown());
|
|
455
456
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
|
|
456
457
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
|
|
457
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-finish", "SKILL.md"), finishCommandSkillMarkdown());
|
|
458
458
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
|
|
459
459
|
await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
|
|
460
460
|
await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
|
|
@@ -519,7 +519,6 @@ async function writeEntryCommands(projectRoot) {
|
|
|
519
519
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
520
520
|
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
521
521
|
await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
|
|
522
|
-
await writeFileSafe(runtimePath(projectRoot, "commands", "finish.md"), finishCommandContract());
|
|
523
522
|
await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
|
|
524
523
|
for (const stage of FLOW_STAGES) {
|
|
525
524
|
await writeFileSafe(runtimePath(projectRoot, "commands", `${stage}.md`), stageCommandShimMarkdown(stage));
|
|
@@ -1062,13 +1061,8 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
1062
1061
|
}
|
|
1063
1062
|
async function cleanStaleFiles(projectRoot) {
|
|
1064
1063
|
const expectedShimFiles = new Set(harnessShimFileNames());
|
|
1064
|
+
const expectedShimSkills = new Set(harnessShimFileNames().map((fileName) => fileName.replace(/\.md$/u, "")));
|
|
1065
1065
|
for (const adapter of Object.values(HARNESS_ADAPTERS)) {
|
|
1066
|
-
// Skill-kind shims (Codex) live in per-skill directories, not flat
|
|
1067
|
-
// markdown files, so the regex-based stale sweep below would never
|
|
1068
|
-
// match them anyway. The legacy `.codex/commands/` cleanup happens in
|
|
1069
|
-
// `cleanupLegacyCodexSurfaces` inside syncHarnessShims().
|
|
1070
|
-
if (adapter.shimKind === "skill")
|
|
1071
|
-
continue;
|
|
1072
1066
|
const commandDir = path.join(projectRoot, adapter.commandDir);
|
|
1073
1067
|
if (!(await exists(commandDir)))
|
|
1074
1068
|
continue;
|
|
@@ -1079,6 +1073,16 @@ async function cleanStaleFiles(projectRoot) {
|
|
|
1079
1073
|
catch {
|
|
1080
1074
|
entries = [];
|
|
1081
1075
|
}
|
|
1076
|
+
if (adapter.shimKind === "skill") {
|
|
1077
|
+
for (const entry of entries) {
|
|
1078
|
+
if (!/^cc(?:-.*)?$/u.test(entry))
|
|
1079
|
+
continue;
|
|
1080
|
+
if (expectedShimSkills.has(entry))
|
|
1081
|
+
continue;
|
|
1082
|
+
await fs.rm(path.join(commandDir, entry), { recursive: true, force: true });
|
|
1083
|
+
}
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1082
1086
|
for (const entry of entries) {
|
|
1083
1087
|
if (!/^cc(?:-.*)?\.md$/u.test(entry))
|
|
1084
1088
|
continue;
|
|
@@ -1422,7 +1422,7 @@ async function runRewind(projectRoot, args, io) {
|
|
|
1422
1422
|
nextActions: [
|
|
1423
1423
|
`Re-run ${args.targetStage} stage work and update its artifact evidence.`,
|
|
1424
1424
|
`Then run cclaw internal rewind --ack ${args.targetStage}.`,
|
|
1425
|
-
"Continue with /cc
|
|
1425
|
+
"Continue with /cc after the stale marker is acknowledged."
|
|
1426
1426
|
]
|
|
1427
1427
|
};
|
|
1428
1428
|
await appendRewindLog(projectRoot, payload);
|