pan-wizard 3.32.0 → 3.34.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.
Files changed (209) hide show
  1. package/README.md +23 -20
  2. package/agents/pan-conductor.md +18 -17
  3. package/agents/pan-counterfactual.md +2 -2
  4. package/agents/pan-debugger.md +27 -8
  5. package/agents/pan-design-checker.md +1 -1
  6. package/agents/pan-designer.md +2 -2
  7. package/agents/pan-distiller.md +3 -3
  8. package/agents/pan-document_code.md +16 -16
  9. package/agents/pan-executor.md +15 -11
  10. package/agents/pan-experiment-runner.md +15 -17
  11. package/agents/pan-hardener.md +15 -15
  12. package/agents/pan-integration-checker.md +2 -2
  13. package/agents/pan-knowledge.md +2 -2
  14. package/agents/pan-meta-reviewer.md +5 -5
  15. package/agents/pan-optimizer.md +40 -44
  16. package/agents/pan-phase-researcher.md +10 -10
  17. package/agents/pan-plan-checker.md +36 -30
  18. package/agents/pan-planner.md +77 -53
  19. package/agents/pan-previewer.md +1 -1
  20. package/agents/pan-project-researcher.md +21 -78
  21. package/agents/pan-release.md +1 -1
  22. package/agents/pan-research-synthesizer.md +2 -2
  23. package/agents/pan-reviewer.md +24 -2
  24. package/agents/pan-roadmapper.md +20 -62
  25. package/agents/pan-verifier.md +53 -30
  26. package/bin/install-lib.cjs +228 -38
  27. package/bin/install.js +86 -15
  28. package/commands/pan/army.md +16 -12
  29. package/commands/pan/assumptions.md +2 -2
  30. package/commands/pan/audit-deployment.md +11 -12
  31. package/commands/pan/cost.md +8 -4
  32. package/commands/pan/debug.md +10 -7
  33. package/commands/pan/design-phase.md +3 -3
  34. package/commands/pan/discuss-phase.md +2 -0
  35. package/commands/pan/exec-phase.md +21 -37
  36. package/commands/pan/experiment.md +11 -10
  37. package/commands/pan/focus-auto.md +33 -27
  38. package/commands/pan/focus-design.md +9 -6
  39. package/commands/pan/focus-doc-audit.md +4 -3
  40. package/commands/pan/focus-drift-walking.md +4 -3
  41. package/commands/pan/focus-exec.md +7 -7
  42. package/commands/pan/focus-plan.md +12 -11
  43. package/commands/pan/focus-scan.md +7 -10
  44. package/commands/pan/focus-sync.md +3 -2
  45. package/commands/pan/git.md +8 -8
  46. package/commands/pan/health.md +2 -2
  47. package/commands/pan/hud.md +3 -2
  48. package/commands/pan/hygiene.md +14 -7
  49. package/commands/pan/knowledge.md +9 -9
  50. package/commands/pan/learn.md +10 -7
  51. package/commands/pan/links.md +4 -1
  52. package/commands/pan/map-codebase.md +5 -5
  53. package/commands/pan/mcp-bridge.md +18 -18
  54. package/commands/pan/milestone-done.md +7 -5
  55. package/commands/pan/milestone-new.md +1 -1
  56. package/commands/pan/new-project.md +10 -10
  57. package/commands/pan/optimize.md +16 -14
  58. package/commands/pan/patches.md +2 -6
  59. package/commands/pan/pause.md +1 -1
  60. package/commands/pan/phase-tests.md +4 -3
  61. package/commands/pan/plan-phase.md +9 -19
  62. package/commands/pan/preview.md +1 -1
  63. package/commands/pan/profile.md +4 -4
  64. package/commands/pan/quick.md +1 -1
  65. package/commands/pan/remove-phase.md +1 -1
  66. package/commands/pan/report.md +2 -2
  67. package/commands/pan/research-phase.md +14 -15
  68. package/commands/pan/resume.md +9 -11
  69. package/commands/pan/retro.md +5 -3
  70. package/commands/pan/review-deep.md +10 -10
  71. package/commands/pan/settings.md +1 -1
  72. package/commands/pan/todo-add.md +2 -2
  73. package/commands/pan/todo-check.md +2 -2
  74. package/commands/pan/verify-phase.md +0 -11
  75. package/commands/pan/what-if.md +2 -2
  76. package/hooks/dist/pan-context-monitor.js +343 -44
  77. package/hooks/dist/pan-cost-logger.js +30 -0
  78. package/hooks/dist/pan-state-reinject.js +140 -15
  79. package/hooks/dist/pan-statusline.js +68 -22
  80. package/hooks/dist/pan-stop-guard.js +7 -2
  81. package/package.json +1 -1
  82. package/pan-wizard-core/bin/lib/agents-md.cjs +48 -7
  83. package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
  84. package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
  85. package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
  86. package/pan-wizard-core/bin/lib/commands.cjs +126 -5
  87. package/pan-wizard-core/bin/lib/config.cjs +112 -1
  88. package/pan-wizard-core/bin/lib/constants.cjs +29 -15
  89. package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
  90. package/pan-wizard-core/bin/lib/core.cjs +77 -23
  91. package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
  92. package/pan-wizard-core/bin/lib/cost.cjs +149 -10
  93. package/pan-wizard-core/bin/lib/distill.cjs +6 -3
  94. package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
  95. package/pan-wizard-core/bin/lib/findings.cjs +15 -4
  96. package/pan-wizard-core/bin/lib/focus.cjs +13 -4
  97. package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
  98. package/pan-wizard-core/bin/lib/hygiene.cjs +178 -17
  99. package/pan-wizard-core/bin/lib/init.cjs +12 -5
  100. package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
  101. package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
  102. package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
  103. package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
  104. package/pan-wizard-core/bin/lib/memory.cjs +508 -54
  105. package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
  106. package/pan-wizard-core/bin/lib/optimize.cjs +84 -16
  107. package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
  108. package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
  109. package/pan-wizard-core/bin/lib/phase.cjs +67 -15
  110. package/pan-wizard-core/bin/lib/preview.cjs +3 -1
  111. package/pan-wizard-core/bin/lib/review-deep.cjs +2 -2
  112. package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
  113. package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
  114. package/pan-wizard-core/bin/lib/runner.cjs +2 -1
  115. package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
  116. package/pan-wizard-core/bin/lib/state.cjs +47 -31
  117. package/pan-wizard-core/bin/lib/template.cjs +11 -4
  118. package/pan-wizard-core/bin/lib/verdict.cjs +54 -4
  119. package/pan-wizard-core/bin/lib/verify-drift.cjs +14 -5
  120. package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
  121. package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
  122. package/pan-wizard-core/bin/lib/verify-scope.cjs +74 -4
  123. package/pan-wizard-core/bin/lib/verify.cjs +117 -35
  124. package/pan-wizard-core/bin/lib/whatif.cjs +5 -3
  125. package/pan-wizard-core/bin/pan-tools.cjs +111 -17
  126. package/pan-wizard-core/learnings/index.json +5 -5
  127. package/pan-wizard-core/mcp/server.cjs +18 -9
  128. package/pan-wizard-core/opencode/pan-wizard.js +111 -0
  129. package/pan-wizard-core/references/checkpoints.md +7 -5
  130. package/pan-wizard-core/references/continuation-format.md +3 -3
  131. package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
  132. package/pan-wizard-core/references/design-methodology.md +1 -1
  133. package/pan-wizard-core/references/git-integration.md +4 -6
  134. package/pan-wizard-core/references/git-planning-commit.md +2 -2
  135. package/pan-wizard-core/references/guardrails.md +2 -2
  136. package/pan-wizard-core/references/handoff-decisions.md +3 -3
  137. package/pan-wizard-core/references/model-profile-resolution.md +5 -5
  138. package/pan-wizard-core/references/model-profiles.md +14 -9
  139. package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
  140. package/pan-wizard-core/references/planning-config.md +4 -4
  141. package/pan-wizard-core/references/tdd.md +10 -1
  142. package/pan-wizard-core/references/verdict-contract.md +2 -1
  143. package/pan-wizard-core/references/verification-patterns.md +5 -5
  144. package/pan-wizard-core/templates/codebase/architecture.md +2 -2
  145. package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
  146. package/pan-wizard-core/templates/codebase/concerns.md +3 -3
  147. package/pan-wizard-core/templates/codebase/integrations.md +6 -6
  148. package/pan-wizard-core/templates/codebase/relationships.md +2 -2
  149. package/pan-wizard-core/templates/codebase/stack.md +2 -2
  150. package/pan-wizard-core/templates/codebase/structure.md +4 -4
  151. package/pan-wizard-core/templates/codebase/testing.md +4 -4
  152. package/pan-wizard-core/templates/continue-here.md +1 -1
  153. package/pan-wizard-core/templates/debug-subagent-prompt.md +3 -2
  154. package/pan-wizard-core/templates/debug.md +15 -1
  155. package/pan-wizard-core/templates/discovery.md +1 -1
  156. package/pan-wizard-core/templates/milestone-archive.md +6 -6
  157. package/pan-wizard-core/templates/milestone.md +1 -1
  158. package/pan-wizard-core/templates/phase-prompt.md +31 -18
  159. package/pan-wizard-core/templates/planner-subagent-prompt.md +3 -5
  160. package/pan-wizard-core/templates/research-project/architecture.md +3 -3
  161. package/pan-wizard-core/templates/research-project/features.md +1 -1
  162. package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
  163. package/pan-wizard-core/templates/research-project/stack.md +3 -3
  164. package/pan-wizard-core/templates/research.md +5 -5
  165. package/pan-wizard-core/templates/retrospective.md +1 -1
  166. package/pan-wizard-core/templates/roadmap.md +15 -15
  167. package/pan-wizard-core/templates/summary-complex.md +1 -0
  168. package/pan-wizard-core/templates/summary-minimal.md +1 -0
  169. package/pan-wizard-core/templates/summary-standard.md +2 -1
  170. package/pan-wizard-core/templates/summary.md +18 -2
  171. package/pan-wizard-core/templates/uat.md +3 -3
  172. package/pan-wizard-core/templates/user-setup.md +8 -8
  173. package/pan-wizard-core/templates/verification-report.md +54 -4
  174. package/pan-wizard-core/workflows/add-phase.md +1 -1
  175. package/pan-wizard-core/workflows/assumptions.md +1 -1
  176. package/pan-wizard-core/workflows/discuss-phase.md +9 -8
  177. package/pan-wizard-core/workflows/exec-phase.md +34 -60
  178. package/pan-wizard-core/workflows/execute-plan.md +8 -7
  179. package/pan-wizard-core/workflows/health.md +13 -11
  180. package/pan-wizard-core/workflows/help.md +8 -9
  181. package/pan-wizard-core/workflows/learn.md +13 -11
  182. package/pan-wizard-core/workflows/map-codebase.md +10 -10
  183. package/pan-wizard-core/workflows/milestone-audit.md +10 -10
  184. package/pan-wizard-core/workflows/milestone-gaps.md +17 -24
  185. package/pan-wizard-core/workflows/milestone-new.md +7 -6
  186. package/pan-wizard-core/workflows/new-project.md +10 -10
  187. package/pan-wizard-core/workflows/optimize.md +11 -9
  188. package/pan-wizard-core/workflows/pause.md +6 -1
  189. package/pan-wizard-core/workflows/phase-tests.md +4 -4
  190. package/pan-wizard-core/workflows/plan-phase.md +31 -34
  191. package/pan-wizard-core/workflows/profile.md +2 -2
  192. package/pan-wizard-core/workflows/progress.md +5 -7
  193. package/pan-wizard-core/workflows/quick.md +10 -8
  194. package/pan-wizard-core/workflows/remove-phase.md +1 -1
  195. package/pan-wizard-core/workflows/research-phase.md +9 -5
  196. package/pan-wizard-core/workflows/resume-project.md +4 -6
  197. package/pan-wizard-core/workflows/retro.md +3 -1
  198. package/pan-wizard-core/workflows/settings.md +16 -24
  199. package/pan-wizard-core/workflows/todo-check.md +1 -1
  200. package/pan-wizard-core/workflows/transition.md +12 -12
  201. package/pan-wizard-core/workflows/update.md +25 -17
  202. package/pan-wizard-core/workflows/verify-phase.md +24 -17
  203. package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
  204. package/pan-zcode/README.md +7 -7
  205. package/pan-zcode/bin/install-zcode.js +1 -1
  206. package/scripts/build-agent-plugin.js +19 -5
  207. package/scripts/build-plugin.js +3 -1
  208. package/scripts/generate-skills-docs.py +13 -4
  209. package/scripts/test-surface.cjs +4 -0
@@ -168,15 +168,102 @@ function readIndex(sourceRoot) {
168
168
  }
169
169
  }
170
170
 
171
+ // ─── Cue ranking (memory optimisation O5) ───────────────────────────────────
172
+ //
173
+ // The role ranking loads the same topics whatever the task, filling the budget
174
+ // smallest-first, so a large topic the task needs (migration-safety for a
175
+ // migration) drops out. With a cue — the phase objective and the files it
176
+ // touches — topics are matched to the task: the cue's words against each topic's
177
+ // name, pattern summaries and rules. Lexical, like `memory select`'s scorer, with
178
+ // two changes topic text needs: common words are ignored, and a word meets its
179
+ // own inflections (plurals fold; "corrupting" meets "corrupt", "parsed" "parse").
180
+
181
+ const CUE_STOPWORDS = new Set((
182
+ 'the and for with that this from into onto over under when then than them they their there these those '
183
+ + 'are was were been being have has had not but all any each every per its our your you who what which '
184
+ + 'can could should would will shall may might must also only just very more most less such same other '
185
+ + 'add adds added adding make makes made making use uses used using new old get gets set sets run runs '
186
+ + 'fix fixes fixed one two way ways out off how why where while after before about again still both '
187
+ + 'file files code thing things project phase plan plans task tasks work works working change changes'
188
+ ).split(' '));
189
+
190
+ /**
191
+ * Fold a plural: "retries" → "retry", "classes" → "class", "matches" → "match",
192
+ * "failures" → "failure", "cases" → "case"; "class" and "status" stay.
193
+ */
194
+ function foldPlural(w) {
195
+ if (w.length > 4 && w.endsWith('ies')) return `${w.slice(0, -3)}y`;
196
+ if (w.length > 5 && w.endsWith('sses')) return w.slice(0, -2);
197
+ if (w.length > 4 && /(x|z|ch|sh)es$/.test(w)) return w.slice(0, -2);
198
+ if (w.length > 3 && w.endsWith('s') && !/(ss|us|is)$/.test(w)) return w.slice(0, -1);
199
+ return w;
200
+ }
201
+
202
+ /** The content words of a text (3+ letters, plurals folded, no stopwords). */
203
+ function cueTerms(text) {
204
+ const out = new Set();
205
+ for (const w of String(text || '').toLowerCase().match(/[a-z0-9]{3,}/g) || []) {
206
+ const f = foldPlural(w);
207
+ if (!CUE_STOPWORDS.has(w) && !CUE_STOPWORDS.has(f)) out.add(f);
208
+ }
209
+ return out;
210
+ }
211
+
212
+ /** Two words meet when equal, or when the shorter (4+ letters) begins the longer, at most 3 letters apart. */
213
+ function termsMeet(a, b) {
214
+ if (a === b) return true;
215
+ const [s, l] = a.length <= b.length ? [a, b] : [b, a];
216
+ return s.length >= 4 && l.length - s.length <= 3 && l.startsWith(s);
217
+ }
218
+
219
+ function setHasTerm(set, t) {
220
+ if (set.has(t)) return true;
221
+ for (const u of set) if (termsMeet(t, u)) return true;
222
+ return false;
223
+ }
224
+
225
+ /**
226
+ * Each topic's terms from the store: its name, and its patterns' summaries and
227
+ * rules (not their evidence, which is long and incidental).
228
+ * @returns {Map<string, {name: Set<string>, text: Set<string>}>}
229
+ */
230
+ function topicTermsFromStore(sourceRoot) {
231
+ const { extractFieldSection } = require('./learn-lint.cjs');
232
+ const text = new Map();
233
+ for (const p of collectAllPatterns(sourceRoot)) {
234
+ const prev = text.get(p.topic) || '';
235
+ text.set(p.topic, `${prev}\n${p.summary}\n${extractFieldSection(p.body, 'Rule')}`);
236
+ }
237
+ const out = new Map();
238
+ for (const [topic, t] of text) out.set(topic, { name: cueTerms(topic.replace(/-/g, ' ')), text: cueTerms(t) });
239
+ return out;
240
+ }
241
+
242
+ /** How well a topic matches the cue: 2 per cue term in its name, 1 per term in its text. */
243
+ function cueScore(cue, terms) {
244
+ if (!terms) return 0;
245
+ let s = 0;
246
+ for (const t of cue) s += setHasTerm(terms.name, t) ? 2 : setHasTerm(terms.text, t) ? 1 : 0;
247
+ return s;
248
+ }
249
+
171
250
  /**
172
251
  * Query: what topics should agent X load at this point?
173
252
  *
253
+ * With `opts.cue` and `opts.topicTerms`, topics are matched to the task (mode
254
+ * `cue`): every topic scoring at least half the best match is a candidate
255
+ * whatever its role relevance, ranked by cue score, then relevance, then size;
256
+ * the rest are not loaded. When nothing matches, or there is no cue, the role
257
+ * ranking applies (mode `role`): relevance, then smallest first.
258
+ *
174
259
  * @param {object} index - the index object
175
260
  * @param {object} opts
176
261
  * @param {string} opts.agent - 'planner' | 'executor' | 'verifier' | 'reviewer'
177
- * @param {string} [opts.minRelevance] - 'high' | 'medium' | 'low' (default 'medium')
262
+ * @param {string} [opts.minRelevance] - 'high' | 'medium' | 'low' (default 'medium'; role mode)
178
263
  * @param {number} [opts.tokenBudget] - max tokens to fit (default 5000)
179
- * @returns {object} { topics: [{name, scope, file, tokens, relevance}], total_tokens, dropped: [...] }
264
+ * @param {string} [opts.cue] - the phase objective and the files it touches
265
+ * @param {Map} [opts.topicTerms] - from topicTermsFromStore()
266
+ * @returns {object} { selected: [{name, scope, file, tokens, relevance, cue_score?}], total_tokens, dropped: [...], mode }
180
267
  */
181
268
  function topicsForAgent(index, opts) {
182
269
  const agent = opts.agent;
@@ -185,14 +272,22 @@ function topicsForAgent(index, opts) {
185
272
  const ranks = { high: 3, medium: 2, low: 1 };
186
273
  const minRank = ranks[minLevel] || 2;
187
274
 
188
- const ranked = [];
189
- for (const t of index.topics) {
275
+ const cue = cueTerms(opts.cue);
276
+ const scored = index.topics.map(t => {
190
277
  const rel = (t.agent_relevance || {})[agent] || 'low';
191
- const rank = ranks[rel] || 1;
192
- if (rank < minRank) continue;
193
- ranked.push({ ...t, _relevance: rel, _rank: rank });
194
- }
195
- ranked.sort((a, b) => b._rank - a._rank || a.size_tokens_est - b.size_tokens_est);
278
+ return { ...t, _relevance: rel, _rank: ranks[rel] || 1, _cue: cue.size ? cueScore(cue, opts.topicTerms && opts.topicTerms.get(t.name)) : 0 };
279
+ });
280
+ // A topic must score at least half the best match: a single incidental shared
281
+ // word should not load a topic beside the ones the task is about. On the golden
282
+ // set (tests/fixtures/learn-cue-golden.json) this cut raised precision from
283
+ // 0.22 to 0.37 for a recall of 0.76 instead of 0.81.
284
+ const top = Math.max(0, ...scored.map(t => t._cue));
285
+ const matched = scored.filter(t => t._cue > 0 && t._cue >= Math.ceil(top / 2));
286
+ const mode = matched.length ? 'cue' : 'role';
287
+
288
+ const ranked = mode === 'cue'
289
+ ? matched.sort((a, b) => b._cue - a._cue || b._rank - a._rank || a.size_tokens_est - b.size_tokens_est)
290
+ : scored.filter(t => t._rank >= minRank).sort((a, b) => b._rank - a._rank || a.size_tokens_est - b.size_tokens_est);
196
291
 
197
292
  const selected = [];
198
293
  const dropped = [];
@@ -209,6 +304,7 @@ function topicsForAgent(index, opts) {
209
304
  tokens: t.size_tokens_est,
210
305
  relevance: t._relevance,
211
306
  patterns: t.patterns,
307
+ ...(mode === 'cue' ? { cue_score: t._cue } : {}),
212
308
  });
213
309
  total += t.size_tokens_est;
214
310
  }
@@ -217,6 +313,8 @@ function topicsForAgent(index, opts) {
217
313
  agent,
218
314
  min_relevance: minLevel,
219
315
  token_budget: budget,
316
+ mode,
317
+ ...(opts.cue ? { cue: opts.cue } : {}),
220
318
  selected,
221
319
  dropped,
222
320
  total_tokens: total,
@@ -237,7 +335,8 @@ function cmdBuildIndex(sourceRoot) {
237
335
 
238
336
  function cmdTopicsFor(sourceRoot, opts) {
239
337
  const index = readIndex(sourceRoot);
240
- return topicsForAgent(index, opts);
338
+ const topicTerms = opts.cue ? topicTermsFromStore(sourceRoot) : undefined;
339
+ return topicsForAgent(index, { ...opts, topicTerms });
241
340
  }
242
341
 
243
342
  module.exports = {
@@ -245,6 +344,8 @@ module.exports = {
245
344
  writeIndex,
246
345
  readIndex,
247
346
  topicsForAgent,
347
+ topicTermsFromStore,
348
+ cueTerms,
248
349
  cmdBuildIndex,
249
350
  cmdTopicsFor,
250
351
  INDEX_PATH_REL,
@@ -11,6 +11,8 @@
11
11
  * (candidate for internal/ scope rather than universal/)
12
12
  * - L-005: Revision marker (rN) appended in body but no supersession field
13
13
  * - L-006: Universal-scope pattern cites an internal pattern id (dangles after install)
14
+ * - L-007: A pattern's `cites` no longer holds — the cited file or symbol is gone
15
+ * from the tree that holds the store (memory optimisation O4)
14
16
  *
15
17
  * These are not patterns themselves — they're integrity checks for the
16
18
  * pattern store. Wired to `pan-tools learn lint`.
@@ -45,7 +47,9 @@ function getLearningsDir(sourceRoot, scope) {
45
47
  }
46
48
 
47
49
  function readTopicFile(filePath) {
48
- const content = fs.readFileSync(filePath, 'utf-8');
50
+ // LF: an install committed with core.autocrlf=true holds these files as CRLF,
51
+ // and the LF-only match below then read every topic as having no patterns.
52
+ const content = fs.readFileSync(filePath, 'utf-8').replace(/^/, '').replace(/\r\n/g, '\n');
49
53
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
50
54
  if (!fmMatch) return { frontmatter: { patterns: [] }, body: content };
51
55
  const fmText = fmMatch[1];
@@ -113,6 +117,7 @@ function collectAllPatterns(sourceRoot) {
113
117
  source_experiments: Array.isArray(p.source_experiments) ? p.source_experiments : [],
114
118
  superseded_by: p.superseded_by || null,
115
119
  superseded_id: p.superseded_id || null,
120
+ cites: Array.isArray(p.cites) ? p.cites : [],
116
121
  body: patternBody,
117
122
  });
118
123
  }
@@ -170,7 +175,7 @@ const PATTERN_REF_RE = /\bP-(?:[A-Z]+-)?\d+(?:-r\d+)?\b/g;
170
175
  * pattern_count, file_count, scopes
171
176
  * }
172
177
  */
173
- function lintPatterns(patterns) {
178
+ function lintPatterns(patterns, opts = {}) {
174
179
  const violations = [];
175
180
 
176
181
  const idIndex = new Map(); // id -> [{scope, topic, file}, ...]
@@ -297,6 +302,28 @@ function lintPatterns(patterns) {
297
302
  }
298
303
  }
299
304
 
305
+ // L-007: a citation that no longer holds. A pattern may name the code it rests
306
+ // on (`cites: [pan-wizard-core/bin/lib/x.cjs#symbol]`, relative to the tree
307
+ // holding the store). When that code is gone the pattern is stale evidence, the
308
+ // way a memory entry is (O4). Checked only when the caller passes the root.
309
+ if (opts.sourceRoot) {
310
+ const { citationProblem } = require('./memory.cjs');
311
+ for (const p of patterns) {
312
+ for (const c of p.cites || []) {
313
+ const problem = citationProblem(opts.sourceRoot, c);
314
+ if (!problem) continue;
315
+ violations.push({
316
+ code: 'L-007',
317
+ severity: 'error',
318
+ pattern_id: p.id,
319
+ file: p.file,
320
+ message: `Pattern "${p.id}" cites ${c}, which no longer holds (${problem}) — update the pattern or retire it`,
321
+ cite: c,
322
+ });
323
+ }
324
+ }
325
+ }
326
+
300
327
  return {
301
328
  violations,
302
329
  pattern_count: patterns.length,
@@ -316,7 +343,7 @@ function cmdLearnLint(sourceRoot, opts = {}) {
316
343
  const filtered = opts.scope
317
344
  ? all.filter(p => p.scope === opts.scope)
318
345
  : all;
319
- const result = lintPatterns(filtered);
346
+ const result = lintPatterns(filtered, { sourceRoot });
320
347
 
321
348
  const errors = result.violations.filter(v => v.severity === 'error').length;
322
349
  const warnings = result.violations.filter(v => v.severity === 'warning').length;
@@ -17,7 +17,7 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const { output, safeReadFile } = require('./core.cjs');
20
+ const { output, safeReadFile, toLf, dominantEol, withEol } = require('./core.cjs');
21
21
  const { planningPath } = require('./utils.cjs');
22
22
  const { writeStateMd } = require('./state.cjs');
23
23
  const { readMemory, parseEntries, listMemoryAgents, compactMemory, DEFAULT_MAX_ENTRIES, MEMORY_DIR } = require('./memory.cjs');
@@ -167,7 +167,11 @@ function reconcileBullets(lines, keepN, archived, quarantined) {
167
167
  */
168
168
  function optimizeStateContent(content, opts = {}) {
169
169
  const keepN = Number.isFinite(opts.keep) && opts.keep > 0 ? opts.keep : DEFAULT_KEEP;
170
- const sections = parseSections(content);
170
+ // Work on LF and hand the file back in its own ending. On CRLF input
171
+ // headingText's `(.*)$` could not cross the trailing '\r', so no heading
172
+ // matched APPEND_HEAVY and every Windows project read as already lean.
173
+ const eol = dominantEol(content);
174
+ const sections = parseSections(toLf(content));
171
175
  const archived = [];
172
176
  const quarantined = [];
173
177
  const sectionsTouched = [];
@@ -184,7 +188,7 @@ function optimizeStateContent(content, opts = {}) {
184
188
  }
185
189
  }
186
190
 
187
- return { content: changed ? joinSections(sections) : content, changed, archived, quarantined, sectionsTouched };
191
+ return { content: changed ? withEol(joinSections(sections), eol) : content, changed, archived, quarantined, sectionsTouched };
188
192
  }
189
193
 
190
194
  // ─── Command ────────────────────────────────────────────────────────────────
@@ -339,5 +343,5 @@ function maybeAutoOptimizeMemory(cwd, opts = {}) {
339
343
  module.exports = {
340
344
  optimizeStateContent, reconcileBullets, parseSections, joinSections, cmdMemoryOptimize,
341
345
  maybeAutoOptimizeMemory, autoOptimizeEnabled, isSuspiciousDirective,
342
- APPEND_HEAVY, PLACEHOLDER, DIRECTIVE_PATTERNS, DEFAULT_KEEP, STATE_ARCHIVE_FILE, QUARANTINE_FILE,
346
+ APPEND_HEAVY, PLACEHOLDER, DIRECTIVE_PATTERNS, DEFAULT_KEEP, STATE_ARCHIVE_FILE, QUARANTINE_FILE, QUARANTINE_HEADER,
343
347
  };