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 +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/research-playbooks.js +36 -0
- package/dist/content/stage-schema.js +8 -1
- package/dist/content/stages/design.js +14 -7
- package/dist/content/templates.js +49 -0
- package/dist/content/utility-skills.js +1 -1
- package/dist/gate-evidence.js +56 -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;
|