@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.
- package/README.md +25 -19
- package/dist/bundled/skills/dev-planner/SKILL.md +25 -12
- package/dist/bundled/skills/documentation/SKILL.md +2 -12
- package/dist/cli.js +11580 -10370
- package/dist/effort/store/index.js +2 -2
- package/dist/index-mgh2s9bj.js +937 -0
- package/dist/{index-6q30dtgm.js → index-nsr78ct2.js} +6974 -5097
- package/dist/{index-57mmzkcn.js → index-r28fzsvc.js} +191 -77
- package/dist/index.js +1307 -1213
- package/dist/plan/index.js +2 -2
- package/dist/plan-prompt.md +8 -1
- package/package.json +2 -2
- package/dist/index-cknayrpc.js +0 -2414
|
@@ -5,16 +5,17 @@ import {
|
|
|
5
5
|
messageOf,
|
|
6
6
|
parseTaskDeclaration,
|
|
7
7
|
readRafaBlocks
|
|
8
|
-
} from "./index-
|
|
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
|
|
16
|
-
var
|
|
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
|
|
49
|
+
function sectionMark(scope, mark) {
|
|
38
50
|
for (let index = scope.length - 1;index >= 0; index -= 1) {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
return
|
|
51
|
+
const named = scope[index]?.[mark];
|
|
52
|
+
if (named)
|
|
53
|
+
return named;
|
|
42
54
|
}
|
|
43
|
-
return
|
|
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
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
74
|
-
|
|
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,
|
|
96
|
+
function readItem(lines, lineIndex, headings) {
|
|
84
97
|
const raw = lines[lineIndex] ?? "";
|
|
85
|
-
const
|
|
86
|
-
const
|
|
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,
|
|
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
|
|
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
|
|
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 =
|
|
424
|
-
const
|
|
425
|
-
|
|
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
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
433
|
-
return { text: taskText,
|
|
434
|
-
const
|
|
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
|
-
|
|
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 =
|
|
449
|
-
return found === null ? line : `${prefix}${found.
|
|
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
|
|
464
|
-
|
|
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
|
|
526
|
-
const text =
|
|
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
|
|
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
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
function
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
|
868
|
-
return
|
|
869
|
-
return
|
|
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).
|
|
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, [
|
|
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
|
-
|
|
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 };
|