cclaw-cli 0.46.13 → 0.46.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -154,10 +154,11 @@ If cclaw detects a Node / Python / Go project at init time, a sixth
154
154
  default surface — a new user sees nothing they need to understand yet.
155
155
 
156
156
  Advanced knobs (`promptGuardMode` / `tddEnforcement` per-axis overrides,
157
- `tddTestGlobs`, `defaultTrack`, `trackHeuristics`, `sliceReview`) are
158
- **opt-in**: add them by hand when you need them. `cclaw upgrade`
159
- preserves exactly what you wrote it never silently reintroduces
160
- defaults you removed.
157
+ `tdd.testPathPatterns` / `tdd.productionPathPatterns`,
158
+ `compound.recurrenceThreshold`, `defaultTrack`, `trackHeuristics`,
159
+ `sliceReview`) are **opt-in**: add them by hand when you need them.
160
+ `cclaw upgrade` preserves exactly what you wrote — it never silently
161
+ reintroduces defaults you removed.
161
162
 
162
163
  Full key-by-key reference: [`docs/config.md`](./docs/config.md).
163
164
 
@@ -240,7 +241,7 @@ the flow matches the task.
240
241
  |---|---|---|
241
242
  | **quick** | `spec → tdd → review → ship` | `bug`, `hotfix`, `typo`, `rename`, `bump`, `docs only`, one-liners |
242
243
  | **medium** | `brainstorm → spec → plan → tdd → review → ship` | `add endpoint`, `add field`, `extend existing`, `wire integration` |
243
- | **standard** _(default)_ | all 8 stages | `new feature`, `refactor`, `migration`, `platform`, `schema`, `architecture` |
244
+ | **standard** _(default)_ | all 8 stages (+ mandatory design-time parallel research fleet) | `new feature`, `refactor`, `migration`, `platform`, `schema`, `architecture` |
244
245
 
245
246
  **Every track ends with the same auto-closeout chain.** Once ship
246
247
  completes, `/cc-next` automatically drives
@@ -250,10 +251,11 @@ without re-drafting retros or re-asking structured questions. See
250
251
  [Ship and closeout](#ship-and-closeout--automatic-resumable).
251
252
 
252
253
  Each critical-path stage produces a dated artifact under
253
- `.cclaw/artifacts/`: `00-idea.md` (seed), `01-brainstorm.md` through
254
+ `.cclaw/artifacts/`: `00-idea.md` (seed), `01-brainstorm.md`, `02-scope.md`,
255
+ `02a-research.md` (design research fleet synthesis), `03-design.md` through
254
256
  `08-ship.md`. Closeout adds `09-retro.md`; archive then rolls the whole
255
- bundle into `.cclaw/runs/<YYYY-MM-DD-slug>/` and resets the active flow
256
- for the next feature.
257
+ bundle into `.cclaw/runs/<YYYY-MM-DD-slug>/` and resets the active flow for
258
+ the next feature.
257
259
 
258
260
  ### Track heuristics are configurable (advisory)
259
261
 
@@ -312,9 +314,12 @@ it into ceremony:
312
314
  protocol emits typed entries (`rule` / `pattern` / `lesson`) to
313
315
  `.cclaw/knowledge.jsonl` as the flow progresses — not only at retro.
314
316
  Retro itself adds a `compound` entry, and the automatic compound pass
315
- after ship promotes recurring entries (≥ 3) into first-class
316
- rules/protocols/skills so the **next** run is easier. Strict JSONL
317
- schema keeps the whole thing machine-queryable.
317
+ after ship promotes recurring entries into first-class
318
+ rules/protocols/skills (base threshold from
319
+ `compound.recurrenceThreshold`, temporarily lowered to 2 for repositories
320
+ with <5 archived runs, plus a critical-severity single-hit override) so
321
+ the **next** run is easier. Strict JSONL schema keeps the whole thing
322
+ machine-queryable.
318
323
  - **Automatic integrity checks.** Runtime health is verified on every
319
324
  stage transition — no command you need to remember to run.
320
325
 
@@ -15,6 +15,7 @@ export interface LintResult {
15
15
  export declare function extractMarkdownSectionBody(markdown: string, section: string): string | null;
16
16
  export type LearningEntryType = "rule" | "pattern" | "lesson" | "compound";
17
17
  export type LearningConfidence = "high" | "medium" | "low";
18
+ export type LearningSeverity = "critical" | "important" | "suggestion";
18
19
  export type LearningUniversality = "project" | "personal" | "universal";
19
20
  export type LearningMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
20
21
  export type LearningSource = "stage" | "retro" | "compound" | "ideate" | "manual";
@@ -23,6 +24,7 @@ export interface LearningSeedEntry {
23
24
  trigger: string;
24
25
  action: string;
25
26
  confidence: LearningConfidence;
27
+ severity?: LearningSeverity;
26
28
  domain?: string | null;
27
29
  stage?: FlowStage | null;
28
30
  origin_stage?: FlowStage | null;
@@ -299,6 +299,7 @@ function validateVerificationLadder(sectionBody) {
299
299
  }
300
300
  const LEARNING_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
301
301
  const LEARNING_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
302
+ const LEARNING_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
302
303
  const LEARNING_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
303
304
  const LEARNING_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
304
305
  const LEARNING_SOURCE_SET = new Set([
@@ -314,6 +315,7 @@ const LEARNING_ALLOWED_KEYS = new Set([
314
315
  "trigger",
315
316
  "action",
316
317
  "confidence",
318
+ "severity",
317
319
  "domain",
318
320
  "stage",
319
321
  "origin_stage",
@@ -377,6 +379,13 @@ function parseLearningSeedEntry(raw, index) {
377
379
  error: `Learnings bullet #${index} must set confidence to high|medium|low.`
378
380
  };
379
381
  }
382
+ const severity = typeof obj.severity === "string" ? obj.severity.toLowerCase() : undefined;
383
+ if (severity !== undefined && !LEARNING_SEVERITY_SET.has(severity)) {
384
+ return {
385
+ ok: false,
386
+ error: `Learnings bullet #${index} field "severity" must be critical|important|suggestion.`
387
+ };
388
+ }
380
389
  if (obj.domain !== undefined && !isNullableString(obj.domain)) {
381
390
  return { ok: false, error: `Learnings bullet #${index} field "domain" must be string or null.` };
382
391
  }
@@ -443,7 +452,8 @@ function parseLearningSeedEntry(raw, index) {
443
452
  type: type,
444
453
  trigger,
445
454
  action,
446
- confidence: confidence
455
+ confidence: confidence,
456
+ ...(severity ? { severity: severity } : {})
447
457
  }
448
458
  };
449
459
  }
package/dist/config.d.ts CHANGED
@@ -1,14 +1,19 @@
1
1
  import type { FlowTrack, HarnessId, LanguageRulePack, VibyConfig } from "./types.js";
2
2
  export declare function configPath(projectRoot: string): string;
3
3
  /**
4
- * Default test-file globs used by workflow-guard.sh to detect when a write
5
- * targets a test file during TDD. Users rarely need to override this — the
6
- * defaults cover TypeScript / JavaScript / Python / Go / Rust / Java layouts.
7
- * Exposed so `install.ts` can reuse the same list when seeding the shell
8
- * guard script, even though the field is no longer written to the default
9
- * `config.yaml` template.
4
+ * Default test-path patterns used by workflow-guard.sh to classify TDD writes.
5
+ *
6
+ * Scope is intentionally narrow and language-agnostic; users can extend this
7
+ * list in config when their repository uses different conventions.
8
+ */
9
+ export declare const DEFAULT_TDD_TEST_PATH_PATTERNS: readonly string[];
10
+ /**
11
+ * Legacy alias kept for backwards compatibility with `tddTestGlobs`.
12
+ * Prefer `tdd.testPathPatterns` in new configurations.
10
13
  */
11
14
  export declare const DEFAULT_TDD_TEST_GLOBS: readonly string[];
15
+ export declare const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS: readonly string[];
16
+ export declare const DEFAULT_COMPOUND_RECURRENCE_THRESHOLD = 3;
12
17
  /**
13
18
  * Populated runtime view of config values that downstream callers (install,
14
19
  * observe, doctor) consume. Always has the derived guard modes populated,
@@ -34,7 +39,7 @@ export declare function readConfig(projectRoot: string): Promise<VibyConfig>;
34
39
  * the user set them explicitly. Keeps the default template small and honest:
35
40
  * only knobs a new user would meaningfully flip show up.
36
41
  */
37
- type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview";
42
+ type AdvancedConfigKey = "promptGuardMode" | "tddEnforcement" | "tddTestGlobs" | "tdd" | "compound" | "defaultTrack" | "languageRulePacks" | "trackHeuristics" | "sliceReview";
38
43
  /**
39
44
  * Options controlling the serialisation shape of `config.yaml`.
40
45
  *
package/dist/config.js CHANGED
@@ -19,6 +19,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
19
19
  "promptGuardMode",
20
20
  "tddEnforcement",
21
21
  "tddTestGlobs",
22
+ "tdd",
23
+ "compound",
22
24
  "gitHookGuards",
23
25
  "defaultTrack",
24
26
  "languageRulePacks",
@@ -74,18 +76,23 @@ export function configPath(projectRoot) {
74
76
  return path.join(projectRoot, CONFIG_PATH);
75
77
  }
76
78
  /**
77
- * Default test-file globs used by workflow-guard.sh to detect when a write
78
- * targets a test file during TDD. Users rarely need to override this — the
79
- * defaults cover TypeScript / JavaScript / Python / Go / Rust / Java layouts.
80
- * Exposed so `install.ts` can reuse the same list when seeding the shell
81
- * guard script, even though the field is no longer written to the default
82
- * `config.yaml` template.
79
+ * Default test-path patterns used by workflow-guard.sh to classify TDD writes.
80
+ *
81
+ * Scope is intentionally narrow and language-agnostic; users can extend this
82
+ * list in config when their repository uses different conventions.
83
83
  */
84
- export const DEFAULT_TDD_TEST_GLOBS = [
84
+ export const DEFAULT_TDD_TEST_PATH_PATTERNS = [
85
85
  "**/*.test.*",
86
- "**/*.spec.*",
87
- "**/test/**"
86
+ "**/tests/**",
87
+ "**/__tests__/**"
88
88
  ];
89
+ /**
90
+ * Legacy alias kept for backwards compatibility with `tddTestGlobs`.
91
+ * Prefer `tdd.testPathPatterns` in new configurations.
92
+ */
93
+ export const DEFAULT_TDD_TEST_GLOBS = [...DEFAULT_TDD_TEST_PATH_PATTERNS];
94
+ export const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS = [];
95
+ export const DEFAULT_COMPOUND_RECURRENCE_THRESHOLD = 3;
89
96
  /**
90
97
  * Populated runtime view of config values that downstream callers (install,
91
98
  * observe, doctor) consume. Always has the derived guard modes populated,
@@ -93,6 +100,8 @@ export const DEFAULT_TDD_TEST_GLOBS = [
93
100
  * or neither.
94
101
  */
95
102
  export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack = "standard") {
103
+ const tddTestPathPatterns = [...DEFAULT_TDD_TEST_PATH_PATTERNS];
104
+ const tddProductionPathPatterns = [...DEFAULT_TDD_PRODUCTION_PATH_PATTERNS];
96
105
  return {
97
106
  version: CCLAW_VERSION,
98
107
  flowVersion: FLOW_VERSION,
@@ -100,7 +109,14 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
100
109
  strictness: "advisory",
101
110
  promptGuardMode: "advisory",
102
111
  tddEnforcement: "advisory",
103
- tddTestGlobs: [...DEFAULT_TDD_TEST_GLOBS],
112
+ tddTestGlobs: [...tddTestPathPatterns],
113
+ tdd: {
114
+ testPathPatterns: tddTestPathPatterns,
115
+ productionPathPatterns: tddProductionPathPatterns
116
+ },
117
+ compound: {
118
+ recurrenceThreshold: DEFAULT_COMPOUND_RECURRENCE_THRESHOLD
119
+ },
104
120
  gitHookGuards: false,
105
121
  defaultTrack,
106
122
  languageRulePacks: []
@@ -213,6 +229,48 @@ export async function readConfig(projectRoot) {
213
229
  const tddTestGlobsRaw = parsed.tddTestGlobs;
214
230
  const tddTestGlobs = validateStringArray(tddTestGlobsRaw, "tddTestGlobs", fullPath)
215
231
  ?? [...DEFAULT_TDD_TEST_GLOBS];
232
+ const hasTddField = Object.prototype.hasOwnProperty.call(parsed, "tdd");
233
+ const tddRaw = parsed.tdd;
234
+ let explicitTddTestPathPatterns;
235
+ let explicitTddProductionPathPatterns;
236
+ if (hasTddField) {
237
+ if (!isRecord(tddRaw)) {
238
+ throw configValidationError(fullPath, `"tdd" must be an object`);
239
+ }
240
+ const unknownTddKeys = Object.keys(tddRaw).filter((key) => key !== "testPathPatterns" && key !== "productionPathPatterns");
241
+ if (unknownTddKeys.length > 0) {
242
+ throw configValidationError(fullPath, `"tdd" has unknown key(s): ${unknownTddKeys.join(", ")}`);
243
+ }
244
+ explicitTddTestPathPatterns = validateStringArray(tddRaw.testPathPatterns, "tdd.testPathPatterns", fullPath);
245
+ explicitTddProductionPathPatterns = validateStringArray(tddRaw.productionPathPatterns, "tdd.productionPathPatterns", fullPath);
246
+ }
247
+ const resolvedTddTestPathPatterns = [
248
+ ...(explicitTddTestPathPatterns ?? tddTestGlobs ?? DEFAULT_TDD_TEST_PATH_PATTERNS)
249
+ ];
250
+ const resolvedTddProductionPathPatterns = [
251
+ ...(explicitTddProductionPathPatterns ?? DEFAULT_TDD_PRODUCTION_PATH_PATTERNS)
252
+ ];
253
+ const hasCompoundField = Object.prototype.hasOwnProperty.call(parsed, "compound");
254
+ const compoundRaw = parsed.compound;
255
+ let compoundRecurrenceThreshold = DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
256
+ if (hasCompoundField) {
257
+ if (!isRecord(compoundRaw)) {
258
+ throw configValidationError(fullPath, `"compound" must be an object`);
259
+ }
260
+ const unknownCompoundKeys = Object.keys(compoundRaw).filter((key) => key !== "recurrenceThreshold");
261
+ if (unknownCompoundKeys.length > 0) {
262
+ throw configValidationError(fullPath, `"compound" has unknown key(s): ${unknownCompoundKeys.join(", ")}`);
263
+ }
264
+ if (compoundRaw.recurrenceThreshold !== undefined &&
265
+ (typeof compoundRaw.recurrenceThreshold !== "number" ||
266
+ !Number.isInteger(compoundRaw.recurrenceThreshold) ||
267
+ compoundRaw.recurrenceThreshold < 1)) {
268
+ throw configValidationError(fullPath, `"compound.recurrenceThreshold" must be a positive integer`);
269
+ }
270
+ if (typeof compoundRaw.recurrenceThreshold === "number") {
271
+ compoundRecurrenceThreshold = compoundRaw.recurrenceThreshold;
272
+ }
273
+ }
216
274
  const gitHookGuardsRaw = parsed.gitHookGuards;
217
275
  if (Object.prototype.hasOwnProperty.call(parsed, "gitHookGuards") &&
218
276
  typeof gitHookGuardsRaw !== "boolean") {
@@ -327,6 +385,13 @@ export async function readConfig(projectRoot) {
327
385
  promptGuardMode,
328
386
  tddEnforcement,
329
387
  tddTestGlobs,
388
+ tdd: {
389
+ testPathPatterns: resolvedTddTestPathPatterns,
390
+ productionPathPatterns: resolvedTddProductionPathPatterns
391
+ },
392
+ compound: {
393
+ recurrenceThreshold: compoundRecurrenceThreshold
394
+ },
330
395
  gitHookGuards,
331
396
  defaultTrack,
332
397
  languageRulePacks,
@@ -349,6 +414,8 @@ function buildSerializableConfig(config, options = {}) {
349
414
  "promptGuardMode",
350
415
  "tddEnforcement",
351
416
  "tddTestGlobs",
417
+ "tdd",
418
+ "compound",
352
419
  "gitHookGuards",
353
420
  "defaultTrack",
354
421
  "languageRulePacks",
@@ -402,6 +469,8 @@ export async function detectAdvancedKeys(projectRoot) {
402
469
  "promptGuardMode",
403
470
  "tddEnforcement",
404
471
  "tddTestGlobs",
472
+ "tdd",
473
+ "compound",
405
474
  "defaultTrack",
406
475
  "languageRulePacks",
407
476
  "trackHeuristics",
@@ -1,2 +1,5 @@
1
- export declare function compoundCommandContract(): string;
2
- export declare function compoundCommandSkillMarkdown(): string;
1
+ export interface CompoundCommandOptions {
2
+ recurrenceThreshold?: number;
3
+ }
4
+ export declare function compoundCommandContract(options?: CompoundCommandOptions): string;
5
+ export declare function compoundCommandSkillMarkdown(options?: CompoundCommandOptions): string;
@@ -1,7 +1,18 @@
1
1
  import { RUNTIME_ROOT } from "../constants.js";
2
2
  const COMPOUND_SKILL_FOLDER = "flow-compound";
3
3
  const COMPOUND_SKILL_NAME = "flow-compound";
4
- export function compoundCommandContract() {
4
+ const DEFAULT_RECURRENCE_THRESHOLD = 3;
5
+ const SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD = 5;
6
+ const SMALL_PROJECT_RECURRENCE_THRESHOLD = 2;
7
+ function resolveRecurrenceThreshold(options) {
8
+ const threshold = options.recurrenceThreshold;
9
+ if (typeof threshold === "number" && Number.isInteger(threshold) && threshold >= 1) {
10
+ return threshold;
11
+ }
12
+ return DEFAULT_RECURRENCE_THRESHOLD;
13
+ }
14
+ export function compoundCommandContract(options = {}) {
15
+ const recurrenceThreshold = resolveRecurrenceThreshold(options);
5
16
  return `# /cc-ops compound
6
17
 
7
18
  ## Purpose
@@ -29,39 +40,48 @@ the user can approve individual lifts, accept-all, or skip.
29
40
 
30
41
  1. Read \`${RUNTIME_ROOT}/knowledge.jsonl\` (strict JSONL, one entry per line).
31
42
  2. Cluster entries by \`trigger\` + \`action\` similarity.
32
- 3. Filter candidates whose recurrence count >= 3.
33
- 4. If **no candidates** exist:
43
+ 3. Resolve recurrence policy:
44
+ - base threshold = \`${recurrenceThreshold}\` (from \`config.compound.recurrenceThreshold\`),
45
+ - count archived runs under \`${RUNTIME_ROOT}/runs/\`,
46
+ - if archived run count is < ${SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD}, use
47
+ effective threshold = \`min(base threshold, ${SMALL_PROJECT_RECURRENCE_THRESHOLD})\` for this pass.
48
+ 4. Filter candidates that satisfy at least one trigger:
49
+ - recurrence count >= effective threshold, or
50
+ - any knowledge entry in the cluster has \`severity: "critical"\`
51
+ (critical override, recurrence can be 1).
52
+ 5. If **no candidates** exist:
34
53
  - set \`closeout.compoundCompletedAt = <ISO>\`,
35
54
  - set \`closeout.compoundPromoted = 0\`,
36
55
  - set \`closeout.shipSubstate = "ready_to_archive"\`,
37
56
  - emit \`compound: no candidates | next: /cc-next\` and stop.
38
- 5. **Drift check** each surviving candidate before presenting it (see
57
+ 6. **Drift check** each surviving candidate before presenting it (see
39
58
  "Drift check" section in the skill): confirm the lift target file is
40
59
  current, spot-check the repo for contradictions, demote stale clusters
41
60
  into a new superseding entry instead of a lift.
42
- 6. Otherwise, present **one** structured ask via the harness's native ask
61
+ 7. Otherwise, present **one** structured ask via the harness's native ask
43
62
  tool (\`AskUserQuestion\` / \`AskQuestion\` / \`question\` /
44
63
  \`request_user_input\`; plain-text lettered list as fallback) summarising
45
64
  all candidates at once:
46
65
  - \`apply-all\` (default) — apply every listed lift,
47
66
  - \`apply-selected\` — prompt per-candidate,
48
67
  - \`skip\` — record a skip reason and advance without changes.
49
- 7. Apply approved lifts to the target file(s). Each lift also appends a
68
+ 8. Apply approved lifts to the target file(s). Each lift also appends a
50
69
  \`type: "compound"\` entry back to \`${RUNTIME_ROOT}/knowledge.jsonl\`
51
70
  summarising what was lifted.
52
- 8. Update flow-state:
71
+ 9. Update flow-state:
53
72
  - \`closeout.compoundCompletedAt = <ISO>\`,
54
73
  - \`closeout.compoundPromoted = <count>\`,
55
74
  - \`closeout.compoundSkipped = true\` if user picked skip,
56
75
  - \`closeout.shipSubstate = "ready_to_archive"\`.
57
- 9. Emit one-line summary: \`compound: promoted=<N> skipped=<bool> | next: /cc-next\`.
76
+ 10. Emit one-line summary: \`compound: promoted=<N> skipped=<bool> | next: /cc-next\`.
58
77
 
59
78
  ## Primary skill
60
79
 
61
80
  **${RUNTIME_ROOT}/skills/${COMPOUND_SKILL_FOLDER}/SKILL.md**
62
81
  `;
63
82
  }
64
- export function compoundCommandSkillMarkdown() {
83
+ export function compoundCommandSkillMarkdown(options = {}) {
84
+ const recurrenceThreshold = resolveRecurrenceThreshold(options);
65
85
  return `---
66
86
  name: ${COMPOUND_SKILL_NAME}
67
87
  description: "Lift repeated learnings into durable rules/protocols/skills. Auto-triggered after retro accept."
@@ -83,13 +103,20 @@ empty pass is allowed and must advance \`closeout.shipSubstate\` to
83
103
 
84
104
  1. Parse \`.cclaw/knowledge.jsonl\` and group repeated lessons by
85
105
  trigger+action similarity.
86
- 2. Keep only candidates with recurrence >= 3 and an actionable lift path.
87
- 3. If none qualify, record an empty pass:
106
+ 2. Resolve recurrence policy:
107
+ - base threshold = \`${recurrenceThreshold}\` from \`config.compound.recurrenceThreshold\`,
108
+ - count archived runs under \`.cclaw/runs/\`,
109
+ - if archived run count is < ${SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD}, use
110
+ effective threshold = \`min(base threshold, ${SMALL_PROJECT_RECURRENCE_THRESHOLD})\` for this pass.
111
+ 3. Keep only candidates that meet at least one trigger:
112
+ - recurrence >= effective threshold and actionable lift path, or
113
+ - a cluster entry with \`severity: critical\` (critical override, recurrence can be 1).
114
+ 4. If none qualify, record an empty pass:
88
115
  - \`closeout.compoundCompletedAt = <ISO>\`,
89
116
  - \`closeout.compoundPromoted = 0\`,
90
117
  - \`closeout.shipSubstate = "ready_to_archive"\`,
91
118
  - announce \`compound: no candidates\` and stop.
92
- 4. **Drift check — run before presenting any candidate.** Knowledge lines
119
+ 5. **Drift check — run before presenting any candidate.** Knowledge lines
93
120
  are append-only, so textual repetition alone does not prove the rule is
94
121
  still true. For every cluster that survives the recurrence filter:
95
122
 
@@ -111,13 +138,17 @@ empty pass is allowed and must advance \`closeout.shipSubstate\` to
111
138
  - **Cite line IDs.** Every surviving candidate must list the concrete
112
139
  knowledge line indices (1-based) that back it, not just a
113
140
  summary string. This is what makes the lift auditable.
141
+ - **Include qualification reason.** Mark each candidate as
142
+ \`recurrence\` or \`critical_override\` so reviewers can see why it passed
143
+ the filter.
114
144
  - Optionally invoke the \`knowledge-curation\` utility skill's
115
145
  stale/duplicate/supersede heuristics if you want a second pass.
116
146
 
117
- 5. Otherwise, render each candidate as:
147
+ 6. Otherwise, render each candidate as:
118
148
 
119
149
  \`\`\`
120
150
  Candidate: <short title>
151
+ Qualification: <recurrence|critical_override>
121
152
  Evidence: <knowledge line-ids>
122
153
  Freshness: <newest last_seen_ts among evidence lines>
123
154
  Lift target: <rule/protocol/skill file>
@@ -125,17 +156,17 @@ Change type: <add/update/remove>
125
156
  Expected benefit: <what regressions this prevents>
126
157
  \`\`\`
127
158
 
128
- 6. Present **one** structured question with three options:
159
+ 7. Present **one** structured question with three options:
129
160
  - \`apply-all\` (default) — apply every candidate,
130
161
  - \`apply-selected\` — prompt per-candidate approval next,
131
162
  - \`skip\` — record a skip reason and advance.
132
163
 
133
- 7. For approved candidates:
164
+ 8. For approved candidates:
134
165
  - edit the target file(s) with the lift,
135
166
  - append a \`type: "compound"\` entry to \`.cclaw/knowledge.jsonl\`
136
167
  describing what was promoted, including the source line IDs.
137
168
 
138
- 8. Update flow-state \`closeout\`:
169
+ 9. Update flow-state \`closeout\`:
139
170
  - \`compoundCompletedAt\`,
140
171
  - \`compoundPromoted\` (count),
141
172
  - \`compoundSkipped\` (boolean) + \`compoundSkipReason\` when applicable,
@@ -34,7 +34,7 @@ ${schema.hardGate}
34
34
  2. Resolve active artifact root: \`.cclaw/artifacts/\`.
35
35
  3. Load required upstream artifacts for this stage:
36
36
  ${hydrationLines}
37
- 4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project).
37
+ 4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project; optional: source, severity).
38
38
  5. Write stage output to ${writeStepPaths}.
39
39
  6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled by \`/cc-ops archive\` (agent-facing wrapper over archive runtime).
40
40
 
@@ -16,4 +16,5 @@ export declare function stageExamplesReferenceMarkdown(stage: FlowStage): string
16
16
  */
17
17
  export declare function stageExamples(stage: FlowStage): string;
18
18
  export type ExampleDomain = "web" | "cli" | "library" | "data-pipeline";
19
+ export declare const RESEARCH_FLEET_USAGE_EXAMPLE: string;
19
20
  export declare function stageDomainExamples(stage: FlowStage): string;
@@ -713,6 +713,14 @@ const DOMAIN_LABELS = {
713
713
  library: "Library / SDK",
714
714
  "data-pipeline": "Data pipeline / ETL"
715
715
  };
716
+ export const RESEARCH_FLEET_USAGE_EXAMPLE = [
717
+ "Before drafting `03-design.md`, run `research/research-fleet.md` once and",
718
+ "capture all four lenses in `.cclaw/artifacts/02a-research.md`.",
719
+ "Dispatch semantics by harness: Claude/Cursor = parallel subagents in one turn;",
720
+ "OpenCode/Codex = sequential role-switch with explicit announcements.",
721
+ "Design must include a `Research Fleet Synthesis` section that maps each",
722
+ "lens to concrete architecture decisions and risks."
723
+ ].join(" ");
716
724
  const STAGE_DOMAIN_SAMPLES = {
717
725
  brainstorm: [
718
726
  {
@@ -759,6 +767,11 @@ const STAGE_DOMAIN_SAMPLES = {
759
767
  }
760
768
  ],
761
769
  design: [
770
+ {
771
+ domain: "web",
772
+ label: "Parallel research fleet handoff",
773
+ body: RESEARCH_FLEET_USAGE_EXAMPLE
774
+ },
762
775
  {
763
776
  domain: "web",
764
777
  label: "Architecture note",
@@ -58,6 +58,17 @@ Fallback legend:
58
58
  - \`role-switch\` — in-session role announce + delegation-log entry with evidenceRefs (OpenCode, Codex).
59
59
  - \`waiver\` — no parity path; reserved for harnesses that cannot role-switch (none shipped).
60
60
 
61
+ ## Parallel research dispatch semantics
62
+
63
+ Design-stage research fleet uses the same parity model:
64
+
65
+ - **Claude / Cursor**: dispatch all four research lenses in one turn
66
+ (stack, features, architecture, pitfalls) and synthesize into
67
+ \`.cclaw/artifacts/02a-research.md\`.
68
+ - **OpenCode / Codex**: execute the same four lenses via sequential
69
+ role-switch, each with explicit announce -> execute -> evidence trail.
70
+ This preserves auditability when native parallel dispatch is unavailable.
71
+
61
72
  ## Semantic hook event coverage
62
73
 
63
74
  | Event | Claude | Cursor | OpenCode | Codex |
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Canonical required JSONL field order (matches strict validator keys).
3
- * Optional keys (for now: `source`) may be appended after these required fields.
3
+ * Optional keys (for now: `source`, `severity`) may be appended after these
4
+ * required fields.
4
5
  * Exported for tests and any programmatic writer that wants a stable base shape.
5
6
  */
6
7
  export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "action", "confidence", "domain", "stage", "origin_stage", "origin_feature", "frequency", "universality", "maturity", "created", "first_seen_ts", "last_seen_ts", "project"];
@@ -11,7 +11,8 @@ const LEARN_SKILL_NAME = "learnings";
11
11
  const LEARN_SKILL_DESCRIPTION = "Project-scoped knowledge store: append and query rule/pattern/lesson/compound entries in the canonical JSONL file at .cclaw/knowledge.jsonl. Strict schema, append-only, machine-queryable.";
12
12
  /**
13
13
  * Canonical required JSONL field order (matches strict validator keys).
14
- * Optional keys (for now: `source`) may be appended after these required fields.
14
+ * Optional keys (for now: `source`, `severity`) may be appended after these
15
+ * required fields.
15
16
  * Exported for tests and any programmatic writer that wants a stable base shape.
16
17
  */
17
18
  export const KNOWLEDGE_JSONL_FIELDS = [
@@ -74,7 +75,7 @@ Do not invent alternate stores (no markdown mirror, no SQLite, no per-stage file
74
75
 
75
76
  Exactly one JSON object per line. Required fields must appear in the order:
76
77
  \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`.
77
- Optional field \`source\` may be appended after \`project\`.
78
+ Optional fields \`source\` and \`severity\` may be appended after \`project\`.
78
79
 
79
80
  \`\`\`json
80
81
  {"type":"pattern","trigger":"when reviewing external payloads","action":"parse through zod before touching service layer","confidence":"high","domain":"api","stage":"review","origin_stage":"review","origin_feature":"payload-hardening","frequency":1,"universality":"project","maturity":"raw","created":"2026-04-14T12:00:00Z","first_seen_ts":"2026-04-14T12:00:00Z","last_seen_ts":"2026-04-14T12:00:00Z","project":"cclaw"}
@@ -98,6 +99,7 @@ Optional field \`source\` may be appended after \`project\`.
98
99
  | \`last_seen_ts\` | ISO 8601 UTC string | yes | Last re-confirmed timestamp. |
99
100
  | \`project\` | string \\| null | yes | Repo or scope name. Use \`null\` when the entry crosses projects. |
100
101
  | \`source\` | \`"stage" \\| "retro" \\| "compound" \\| "ideate" \\| "manual" \\| null\` | no | Origin channel for the entry when known. |
102
+ | \`severity\` | \`"critical" \\| "important" \\| "suggestion"\` | no | Priority signal for compound lifts; \`critical\` enables single-hit override in \`/cc-ops compound\`. |
101
103
 
102
104
  Rules:
103
105
  - No other fields beyond the table above. Extra keys are forbidden and MUST be rejected by any writer.
@@ -170,7 +172,7 @@ Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\`
170
172
  |---|---|---|
171
173
  | (default) | — | Show recent knowledge entries (tail of JSONL, pretty-printed). |
172
174
  | \`search\` | \`<query>\` | Stream-filter the JSONL for matching \`trigger\`, \`action\`, \`domain\`, \`project\`. |
173
- | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict JSONL schema (15 required fields + optional \`source\`). |
175
+ | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict JSONL schema (15 required fields + optional \`source\` / \`severity\`). |
174
176
  | \`curate\` | — | Hand off to the **knowledge-curation** skill: read-only audit + soft-archive plan when the file exceeds the curation threshold. |
175
177
  `;
176
178
  }
@@ -12,7 +12,8 @@ export declare function promptGuardScript(options?: PromptGuardOptions): string;
12
12
  export interface WorkflowGuardOptions {
13
13
  workflowGuardMode?: "advisory" | "strict";
14
14
  tddEnforcementMode?: "advisory" | "strict";
15
- tddTestGlobs?: string[];
15
+ tddTestPathPatterns?: string[];
16
+ tddProductionPathPatterns?: string[];
16
17
  }
17
18
  export declare function workflowGuardScript(options?: WorkflowGuardOptions): string;
18
19
  export declare function contextMonitorScript(): string;