@farmslot/agent-runtime 0.5.1 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.0 - 2026-08-03
6
+
7
+ - fix(mark): step enumeration now skips informational sections (Acceptance Criteria, pre-merge, `<details>`) exactly like the gateway parsers — checkbox-formatted ACs no longer shift `mark N` onto the wrong box. Also tightens checkbox matching to `- [ ]` (no `* [ ]`), matching gateway behavior.
8
+
5
9
  ## 0.5.1 - 2026-08-02
6
10
 
7
11
  - Align the published protocol dependency with 0.16.0 so downstream installs use one Recipe Protocol version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/agent-runtime",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/agent-runtime"
33
33
  },
34
34
  "dependencies": {
35
- "@farmslot/protocol": "0.16.0"
35
+ "@farmslot/protocol": "0.17.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.0.0",
@@ -12,6 +12,75 @@ const ROLE_SIGNAL_SUFFIX = '-SIGNAL.json';
12
12
  const SELF_REVIEW_CHECKLIST = 'SELF-REVIEW.md';
13
13
  const SELF_REVIEW_FIX_CHECKLIST = 'SELF-REVIEW-FIX.md';
14
14
  const CI_FIX_CHECKLIST = 'CI-FIX.md';
15
+ // Sections whose checkboxes are informational (ACs, descriptions, upstream
16
+ // PR-template fields) — not task steps. Canonical copy lives in
17
+ // @farmslot/protocol/checklist-target CHECKLIST_SKIP_SECTIONS.
18
+ const CHECKLIST_SKIP_SECTIONS =
19
+ /^\**\s*(acceptance criteria|description|task|affected area|screenshots|comments|root cause|rules|recipe acs|pre-merge)/i;
20
+
21
+ // Behavioral mirror of @farmslot/protocol/checklist-target
22
+ // enumerateChecklistCheckboxes — the single definition of which markdown
23
+ // checkboxes are checklist steps. Any drift makes `mark N` check a different
24
+ // box than the one gateway progress reporting counts as step N (see
25
+ // test/checklist-target-sync.test.mjs for the parity fixture).
26
+ function enumerateChecklistCheckboxes(markdown) {
27
+ const items = [];
28
+ const lines = markdown.split('\n');
29
+ let inCodeBlock = false;
30
+ let inDetails = 0;
31
+ let inSkippedSection = false;
32
+ let phase = null;
33
+ let phaseIndex = -1;
34
+ let stepNumber = 0;
35
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
36
+ const trimmed = lines[lineIndex].trim();
37
+ if (trimmed.startsWith('```')) {
38
+ inCodeBlock = !inCodeBlock;
39
+ continue;
40
+ }
41
+ if (inCodeBlock) continue;
42
+ // Only lines STARTING with a details tag are structural — prose that
43
+ // mentions `<details>` (e.g. PR-description instructions) must stay inert.
44
+ // On a structural line, net opens minus closes handles both the plain
45
+ // one-liner (`<details>…</details>` opens nothing) and nested same-line
46
+ // opens that leave a block dangling.
47
+ if (trimmed.startsWith('<details')) {
48
+ const opens = (trimmed.match(/<details\b/g) || []).length;
49
+ const closes = (trimmed.match(/<\/details/g) || []).length;
50
+ inDetails = Math.max(0, inDetails + opens - closes);
51
+ continue;
52
+ }
53
+ if (trimmed.startsWith('</details')) {
54
+ inDetails = Math.max(0, inDetails - 1);
55
+ continue;
56
+ }
57
+ if (inDetails > 0) continue;
58
+ if (/^#{2,}\s+[^#]/.test(trimmed)) {
59
+ const name = trimmed.replace(/^#{2,}\s+/, '').trim();
60
+ if (CHECKLIST_SKIP_SECTIONS.test(name)) {
61
+ inSkippedSection = true;
62
+ continue;
63
+ }
64
+ inSkippedSection = false;
65
+ phase = name;
66
+ phaseIndex += 1;
67
+ continue;
68
+ }
69
+ if (inSkippedSection) continue;
70
+ const match = trimmed.match(/^- \[( |x|X)\]\s*(.*)$/);
71
+ if (!match) continue;
72
+ stepNumber += 1;
73
+ items.push({
74
+ lineIndex,
75
+ stepNumber,
76
+ checked: match[1].toLowerCase() === 'x',
77
+ phase,
78
+ phaseIndex,
79
+ rawLabel: match[2],
80
+ });
81
+ }
82
+ return items;
83
+ }
15
84
  const WORKER_TERMINAL_CONTRACT_INPUT = path.join('inputs', 'worker-terminal-contract.json');
16
85
 
17
86
  function signalFileForChecklist(checklistBasename) {
@@ -196,6 +265,8 @@ function resolveChecklistPaths(taskDir) {
196
265
  }
197
266
 
198
267
  module.exports = {
268
+ CHECKLIST_SKIP_SECTIONS,
269
+ enumerateChecklistCheckboxes,
199
270
  CHECKLIST_TARGET_MANIFEST,
200
271
  TASK_PROGRESS_MARKDOWN,
201
272
  INTERACTIVE_CHECKLIST_MARKDOWN,
@@ -7,6 +7,7 @@ const {
7
7
  resolveWorkerTerminalContract,
8
8
  } = require('./worker-terminal-contract.cjs');
9
9
  const {
10
+ enumerateChecklistCheckboxes,
10
11
  parseTaskDirMarkArgs,
11
12
  terminalContractInputForChecklist,
12
13
  } = require('./checklist-target.cjs');
@@ -424,37 +425,26 @@ function stripLabel(raw) {
424
425
  .trim();
425
426
  }
426
427
 
428
+ // Step enumeration is shared with the gateway parsers (generateTaskSchema in
429
+ // tasks/writer.ts and parseCheckboxStates in methods/task.ts) via the
430
+ // checklist-target enumerator: same skip sections, <details> handling, and
431
+ // checkbox shape. Any divergence makes `mark N` check a different box than
432
+ // the one progress reporting counts as step N (checkbox-formatted Acceptance
433
+ // Criteria used to shift every step by the AC count).
427
434
  function parseChecklist(markdown) {
428
435
  const lines = markdown.split(/\n/);
429
- const items = [];
430
- let inFence = false;
431
- let seen = 0;
432
- for (let i = 0; i < lines.length; i += 1) {
433
- if (/^\s*```/.test(lines[i])) {
434
- inFence = !inFence;
435
- continue;
436
- }
437
- if (inFence) continue;
438
- const match = lines[i].match(/^(\s*[-*]\s+\[)( |x|X)(\]\s+)(.*)$/);
439
- if (!match) continue;
440
- seen += 1;
441
- items.push({
442
- lineIndex: i,
443
- stepNumber: seen,
444
- checked: match[2].toLowerCase() === 'x',
445
- prefix: match[1],
446
- suffix: match[3],
447
- rawLabel: match[4],
448
- label: stripLabel(match[4]),
449
- });
450
- }
436
+ const items = enumerateChecklistCheckboxes(markdown).map((item) => ({
437
+ ...item,
438
+ label: stripLabel(item.rawLabel),
439
+ }));
451
440
  return { lines, items };
452
441
  }
453
442
 
454
443
  function markStepInLines(lines, item) {
455
444
  if (item.checked) return false;
456
- lines[item.lineIndex] = `${item.prefix}x${item.suffix}${item.rawLabel}`;
457
- return true;
445
+ const before = lines[item.lineIndex];
446
+ lines[item.lineIndex] = before.replace(/^(\s*- \[)( |x|X)(\])/, '$1x$3');
447
+ return lines[item.lineIndex] !== before;
458
448
  }
459
449
 
460
450
  function resolveTarget(taskPath, stepNumber, markLast) {