@open-tomato/rafa 0.44.0 → 0.45.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.
@@ -5,16 +5,17 @@ import {
5
5
  messageOf,
6
6
  parseTaskDeclaration,
7
7
  readRafaBlocks
8
- } from "./index-cknayrpc.js";
8
+ } from "./index-mgh2s9bj.js";
9
9
  // src/preflight/prerequisites-md.ts
10
10
  import { basename, dirname, join } from "path";
11
11
  var SECTION_AUTO_RE = /\[auto\]/i;
12
12
  var SECTION_START_RE = /\[start\]/i;
13
13
  var SECTION_HUMAN_RE = /\[human\]|manual|human|sign.?off|team/i;
14
+ var TASKS_MARKER_RE = /\[tasks\]/i;
15
+ var WRAP_UP_MARKER_RE = /\[wrap-up\]/i;
14
16
  var ITEM_RE = /^-\s+\[\s*\]\s+/;
15
- var INLINE_AUTO_RE = /^-\s+\[\s*\]\s+\[auto\]\s+/i;
16
- var INLINE_HUMAN_RE = /^-\s+\[\s*\]\s+\[human\]\s+/i;
17
- var INLINE_START_RE = /^-\s+\[\s*\]\s+\[start\]\s+/i;
17
+ var SCOPE_MARKERS = String.raw`((?:\[(?:tasks|wrap-up)\]\s+)*)`;
18
+ var ITEM_OPENING_RE = new RegExp(String.raw`^-\s+\[\s*\]\s+${SCOPE_MARKERS}(?:\[(auto|human|start)\]\s+)?${SCOPE_MARKERS}`, "i");
18
19
  var PROBE_RE = /:\s+`([^`]+)`\s*$/;
19
20
  var HEADING_MARKS_RE = /^#+/;
20
21
  var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
@@ -29,18 +30,29 @@ function inferSectionTag(header) {
29
30
  return "human";
30
31
  return null;
31
32
  }
33
+ function markedScope(text) {
34
+ const isForTasks = TASKS_MARKER_RE.test(text);
35
+ const isForWrapUp = WRAP_UP_MARKER_RE.test(text);
36
+ if (isForTasks && isForWrapUp)
37
+ return "both";
38
+ if (isForTasks)
39
+ return "tasks";
40
+ if (isForWrapUp)
41
+ return "wrap-up";
42
+ return null;
43
+ }
32
44
  function enterHeading(scope, header) {
33
45
  const level = HEADING_MARKS_RE.exec(header)?.[0].length ?? 1;
34
46
  const above = Array.from({ length: level - 1 }, (_, index) => scope[index] ?? null);
35
- return [...above, inferSectionTag(header)];
47
+ return [...above, { tag: inferSectionTag(header), scope: markedScope(header) }];
36
48
  }
37
- function sectionDefault(scope) {
49
+ function sectionMark(scope, mark) {
38
50
  for (let index = scope.length - 1;index >= 0; index -= 1) {
39
- const tag = scope[index];
40
- if (tag)
41
- return tag;
51
+ const named = scope[index]?.[mark];
52
+ if (named)
53
+ return named;
42
54
  }
43
- return "human";
55
+ return null;
44
56
  }
45
57
  function fenceOf(line) {
46
58
  return FENCE_RE.exec(line)?.[1] ?? null;
@@ -61,17 +73,18 @@ function continuationOf(lines, start) {
61
73
  }
62
74
  return following;
63
75
  }
64
- function inlineTag(line) {
65
- if (INLINE_AUTO_RE.test(line))
66
- return "auto";
67
- if (INLINE_START_RE.test(line))
68
- return "start";
69
- if (INLINE_HUMAN_RE.test(line))
70
- return "human";
71
- return null;
76
+ function tagNamed(word) {
77
+ const tag = word?.toLowerCase();
78
+ return tag === "auto" || tag === "human" || tag === "start" ? tag : null;
72
79
  }
73
- function stripItemPrefix(line) {
74
- return line.replace(INLINE_AUTO_RE, "").replace(INLINE_HUMAN_RE, "").replace(INLINE_START_RE, "").replace(ITEM_RE, "").trim();
80
+ function readOpening(line) {
81
+ const opening = ITEM_OPENING_RE.exec(line);
82
+ const [matched = "", before = "", tag, after = ""] = opening ?? [];
83
+ return {
84
+ tag: tagNamed(tag),
85
+ scope: markedScope(`${before}${after}`),
86
+ rest: line.slice(matched.length).trim()
87
+ };
75
88
  }
76
89
  function isProbedTag(tag) {
77
90
  return tag === "auto" || tag === "start";
@@ -80,12 +93,14 @@ function extractProbe(description) {
80
93
  const probe = PROBE_RE.exec(description)?.[1]?.trim();
81
94
  return probe || null;
82
95
  }
83
- function readItem(lines, lineIndex, inherited) {
96
+ function readItem(lines, lineIndex, headings) {
84
97
  const raw = lines[lineIndex] ?? "";
85
- const tag = inlineTag(raw) ?? inherited;
86
- const description = [stripItemPrefix(raw), ...continuationOf(lines, lineIndex + 1)].join(" ");
98
+ const opening = readOpening(raw);
99
+ const tag = opening.tag ?? sectionMark(headings, "tag") ?? "human";
100
+ const scope = opening.scope ?? sectionMark(headings, "scope") ?? "both";
101
+ const description = [opening.rest, ...continuationOf(lines, lineIndex + 1)].join(" ");
87
102
  const probe = isProbedTag(tag) ? extractProbe(description) : null;
88
- return Object.freeze({ raw, description, tag, lineIndex, probe });
103
+ return Object.freeze({ raw, description, tag, scope, lineIndex, probe });
89
104
  }
90
105
  function parsePrerequisites(content) {
91
106
  const lines = content.split(`
@@ -110,7 +125,7 @@ function parsePrerequisites(content) {
110
125
  continue;
111
126
  }
112
127
  if (ITEM_RE.test(line))
113
- items.push(readItem(lines, index, sectionDefault(scope)));
128
+ items.push(readItem(lines, index, scope));
114
129
  }
115
130
  return Object.freeze(items);
116
131
  }
@@ -118,7 +133,7 @@ function isProbed(item) {
118
133
  return isProbedTag(item.tag) && item.probe !== null;
119
134
  }
120
135
  function requiredItem(item) {
121
- return Object.freeze({ kind: "tool", name: item.description, probe: item.probe });
136
+ return Object.freeze({ kind: "tool", name: item.description, probe: item.probe, scope: item.scope });
122
137
  }
123
138
  function requiredItemsTagged(items, tag) {
124
139
  const probed = items.filter(isProbed).filter((item) => item.tag === tag);
@@ -378,9 +393,10 @@ async function runPreflight(items, options) {
378
393
  // src/utils/tracker.ts
379
394
  import fs from "fs";
380
395
  var BLOCKER_OPEN = "<!-- blocked: ";
396
+ var REPAIRS_OPEN = "<!-- repairs: ";
381
397
  var BLOCKER_CLOSE = " -->";
382
398
  var BLOCKER_GAP = " ";
383
- var BLOCKER_COMMENT = /\s<!-- blocked: ((?:(?!<!--|-->).)*) -->$/;
399
+ var TRAILING_COMMENT = /\s<!-- (blocked|repairs): ((?:(?!<!--|-->).)*) -->$/;
384
400
  var OPEN_TASK_PREFIX = /^- \[(?: |BLOCKED)\] /;
385
401
  var ESCAPE_SEQUENCE = /\\(u[0-9A-Fa-f]{4}|[\s\S])/g;
386
402
  var TAB = 9;
@@ -418,25 +434,44 @@ function unescapeBlockerText(escaped) {
418
434
  function blockerComment(blocker) {
419
435
  return `${BLOCKER_OPEN}${escapeBlockerText(blocker)}${BLOCKER_CLOSE}`;
420
436
  }
421
- function findBlockerComment(text) {
437
+ function repairsComment(repaired) {
438
+ return `${REPAIRS_OPEN}${escapeBlockerText(repaired)}${BLOCKER_CLOSE}`;
439
+ }
440
+ function findTrailingComment(text) {
422
441
  const trimmed = text.trimEnd();
423
- const match = BLOCKER_COMMENT.exec(trimmed);
424
- const escaped = match?.[1];
425
- if (match === null || escaped === undefined)
442
+ const match = TRAILING_COMMENT.exec(trimmed);
443
+ const kind = match?.[1];
444
+ const escaped = match?.[2];
445
+ if (match === null || escaped === undefined || kind !== "blocked" && kind !== "repairs")
426
446
  return null;
427
447
  const before = trimmed.slice(0, match.index).trimEnd();
428
- return before.length === 0 ? null : { before, escaped };
429
- }
430
- function splitBlockerComment(taskText) {
431
- const found = findBlockerComment(taskText);
432
- if (found === null)
433
- return { text: taskText, blocker: null };
434
- const blocker = unescapeBlockerText(found.escaped);
448
+ return before.length === 0 ? null : { kind, before, escaped };
449
+ }
450
+ function splitTrailingComments(taskText) {
451
+ const last = findTrailingComment(taskText);
452
+ if (last === null)
453
+ return { text: taskText, blocked: null, repairs: null };
454
+ const earlier = findTrailingComment(last.before);
455
+ const found = earlier === null || earlier.kind === last.kind ? [last] : [last, earlier];
435
456
  return {
436
- text: found.before,
437
- blocker: blocker.trim().length === 0 ? null : blocker
457
+ text: found.at(-1)?.before ?? last.before,
458
+ blocked: found.find((comment) => comment.kind === "blocked")?.escaped ?? null,
459
+ repairs: found.find((comment) => comment.kind === "repairs")?.escaped ?? null
438
460
  };
439
461
  }
462
+ function commentText(escaped) {
463
+ if (escaped === null)
464
+ return null;
465
+ const text = unescapeBlockerText(escaped);
466
+ return text.trim().length === 0 ? null : text;
467
+ }
468
+ function keptRepairs(escaped) {
469
+ return escaped === null ? "" : `${BLOCKER_GAP}${REPAIRS_OPEN}${escaped}${BLOCKER_CLOSE}`;
470
+ }
471
+ function splitBlockerComment(taskText) {
472
+ const { text, blocked } = splitTrailingComments(taskText);
473
+ return { text, blocker: commentText(blocked) };
474
+ }
440
475
  function splitCarriageReturn(line) {
441
476
  return line.endsWith("\r") ? { body: line.slice(0, -1), cr: "\r" } : { body: line, cr: "" };
442
477
  }
@@ -445,8 +480,8 @@ function withoutBlockerComment(line) {
445
480
  const prefix = OPEN_TASK_PREFIX.exec(body)?.[0];
446
481
  if (prefix === undefined)
447
482
  return line;
448
- const found = findBlockerComment(body.slice(prefix.length));
449
- return found === null ? line : `${prefix}${found.before}${cr}`;
483
+ const found = splitTrailingComments(body.slice(prefix.length));
484
+ return found.blocked === null ? line : `${prefix}${found.text}${keptRepairs(found.repairs)}${cr}`;
450
485
  }
451
486
  function closedBlockLines(trackerContent) {
452
487
  const inside = new Set;
@@ -460,8 +495,16 @@ function closedBlockLines(trackerContent) {
460
495
  return inside;
461
496
  }
462
497
  function taskInfoOf(capture, lineNum, status) {
463
- const { text, blocker } = splitBlockerComment(capture.trim());
464
- return blocker === null ? { task: text, lineNum, status } : { task: text, lineNum, status, blocker };
498
+ const found = splitTrailingComments(capture.trim());
499
+ const blocker = commentText(found.blocked);
500
+ const repairs = commentText(found.repairs);
501
+ return {
502
+ task: found.text,
503
+ lineNum,
504
+ status,
505
+ ...blocker === null ? {} : { blocker },
506
+ ...repairs === null ? {} : { repairs }
507
+ };
465
508
  }
466
509
  var TASK_LINE_STATUSES = [
467
510
  [/^- \[BLOCKED\] (.+)/, "blocked"],
@@ -522,12 +565,12 @@ function writeTrackerBlocker(trackerPath, lineNum, blocker) {
522
565
  const prefix = OPEN_TASK_PREFIX.exec(body)?.[0];
523
566
  if (prefix === undefined)
524
567
  return false;
525
- const rest = body.slice(prefix.length);
526
- const text = (findBlockerComment(rest)?.before ?? rest).trimEnd();
568
+ const found = splitTrailingComments(body.slice(prefix.length));
569
+ const text = found.text.trimEnd();
527
570
  if (text.trim().length === 0)
528
571
  return false;
529
572
  const comment = blocker.trim().length === 0 ? "" : `${BLOCKER_GAP}${blockerComment(blocker)}`;
530
- lines[lineNum] = `- [BLOCKED] ${text}${comment}${cr}`;
573
+ lines[lineNum] = `- [BLOCKED] ${text}${keptRepairs(found.repairs)}${comment}${cr}`;
531
574
  fs.writeFileSync(trackerPath, lines.join(`
532
575
  `), "utf8");
533
576
  return true;
@@ -564,7 +607,9 @@ function insertTrackerTask(trackerPath, entry, beforeLine) {
564
607
  }
565
608
  const at = beforeLine ?? endOfChecklist(lines, inBlock);
566
609
  const { cr } = splitCarriageReturn(lines[0] ?? "");
567
- const line = `- [BLOCKED] ${entry.task.trim()}${BLOCKER_GAP}{${entry.declaration.trim()}}` + `${BLOCKER_GAP}${blockerComment(entry.blocker)}${cr}`;
610
+ const repaired = entry.repairs ?? "";
611
+ const repairs = repaired.trim().length === 0 ? "" : `${BLOCKER_GAP}${repairsComment(repaired)}`;
612
+ const line = `- [BLOCKED] ${entry.task.trim()}${BLOCKER_GAP}{${entry.declaration.trim()}}` + `${repairs}${BLOCKER_GAP}${blockerComment(entry.blocker)}${cr}`;
568
613
  const written = [...lines.slice(0, at), line, ...lines.slice(at)];
569
614
  fs.writeFileSync(trackerPath, written.join(`
570
615
  `), "utf8");
@@ -574,7 +619,7 @@ function trackerPathFor(planPath) {
574
619
  return planPath.replace(/PLAN(-[^/]*)?\.md$/, (_m, stub) => `PLAN_TRACKER${stub ?? ""}.md`);
575
620
  }
576
621
 
577
- // src/plan/parse.ts
622
+ // src/core/plan/parse.ts
578
623
  var PLAN_BLOCK_KINDS = ["plan", "context", "stage-context"];
579
624
  var PLAN_HEADER_FIELDS = ["stub", "issue", "spec", "release"];
580
625
  var PLAN_RELEASE_LEVELS = ["patch", "minor", "major", "none"];
@@ -818,7 +863,38 @@ function parsePlan(markdown) {
818
863
  issues
819
864
  };
820
865
  }
821
-
866
+ // src/core/plan/plan-files.ts
867
+ import { statSync } from "fs";
868
+ import { basename as basename2, dirname as dirname2, join as join2, resolve } from "path";
869
+ function plansDirAt(root, planDir) {
870
+ return { label: planDir, path: resolve(root, planDir) };
871
+ }
872
+ function countTasks(tasks) {
873
+ const done = tasks.filter((task) => task.status === "done").length;
874
+ const blocked = tasks.filter((task) => task.status === "blocked").length;
875
+ return { total: tasks.length, done, blocked, open: tasks.length - done - blocked };
876
+ }
877
+ function planFileName(stub, tracker) {
878
+ return tracker ? `PLAN_TRACKER-${stub}.md` : `PLAN-${stub}.md`;
879
+ }
880
+ function stubOfPlanFile(name) {
881
+ const stub = /^PLAN-(.+)\.md$/.exec(name)?.[1];
882
+ return stub !== undefined && isStampableStub(stub) ? stub : null;
883
+ }
884
+ function isFile(path) {
885
+ return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
886
+ }
887
+ var REJECTED_DIR = "rejected";
888
+ function rejectedPath(path) {
889
+ const parent = dirname2(path);
890
+ return parent === "." ? join2(REJECTED_DIR, basename2(path)) : join2(parent, REJECTED_DIR, basename2(path));
891
+ }
892
+ function issueLine(file, issue) {
893
+ return `${file}:${issue.line}: ${issue.reason}: ${issue.text}`;
894
+ }
895
+ function plural(count, noun) {
896
+ return count === 1 ? `${count} ${noun}` : `${count} ${noun}s`;
897
+ }
822
898
  // src/plan/inject.ts
823
899
  var PLAN_CONTEXT_HEADING = "## Plan context";
824
900
  var STAGE_HEADING_PREFIX = "## Stage: ";
@@ -830,19 +906,49 @@ var CHECKBOXES = {
830
906
  blocked: "- [BLOCKED]",
831
907
  done: "- [x]"
832
908
  };
833
- function fallbackFor(reason, lineNum) {
834
- const line = lineNum + 1;
835
- const found = reason === "no-task-at-line" ? "no task the plan model reads" : "a different task than the one dispatched";
836
- const text = `line ${line} of the plan holds ${found}, so the whole plan is handed over; ` + "the plan may have changed since its tracker was copied, or the line sits inside a rafa:* block";
837
- return { found: false, fallback: { reason, line, text } };
838
- }
839
- function locate(model, wanted) {
840
- const task = model.tasks.find((candidate) => candidate.lineNum === wanted.lineNum);
841
- if (task === undefined)
842
- return fallbackFor("no-task-at-line", wanted.lineNum);
843
- if (task.task !== wanted.task)
844
- return fallbackFor("task-text-differs", wanted.lineNum);
845
- return { found: true, task };
909
+ var HANDED_OVER = ", so the whole plan is handed over";
910
+ function placeOf(position) {
911
+ return position !== undefined && Number.isInteger(position) && position >= 0 ? position : null;
912
+ }
913
+ var DISPATCHED_TEXT = "the dispatched text";
914
+ var REPAIRED_TEXT = "the text the dispatched repair names";
915
+ function noTaskWithText(place) {
916
+ const beyond = place === null ? "" : ` and no task number ${place + 1}`;
917
+ const text = `the plan holds no task with ${DISPATCHED_TEXT}${beyond}${HANDED_OVER}; ` + "the plan may have changed since its tracker was copied, or the task line sits inside a rafa:* block";
918
+ return { found: false, fallback: { reason: "no-task-with-text", text } };
919
+ }
920
+ function severalTasksWithText(count, place, sought) {
921
+ const which = place === null ? "the dispatch gives no position among its tasks" : `task number ${place + 1} is none of them`;
922
+ const text = `the plan holds ${count} tasks with ${sought} and ${which}${HANDED_OVER}`;
923
+ return { found: false, fallback: { reason: "several-tasks-with-text", text } };
924
+ }
925
+ function noRepairedTask() {
926
+ const text = `the plan holds no task with ${REPAIRED_TEXT}${HANDED_OVER}; ` + "the plan may have changed since the repair was written";
927
+ return { found: false, fallback: { reason: "no-task-with-text", text } };
928
+ }
929
+ function locate(model, wanted, position) {
930
+ const place = placeOf(position);
931
+ const atPlace = place === null ? undefined : model.tasks[place];
932
+ const holders = model.tasks.filter((candidate) => candidate.task === wanted.task);
933
+ const [first] = holders;
934
+ if (first === undefined) {
935
+ return atPlace === undefined ? noTaskWithText(place) : { found: true, task: atPlace };
936
+ }
937
+ if (holders.length === 1)
938
+ return { found: true, task: first };
939
+ return atPlace !== undefined && holders.includes(atPlace) ? { found: true, task: atPlace } : severalTasksWithText(holders.length, place, DISPATCHED_TEXT);
940
+ }
941
+ function locateRepaired(model, named, position) {
942
+ const sentence = named.trim();
943
+ const holders = model.tasks.filter((candidate) => candidate.text.trim() === sentence);
944
+ const [first] = holders;
945
+ if (first === undefined)
946
+ return noRepairedTask();
947
+ if (holders.length === 1)
948
+ return { found: true, task: first };
949
+ const place = placeOf(position);
950
+ const atPlace = place === null ? undefined : model.tasks[place];
951
+ return atPlace !== undefined && holders.includes(atPlace) ? { found: true, task: atPlace } : severalTasksWithText(holders.length, place, REPAIRED_TEXT);
846
952
  }
847
953
  function contextBody(body) {
848
954
  if (body === null || body.trim().length === 0)
@@ -862,15 +968,22 @@ function renderDocument(sections) {
862
968
  `;
863
969
  }
864
970
  function shownStatus(task, dispatched) {
865
- if (task.lineNum < dispatched.lineNum)
971
+ if (task.lineNum < dispatched.task.lineNum)
866
972
  return "done";
867
- if (task.lineNum === dispatched.lineNum)
868
- return "unchecked";
869
- return task.status;
973
+ if (task.lineNum > dispatched.task.lineNum)
974
+ return task.status;
975
+ return dispatched.repair === null ? "unchecked" : "done";
870
976
  }
871
977
  function taskLine(status, task) {
872
978
  return `${CHECKBOXES[status]} ${task.text}`;
873
979
  }
980
+ function openLine(dispatched) {
981
+ return taskLine("unchecked", { text: dispatched.repair ?? dispatched.task.text });
982
+ }
983
+ function checklistLines(task, dispatched) {
984
+ const line = taskLine(shownStatus(task, dispatched), task);
985
+ return task === dispatched.task && dispatched.repair !== null ? [line, openLine(dispatched)] : [line];
986
+ }
874
987
  function planContextSection(model) {
875
988
  const body = contextBody(model.context);
876
989
  return body === null ? null : section(PLAN_CONTEXT_HEADING, body);
@@ -894,17 +1007,17 @@ function stageListSection(model, dispatched) {
894
1007
  `));
895
1008
  }
896
1009
  function renderStage(model, dispatched) {
897
- const checklist = model.tasks.filter((task) => task.stage === dispatched.stage).map((task) => taskLine(shownStatus(task, dispatched), task));
1010
+ const checklist = model.tasks.filter((task) => task.stage === dispatched.task.stage).flatMap((task) => checklistLines(task, dispatched));
898
1011
  return renderDocument([
899
1012
  planContextSection(model),
900
- stageSection(model, dispatched, checklist),
901
- stageListSection(model, dispatched)
1013
+ stageSection(model, dispatched.task, checklist),
1014
+ stageListSection(model, dispatched.task)
902
1015
  ]);
903
1016
  }
904
1017
  function renderTask(model, dispatched) {
905
1018
  return renderDocument([
906
1019
  planContextSection(model),
907
- stageSection(model, dispatched, [taskLine("unchecked", dispatched)])
1020
+ stageSection(model, dispatched.task, [openLine(dispatched)])
908
1021
  ]);
909
1022
  }
910
1023
  var RENDERERS = {
@@ -912,14 +1025,15 @@ var RENDERERS = {
912
1025
  task: renderTask
913
1026
  };
914
1027
  function renderInjection(request) {
915
- const { mode, plan, task } = request;
916
- if (mode === "full")
917
- return { requested: mode, mode, text: plan, fallback: null };
1028
+ const { mode, plan, task, position, repairs } = request;
1029
+ if (mode === "full" || repairs === null)
1030
+ return { requested: mode, mode: "full", text: plan, fallback: null };
918
1031
  const model = parsePlan(plan);
919
- const location = locate(model, task);
1032
+ const location = repairs === undefined ? locate(model, task, position) : locateRepaired(model, repairs, position);
920
1033
  if (!location.found) {
921
1034
  return { requested: mode, mode: "full", text: plan, fallback: location.fallback };
922
1035
  }
923
- return { requested: mode, mode, text: RENDERERS[mode](model, location.task), fallback: null };
1036
+ const repair = repairs === undefined ? null : parseTaskDeclaration(task.task).text.trim();
1037
+ return { requested: mode, mode, text: RENDERERS[mode](model, { task: location.task, repair }), fallback: null };
924
1038
  }
925
- export { escapeBlockerText, splitBlockerComment, findNextTask, listTrackerTasks, listOpenTasks, updateTrackerLine, writeTrackerBlocker, insertTrackerTask, trackerPathFor, PLAN_BLOCK_KINDS, PLAN_HEADER_FIELDS, PLAN_RELEASE_LEVELS, parsePlan, parsePrerequisites, planPrerequisites, malformedPrerequisiteLines, mergePlanPrerequisites, prerequisitesPathForPlan, loadPlanPrerequisites, PROBE_TIMEOUT_MS, runShellProbe, runPreflight, renderInjection };
1039
+ export { escapeBlockerText, splitBlockerComment, findNextTask, listTrackerTasks, listOpenTasks, updateTrackerLine, writeTrackerBlocker, insertTrackerTask, trackerPathFor, PLAN_BLOCK_KINDS, PLAN_HEADER_FIELDS, PLAN_RELEASE_LEVELS, parsePlan, plansDirAt, countTasks, planFileName, stubOfPlanFile, isFile, rejectedPath, issueLine, plural, parsePrerequisites, planPrerequisites, malformedPrerequisiteLines, mergePlanPrerequisites, prerequisitesPathForPlan, loadPlanPrerequisites, PROBE_TIMEOUT_MS, runShellProbe, runPreflight, renderInjection };