cclaw-cli 0.36.0 → 0.37.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.
@@ -35,20 +35,24 @@ the user can approve individual lifts, accept-all, or skip.
35
35
  - set \`closeout.compoundPromoted = 0\`,
36
36
  - set \`closeout.shipSubstate = "ready_to_archive"\`,
37
37
  - emit \`compound: no candidates | next: /cc-next\` and stop.
38
- 5. Otherwise, present **one** structured ask (AskUserQuestion / AskQuestion /
38
+ 5. **Drift check** each surviving candidate before presenting it (see
39
+ "Drift check" section in the skill): confirm the lift target file is
40
+ current, spot-check the repo for contradictions, demote stale clusters
41
+ into a new superseding entry instead of a lift.
42
+ 6. Otherwise, present **one** structured ask (AskUserQuestion / AskQuestion /
39
43
  plain text) summarising all candidates at once:
40
44
  - \`apply-all\` (default) — apply every listed lift,
41
45
  - \`apply-selected\` — prompt per-candidate,
42
46
  - \`skip\` — record a skip reason and advance without changes.
43
- 6. Apply approved lifts to the target file(s). Each lift also appends a
47
+ 7. Apply approved lifts to the target file(s). Each lift also appends a
44
48
  \`type: "compound"\` entry back to \`${RUNTIME_ROOT}/knowledge.jsonl\`
45
49
  summarising what was lifted.
46
- 7. Update flow-state:
50
+ 8. Update flow-state:
47
51
  - \`closeout.compoundCompletedAt = <ISO>\`,
48
52
  - \`closeout.compoundPromoted = <count>\`,
49
53
  - \`closeout.compoundSkipped = true\` if user picked skip,
50
54
  - \`closeout.shipSubstate = "ready_to_archive"\`.
51
- 8. Emit one-line summary: \`compound: promoted=<N> skipped=<bool> | next: /cc-next\`.
55
+ 9. Emit one-line summary: \`compound: promoted=<N> skipped=<bool> | next: /cc-next\`.
52
56
 
53
57
  ## Primary skill
54
58
 
@@ -83,27 +87,53 @@ empty pass is allowed and must advance \`closeout.shipSubstate\` to
83
87
  - \`closeout.compoundPromoted = 0\`,
84
88
  - \`closeout.shipSubstate = "ready_to_archive"\`,
85
89
  - announce \`compound: no candidates\` and stop.
86
- 4. Otherwise, render each candidate as:
90
+ 4. **Drift check run before presenting any candidate.** Knowledge lines
91
+ are append-only, so textual repetition alone does not prove the rule is
92
+ still true. For every cluster that survives the recurrence filter:
93
+
94
+ - **Read the lift target.** Open the rule/protocol/skill file you would
95
+ edit. If the current contents already encode a stronger version of
96
+ the cluster's \`action\`, drop the candidate (nothing to lift).
97
+ - **Grep for contradictions.** Run a quick repo search on the cluster's
98
+ \`trigger\` keywords. If recent code or docs contradict the cluster,
99
+ treat the cluster as stale.
100
+ - **Check age.** Inspect \`last_seen_ts\` across the cluster's lines. If
101
+ every contributing line is older than ~90 days with no fresh
102
+ observation, treat the cluster as stale.
103
+ - **Handle stale clusters correctly.** Do **not** silently skip them.
104
+ Append a new superseding \`type: "lesson"\` line to
105
+ \`.cclaw/knowledge.jsonl\` whose \`trigger\` explicitly references the
106
+ old pattern (e.g. \`"when previous rule about X no longer holds: ..."\`)
107
+ and whose \`action\` documents the replacement or archive reason.
108
+ Then drop the candidate from the lift list.
109
+ - **Cite line IDs.** Every surviving candidate must list the concrete
110
+ knowledge line indices (1-based) that back it, not just a
111
+ summary string. This is what makes the lift auditable.
112
+ - Optionally invoke the \`knowledge-curation\` utility skill's
113
+ stale/duplicate/supersede heuristics if you want a second pass.
114
+
115
+ 5. Otherwise, render each candidate as:
87
116
 
88
117
  \`\`\`
89
118
  Candidate: <short title>
90
- Evidence: <knowledge refs>
119
+ Evidence: <knowledge line-ids>
120
+ Freshness: <newest last_seen_ts among evidence lines>
91
121
  Lift target: <rule/protocol/skill file>
92
122
  Change type: <add/update/remove>
93
123
  Expected benefit: <what regressions this prevents>
94
124
  \`\`\`
95
125
 
96
- 5. Present **one** structured question with three options:
126
+ 6. Present **one** structured question with three options:
97
127
  - \`apply-all\` (default) — apply every candidate,
98
128
  - \`apply-selected\` — prompt per-candidate approval next,
99
129
  - \`skip\` — record a skip reason and advance.
100
130
 
101
- 6. For approved candidates:
131
+ 7. For approved candidates:
102
132
  - edit the target file(s) with the lift,
103
133
  - append a \`type: "compound"\` entry to \`.cclaw/knowledge.jsonl\`
104
- describing what was promoted.
134
+ describing what was promoted, including the source line IDs.
105
135
 
106
- 7. Update flow-state \`closeout\`:
136
+ 8. Update flow-state \`closeout\`:
107
137
  - \`compoundCompletedAt\`,
108
138
  - \`compoundPromoted\` (count),
109
139
  - \`compoundSkipped\` (boolean) + \`compoundSkipReason\` when applicable,
@@ -122,6 +152,9 @@ closeout chain's perspective.
122
152
  - \`closeout.compoundCompletedAt\` is set.
123
153
  - \`closeout.shipSubstate === "ready_to_archive"\`.
124
154
  - If lifts were applied, the target files show the edit and at least one
125
- new \`compound\` line exists in \`.cclaw/knowledge.jsonl\`.
155
+ new \`compound\` line exists in \`.cclaw/knowledge.jsonl\`, and the new
156
+ line references the source knowledge line IDs.
157
+ - If drift check demoted any cluster, a new superseding \`lesson\` line
158
+ exists on the same run documenting the replacement.
126
159
  `;
127
160
  }
package/dist/doctor.js CHANGED
@@ -873,7 +873,13 @@ export async function doctorChecks(projectRoot, options = {}) {
873
873
  let missingSchemaV2Fields = 0;
874
874
  let parsedKnowledgeLines = 0;
875
875
  let lowConfidenceLines = 0;
876
+ let staleRawEntries = 0;
876
877
  const triggerActionCounts = new Map();
878
+ // Stale threshold for raw entries: ~90 days with no re-observation.
879
+ // Chosen to match the compound drift checklist language; anything newer is
880
+ // recent enough to trust, anything older deserves a curate/supersede pass.
881
+ const STALE_RAW_THRESHOLD_MS = 90 * 24 * 60 * 60 * 1000;
882
+ const now = Date.now();
877
883
  const requiredV2Fields = [
878
884
  "type",
879
885
  "trigger",
@@ -919,6 +925,14 @@ export async function doctorChecks(projectRoot, options = {}) {
919
925
  if (missing) {
920
926
  missingSchemaV2Fields += 1;
921
927
  }
928
+ const maturity = typeof parsed.maturity === "string" ? parsed.maturity.toLowerCase() : "";
929
+ const lastSeenRaw = typeof parsed.last_seen_ts === "string" ? parsed.last_seen_ts : "";
930
+ if (maturity === "raw" && lastSeenRaw.length > 0) {
931
+ const lastSeenMs = Date.parse(lastSeenRaw);
932
+ if (Number.isFinite(lastSeenMs) && now - lastSeenMs > STALE_RAW_THRESHOLD_MS) {
933
+ staleRawEntries += 1;
934
+ }
935
+ }
922
936
  }
923
937
  catch {
924
938
  malformedKnowledgeLines += 1;
@@ -962,6 +976,15 @@ export async function doctorChecks(projectRoot, options = {}) {
962
976
  ? "no high-frequency repeated trigger/action clusters detected"
963
977
  : `warning: ${repeatedClusters.length} repeated learning cluster(s) detected (>=3 repeats). Consider /cc-ops compound to lift them into rules/skills.`
964
978
  });
979
+ checks.push({
980
+ name: "warning:knowledge:stale_raw_entries",
981
+ ok: true,
982
+ details: parsedKnowledgeLines === 0
983
+ ? "knowledge.jsonl is empty"
984
+ : staleRawEntries === 0
985
+ ? `no raw knowledge entries older than 90 days`
986
+ : `warning: ${staleRawEntries} raw knowledge entry(ies) have last_seen_ts older than 90 days. Run /cc-learn curate or append a superseding entry before the next /cc-ops compound pass.`
987
+ });
965
988
  }
966
989
  checks.push({
967
990
  name: "state:checkpoint_exists",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {