@forwardimpact/libwiki 0.2.21 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/libwiki",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Wiki lifecycle primitives — stable memory for agent teams so coordination persists across sessions.",
5
5
  "keywords": [
6
6
  "wiki",
@@ -75,16 +75,22 @@ const columnCount = (expected) => (s) =>
75
75
  const exists = (s) => (s.exists ? null : {});
76
76
  const expired = (s, ctx) => (s.expires_at < ctx.today ? {} : null);
77
77
 
78
+ // The heading must equal `requiredLine` exactly — a suffixed variant like
79
+ // "### Decision — <summary>" does not satisfy it, but is reported as a
80
+ // near miss so the writer fixes the heading instead of hunting for a
81
+ // "missing" line that is right there.
78
82
  function entryHasDecision(lines, startIdx, requiredLine, stopRe) {
79
83
  let seen = 0;
84
+ let nearMiss = null;
80
85
  for (let j = startIdx + 1; j < lines.length && seen < 5; j++) {
81
86
  const ln = lines[j].trim();
82
87
  if (ln === "") continue;
83
88
  seen++;
84
- if (ln === requiredLine) return true;
85
- if (stopRe.test(lines[j])) return false;
89
+ if (ln === requiredLine) return { found: true };
90
+ if (nearMiss === null && ln.startsWith(requiredLine)) nearMiss = ln;
91
+ if (stopRe.test(lines[j])) break;
86
92
  }
87
- return false;
93
+ return { found: false, nearMiss };
88
94
  }
89
95
 
90
96
  const decisionWithin5 =
@@ -92,12 +98,9 @@ const decisionWithin5 =
92
98
  (s) => {
93
99
  const offenders = [];
94
100
  for (let i = 0; i < s.fileLines.length; i++) {
95
- if (
96
- entryRe.test(s.fileLines[i]) &&
97
- !entryHasDecision(s.fileLines, i, requiredLine, stopRe)
98
- ) {
99
- offenders.push({ lineNo: i + 1 });
100
- }
101
+ if (!entryRe.test(s.fileLines[i])) continue;
102
+ const res = entryHasDecision(s.fileLines, i, requiredLine, stopRe);
103
+ if (!res.found) offenders.push({ lineNo: i + 1, nearMiss: res.nearMiss });
101
104
  }
102
105
  return offenders.length === 0 ? null : offenders;
103
106
  };
@@ -283,8 +286,11 @@ export const RULES = [
283
286
  requiredLine: DECISION_HEADING,
284
287
  stopRe: /^##\s/,
285
288
  }),
286
- message: () => "Entry lacks leading '### Decision'",
287
- hint: "open each '## YYYY-MM-DD' entry with a '### Decision' subheading that summarises the decision recorded in the entry's own narrative — do not invent rationale the entry does not support",
289
+ message: (_s, r) =>
290
+ r.nearMiss
291
+ ? `Entry opens with '${r.nearMiss}'; the heading must be exactly '${DECISION_HEADING}' — move the suffix into the body`
292
+ : `Entry lacks a line that is exactly '${DECISION_HEADING}'`,
293
+ hint: `open each '## YYYY-MM-DD' entry with a line containing exactly '${DECISION_HEADING}' (no suffix — the check is an exact match); put the one-line summary in the body below it, drawn from the entry's own narrative — do not invent rationale the entry does not support`,
288
294
  },
289
295
 
290
296
  // -- Weekly logs (sealed parts) --
package/src/boot.js CHANGED
@@ -98,6 +98,10 @@ function parseStoryboardItems(text, agent) {
98
98
  inAgent = h3Match[1].toLowerCase().startsWith(agent.toLowerCase());
99
99
  continue;
100
100
  }
101
+ if (/^#{1,2} /.test(line)) {
102
+ inAgent = false;
103
+ continue;
104
+ }
101
105
  if (!inAgent) continue;
102
106
  const bullet = line.match(/^[-*]\s+(.+)$/);
103
107
  if (bullet) {