cclaw-cli 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +25 -1
  3. package/dist/config.js +19 -8
  4. package/dist/constants.d.ts +2 -2
  5. package/dist/constants.js +16 -1
  6. package/dist/content/archive-command.d.ts +2 -0
  7. package/dist/content/archive-command.js +98 -0
  8. package/dist/content/contracts.js +1 -1
  9. package/dist/content/diff-command.d.ts +2 -0
  10. package/dist/content/diff-command.js +83 -0
  11. package/dist/content/feature-command.d.ts +2 -0
  12. package/dist/content/feature-command.js +120 -0
  13. package/dist/content/harnesses-doc.js +11 -0
  14. package/dist/content/hooks.js +48 -2
  15. package/dist/content/learnings.d.ts +0 -2
  16. package/dist/content/learnings.js +4 -33
  17. package/dist/content/meta-skill.js +4 -2
  18. package/dist/content/next-command.js +18 -9
  19. package/dist/content/observe.d.ts +5 -1
  20. package/dist/content/observe.js +134 -2
  21. package/dist/content/ops-command.d.ts +2 -0
  22. package/dist/content/ops-command.js +60 -0
  23. package/dist/content/protocols.js +14 -2
  24. package/dist/content/retro-command.d.ts +2 -0
  25. package/dist/content/retro-command.js +77 -0
  26. package/dist/content/rewind-command.d.ts +3 -0
  27. package/dist/content/rewind-command.js +120 -0
  28. package/dist/content/skills.js +2 -0
  29. package/dist/content/stage-common-guidance.js +2 -1
  30. package/dist/content/status-command.js +43 -35
  31. package/dist/content/tdd-log-command.d.ts +2 -0
  32. package/dist/content/tdd-log-command.js +75 -0
  33. package/dist/content/templates.d.ts +1 -1
  34. package/dist/content/templates.js +36 -6
  35. package/dist/content/tree-command.d.ts +2 -0
  36. package/dist/content/tree-command.js +91 -0
  37. package/dist/content/utility-skills.js +1 -1
  38. package/dist/content/view-command.d.ts +2 -0
  39. package/dist/content/view-command.js +57 -0
  40. package/dist/doctor-registry.js +3 -3
  41. package/dist/doctor.js +149 -3
  42. package/dist/feature-system.d.ts +18 -0
  43. package/dist/feature-system.js +247 -0
  44. package/dist/flow-state.d.ts +25 -0
  45. package/dist/flow-state.js +8 -1
  46. package/dist/harness-adapters.js +95 -4
  47. package/dist/install.js +44 -2
  48. package/dist/policy.js +22 -0
  49. package/dist/runs.d.ts +33 -1
  50. package/dist/runs.js +365 -6
  51. package/dist/tdd-cycle.d.ts +22 -0
  52. package/dist/tdd-cycle.js +82 -0
  53. package/dist/types.d.ts +4 -2
  54. package/package.json +1 -1
package/dist/cli.d.ts CHANGED
@@ -14,6 +14,8 @@ interface ParsedArgs {
14
14
  doctorQuiet?: boolean;
15
15
  doctorOnly?: string[];
16
16
  archiveName?: string;
17
+ archiveSkipRetro?: boolean;
18
+ archiveSkipRetroReason?: string;
17
19
  showHelp?: boolean;
18
20
  showVersion?: boolean;
19
21
  }
package/dist/cli.js CHANGED
@@ -39,6 +39,8 @@ Commands:
39
39
  --quiet Print only failing checks (and totals).
40
40
  archive Move .cclaw/artifacts into .cclaw/runs/<date>-<slug> and reset flow state.
41
41
  Flags: --name=<feature> Feature slug (default: inferred from 00-idea.md).
42
+ --skip-retro Bypass mandatory retro gate (requires --retro-reason).
43
+ --retro-reason=<t> Reason for bypassing retro gate.
42
44
  upgrade Refresh generated files in .cclaw without modifying user artifacts.
43
45
  uninstall Remove .cclaw runtime and the generated harness shim files.
44
46
 
@@ -113,6 +115,17 @@ function buildInitSurfacePreview(harnesses) {
113
115
  ".cclaw/config.yaml",
114
116
  ".cclaw/commands/*.md",
115
117
  ".cclaw/skills/*/SKILL.md",
118
+ ".cclaw/contexts/*.md",
119
+ ".cclaw/templates/*",
120
+ ".cclaw/agents/*.md",
121
+ ".cclaw/hooks/*",
122
+ ".cclaw/rules/**",
123
+ ".cclaw/adapters/*.md",
124
+ ".cclaw/custom-skills/README.md",
125
+ ".cclaw/features/**",
126
+ ".cclaw/runs/**",
127
+ ".cclaw/artifacts/**",
128
+ ".cclaw/knowledge.jsonl",
116
129
  ".cclaw/state/*.json|*.jsonl",
117
130
  ".cclaw/references/**",
118
131
  "AGENTS.md (managed block)"
@@ -368,6 +381,14 @@ function parseArgs(argv) {
368
381
  }
369
382
  if (flag.startsWith("--name=")) {
370
383
  parsed.archiveName = flag.replace("--name=", "").trim();
384
+ continue;
385
+ }
386
+ if (flag === "--skip-retro") {
387
+ parsed.archiveSkipRetro = true;
388
+ continue;
389
+ }
390
+ if (flag.startsWith("--retro-reason=")) {
391
+ parsed.archiveSkipRetroReason = flag.replace("--retro-reason=", "").trim();
371
392
  }
372
393
  }
373
394
  return parsed;
@@ -466,7 +487,10 @@ async function runCommand(parsed, ctx) {
466
487
  return 0;
467
488
  }
468
489
  if (command === "archive") {
469
- const archived = await archiveRun(ctx.cwd, parsed.archiveName);
490
+ const archived = await archiveRun(ctx.cwd, parsed.archiveName, {
491
+ skipRetro: parsed.archiveSkipRetro === true,
492
+ skipRetroReason: parsed.archiveSkipRetroReason
493
+ });
470
494
  const snapshotSummary = archived.snapshottedStateFiles.length > 0
471
495
  ? ` Snapshotted ${archived.snapshottedStateFiles.length} state file(s) under ${archived.archivePath}/state and wrote archive-manifest.json.`
472
496
  : "";
package/dist/config.js CHANGED
@@ -15,8 +15,9 @@ const ALLOWED_CONFIG_KEYS = new Set([
15
15
  "version",
16
16
  "flowVersion",
17
17
  "harnesses",
18
- "autoAdvance",
19
18
  "promptGuardMode",
19
+ "tddEnforcement",
20
+ "tddTestGlobs",
20
21
  "gitHookGuards",
21
22
  "defaultTrack",
22
23
  "languageRulePacks",
@@ -58,8 +59,9 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
58
59
  version: CCLAW_VERSION,
59
60
  flowVersion: FLOW_VERSION,
60
61
  harnesses,
61
- autoAdvance: false,
62
62
  promptGuardMode: "advisory",
63
+ tddEnforcement: "advisory",
64
+ tddTestGlobs: ["**/*.test.*", "**/*.spec.*", "**/test/**"],
63
65
  gitHookGuards: false,
64
66
  defaultTrack,
65
67
  languageRulePacks: []
@@ -77,8 +79,8 @@ export function createProfileConfig(profile, overrides = {}) {
77
79
  return {
78
80
  ...base,
79
81
  harnesses: overrides.harnesses ?? ["claude"],
80
- autoAdvance: false,
81
82
  promptGuardMode: "advisory",
83
+ tddEnforcement: "advisory",
82
84
  gitHookGuards: false,
83
85
  defaultTrack: overrides.defaultTrack ?? "medium",
84
86
  languageRulePacks: overrides.languageRulePacks ?? []
@@ -87,8 +89,8 @@ export function createProfileConfig(profile, overrides = {}) {
87
89
  return {
88
90
  ...base,
89
91
  harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
90
- autoAdvance: false,
91
92
  promptGuardMode: "advisory",
93
+ tddEnforcement: "advisory",
92
94
  gitHookGuards: false,
93
95
  defaultTrack: overrides.defaultTrack ?? "standard",
94
96
  languageRulePacks: overrides.languageRulePacks ?? []
@@ -97,8 +99,8 @@ export function createProfileConfig(profile, overrides = {}) {
97
99
  return {
98
100
  ...base,
99
101
  harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
100
- autoAdvance: false,
101
102
  promptGuardMode: "strict",
103
+ tddEnforcement: "strict",
102
104
  gitHookGuards: true,
103
105
  defaultTrack: overrides.defaultTrack ?? "standard",
104
106
  languageRulePacks: overrides.languageRulePacks ?? [...LANGUAGE_RULE_PACKS]
@@ -140,8 +142,6 @@ export async function readConfig(projectRoot) {
140
142
  const harnesses = hasHarnessesField
141
143
  ? [...new Set(validatedHarnesses)]
142
144
  : DEFAULT_HARNESSES;
143
- const autoAdvanceRaw = parsed.autoAdvance;
144
- const autoAdvance = typeof autoAdvanceRaw === "boolean" ? autoAdvanceRaw : false;
145
145
  const promptGuardModeRaw = parsed.promptGuardMode;
146
146
  if (Object.prototype.hasOwnProperty.call(parsed, "promptGuardMode") &&
147
147
  promptGuardModeRaw !== "advisory" &&
@@ -149,6 +149,16 @@ export async function readConfig(projectRoot) {
149
149
  throw configValidationError(fullPath, `"promptGuardMode" must be "advisory" or "strict"`);
150
150
  }
151
151
  const promptGuardMode = promptGuardModeRaw === "strict" ? "strict" : "advisory";
152
+ const tddEnforcementRaw = parsed.tddEnforcement;
153
+ if (Object.prototype.hasOwnProperty.call(parsed, "tddEnforcement") &&
154
+ tddEnforcementRaw !== "advisory" &&
155
+ tddEnforcementRaw !== "strict") {
156
+ throw configValidationError(fullPath, `"tddEnforcement" must be "advisory" or "strict"`);
157
+ }
158
+ const tddEnforcement = tddEnforcementRaw === "strict" ? "strict" : "advisory";
159
+ const tddTestGlobsRaw = parsed.tddTestGlobs;
160
+ const tddTestGlobs = validateStringArray(tddTestGlobsRaw, "tddTestGlobs", fullPath)
161
+ ?? ["**/*.test.*", "**/*.spec.*", "**/test/**"];
152
162
  const gitHookGuardsRaw = parsed.gitHookGuards;
153
163
  if (Object.prototype.hasOwnProperty.call(parsed, "gitHookGuards") &&
154
164
  typeof gitHookGuardsRaw !== "boolean") {
@@ -244,8 +254,9 @@ export async function readConfig(projectRoot) {
244
254
  version: parsed.version ?? CCLAW_VERSION,
245
255
  flowVersion: parsed.flowVersion ?? FLOW_VERSION,
246
256
  harnesses,
247
- autoAdvance,
248
257
  promptGuardMode,
258
+ tddEnforcement,
259
+ tddTestGlobs,
249
260
  gitHookGuards,
250
261
  defaultTrack,
251
262
  languageRulePacks,
@@ -4,9 +4,9 @@ export declare const RUNTIME_ROOT = ".cclaw";
4
4
  export declare const CCLAW_VERSION = "0.1.1";
5
5
  export declare const FLOW_VERSION = "1.0.0";
6
6
  export declare const DEFAULT_HARNESSES: HarnessId[];
7
- export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills"];
7
+ export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/features", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills"];
8
8
  export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".codex/commands/cc-*.md", ".codex/commands/cc.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
9
9
  export declare const COMMAND_FILE_ORDER: FlowStage[];
10
- export declare const UTILITY_COMMANDS: readonly ["learn", "next", "status"];
10
+ export declare const UTILITY_COMMANDS: readonly ["learn", "next", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "archive", "rewind", "rewind-ack"];
11
11
  export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
12
12
  export type UtilityCommand = (typeof UTILITY_COMMANDS)[number];
package/dist/constants.js CHANGED
@@ -15,6 +15,7 @@ export const REQUIRED_DIRS = [
15
15
  `${RUNTIME_ROOT}/contexts`,
16
16
  `${RUNTIME_ROOT}/templates`,
17
17
  `${RUNTIME_ROOT}/artifacts`,
18
+ `${RUNTIME_ROOT}/features`,
18
19
  `${RUNTIME_ROOT}/state`,
19
20
  `${RUNTIME_ROOT}/runs`,
20
21
  `${RUNTIME_ROOT}/rules`,
@@ -50,7 +51,21 @@ export const COMMAND_FILE_ORDER = [
50
51
  "review",
51
52
  "ship"
52
53
  ];
53
- export const UTILITY_COMMANDS = ["learn", "next", "status"];
54
+ export const UTILITY_COMMANDS = [
55
+ "learn",
56
+ "next",
57
+ "view",
58
+ "status",
59
+ "tree",
60
+ "diff",
61
+ "ops",
62
+ "feature",
63
+ "tdd-log",
64
+ "retro",
65
+ "archive",
66
+ "rewind",
67
+ "rewind-ack"
68
+ ];
54
69
  export const SUBAGENT_SKILL_FOLDERS = [
55
70
  "subagent-dev",
56
71
  "parallel-dispatch"
@@ -0,0 +1,2 @@
1
+ export declare function archiveCommandContract(): string;
2
+ export declare function archiveCommandSkillMarkdown(): string;
@@ -0,0 +1,98 @@
1
+ import { RUNTIME_ROOT } from "../constants.js";
2
+ const ARCHIVE_SKILL_FOLDER = "flow-archive";
3
+ const ARCHIVE_SKILL_NAME = "flow-archive";
4
+ function flowStatePath() {
5
+ return `${RUNTIME_ROOT}/state/flow-state.json`;
6
+ }
7
+ function runsPath() {
8
+ return `${RUNTIME_ROOT}/runs`;
9
+ }
10
+ function activeArtifactsPath() {
11
+ return `${RUNTIME_ROOT}/artifacts`;
12
+ }
13
+ export function archiveCommandContract() {
14
+ return `# /cc-archive
15
+
16
+ ## Purpose
17
+
18
+ Archive the active cclaw run from inside the harness flow (agent-first finish).
19
+
20
+ This command removes the user-facing CLI gap: users can stay in \`/cc-*\` flow and
21
+ finish with \`/cc-archive\` after ship + retro are complete.
22
+
23
+ ## HARD-GATE
24
+
25
+ - Do not archive a shipped run when retro is still incomplete.
26
+ - Do not manually move files between \`${activeArtifactsPath()}\` and \`${runsPath()}\`.
27
+ - Use the archive runtime so state snapshots + manifest stay consistent.
28
+
29
+ ## Inputs
30
+
31
+ \`/cc-archive [--name=<slug>] [--skip-retro --retro-reason=<text>]\`
32
+
33
+ ## Algorithm
34
+
35
+ 1. Read \`${flowStatePath()}\`.
36
+ 2. If ship is complete and \`retro.completedAt\` is absent:
37
+ - block with explicit instruction: run \`/cc-retro\` first.
38
+ 3. Build archive command:
39
+ - base: \`npx cclaw archive\`
40
+ - optional: \`--name=<slug>\`
41
+ - optional override: \`--skip-retro --retro-reason=<text>\`
42
+ 4. Execute archive command in project root.
43
+ 5. Surface result:
44
+ - archive id/path,
45
+ - reset stage (brainstorm/spec depending on track default),
46
+ - knowledge curation hint when threshold exceeded.
47
+
48
+ ## Output format
49
+
50
+ \`\`\`
51
+ cclaw archive
52
+ status: archived
53
+ run: <archive-id>
54
+ path: .cclaw/runs/<archive-id>
55
+ next: /cc <new-idea>
56
+ \`\`\`
57
+
58
+ ## Primary skill
59
+
60
+ **${RUNTIME_ROOT}/skills/${ARCHIVE_SKILL_FOLDER}/SKILL.md**
61
+ `;
62
+ }
63
+ export function archiveCommandSkillMarkdown() {
64
+ return `---
65
+ name: ${ARCHIVE_SKILL_NAME}
66
+ description: "Archive the active cclaw run from harness flow and reset runtime safely."
67
+ ---
68
+
69
+ # /cc-archive
70
+
71
+ ## HARD-GATE
72
+
73
+ Never simulate archive by hand-editing runtime files. Always execute the archive
74
+ runtime command so state snapshots and manifest generation stay atomic.
75
+
76
+ ## Protocol
77
+
78
+ 1. Read \`${flowStatePath()}\`:
79
+ - confirm whether ship is completed,
80
+ - check \`retro.completedAt\` for post-ship runs.
81
+ 2. If ship complete and retro incomplete -> stop and direct user to \`/cc-retro\`.
82
+ 3. Build shell command:
83
+ - \`npx cclaw archive\`
84
+ - append \`--name=<slug>\` when provided
85
+ - append \`--skip-retro --retro-reason=<text>\` only when user explicitly requests skip
86
+ 4. Run command from repo root.
87
+ 5. Relay key lines from output:
88
+ - archive destination under \`${runsPath()}\`
89
+ - flow reset confirmation
90
+ - knowledge curation recommendation
91
+
92
+ ## Validation
93
+
94
+ - \`${runsPath()}\` contains a new archive folder.
95
+ - \`${activeArtifactsPath()}\` is reset for the next run.
96
+ - \`${flowStatePath()}\` is valid JSON and points to the initial stage.
97
+ `;
98
+ }
@@ -36,7 +36,7 @@ ${schema.hardGate}
36
36
  ${hydrationLines}
37
37
  4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, created, project).
38
38
  5. Write stage output to ${writeStepPaths}.
39
- 6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled only by \`cclaw archive\`.
39
+ 6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled by \`/cc-archive\` (agent-facing wrapper over archive runtime).
40
40
 
41
41
  ## Gates
42
42
  ${gateIds}
@@ -0,0 +1,2 @@
1
+ export declare function diffCommandContract(): string;
2
+ export declare function diffCommandSkillMarkdown(): string;
@@ -0,0 +1,83 @@
1
+ import { RUNTIME_ROOT } from "../constants.js";
2
+ const DIFF_SKILL_FOLDER = "flow-diff";
3
+ const DIFF_SKILL_NAME = "flow-diff";
4
+ function flowStatePath() {
5
+ return `${RUNTIME_ROOT}/state/flow-state.json`;
6
+ }
7
+ function snapshotPath() {
8
+ return `${RUNTIME_ROOT}/state/flow-state.snapshot.json`;
9
+ }
10
+ export function diffCommandContract() {
11
+ return `# /cc-diff
12
+
13
+ ## Purpose
14
+
15
+ Show a visual before/after diff map for flow-state progression.
16
+
17
+ ## HARD-GATE
18
+
19
+ - Compare against \`${snapshotPath()}\` first; do not overwrite baseline before rendering.
20
+ - If no snapshot exists, initialize baseline and report "baseline created" explicitly.
21
+
22
+ ## Algorithm
23
+
24
+ 1. Read current state from \`${flowStatePath()}\`.
25
+ 2. Read baseline from \`${snapshotPath()}\` (if missing -> create baseline from current state and stop).
26
+ 3. Compute deltas:
27
+ - stage transition (\`from -> to\`)
28
+ - completed stage additions/removals
29
+ - skipped stage additions/removals
30
+ - stale stage additions/removals
31
+ - current-stage gate \`passed\` and \`blocked\` changes
32
+ 4. Render a compact diff map (added \`+\`, removed \`-\`, changed \`->\`).
33
+ 5. Persist current state back to \`${snapshotPath()}\` as new baseline with \`capturedAt\`.
34
+
35
+ ## Diff Map Format
36
+
37
+ \`\`\`
38
+ cclaw flow diff
39
+ stage: design -> spec
40
+ completed: +design
41
+ stale: -design
42
+ gates(spec): +spec_contract_complete -spec_open_questions_closed
43
+ blocked(spec): +spec_trace_matrix_missing
44
+ \`\`\`
45
+
46
+ ## Primary skill
47
+
48
+ **${RUNTIME_ROOT}/skills/${DIFF_SKILL_FOLDER}/SKILL.md**
49
+ `;
50
+ }
51
+ export function diffCommandSkillMarkdown() {
52
+ return `---
53
+ name: ${DIFF_SKILL_NAME}
54
+ description: "Compare current flow-state against saved snapshot and render gate/stage deltas."
55
+ ---
56
+
57
+ # /cc-diff
58
+
59
+ ## HARD-GATE
60
+
61
+ Never lose baseline visibility: render deltas before writing a new snapshot.
62
+
63
+ ## Protocol
64
+
65
+ 1. Read \`${flowStatePath()}\`.
66
+ 2. Read \`${snapshotPath()}\`.
67
+ 3. If snapshot missing:
68
+ - write baseline snapshot from current state,
69
+ - print \`flow diff baseline created\`,
70
+ - stop.
71
+ 4. Build deltas for stage, completed/skipped/stale sets, and current-stage gate arrays.
72
+ 5. Print a compact diff map with explicit \`+\`, \`-\`, and \`->\` markers.
73
+ 6. Write updated snapshot with:
74
+ - \`capturedAt\` (ISO)
75
+ - \`state\` (full current flow-state object)
76
+
77
+ ## Validation
78
+
79
+ - Diff output must be deterministic for identical states ("no changes").
80
+ - Snapshot file stays valid JSON after every run.
81
+ - Do not suppress removed values; removals are first-class evidence.
82
+ `;
83
+ }
@@ -0,0 +1,2 @@
1
+ export declare function featureCommandContract(): string;
2
+ export declare function featureCommandSkillMarkdown(): string;
@@ -0,0 +1,120 @@
1
+ import { RUNTIME_ROOT } from "../constants.js";
2
+ const FEATURE_SKILL_FOLDER = "feature-workspaces";
3
+ const FEATURE_SKILL_NAME = "feature-workspaces";
4
+ function activeFeaturePath() {
5
+ return `${RUNTIME_ROOT}/state/active-feature.json`;
6
+ }
7
+ function featuresRoot() {
8
+ return `${RUNTIME_ROOT}/features`;
9
+ }
10
+ function runtimeArtifactsPath() {
11
+ return `${RUNTIME_ROOT}/artifacts`;
12
+ }
13
+ function runtimeStatePath() {
14
+ return `${RUNTIME_ROOT}/state`;
15
+ }
16
+ export function featureCommandContract() {
17
+ return `# /cc-feature
18
+
19
+ ## Purpose
20
+
21
+ Manage multi-feature workspaces without flow-state/artifact collisions.
22
+
23
+ The active runtime remains:
24
+ - \`${runtimeArtifactsPath()}\` (active artifacts)
25
+ - \`${runtimeStatePath()}\` (active state)
26
+
27
+ Feature snapshots live under \`${featuresRoot()}/<feature-id>/\`.
28
+
29
+ ## HARD-GATE
30
+
31
+ - Never overwrite another feature snapshot silently.
32
+ - Before switching feature, snapshot the current active runtime first.
33
+ - Keep \`${activeFeaturePath()}\` as the single source of "current feature".
34
+
35
+ ## Subcommands
36
+
37
+ ### \`/cc-feature status\`
38
+ Show active feature id and snapshot location.
39
+
40
+ ### \`/cc-feature list\`
41
+ List all feature ids in \`${featuresRoot()}/\` (directory names).
42
+
43
+ ### \`/cc-feature new <feature-id>\`
44
+ Create \`${featuresRoot()}/<feature-id>/artifacts\` and \`${featuresRoot()}/<feature-id>/state\`.
45
+
46
+ Optional flag:
47
+ - \`--clone-active\`: clone current active runtime into the new feature snapshot.
48
+
49
+ ### \`/cc-feature switch <feature-id>\`
50
+ 1. Snapshot current active runtime into \`${featuresRoot()}/<active>/\`.
51
+ 2. Restore target snapshot from \`${featuresRoot()}/<feature-id>/\` into active runtime:
52
+ - \`${runtimeArtifactsPath()}\`
53
+ - \`${runtimeStatePath()}\` (preserve \`active-feature.json\`)
54
+ 3. Update \`${activeFeaturePath()}\` with \`activeFeature=<feature-id>\`.
55
+
56
+ If the target snapshot is empty, initialize runtime as a fresh flow.
57
+
58
+ ## Output
59
+
60
+ Always print:
61
+ - active feature before
62
+ - active feature after
63
+ - whether snapshot/restore changed files
64
+
65
+ ## Primary skill
66
+
67
+ **${RUNTIME_ROOT}/skills/${FEATURE_SKILL_FOLDER}/SKILL.md**
68
+ `;
69
+ }
70
+ export function featureCommandSkillMarkdown() {
71
+ return `---
72
+ name: ${FEATURE_SKILL_NAME}
73
+ description: "Manage cclaw multi-feature workspaces (status/list/new/switch) while preserving active flow runtime."
74
+ ---
75
+
76
+ # /cc-feature — Feature Workspace Manager
77
+
78
+ ## HARD-GATE
79
+
80
+ Do not switch feature by editing only \`active-feature.json\`. A valid switch must snapshot current runtime and restore target runtime.
81
+
82
+ ## Paths
83
+
84
+ - Active pointer: \`${activeFeaturePath()}\`
85
+ - Feature snapshots: \`${featuresRoot()}/<feature-id>/\`
86
+ - Active runtime artifacts: \`${runtimeArtifactsPath()}\`
87
+ - Active runtime state: \`${runtimeStatePath()}\`
88
+
89
+ ## Protocol
90
+
91
+ ### status
92
+ 1. Read \`${activeFeaturePath()}\`.
93
+ 2. Print active feature id and its snapshot folder.
94
+
95
+ ### list
96
+ 1. Enumerate directories in \`${featuresRoot()}/\`.
97
+ 2. Mark the active one.
98
+
99
+ ### new <feature-id> [--clone-active]
100
+ 1. Validate \`feature-id\` (lowercase slug, letters/numbers/dashes).
101
+ 2. Create snapshot dirs:
102
+ - \`${featuresRoot()}/<feature-id>/artifacts\`
103
+ - \`${featuresRoot()}/<feature-id>/state\`
104
+ 3. If \`--clone-active\`: copy active runtime artifacts/state into the new snapshot.
105
+ 4. Do not change active feature unless the user explicitly requests switch.
106
+
107
+ ### switch <feature-id>
108
+ 1. Read current active feature id.
109
+ 2. Snapshot current runtime into current feature snapshot.
110
+ 3. Restore target snapshot into active runtime.
111
+ 4. Update \`${activeFeaturePath()}\`.
112
+ 5. Report stage/run after restore (\`flow-state.json\`).
113
+
114
+ ## Safety checks
115
+
116
+ - If target feature does not exist: block and suggest \`/cc-feature new <id>\`.
117
+ - If snapshot copy fails: abort switch, keep current active feature unchanged.
118
+ - Preserve global pointer file \`active-feature.json\` when restoring state.
119
+ `;
120
+ }
@@ -65,7 +65,18 @@ All harnesses receive the same utility commands:
65
65
 
66
66
  - \`/cc\` - flow entry and resume
67
67
  - \`/cc-next\` - stage progression
68
+ - \`/cc-view\` - read-only router for status/tree/diff
68
69
  - \`/cc-learn\` - knowledge capture/lookup
70
+ - \`/cc-status\` - read-only visual flow snapshot
71
+ - \`/cc-tree\` - deep flow tree (stages, artifacts, stale markers)
72
+ - \`/cc-diff\` - before/after flow-state diff map
73
+ - \`/cc-ops\` - operations router for feature/tdd-log/retro/archive/rewind
74
+ - \`/cc-feature\` - multi-feature workspace management
75
+ - \`/cc-tdd-log\` - explicit RED/GREEN/REFACTOR evidence log
76
+ - \`/cc-retro\` - mandatory retrospective gate before archive
77
+ - \`/cc-archive\` - archive active run from harness flow
78
+ - \`/cc-rewind\` - rewind flow and invalidate downstream stages
79
+ - \`/cc-rewind-ack\` - clear stale stage markers after redo
69
80
 
70
81
  Stage order remains canonical:
71
82
  \`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship\`
@@ -45,6 +45,7 @@ set -euo pipefail
45
45
  ${DETECT_ROOT}
46
46
 
47
47
  STATE_FILE="$ROOT/${RUNTIME_ROOT}/state/flow-state.json"
48
+ ACTIVE_FEATURE_FILE="$ROOT/${RUNTIME_ROOT}/state/active-feature.json"
48
49
  CHECKPOINT_FILE="$ROOT/${RUNTIME_ROOT}/state/checkpoint.json"
49
50
  ACTIVITY_FILE="$ROOT/${RUNTIME_ROOT}/state/stage-activity.jsonl"
50
51
  SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
@@ -59,13 +60,16 @@ META_SKILL="$ROOT/${RUNTIME_ROOT}/skills/${META_SKILL_NAME}/SKILL.md"
59
60
  STAGE="none"
60
61
  COMPLETED="0"
61
62
  ACTIVE_RUN="none"
63
+ ACTIVE_FEATURE="default"
62
64
  ACTIVE_CONTEXT_MODE="default"
65
+ STALE_STAGES=""
63
66
  CONTEXT_MODE_NOTE=""
64
67
  if [ -f "$STATE_FILE" ]; then
65
68
  if command -v jq >/dev/null 2>&1; then
66
69
  STAGE=$(jq -r '.currentStage // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
67
70
  COMPLETED=$(jq -r '(.completedStages | length) // 0' "$STATE_FILE" 2>/dev/null || echo "0")
68
71
  ACTIVE_RUN=$(jq -r '.activeRunId // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
72
+ STALE_STAGES=$(jq -r '(.staleStages // {} | keys | join(", "))' "$STATE_FILE" 2>/dev/null || echo "")
69
73
  else
70
74
  if command -v python3 >/dev/null 2>&1; then
71
75
  STAGE=$(python3 - "$STATE_FILE" <<'PY'
@@ -115,6 +119,22 @@ except Exception:
115
119
  pass
116
120
  print(run)
117
121
  PY
122
+ )
123
+ STALE_STAGES=$(python3 - "$STATE_FILE" <<'PY'
124
+ import json
125
+ import sys
126
+ value = ""
127
+ try:
128
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
129
+ data = json.load(fh)
130
+ stale = data.get("staleStages", {})
131
+ if isinstance(stale, dict):
132
+ keys = [k for k, v in stale.items() if isinstance(v, dict)]
133
+ value = ", ".join(keys)
134
+ except Exception:
135
+ pass
136
+ print(value)
137
+ PY
118
138
  )
119
139
  else
120
140
  STAGE=$(grep -o '"currentStage"[[:space:]]*:[[:space:]]*"[^"]*"' "$STATE_FILE" 2>/dev/null | head -1 | sed 's/.*"\\([^"]*\\)"$/\\1/' || echo "none")
@@ -129,6 +149,28 @@ PY
129
149
  fi
130
150
  fi
131
151
 
152
+ if [ -f "$ACTIVE_FEATURE_FILE" ]; then
153
+ if command -v jq >/dev/null 2>&1; then
154
+ ACTIVE_FEATURE=$(jq -r '.activeFeature // "default"' "$ACTIVE_FEATURE_FILE" 2>/dev/null || echo "default")
155
+ elif command -v python3 >/dev/null 2>&1; then
156
+ ACTIVE_FEATURE=$(python3 - "$ACTIVE_FEATURE_FILE" <<'PY'
157
+ import json
158
+ import sys
159
+ feature = "default"
160
+ try:
161
+ with open(sys.argv[1], "r", encoding="utf-8") as fh:
162
+ data = json.load(fh)
163
+ value = data.get("activeFeature")
164
+ if isinstance(value, str) and value:
165
+ feature = value
166
+ except Exception:
167
+ pass
168
+ print(feature)
169
+ PY
170
+ )
171
+ fi
172
+ fi
173
+
132
174
  if [ -f "$CONTEXT_MODE_FILE" ]; then
133
175
  if command -v jq >/dev/null 2>&1; then
134
176
  ACTIVE_CONTEXT_MODE=$(jq -r '.activeMode // "default"' "$CONTEXT_MODE_FILE" 2>/dev/null || echo "default")
@@ -415,7 +457,7 @@ if [ -n "$ROUTING_MISSING" ]; then
415
457
  fi
416
458
 
417
459
  # --- Build context message ---
418
- CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN). Active artifacts: ${RUNTIME_ROOT}/artifacts/. Learnings: $LEARNINGS_COUNT entries."
460
+ CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN, feature=$ACTIVE_FEATURE). Active artifacts: ${RUNTIME_ROOT}/artifacts/. Feature snapshots: ${RUNTIME_ROOT}/features/$ACTIVE_FEATURE/. Learnings: $LEARNINGS_COUNT entries."
419
461
  if [ -n "$VERSION_NOTE" ]; then
420
462
  CTX="$CTX
421
463
  $VERSION_NOTE"
@@ -452,6 +494,10 @@ if [ -n "$STAGE_SUGGESTION" ]; then
452
494
  $STAGE_SUGGESTION
453
495
  To disable suggestions persistently set ${RUNTIME_ROOT}/state/suggestion-memory.json -> enabled=false."
454
496
  fi
497
+ if [ -n "$STALE_STAGES" ]; then
498
+ CTX="$CTX
499
+ Stale stages pending acknowledgement: $STALE_STAGES (use /cc-rewind-ack <stage> after redo)."
500
+ fi
455
501
  if [ -n "$KNOWLEDGE_DIGEST" ]; then
456
502
  CTX="$CTX
457
503
  Knowledge digest (top relevant entries):
@@ -696,7 +742,7 @@ if [ "$CHECKPOINT_WRITTEN" -eq 0 ]; then
696
742
  CHECKPOINT_NOTE="Checkpoint update failed. Review ${RUNTIME_ROOT}/state/checkpoint.json manually."
697
743
  fi
698
744
 
699
- RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until cclaw archive."
745
+ RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until /cc-archive (or cclaw archive runtime)."
700
746
 
701
747
  # --- Escape for JSON ---
702
748
  ${ESCAPE_FN}
@@ -6,5 +6,3 @@ export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "actio
6
6
  export declare function learnSkillMarkdown(): string;
7
7
  export declare function learnCommandContract(): string;
8
8
  export declare function selfImprovementBlock(stageName: string): string;
9
- export declare function learningsSearchPreamble(stage: string): string;
10
- export declare function learningsAgentsMdBlock(): string;