cclaw-cli 0.46.14 → 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 +16 -11
- package/dist/artifact-linter.d.ts +2 -0
- package/dist/artifact-linter.js +11 -1
- package/dist/config.d.ts +12 -7
- package/dist/config.js +79 -10
- package/dist/content/compound-command.d.ts +5 -2
- package/dist/content/compound-command.js +47 -16
- package/dist/content/contracts.js +1 -1
- package/dist/content/examples.d.ts +1 -0
- package/dist/content/examples.js +13 -0
- package/dist/content/harnesses-doc.js +11 -0
- package/dist/content/learnings.d.ts +2 -1
- package/dist/content/learnings.js +5 -3
- package/dist/content/observe.d.ts +2 -1
- package/dist/content/observe.js +174 -14
- package/dist/content/stages/design.js +0 -2
- package/dist/content/utility-skills.js +1 -1
- package/dist/install.d.ts +3 -3
- package/dist/install.js +13 -8
- package/dist/knowledge-store.d.ts +3 -0
- package/dist/knowledge-store.js +11 -1
- package/dist/types.d.ts +30 -2
- package/package.json +1 -1
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
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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`
|
|
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
|
-
|
|
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
|
|
316
|
-
rules/protocols/skills
|
|
317
|
-
|
|
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;
|
package/dist/artifact-linter.js
CHANGED
|
@@ -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-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
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-
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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
|
|
84
|
+
export const DEFAULT_TDD_TEST_PATH_PATTERNS = [
|
|
85
85
|
"**/*.test.*",
|
|
86
|
-
"
|
|
87
|
-
"**/
|
|
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: [...
|
|
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
|
|
2
|
-
|
|
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
|
-
|
|
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.
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/content/examples.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
15
|
+
tddTestPathPatterns?: string[];
|
|
16
|
+
tddProductionPathPatterns?: string[];
|
|
16
17
|
}
|
|
17
18
|
export declare function workflowGuardScript(options?: WorkflowGuardOptions): string;
|
|
18
19
|
export declare function contextMonitorScript(): string;
|
package/dist/content/observe.js
CHANGED
|
@@ -156,9 +156,12 @@ exit 0
|
|
|
156
156
|
export function workflowGuardScript(options = {}) {
|
|
157
157
|
const workflowGuardMode = options.workflowGuardMode === "strict" ? "strict" : "advisory";
|
|
158
158
|
const tddEnforcementMode = options.tddEnforcementMode === "strict" ? "strict" : "advisory";
|
|
159
|
-
const
|
|
160
|
-
? options.
|
|
161
|
-
: "**/*.test
|
|
159
|
+
const tddTestPathPatterns = options.tddTestPathPatterns && options.tddTestPathPatterns.length > 0
|
|
160
|
+
? options.tddTestPathPatterns.join(",")
|
|
161
|
+
: "**/*.test.*,**/tests/**,**/__tests__/**";
|
|
162
|
+
const tddProductionPathPatterns = options.tddProductionPathPatterns && options.tddProductionPathPatterns.length > 0
|
|
163
|
+
? options.tddProductionPathPatterns.join(",")
|
|
164
|
+
: "";
|
|
162
165
|
return `#!/usr/bin/env bash
|
|
163
166
|
# cclaw workflow guard hook — generated by cclaw sync
|
|
164
167
|
# Enforces stage-aware command discipline and recent flow-state read hygiene.
|
|
@@ -166,7 +169,8 @@ set -uo pipefail
|
|
|
166
169
|
WORKFLOW_GUARD_MODE="\${CCLAW_WORKFLOW_GUARD_MODE:-${workflowGuardMode}}"
|
|
167
170
|
MAX_FLOW_READ_AGE_SEC="\${CCLAW_WORKFLOW_GUARD_MAX_AGE_SEC:-1800}"
|
|
168
171
|
TDD_ENFORCEMENT_MODE="${tddEnforcementMode}"
|
|
169
|
-
|
|
172
|
+
TDD_TEST_PATH_PATTERNS="${tddTestPathPatterns}"
|
|
173
|
+
TDD_PRODUCTION_PATH_PATTERNS="${tddProductionPathPatterns}"
|
|
170
174
|
|
|
171
175
|
${RUNTIME_SHELL_DETECT_ROOT}
|
|
172
176
|
|
|
@@ -425,34 +429,153 @@ is_preimplementation_stage() {
|
|
|
425
429
|
esac
|
|
426
430
|
}
|
|
427
431
|
|
|
432
|
+
normalize_payload_path() {
|
|
433
|
+
local raw="$1"
|
|
434
|
+
local normalized="$raw"
|
|
435
|
+
normalized=$(printf '%s' "$normalized" | tr '\\\\' '/')
|
|
436
|
+
normalized=$(printf '%s' "$normalized" | tr '[:upper:]' '[:lower:]')
|
|
437
|
+
normalized="\${normalized#./}"
|
|
438
|
+
printf '%s' "$normalized"
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
extract_payload_paths() {
|
|
442
|
+
if command -v jq >/dev/null 2>&1; then
|
|
443
|
+
printf '%s' "$INPUT" | jq -r '
|
|
444
|
+
[.. | objects | (.path?, .file_path?, .filepath?) | select(type == "string" and length > 0)]
|
|
445
|
+
| unique
|
|
446
|
+
| .[]
|
|
447
|
+
' 2>/dev/null || printf ''
|
|
448
|
+
return 0
|
|
449
|
+
fi
|
|
450
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
451
|
+
INPUT_JSON="$INPUT" python3 - <<'PY'
|
|
452
|
+
import json
|
|
453
|
+
import os
|
|
454
|
+
|
|
455
|
+
def visit(node, acc):
|
|
456
|
+
if isinstance(node, dict):
|
|
457
|
+
for key in ("path", "file_path", "filepath"):
|
|
458
|
+
value = node.get(key)
|
|
459
|
+
if isinstance(value, str) and value.strip():
|
|
460
|
+
acc.add(value.strip())
|
|
461
|
+
for value in node.values():
|
|
462
|
+
visit(value, acc)
|
|
463
|
+
elif isinstance(node, list):
|
|
464
|
+
for value in node:
|
|
465
|
+
visit(value, acc)
|
|
466
|
+
|
|
467
|
+
try:
|
|
468
|
+
payload = json.loads(os.environ.get("INPUT_JSON", "{}"))
|
|
469
|
+
except Exception:
|
|
470
|
+
payload = {}
|
|
471
|
+
|
|
472
|
+
items = set()
|
|
473
|
+
visit(payload, items)
|
|
474
|
+
for value in sorted(items):
|
|
475
|
+
print(value)
|
|
476
|
+
PY
|
|
477
|
+
return 0
|
|
478
|
+
fi
|
|
479
|
+
printf ''
|
|
480
|
+
return 0
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
matches_path_patterns() {
|
|
484
|
+
local candidate="$1"
|
|
485
|
+
local patterns_csv="$2"
|
|
486
|
+
[ -n "$candidate" ] || return 1
|
|
487
|
+
[ -n "$patterns_csv" ] || return 1
|
|
488
|
+
local old_ifs="$IFS"
|
|
489
|
+
IFS=','
|
|
490
|
+
for pattern in $patterns_csv; do
|
|
491
|
+
local normalized_pattern
|
|
492
|
+
normalized_pattern=$(normalize_payload_path "$pattern")
|
|
493
|
+
[ -n "$normalized_pattern" ] || continue
|
|
494
|
+
case "$candidate" in
|
|
495
|
+
$normalized_pattern)
|
|
496
|
+
IFS="$old_ifs"
|
|
497
|
+
return 0
|
|
498
|
+
;;
|
|
499
|
+
esac
|
|
500
|
+
done
|
|
501
|
+
IFS="$old_ifs"
|
|
502
|
+
return 1
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
is_code_like_path() {
|
|
506
|
+
local candidate="$1"
|
|
507
|
+
printf '%s' "$candidate" | grep -Eq '\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|swift)$'
|
|
508
|
+
}
|
|
509
|
+
|
|
428
510
|
is_tdd_test_payload() {
|
|
429
511
|
local text="$1"
|
|
430
|
-
|
|
431
|
-
|
|
512
|
+
local payload_paths="$2"
|
|
513
|
+
if [ -n "$payload_paths" ]; then
|
|
514
|
+
while IFS= read -r raw_path; do
|
|
515
|
+
[ -n "$raw_path" ] || continue
|
|
516
|
+
local normalized
|
|
517
|
+
normalized=$(normalize_payload_path "$raw_path")
|
|
518
|
+
if matches_path_patterns "$normalized" "$TDD_TEST_PATH_PATTERNS"; then
|
|
519
|
+
return 0
|
|
520
|
+
fi
|
|
521
|
+
done <<< "$payload_paths"
|
|
432
522
|
fi
|
|
433
|
-
if printf '%s' "$
|
|
523
|
+
if printf '%s' "$text" | grep -Eq '/tests?/|/__tests__/|\\.test\\.'; then
|
|
434
524
|
return 0
|
|
435
525
|
fi
|
|
436
526
|
return 1
|
|
437
527
|
}
|
|
438
528
|
|
|
439
|
-
|
|
529
|
+
is_tdd_production_path() {
|
|
530
|
+
local normalized="$1"
|
|
531
|
+
[ -n "$normalized" ] || return 1
|
|
532
|
+
if printf '%s' "$normalized" | grep -Eq '(^|/)\\.cclaw/'; then
|
|
533
|
+
return 1
|
|
534
|
+
fi
|
|
535
|
+
if matches_path_patterns "$normalized" "$TDD_TEST_PATH_PATTERNS"; then
|
|
536
|
+
return 1
|
|
537
|
+
fi
|
|
538
|
+
if [ -n "$TDD_PRODUCTION_PATH_PATTERNS" ]; then
|
|
539
|
+
matches_path_patterns "$normalized" "$TDD_PRODUCTION_PATH_PATTERNS"
|
|
540
|
+
return $?
|
|
541
|
+
fi
|
|
542
|
+
is_code_like_path "$normalized"
|
|
543
|
+
return $?
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
is_tdd_production_write_payload() {
|
|
440
547
|
local text="$1"
|
|
548
|
+
local payload_paths="$2"
|
|
549
|
+
if [ -n "$payload_paths" ]; then
|
|
550
|
+
while IFS= read -r raw_path; do
|
|
551
|
+
[ -n "$raw_path" ] || continue
|
|
552
|
+
local normalized
|
|
553
|
+
normalized=$(normalize_payload_path "$raw_path")
|
|
554
|
+
if is_tdd_production_path "$normalized"; then
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
done <<< "$payload_paths"
|
|
558
|
+
return 1
|
|
559
|
+
fi
|
|
560
|
+
if [ -n "$TDD_PRODUCTION_PATH_PATTERNS" ]; then
|
|
561
|
+
return 1
|
|
562
|
+
fi
|
|
441
563
|
if printf '%s' "$text" | grep -Eq '\\.cclaw/'; then
|
|
442
564
|
return 1
|
|
443
565
|
fi
|
|
444
566
|
if ! printf '%s' "$text" | grep -Eq '\\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|cs|swift)'; then
|
|
445
567
|
return 1
|
|
446
568
|
fi
|
|
447
|
-
if is_tdd_test_payload "$text"; then
|
|
569
|
+
if is_tdd_test_payload "$text" ""; then
|
|
448
570
|
return 1
|
|
449
571
|
fi
|
|
450
572
|
return 0
|
|
451
573
|
}
|
|
452
574
|
|
|
453
|
-
|
|
575
|
+
tdd_cycle_counts() {
|
|
454
576
|
if [ ! -f "$TDD_LOG_FILE" ] || [ ! -s "$TDD_LOG_FILE" ]; then
|
|
455
|
-
|
|
577
|
+
printf '0:0'
|
|
578
|
+
return 0
|
|
456
579
|
fi
|
|
457
580
|
local red_count="0"
|
|
458
581
|
local green_count="0"
|
|
@@ -512,12 +635,37 @@ PY
|
|
|
512
635
|
fi
|
|
513
636
|
[ -n "$red_count" ] || red_count="0"
|
|
514
637
|
[ -n "$green_count" ] || green_count="0"
|
|
638
|
+
printf '%s:%s' "$red_count" "$green_count"
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
has_open_red_cycle() {
|
|
642
|
+
local counts
|
|
643
|
+
counts=$(tdd_cycle_counts)
|
|
644
|
+
local red_count="\${counts%%:*}"
|
|
645
|
+
local green_count="\${counts##*:}"
|
|
515
646
|
if [ "$red_count" -gt "$green_count" ]; then
|
|
516
647
|
return 0
|
|
517
648
|
fi
|
|
518
649
|
return 1
|
|
519
650
|
}
|
|
520
651
|
|
|
652
|
+
tdd_cycle_state() {
|
|
653
|
+
local counts
|
|
654
|
+
counts=$(tdd_cycle_counts)
|
|
655
|
+
local red_count="\${counts%%:*}"
|
|
656
|
+
local green_count="\${counts##*:}"
|
|
657
|
+
if [ "$red_count" -le 0 ]; then
|
|
658
|
+
printf 'need_red'
|
|
659
|
+
return 0
|
|
660
|
+
fi
|
|
661
|
+
if [ "$red_count" -gt "$green_count" ]; then
|
|
662
|
+
printf 'red_open'
|
|
663
|
+
return 0
|
|
664
|
+
fi
|
|
665
|
+
printf 'green_done'
|
|
666
|
+
return 0
|
|
667
|
+
}
|
|
668
|
+
|
|
521
669
|
detect_target_stage() {
|
|
522
670
|
local text="$1"
|
|
523
671
|
for stage in brainstorm scope design spec plan tdd review ship; do
|
|
@@ -546,6 +694,11 @@ FLOW_COMMAND_INVOKED=0
|
|
|
546
694
|
if is_flow_progression_command "$PAYLOAD_LOWER"; then
|
|
547
695
|
FLOW_COMMAND_INVOKED=1
|
|
548
696
|
fi
|
|
697
|
+
MUTATION_PATHS=""
|
|
698
|
+
if is_mutating_tool "$TOOL_LOWER"; then
|
|
699
|
+
MUTATION_PATHS=$(extract_payload_paths)
|
|
700
|
+
fi
|
|
701
|
+
TDD_CYCLE_STATE="unknown"
|
|
549
702
|
if [ -n "$TARGET_STAGE" ] && [ "$CURRENT_STAGE" != "none" ]; then
|
|
550
703
|
CURRENT_IDX=$(stage_index "$CURRENT_STAGE")
|
|
551
704
|
TARGET_IDX=$(stage_index "$TARGET_STAGE")
|
|
@@ -583,8 +736,13 @@ if is_preimplementation_stage "$CURRENT_STAGE" && is_mutating_tool "$TOOL_LOWER"
|
|
|
583
736
|
fi
|
|
584
737
|
|
|
585
738
|
if [ "$CURRENT_STAGE" = "tdd" ] && is_mutating_tool "$TOOL_LOWER"; then
|
|
586
|
-
if
|
|
587
|
-
if
|
|
739
|
+
if is_tdd_production_write_payload "$PAYLOAD_LOWER" "$MUTATION_PATHS"; then
|
|
740
|
+
if has_open_red_cycle; then
|
|
741
|
+
TDD_CYCLE_STATE="red_open"
|
|
742
|
+
else
|
|
743
|
+
TDD_CYCLE_STATE=$(tdd_cycle_state)
|
|
744
|
+
fi
|
|
745
|
+
if [ "$TDD_CYCLE_STATE" = "need_red" ]; then
|
|
588
746
|
if [ -n "$REASONS" ]; then
|
|
589
747
|
REASONS="$REASONS,tdd_write_without_open_red"
|
|
590
748
|
else
|
|
@@ -659,7 +817,9 @@ PY
|
|
|
659
817
|
fi
|
|
660
818
|
|
|
661
819
|
if [ -n "$REASONS" ]; then
|
|
662
|
-
if printf '%s' "$REASONS" | grep -Eq '
|
|
820
|
+
if printf '%s' "$REASONS" | grep -Eq 'tdd_write_without_open_red'; then
|
|
821
|
+
NOTE="Cclaw workflow guard: Write a failing test first before editing production files during tdd stage (state=\${TDD_CYCLE_STATE})."
|
|
822
|
+
elif printf '%s' "$REASONS" | grep -Eq 'direct_flow_state_edit'; then
|
|
663
823
|
NOTE="Cclaw workflow guard: direct flow-state edit bypasses the canonical stage-complete helper (\${REASONS}). Prefer: bash ${RUNTIME_ROOT}/hooks/stage-complete.sh <stage>. In strict mode this is blocked."
|
|
664
824
|
else
|
|
665
825
|
NOTE="Cclaw workflow guard: detected potential flow violation (\${REASONS}). Re-read ${RUNTIME_ROOT}/state/flow-state.json, avoid source edits before tdd stage, and enforce RED -> GREEN -> REFACTOR discipline inside tdd."
|
|
@@ -118,9 +118,7 @@ export const DESIGN = {
|
|
|
118
118
|
"No performance budget for critical path",
|
|
119
119
|
"Batching multiple design issues into one question",
|
|
120
120
|
"Agreeing with user's architecture choice without evaluating alternatives",
|
|
121
|
-
"Hedging every recommendation with 'it depends' instead of taking a position",
|
|
122
121
|
"No NOT-in-scope output section",
|
|
123
|
-
"No What-already-exists output section",
|
|
124
122
|
"Design decisions made without reading the actual code first"
|
|
125
123
|
],
|
|
126
124
|
policyNeedles: [
|
|
@@ -1469,7 +1469,7 @@ For each lens, write either a knowledge entry **or** the explicit string
|
|
|
1469
1469
|
## Output protocol
|
|
1470
1470
|
|
|
1471
1471
|
For every harvested insight, append one strict-schema JSON line to
|
|
1472
|
-
\`.cclaw/knowledge.jsonl\` (fields: \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`).
|
|
1472
|
+
\`.cclaw/knowledge.jsonl\` (fields: \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`; optional: \`source\`, \`severity\`).
|
|
1473
1473
|
See the \`learnings\` skill for the canonical shape. Choose \`type\`:
|
|
1474
1474
|
|
|
1475
1475
|
- \`compound\` for process/speed accelerators.
|
package/dist/install.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export declare function syncCclaw(projectRoot: string): Promise<void>;
|
|
|
12
12
|
* stamps are rewritten so the on-disk config reflects the installed CLI.
|
|
13
13
|
*
|
|
14
14
|
* Shape preservation: if the user previously hand-authored advanced keys
|
|
15
|
-
* (e.g. `
|
|
16
|
-
* yaml. If their existing config is minimal, the upgrade keeps it
|
|
17
|
-
* advanced knobs are never silently added.
|
|
15
|
+
* (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
|
|
16
|
+
* the yaml. If their existing config is minimal, the upgrade keeps it
|
|
17
|
+
* minimal — advanced knobs are never silently added.
|
|
18
18
|
*/
|
|
19
19
|
export declare function upgradeCclaw(projectRoot: string): Promise<void>;
|
|
20
20
|
export declare function uninstallCclaw(projectRoot: string): Promise<void>;
|
package/dist/install.js
CHANGED
|
@@ -245,7 +245,9 @@ async function writeSkills(projectRoot, config) {
|
|
|
245
245
|
await writeFileSafe(runtimePath(projectRoot, "skills", "using-git-worktrees", "SKILL.md"), featureCommandSkillMarkdown());
|
|
246
246
|
await writeFileSafe(runtimePath(projectRoot, "skills", "tdd-cycle-log", "SKILL.md"), tddLogCommandSkillMarkdown());
|
|
247
247
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-retro", "SKILL.md"), retroCommandSkillMarkdown());
|
|
248
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-compound", "SKILL.md"), compoundCommandSkillMarkdown(
|
|
248
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-compound", "SKILL.md"), compoundCommandSkillMarkdown({
|
|
249
|
+
recurrenceThreshold: config?.compound?.recurrenceThreshold
|
|
250
|
+
}));
|
|
249
251
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-rewind", "SKILL.md"), rewindCommandSkillMarkdown());
|
|
250
252
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-archive", "SKILL.md"), archiveCommandSkillMarkdown());
|
|
251
253
|
await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
|
|
@@ -306,7 +308,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
306
308
|
await writeFileSafe(runtimePath(projectRoot, ...playbookDirSegments, harnessPlaybookFileName(harness)), harnessPlaybookMarkdown(harness));
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
|
-
async function writeUtilityCommands(projectRoot) {
|
|
311
|
+
async function writeUtilityCommands(projectRoot, config) {
|
|
310
312
|
await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
|
|
311
313
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
312
314
|
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
@@ -319,7 +321,9 @@ async function writeUtilityCommands(projectRoot) {
|
|
|
319
321
|
await writeFileSafe(runtimePath(projectRoot, "commands", "feature.md"), featureCommandContract());
|
|
320
322
|
await writeFileSafe(runtimePath(projectRoot, "commands", "tdd-log.md"), tddLogCommandContract());
|
|
321
323
|
await writeFileSafe(runtimePath(projectRoot, "commands", "retro.md"), retroCommandContract());
|
|
322
|
-
await writeFileSafe(runtimePath(projectRoot, "commands", "compound.md"), compoundCommandContract(
|
|
324
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "compound.md"), compoundCommandContract({
|
|
325
|
+
recurrenceThreshold: config.compound?.recurrenceThreshold
|
|
326
|
+
}));
|
|
323
327
|
await writeFileSafe(runtimePath(projectRoot, "commands", "archive.md"), archiveCommandContract());
|
|
324
328
|
await writeFileSafe(runtimePath(projectRoot, "commands", "rewind.md"), rewindCommandContract());
|
|
325
329
|
}
|
|
@@ -627,7 +631,8 @@ async function writeHooks(projectRoot, config) {
|
|
|
627
631
|
await writeFileSafe(path.join(hooksDir, "workflow-guard.sh"), workflowGuardScript({
|
|
628
632
|
workflowGuardMode: config.strictness ?? "advisory",
|
|
629
633
|
tddEnforcementMode: config.tddEnforcement ?? "advisory",
|
|
630
|
-
|
|
634
|
+
tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
|
|
635
|
+
tddProductionPathPatterns: config.tdd?.productionPathPatterns
|
|
631
636
|
}));
|
|
632
637
|
await writeFileSafe(path.join(hooksDir, "context-monitor.sh"), contextMonitorScript());
|
|
633
638
|
const opencodePluginSource = opencodePluginJs();
|
|
@@ -1146,7 +1151,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
|
|
|
1146
1151
|
await cleanLegacyArtifacts(projectRoot);
|
|
1147
1152
|
await cleanStaleFiles(projectRoot);
|
|
1148
1153
|
await writeCommandContracts(projectRoot);
|
|
1149
|
-
await writeUtilityCommands(projectRoot);
|
|
1154
|
+
await writeUtilityCommands(projectRoot, config);
|
|
1150
1155
|
await writeSkills(projectRoot, config);
|
|
1151
1156
|
await writeContextModes(projectRoot);
|
|
1152
1157
|
await writeArtifactTemplates(projectRoot);
|
|
@@ -1195,9 +1200,9 @@ export async function syncCclaw(projectRoot) {
|
|
|
1195
1200
|
* stamps are rewritten so the on-disk config reflects the installed CLI.
|
|
1196
1201
|
*
|
|
1197
1202
|
* Shape preservation: if the user previously hand-authored advanced keys
|
|
1198
|
-
* (e.g. `
|
|
1199
|
-
* yaml. If their existing config is minimal, the upgrade keeps it
|
|
1200
|
-
* advanced knobs are never silently added.
|
|
1203
|
+
* (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
|
|
1204
|
+
* the yaml. If their existing config is minimal, the upgrade keeps it
|
|
1205
|
+
* minimal — advanced knobs are never silently added.
|
|
1201
1206
|
*/
|
|
1202
1207
|
export async function upgradeCclaw(projectRoot) {
|
|
1203
1208
|
const advancedKeysPresent = await detectAdvancedKeys(projectRoot);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type FlowStage } from "./types.js";
|
|
2
2
|
export type KnowledgeEntryType = "rule" | "pattern" | "lesson" | "compound";
|
|
3
3
|
export type KnowledgeEntryConfidence = "high" | "medium" | "low";
|
|
4
|
+
export type KnowledgeEntrySeverity = "critical" | "important" | "suggestion";
|
|
4
5
|
export type KnowledgeEntryUniversality = "project" | "personal" | "universal";
|
|
5
6
|
export type KnowledgeEntryMaturity = "raw" | "lifted-to-rule" | "lifted-to-enforcement";
|
|
6
7
|
export type KnowledgeEntrySource = "stage" | "retro" | "compound" | "ideate" | "manual";
|
|
@@ -9,6 +10,7 @@ export interface KnowledgeEntry {
|
|
|
9
10
|
trigger: string;
|
|
10
11
|
action: string;
|
|
11
12
|
confidence: KnowledgeEntryConfidence;
|
|
13
|
+
severity?: KnowledgeEntrySeverity;
|
|
12
14
|
domain: string | null;
|
|
13
15
|
stage: FlowStage | null;
|
|
14
16
|
origin_stage: FlowStage | null;
|
|
@@ -27,6 +29,7 @@ export interface KnowledgeSeedEntry {
|
|
|
27
29
|
trigger: string;
|
|
28
30
|
action: string;
|
|
29
31
|
confidence: KnowledgeEntryConfidence;
|
|
32
|
+
severity?: KnowledgeEntrySeverity;
|
|
30
33
|
domain?: string | null;
|
|
31
34
|
stage?: FlowStage | null;
|
|
32
35
|
origin_stage?: FlowStage | null;
|
package/dist/knowledge-store.js
CHANGED
|
@@ -5,6 +5,7 @@ import { withDirectoryLock } from "./fs-utils.js";
|
|
|
5
5
|
import { FLOW_STAGES } from "./types.js";
|
|
6
6
|
const KNOWLEDGE_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
|
|
7
7
|
const KNOWLEDGE_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
|
|
8
|
+
const KNOWLEDGE_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
|
|
8
9
|
const KNOWLEDGE_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
|
|
9
10
|
const KNOWLEDGE_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
|
|
10
11
|
const KNOWLEDGE_SOURCE_SET = new Set([
|
|
@@ -34,6 +35,7 @@ const KNOWLEDGE_REQUIRED_KEYS = [
|
|
|
34
35
|
];
|
|
35
36
|
const KNOWLEDGE_ALLOWED_KEYS = new Set(KNOWLEDGE_REQUIRED_KEYS);
|
|
36
37
|
KNOWLEDGE_ALLOWED_KEYS.add("source");
|
|
38
|
+
KNOWLEDGE_ALLOWED_KEYS.add("severity");
|
|
37
39
|
function knowledgePath(projectRoot) {
|
|
38
40
|
return path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
|
|
39
41
|
}
|
|
@@ -60,7 +62,8 @@ function dedupeKey(entry) {
|
|
|
60
62
|
entry.origin_feature === null ? "null" : normalizeText(entry.origin_feature),
|
|
61
63
|
entry.universality,
|
|
62
64
|
entry.project === null ? "null" : normalizeText(entry.project),
|
|
63
|
-
entry.source === undefined || entry.source === null ? "null" : entry.source
|
|
65
|
+
entry.source === undefined || entry.source === null ? "null" : entry.source,
|
|
66
|
+
entry.severity === undefined ? "none" : entry.severity
|
|
64
67
|
].join("|");
|
|
65
68
|
}
|
|
66
69
|
function isIsoUtcTimestamp(value) {
|
|
@@ -100,6 +103,10 @@ export function validateKnowledgeEntry(entry) {
|
|
|
100
103
|
if (!KNOWLEDGE_CONFIDENCE_SET.has(obj.confidence)) {
|
|
101
104
|
errors.push("confidence must be one of: high, medium, low.");
|
|
102
105
|
}
|
|
106
|
+
if (obj.severity !== undefined &&
|
|
107
|
+
(typeof obj.severity !== "string" || !KNOWLEDGE_SEVERITY_SET.has(obj.severity))) {
|
|
108
|
+
errors.push("severity must be one of: critical, important, suggestion.");
|
|
109
|
+
}
|
|
103
110
|
if (!isNullableString(obj.domain)) {
|
|
104
111
|
errors.push("domain must be string or null.");
|
|
105
112
|
}
|
|
@@ -161,6 +168,9 @@ export function materializeKnowledgeEntry(seed, defaults = {}) {
|
|
|
161
168
|
last_seen_ts: normalizeUtcIso(seed.last_seen_ts ?? now),
|
|
162
169
|
project: seed.project ?? defaults.project ?? null
|
|
163
170
|
};
|
|
171
|
+
if (seed.severity !== undefined) {
|
|
172
|
+
entry.severity = seed.severity;
|
|
173
|
+
}
|
|
164
174
|
if (source !== null) {
|
|
165
175
|
entry.source = source;
|
|
166
176
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -88,6 +88,27 @@ export interface SliceReviewConfig {
|
|
|
88
88
|
/** Tracks on which missed reviews escalate to a doctor warning. */
|
|
89
89
|
enforceOnTracks?: FlowTrack[];
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* File-path routing hints used by workflow-guard during `tdd` stage.
|
|
93
|
+
*
|
|
94
|
+
* - `testPathPatterns`: paths considered test-side changes (RED writes).
|
|
95
|
+
* - `productionPathPatterns`: optional allowlist for production paths that
|
|
96
|
+
* participate in GREEN/REFACTOR checks. When omitted, workflow-guard treats
|
|
97
|
+
* non-test code files as production writes.
|
|
98
|
+
*/
|
|
99
|
+
export interface TddPathConfig {
|
|
100
|
+
testPathPatterns?: string[];
|
|
101
|
+
productionPathPatterns?: string[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Compound-stage clustering policy.
|
|
105
|
+
*
|
|
106
|
+
* `recurrenceThreshold` is the base minimum repeat count for a trigger/action
|
|
107
|
+
* cluster before it is eligible for promotion into durable rules/skills.
|
|
108
|
+
*/
|
|
109
|
+
export interface CompoundConfig {
|
|
110
|
+
recurrenceThreshold?: number;
|
|
111
|
+
}
|
|
91
112
|
export interface VibyConfig {
|
|
92
113
|
version: string;
|
|
93
114
|
flowVersion: string;
|
|
@@ -112,13 +133,20 @@ export interface VibyConfig {
|
|
|
112
133
|
*/
|
|
113
134
|
promptGuardMode?: "advisory" | "strict";
|
|
114
135
|
/**
|
|
115
|
-
* TDD
|
|
136
|
+
* TDD RED -> GREEN -> REFACTOR enforcement mode used by workflow guard hooks.
|
|
116
137
|
*
|
|
117
138
|
* Since v0.43.0 this is an advanced override — see `strictness`.
|
|
118
139
|
*/
|
|
119
140
|
tddEnforcement?: "advisory" | "strict";
|
|
120
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Legacy alias for test-side path detection in workflow-guard.
|
|
143
|
+
* Prefer `tdd.testPathPatterns` in new configs.
|
|
144
|
+
*/
|
|
121
145
|
tddTestGlobs?: string[];
|
|
146
|
+
/** Path-pattern routing for TDD test/production write classification. */
|
|
147
|
+
tdd?: TddPathConfig;
|
|
148
|
+
/** Compound-stage recurrence policy overrides. */
|
|
149
|
+
compound?: CompoundConfig;
|
|
122
150
|
/** When true, cclaw installs managed git pre-commit/pre-push wrappers. */
|
|
123
151
|
gitHookGuards?: boolean;
|
|
124
152
|
/** Default flow track for new runs (quick = shortened path, standard = full pipeline). */
|