cclaw-cli 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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) {
@@ -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. Load \`.cclaw/knowledge.md\` and apply relevant entries.
37
+ 4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, created, project).
38
38
  5. Write stage output to ${writeStepPaths}.
39
39
  6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled only by \`cclaw archive\`.
40
40
 
@@ -51,7 +51,7 @@ SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
51
51
  CONTEXT_WARNINGS_FILE="$ROOT/${RUNTIME_ROOT}/state/context-warnings.jsonl"
52
52
  CONTEXT_MODE_FILE="$ROOT/${RUNTIME_ROOT}/state/context-mode.json"
53
53
  CONTEXTS_DIR="$ROOT/${RUNTIME_ROOT}/contexts"
54
- KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.md"
54
+ KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.jsonl"
55
55
  META_SKILL="$ROOT/${RUNTIME_ROOT}/skills/${META_SKILL_NAME}/SKILL.md"
56
56
 
57
57
  # --- Read flow state ---
@@ -309,7 +309,7 @@ if [ -f "$META_SKILL" ]; then
309
309
  META_CONTENT=$(cat "$META_SKILL" 2>/dev/null || echo "")
310
310
  fi
311
311
 
312
- # --- Load knowledge snapshot (append-only markdown) ---
312
+ # --- Load knowledge snapshot (canonical JSONL tail) ---
313
313
  KNOWLEDGE_SUMMARY=""
314
314
  if [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
315
315
  KNOWLEDGE_SUMMARY=$(tail -n 30 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
@@ -593,7 +593,7 @@ RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROO
593
593
 
594
594
  # --- Escape for JSON ---
595
595
  ${ESCAPE_FN}
596
- MSG=$(escape_json "Cclaw: session ending (stage=$STAGE, run=$ACTIVE_RUN). $CHECKPOINT_NOTE $RUN_SYNC_NOTE Before stopping: (1) confirm flow-state reflects reality, (2) ensure artifact changes match current feature intent, (3) if you discovered a non-obvious rule/pattern, append it to ${RUNTIME_ROOT}/knowledge.md, (4) commit or revert pending changes.")
596
+ MSG=$(escape_json "Cclaw: session ending (stage=$STAGE, run=$ACTIVE_RUN). $CHECKPOINT_NOTE $RUN_SYNC_NOTE Before stopping: (1) confirm flow-state reflects reality, (2) ensure artifact changes match current feature intent, (3) if you discovered a non-obvious rule/pattern, append one strict-schema JSON line to ${RUNTIME_ROOT}/knowledge.jsonl, (4) commit or revert pending changes.")
597
597
 
598
598
  # --- Output harness-specific JSON ---
599
599
  case "$HARNESS" in
@@ -631,7 +631,7 @@ INPUT=$(cat 2>/dev/null || echo '{}')
631
631
  STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
632
632
  STATE_FILE="$STATE_DIR/flow-state.json"
633
633
  DELEGATION_FILE="$STATE_DIR/delegation-log.json"
634
- KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.md"
634
+ KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.jsonl"
635
635
  DIGEST_FILE="$STATE_DIR/session-digest.md"
636
636
  DIGEST_TMP="$STATE_DIR/session-digest.md.tmp.$$"
637
637
 
@@ -786,7 +786,7 @@ export default function cclawPlugin(ctx) {
786
786
  const contextModePath = join(stateDir, "context-mode.json");
787
787
  const contextsDir = join(runtimeDir, "contexts");
788
788
  const sessionDigestPath = join(stateDir, "session-digest.md");
789
- const knowledgePath = join(runtimeDir, "knowledge.md");
789
+ const knowledgePath = join(runtimeDir, "knowledge.jsonl");
790
790
  const metaSkillPath = join(runtimeDir, "skills/${META_SKILL_NAME}/SKILL.md");
791
791
 
792
792
  function ensureRuntimeDirs() {
@@ -923,7 +923,7 @@ export default function cclawPlugin(ctx) {
923
923
  if (knowledge.length > 0) parts.push("Knowledge snapshot (latest entries):", ...knowledge);
924
924
 
925
925
  parts.push(
926
- "If you discover a non-obvious rule or pattern, append it to .cclaw/knowledge.md using type: rule, pattern, or lesson."
926
+ "If you discover a non-obvious rule or pattern, append one strict-schema JSON line to .cclaw/knowledge.jsonl using type: rule, pattern, lesson, or compound."
927
927
  );
928
928
 
929
929
  const meta = readFileText(metaSkillPath).trim();
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Canonical JSONL field order (matches the reference spec).
3
+ * Exported for tests and any programmatic writer that wants the exact shape.
4
+ */
5
+ export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "action", "confidence", "domain", "stage", "created", "project"];
1
6
  export declare function learnSkillMarkdown(): string;
2
7
  export declare function learnCommandContract(): string;
3
8
  export declare function selfImprovementBlock(stageName: string): string;
@@ -1,9 +1,28 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Knowledge store content for /cc-learn and stage self-improvement prompts.
3
+ //
4
+ // The knowledge store is a single canonical JSONL file. Each line is one
5
+ // self-contained JSON object matching the strict schema in this module.
6
+ // There is no markdown mirror — cclaw is JSONL-native.
3
7
  // ---------------------------------------------------------------------------
4
- const KNOWLEDGE_PATH = ".cclaw/knowledge.md";
8
+ const KNOWLEDGE_PATH = ".cclaw/knowledge.jsonl";
9
+ const KNOWLEDGE_ARCHIVE_PATH = ".cclaw/knowledge.archive.jsonl";
5
10
  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.";
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
+ /**
13
+ * Canonical JSONL field order (matches the reference spec).
14
+ * Exported for tests and any programmatic writer that wants the exact shape.
15
+ */
16
+ export const KNOWLEDGE_JSONL_FIELDS = [
17
+ "type",
18
+ "trigger",
19
+ "action",
20
+ "confidence",
21
+ "domain",
22
+ "stage",
23
+ "created",
24
+ "project"
25
+ ];
7
26
  export function learnSkillMarkdown() {
8
27
  return `---
9
28
  name: ${LEARN_SKILL_NAME}
@@ -14,65 +33,81 @@ description: "${LEARN_SKILL_DESCRIPTION}"
14
33
 
15
34
  ## Overview
16
35
 
17
- This skill manages the append-only project knowledge file at \`${KNOWLEDGE_PATH}\`.
36
+ The project knowledge store is **one canonical JSONL file**: \`${KNOWLEDGE_PATH}\`.
37
+ Each line is one self-contained JSON object. Append-only. Machine-queryable.
18
38
 
19
- Use it to keep durable knowledge that should survive sessions:
20
- - **rule**: hard constraint to follow every time
21
- - **pattern**: repeatable way that works well in this project
22
- - **lesson**: non-obvious outcome from a failure or trade-off
23
- - **compound**: post-ship insight about how to make the *next* feature faster (process accelerator, not domain rule)
39
+ Use the store to keep durable knowledge that should survive sessions:
40
+ - **rule**: hard constraint to follow every time.
41
+ - **pattern**: repeatable way that works well in this project.
42
+ - **lesson**: non-obvious outcome from a failure or trade-off.
43
+ - **compound**: post-ship insight about how to make the *next* feature faster (process accelerator, not domain rule).
24
44
 
25
45
  ## HARD-GATE
26
46
 
27
- Under \`/cc-learn\`, only modify the knowledge store (\`${KNOWLEDGE_PATH}\`) or an explicitly user-approved summary file. Do not modify application code here.
47
+ Under \`/cc-learn\`, only modify \`${KNOWLEDGE_PATH}\`, \`${KNOWLEDGE_ARCHIVE_PATH}\`,
48
+ or an explicitly user-approved summary file. Do not modify application code here.
49
+ Do not invent alternate stores (no markdown mirror, no SQLite, no per-stage files).
28
50
 
29
- ## Entry format (append-only)
51
+ ## Entry format — strict JSONL schema
30
52
 
31
- \`\`\`markdown
32
- ### 2026-04-14T12:00:00Z [pattern] short-title
33
- - Stage: design
34
- - Context: one short line
35
- - Insight: one short line
36
- - Reuse: one short line
37
- - Confidence: high | medium | low (optional)
38
- - Domain: api | infra | ui | testing | … (optional)
39
- - Project: <repo or scope name> (optional)
53
+ Exactly one JSON object per line. Fields must appear in the order:
54
+ \`type, trigger, action, confidence, domain, stage, created, project\`.
55
+
56
+ \`\`\`json
57
+ {"type":"pattern","trigger":"when reviewing external payloads","action":"parse through zod before touching service layer","confidence":"high","domain":"api","stage":"review","created":"2026-04-14T12:00:00Z","project":"cclaw"}
40
58
  \`\`\`
41
59
 
60
+ | field | type | required | notes |
61
+ |---|---|---|---|
62
+ | \`type\` | \`"rule" \\| "pattern" \\| "lesson" \\| "compound"\` | yes | Lowercase. |
63
+ | \`trigger\` | string | yes | The concrete situation that must be recognized. Start with a verb or \`when …\`. |
64
+ | \`action\` | string | yes | The concrete move to take when the trigger fires. One sentence. |
65
+ | \`confidence\` | \`"high" \\| "medium" \\| "low"\` | yes | Write \`medium\` when unsure; do not omit. |
66
+ | \`domain\` | string \\| null | yes | Free-form taxonomy (\`api\`, \`infra\`, \`ui\`, \`security\`, \`testing\`, …). Use \`null\` when cross-cutting. |
67
+ | \`stage\` | \`FlowStage\` \\| null | yes | One of brainstorm / scope / design / spec / plan / tdd / review / ship, or \`null\` when cross-stage. |
68
+ | \`created\` | ISO 8601 UTC string | yes | \`date -u +%Y-%m-%dT%H:%M:%SZ\`. |
69
+ | \`project\` | string \\| null | yes | Repo or scope name. Use \`null\` when the entry crosses projects. |
70
+
42
71
  Rules:
43
- - 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>\`.
45
- - Keep entries concise and actionable.
46
- - Optional fields (\`Confidence\`, \`Domain\`, \`Project\`) are forward-compatible and used by the **knowledge-curation** skill — fill them when known.
72
+ - No other fields. Extra keys are forbidden and MUST be rejected by any writer.
73
+ - Every required-null field must be emitted explicitly as \`null\` (not omitted). This keeps the file grep-friendly.
74
+ - Append-only: never rewrite or delete a historical line. Corrections are new
75
+ entries whose \`trigger\` clearly supersedes the earlier one.
76
+ - Keep each entry one line. No pretty-printing. No trailing commas.
47
77
 
48
78
  ## Curation policy (target: ≤ 50 active entries)
49
79
 
50
- The knowledge file is append-only, but entries can be **superseded** rather than deleted:
51
-
52
- - When you discover a more correct rule, append a new entry with \`Supersedes: <old-title>\`.
53
- - During \`/cc-learn curate\`, the assistant surfaces candidates for soft-archive (move to \`.cclaw/knowledge.archive.md\`) when the active file exceeds 50 entries or contains stale/duplicate entries.
54
-
55
- See the **knowledge-curation** utility skill for the full curation protocol.
80
+ - The file is append-only entries are never physically deleted.
81
+ - When the canonical file exceeds 50 lines, \`/cc-learn curate\` proposes
82
+ soft-archiving: the approved lines are **moved** to \`${KNOWLEDGE_ARCHIVE_PATH}\`
83
+ verbatim (same JSONL shape). The working file stays lean.
84
+ - See the **knowledge-curation** utility skill for the full curation protocol.
56
85
 
57
86
  ## Subcommands
58
87
 
59
88
  ### \`/cc-learn\` (default)
60
- - Show the last 30 lines from \`${KNOWLEDGE_PATH}\`.
61
- - If file is missing or empty, report that clearly.
89
+ - Read \`${KNOWLEDGE_PATH}\`. Stream the last 30 lines; pretty-print each
90
+ line's \`type\` / \`trigger\` / \`action\` for human review.
91
+ - If file is missing or empty, report that clearly and suggest \`/cc-learn add\`.
62
92
 
63
93
  ### \`/cc-learn search <query>\`
64
- - Perform case-insensitive text search in \`${KNOWLEDGE_PATH}\`.
65
- - Return matched headings and nearby lines.
94
+ - Stream \`${KNOWLEDGE_PATH}\`, JSON.parse each line, filter where any of
95
+ \`trigger\`, \`action\`, \`domain\`, \`project\` contains \`<query>\` (case-insensitive).
96
+ - Return the matched lines pretty-printed (do not mutate the file).
66
97
 
67
98
  ### \`/cc-learn add\`
68
- - 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.
99
+ - Ask for required fields in order: \`type\`, \`trigger\`, \`action\`, \`confidence\`, \`domain\`, \`stage\`, \`project\`.
100
+ - \`confidence\` must be one of \`high\`, \`medium\`, \`low\`. Default to \`medium\` if the user declines to set it.
101
+ - \`domain\`, \`stage\`, and \`project\` may be explicitly \`null\`.
102
+ - \`created\` is set automatically to the current UTC ISO timestamp.
103
+ - Append exactly one JSON line to \`${KNOWLEDGE_PATH}\` with the field order from the schema table above.
104
+ - Re-read the file tail to confirm the new line is valid JSON and parses back to the same object.
72
105
 
73
106
  ### \`/cc-learn curate\`
74
107
  - 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.
108
+ - Never deletes. Soft-archive means **moving** full JSON lines from
109
+ \`${KNOWLEDGE_PATH}\` to \`${KNOWLEDGE_ARCHIVE_PATH}\` as part of a
110
+ user-approved curation pass.
76
111
  `;
77
112
  }
78
113
  export function learnCommandContract() {
@@ -80,20 +115,23 @@ export function learnCommandContract() {
80
115
 
81
116
  ## Purpose
82
117
 
83
- Manage the project knowledge store at \`${KNOWLEDGE_PATH}\` (append-only markdown).
118
+ Manage the project knowledge store. One canonical file, strict JSONL:
119
+ - \`${KNOWLEDGE_PATH}\` — append-only JSONL, one entry per line.
120
+ - \`${KNOWLEDGE_ARCHIVE_PATH}\` — soft-archive target written only by curate.
84
121
 
85
122
  ## HARD-GATE
86
123
 
87
- Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\` (or user-approved summary output).
124
+ Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\`,
125
+ \`${KNOWLEDGE_ARCHIVE_PATH}\`, or user-approved summary output.
88
126
 
89
127
  ## Subcommands
90
128
 
91
129
  | subcommand | args | description |
92
130
  |---|---|---|
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\`. |
96
- | \`curate\` | — | Hand off to the **knowledge-curation** skill: read-only audit + soft-archive plan when the active file exceeds the curation threshold. |
131
+ | (default) | — | Show recent knowledge entries (tail of JSONL, pretty-printed). |
132
+ | \`search\` | \`<query>\` | Stream-filter the JSONL for matching \`trigger\`, \`action\`, \`domain\`, \`project\`. |
133
+ | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict 8-field schema. |
134
+ | \`curate\` | — | Hand off to the **knowledge-curation** skill: read-only audit + soft-archive plan when the file exceeds the curation threshold. |
97
135
  `;
98
136
  }
99
137
  export function selfImprovementBlock(stageName) {
@@ -103,35 +141,34 @@ After this stage, ask:
103
141
  - Did I discover a non-obvious reusable **rule** or **pattern**?
104
142
  - Did a failure reveal a reusable **lesson**?
105
143
 
106
- If yes, append one concise entry to \`${KNOWLEDGE_PATH}\`:
144
+ If yes, append one concise JSON line to the canonical knowledge store
145
+ (\`${KNOWLEDGE_PATH}\`) using the strict 8-field schema:
107
146
 
108
147
  \`\`\`bash
109
148
  TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
110
- cat >> ${KNOWLEDGE_PATH} <<EOF
111
- ### $TS [pattern] short-title
112
- - Stage: ${stageName}
113
- - Context: what situation triggered this
114
- - Insight: what should be remembered
115
- - Reuse: how to apply this next time
116
- EOF
149
+ printf '%s\\n' '{"type":"pattern","trigger":"when <situation>","action":"<concrete move>","confidence":"medium","domain":null,"stage":"${stageName}","created":"'"$TS"'","project":null}' >> ${KNOWLEDGE_PATH}
117
150
  \`\`\`
118
151
 
119
152
  Type must be exactly one of: \`rule\`, \`pattern\`, \`lesson\`, \`compound\`.
153
+ Fields must appear in the order: \`type, trigger, action, confidence, domain, stage, created, project\`.
154
+ Missing optional values must be emitted as \`null\`, never omitted.
120
155
  `;
121
156
  }
122
157
  export function learningsSearchPreamble(stage) {
123
158
  return `## Prior Knowledge (load at stage start)
124
159
 
125
- Before stage work, search \`${KNOWLEDGE_PATH}\` for relevant entries (for example: \`${stage}\`, affected systems, key constraints) and apply them explicitly.
126
-
127
- If the file is empty, continue normally.
160
+ Before stage work, stream \`${KNOWLEDGE_PATH}\` and filter for entries relevant to
161
+ this stage (\`${stage}\`), affected domains, and key constraints. Apply matching
162
+ entries explicitly. If the file is empty, continue normally.
128
163
  `;
129
164
  }
130
165
  export function learningsAgentsMdBlock() {
131
166
  return `### Knowledge Store
132
167
 
133
- \`${KNOWLEDGE_PATH}\` — append-only markdown memory with entry types \`rule\`, \`pattern\`, \`lesson\`, \`compound\`.
134
- At session start and stage transitions, load recent entries and apply relevant ones.
135
- If a non-obvious reusable rule/pattern/lesson is discovered, append a new entry.
168
+ \`${KNOWLEDGE_PATH}\` — append-only JSONL memory with entry types \`rule\`, \`pattern\`, \`lesson\`, \`compound\`.
169
+ Strict 8-field schema: \`type, trigger, action, confidence, domain, stage, created, project\`.
170
+ At session start and stage transitions, tail the file and apply relevant entries.
171
+ If a non-obvious reusable rule/pattern/lesson is discovered, append a new line
172
+ through \`/cc-learn add\` (never hand-edit).
136
173
  `;
137
174
  }