runlist 0.95.5 → 0.95.6
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/README.md +2 -2
- package/package.json +1 -1
- package/plugins/runlist-codex/.codex-plugin/plugin.json +1 -1
- package/src/extractors.mjs +1 -1
- package/src/index.mjs +3 -1
- package/src/parse-cache.mjs +1 -1
- package/src/section.mjs +12 -5
- package/src/validate.mjs +2 -2
package/README.md
CHANGED
|
@@ -47,8 +47,8 @@ controls are experimental; live retrieval depends on a compatible external
|
|
|
47
47
|
provider. Local chat and push are deferred.
|
|
48
48
|
|
|
49
49
|
<!-- runlist:release:start -->
|
|
50
|
-
CLI and desktop release version: **0.95.
|
|
51
|
-
[Mac installer and release notes](https://github.com/reowens/runlist/releases/tag/desktop-v0.95.
|
|
50
|
+
CLI and desktop release version: **0.95.6**.
|
|
51
|
+
[Mac installer and release notes](https://github.com/reowens/runlist/releases/tag/desktop-v0.95.6).
|
|
52
52
|
Normal product releases publish both components; an installed Mac app is updated with rollback after it has quit.
|
|
53
53
|
<!-- runlist:release:end -->
|
|
54
54
|
|
package/package.json
CHANGED
package/src/extractors.mjs
CHANGED
|
@@ -81,7 +81,7 @@ export function extractBodyLinks(body) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function extractChecklistCounts(body) {
|
|
84
|
-
const matches = [...body.matchAll(/^\s*[-*]\s+\[([ xX])\]\s+/gm)];
|
|
84
|
+
const matches = [...body.matchAll(/^\s*[-*]\s+\[([ xX~])\]\s+/gm)];
|
|
85
85
|
let completed = 0;
|
|
86
86
|
let open = 0;
|
|
87
87
|
|
package/src/index.mjs
CHANGED
|
@@ -335,12 +335,14 @@ export function parseDocFile(filePath, config, opts = {}) {
|
|
|
335
335
|
if (text && text.shipsMetadataVersion !== 1) text = null;
|
|
336
336
|
// Validation reads the body itself, so only a fast build can skip the read.
|
|
337
337
|
let body = null;
|
|
338
|
+
let bodyLineOffset = 0;
|
|
338
339
|
let rawSource = null;
|
|
339
340
|
if (!text || !fast) {
|
|
340
341
|
const raw = opts.source ?? readFileSync(filePath, 'utf8');
|
|
341
342
|
rawSource = raw;
|
|
342
343
|
const extracted = extractFrontmatter(raw);
|
|
343
344
|
body = extracted.body;
|
|
345
|
+
bodyLineOffset = extracted.bodyLineOffset;
|
|
344
346
|
// A file rewritten between the stat and the read no longer matches its stamp.
|
|
345
347
|
if (text && Buffer.byteLength(raw) !== stampSize(stamp)) text = null;
|
|
346
348
|
if (!text) {
|
|
@@ -471,7 +473,7 @@ export function parseDocFile(filePath, config, opts = {}) {
|
|
|
471
473
|
return doc;
|
|
472
474
|
}
|
|
473
475
|
validateDoc(doc, parsedFrontmatter, headingTitle, config);
|
|
474
|
-
validatePlanShape(doc, body, parsedFrontmatter, config);
|
|
476
|
+
validatePlanShape(doc, body, parsedFrontmatter, config, bodyLineOffset);
|
|
475
477
|
validateDocShape(doc, body, parsedFrontmatter, config);
|
|
476
478
|
}
|
|
477
479
|
return doc;
|
package/src/parse-cache.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { readEnv, stateDir } from './naming.mjs';
|
|
|
17
17
|
// a missing, unreadable or foreign-version file is treated as empty, a failed
|
|
18
18
|
// write is dropped, and `RUNLIST_NO_PARSE_CACHE=1` turns it off.
|
|
19
19
|
|
|
20
|
-
const SCHEMA =
|
|
20
|
+
const SCHEMA = 2;
|
|
21
21
|
const FILE_NAME = 'parse-cache';
|
|
22
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const VERSION = (() => {
|
package/src/section.mjs
CHANGED
|
@@ -73,7 +73,7 @@ export function findSection(sections, name) {
|
|
|
73
73
|
const GLYPH_PATTERNS = [
|
|
74
74
|
{ kind: 'shipped', re: /(✅|☑|✔|✓)/ },
|
|
75
75
|
{ kind: 'skipped', re: /(⏭)/ },
|
|
76
|
-
{ kind: 'in-progress', re: /(
|
|
76
|
+
{ kind: 'in-progress', re: /(🟡|🟨|🔄)/ },
|
|
77
77
|
{ kind: 'blocked', re: /(🚧|🔴)/ },
|
|
78
78
|
{ kind: 'todo', re: /(⬜|⬛|◻|☐)/ },
|
|
79
79
|
];
|
|
@@ -115,7 +115,7 @@ export function detectMarker(heading) {
|
|
|
115
115
|
// and the space, is not \s, and ends the run — so a heading led by the
|
|
116
116
|
// emoji-presentation form was not a phase heading at all, while the bare
|
|
117
117
|
// codepoint was. Reported by the owner, 2026-08-16.
|
|
118
|
-
const PHASE_DECORATION = String.raw`[\s>*_~\`#-]*(?:[
|
|
118
|
+
const PHASE_DECORATION = String.raw`[\s>*_~\`#-]*(?:[✅🚧⬜🟡🟨⏭☑✔✓◻☐⬛🔴🔄][︎️]?\s*)*`;
|
|
119
119
|
const PHASE_LEAD = new RegExp(`^${PHASE_DECORATION}phase\\b`, 'i');
|
|
120
120
|
const FILES_MANIFEST_ANCESTOR = new RegExp(`^${PHASE_DECORATION}files\\b`, 'i');
|
|
121
121
|
|
|
@@ -157,18 +157,25 @@ const PHASE_COMPOUND_COMMENTARY = new RegExp(
|
|
|
157
157
|
// Dated execution receipts describe an attempt or acceptance record, not a work phase.
|
|
158
158
|
const PHASE_DATED_RECEIPT = new RegExp(
|
|
159
159
|
`^${PHASE_DECORATION}phase\\s+\\S+\\s+` +
|
|
160
|
-
`(?:execution|completed[- ]work\\s+record|personal\\s+billing\\s+acceptance|portal\\s+attempt
|
|
160
|
+
`(?:execution|completed[- ]work\\s+record|personal\\s+billing\\s+acceptance|portal\\s+attempt|` +
|
|
161
|
+
`receipts?|state|verification|build|shaping\\s+walk)` +
|
|
161
162
|
`\\s*,\\s*(?:closed\\s+)?(?:[A-Z][a-z]+\\s+\\d{1,2},\\s+\\d{4}|\\d{4}-\\d{2}-\\d{2})\\b`,
|
|
162
163
|
'i',
|
|
163
164
|
);
|
|
164
165
|
|
|
166
|
+
const PHASE_RECORDED_MECHANICS = new RegExp(
|
|
167
|
+
`^${PHASE_DECORATION}phase\\s+\\S+\\s+mechanics\\s*,\\s*recorded\\b`,
|
|
168
|
+
'i',
|
|
169
|
+
);
|
|
170
|
+
|
|
165
171
|
export function isPhaseHeading(section) {
|
|
166
172
|
if (section.level !== 3) return false;
|
|
167
173
|
if (FILES_MANIFEST_ANCESTOR.test(section.h2Ancestor ?? '')) return false;
|
|
168
174
|
return PHASE_LEAD.test(section.heading)
|
|
169
175
|
&& !PHASE_COMMENTARY.test(section.heading)
|
|
170
176
|
&& !PHASE_COMPOUND_COMMENTARY.test(section.heading)
|
|
171
|
-
&& !PHASE_DATED_RECEIPT.test(section.heading)
|
|
177
|
+
&& !PHASE_DATED_RECEIPT.test(section.heading)
|
|
178
|
+
&& !PHASE_RECORDED_MECHANICS.test(section.heading);
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
// A phase's OWN checklist — the boxes directly under its heading, stopping at
|
|
@@ -179,7 +186,7 @@ export function phaseTally(section) {
|
|
|
179
186
|
for (const line of String(section?.body ?? '').split('\n')) {
|
|
180
187
|
if (/^#{1,6}\s/.test(line)) break;
|
|
181
188
|
if (/^\s*[-*]\s*\[x\]/i.test(line)) checked++;
|
|
182
|
-
else if (/^\s*[-*]\s*\[ \]/.test(line)) unchecked++;
|
|
189
|
+
else if (/^\s*[-*]\s*\[[ ~]\]/.test(line)) unchecked++;
|
|
183
190
|
}
|
|
184
191
|
return { checked, unchecked, total: checked + unchecked };
|
|
185
192
|
}
|
package/src/validate.mjs
CHANGED
|
@@ -626,7 +626,7 @@ export function checkGitStaleness(docs, config, options = {}) {
|
|
|
626
626
|
|
|
627
627
|
// Plan-shape lint: soft warnings on convention drift. Plan-only.
|
|
628
628
|
// Body is the unparsed plan body (everything after the closing `---`).
|
|
629
|
-
export function validatePlanShape(doc, body, frontmatter, config) {
|
|
629
|
+
export function validatePlanShape(doc, body, frontmatter, config, bodyLineOffset = 0) {
|
|
630
630
|
if (doc.type !== 'plan') return;
|
|
631
631
|
// Skip plans in terminal/archive statuses (closed work shouldn't generate noise)
|
|
632
632
|
if (config.lifecycle.terminalStatuses.has(doc.status) || config.lifecycle.archiveStatuses.has(doc.status)) return;
|
|
@@ -707,7 +707,7 @@ export function validatePlanShape(doc, body, frontmatter, config) {
|
|
|
707
707
|
doc.warnings.push({
|
|
708
708
|
path: doc.path,
|
|
709
709
|
level: 'warning',
|
|
710
|
-
message: `Phase marker contradicts its own checklist (line ${phase.lineStart}): \`${phase.heading.trim()}\` is marked ${conflict.declared}, but ${conflict.checked}/${conflict.total} boxes are checked — that reads as ${conflict.implied}. Update whichever half is stale.`,
|
|
710
|
+
message: `Phase marker contradicts its own checklist (line ${bodyLineOffset + phase.lineStart}): \`${phase.heading.trim()}\` is marked ${conflict.declared}, but ${conflict.checked}/${conflict.total} boxes are checked — that reads as ${conflict.implied}. Update whichever half is stale.`,
|
|
711
711
|
});
|
|
712
712
|
}
|
|
713
713
|
}
|