@mjasnikovs/pi-task 0.17.8 → 0.17.10

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.
@@ -755,7 +755,7 @@ async function handleTaskResume(args, ctx) {
755
755
  if (!m)
756
756
  continue;
757
757
  try {
758
- const raw = await fsp.readFile(path.join(tasksDir(cwd), f), 'utf8');
758
+ const raw = await readTextFile(path.join(tasksDir(cwd), f));
759
759
  const fm = parseFrontMatter(raw);
760
760
  if (!fm)
761
761
  continue;
@@ -655,7 +655,19 @@ export async function phaseCompose(deps, refined, research, qa) {
655
655
  // value starts at GOAL.
656
656
  const stripped = stripSpecPreamble(text);
657
657
  const problem = validateSpecShape(stripped);
658
- return problem ? { ok: false, problem } : { ok: true, value: stripped };
658
+ if (problem)
659
+ return { ok: false, problem };
660
+ // validateSpecShape only checks the VERIFY *header* exists. The
661
+ // critique gate and the handoff gate both require a *runnable*
662
+ // fenced block (parseVerifyBlock). Enforce the same bar here so a
663
+ // draft ending in a bare `VERIFY:` retries with emphasis now,
664
+ // instead of passing compose and being rejected downstream — which
665
+ // otherwise leaves a persisted VERIFY-less spec that resume can't
666
+ // heal.
667
+ if (parseVerifyBlock(stripped) === null) {
668
+ return { ok: false, problem: 'VERIFY block has no runnable ```bash fenced commands' };
669
+ }
670
+ return { ok: true, value: stripped };
659
671
  }, problem => new Error(`compose_invalid: ${problem}`));
660
672
  }
661
673
  export async function phaseCritique(deps, spec, refined, qa) {
@@ -716,6 +728,15 @@ export async function critiqueWithFallback(d, p) {
716
728
  const msg = err instanceof Error ? err.message : String(err);
717
729
  if (msg !== 'no_verify_block')
718
730
  throw err;
731
+ // Fall back to the compose draft — but only if it actually carries a
732
+ // runnable VERIFY block. Critique reaches its rewrite path precisely
733
+ // when the compose draft lacked one (triage is skipped in that case),
734
+ // so returning that same draft would persist a VERIFY-less spec the
735
+ // handoff gate rejects and resume can't heal. Compose now enforces a
736
+ // parseable VERIFY, so this should hold; keep the guard so a regression
737
+ // fails the run cleanly instead of shipping a broken spec.
738
+ if (parseVerifyBlock(p.spec) === null)
739
+ throw err;
719
740
  p.ctx.ui.notify("Critique couldn't produce a VERIFY block — using compose draft. Edit the spec manually if needed.", 'warning');
720
741
  return p.spec;
721
742
  }
@@ -66,7 +66,7 @@ export async function readTaskFile(cwd, id) {
66
66
  const fm = parseFrontMatter(raw);
67
67
  if (!fm)
68
68
  throw new Error(`malformed front matter in ${id}.md`);
69
- const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, '');
69
+ const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
70
70
  return { frontMatter: fm, body };
71
71
  }
72
72
  export async function writeTaskFile(cwd, fm, body) {
@@ -32,11 +32,15 @@ export function emitFrontMatter(fm) {
32
32
  return lines.join('\n');
33
33
  }
34
34
  export function parseFrontMatter(content) {
35
- const m = /^---\n([\s\S]*?)\n---\n?/.exec(content);
35
+ // `\r?\n` throughout so a CRLF/Windows task file parses too. Reads normally
36
+ // pass through readTextFile (which normalizes to LF), but tolerating CRLF at
37
+ // the parser itself is the safety net for any read site that forgets to —
38
+ // exactly the class of miss that shipped in 0.17.8. See shared/fs-text.ts.
39
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(content);
36
40
  if (!m)
37
41
  return null;
38
42
  const obj = {};
39
- for (const line of m[1].split('\n')) {
43
+ for (const line of m[1].split(/\r?\n/)) {
40
44
  const kv = /^([a-z_]+):\s*(.*)$/.exec(line);
41
45
  if (kv)
42
46
  obj[kv[1]] = kv[2];
@@ -69,7 +73,9 @@ export function sectionRegex(heading) {
69
73
  // after a long /task-auto run). Keeping blanks in group 2 lets the `.trim()`
70
74
  // every reader already applies collapse them, which also self-heals files
71
75
  // that already accumulated the gap.
72
- return new RegExp(`(^## ${escapeRegex(heading)}[ \\t]*\\n)([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, 'm');
76
+ // `[ \t]*\r?\n` on the heading line so a CRLF file still matches the section
77
+ // (belt-and-suspenders alongside read-boundary normalization; see fs-text.ts).
78
+ return new RegExp(`(^## ${escapeRegex(heading)}[ \\t]*\\r?\\n)([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, 'm');
73
79
  }
74
80
  export function extractSection(body, heading) {
75
81
  const m = sectionRegex(heading).exec(body);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.17.8",
3
+ "version": "0.17.10",
4
4
  "description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",