cclaw-cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import type { FlowTrack, HarnessId } from "./types.js";
2
+ import type { FlowTrack, HarnessId, InitProfile } from "./types.js";
3
3
  type CommandName = "init" | "sync" | "doctor" | "upgrade" | "uninstall" | "archive";
4
4
  interface ParsedArgs {
5
5
  command?: CommandName;
6
6
  harnesses?: HarnessId[];
7
7
  track?: FlowTrack;
8
+ profile?: InitProfile;
8
9
  reconcileGates?: boolean;
9
10
  archiveName?: string;
10
11
  showHelp?: boolean;
@@ -13,5 +14,6 @@ interface ParsedArgs {
13
14
  export declare function usage(): string;
14
15
  declare function parseHarnesses(raw: string): HarnessId[];
15
16
  declare function parseTrack(raw: string): FlowTrack;
17
+ declare function parseProfile(raw: string): InitProfile;
16
18
  declare function parseArgs(argv: string[]): ParsedArgs;
17
- export { parseArgs, parseHarnesses, parseTrack };
19
+ export { parseArgs, parseHarnesses, parseTrack, parseProfile };
package/dist/cli.js CHANGED
@@ -3,11 +3,12 @@ import { readFileSync, realpathSync } from "node:fs";
3
3
  import process from "node:process";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { FLOW_TRACKS, HARNESS_IDS } from "./types.js";
6
+ import { FLOW_TRACKS, HARNESS_IDS, INIT_PROFILES } from "./types.js";
7
7
  import { doctorChecks, doctorSucceeded } from "./doctor.js";
8
8
  import { initCclaw, syncCclaw, uninstallCclaw, upgradeCclaw } from "./install.js";
9
9
  import { error, info } from "./logger.js";
10
10
  import { archiveRun } from "./runs.js";
11
+ import { RUNTIME_ROOT } from "./constants.js";
11
12
  const INSTALLER_COMMANDS = ["init", "sync", "doctor", "upgrade", "uninstall", "archive"];
12
13
  export function usage() {
13
14
  return `cclaw - installer-first flow toolkit
@@ -19,8 +20,9 @@ Usage:
19
20
 
20
21
  Commands:
21
22
  init Bootstrap .cclaw runtime, state, and harness shims in this project.
22
- Flags: --harnesses=<list> Comma list of harnesses (claude,cursor,opencode,codex).
23
- --track=<id> Flow track for new runs (standard | quick). Default: standard.
23
+ Flags: --profile=<id> Pre-fill defaults. One of: minimal | standard | full. Default: standard.
24
+ --harnesses=<list> Comma list of harnesses (claude,cursor,opencode,codex). Overrides the profile default.
25
+ --track=<id> Flow track for new runs (standard | quick). Overrides the profile default.
24
26
  sync Regenerate harness shim files from the current .cclaw config (non-destructive).
25
27
  doctor Run health checks against the local .cclaw runtime. Exit code 2 on failure.
26
28
  Flags: --reconcile-gates Recompute current-stage gate evidence before checks.
@@ -85,6 +87,13 @@ function parseTrack(raw) {
85
87
  }
86
88
  return trimmed;
87
89
  }
90
+ function parseProfile(raw) {
91
+ const trimmed = raw.trim();
92
+ if (!INIT_PROFILES.includes(trimmed)) {
93
+ throw new Error(`Unknown profile: ${trimmed}. Supported: ${INIT_PROFILES.join(", ")}`);
94
+ }
95
+ return trimmed;
96
+ }
88
97
  function parseArgs(argv) {
89
98
  const parsed = {};
90
99
  const helpFlag = argv.find((arg) => arg === "--help" || arg === "-h");
@@ -108,6 +117,10 @@ function parseArgs(argv) {
108
117
  parsed.track = parseTrack(flag.replace("--track=", ""));
109
118
  continue;
110
119
  }
120
+ if (flag.startsWith("--profile=")) {
121
+ parsed.profile = parseProfile(flag.replace("--profile=", ""));
122
+ continue;
123
+ }
111
124
  if (flag === "--reconcile-gates") {
112
125
  parsed.reconcileGates = true;
113
126
  continue;
@@ -136,10 +149,13 @@ async function runCommand(parsed, ctx) {
136
149
  await initCclaw({
137
150
  projectRoot: ctx.cwd,
138
151
  harnesses: parsed.harnesses,
139
- track: parsed.track
152
+ track: parsed.track,
153
+ profile: parsed.profile
140
154
  });
141
- const trackNote = parsed.track ? ` (track: ${parsed.track})` : "";
142
- info(ctx, `Initialized .cclaw runtime and generated harness shims${trackNote}`);
155
+ const profileNote = parsed.profile ? ` profile=${parsed.profile}` : "";
156
+ const trackNote = parsed.track ? ` track=${parsed.track}` : "";
157
+ const suffix = profileNote || trackNote ? ` (${(profileNote + trackNote).trim()})` : "";
158
+ info(ctx, `Initialized .cclaw runtime and generated harness shims${suffix}`);
143
159
  return 0;
144
160
  }
145
161
  if (command === "sync") {
@@ -167,6 +183,16 @@ async function runCommand(parsed, ctx) {
167
183
  ? ` Snapshotted ${archived.snapshottedStateFiles.length} state file(s) under ${archived.archivePath}/state and wrote archive-manifest.json.`
168
184
  : "";
169
185
  info(ctx, `Archived active artifacts to ${archived.archivePath}. Flow state reset to brainstorm.${snapshotSummary}`);
186
+ const k = archived.knowledge;
187
+ if (k.overThreshold) {
188
+ info(ctx, `Knowledge curation recommended: ${k.knowledgePath} now has ${k.activeEntryCount} active entries (soft threshold ${k.softThreshold}). Run \`/cc-learn curate\` to plan a soft-archive of stale/duplicate entries to ${RUNTIME_ROOT}/knowledge.archive.md.`);
189
+ }
190
+ else if (k.activeEntryCount > 0) {
191
+ info(ctx, `Knowledge: ${k.activeEntryCount}/${k.softThreshold} active entries. Run \`/cc-learn curate\` if you want a sweep before the next run.`);
192
+ }
193
+ else {
194
+ info(ctx, `Knowledge: 0 active entries in ${k.knowledgePath}. Capture lessons from this run with \`/cc-learn add\` before they fade.`);
195
+ }
170
196
  return 0;
171
197
  }
172
198
  await uninstallCclaw(ctx.cwd);
@@ -204,4 +230,4 @@ function isDirectExecution() {
204
230
  if (isDirectExecution()) {
205
231
  void main();
206
232
  }
207
- export { parseArgs, parseHarnesses, parseTrack };
233
+ export { parseArgs, parseHarnesses, parseTrack, parseProfile };
package/dist/config.d.ts CHANGED
@@ -1,5 +1,15 @@
1
- import type { FlowTrack, HarnessId, VibyConfig } from "./types.js";
1
+ import type { FlowTrack, HarnessId, InitProfile, LanguageRulePack, VibyConfig } from "./types.js";
2
2
  export declare function configPath(projectRoot: string): string;
3
3
  export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrack?: FlowTrack): VibyConfig;
4
+ /**
5
+ * Build a VibyConfig for a named init profile. Profile defaults are applied
6
+ * first, then any explicit overrides (CLI flags) win. This keeps the profile
7
+ * contract deterministic and testable.
8
+ */
9
+ export declare function createProfileConfig(profile: InitProfile, overrides?: {
10
+ harnesses?: HarnessId[];
11
+ defaultTrack?: FlowTrack;
12
+ languageRulePacks?: LanguageRulePack[];
13
+ }): VibyConfig;
4
14
  export declare function readConfig(projectRoot: string): Promise<VibyConfig>;
5
15
  export declare function writeConfig(projectRoot: string, config: VibyConfig): Promise<void>;
package/dist/config.js CHANGED
@@ -3,12 +3,14 @@ import path from "node:path";
3
3
  import { parse, stringify } from "yaml";
4
4
  import { CCLAW_VERSION, DEFAULT_HARNESSES, FLOW_VERSION, RUNTIME_ROOT } from "./constants.js";
5
5
  import { exists, writeFileSafe } from "./fs-utils.js";
6
- import { FLOW_TRACKS, HARNESS_IDS } from "./types.js";
6
+ import { FLOW_TRACKS, HARNESS_IDS, LANGUAGE_RULE_PACKS } from "./types.js";
7
7
  const CONFIG_PATH = `${RUNTIME_ROOT}/config.yaml`;
8
8
  const HARNESS_ID_SET = new Set(HARNESS_IDS);
9
9
  const FLOW_TRACK_SET = new Set(FLOW_TRACKS);
10
+ const LANGUAGE_RULE_PACK_SET = new Set(LANGUAGE_RULE_PACKS);
10
11
  const SUPPORTED_HARNESSES_TEXT = HARNESS_IDS.join(", ");
11
12
  const SUPPORTED_TRACKS_TEXT = FLOW_TRACKS.join(", ");
13
+ const SUPPORTED_LANGUAGE_RULE_PACKS_TEXT = LANGUAGE_RULE_PACKS.join(", ");
12
14
  const ALLOWED_CONFIG_KEYS = new Set([
13
15
  "version",
14
16
  "flowVersion",
@@ -16,7 +18,8 @@ const ALLOWED_CONFIG_KEYS = new Set([
16
18
  "autoAdvance",
17
19
  "promptGuardMode",
18
20
  "gitHookGuards",
19
- "defaultTrack"
21
+ "defaultTrack",
22
+ "languageRulePacks"
20
23
  ]);
21
24
  function configFixExample() {
22
25
  return `harnesses:
@@ -27,6 +30,7 @@ function configValidationError(configFilePath, reason) {
27
30
  return new Error(`Invalid cclaw config at ${configFilePath}: ${reason}\n` +
28
31
  `Supported harnesses: ${SUPPORTED_HARNESSES_TEXT}\n` +
29
32
  `Supported tracks: ${SUPPORTED_TRACKS_TEXT}\n` +
33
+ `Supported languageRulePacks: ${SUPPORTED_LANGUAGE_RULE_PACKS_TEXT}\n` +
30
34
  `Example config:\n${configFixExample()}\n` +
31
35
  `After fixing, run: cclaw sync`);
32
36
  }
@@ -41,9 +45,50 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
41
45
  autoAdvance: false,
42
46
  promptGuardMode: "advisory",
43
47
  gitHookGuards: false,
44
- defaultTrack
48
+ defaultTrack,
49
+ languageRulePacks: []
45
50
  };
46
51
  }
52
+ /**
53
+ * Build a VibyConfig for a named init profile. Profile defaults are applied
54
+ * first, then any explicit overrides (CLI flags) win. This keeps the profile
55
+ * contract deterministic and testable.
56
+ */
57
+ export function createProfileConfig(profile, overrides = {}) {
58
+ const base = createDefaultConfig();
59
+ switch (profile) {
60
+ case "minimal":
61
+ return {
62
+ ...base,
63
+ harnesses: overrides.harnesses ?? ["claude"],
64
+ autoAdvance: false,
65
+ promptGuardMode: "advisory",
66
+ gitHookGuards: false,
67
+ defaultTrack: overrides.defaultTrack ?? "quick",
68
+ languageRulePacks: overrides.languageRulePacks ?? []
69
+ };
70
+ case "standard":
71
+ return {
72
+ ...base,
73
+ harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
74
+ autoAdvance: false,
75
+ promptGuardMode: "advisory",
76
+ gitHookGuards: false,
77
+ defaultTrack: overrides.defaultTrack ?? "standard",
78
+ languageRulePacks: overrides.languageRulePacks ?? []
79
+ };
80
+ case "full":
81
+ return {
82
+ ...base,
83
+ harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
84
+ autoAdvance: false,
85
+ promptGuardMode: "strict",
86
+ gitHookGuards: true,
87
+ defaultTrack: overrides.defaultTrack ?? "standard",
88
+ languageRulePacks: overrides.languageRulePacks ?? [...LANGUAGE_RULE_PACKS]
89
+ };
90
+ }
91
+ }
47
92
  export async function readConfig(projectRoot) {
48
93
  const fullPath = configPath(projectRoot);
49
94
  if (!(await exists(fullPath))) {
@@ -102,6 +147,20 @@ export async function readConfig(projectRoot) {
102
147
  const defaultTrack = typeof defaultTrackRaw === "string" && FLOW_TRACK_SET.has(defaultTrackRaw)
103
148
  ? defaultTrackRaw
104
149
  : "standard";
150
+ const languageRulePacksRaw = parsed.languageRulePacks;
151
+ const hasLanguageRulePacksField = Object.prototype.hasOwnProperty.call(parsed, "languageRulePacks");
152
+ if (hasLanguageRulePacksField && !Array.isArray(languageRulePacksRaw)) {
153
+ throw configValidationError(fullPath, `"languageRulePacks" must be an array`);
154
+ }
155
+ const rawPacks = (languageRulePacksRaw ?? []);
156
+ const invalidPacks = rawPacks.filter((pack) => typeof pack !== "string" || !LANGUAGE_RULE_PACK_SET.has(pack));
157
+ if (invalidPacks.length > 0) {
158
+ const formatted = invalidPacks
159
+ .map((item) => (typeof item === "string" ? item : JSON.stringify(item)))
160
+ .join(", ");
161
+ throw configValidationError(fullPath, `unknown languageRulePacks id(s): ${formatted}`);
162
+ }
163
+ const languageRulePacks = [...new Set(rawPacks)];
105
164
  return {
106
165
  version: parsed.version ?? CCLAW_VERSION,
107
166
  flowVersion: parsed.flowVersion ?? FLOW_VERSION,
@@ -109,7 +168,8 @@ export async function readConfig(projectRoot) {
109
168
  autoAdvance,
110
169
  promptGuardMode,
111
170
  gitHookGuards,
112
- defaultTrack
171
+ defaultTrack,
172
+ languageRulePacks
113
173
  };
114
174
  }
115
175
  export async function writeConfig(projectRoot, config) {
@@ -2,8 +2,9 @@
2
2
  // Knowledge store content for /cc-learn and stage self-improvement prompts.
3
3
  // ---------------------------------------------------------------------------
4
4
  const KNOWLEDGE_PATH = ".cclaw/knowledge.md";
5
+ const KNOWLEDGE_JSONL_PATH = ".cclaw/knowledge.jsonl";
5
6
  const LEARN_SKILL_NAME = "learnings";
6
- const LEARN_SKILL_DESCRIPTION = "Project-scoped knowledge store: review and append rule/pattern/lesson entries in .cclaw/knowledge.md.";
7
+ const LEARN_SKILL_DESCRIPTION = "Project-scoped knowledge store: review and append rule/pattern/lesson/compound entries. Maintains a human-readable markdown mirror at .cclaw/knowledge.md and a canonical JSONL store at .cclaw/knowledge.jsonl.";
7
8
  export function learnSkillMarkdown() {
8
9
  return `---
9
10
  name: ${LEARN_SKILL_NAME}
@@ -14,9 +15,14 @@ description: "${LEARN_SKILL_DESCRIPTION}"
14
15
 
15
16
  ## Overview
16
17
 
17
- This skill manages the append-only project knowledge file at \`${KNOWLEDGE_PATH}\`.
18
+ This skill manages the project knowledge store. The store has **two mirrored formats**:
18
19
 
19
- Use it to keep durable knowledge that should survive sessions:
20
+ - \`${KNOWLEDGE_PATH}\` human-readable, append-only markdown (the reading view).
21
+ - \`${KNOWLEDGE_JSONL_PATH}\` — canonical, machine-queryable JSONL (one JSON object per line). Used by the curator, /cc-status, and future analytics.
22
+
23
+ Every \`/cc-learn add\` appends to **both** files. \`/cc-learn search\` prefers the JSONL store if it exists; otherwise it falls back to the markdown file.
24
+
25
+ Use the store to keep durable knowledge that should survive sessions:
20
26
  - **rule**: hard constraint to follow every time
21
27
  - **pattern**: repeatable way that works well in this project
22
28
  - **lesson**: non-obvious outcome from a failure or trade-off
@@ -24,9 +30,9 @@ Use it to keep durable knowledge that should survive sessions:
24
30
 
25
31
  ## HARD-GATE
26
32
 
27
- Under \`/cc-learn\`, only modify the knowledge store (\`${KNOWLEDGE_PATH}\`) or an explicitly user-approved summary file. Do not modify application code here.
33
+ Under \`/cc-learn\`, only modify the knowledge store files (\`${KNOWLEDGE_PATH}\` and \`${KNOWLEDGE_JSONL_PATH}\`) or an explicitly user-approved summary file. Do not modify application code here.
28
34
 
29
- ## Entry format (append-only)
35
+ ## Entry format — markdown mirror (append-only)
30
36
 
31
37
  \`\`\`markdown
32
38
  ### 2026-04-14T12:00:00Z [pattern] short-title
@@ -39,12 +45,49 @@ Under \`/cc-learn\`, only modify the knowledge store (\`${KNOWLEDGE_PATH}\`) or
39
45
  - Project: <repo or scope name> (optional)
40
46
  \`\`\`
41
47
 
48
+ ## Entry format — canonical JSONL (one entry per line)
49
+
50
+ \`\`\`json
51
+ {"type":"pattern","title":"short-title","stage":"design","context":"one short line","insight":"one short line","reuse":"one short line","created":"2026-04-14T12:00:00Z","confidence":"high","domain":"api","project":"cclaw","supersedes":null,"superseded":false,"archived":false}
52
+ \`\`\`
53
+
54
+ Schema:
55
+
56
+ | field | type | required | notes |
57
+ |---|---|---|---|
58
+ | \`type\` | \`"rule" \\| "pattern" \\| "lesson" \\| "compound"\` | yes | Lowercase. |
59
+ | \`title\` | string | yes | Short title, used as a human-readable identifier. |
60
+ | \`stage\` | \`FlowStage\` | yes | One of brainstorm / scope / design / spec / plan / tdd / review / ship. |
61
+ | \`context\` | string | yes | What situation triggered this. |
62
+ | \`insight\` | string | yes | What must be remembered. |
63
+ | \`reuse\` | string | yes | How to apply this next time — concrete trigger/action. |
64
+ | \`created\` | ISO 8601 UTC string | yes | When the entry was written. |
65
+ | \`confidence\` | \`"high" \\| "medium" \\| "low"\` | optional | Default \`medium\` if omitted. |
66
+ | \`domain\` | string | optional | Free-form taxonomy (\`api\`, \`infra\`, \`ui\`, …). |
67
+ | \`project\` | string | optional | Repo or scope name when the entry crosses features. |
68
+ | \`supersedes\` | string \\| null | optional | Title of the entry this one replaces. |
69
+ | \`superseded\` | boolean | optional | \`true\` when a newer entry replaces this one. |
70
+ | \`archived\` | boolean | optional | \`true\` once the curator soft-archives the entry. |
71
+
42
72
  Rules:
43
73
  - Type must be exactly one of \`rule\`, \`pattern\`, \`lesson\`, \`compound\` (lowercase).
44
- - Never rewrite history silently; append a newer correction entry instead. To replace, prefix the new entry with \`Supersedes: <old-title>\`.
74
+ - Never rewrite history silently; append a newer correction entry instead. To replace, set \`supersedes\` to the old title in the new JSONL entry and in the new markdown entry prefix with \`Supersedes: <old-title>\`. Flip \`superseded: true\` on the old JSONL entry via a new JSONL line (the file is append-only; use a \`replace\` line by convention — see Curation policy).
45
75
  - Keep entries concise and actionable.
46
76
  - Optional fields (\`Confidence\`, \`Domain\`, \`Project\`) are forward-compatible and used by the **knowledge-curation** skill — fill them when known.
47
77
 
78
+ ## Backward-compat migration (markdown → JSONL)
79
+
80
+ Run \`/cc-learn migrate\` once per repo when \`${KNOWLEDGE_JSONL_PATH}\` is missing:
81
+
82
+ 1. Parse \`${KNOWLEDGE_PATH}\`. Each entry starts with \`### <ISO8601> [<type>] <title>\` and is followed by \`- <Field>: <value>\` lines until the next \`###\` or EOF.
83
+ 2. Map fields to JSONL schema:
84
+ - Heading timestamp → \`created\`; heading \`[type]\` → \`type\`; heading title → \`title\`.
85
+ - Bullet \`Stage:\`, \`Context:\`, \`Insight:\`, \`Reuse:\`, \`Confidence:\`, \`Domain:\`, \`Project:\` → matching fields.
86
+ - A \`Supersedes:\` prefix line becomes \`"supersedes": "<old-title>"\`.
87
+ 3. Emit one JSON object per line to \`${KNOWLEDGE_JSONL_PATH}\` preserving the original order. Set defaults: \`confidence = "medium"\`, \`superseded = false\`, \`archived = false\`, missing optional fields = \`null\`.
88
+ 4. Do **not** rewrite \`${KNOWLEDGE_PATH}\`. The markdown stays as the human-readable mirror; new additions continue to write both files.
89
+ 5. After migration, \`/cc-learn search\` reads the JSONL store first; if absent, it continues to parse the markdown file (so users who never migrate still work).
90
+
48
91
  ## Curation policy (target: ≤ 50 active entries)
49
92
 
50
93
  The knowledge file is append-only, but entries can be **superseded** rather than deleted:
@@ -61,18 +104,24 @@ See the **knowledge-curation** utility skill for the full curation protocol.
61
104
  - If file is missing or empty, report that clearly.
62
105
 
63
106
  ### \`/cc-learn search <query>\`
64
- - Perform case-insensitive text search in \`${KNOWLEDGE_PATH}\`.
107
+ - If \`${KNOWLEDGE_JSONL_PATH}\` exists: stream it, JSON.parse each line, filter where any of \`title\`, \`context\`, \`insight\`, \`reuse\`, \`domain\` contains \`<query>\` (case-insensitive). Skip \`archived: true\` unless \`--include-archived\` is passed.
108
+ - Otherwise: case-insensitive text search in \`${KNOWLEDGE_PATH}\`.
65
109
  - Return matched headings and nearby lines.
66
110
 
67
111
  ### \`/cc-learn add\`
68
112
  - Ask for: \`type\`, \`short title\`, \`context\`, \`insight\`, \`reuse\`.
69
- - Optionally ask for: \`confidence\`, \`domain\`, \`project\`.
70
- - Append one entry using current UTC timestamp.
71
- - Re-read the file tail and confirm the entry was written.
113
+ - Optionally ask for: \`confidence\`, \`domain\`, \`project\`, \`supersedes\`.
114
+ - Append one markdown entry to \`${KNOWLEDGE_PATH}\` (human mirror).
115
+ - Append one JSON line to \`${KNOWLEDGE_JSONL_PATH}\` (canonical store) using the same UTC timestamp as the markdown entry's heading.
116
+ - Re-read both tails to confirm both writes.
117
+
118
+ ### \`/cc-learn migrate\`
119
+ - Parse \`${KNOWLEDGE_PATH}\` and emit \`${KNOWLEDGE_JSONL_PATH}\` per the Backward-compat migration protocol above.
120
+ - Safe to re-run: if JSONL already exists, report the current entry count and exit (no destructive rewrite).
72
121
 
73
122
  ### \`/cc-learn curate\`
74
123
  - Hand off to the **knowledge-curation** skill (read-only audit + soft-archive plan).
75
- - Never deletes from \`${KNOWLEDGE_PATH}\` without an explicit user-approved archive plan.
124
+ - Never deletes from \`${KNOWLEDGE_PATH}\` or \`${KNOWLEDGE_JSONL_PATH}\` without an explicit user-approved archive plan. Soft-archive in JSONL means appending a new line with the same \`title\` and \`archived: true\` (entries are never physically removed).
76
125
  `;
77
126
  }
78
127
  export function learnCommandContract() {
@@ -80,19 +129,22 @@ export function learnCommandContract() {
80
129
 
81
130
  ## Purpose
82
131
 
83
- Manage the project knowledge store at \`${KNOWLEDGE_PATH}\` (append-only markdown).
132
+ Manage the project knowledge store. Two mirrored formats:
133
+ - \`${KNOWLEDGE_PATH}\` — human-readable markdown (append-only, tail view).
134
+ - \`${KNOWLEDGE_JSONL_PATH}\` — canonical JSONL (one entry per line) used by the curator and machine consumers.
84
135
 
85
136
  ## HARD-GATE
86
137
 
87
- Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\` (or user-approved summary output).
138
+ Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\`, \`${KNOWLEDGE_JSONL_PATH}\`, or user-approved summary output.
88
139
 
89
140
  ## Subcommands
90
141
 
91
142
  | subcommand | args | description |
92
143
  |---|---|---|
93
- | (default) | — | Show recent knowledge entries (tail view). |
94
- | \`search\` | \`<query>\` | Search knowledge text for relevant prior rules/patterns/lessons. |
95
- | \`add\` | — | Append a new entry with type \`rule\` / \`pattern\` / \`lesson\` / \`compound\`. |
144
+ | (default) | — | Show recent knowledge entries (tail view from markdown mirror). |
145
+ | \`search\` | \`<query>\` | Search knowledge for relevant prior rules/patterns/lessons. Prefers JSONL when present. |
146
+ | \`add\` | — | Append a new entry (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) to **both** markdown and JSONL. |
147
+ | \`migrate\` | — | Emit the canonical JSONL mirror from the markdown file (idempotent). |
96
148
  | \`curate\` | — | Hand off to the **knowledge-curation** skill: read-only audit + soft-archive plan when the active file exceeds the curation threshold. |
97
149
  `;
98
150
  }
@@ -103,7 +155,7 @@ After this stage, ask:
103
155
  - Did I discover a non-obvious reusable **rule** or **pattern**?
104
156
  - Did a failure reveal a reusable **lesson**?
105
157
 
106
- If yes, append one concise entry to \`${KNOWLEDGE_PATH}\`:
158
+ If yes, append one concise entry to **both** the markdown mirror (\`${KNOWLEDGE_PATH}\`) and the canonical JSONL store (\`${KNOWLEDGE_JSONL_PATH}\`) with the same timestamp:
107
159
 
108
160
  \`\`\`bash
109
161
  TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
@@ -114,6 +166,7 @@ cat >> ${KNOWLEDGE_PATH} <<EOF
114
166
  - Insight: what should be remembered
115
167
  - Reuse: how to apply this next time
116
168
  EOF
169
+ printf '%s\\n' '{"type":"pattern","title":"short-title","stage":"${stageName}","context":"what situation triggered this","insight":"what should be remembered","reuse":"how to apply this next time","created":"'"$TS"'","confidence":"medium","domain":null,"project":null,"supersedes":null,"superseded":false,"archived":false}' >> ${KNOWLEDGE_JSONL_PATH}
117
170
  \`\`\`
118
171
 
119
172
  Type must be exactly one of: \`rule\`, \`pattern\`, \`lesson\`, \`compound\`.
@@ -89,14 +89,37 @@ These skills live in \`.cclaw/skills/\` but have no slash commands. They activat
89
89
 
90
90
  **Activation rule:** When a contextual skill applies, read its SKILL.md and follow it as a supplementary lens alongside the current stage. Do not skip the stage workflow — the contextual skill adds depth, not a detour.
91
91
 
92
+ ### Opt-in language rule packs
93
+
94
+ cclaw stays language-agnostic by default. Projects that want language-specific
95
+ review lenses can enable opt-in rule packs in \`.cclaw/config.yaml\`:
96
+
97
+ \`\`\`yaml
98
+ languageRulePacks:
99
+ - typescript # → skills/language-typescript/SKILL.md
100
+ - python # → skills/language-python/SKILL.md
101
+ - go # → skills/language-go/SKILL.md
102
+ \`\`\`
103
+
104
+ After editing the list, run \`cclaw sync\` to materialize the enabled pack SKILL.md
105
+ files. Packs activate during \`tdd\` and \`review\` when the diff touches files in
106
+ their language. They are additive lenses — Tier-1 rules block merge, Tier-2 rules
107
+ require a named follow-up. Never silently override them.
108
+
92
109
  ## Custom Skills (project-owned, sync-safe)
93
110
 
94
111
  \`.cclaw/custom-skills/\` is a sync-safe directory. \`cclaw sync\` and \`cclaw upgrade\` **never overwrite** files there.
95
112
 
96
113
  Use it to add **project-specific** skills that complement the managed library:
97
114
 
98
- - Each skill: \`.cclaw/custom-skills/<folder>/SKILL.md\` with the same frontmatter format as managed skills (\`name\` + \`description\` triggering routing).
99
- - Activate by mentioning the skill name explicitly, or rely on semantic routing from the description.
115
+ - Each skill: \`.cclaw/custom-skills/<folder>/SKILL.md\` following the public-API frontmatter schema documented in \`.cclaw/custom-skills/README.md\`.
116
+ - The frontmatter public API is stable across cclaw releases: \`name\`, \`description\` (required), plus optional \`stages\`, \`triggers\`, \`hardGate\`, \`owners\`, \`version\`.
117
+ - Routing precedence when loading a stage:
118
+ 1. Active stage skill under \`.cclaw/skills/<stage>/\`.
119
+ 2. Managed utility skills whose trigger matches (\`landscape-check\`, \`security-audit\`, \`adversarial-review\`, etc.).
120
+ 3. **Custom skills** whose \`stages\` array includes the active stage (or is missing) AND whose \`description\` / \`triggers\` match the prompt.
121
+ - Custom skills are **never mandatory delegations** — they are opt-in lenses. If you need a mandatory dispatch, promote the skill upstream or add a managed specialist instead.
122
+ - Activate by mentioning the skill name explicitly, or rely on semantic routing from the description + triggers.
100
123
  - See \`.cclaw/custom-skills/README.md\` for the full convention and a starter template under \`.cclaw/custom-skills/example/\`.
101
124
 
102
125
  If a custom skill turns out to generalize (e.g. another project would want the same lens), promote it to a managed skill via a contribution to the cclaw repo — managed skills get versioning and maintenance.
@@ -129,13 +152,27 @@ When a stage requires user input (approval, choice, direction), use this structu
129
152
  2. **Present options** as labeled choices (A, B, C...) with:
130
153
  - One-line description of each option
131
154
  - Trade-off or consequence
155
+ - **\`Completeness: X/10\`** — how thoroughly does this option cover the dimensions the stage cares about (failure modes, data flow, blast radius, observability, rollback, etc. — pick the dimensions that matter for *this* decision and subtract for each gap). Force a numeric score; vague text scores ≤ 5.
132
156
  - Mark one as **(recommended)** with brief why
133
- 3. **Use the harness ask-user tool** when available:
157
+ 3. **Pick the highest-scoring option as the recommendation.** If scores tie, prefer the option with the smallest blast radius (review/ship), the lowest risk (design/spec), or the most reversible outcome (ship finalization).
158
+ 4. **Use the harness ask-user tool** when available:
134
159
  - Claude Code: \`AskUserQuestion\` tool
135
160
  - Cursor: \`AskQuestion\` tool with options array
136
161
  - Codex/OpenCode: numbered list in message (no native ask tool)
137
- 4. **Wait for response.** Do not proceed until the user picks.
138
- 5. **Commit to the choice.** Once decided, do not re-argue.
162
+ 5. **Wait for response.** Do not proceed until the user picks.
163
+ 6. **Commit to the choice.** Once decided, do not re-argue.
164
+
165
+ ### Completeness scoring rubric (apply per option)
166
+
167
+ | Score | Meaning |
168
+ |---|---|
169
+ | 9-10 | Closes the decision with no carry-over risk; covers every dimension stage cares about. |
170
+ | 7-8 | Closes the decision with a small named follow-up; one dimension partially covered. |
171
+ | 5-6 | Plausible but leaves at least one dimension visibly open; needs follow-up before next stage. |
172
+ | 3-4 | Workaround, not a solution; defers the real problem. |
173
+ | 0-2 | Wishful thinking; do not recommend. |
174
+
175
+ Always show the score next to the option label, e.g. \`(B) [Completeness: 8/10]\`.
139
176
 
140
177
  ### When to use structured asks vs conversational
141
178
  - **Structured (tool):** Architecture choices, scope decisions, approval gates, mode selection, scope boundary issues
@@ -28,10 +28,25 @@ export interface ArtifactValidation {
28
28
  }
29
29
  export interface StageAutoSubagentDispatch {
30
30
  agent: "planner" | "spec-reviewer" | "code-reviewer" | "security-reviewer" | "test-author" | "doc-updater";
31
- mode: "mandatory" | "proactive";
31
+ /**
32
+ * - `mandatory` — must be dispatched (or explicitly waived) before stage transition.
33
+ * - `proactive` — should be dispatched automatically when context matches `when`.
34
+ * - `conditional` — dispatched only when `condition` evaluates true at runtime; counted as
35
+ * mandatory **only when the condition holds**.
36
+ */
37
+ mode: "mandatory" | "proactive" | "conditional";
32
38
  when: string;
33
39
  purpose: string;
34
40
  requiresUserGate: boolean;
41
+ /**
42
+ * Optional machine-friendly trigger expression for `conditional` rows.
43
+ * Supported predicates: `diff_lines_gt:<N>`, `files_touched_gt:<N>`,
44
+ * `trust_boundary_changed`, `release_blast_radius_high`.
45
+ * Multiple predicates joined by `||` mean ANY trigger satisfies the condition.
46
+ */
47
+ condition?: string;
48
+ /** Optional skill folder the dispatched agent should load as additional context. */
49
+ skill?: string;
35
50
  }
36
51
  export interface NamedAntiPattern {
37
52
  title: string;
@@ -80,6 +95,8 @@ export declare const QUESTION_FORMAT_SPEC: string;
80
95
  export declare const ERROR_BUDGET_SPEC: string;
81
96
  /** Transition guard: agents with `mode: "mandatory"` in auto-subagent dispatch for this stage. */
82
97
  export declare function mandatoryDelegationsForStage(stage: FlowStage): string[];
98
+ /** Conditional dispatches that become mandatory only when their `condition` predicate evaluates true. */
99
+ export declare function conditionalDispatchesForStage(stage: FlowStage): StageAutoSubagentDispatch[];
83
100
  export declare function stageSchema(stage: FlowStage): StageSchema;
84
101
  export declare function orderedStageSchemas(): StageSchema[];
85
102
  export declare function stageGateIds(stage: FlowStage): string[];
@@ -195,7 +195,7 @@ const SCOPE = {
195
195
  "**Error and Rescue Registry** — For each capability: what breaks, how detected, what fallback."
196
196
  ],
197
197
  interactionProtocol: [
198
- "For scope mode selection: use the Decision Protocol — present expand/selective/hold/reduce as labeled options with trade-offs and mark one as (recommended). Base your recommendation on default heuristics: greenfield -> expand, enhancement -> selective, bugfix/hotfix/refactor -> hold, broad blast radius -> reduce. If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
198
+ "For scope mode selection: use the Decision Protocol — present expand/selective/hold/reduce as labeled options with trade-offs and mark one as (recommended). **Score each option `Completeness: X/10`** (10 = covers every prime-directive failure mode, four data-flow paths, observability, and deferred handling for the in-scope set; subtract for each gap). Recommend the highest-scoring option; if scores tie, pick the lowest blast radius. Base your recommendation on default heuristics: greenfield -> expand, enhancement -> selective, bugfix/hotfix/refactor -> hold, broad blast radius -> reduce. If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
199
199
  "Walk through the scope checklist interactively. Each checklist item that surfaces a decision should be presented to the user as a question, not as a monologue. Do not dump all items at once.",
200
200
  "Challenge premise and verify the problem framing before anything else.",
201
201
  "Take a position on every scope decision. Avoid hedging phrases like 'this could work' or 'there are many ways'; state your recommendation and one concrete condition that would change it.",
@@ -405,7 +405,7 @@ const DESIGN = {
405
405
  interactionProtocol: [
406
406
  "Review architecture decisions section-by-section.",
407
407
  "For EACH issue found in a review section, present it ONE AT A TIME. Do NOT batch multiple issues.",
408
- "For each issue: use the Decision Protocol — describe concretely with file/line references, present labeled options (A/B/C) with trade-offs, effort estimate (S/M/L/XL), risk level (Low/Med/High), and mark one as (recommended). If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
408
+ "For each issue: use the Decision Protocol — describe concretely with file/line references, present labeled options (A/B/C) with trade-offs, effort estimate (S/M/L/XL), risk level (Low/Med/High), **`Completeness: X/10` per option** (10 = fully addresses architecture/data-flow/failure-modes/test+perf review concerns for the issue, subtract for each unaddressed dimension), and mark one as (recommended). Prefer the highest-scoring option; if scores tie, prefer the lower-risk one. If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
409
409
  "Only proceed to the next review section after ALL issues in the current section are resolved.",
410
410
  "If a section has no issues, say 'No issues found' and move on.",
411
411
  "Do not skip failure-mode mapping.",
@@ -1108,10 +1108,11 @@ const REVIEW = {
1108
1108
  checklist: [
1109
1109
  "Diff Scope — Run `git diff` against base branch. If no diff, exit early with APPROVED (no changes to review). Scope the review to changed files unless blast-radius analysis requires wider inspection.",
1110
1110
  "Change-Size Check — ~100 lines = normal. ~300 lines = consider splitting. ~1000+ lines = strongly recommend stacked PRs. Flag large diffs to the user.",
1111
+ "Adversarial Trigger Check — compute changed-line count (`git diff --shortstat <base>..HEAD`), files-touched count, and whether trust boundaries changed (auth/secrets/external inputs/permissions). If `lines > 100` OR `files > 10` OR `trust boundary changed`, **dispatch a SECOND code-reviewer agent with the `adversarial-review` skill loaded** and reconcile its findings into the review army (treat the conditional dispatch as mandatory whenever the trigger holds; record the trigger that fired in the dashboard).",
1111
1112
  "Load upstream evidence — read TDD artifact (RED + GREEN + REFACTOR), spec, and plan. Verify evidence chain is unbroken.",
1112
1113
  "Layer 1: Spec Compliance — check every acceptance criterion against implementation. Verdict: pass/fail per criterion.",
1113
1114
  "Layer 2a: Correctness — logic errors, race conditions, boundary violations, null handling.",
1114
- "Layer 2b: Security — input validation, auth boundaries, secrets exposure, injection vectors.",
1115
+ "Layer 2b: Security — input validation, auth boundaries, secrets exposure, injection vectors. **Mandatory:** also load and execute the `.cclaw/skills/security-audit/SKILL.md` utility skill (proactive pattern sweep across diff + touched modules, not just the diff itself) and merge findings into the review army. The Layer 2 security pass is not complete until the audit sweep records a finding count (0 acceptable) with file:line evidence for every Critical.",
1115
1116
  "Layer 2c: Performance — N+1 queries, memory leaks, missing caching, hot paths.",
1116
1117
  "Layer 2d: Architecture Fit — does the implementation match the locked design? Coupling, cohesion, interface contracts.",
1117
1118
  "Layer 2e: External Safety — SQL safety, concurrency, secrets in logs, enum completeness (grep outside diff), LLM trust boundaries.",
@@ -1124,7 +1125,7 @@ const REVIEW = {
1124
1125
  "Run Layer 1 (spec compliance) completely before starting Layer 2.",
1125
1126
  "In each review section, present findings ONE AT A TIME. Do NOT batch.",
1126
1127
  "Classify every finding as Critical, Important, or Suggestion.",
1127
- "For each Critical finding: use the Decision Protocol — present resolution options (A/B/C) with trade-offs and mark one as (recommended). If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
1128
+ "For each Critical finding: use the Decision Protocol — present resolution options (A/B/C) with trade-offs, **score each option `Completeness: X/10`** (10 = fully closes the finding with no carry-over risk; subtract for partial fixes, deferred follow-ups, or new risk introduced), and mark one as (recommended). Prefer the highest-scoring option; if scores tie, prefer the option with the smallest blast radius. If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
1128
1129
  "Resolve all critical blockers before ship.",
1129
1130
  "For final verdict: use AskQuestion/AskUserQuestion only if runtime schema is confirmed; otherwise collect verdict with a plain-text single-choice prompt (APPROVED / APPROVED_WITH_CONCERNS / BLOCKED).",
1130
1131
  "**STOP.** Do NOT proceed to ship until the user provides an explicit verdict."
@@ -1148,7 +1149,8 @@ const REVIEW = {
1148
1149
  { id: "review_severity_classified", description: "All findings are severity-tagged." },
1149
1150
  { id: "review_criticals_resolved", description: "No unresolved critical blockers remain." },
1150
1151
  { id: "review_army_json_valid", description: "07-review-army.json passes schema validation (validateReviewArmy)." },
1151
- { id: "review_completeness_scored", description: "Completeness score is computed and recorded (AC coverage, task coverage, slice coverage, adversarial pass)." }
1152
+ { id: "review_completeness_scored", description: "Completeness score is computed and recorded (AC coverage, task coverage, slice coverage, adversarial pass)." },
1153
+ { id: "review_security_audit_swept", description: "The security-audit utility skill was run against the diff scope and the modules it touches. Finding count (0 if clean) recorded in the review army with file:line evidence for every Critical." }
1152
1154
  ],
1153
1155
  requiredEvidence: [
1154
1156
  "Artifact written to `.cclaw/artifacts/07-review.md`.",
@@ -1289,7 +1291,7 @@ const REVIEW = {
1289
1291
  { section: "Layer 1 Verdict", required: true, validationRule: "Per-criterion pass/fail with references." },
1290
1292
  { section: "Layer 2 Findings", required: true, validationRule: "Each finding has severity, description, and resolution status." },
1291
1293
  { section: "Review Army Contract", required: true, validationRule: "Structured findings include id/severity/confidence/fingerprint/reportedBy/status with dedup reconciliation summary." },
1292
- { section: "Review Readiness Dashboard", required: true, validationRule: "At least 4 readiness checklist lines including blocker and recommendation status." },
1294
+ { section: "Review Readiness Dashboard", required: true, validationRule: "Includes a per-pass table (Layer 1 / Layer 2 / Adversarial / Schema) with a 'Completed at' column, a Delegation log snapshot block (path .cclaw/state/delegation-log.json with required/completed/waived/pending), a Staleness signal block (commit at last review pass and current commit), and a Headline with open critical blockers + ship recommendation. At minimum, the section text must contain the substrings 'Completed at', 'delegation-log.json', 'commit at last review pass', and 'Ship recommendation'." },
1293
1295
  { section: "Completeness Score", required: true, validationRule: "Records AC coverage, task coverage, test-slice coverage, and adversarial-review pass status as numeric or boolean values. At minimum, a line like 'AC coverage: N/M' or 'AC coverage: 100%'." },
1294
1296
  { section: "Severity Summary", required: true, validationRule: "Per-severity count lines for critical, important, and suggestion buckets." },
1295
1297
  { section: "Final Verdict", required: true, validationRule: "Exactly one of: APPROVED, APPROVED_WITH_CONCERNS, BLOCKED." }
@@ -1334,7 +1336,7 @@ const SHIP = {
1334
1336
  interactionProtocol: [
1335
1337
  "Run preflight checks before any release action.",
1336
1338
  "Document release notes and rollback plan explicitly.",
1337
- "For finalization mode: use the Decision Protocol — present modes as labeled options (A/B/C/D) with consequences and mark one as (recommended). If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
1339
+ "For finalization mode: use the Decision Protocol — present modes as labeled options (A/B/C/D) with consequences, **score each option `Completeness: X/10`** (10 = fully addresses release blast-radius, rollback readiness, observability, and stakeholder communication), and mark one as (recommended). Prefer the highest-scoring option; if scores tie, prefer the most reversible one. If AskQuestion/AskUserQuestion is available, send exactly ONE question per call, validate fields against runtime schema, and on schema error immediately fall back to plain-text question instead of retrying guessed payloads.",
1338
1340
  "Do not proceed if critical blockers remain from review.",
1339
1341
  "**STOP.** Present finalization options and wait for user selection before executing any finalization action."
1340
1342
  ],
@@ -1568,8 +1570,18 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
1568
1570
  agent: "security-reviewer",
1569
1571
  mode: "mandatory",
1570
1572
  when: "Always in review stage. Even when no trust boundaries changed, produce an explicit 'no-change' security attestation.",
1571
- purpose: "Guarantee a dedicated security pass on every diff: auth, input validation, secrets, injection, privilege, and blast-radius review are never opt-in.",
1572
- requiresUserGate: false
1573
+ purpose: "Guarantee a dedicated security pass on every diff: auth, input validation, secrets, injection, privilege, and blast-radius review are never opt-in. MUST load the `security-audit` skill and run a pattern-based sweep across the diff scope and touched modules in addition to the per-diff Layer 2 security checklist.",
1574
+ requiresUserGate: false,
1575
+ skill: "security-audit"
1576
+ },
1577
+ {
1578
+ agent: "code-reviewer",
1579
+ mode: "conditional",
1580
+ condition: "diff_lines_gt:100||files_touched_gt:10||trust_boundary_changed",
1581
+ when: "When the diff exceeds 100 changed lines, touches more than 10 files, or modifies trust boundaries — dispatch a SECOND, independent code-reviewer with the adversarial-review skill loaded so the review army has at least two voices on a high-blast-radius change.",
1582
+ purpose: "Adversarial second-opinion review on large or trust-sensitive diffs. The second reviewer treats the implementation as hostile and tries to break it (hostile-user, future-maintainer, competitor lenses) instead of sympathetically explaining it.",
1583
+ requiresUserGate: false,
1584
+ skill: "adversarial-review"
1573
1585
  }
1574
1586
  ],
1575
1587
  ship: [
@@ -1595,6 +1607,10 @@ export function mandatoryDelegationsForStage(stage) {
1595
1607
  .filter((d) => d.mode === "mandatory")
1596
1608
  .map((d) => d.agent);
1597
1609
  }
1610
+ /** Conditional dispatches that become mandatory only when their `condition` predicate evaluates true. */
1611
+ export function conditionalDispatchesForStage(stage) {
1612
+ return STAGE_AUTO_SUBAGENT_DISPATCH[stage].filter((d) => d.mode === "conditional");
1613
+ }
1598
1614
  export function stageSchema(stage) {
1599
1615
  const base = STAGE_SCHEMA_MAP[stage];
1600
1616
  return {