dotmd-cli 0.76.1 → 0.76.3

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/section.mjs +36 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotmd-cli",
3
- "version": "0.76.1",
3
+ "version": "0.76.3",
4
4
  "description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, lifecycle, and AI summaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/section.mjs CHANGED
@@ -9,6 +9,7 @@ export function walkSections(body) {
9
9
  const headingRe = /^(#{1,6})\s+(.+?)\s*$/;
10
10
  const sections = [];
11
11
  let fenceChar = null;
12
+ let h2Ancestor = null;
12
13
 
13
14
  for (let i = 0; i < lines.length; i++) {
14
15
  const line = lines[i];
@@ -22,13 +23,18 @@ export function walkSections(body) {
22
23
  if (fenceChar !== null) continue;
23
24
  const h = line.match(headingRe);
24
25
  if (!h) continue;
26
+ const level = h[1].length;
27
+ const heading = h[2];
28
+ if (level === 1) h2Ancestor = null;
25
29
  sections.push({
26
- level: h[1].length,
27
- heading: h[2],
30
+ level,
31
+ heading,
32
+ h2Ancestor: level > 2 ? h2Ancestor : null,
28
33
  lineStart: i + 1, // 1-indexed
29
34
  lineEnd: lines.length, // patched below
30
35
  bodyLineStart: i + 2,
31
36
  });
37
+ if (level === 2) h2Ancestor = heading;
32
38
  }
33
39
 
34
40
  for (let i = 0; i < sections.length; i++) {
@@ -65,7 +71,7 @@ export function findSection(sections, name) {
65
71
  // Within each tier the order is still priority order, which decides a heading
66
72
  // carrying more than one mark.
67
73
  const GLYPH_PATTERNS = [
68
- { kind: 'shipped', re: /(✅|☑|✔)/ },
74
+ { kind: 'shipped', re: /(✅|☑|✔|✓)/ },
69
75
  { kind: 'skipped', re: /(⏭)/ },
70
76
  { kind: 'in-progress', re: /(🟡|🔄)/ },
71
77
  { kind: 'blocked', re: /(🚧|🔴)/ },
@@ -109,8 +115,9 @@ export function detectMarker(heading) {
109
115
  // and the space, is not \s, and ends the run — so a heading led by the
110
116
  // emoji-presentation form was not a phase heading at all, while the bare
111
117
  // codepoint was. Reported by the owner, 2026-08-16.
112
- const PHASE_DECORATION = String.raw`[\s>*_~\`#-]*(?:[✅🚧⬜🟡⏭☑✔◻☐⬛🔴🔄][︎️]?\s*)*`;
118
+ const PHASE_DECORATION = String.raw`[\s>*_~\`#-]*(?:[✅🚧⬜🟡⏭☑✔✓◻☐⬛🔴🔄][︎️]?\s*)*`;
113
119
  const PHASE_LEAD = new RegExp(`^${PHASE_DECORATION}phase\\b`, 'i');
120
+ const FILES_MANIFEST_ANCESTOR = new RegExp(`^${PHASE_DECORATION}files\\b`, 'i');
114
121
 
115
122
  // "Phase 3 outcome" is commentary ABOUT a phase, not a phase. Counting it
116
123
  // inflates the phase set, and because `findActivePhase` ranks blocked above
@@ -126,9 +133,33 @@ const PHASE_COMMENTARY = new RegExp(
126
133
  'i',
127
134
  );
128
135
 
136
+ // Compound commentary shapes proven against the full platform corpus. Keep
137
+ // these anchored immediately after the phase identifier: the individual nouns
138
+ // all collide with genuine work. In particular, do not add bare `status`,
139
+ // `audit`, `design`, `shape`, `plan`, `pre-plan`, or `gap-check`; the corpus has
140
+ // executable phases with each of those names. The dated gap-check form below is
141
+ // a status report, while `Phase N — gap-check fixes` remains a real phase.
142
+ const PHASE_COMPOUND_COMMENTARY = new RegExp(
143
+ `^${PHASE_DECORATION}phase\\s+\\S+\\s+(?:` +
144
+ `execution\\s+status|` +
145
+ `status\\s+snapshot|` +
146
+ `gap[- ]check\\s+\\d{4}-\\d{2}-\\d{2}|` +
147
+ `audit\\s+results?|` +
148
+ `inventory\\s+findings?|` +
149
+ `deviations\\s*(?:\\+|&|and)\\s*(?:open\\s+questions|notes?)|` +
150
+ `design\\s+[-—:]\\s*researched|` +
151
+ `shape\\s+[-—:]\\s*worked\\s+out|` +
152
+ `sub[- ]phases` +
153
+ `)\\b`,
154
+ 'i',
155
+ );
156
+
129
157
  export function isPhaseHeading(section) {
130
158
  if (section.level !== 3) return false;
131
- return PHASE_LEAD.test(section.heading) && !PHASE_COMMENTARY.test(section.heading);
159
+ if (FILES_MANIFEST_ANCESTOR.test(section.h2Ancestor ?? '')) return false;
160
+ return PHASE_LEAD.test(section.heading)
161
+ && !PHASE_COMMENTARY.test(section.heading)
162
+ && !PHASE_COMPOUND_COMMENTARY.test(section.heading);
132
163
  }
133
164
 
134
165
  // A phase's OWN checklist — the boxes directly under its heading, stopping at