cclaw-cli 0.47.0 → 0.48.1

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 (48) hide show
  1. package/README.md +3 -1
  2. package/dist/artifact-linter.d.ts +9 -2
  3. package/dist/artifact-linter.js +45 -2
  4. package/dist/config.d.ts +6 -6
  5. package/dist/config.js +22 -0
  6. package/dist/constants.d.ts +10 -1
  7. package/dist/constants.js +19 -10
  8. package/dist/content/contracts.d.ts +1 -1
  9. package/dist/content/contracts.js +1 -1
  10. package/dist/content/core-agents.d.ts +53 -1
  11. package/dist/content/core-agents.js +6 -0
  12. package/dist/content/{harnesses-doc.js → harness-doc.js} +32 -1
  13. package/dist/content/harness-playbooks.js +4 -4
  14. package/dist/content/ideate-command.js +19 -19
  15. package/dist/content/observe.js +22 -1
  16. package/dist/content/opencode-plugin.js +5 -1
  17. package/dist/content/skills.js +2 -2
  18. package/dist/content/stage-schema.js +36 -8
  19. package/dist/content/stages/design.js +2 -2
  20. package/dist/content/stages/review.js +1 -1
  21. package/dist/content/stages/ship.js +2 -0
  22. package/dist/content/stages/tdd.js +8 -4
  23. package/dist/content/templates.js +15 -13
  24. package/dist/content/utility-skills.d.ts +7 -1
  25. package/dist/content/utility-skills.js +5 -0
  26. package/dist/delegation.d.ts +10 -0
  27. package/dist/delegation.js +111 -33
  28. package/dist/doctor.js +80 -12
  29. package/dist/flow-state.d.ts +9 -1
  30. package/dist/flow-state.js +26 -9
  31. package/dist/fs-utils.d.ts +9 -0
  32. package/dist/fs-utils.js +35 -1
  33. package/dist/gate-evidence.js +21 -2
  34. package/dist/gitignore.js +6 -3
  35. package/dist/harness-adapters.d.ts +2 -2
  36. package/dist/harness-adapters.js +13 -3
  37. package/dist/install.js +68 -10
  38. package/dist/internal/detect-public-api-changes.d.ts +5 -0
  39. package/dist/internal/detect-public-api-changes.js +45 -0
  40. package/dist/knowledge-store.js +2 -2
  41. package/dist/policy.js +3 -2
  42. package/dist/retro-gate.js +41 -15
  43. package/dist/run-archive.js +63 -33
  44. package/dist/run-persistence.js +12 -4
  45. package/dist/tdd-cycle.js +6 -1
  46. package/dist/types.d.ts +6 -1
  47. package/package.json +4 -1
  48. /package/dist/content/{harnesses-doc.d.ts → harness-doc.d.ts} +0 -0
package/README.md CHANGED
@@ -174,7 +174,7 @@ inside `/cc-ops` subcommands.
174
174
  |---|---|
175
175
  | **`/cc <idea>`** | Classify the task, discover origin docs (`docs/prd/**`, ADRs, root `PRD.md`, …), sniff the stack, recommend a track, then start the first stage of that track. `/cc` without arguments resumes the current flow. |
176
176
  | **`/cc-next`** | The one progression primitive. Reads `flow-state.json`, checks gates + mandatory subagent delegations, and either resumes the current stage or advances to the next. `/cc-next` in a new session is how you **resume**. |
177
- | **`/cc-ideate`** | Repository improvement discovery. Scans for TODOs, flaky tests, oversized modules, docs drift, and recurring knowledge-store lessons, **persists the ranked backlog** to `.cclaw/artifacts/ideation-<date>-<slug>.md`, and ends with a concrete handoff: launch `/cc` on the selected candidate in the same session, save-and-close, or discard. Resume check on next run reuses any ideation artifact younger than 30 days. Never mutates `flow-state.json`. |
177
+ | **`/cc-ideate`** | Repository improvement ideate mode. Scans for TODOs, flaky tests, oversized modules, docs drift, and recurring knowledge-store lessons, **persists the ranked backlog** to `.cclaw/artifacts/ideate-<date>-<slug>.md`, and ends with a concrete handoff: launch `/cc` on the selected candidate in the same session, save-and-close, or discard. Resume check on next run reuses any ideate artifact younger than 30 days. Never mutates `flow-state.json`. |
178
178
  | **`/cc-view`** | Read-only flow visibility. `/cc-view status` (default) shows stage progress, mandatory delegations with their fulfillment mode (isolated / generic-dispatch / role-switch), the ship closeout substate (retro → compound → archive), and the active harness parity row. `/cc-view tree` renders the same picture as a tree with a closeout sub-branch under ship and a per-harness playbook summary. `/cc-view diff` shows stage/gate/closeout/delegation deltas since the last run. Never mutates state (except diff's snapshot baseline). |
179
179
 
180
180
  > Power-user surface: `/cc-ops` is an operational router for manual
@@ -458,6 +458,8 @@ CCLAW_EVAL_MODEL=glm-5.1 # default
458
458
 
459
459
  Full details, corpus format, and the eval contract live in
460
460
  [`docs/evals.md`](./docs/evals.md).
461
+ Mutation-testing setup lives in `stryker.config.mjs` and
462
+ `.github/workflows/mutation.yml` (manual + weekly run).
461
463
 
462
464
  ---
463
465
 
@@ -1,4 +1,4 @@
1
- import { type FlowStage } from "./types.js";
1
+ import { type FlowStage, type FlowTrack } from "./types.js";
2
2
  export interface LintFinding {
3
3
  section: string;
4
4
  required: boolean;
@@ -46,7 +46,7 @@ export interface LearningsParseResult {
46
46
  details: string;
47
47
  }
48
48
  export declare function parseLearningsSection(sectionBody: string): LearningsParseResult;
49
- export declare function lintArtifact(projectRoot: string, stage: FlowStage): Promise<LintResult>;
49
+ export declare function lintArtifact(projectRoot: string, stage: FlowStage, track?: FlowTrack): Promise<LintResult>;
50
50
  export declare function validateReviewArmy(projectRoot: string): Promise<{
51
51
  valid: boolean;
52
52
  errors: string[];
@@ -58,9 +58,16 @@ export interface ReviewVerdictConsistencyResult {
58
58
  openCriticalCount: number;
59
59
  shipBlockerCount: number;
60
60
  }
61
+ export interface ReviewSecurityNoChangeAttestationResult {
62
+ ok: boolean;
63
+ errors: string[];
64
+ hasSecurityFinding: boolean;
65
+ hasNoChangeAttestation: boolean;
66
+ }
61
67
  /**
62
68
  * Ensure the narrative verdict in 07-review.md is consistent with the
63
69
  * structured review-army reconciliation. A review cannot declare
64
70
  * APPROVED while open Critical findings or shipBlockers remain.
65
71
  */
66
72
  export declare function checkReviewVerdictConsistency(projectRoot: string): Promise<ReviewVerdictConsistencyResult>;
73
+ export declare function checkReviewSecurityNoChangeAttestation(projectRoot: string): Promise<ReviewSecurityNoChangeAttestationResult>;
@@ -795,8 +795,8 @@ function validateSectionBody(sectionBody, rule, sectionName) {
795
795
  details: "Section heading and content satisfy lint heuristics."
796
796
  };
797
797
  }
798
- export async function lintArtifact(projectRoot, stage) {
799
- const schema = stageSchema(stage);
798
+ export async function lintArtifact(projectRoot, stage, track = "standard") {
799
+ const schema = stageSchema(stage, track);
800
800
  const { absPath: absFile, relPath: relFile } = await resolveArtifactPath(projectRoot, schema.artifactFile);
801
801
  const findings = [];
802
802
  if (!(await exists(absFile))) {
@@ -1324,3 +1324,46 @@ export async function checkReviewVerdictConsistency(projectRoot) {
1324
1324
  shipBlockerCount
1325
1325
  };
1326
1326
  }
1327
+ export async function checkReviewSecurityNoChangeAttestation(projectRoot) {
1328
+ const reviewMdPath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "07-review.md");
1329
+ if (!(await exists(reviewMdPath))) {
1330
+ return {
1331
+ ok: true,
1332
+ errors: [],
1333
+ hasSecurityFinding: false,
1334
+ hasNoChangeAttestation: false
1335
+ };
1336
+ }
1337
+ const errors = [];
1338
+ const raw = await fs.readFile(reviewMdPath, "utf8");
1339
+ const sections = extractH2Sections(raw);
1340
+ const securityBody = sectionBodyByName(sections, "Layer 2 Security")
1341
+ ?? sectionBodyByName(sections, "Layer 2b: Security")
1342
+ ?? sectionBodyByName(sections, "Layer 2 Findings");
1343
+ if (!securityBody) {
1344
+ errors.push('07-review.md is missing a Layer 2 security section.');
1345
+ return {
1346
+ ok: false,
1347
+ errors,
1348
+ hasSecurityFinding: false,
1349
+ hasNoChangeAttestation: false
1350
+ };
1351
+ }
1352
+ const securityTableRowPattern = /^\|\s*[^|\n]+\|\s*[^|\n]+\|\s*security\s*\|\s*[^|\n]+\|\s*[^|\n]+\|/imu;
1353
+ const securityBulletPattern = /^[*-]\s+.*\b(?:security|auth|injection|secret|credential|permission)\b/imu;
1354
+ const hasSecurityFinding = securityTableRowPattern.test(securityBody) || securityBulletPattern.test(securityBody);
1355
+ const attestationMatch = /NO_CHANGE_ATTESTATION\s*:\s*(.*)/iu.exec(securityBody);
1356
+ const hasNoChangeAttestation = Boolean(attestationMatch && attestationMatch[1]?.trim().length > 0);
1357
+ if (attestationMatch && attestationMatch[1]?.trim().length === 0) {
1358
+ errors.push("NO_CHANGE_ATTESTATION must include a non-empty rationale.");
1359
+ }
1360
+ if (!hasSecurityFinding && !hasNoChangeAttestation) {
1361
+ errors.push("Layer 2 security evidence missing: include at least one security finding or `NO_CHANGE_ATTESTATION: <reason>`.");
1362
+ }
1363
+ return {
1364
+ ok: errors.length === 0,
1365
+ errors,
1366
+ hasSecurityFinding,
1367
+ hasNoChangeAttestation
1368
+ };
1369
+ }
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { FlowTrack, HarnessId, LanguageRulePack, VibyConfig } from "./types.js";
1
+ import type { CclawConfig, FlowTrack, HarnessId, LanguageRulePack } from "./types.js";
2
2
  export declare function configPath(projectRoot: string): string;
3
3
  /**
4
4
  * Default test-path patterns used by workflow-guard.sh to classify TDD writes.
@@ -20,7 +20,7 @@ export declare const DEFAULT_COMPOUND_RECURRENCE_THRESHOLD = 3;
20
20
  * regardless of whether the user wrote `strictness`, the legacy keys, both,
21
21
  * or neither.
22
22
  */
23
- export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrack?: FlowTrack): VibyConfig;
23
+ export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrack?: FlowTrack): CclawConfig;
24
24
  /**
25
25
  * Probe common project-root manifests to infer which language rule packs the
26
26
  * user would reasonably want. Pure-functional best-effort: any filesystem
@@ -31,9 +31,9 @@ export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrac
31
31
  * never surprise a user who intentionally cleared the list.
32
32
  */
33
33
  export declare function detectLanguageRulePacks(projectRoot: string): Promise<LanguageRulePack[]>;
34
- export declare function readConfig(projectRoot: string): Promise<VibyConfig>;
34
+ export declare function readConfig(projectRoot: string): Promise<CclawConfig>;
35
35
  /**
36
- * Fields that live on the populated runtime `VibyConfig` but are considered
36
+ * Fields that live on the populated runtime `CclawConfig` but are considered
37
37
  * "advanced" — we keep them in the in-memory object so downstream callers
38
38
  * don't have to branch, but we do **not** write them to `config.yaml` unless
39
39
  * the user set them explicitly. Keeps the default template small and honest:
@@ -43,7 +43,7 @@ type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" |
43
43
  /**
44
44
  * Options controlling the serialisation shape of `config.yaml`.
45
45
  *
46
- * - `"full"` (default): write every field on the `VibyConfig` object that
46
+ * - `"full"` (default): write every field on the `CclawConfig` object that
47
47
  * isn't `undefined`. Preserves existing shapes and keeps legacy callers
48
48
  * working without migration.
49
49
  * - `"minimal"`: write only the user-facing knobs (`MINIMAL_CONFIG_KEYS`)
@@ -60,7 +60,7 @@ export interface WriteConfigOptions {
60
60
  mode?: "full" | "minimal";
61
61
  advancedKeysPresent?: ReadonlySet<AdvancedConfigKey>;
62
62
  }
63
- export declare function writeConfig(projectRoot: string, config: VibyConfig, options?: WriteConfigOptions): Promise<void>;
63
+ export declare function writeConfig(projectRoot: string, config: CclawConfig, options?: WriteConfigOptions): Promise<void>;
64
64
  /**
65
65
  * Enumerate which advanced keys are currently set in the on-disk config.
66
66
  * Used by `cclaw upgrade` to preserve the user's existing shape — if they
package/dist/config.js CHANGED
@@ -44,6 +44,22 @@ const MINIMAL_CONFIG_KEYS = [
44
44
  ];
45
45
  const DEFAULT_SLICE_REVIEW_THRESHOLD = 5;
46
46
  const DEFAULT_SLICE_REVIEW_TRACKS = ["standard"];
47
+ const emittedConfigWarnings = new Set();
48
+ function emitConfigWarningOnce(code, message) {
49
+ const key = `${code}:${message}`;
50
+ if (emittedConfigWarnings.has(key)) {
51
+ return;
52
+ }
53
+ emittedConfigWarnings.add(key);
54
+ process.emitWarning(message, { code });
55
+ }
56
+ function sameStringArray(a, b) {
57
+ if (!a || !b)
58
+ return false;
59
+ if (a.length !== b.length)
60
+ return false;
61
+ return a.every((value, index) => value === b[index]);
62
+ }
47
63
  function configFixExample() {
48
64
  return `harnesses:
49
65
  - claude
@@ -244,6 +260,12 @@ export async function readConfig(projectRoot) {
244
260
  explicitTddTestPathPatterns = validateStringArray(tddRaw.testPathPatterns, "tdd.testPathPatterns", fullPath);
245
261
  explicitTddProductionPathPatterns = validateStringArray(tddRaw.productionPathPatterns, "tdd.productionPathPatterns", fullPath);
246
262
  }
263
+ if (tddTestGlobsRaw !== undefined &&
264
+ explicitTddTestPathPatterns !== undefined &&
265
+ !sameStringArray(tddTestGlobs, explicitTddTestPathPatterns)) {
266
+ emitConfigWarningOnce("CCLAW_CONFIG_DEPRECATED_TDD_TEST_GLOBS", `[cclaw] Both "tddTestGlobs" (deprecated) and "tdd.testPathPatterns" are set in ${fullPath}. ` +
267
+ `Using "tdd.testPathPatterns".`);
268
+ }
247
269
  const resolvedTddTestPathPatterns = [
248
270
  ...(explicitTddTestPathPatterns ?? tddTestGlobs ?? DEFAULT_TDD_TEST_PATH_PATTERNS)
249
271
  ];
@@ -15,7 +15,16 @@ export declare const EVALS_CONFIG_PATH = ".cclaw/evals/config.yaml";
15
15
  export declare const EVALS_DIRS: readonly [".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
16
16
  export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/worktrees", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills", ".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
17
17
  export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".agents/skills/cc/SKILL.md", ".agents/skills/cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
18
- export declare const COMMAND_FILE_ORDER: FlowStage[];
18
+ /**
19
+ * Canonical stage -> skill folder mapping.
20
+ *
21
+ * Intentional divergence from stage ids:
22
+ * - stage ids stay short and flow-oriented (`spec`, `tdd`, `ship`)
23
+ * - skill folders stay descriptive and user-facing for `.cclaw/skills/*`.
24
+ *
25
+ * Keep this map as the single source of truth for generated skill paths.
26
+ */
27
+ export declare const STAGE_TO_SKILL_FOLDER: Record<FlowStage, string>;
19
28
  export declare const UTILITY_COMMANDS: readonly ["learn", "next", "ideate", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "compound", "archive", "rewind"];
20
29
  export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
21
30
  export type UtilityCommand = (typeof UTILITY_COMMANDS)[number];
package/dist/constants.js CHANGED
@@ -103,16 +103,25 @@ export const REQUIRED_GITIGNORE_PATTERNS = [
103
103
  ".opencode/plugins/cclaw-plugin.mjs",
104
104
  ".cursor/rules/cclaw-workflow.mdc"
105
105
  ];
106
- export const COMMAND_FILE_ORDER = [
107
- "brainstorm",
108
- "scope",
109
- "design",
110
- "spec",
111
- "plan",
112
- "tdd",
113
- "review",
114
- "ship"
115
- ];
106
+ /**
107
+ * Canonical stage -> skill folder mapping.
108
+ *
109
+ * Intentional divergence from stage ids:
110
+ * - stage ids stay short and flow-oriented (`spec`, `tdd`, `ship`)
111
+ * - skill folders stay descriptive and user-facing for `.cclaw/skills/*`.
112
+ *
113
+ * Keep this map as the single source of truth for generated skill paths.
114
+ */
115
+ export const STAGE_TO_SKILL_FOLDER = {
116
+ brainstorm: "brainstorming",
117
+ scope: "scope-shaping",
118
+ design: "engineering-design-lock",
119
+ spec: "specification-authoring",
120
+ plan: "planning-and-task-breakdown",
121
+ tdd: "test-driven-development",
122
+ review: "two-layer-review",
123
+ ship: "shipping-and-handoff"
124
+ };
116
125
  export const UTILITY_COMMANDS = [
117
126
  "learn",
118
127
  "next",
@@ -1,2 +1,2 @@
1
1
  import type { FlowStage } from "../types.js";
2
- export declare function commandContract(stage: FlowStage): string;
2
+ export declare function stageCommandContract(stage: FlowStage): string;
@@ -1,6 +1,6 @@
1
1
  import { stageSchema } from "./stage-schema.js";
2
2
  import { stageSkillFolder } from "./skills.js";
3
- export function commandContract(stage) {
3
+ export function stageCommandContract(stage) {
4
4
  const schema = stageSchema(stage);
5
5
  const skillPath = `.cclaw/skills/${stageSkillFolder(stage)}/SKILL.md`;
6
6
  const reads = schema.crossStageTrace.readsFrom;
@@ -24,8 +24,60 @@ export interface AgentDefinition {
24
24
  }
25
25
  /**
26
26
  * Canonical specialist roster (core-5) materialized under `.cclaw/agents/`.
27
+ *
28
+ * Declared with `satisfies` so the array retains literal `name` types for
29
+ * downstream type-level consumers (e.g. `AgentName`), while still being
30
+ * checked against the `AgentDefinition` shape at compile time. Do not add
31
+ * an explicit `AgentDefinition[]` annotation here — it would widen `name`
32
+ * to `string` and break the compile-time drift guard.
33
+ */
34
+ export declare const CCLAW_AGENTS: readonly [{
35
+ readonly name: "planner";
36
+ readonly description: "MANDATORY for scope/design/plan and PROACTIVE for high-ambiguity work. MUST BE USED when sequencing, dependency mapping, or risk trade-offs are required before coding.";
37
+ readonly tools: ["Read", "Grep", "Glob", "WebSearch"];
38
+ readonly model: "deep";
39
+ readonly activation: "mandatory";
40
+ readonly relatedStages: ["brainstorm", "scope", "design", "spec", "plan"];
41
+ readonly body: string;
42
+ }, {
43
+ readonly name: "reviewer";
44
+ readonly description: "MANDATORY during review. MUST BE USED to run a two-pass audit: spec compliance first, then correctness/maintainability/performance/architecture.";
45
+ readonly tools: ["Read", "Grep", "Glob"];
46
+ readonly model: "balanced";
47
+ readonly activation: "mandatory";
48
+ readonly relatedStages: ["spec", "review", "ship"];
49
+ readonly body: string;
50
+ }, {
51
+ readonly name: "security-reviewer";
52
+ readonly description: "MANDATORY during review; PROACTIVE during design/ship for trust-boundary changes. Always produce an explicit no-change attestation when no security-relevant surface moved.";
53
+ readonly tools: ["Read", "Grep", "Glob"];
54
+ readonly model: "balanced";
55
+ readonly activation: "mandatory";
56
+ readonly relatedStages: ["design", "review", "ship"];
57
+ readonly body: string;
58
+ }, {
59
+ readonly name: "test-author";
60
+ readonly description: "MANDATORY in TDD stage. MUST BE USED for RED -> GREEN -> REFACTOR with evidence-first discipline.";
61
+ readonly tools: ["Read", "Write", "Edit", "Grep", "Glob", "Bash"];
62
+ readonly model: "balanced";
63
+ readonly activation: "mandatory";
64
+ readonly relatedStages: ["tdd"];
65
+ readonly body: string;
66
+ }, {
67
+ readonly name: "doc-updater";
68
+ readonly description: "MANDATORY at ship and PROACTIVE when behavior/config/public API changes. Keep docs and runbooks in lockstep with shipped behavior.";
69
+ readonly tools: ["Read", "Write", "Edit", "Grep", "Glob"];
70
+ readonly model: "fast";
71
+ readonly activation: "mandatory";
72
+ readonly relatedStages: ["tdd", "ship"];
73
+ readonly body: string;
74
+ }];
75
+ /**
76
+ * Union of known agent names (compile-time). Use this in content that
77
+ * references agents by name so the TypeScript compiler catches renames
78
+ * and typos instead of letting them slip into generated artifacts.
27
79
  */
28
- export declare const CCLAW_AGENTS: AgentDefinition[];
80
+ export type AgentName = (typeof CCLAW_AGENTS)[number]["name"];
29
81
  /**
30
82
  * Render a complete cclaw agent markdown file (YAML frontmatter + body).
31
83
  */
@@ -15,6 +15,12 @@ function yamlFlowSequence(values) {
15
15
  }
16
16
  /**
17
17
  * Canonical specialist roster (core-5) materialized under `.cclaw/agents/`.
18
+ *
19
+ * Declared with `satisfies` so the array retains literal `name` types for
20
+ * downstream type-level consumers (e.g. `AgentName`), while still being
21
+ * checked against the `AgentDefinition` shape at compile time. Do not add
22
+ * an explicit `AgentDefinition[]` annotation here — it would widen `name`
23
+ * to `string` and break the compile-time drift guard.
18
24
  */
19
25
  export const CCLAW_AGENTS = [
20
26
  {
@@ -1,4 +1,5 @@
1
1
  import { HARNESS_ADAPTERS, harnessTier } from "../harness-adapters.js";
2
+ import { STAGE_TO_SKILL_FOLDER } from "../constants.js";
2
3
  import { HOOK_EVENTS_BY_HARNESS, HOOK_SEMANTIC_EVENTS } from "./hook-events.js";
3
4
  import { HARNESS_PLAYBOOKS_DIR, harnessPlaybookFileName } from "./harness-playbooks.js";
4
5
  import { HARNESS_TOOL_REFS_DIR } from "./harness-tool-refs.js";
@@ -23,6 +24,15 @@ function tierDescription(tier) {
23
24
  }
24
25
  export function harnessIntegrationDocMarkdown() {
25
26
  const harnesses = Object.keys(HARNESS_ADAPTERS);
27
+ const stageSkillRows = Object.entries(STAGE_TO_SKILL_FOLDER)
28
+ .map(([stage, skillFolder]) => `| \`${stage}\` | \`${skillFolder}\` |`)
29
+ .join("\n");
30
+ const hookCasingRows = [
31
+ "| Claude Code | `claude` | PascalCase (`SessionStart`, `PreToolUse`) |",
32
+ "| Cursor | `cursor` | camelCase (`sessionStart`, `preToolUse`) |",
33
+ "| OpenCode | `opencode` | camelCase (`sessionStart`, `preToolUse`) |",
34
+ "| OpenAI Codex | `codex` | PascalCase (`SessionStart`, `PreToolUse`) |"
35
+ ].join("\n");
26
36
  const capabilityRows = harnesses
27
37
  .map((harness) => {
28
38
  const adapter = HARNESS_ADAPTERS[harness];
@@ -75,6 +85,17 @@ Design-stage research fleet uses the same parity model:
75
85
  |---|---|---|---|---|
76
86
  ${hookRows}
77
87
 
88
+ ## Hook event casing
89
+
90
+ Hook keys are intentionally harness-native and must not be normalized:
91
+
92
+ | Harness | ID | Event key casing |
93
+ |---|---|---|
94
+ ${hookCasingRows}
95
+
96
+ Use the exact event names from each harness schema. Treating all hooks as one
97
+ shared casing silently breaks generated wiring.
98
+
78
99
  ## Interpretation
79
100
 
80
101
  - \`tier1\`: full native delegation + structured asks + full hook surface.
@@ -91,7 +112,7 @@ All harnesses receive the same utility commands:
91
112
 
92
113
  - \`/cc\` - flow entry and resume
93
114
  - \`/cc-next\` - stage progression
94
- - \`/cc-ideate\` - discovery mode for ranked repo-improvement backlog
115
+ - \`/cc-ideate\` - ideate mode for ranked repo-improvement backlog
95
116
  - \`/cc-view\` - read-only router for status/tree/diff
96
117
  - \`/cc-ops\` - operations router for feature/tdd-log/retro/compound/archive/rewind
97
118
 
@@ -112,6 +133,16 @@ Operations subcommands:
112
133
  Stage order remains canonical:
113
134
  \`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship\`
114
135
 
136
+ ## Stage -> skill folder mapping
137
+
138
+ | Stage | Skill folder |
139
+ |---|---|
140
+ ${stageSkillRows}
141
+
142
+ This map is generated from \`src/constants.ts::STAGE_TO_SKILL_FOLDER\` so
143
+ skill-path naming stays explicit and stable even when stage ids differ from
144
+ folder names.
145
+
115
146
  ## Install surfaces
116
147
 
117
148
  Always generated:
@@ -95,9 +95,9 @@ generic dispatcher with a strict role prompt.
95
95
  ## Dispatch pattern
96
96
 
97
97
  1. Pick the mapped \`subagent_type\` from the table above.
98
- 2. Build the \`prompt\` from the cclaw agent contract in
98
+ 2. Build the \`prompt\` from the cclaw agent role brief in
99
99
  \`.cclaw/agents/<agent>.md\`, prefaced with a single line naming the
100
- cclaw role (\`You are the cclaw <agent>. Follow the contract below.\`).
100
+ cclaw role (\`You are the cclaw <agent>. Follow the role brief below.\`).
101
101
  3. Set \`readonly: true\` when the table says yes — Cursor enforces it.
102
102
  4. Before dispatch, append a delegation row:
103
103
 
@@ -142,7 +142,7 @@ description: "OpenCode has plugin-based dispatch hooks and a native structured-a
142
142
  **Fallback: role-switch.** OpenCode exposes tool/session event hooks via a
143
143
  plugin but does not provide an isolated subagent worker. cclaw closes the
144
144
  delegation gate by role-switching inside the same session: the agent
145
- announces the role, performs the work against the contract, and records
145
+ announces the role, performs the work against the role brief, and records
146
146
  evidence.
147
147
 
148
148
  **Structured ask: native \`question\` tool.** OpenCode ships a first-class
@@ -168,7 +168,7 @@ artifact decision log. Full mapping:
168
168
  > Acting as cclaw **<agent>** per \`.cclaw/agents/<agent>.md\`. No other
169
169
  > role may be assumed until the delegation row is closed.
170
170
 
171
- 2. Execute the role's contract. Do NOT interleave other roles' work.
171
+ 2. Execute the role's brief. Do NOT interleave other roles' work.
172
172
  3. Write the result into the stage artifact (e.g. TDD work lands in
173
173
  \`.cclaw/artifacts/06-tdd.md\`).
174
174
  4. Append a delegation row:
@@ -2,13 +2,13 @@ import { RUNTIME_ROOT } from "../constants.js";
2
2
  const IDEATE_SKILL_FOLDER = "flow-ideate";
3
3
  const IDEATE_SKILL_NAME = "flow-ideate";
4
4
  /**
5
- * Directory + filename convention for ideation artifacts. These are separate
5
+ * Directory + filename convention for ideate artifacts. These are separate
6
6
  * from stage artifacts (00-..08-*.md) because `/cc-ideate` runs outside the
7
7
  * critical-path flow state machine and must not collide with stage numbering.
8
8
  */
9
- const IDEATION_ARTIFACT_GLOB = ".cclaw/artifacts/ideation-*.md";
10
- const IDEATION_ARTIFACT_PATTERN = ".cclaw/artifacts/ideation-<YYYY-MM-DD-slug>.md";
11
- const IDEATION_RESUME_WINDOW_DAYS = 30;
9
+ const IDEATE_ARTIFACT_GLOB = ".cclaw/artifacts/ideate-*.md";
10
+ const IDEATE_ARTIFACT_PATTERN = ".cclaw/artifacts/ideate-<YYYY-MM-DD-slug>.md";
11
+ const IDEATE_RESUME_WINDOW_DAYS = 30;
12
12
  /**
13
13
  * Structured-ask tool list reused across cclaw skills. Kept inline here (small
14
14
  * enough) to avoid cross-module coupling; larger stage skills cite the shared
@@ -23,25 +23,25 @@ export function ideateCommandContract() {
23
23
 
24
24
  ## Purpose
25
25
 
26
- Repository-improvement discovery mode. Generate a ranked backlog of
26
+ Repository-improvement ideate mode. Generate a ranked backlog of
27
27
  high-value improvements, persist it as an artifact on disk, and end with
28
28
  an explicit handoff — either launch \`/cc\` on a chosen candidate in the
29
29
  same session, or save/discard the backlog.
30
30
 
31
31
  ## HARD-GATE
32
32
 
33
- - Discovery mode only. Never mutate \`.cclaw/state/flow-state.json\`.
33
+ - Ideate mode only. Never mutate \`.cclaw/state/flow-state.json\`.
34
34
  - Every recommendation cites evidence from the current repository
35
35
  (file path, command output, or knowledge-store entry id).
36
36
  - Always write a persisted artifact to
37
- \`${IDEATION_ARTIFACT_PATTERN}\`. Chat-only output is not acceptable —
37
+ \`${IDEATE_ARTIFACT_PATTERN}\`. Chat-only output is not acceptable —
38
38
  the next session must be able to resume.
39
39
  - Always end with a structured handoff prompt, not an open question.
40
40
 
41
41
  ## Algorithm
42
42
 
43
- 1. **Resume check.** Glob \`${IDEATION_ARTIFACT_GLOB}\`. If any artifact
44
- has been modified within the last ${IDEATION_RESUME_WINDOW_DAYS} days,
43
+ 1. **Resume check.** Glob \`${IDEATE_ARTIFACT_GLOB}\`. If any artifact
44
+ has been modified within the last ${IDEATE_RESUME_WINDOW_DAYS} days,
45
45
  offer the user: continue that backlog, start fresh, or cancel.
46
46
  2. **Scan repo signals:**
47
47
  - open TODO/FIXME/XXX/HACK notes,
@@ -54,7 +54,7 @@ same session, or save/discard the backlog.
54
54
  per candidate.
55
55
  4. **Rank by impact/effort**, recommend the top item.
56
56
  5. **Write the artifact** at
57
- \`${IDEATION_ARTIFACT_PATTERN}\` using the schema in the skill.
57
+ \`${IDEATE_ARTIFACT_PATTERN}\` using the schema in the skill.
58
58
  6. **Present the handoff prompt** with four concrete options — not A/B/C
59
59
  letters. Default = "Start /cc on the top recommendation".
60
60
 
@@ -66,7 +66,7 @@ same session, or save/discard the backlog.
66
66
  export function ideateCommandSkillMarkdown() {
67
67
  return `---
68
68
  name: ${IDEATE_SKILL_NAME}
69
- description: "Repository ideation mode: detect and rank high-leverage improvements, persist a backlog artifact, and hand off to /cc or save/discard."
69
+ description: "Repository ideate mode: detect and rank high-leverage improvements, persist a backlog artifact, and hand off to /cc or save/discard."
70
70
  ---
71
71
 
72
72
  # /cc-ideate
@@ -75,12 +75,12 @@ description: "Repository ideation mode: detect and rank high-leverage improvemen
75
75
 
76
76
  "Using flow-ideate to identify highest-leverage improvements in this
77
77
  repository. Will persist a ranked backlog to
78
- \`${IDEATION_ARTIFACT_PATTERN}\` and end with an explicit handoff."
78
+ \`${IDEATE_ARTIFACT_PATTERN}\` and end with an explicit handoff."
79
79
 
80
80
  ## HARD-GATE
81
81
 
82
82
  - Do not start coding in ideate mode.
83
- - Do not mutate \`.cclaw/state/flow-state.json\` — ideation sits outside
83
+ - Do not mutate \`.cclaw/state/flow-state.json\` — ideate mode sits outside
84
84
  the critical-path flow.
85
85
  - Always produce the artifact file on disk before presenting the handoff.
86
86
  - Always end with a structured handoff that names the concrete follow-up
@@ -91,12 +91,12 @@ repository. Will persist a ranked backlog to
91
91
  ### Phase 0 — Resume check
92
92
 
93
93
  1. Use the harness's file-glob tool (\`Glob\` pattern
94
- \`${IDEATION_ARTIFACT_GLOB}\` or equivalent \`ls\`/\`find\`).
95
- 2. Filter to files modified within the last ${IDEATION_RESUME_WINDOW_DAYS} days.
94
+ \`${IDEATE_ARTIFACT_GLOB}\` or equivalent \`ls\`/\`find\`).
95
+ 2. Filter to files modified within the last ${IDEATE_RESUME_WINDOW_DAYS} days.
96
96
  3. If one or more match, present **one** structured ask using the
97
97
  harness's native tool (${STRUCTURED_ASK_TOOLS}) with options:
98
98
  - **Continue the existing backlog** — read the most-recent
99
- ideation-*.md and work from its candidate list; skip re-scanning.
99
+ ideate-*.md and work from its candidate list; skip re-scanning.
100
100
  - **Start a fresh scan** — proceed to Phase 1; the old artifact stays
101
101
  on disk for history.
102
102
  - **Cancel** — stop; do not scan or write anything.
@@ -137,10 +137,10 @@ Aim for 5–10 candidates. Do not invent candidates without evidence.
137
137
  1. Sort by impact/effort ratio; break ties with confidence.
138
138
  2. Compute the artifact filename:
139
139
  - \`slug\` = first 3–5 words of the top recommendation, lowercase,
140
- non-alphanumeric collapsed to \`-\`, trimmed. When ideation is
140
+ non-alphanumeric collapsed to \`-\`, trimmed. When ideate mode is
141
141
  focus-hinted (user passed an argument), use the focus hint instead.
142
142
  - \`date\` = today in \`YYYY-MM-DD\` (local time).
143
- - Path = \`.cclaw/artifacts/ideation-<date>-<slug>.md\`.
143
+ - Path = \`.cclaw/artifacts/ideate-<date>-<slug>.md\`.
144
144
  3. Use the harness's write-file tool (\`Write\`, \`apply_patch\`, or shell
145
145
  \`cat <<EOF > path\`) to create the artifact with this schema:
146
146
 
@@ -195,7 +195,7 @@ lettered list with the same four labels. Do not invent extra options.
195
195
  - **Start /cc on I-1** or **different candidate:** announce
196
196
  "Handing off to /cc <phrase>" and load the \`using-cclaw\` router
197
197
  skill. From there, the normal \`/cc\` classification and stage flow
198
- takes over. Do not produce a second artifact; the ideation file is
198
+ takes over. Do not produce a second artifact; the ideate file is
199
199
  preserved as the origin document for this run.
200
200
  - **Save and close:** reply with the artifact path and stop.
201
201
  - **Discard:** delete the artifact file, confirm deletion, stop.
@@ -1094,11 +1094,20 @@ export function claudeHooksJsonWithObservation() {
1094
1094
  }]
1095
1095
  }],
1096
1096
  PreToolUse: [{
1097
+ // `prompt-guard.sh` inspects tool inputs across all tool calls;
1098
+ // it has to stay on `*` so it sees MCP/Edit/Write/WebSearch
1099
+ // traffic too. `workflow-guard.sh`, however, only checks TDD
1100
+ // ordering on write-like operations — it is a no-op for reads.
1101
+ // Splitting the two matchers cuts Claude's per-read hook
1102
+ // overhead in half without reducing coverage on write paths.
1097
1103
  matcher: "*",
1098
1104
  hooks: [{
1099
1105
  type: "command",
1100
1106
  command: hookDispatcherCommand("prompt-guard.sh")
1101
- }, {
1107
+ }]
1108
+ }, {
1109
+ matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash",
1110
+ hooks: [{
1102
1111
  type: "command",
1103
1112
  command: hookDispatcherCommand("workflow-guard.sh")
1104
1113
  }]
@@ -1196,6 +1205,18 @@ export function codexHooksJsonWithObservation() {
1196
1205
  hooks: [{
1197
1206
  type: "command",
1198
1207
  command: hookDispatcherCommand("prompt-guard.sh")
1208
+ }, {
1209
+ // `workflow-guard.sh` also runs here because Codex's PreToolUse
1210
+ // only sees Bash; Write/Edit/MCP writes never reach the hook
1211
+ // surface. Running workflow-guard on UserPromptSubmit catches
1212
+ // TDD-order violations that originate from the user's prompt
1213
+ // text (e.g. "edit X.ts to ..."). Payload is a prompt envelope,
1214
+ // not a tool call, so the script's TOOL extraction falls back
1215
+ // to "unknown" and advisory mode is a no-op by design — the
1216
+ // value is that prompt text is scanned for write-shaped intent
1217
+ // via the existing PAYLOAD_LOWER heuristics.
1218
+ type: "command",
1219
+ command: hookDispatcherCommand("workflow-guard.sh")
1199
1220
  }, {
1200
1221
  type: "command",
1201
1222
  command: "bash -lc 'if command -v cclaw >/dev/null 2>&1; then cclaw internal verify-current-state --quiet >/dev/null || true; else npx -y cclaw-cli internal verify-current-state --quiet >/dev/null || true; fi'"
@@ -233,11 +233,15 @@ export default function cclawPlugin(ctx) {
233
233
  eventType === "session.created" ||
234
234
  eventType === "session.resumed" ||
235
235
  eventType === "session.compacted" ||
236
- eventType === "session.cleared"
236
+ eventType === "session.cleared" ||
237
+ eventType === "session.updated"
237
238
  ) {
238
239
  // Avoid writing directly to stdout in lifecycle hooks because it can
239
240
  // interfere with OpenCode TUI rendering. Bootstrap is injected via
240
241
  // the system transform hook instead.
242
+ // session.updated covers config reloads and artifact/rules edits
243
+ // that happen mid-session; without it the cache would stay stale
244
+ // until the next compaction or restart.
241
245
  refreshBootstrapCache();
242
246
  }
243
247
  if (eventType === "session.compacted") {
@@ -1,4 +1,4 @@
1
- import { RUNTIME_ROOT } from "../constants.js";
1
+ import { RUNTIME_ROOT, STAGE_TO_SKILL_FOLDER } from "../constants.js";
2
2
  import { STAGE_EXAMPLES_REFERENCE_DIR, stageDomainExamples, stageExamples, stageGoodBadExamples } from "./examples.js";
3
3
  import { STAGE_COMMON_GUIDANCE_REL_PATH } from "./stage-common-guidance.js";
4
4
  import { stageAutoSubagentDispatch, stageSchema } from "./stage-schema.js";
@@ -295,7 +295,7 @@ After T-3 REFACTOR, before declaring Batch 1 done:
295
295
  - The same RED failure reappears after a GREEN change → **escalate** per the 3-attempts rule; do not keep patching.
296
296
  `;
297
297
  export function stageSkillFolder(stage) {
298
- return stageSchema(stage).skillFolder;
298
+ return STAGE_TO_SKILL_FOLDER[stage];
299
299
  }
300
300
  export function stageSkillMarkdown(stage, track = "standard") {
301
301
  const schema = stageSchema(stage, track);