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.
@@ -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-next\`.
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-next\` = only progression path.
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", "finish", "cancel"]) {
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-next`
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-next");
1268
+ content.includes("/cc");
1274
1269
  }
1275
1270
  checks.push({
1276
1271
  name: "rules:cursor:workflow",
@@ -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-next` reads `shipSubstate` and resumes
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.
@@ -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-next` reads `shipSubstate` and resumes
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-next`, etc.
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-next.md`, …).
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
  */
@@ -1,11 +1,10 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { RUNTIME_ROOT, STAGE_TO_SKILL_FOLDER } from "./constants.js";
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 \`/cc-next\` to continue the flow.
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 stage. With prompt = classify task and start the right flow. |
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-finish\` for completed runs and \`/cc-cancel\` for cancelled/abandoned runs.
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-next\`,
410
- \`/cc-ideate\`, \`/cc-view\`, \`/cc-finish\`, and \`/cc-cancel\`
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-next\`, etc.) at Codex's prompt.
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-next after the stale marker is acknowledged."
1425
+ "Continue with /cc after the stale marker is acknowledged."
1426
1426
  ]
1427
1427
  };
1428
1428
  await appendRewindLog(projectRoot, payload);