cclaw-cli 0.7.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.
@@ -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,10 +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";
5
- const KNOWLEDGE_JSONL_PATH = ".cclaw/knowledge.jsonl";
8
+ const KNOWLEDGE_PATH = ".cclaw/knowledge.jsonl";
9
+ const KNOWLEDGE_ARCHIVE_PATH = ".cclaw/knowledge.archive.jsonl";
6
10
  const LEARN_SKILL_NAME = "learnings";
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.";
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
+ ];
8
26
  export function learnSkillMarkdown() {
9
27
  return `---
10
28
  name: ${LEARN_SKILL_NAME}
@@ -15,113 +33,81 @@ description: "${LEARN_SKILL_DESCRIPTION}"
15
33
 
16
34
  ## Overview
17
35
 
18
- This skill manages the project knowledge store. The store has **two mirrored formats**:
19
-
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.
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.
24
38
 
25
39
  Use the store to keep durable knowledge that should survive sessions:
26
- - **rule**: hard constraint to follow every time
27
- - **pattern**: repeatable way that works well in this project
28
- - **lesson**: non-obvious outcome from a failure or trade-off
29
- - **compound**: post-ship insight about how to make the *next* feature faster (process accelerator, not domain rule)
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).
30
44
 
31
45
  ## HARD-GATE
32
46
 
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.
34
-
35
- ## Entry format markdown mirror (append-only)
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).
36
50
 
37
- \`\`\`markdown
38
- ### 2026-04-14T12:00:00Z [pattern] short-title
39
- - Stage: design
40
- - Context: one short line
41
- - Insight: one short line
42
- - Reuse: one short line
43
- - Confidence: high | medium | low (optional)
44
- - Domain: api | infra | ui | testing | … (optional)
45
- - Project: <repo or scope name> (optional)
46
- \`\`\`
51
+ ## Entry format — strict JSONL schema
47
52
 
48
- ## Entry format canonical JSONL (one entry per line)
53
+ Exactly one JSON object per line. Fields must appear in the order:
54
+ \`type, trigger, action, confidence, domain, stage, created, project\`.
49
55
 
50
56
  \`\`\`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}
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"}
52
58
  \`\`\`
53
59
 
54
- Schema:
55
-
56
60
  | field | type | required | notes |
57
61
  |---|---|---|---|
58
62
  | \`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. |
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. |
71
70
 
72
71
  Rules:
73
- - Type must be exactly one of \`rule\`, \`pattern\`, \`lesson\`, \`compound\` (lowercase).
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).
75
- - Keep entries concise and actionable.
76
- - Optional fields (\`Confidence\`, \`Domain\`, \`Project\`) are forward-compatible and used by the **knowledge-curation** skill — fill them when known.
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).
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.
90
77
 
91
78
  ## Curation policy (target: ≤ 50 active entries)
92
79
 
93
- The knowledge file is append-only, but entries can be **superseded** rather than deleted:
94
-
95
- - When you discover a more correct rule, append a new entry with \`Supersedes: <old-title>\`.
96
- - 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.
97
-
98
- 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.
99
85
 
100
86
  ## Subcommands
101
87
 
102
88
  ### \`/cc-learn\` (default)
103
- - Show the last 30 lines from \`${KNOWLEDGE_PATH}\`.
104
- - 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\`.
105
92
 
106
93
  ### \`/cc-learn search <query>\`
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}\`.
109
- - 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).
110
97
 
111
98
  ### \`/cc-learn add\`
112
- - Ask for: \`type\`, \`short title\`, \`context\`, \`insight\`, \`reuse\`.
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).
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.
121
105
 
122
106
  ### \`/cc-learn curate\`
123
107
  - Hand off to the **knowledge-curation** skill (read-only audit + soft-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).
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.
125
111
  `;
126
112
  }
127
113
  export function learnCommandContract() {
@@ -129,23 +115,23 @@ export function learnCommandContract() {
129
115
 
130
116
  ## Purpose
131
117
 
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.
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.
135
121
 
136
122
  ## HARD-GATE
137
123
 
138
- Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\`, \`${KNOWLEDGE_JSONL_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.
139
126
 
140
127
  ## Subcommands
141
128
 
142
129
  | subcommand | args | description |
143
130
  |---|---|---|
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). |
148
- | \`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. |
149
135
  `;
150
136
  }
151
137
  export function selfImprovementBlock(stageName) {
@@ -155,36 +141,34 @@ After this stage, ask:
155
141
  - Did I discover a non-obvious reusable **rule** or **pattern**?
156
142
  - Did a failure reveal a reusable **lesson**?
157
143
 
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:
144
+ If yes, append one concise JSON line to the canonical knowledge store
145
+ (\`${KNOWLEDGE_PATH}\`) using the strict 8-field schema:
159
146
 
160
147
  \`\`\`bash
161
148
  TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
162
- cat >> ${KNOWLEDGE_PATH} <<EOF
163
- ### $TS [pattern] short-title
164
- - Stage: ${stageName}
165
- - Context: what situation triggered this
166
- - Insight: what should be remembered
167
- - Reuse: how to apply this next time
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}
149
+ printf '%s\\n' '{"type":"pattern","trigger":"when <situation>","action":"<concrete move>","confidence":"medium","domain":null,"stage":"${stageName}","created":"'"$TS"'","project":null}' >> ${KNOWLEDGE_PATH}
170
150
  \`\`\`
171
151
 
172
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.
173
155
  `;
174
156
  }
175
157
  export function learningsSearchPreamble(stage) {
176
158
  return `## Prior Knowledge (load at stage start)
177
159
 
178
- Before stage work, search \`${KNOWLEDGE_PATH}\` for relevant entries (for example: \`${stage}\`, affected systems, key constraints) and apply them explicitly.
179
-
180
- 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.
181
163
  `;
182
164
  }
183
165
  export function learningsAgentsMdBlock() {
184
166
  return `### Knowledge Store
185
167
 
186
- \`${KNOWLEDGE_PATH}\` — append-only markdown memory with entry types \`rule\`, \`pattern\`, \`lesson\`, \`compound\`.
187
- At session start and stage transitions, load recent entries and apply relevant ones.
188
- 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).
189
173
  `;
190
174
  }
@@ -96,15 +96,22 @@ review lenses can enable opt-in rule packs in \`.cclaw/config.yaml\`:
96
96
 
97
97
  \`\`\`yaml
98
98
  languageRulePacks:
99
- - typescript # → skills/language-typescript/SKILL.md
100
- - python # → skills/language-python/SKILL.md
101
- - go # → skills/language-go/SKILL.md
99
+ - typescript # → .cclaw/rules/lang/typescript.md
100
+ - python # → .cclaw/rules/lang/python.md
101
+ - go # → .cclaw/rules/lang/go.md
102
102
  \`\`\`
103
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.
104
+ After editing the list, run \`cclaw sync\` to materialize the enabled packs
105
+ under \`.cclaw/rules/lang/\` (one \`<language>.md\` file per pack, each with
106
+ YAML frontmatter declaring \`stages\` and \`triggers\`). Packs activate during
107
+ \`tdd\` and \`review\` when the diff touches files in their language. They are
108
+ additive lenses — Tier-1 rules block merge, Tier-2 rules require a named
109
+ follow-up. Never silently override them.
110
+
111
+ \`cclaw sync\` and \`cclaw doctor\` also refuse the legacy v0.7.0 location
112
+ \`.cclaw/skills/language-*/\` — if a project still has those folders,
113
+ \`sync\` removes them on the next run and \`doctor\` surfaces the drift until
114
+ they are gone.
108
115
 
109
116
  ## Custom Skills (project-owned, sync-safe)
110
117
 
@@ -189,10 +196,12 @@ Watch for these anti-patterns:
189
196
 
190
197
  ## Knowledge Integration
191
198
 
192
- At session start and stage transitions, check \`.cclaw/knowledge.md\` for project-specific knowledge:
193
- - Review recent entries and apply relevant rules/patterns to the current task
194
- - If you discover a non-obvious reusable rule or pattern, append a new entry with type \`rule\`, \`pattern\`, or \`lesson\`
199
+ At session start and stage transitions, stream \`.cclaw/knowledge.jsonl\` (the canonical strict-JSONL knowledge store) and apply relevant entries:
200
+ - Each line is one JSON object with fields \`type, trigger, action, confidence, domain, stage, created, project\`.
201
+ - Review recent entries and apply relevant rules/patterns to the current task.
202
+ - If you discover a non-obvious reusable rule or pattern, append one new JSON line via \`/cc-learn add\` with type \`rule\`, \`pattern\`, \`lesson\`, or \`compound\`.
195
203
 
196
- Knowledge capture is append-only and should preserve historical context rather than rewriting prior entries.
204
+ Knowledge capture is append-only and strict-schema. Never rewrite or delete
205
+ historical entries; corrections are new lines whose \`trigger\` supersedes the earlier one.
197
206
  `;
198
207
  }
@@ -26,7 +26,7 @@ These are prompt-discipline guidelines that complement the real hooks cclaw gene
26
26
  When a new session begins in any harness:
27
27
 
28
28
  1. **Read flow state:** Load \`.cclaw/state/flow-state.json\` to find the current stage and completed stages.
29
- 2. **Load knowledge:** Review recent entries from \`.cclaw/knowledge.md\` and surface the most relevant rules/patterns.
29
+ 2. **Load knowledge:** Stream the tail of \`.cclaw/knowledge.jsonl\` (strict JSONL store) and surface the most relevant rules/patterns.
30
30
  3. **Check for in-progress work:** If the last stage is incomplete, remind the user and offer to resume.
31
31
  4. **Load suggestion memory:** Read \`.cclaw/state/suggestion-memory.json\` and honor \`enabled=false\` opt-out.
32
32
  5. **Read AGENTS.md:** The cclaw block contains routing and rules — follow them.
@@ -45,7 +45,7 @@ Before ending a session or when context is full:
45
45
 
46
46
  1. **Verify no pending changes:** All modified files must be either committed or explicitly reverted.
47
47
  2. **Update flow state:** Mark the current stage as its actual status (DONE / DONE_WITH_CONCERNS / BLOCKED).
48
- 3. **Write knowledge:** If any non-obvious reusable insight appears, append to \`.cclaw/knowledge.md\` with type \`rule\`, \`pattern\`, or \`lesson\`.
48
+ 3. **Write knowledge:** If any non-obvious reusable insight appears, append one strict-schema JSON line to \`.cclaw/knowledge.jsonl\` with type \`rule\`, \`pattern\`, \`lesson\`, or \`compound\`.
49
49
  4. **Create checkpoint:** Write a brief status note to the current artifact or as a comment in flow-state.json.
50
50
 
51
51
  ### Stop conditions (agent must halt and report)
@@ -81,7 +81,7 @@ Before starting stage execution:
81
81
  2. Resolve active artifact root: \`.cclaw/artifacts/\`.
82
82
  3. Load upstream artifacts required by this stage:
83
83
  ${readLines}
84
- 4. Read \`.cclaw/knowledge.md\` and apply relevant entries before making decisions.
84
+ 4. Stream \`.cclaw/knowledge.jsonl\` (strict-JSONL knowledge store) and apply relevant entries before making decisions.
85
85
  `;
86
86
  }
87
87
  function whenNotToUseBlock(stage) {
@@ -236,7 +236,7 @@ function progressiveDisclosureBlock(stage) {
236
236
  - Primary stage procedure (this file): \`.cclaw/skills/${schema.skillFolder}/SKILL.md\`
237
237
  - Orchestrator contract (gate language and handoff): \`.cclaw/commands/${stage}.md\`
238
238
  - Artifact structure baseline: \`.cclaw/templates/${schema.artifactFile}\`
239
- - Runtime state truth source: \`.cclaw/state/flow-state.json\` + \`.cclaw/artifacts/\` + \`.cclaw/knowledge.md\`
239
+ - Runtime state truth source: \`.cclaw/state/flow-state.json\` + \`.cclaw/artifacts/\` + \`.cclaw/knowledge.jsonl\`
240
240
 
241
241
  ### See also
242
242
  - Meta routing and activation rules: \`.cclaw/skills/using-cclaw/SKILL.md\`
@@ -1455,7 +1455,7 @@ const SHIP = {
1455
1455
  { section: "Monitoring", required: false, validationRule: "If applicable: what metrics/logs to watch post-deploy. Risk note if no monitoring." },
1456
1456
  { section: "Finalization", required: true, validationRule: "Exactly one finalization enum token selected. Execution result documented. Worktree cleaned if applicable." },
1457
1457
  { section: "Completion Status", required: false, validationRule: "If present: exactly one of SHIPPED, SHIPPED_WITH_EXCEPTIONS, BLOCKED. Exceptions documented when applicable." },
1458
- { section: "Compound Step", required: false, validationRule: "Optional retrospective: at least one bullet of the form 'Insight: ... | Action: append [compound] entry to .cclaw/knowledge.md', or an explicit 'No compound insight this run.' line." }
1458
+ { section: "Compound Step", required: false, validationRule: "Optional retrospective: at least one bullet of the form 'Insight: ... | Action: append [compound] entry to .cclaw/knowledge.jsonl', or an explicit 'No compound insight this run.' line." }
1459
1459
  ],
1460
1460
  namedAntiPattern: {
1461
1461
  title: "Green CI Means Safe to Merge",
@@ -8,7 +8,7 @@ function delegationLogPath() {
8
8
  return `${RUNTIME_ROOT}/state/delegation-log.json`;
9
9
  }
10
10
  function knowledgePath() {
11
- return `${RUNTIME_ROOT}/knowledge.md`;
11
+ return `${RUNTIME_ROOT}/knowledge.jsonl`;
12
12
  }
13
13
  function contextModePath() {
14
14
  return `${RUNTIME_ROOT}/state/context-mode.json`;
@@ -131,7 +131,7 @@ a read-only command.
131
131
  - Prefer \`${checkpointPath()}\` when \`stage === currentStage\` and \`timestamp\` parses as ISO 8601.
132
132
  - Else scan \`${stageActivityPath()}\` from tail for the most recent entry whose \`stage === currentStage\`; use its \`ts\`.
133
133
  - Render \`<X>d<Y>h\`, \`<X>h<Y>m\`, \`<X>m\`, or \`(unknown)\`.
134
- 5. Read \`${RUNTIME_ROOT}/knowledge.md\`. If missing or empty → knowledge highlights are \`(none recorded)\`.
134
+ 5. Read \`${RUNTIME_ROOT}/knowledge.jsonl\`. If missing or empty → knowledge highlights are \`(none recorded)\`. Parse each line as JSON and surface its \`trigger\`/\`action\`.
135
135
  6. For each gate in \`stageGateCatalog[currentStage].required\`:
136
136
  - Satisfied if present in \`passed\` and absent from \`blocked\`.
137
137
  7. Build and print the status block (see command contract for layout).
@@ -446,7 +446,7 @@ Execution rule: complete and verify each wave before starting the next wave.
446
446
  _Optional retrospective. The goal is to make the **next** feature faster, not to evaluate this one._
447
447
  _If you have nothing to add, write the explicit line: \`No compound insight this run.\`_
448
448
  - Insight: <one short line about what should accelerate the next run>
449
- - Action: append \`[compound]\` entry to \`.cclaw/knowledge.md\` capturing the insight
449
+ - Action: append one strict-schema JSON line with \`"type":"compound"\` to \`.cclaw/knowledge.jsonl\` capturing the insight (fields: type, trigger, action, confidence, domain, stage, created, project)
450
450
  `
451
451
  };
452
452
  export const RULEBOOK_MARKDOWN = `# Cclaw Rulebook
@@ -20,11 +20,28 @@ export declare function retrospectiveSkill(): string;
20
20
  export declare function languageTypescriptSkill(): string;
21
21
  export declare function languagePythonSkill(): string;
22
22
  export declare function languageGoSkill(): string;
23
- export declare const LANGUAGE_RULE_PACK_FOLDERS: {
24
- readonly typescript: "language-typescript";
25
- readonly python: "language-python";
26
- readonly go: "language-go";
23
+ /**
24
+ * Language rule packs live under `.cclaw/rules/lang/<pack>.md`. They are NOT
25
+ * skills (no folder, no `SKILL.md`) — they are opt-in **rule files** that the
26
+ * meta-skill router and stage hooks consult when the corresponding language
27
+ * appears in a diff. The pack id doubles as the on-disk filename stem.
28
+ */
29
+ export declare const LANGUAGE_RULE_PACK_FILES: {
30
+ readonly typescript: "typescript.md";
31
+ readonly python: "python.md";
32
+ readonly go: "go.md";
27
33
  };
34
+ /**
35
+ * Folder (relative to runtime root) that holds every enabled language rule
36
+ * pack. A single folder keeps discovery trivial for hooks and for `doctor`.
37
+ */
38
+ export declare const LANGUAGE_RULE_PACK_DIR: readonly ["rules", "lang"];
28
39
  export declare const LANGUAGE_RULE_PACK_GENERATORS: Record<string, () => string>;
40
+ /**
41
+ * Legacy per-language folders under `.cclaw/skills/` used in v0.7.0. Listed
42
+ * here so `cclaw sync` and `doctor` can surface drift and the installer can
43
+ * clean them up after the move to `.cclaw/rules/lang/`.
44
+ */
45
+ export declare const LEGACY_LANGUAGE_RULE_PACK_FOLDERS: readonly ["language-typescript", "language-python", "language-go"];
29
46
  export declare const UTILITY_SKILL_FOLDERS: readonly ["security", "debugging", "performance", "ci-cd", "docs", "executing-plans", "context-engineering", "source-driven-development", "frontend-accessibility", "landscape-check", "adversarial-review", "security-audit", "knowledge-curation", "retrospective"];
30
47
  export declare const UTILITY_SKILL_MAP: Record<string, () => string>;
@@ -751,22 +751,22 @@ candidates exist).
751
751
  export function knowledgeCurationSkill() {
752
752
  return `---
753
753
  name: knowledge-curation
754
- description: "Read-only curation pass over .cclaw/knowledge.md and .cclaw/knowledge.jsonl. Surfaces stale, duplicate, or low-confidence entries and proposes a soft-archive plan; never deletes without explicit user approval."
754
+ description: "Read-only curation pass over the canonical strict-JSONL knowledge store at .cclaw/knowledge.jsonl. Surfaces stale, duplicate, or low-confidence entries and proposes a soft-archive plan that moves approved lines to .cclaw/knowledge.archive.jsonl. Never deletes without explicit user approval."
755
755
  ---
756
756
 
757
757
  # Knowledge Curation
758
758
 
759
759
  ## Quick Start
760
760
 
761
- > 1. This is a **read-only audit** of \`.cclaw/knowledge.md\` and, when present, \`.cclaw/knowledge.jsonl\`. Never delete or rewrite entries here.
761
+ > 1. This is a **read-only audit** of \`.cclaw/knowledge.jsonl\`. Never delete or rewrite entries here.
762
762
  > 2. Surface candidates for soft-archive when the active file > 50 entries OR contains stale/duplicate/superseded entries.
763
763
  > 3. Propose a single archive plan and require explicit user approval before any move.
764
764
 
765
765
  ## HARD-GATE
766
766
 
767
- - Do not modify \`.cclaw/knowledge.md\` or \`.cclaw/knowledge.jsonl\` from this skill except via an explicit user-approved archive plan that **moves** markdown entries to \`.cclaw/knowledge.archive.md\` and appends soft-archive lines (same title, \`archived: true\`) to the JSONL. Never physically removes entries.
768
- - Do not silently rewrite or summarize entries — preserve original wording.
769
- - Prefer the JSONL store for queries when present (faster, structured); use the markdown mirror as the human-readable source of truth for final user approval.
767
+ - Do not modify \`.cclaw/knowledge.jsonl\` from this skill except via an explicit user-approved archive plan that **moves** full JSON lines verbatim to \`.cclaw/knowledge.archive.jsonl\`. Never physically delete history that is already archived the archive file is append-only too.
768
+ - Do not silently rewrite or summarize entries — preserve the original JSON line byte-for-byte.
769
+ - Operate only on the canonical JSONL store. If you see a stray \`.cclaw/knowledge.md\`, report it as a drift signal; do **not** read or rewrite it.
770
770
 
771
771
  ## When to run
772
772
 
@@ -776,28 +776,28 @@ description: "Read-only curation pass over .cclaw/knowledge.md and .cclaw/knowle
776
776
 
777
777
  ## Audit dimensions
778
778
 
779
- For each entry in \`.cclaw/knowledge.md\` produce a row with:
779
+ For each JSON line in \`.cclaw/knowledge.jsonl\` produce a row with:
780
780
 
781
781
  | Field | Source |
782
782
  |---|---|
783
- | Title | \`### <ts> [type] <title>\` heading |
784
- | Type | \`rule\` / \`pattern\` / \`lesson\` / \`compound\` |
785
- | Stage | \`Stage:\` field (or \`unknown\`) |
786
- | Age | days since timestamp |
787
- | Confidence | \`Confidence:\` field if present, else \`unstated\` |
788
- | Domain | \`Domain:\` field if present |
789
- | Supersedes | \`Supersedes:\` field if present |
783
+ | # | 1-based line number in the JSONL file |
784
+ | Type | \`type\` field (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) |
785
+ | Stage | \`stage\` field (or \`null\`) |
786
+ | Age | days since \`created\` |
787
+ | Confidence | \`confidence\` field |
788
+ | Domain | \`domain\` field (or \`null\`) |
789
+ | Trigger | \`trigger\` field, truncated to 60 chars |
790
790
  | Status hint | one of: keep / supersede-candidate / archive-candidate / duplicate |
791
791
 
792
792
  ### Status rules
793
793
 
794
- - **supersede-candidate**: another entry has \`Supersedes: <this-title>\`.
795
- - **duplicate**: title or insight ≈ another entry's (caller's judgment, not regex).
794
+ - **supersede-candidate**: a newer line shares the same \`trigger\` (case-insensitive) and a different \`action\`.
795
+ - **duplicate**: \`trigger\` ≈ another line's AND \`action\` ≈ the same (caller's judgment).
796
796
  - **archive-candidate**:
797
- - Type \`lesson\` AND age > 180 days AND no \`Supersedes\` chain points to it; OR
798
- - Stage = \`brainstorm\` AND age > 90 days; OR
799
- - Confidence = \`low\` AND age > 60 days; OR
800
- - Total active entries > 50 and entry has lowest reuse signal.
797
+ - \`type=lesson\` AND age > 180 days AND no newer line references the same \`trigger\`; OR
798
+ - \`stage=brainstorm\` AND age > 90 days; OR
799
+ - \`confidence=low\` AND age > 60 days; OR
800
+ - Total active entries > 50 and entry has the lowest estimated reuse signal.
801
801
  - **keep**: everything else.
802
802
 
803
803
  ## Output format
@@ -807,7 +807,7 @@ Produce two artifacts as **chat output only** (do not write files):
807
807
  ### 1. Audit table
808
808
 
809
809
  \`\`\`markdown
810
- | # | Title | Type | Stage | Age | Confidence | Status hint |
810
+ | # | Type | Stage | Age | Confidence | Trigger | Status hint |
811
811
  |---|---|---|---|---|---|---|
812
812
  | 1 | … | … | … | … | … | … |
813
813
  \`\`\`
@@ -820,14 +820,15 @@ Produce two artifacts as **chat output only** (do not write files):
820
820
  Threshold reasoning: <why entries below were selected>
821
821
 
822
822
  Entries to archive:
823
- 1. <title> — reason
824
- 2. <title> — reason
823
+ 1. line #<N> — <trigger> — reason
824
+ 2. line #<N> — <trigger> — reason
825
825
 
826
826
  Action plan if approved:
827
- 1. Append a header to \`.cclaw/knowledge.archive.md\` with today's UTC date.
828
- 2. Move (cut/paste) selected entries verbatim from \`.cclaw/knowledge.md\` into the archive file.
829
- 3. Append a single supersession line to \`.cclaw/knowledge.md\`:
830
- \\\`### <ts> [pattern] knowledge-curation-<date> archived <N> entries, see knowledge.archive.md\\\`
827
+ 1. Ensure \`.cclaw/knowledge.archive.jsonl\` exists (create empty if missing).
828
+ 2. Move (cut/paste) the selected JSON lines verbatim from
829
+ \`.cclaw/knowledge.jsonl\` into \`.cclaw/knowledge.archive.jsonl\`,
830
+ preserving byte order within each file.
831
+ 3. Do not rewrite, re-order, or pretty-print any remaining line in the active file.
831
832
 
832
833
  After approval: ask the user to run the move themselves, or — if they explicitly grant write access — perform the move atomically and report the new active count.
833
834
  \`\`\`
@@ -1079,7 +1080,7 @@ description: "Post-ship retrospective lens. Use after a ship to extract durable
1079
1080
  ## Quick Start
1080
1081
 
1081
1082
  > 1. Run **after** the ship stage closes (PR merged or release tagged), while the run is still loaded in memory.
1082
- > 2. Walk the four lenses below; harvest concrete entries for \`.cclaw/knowledge.md\`.
1083
+ > 2. Walk the four lenses below; harvest concrete entries for \`.cclaw/knowledge.jsonl\`.
1083
1084
  > 3. Stop when you have at least one durable entry **or** an explicit "no new lesson this run".
1084
1085
 
1085
1086
  ## HARD-GATE
@@ -1132,13 +1133,14 @@ For each lens, write either a knowledge entry **or** the explicit string
1132
1133
 
1133
1134
  ## Output protocol
1134
1135
 
1135
- For every harvested insight, append one entry to \`.cclaw/knowledge.md\` using
1136
- the standard format (see \`learnings\` skill). Prefer:
1136
+ For every harvested insight, append one strict-schema JSON line to
1137
+ \`.cclaw/knowledge.jsonl\` (fields: \`type, trigger, action, confidence, domain, stage, created, project\`).
1138
+ See the \`learnings\` skill for the canonical shape. Choose \`type\`:
1137
1139
 
1138
- - \`[compound]\` for process/speed accelerators.
1139
- - \`[lesson]\` for "we learned this the hard way".
1140
- - \`[pattern]\` for repeatable shapes that worked.
1141
- - \`[rule]\` only for hard constraints that must always hold.
1140
+ - \`compound\` for process/speed accelerators.
1141
+ - \`lesson\` for "we learned this the hard way".
1142
+ - \`pattern\` for repeatable shapes that worked.
1143
+ - \`rule\` only for hard constraints that must always hold.
1142
1144
 
1143
1145
  Then write a one-paragraph **Run Summary** at the top of the next
1144
1146
  \`/cc <idea>\` brainstorm context citing the lessons in scope.
@@ -1347,16 +1349,37 @@ irrelevant. Discarded errors are Go's #1 source of silent data loss.
1347
1349
  \`\`\`
1348
1350
  `;
1349
1351
  }
1350
- export const LANGUAGE_RULE_PACK_FOLDERS = {
1351
- typescript: "language-typescript",
1352
- python: "language-python",
1353
- go: "language-go"
1352
+ /**
1353
+ * Language rule packs live under `.cclaw/rules/lang/<pack>.md`. They are NOT
1354
+ * skills (no folder, no `SKILL.md`) — they are opt-in **rule files** that the
1355
+ * meta-skill router and stage hooks consult when the corresponding language
1356
+ * appears in a diff. The pack id doubles as the on-disk filename stem.
1357
+ */
1358
+ export const LANGUAGE_RULE_PACK_FILES = {
1359
+ typescript: "typescript.md",
1360
+ python: "python.md",
1361
+ go: "go.md"
1354
1362
  };
1363
+ /**
1364
+ * Folder (relative to runtime root) that holds every enabled language rule
1365
+ * pack. A single folder keeps discovery trivial for hooks and for `doctor`.
1366
+ */
1367
+ export const LANGUAGE_RULE_PACK_DIR = ["rules", "lang"];
1355
1368
  export const LANGUAGE_RULE_PACK_GENERATORS = {
1356
- "language-typescript": languageTypescriptSkill,
1357
- "language-python": languagePythonSkill,
1358
- "language-go": languageGoSkill
1369
+ typescript: languageTypescriptSkill,
1370
+ python: languagePythonSkill,
1371
+ go: languageGoSkill
1359
1372
  };
1373
+ /**
1374
+ * Legacy per-language folders under `.cclaw/skills/` used in v0.7.0. Listed
1375
+ * here so `cclaw sync` and `doctor` can surface drift and the installer can
1376
+ * clean them up after the move to `.cclaw/rules/lang/`.
1377
+ */
1378
+ export const LEGACY_LANGUAGE_RULE_PACK_FOLDERS = [
1379
+ "language-typescript",
1380
+ "language-python",
1381
+ "language-go"
1382
+ ];
1360
1383
  export const UTILITY_SKILL_FOLDERS = [
1361
1384
  "security",
1362
1385
  "debugging",
package/dist/doctor.js CHANGED
@@ -17,7 +17,7 @@ import { checkMandatoryDelegations } from "./delegation.js";
17
17
  import { buildTraceMatrix } from "./trace-matrix.js";
18
18
  import { reconcileAndWriteCurrentStageGateCatalog, verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "./gate-evidence.js";
19
19
  import { stageSkillFolder } from "./content/skills.js";
20
- import { LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
20
+ import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
21
21
  import { CONTEXT_MODES, DEFAULT_CONTEXT_MODE } from "./content/contexts.js";
22
22
  import { validateHookDocument } from "./hook-schema.js";
23
23
  const execFileAsync = promisify(execFile);
@@ -409,15 +409,29 @@ export async function doctorChecks(projectRoot, options = {}) {
409
409
  });
410
410
  }
411
411
  // Opt-in language rule packs: only check presence for packs the user enabled.
412
+ // Canonical location is .cclaw/rules/lang/<pack>.md.
412
413
  for (const pack of parsedConfig?.languageRulePacks ?? []) {
413
- const folder = LANGUAGE_RULE_PACK_FOLDERS[pack];
414
- if (!folder)
414
+ const fileName = LANGUAGE_RULE_PACK_FILES[pack];
415
+ if (!fileName)
415
416
  continue;
416
- const skillPath = path.join(projectRoot, RUNTIME_ROOT, "skills", folder, "SKILL.md");
417
+ const packPath = path.join(projectRoot, RUNTIME_ROOT, ...LANGUAGE_RULE_PACK_DIR, fileName);
417
418
  checks.push({
418
419
  name: `language_rule_pack:${pack}`,
419
- ok: await exists(skillPath),
420
- details: skillPath
420
+ ok: await exists(packPath),
421
+ details: packPath
422
+ });
423
+ }
424
+ // Drift: legacy per-language skill folders from v0.7.0 must not coexist with
425
+ // the new rules/lang/ layout. `cclaw sync` removes them on the next run.
426
+ for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
427
+ const legacyPath = path.join(projectRoot, RUNTIME_ROOT, "skills", legacyFolder);
428
+ const legacyPresent = await exists(legacyPath);
429
+ checks.push({
430
+ name: `language_rule_pack:no_legacy:${legacyFolder}`,
431
+ ok: !legacyPresent,
432
+ details: legacyPresent
433
+ ? `legacy ${legacyPath} must be removed — language packs moved to ${RUNTIME_ROOT}/${LANGUAGE_RULE_PACK_DIR.join("/")}/. Run \`cclaw sync\`.`
434
+ : `no legacy ${legacyFolder} skill folder`
421
435
  });
422
436
  }
423
437
  // Agent definition files
@@ -680,11 +694,21 @@ export async function doctorChecks(projectRoot, options = {}) {
680
694
  ok: true,
681
695
  details: hasPython ? "python3 available" : "warning: python3 not found, jq/node paths must stay healthy"
682
696
  });
683
- // Knowledge store exists
697
+ // Knowledge store exists (canonical JSONL, no markdown mirror)
684
698
  checks.push({
685
699
  name: "knowledge:store_exists",
686
- ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "knowledge.md")),
687
- details: `${RUNTIME_ROOT}/knowledge.md must exist`
700
+ ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl")),
701
+ details: `${RUNTIME_ROOT}/knowledge.jsonl must exist`
702
+ });
703
+ // There must be NO legacy markdown knowledge store — JSONL is the only store.
704
+ const legacyKnowledgeMdPath = path.join(projectRoot, RUNTIME_ROOT, "knowledge.md");
705
+ const legacyExists = await exists(legacyKnowledgeMdPath);
706
+ checks.push({
707
+ name: "knowledge:no_legacy_markdown",
708
+ ok: !legacyExists,
709
+ details: legacyExists
710
+ ? `legacy ${RUNTIME_ROOT}/knowledge.md must be removed — cclaw is JSONL-native`
711
+ : `no legacy markdown store present`
688
712
  });
689
713
  checks.push({
690
714
  name: "state:checkpoint_exists",
package/dist/install.js CHANGED
@@ -17,7 +17,7 @@ import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./
17
17
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
18
18
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
19
19
  import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
20
- import { LANGUAGE_RULE_PACK_FOLDERS, LANGUAGE_RULE_PACK_GENERATORS, UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
20
+ import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
21
21
  import { createInitialFlowState } from "./flow-state.js";
22
22
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
23
23
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
@@ -183,13 +183,23 @@ async function writeSkills(projectRoot, config) {
183
183
  const generator = UTILITY_SKILL_MAP[folder];
184
184
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
185
185
  }
186
+ // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
187
+ // only the packs listed in config.languageRulePacks are materialised. Any
188
+ // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
189
+ // are cleaned up below so the new rules/lang layout is the only truth.
186
190
  const enabledPacks = config?.languageRulePacks ?? [];
187
191
  for (const pack of enabledPacks) {
188
- const folder = LANGUAGE_RULE_PACK_FOLDERS[pack];
189
- const generator = LANGUAGE_RULE_PACK_GENERATORS[folder];
190
- if (!folder || !generator)
192
+ const fileName = LANGUAGE_RULE_PACK_FILES[pack];
193
+ const generator = LANGUAGE_RULE_PACK_GENERATORS[pack];
194
+ if (!fileName || !generator)
191
195
  continue;
192
- await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
196
+ await writeFileSafe(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR, fileName), generator());
197
+ }
198
+ for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
199
+ const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
200
+ if (await exists(legacyPath)) {
201
+ await fs.rm(legacyPath, { recursive: true, force: true });
202
+ }
193
203
  }
194
204
  }
195
205
  async function writeUtilityCommands(projectRoot) {
@@ -550,9 +560,13 @@ async function writeHooks(projectRoot, config) {
550
560
  }
551
561
  }
552
562
  async function ensureKnowledgeStore(projectRoot) {
553
- const storePath = runtimePath(projectRoot, "knowledge.md");
563
+ const storePath = runtimePath(projectRoot, "knowledge.jsonl");
554
564
  if (!(await exists(storePath))) {
555
- await writeFileSafe(storePath, "# Project Knowledge\n\n");
565
+ await writeFileSafe(storePath, "");
566
+ }
567
+ const legacyMdPath = runtimePath(projectRoot, "knowledge.md");
568
+ if (await exists(legacyMdPath)) {
569
+ await fs.rm(legacyMdPath, { force: true });
556
570
  }
557
571
  }
558
572
  async function ensureCustomSkillsScaffold(projectRoot) {
package/dist/policy.js CHANGED
@@ -85,8 +85,9 @@ export async function policyChecks(projectRoot, options = {}) {
85
85
  // --- utility skill checks ---
86
86
  const runtimeFile = (relativePath) => `${RUNTIME_ROOT}/${relativePath}`;
87
87
  const utilitySkillChecks = [
88
- { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Entry format", name: "utility_skill:learnings:entry_format" },
89
- { file: runtimeFile("skills/learnings/SKILL.md"), needle: "knowledge.jsonl", name: "utility_skill:learnings:jsonl_mirror" },
88
+ { file: runtimeFile("skills/learnings/SKILL.md"), needle: "strict JSONL schema", name: "utility_skill:learnings:jsonl_schema" },
89
+ { file: runtimeFile("skills/learnings/SKILL.md"), needle: "knowledge.jsonl", name: "utility_skill:learnings:jsonl_store" },
90
+ { file: runtimeFile("skills/learnings/SKILL.md"), needle: "type, trigger, action, confidence, domain, stage, created, project", name: "utility_skill:learnings:field_order" },
90
91
  { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Subcommands", name: "utility_skill:learnings:subcommands" },
91
92
  { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:learnings:hard_gate" },
92
93
  { file: runtimeFile("commands/learn.md"), needle: "## Subcommands", name: "utility_command:learn:subcommands" },
package/dist/runs.d.ts CHANGED
@@ -56,11 +56,10 @@ export declare function ensureRunSystem(projectRoot: string, _options?: EnsureRu
56
56
  export declare function listRuns(projectRoot: string): Promise<CclawRunMeta[]>;
57
57
  export declare function archiveRun(projectRoot: string, featureName?: string): Promise<ArchiveRunResult>;
58
58
  /**
59
- * Counts active (non-superseded) knowledge entries.
60
- * An entry is a markdown H3 heading with the canonical timestamped format produced by
61
- * `learn add` / `learn curate`. Entries marked `Supersedes:` themselves are still active;
62
- * this helper does not currently follow supersession chains beyond raw count, which is
63
- * deliberate — the curator reads the file directly to make the soft-archive plan.
59
+ * Counts entries in the canonical JSONL knowledge store. An "active" entry is one
60
+ * non-empty line that parses as JSON with the required `type` field belonging to the
61
+ * allowed set. Malformed lines are ignored (not counted) but do not throw so that a
62
+ * hand-edited file cannot break doctor/archive flows.
64
63
  */
65
64
  export declare function countActiveKnowledgeEntries(text: string): number;
66
65
  export {};
package/dist/runs.js CHANGED
@@ -407,7 +407,7 @@ export async function archiveRun(projectRoot, featureName) {
407
407
  }
408
408
  const KNOWLEDGE_SOFT_THRESHOLD = 50;
409
409
  async function readKnowledgeStats(projectRoot) {
410
- const knowledgePath = path.join(projectRoot, RUNTIME_ROOT, "knowledge.md");
410
+ const knowledgePath = path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
411
411
  let activeEntryCount = 0;
412
412
  if (await exists(knowledgePath)) {
413
413
  const text = await fs.readFile(knowledgePath, "utf8");
@@ -417,22 +417,30 @@ async function readKnowledgeStats(projectRoot) {
417
417
  activeEntryCount,
418
418
  softThreshold: KNOWLEDGE_SOFT_THRESHOLD,
419
419
  overThreshold: activeEntryCount > KNOWLEDGE_SOFT_THRESHOLD,
420
- knowledgePath: `${RUNTIME_ROOT}/knowledge.md`
420
+ knowledgePath: `${RUNTIME_ROOT}/knowledge.jsonl`
421
421
  };
422
422
  }
423
423
  /**
424
- * Counts active (non-superseded) knowledge entries.
425
- * An entry is a markdown H3 heading with the canonical timestamped format produced by
426
- * `learn add` / `learn curate`. Entries marked `Supersedes:` themselves are still active;
427
- * this helper does not currently follow supersession chains beyond raw count, which is
428
- * deliberate — the curator reads the file directly to make the soft-archive plan.
424
+ * Counts entries in the canonical JSONL knowledge store. An "active" entry is one
425
+ * non-empty line that parses as JSON with the required `type` field belonging to the
426
+ * allowed set. Malformed lines are ignored (not counted) but do not throw so that a
427
+ * hand-edited file cannot break doctor/archive flows.
429
428
  */
430
429
  export function countActiveKnowledgeEntries(text) {
431
- const lines = text.split(/\r?\n/);
430
+ const allowed = new Set(["rule", "pattern", "lesson", "compound"]);
432
431
  let count = 0;
433
- for (const line of lines) {
434
- if (/^###\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\s+\[(rule|pattern|lesson|compound)\]/u.test(line)) {
435
- count += 1;
432
+ for (const raw of text.split(/\r?\n/)) {
433
+ const line = raw.trim();
434
+ if (line.length === 0)
435
+ continue;
436
+ try {
437
+ const parsed = JSON.parse(line);
438
+ if (typeof parsed.type === "string" && allowed.has(parsed.type)) {
439
+ count += 1;
440
+ }
441
+ }
442
+ catch {
443
+ // Skip malformed lines silently; curation surfaces them separately.
436
444
  }
437
445
  }
438
446
  return count;
package/dist/types.d.ts CHANGED
@@ -48,10 +48,10 @@ export interface VibyConfig {
48
48
  /** Default flow track for new runs (quick = shortened path, standard = full pipeline). */
49
49
  defaultTrack?: FlowTrack;
50
50
  /**
51
- * Opt-in language rule packs. Each enabled pack materializes a matching utility
52
- * skill under `.cclaw/skills/language-<id>/SKILL.md` on next `cclaw sync`. The
53
- * meta-skill router loads the pack during review/tdd when the diff touches the
54
- * language in question.
51
+ * Opt-in language rule packs. Each enabled pack materializes a matching rule
52
+ * file under `.cclaw/rules/lang/<id>.md` on the next `cclaw sync`. The
53
+ * meta-skill router loads the pack during review/tdd when the diff touches
54
+ * the language in question. Disabled packs have no on-disk footprint.
55
55
  */
56
56
  languageRulePacks?: LanguageRulePack[];
57
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {