cclaw-cli 0.12.0 → 0.13.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 (39) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +14 -1
  3. package/dist/config.js +19 -0
  4. package/dist/constants.d.ts +2 -2
  5. package/dist/constants.js +13 -1
  6. package/dist/content/diff-command.d.ts +2 -0
  7. package/dist/content/diff-command.js +83 -0
  8. package/dist/content/feature-command.d.ts +2 -0
  9. package/dist/content/feature-command.js +120 -0
  10. package/dist/content/harnesses-doc.js +8 -0
  11. package/dist/content/hooks.js +47 -1
  12. package/dist/content/meta-skill.js +3 -2
  13. package/dist/content/next-command.js +8 -6
  14. package/dist/content/observe.d.ts +5 -1
  15. package/dist/content/observe.js +134 -2
  16. package/dist/content/retro-command.d.ts +2 -0
  17. package/dist/content/retro-command.js +77 -0
  18. package/dist/content/rewind-command.d.ts +3 -0
  19. package/dist/content/rewind-command.js +120 -0
  20. package/dist/content/status-command.js +43 -35
  21. package/dist/content/tdd-log-command.d.ts +2 -0
  22. package/dist/content/tdd-log-command.js +75 -0
  23. package/dist/content/templates.js +35 -5
  24. package/dist/content/tree-command.d.ts +2 -0
  25. package/dist/content/tree-command.js +91 -0
  26. package/dist/doctor.js +149 -3
  27. package/dist/feature-system.d.ts +18 -0
  28. package/dist/feature-system.js +247 -0
  29. package/dist/flow-state.d.ts +25 -0
  30. package/dist/flow-state.js +8 -1
  31. package/dist/harness-adapters.js +74 -4
  32. package/dist/install.js +35 -2
  33. package/dist/policy.js +22 -0
  34. package/dist/runs.d.ts +33 -1
  35. package/dist/runs.js +365 -6
  36. package/dist/tdd-cycle.d.ts +22 -0
  37. package/dist/tdd-cycle.js +82 -0
  38. package/dist/types.d.ts +4 -0
  39. 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
 
@@ -368,6 +370,14 @@ function parseArgs(argv) {
368
370
  }
369
371
  if (flag.startsWith("--name=")) {
370
372
  parsed.archiveName = flag.replace("--name=", "").trim();
373
+ continue;
374
+ }
375
+ if (flag === "--skip-retro") {
376
+ parsed.archiveSkipRetro = true;
377
+ continue;
378
+ }
379
+ if (flag.startsWith("--retro-reason=")) {
380
+ parsed.archiveSkipRetroReason = flag.replace("--retro-reason=", "").trim();
371
381
  }
372
382
  }
373
383
  return parsed;
@@ -466,7 +476,10 @@ async function runCommand(parsed, ctx) {
466
476
  return 0;
467
477
  }
468
478
  if (command === "archive") {
469
- const archived = await archiveRun(ctx.cwd, parsed.archiveName);
479
+ const archived = await archiveRun(ctx.cwd, parsed.archiveName, {
480
+ skipRetro: parsed.archiveSkipRetro === true,
481
+ skipRetroReason: parsed.archiveSkipRetroReason
482
+ });
470
483
  const snapshotSummary = archived.snapshottedStateFiles.length > 0
471
484
  ? ` Snapshotted ${archived.snapshottedStateFiles.length} state file(s) under ${archived.archivePath}/state and wrote archive-manifest.json.`
472
485
  : "";
package/dist/config.js CHANGED
@@ -17,6 +17,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
17
17
  "harnesses",
18
18
  "autoAdvance",
19
19
  "promptGuardMode",
20
+ "tddEnforcement",
21
+ "tddTestGlobs",
20
22
  "gitHookGuards",
21
23
  "defaultTrack",
22
24
  "languageRulePacks",
@@ -60,6 +62,8 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
60
62
  harnesses,
61
63
  autoAdvance: false,
62
64
  promptGuardMode: "advisory",
65
+ tddEnforcement: "advisory",
66
+ tddTestGlobs: ["**/*.test.*", "**/*.spec.*", "**/test/**"],
63
67
  gitHookGuards: false,
64
68
  defaultTrack,
65
69
  languageRulePacks: []
@@ -79,6 +83,7 @@ export function createProfileConfig(profile, overrides = {}) {
79
83
  harnesses: overrides.harnesses ?? ["claude"],
80
84
  autoAdvance: false,
81
85
  promptGuardMode: "advisory",
86
+ tddEnforcement: "advisory",
82
87
  gitHookGuards: false,
83
88
  defaultTrack: overrides.defaultTrack ?? "medium",
84
89
  languageRulePacks: overrides.languageRulePacks ?? []
@@ -89,6 +94,7 @@ export function createProfileConfig(profile, overrides = {}) {
89
94
  harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
90
95
  autoAdvance: false,
91
96
  promptGuardMode: "advisory",
97
+ tddEnforcement: "advisory",
92
98
  gitHookGuards: false,
93
99
  defaultTrack: overrides.defaultTrack ?? "standard",
94
100
  languageRulePacks: overrides.languageRulePacks ?? []
@@ -99,6 +105,7 @@ export function createProfileConfig(profile, overrides = {}) {
99
105
  harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
100
106
  autoAdvance: false,
101
107
  promptGuardMode: "strict",
108
+ tddEnforcement: "strict",
102
109
  gitHookGuards: true,
103
110
  defaultTrack: overrides.defaultTrack ?? "standard",
104
111
  languageRulePacks: overrides.languageRulePacks ?? [...LANGUAGE_RULE_PACKS]
@@ -149,6 +156,16 @@ export async function readConfig(projectRoot) {
149
156
  throw configValidationError(fullPath, `"promptGuardMode" must be "advisory" or "strict"`);
150
157
  }
151
158
  const promptGuardMode = promptGuardModeRaw === "strict" ? "strict" : "advisory";
159
+ const tddEnforcementRaw = parsed.tddEnforcement;
160
+ if (Object.prototype.hasOwnProperty.call(parsed, "tddEnforcement") &&
161
+ tddEnforcementRaw !== "advisory" &&
162
+ tddEnforcementRaw !== "strict") {
163
+ throw configValidationError(fullPath, `"tddEnforcement" must be "advisory" or "strict"`);
164
+ }
165
+ const tddEnforcement = tddEnforcementRaw === "strict" ? "strict" : "advisory";
166
+ const tddTestGlobsRaw = parsed.tddTestGlobs;
167
+ const tddTestGlobs = validateStringArray(tddTestGlobsRaw, "tddTestGlobs", fullPath)
168
+ ?? ["**/*.test.*", "**/*.spec.*", "**/test/**"];
152
169
  const gitHookGuardsRaw = parsed.gitHookGuards;
153
170
  if (Object.prototype.hasOwnProperty.call(parsed, "gitHookGuards") &&
154
171
  typeof gitHookGuardsRaw !== "boolean") {
@@ -246,6 +263,8 @@ export async function readConfig(projectRoot) {
246
263
  harnesses,
247
264
  autoAdvance,
248
265
  promptGuardMode,
266
+ tddEnforcement,
267
+ tddTestGlobs,
249
268
  gitHookGuards,
250
269
  defaultTrack,
251
270
  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", "status", "tree", "diff", "feature", "tdd-log", "retro", "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,18 @@ 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
+ "status",
58
+ "tree",
59
+ "diff",
60
+ "feature",
61
+ "tdd-log",
62
+ "retro",
63
+ "rewind",
64
+ "rewind-ack"
65
+ ];
54
66
  export const SUBAGENT_SKILL_FOLDERS = [
55
67
  "subagent-dev",
56
68
  "parallel-dispatch"
@@ -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
+ }
@@ -66,6 +66,14 @@ All harnesses receive the same utility commands:
66
66
  - \`/cc\` - flow entry and resume
67
67
  - \`/cc-next\` - stage progression
68
68
  - \`/cc-learn\` - knowledge capture/lookup
69
+ - \`/cc-status\` - read-only visual flow snapshot
70
+ - \`/cc-tree\` - deep flow tree (stages, artifacts, stale markers)
71
+ - \`/cc-diff\` - before/after flow-state diff map
72
+ - \`/cc-feature\` - multi-feature workspace management
73
+ - \`/cc-tdd-log\` - explicit RED/GREEN/REFACTOR evidence log
74
+ - \`/cc-retro\` - mandatory retrospective gate before archive
75
+ - \`/cc-rewind\` - rewind flow and invalidate downstream stages
76
+ - \`/cc-rewind-ack\` - clear stale stage markers after redo
69
77
 
70
78
  Stage order remains canonical:
71
79
  \`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):
@@ -3,7 +3,7 @@ export const META_SKILL_NAME = "using-cclaw";
3
3
  export function usingCclawSkillMarkdown() {
4
4
  return `---
5
5
  name: using-cclaw
6
- description: "Routing brain for cclaw. Decide whether to start/resume a stage, answer directly, or use /cc-learn."
6
+ description: "Routing brain for cclaw. Decide whether to start/resume a stage, answer directly, or use utility commands like /cc-learn, /cc-status, /cc-tree, and /cc-diff."
7
7
  ---
8
8
 
9
9
  # Using Cclaw
@@ -26,7 +26,8 @@ Task arrives
26
26
  ├─ Pure question / non-software ask? -> answer directly (no stage)
27
27
  ├─ New software work? -> /cc <idea>
28
28
  ├─ Resume existing flow? -> /cc or /cc-next
29
- └─ Knowledge operation? -> /cc-learn
29
+ ├─ Knowledge operation? -> /cc-learn
30
+ └─ Workspace operation? -> /cc-status, /cc-tree, /cc-diff, /cc-feature, /cc-tdd-log, /cc-retro, /cc-rewind
30
31
  \`\`\`
31
32
 
32
33
  ## Task classification
@@ -39,11 +39,12 @@ This is the only progression command the user needs to drive the entire flow. St
39
39
 
40
40
  1. Read **\`${flowPath}\`**. If missing → **BLOCKED** (state missing).
41
41
  2. Parse JSON. Capture \`currentStage\` and \`stageGateCatalog[currentStage]\`.
42
- 3. Let \`G\` = \`requiredGates\` for **\`currentStage\`** from the stage schema.
43
- 4. Let \`catalog\` = \`stageGateCatalog[currentStage]\` from flow state.
44
- 5. **Satisfied** for gate id \`g\`: \`g\` in \`catalog.passed\` and \`g\` not in \`catalog.blocked\`.
45
- 6. Let \`M\` = \`mandatoryDelegations\` for \`currentStage\`.
46
- 7. If \`M\` is non-empty, inspect **\`${delegationPath}\`**. Treat as satisfied only if the agent is **completed** or **waived**.
42
+ 3. If \`staleStages[currentStage]\` exists, do not advance automatically. Re-run the stage artifact work, then clear the marker with \`/cc-rewind-ack <currentStage>\`.
43
+ 4. Let \`G\` = \`requiredGates\` for **\`currentStage\`** from the stage schema.
44
+ 5. Let \`catalog\` = \`stageGateCatalog[currentStage]\` from flow state.
45
+ 6. **Satisfied** for gate id \`g\`: \`g\` in \`catalog.passed\` and \`g\` not in \`catalog.blocked\`.
46
+ 7. Let \`M\` = \`mandatoryDelegations\` for \`currentStage\`.
47
+ 8. If \`M\` is non-empty, inspect **\`${delegationPath}\`**. Treat as satisfied only if the agent is **completed** or **waived**.
47
48
 
48
49
  ### Path A: Current stage is NOT complete (any gate unmet or delegation missing)
49
50
 
@@ -120,7 +121,8 @@ Do **not** mark gates satisfied from memory alone. Cite **artifact evidence** (p
120
121
 
121
122
  1. Open **\`${flowPath}\`**.
122
123
  2. Record \`currentStage\` and \`stageGateCatalog[currentStage]\`.
123
- 3. If the file is missing or invalid JSON **BLOCKED** (report and stop).
124
+ 3. If \`staleStages[currentStage]\` exists, re-run the stage and clear marker via \`/cc-rewind-ack <currentStage>\` before advancing.
125
+ 4. If the file is missing or invalid JSON → **BLOCKED** (report and stop).
124
126
 
125
127
  ### Step 2: Evaluate gates
126
128
 
@@ -9,7 +9,11 @@ export interface PromptGuardOptions {
9
9
  strictMode?: boolean;
10
10
  }
11
11
  export declare function promptGuardScript(options?: PromptGuardOptions): string;
12
- export declare function workflowGuardScript(): string;
12
+ export interface WorkflowGuardOptions {
13
+ tddEnforcementMode?: "advisory" | "strict";
14
+ tddTestGlobs?: string[];
15
+ }
16
+ export declare function workflowGuardScript(options?: WorkflowGuardOptions): string;
13
17
  export declare function observeScript(): string;
14
18
  export declare function contextMonitorScript(): string;
15
19
  export declare function summarizeObservationsRuntimeModule(): string;