@open-tomato/rafa 0.44.0 → 0.46.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.
Files changed (142) hide show
  1. package/README.md +280 -42
  2. package/dist/PROMPT.md +18 -1
  3. package/dist/adapters/logger/settings.d.ts +132 -0
  4. package/dist/bugs/closest.d.ts +19 -0
  5. package/dist/bugs/fenced.d.ts +8 -0
  6. package/dist/bugs/places.d.ts +20 -0
  7. package/dist/bugs/record.d.ts +145 -0
  8. package/dist/bugs/title.d.ts +83 -0
  9. package/dist/bundled/agents/doc-updater.md +17 -3
  10. package/dist/bundled/agents/loop-implementer.md +14 -2
  11. package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
  12. package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
  13. package/dist/bundled/skills/dev-planner/SKILL.md +120 -24
  14. package/dist/bundled/skills/documentation/SKILL.md +2 -12
  15. package/dist/cli.js +72387 -59411
  16. package/dist/config/entry.d.ts +119 -0
  17. package/dist/config/index.d.ts +36 -0
  18. package/dist/config/index.js +51 -0
  19. package/dist/config/shape.d.ts +232 -0
  20. package/dist/config-registry.d.ts +127 -0
  21. package/dist/config-schema-board-project.d.ts +157 -0
  22. package/dist/config-schema-bugs.d.ts +71 -0
  23. package/dist/config-schema-errors.d.ts +54 -0
  24. package/dist/config-schema-features.d.ts +64 -0
  25. package/dist/config-schema-hub.d.ts +150 -0
  26. package/dist/config-schema-issues.d.ts +95 -0
  27. package/dist/config-schema-logger.d.ts +91 -0
  28. package/dist/config-schema-loop-continue.d.ts +122 -0
  29. package/dist/config-schema-loop-retries.d.ts +94 -0
  30. package/dist/config-schema-release.d.ts +307 -0
  31. package/dist/config-schema-tests.d.ts +170 -0
  32. package/dist/config-schema-triage.d.ts +142 -0
  33. package/dist/config-schema-wrap-up.d.ts +96 -0
  34. package/dist/core/command/command.d.ts +228 -0
  35. package/dist/core/command/confirm.d.ts +67 -0
  36. package/dist/core/command/index.d.ts +112 -0
  37. package/dist/core/command/lazy.d.ts +54 -0
  38. package/dist/core/command/registry-types.d.ts +96 -0
  39. package/dist/core/command/running.d.ts +96 -0
  40. package/dist/core/command/spends.d.ts +39 -0
  41. package/dist/core/command/types.d.ts +92 -0
  42. package/dist/core/command/version.d.ts +4 -0
  43. package/dist/core/config/branch-template.d.ts +20 -0
  44. package/dist/core/config/config-items.d.ts +86 -0
  45. package/dist/core/config/config-load.d.ts +47 -0
  46. package/dist/core/config/config-origins.d.ts +57 -0
  47. package/dist/core/config/config-readers.d.ts +167 -0
  48. package/dist/core/config/config-schema.d.ts +207 -0
  49. package/dist/core/config/config-sections.d.ts +493 -0
  50. package/dist/core/config/config.d.ts +320 -0
  51. package/dist/core/config/core-section.d.ts +39 -0
  52. package/dist/core/config/dangerous.d.ts +58 -0
  53. package/dist/core/config/default-routing.d.ts +16 -0
  54. package/dist/core/config/index.d.ts +141 -0
  55. package/dist/core/config/scope.d.ts +42 -0
  56. package/dist/core/config/section.d.ts +206 -0
  57. package/dist/core/config/sections.d.ts +45 -0
  58. package/dist/core/config/standard-schema.d.ts +27 -0
  59. package/dist/core/git/commit.d.ts +232 -0
  60. package/dist/core/git/gh-runner.d.ts +24 -0
  61. package/dist/core/git/git.d.ts +22 -0
  62. package/dist/core/git/index.d.ts +85 -0
  63. package/dist/core/loop/claude.d.ts +388 -0
  64. package/dist/core/loop/hop-record.d.ts +52 -0
  65. package/dist/core/loop/index.d.ts +179 -0
  66. package/dist/core/loop/session-env.d.ts +20 -0
  67. package/dist/core/loop/session-record-parse.d.ts +63 -0
  68. package/dist/core/loop/sessions.d.ts +408 -0
  69. package/dist/core/loop/suite-failure.d.ts +25 -0
  70. package/dist/core/output/active.d.ts +100 -0
  71. package/dist/core/output/stream.d.ts +20 -0
  72. package/dist/core/output/text.d.ts +55 -0
  73. package/dist/core/plan/block-reader.d.ts +46 -0
  74. package/dist/core/plan/blocks.d.ts +139 -0
  75. package/dist/core/plan/declaration.d.ts +366 -0
  76. package/dist/core/plan/plan-stamp.d.ts +75 -0
  77. package/dist/core/runtime/identity.d.ts +21 -0
  78. package/dist/core/runtime/index.d.ts +46 -0
  79. package/dist/effort/store/bring-forward.d.ts +131 -0
  80. package/dist/effort/store/check-rows.d.ts +31 -0
  81. package/dist/effort/store/commit-stats.d.ts +42 -0
  82. package/dist/effort/store/copy.d.ts +35 -0
  83. package/dist/effort/store/development-build.d.ts +105 -0
  84. package/dist/effort/store/effort-levels.d.ts +13 -0
  85. package/dist/effort/store/findings.d.ts +214 -0
  86. package/dist/effort/store/fix-schema.d.ts +184 -0
  87. package/dist/effort/store/index.js +10 -7
  88. package/dist/effort/store/location.d.ts +44 -0
  89. package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
  90. package/dist/effort/store/merge-conflicts.d.ts +91 -0
  91. package/dist/effort/store/merge-rules.d.ts +82 -0
  92. package/dist/effort/store/merge-store.d.ts +211 -0
  93. package/dist/effort/store/merge-union.d.ts +92 -0
  94. package/dist/effort/store/migrate.d.ts +146 -0
  95. package/dist/effort/store/migrations.d.ts +92 -0
  96. package/dist/effort/store/ndjson-files.d.ts +114 -0
  97. package/dist/effort/store/origins.d.ts +116 -0
  98. package/dist/effort/store/plan-stubs.d.ts +79 -0
  99. package/dist/effort/store/rebuild-aside.d.ts +172 -0
  100. package/dist/effort/store/schema-plan.d.ts +102 -0
  101. package/dist/effort/store/session-row-types.d.ts +64 -0
  102. package/dist/effort/store/settings.d.ts +13 -0
  103. package/dist/effort/store/sqlite.d.ts +340 -0
  104. package/dist/effort/store/store-generation.d.ts +17 -0
  105. package/dist/effort/store/store-identity.d.ts +220 -0
  106. package/dist/effort/store/store-meta.d.ts +305 -0
  107. package/dist/effort/store/store-replaced.d.ts +72 -0
  108. package/dist/effort/store/types.d.ts +223 -0
  109. package/dist/errors/codes.d.ts +56 -0
  110. package/dist/errors/rafa-codes.d.ts +36 -0
  111. package/dist/index-2a7nb3an.js +125 -0
  112. package/dist/{index-57mmzkcn.js → index-39kbcw68.js} +516 -217
  113. package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
  114. package/dist/index-cy98y87p.js +2597 -0
  115. package/dist/index-gqz4jn6j.js +13447 -0
  116. package/dist/index-gvek4r8m.js +710 -0
  117. package/dist/index-j4k2vdz3.js +1125 -0
  118. package/dist/index-jh1r4kcn.js +1067 -0
  119. package/dist/index-ykggr043.js +540 -0
  120. package/dist/index-z1w83f81.js +4 -0
  121. package/dist/index.js +16091 -22798
  122. package/dist/learning/index.js +5 -6
  123. package/dist/plan/index.js +12 -11
  124. package/dist/plan-prompt.md +11 -2
  125. package/dist/ports/index.d.ts +732 -0
  126. package/dist/ports/learning.d.ts +101 -0
  127. package/dist/ports/log-level.d.ts +13 -0
  128. package/dist/ports/spec-review.d.ts +66 -0
  129. package/dist/pr/checks.d.ts +133 -0
  130. package/dist/pr/types.d.ts +422 -0
  131. package/dist/ready-4f8edezt.js +2016 -0
  132. package/dist/release-templates/release-bot-fragment.yml +153 -0
  133. package/dist/release-templates/release-publish.yml +110 -0
  134. package/dist/release-templates/release-settle.yml +122 -0
  135. package/dist/release-templates/release-tag.yml +102 -0
  136. package/dist/report/parse.d.ts +415 -0
  137. package/dist/schema/project-id.d.ts +79 -0
  138. package/dist/schema/tiers.d.ts +107 -0
  139. package/package.json +42 -4
  140. package/dist/index-6q30dtgm.js +0 -6342
  141. package/dist/index-cknayrpc.js +0 -2414
  142. package/dist/index-hf2k497d.js +0 -16
@@ -1,20 +1,22 @@
1
1
  // @bun
2
2
  import {
3
3
  activeOutput,
4
- isStampableStub,
4
+ isStampableStub
5
+ } from "./index-2a7nb3an.js";
6
+ import {
5
7
  messageOf,
6
- parseTaskDeclaration,
7
- readRafaBlocks
8
- } from "./index-cknayrpc.js";
8
+ parseTaskDeclaration
9
+ } from "./index-ykggr043.js";
9
10
  // src/preflight/prerequisites-md.ts
10
11
  import { basename, dirname, join } from "path";
11
12
  var SECTION_AUTO_RE = /\[auto\]/i;
12
13
  var SECTION_START_RE = /\[start\]/i;
13
14
  var SECTION_HUMAN_RE = /\[human\]|manual|human|sign.?off|team/i;
15
+ var TASKS_MARKER_RE = /\[tasks\]/i;
16
+ var WRAP_UP_MARKER_RE = /\[wrap-up\]/i;
14
17
  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;
18
+ var SCOPE_MARKERS = String.raw`((?:\[(?:tasks|wrap-up)\]\s+)*)`;
19
+ var ITEM_OPENING_RE = new RegExp(String.raw`^-\s+\[\s*\]\s+${SCOPE_MARKERS}(?:\[(auto|human|start)\]\s+)?${SCOPE_MARKERS}`, "i");
18
20
  var PROBE_RE = /:\s+`([^`]+)`\s*$/;
19
21
  var HEADING_MARKS_RE = /^#+/;
20
22
  var FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
@@ -29,18 +31,29 @@ function inferSectionTag(header) {
29
31
  return "human";
30
32
  return null;
31
33
  }
34
+ function markedScope(text) {
35
+ const isForTasks = TASKS_MARKER_RE.test(text);
36
+ const isForWrapUp = WRAP_UP_MARKER_RE.test(text);
37
+ if (isForTasks && isForWrapUp)
38
+ return "both";
39
+ if (isForTasks)
40
+ return "tasks";
41
+ if (isForWrapUp)
42
+ return "wrap-up";
43
+ return null;
44
+ }
32
45
  function enterHeading(scope, header) {
33
46
  const level = HEADING_MARKS_RE.exec(header)?.[0].length ?? 1;
34
47
  const above = Array.from({ length: level - 1 }, (_, index) => scope[index] ?? null);
35
- return [...above, inferSectionTag(header)];
48
+ return [...above, { tag: inferSectionTag(header), scope: markedScope(header) }];
36
49
  }
37
- function sectionDefault(scope) {
50
+ function sectionMark(scope, mark) {
38
51
  for (let index = scope.length - 1;index >= 0; index -= 1) {
39
- const tag = scope[index];
40
- if (tag)
41
- return tag;
52
+ const named = scope[index]?.[mark];
53
+ if (named)
54
+ return named;
42
55
  }
43
- return "human";
56
+ return null;
44
57
  }
45
58
  function fenceOf(line) {
46
59
  return FENCE_RE.exec(line)?.[1] ?? null;
@@ -61,17 +74,18 @@ function continuationOf(lines, start) {
61
74
  }
62
75
  return following;
63
76
  }
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;
77
+ function tagNamed(word) {
78
+ const tag = word?.toLowerCase();
79
+ return tag === "auto" || tag === "human" || tag === "start" ? tag : null;
72
80
  }
73
- function stripItemPrefix(line) {
74
- return line.replace(INLINE_AUTO_RE, "").replace(INLINE_HUMAN_RE, "").replace(INLINE_START_RE, "").replace(ITEM_RE, "").trim();
81
+ function readOpening(line) {
82
+ const opening = ITEM_OPENING_RE.exec(line);
83
+ const [matched = "", before = "", tag, after = ""] = opening ?? [];
84
+ return {
85
+ tag: tagNamed(tag),
86
+ scope: markedScope(`${before}${after}`),
87
+ rest: line.slice(matched.length).trim()
88
+ };
75
89
  }
76
90
  function isProbedTag(tag) {
77
91
  return tag === "auto" || tag === "start";
@@ -80,12 +94,14 @@ function extractProbe(description) {
80
94
  const probe = PROBE_RE.exec(description)?.[1]?.trim();
81
95
  return probe || null;
82
96
  }
83
- function readItem(lines, lineIndex, inherited) {
97
+ function readItem(lines, lineIndex, headings) {
84
98
  const raw = lines[lineIndex] ?? "";
85
- const tag = inlineTag(raw) ?? inherited;
86
- const description = [stripItemPrefix(raw), ...continuationOf(lines, lineIndex + 1)].join(" ");
99
+ const opening = readOpening(raw);
100
+ const tag = opening.tag ?? sectionMark(headings, "tag") ?? "human";
101
+ const scope = opening.scope ?? sectionMark(headings, "scope") ?? "both";
102
+ const description = [opening.rest, ...continuationOf(lines, lineIndex + 1)].join(" ");
87
103
  const probe = isProbedTag(tag) ? extractProbe(description) : null;
88
- return Object.freeze({ raw, description, tag, lineIndex, probe });
104
+ return Object.freeze({ raw, description, tag, scope, lineIndex, probe });
89
105
  }
90
106
  function parsePrerequisites(content) {
91
107
  const lines = content.split(`
@@ -110,7 +126,7 @@ function parsePrerequisites(content) {
110
126
  continue;
111
127
  }
112
128
  if (ITEM_RE.test(line))
113
- items.push(readItem(lines, index, sectionDefault(scope)));
129
+ items.push(readItem(lines, index, scope));
114
130
  }
115
131
  return Object.freeze(items);
116
132
  }
@@ -118,7 +134,7 @@ function isProbed(item) {
118
134
  return isProbedTag(item.tag) && item.probe !== null;
119
135
  }
120
136
  function requiredItem(item) {
121
- return Object.freeze({ kind: "tool", name: item.description, probe: item.probe });
137
+ return Object.freeze({ kind: "tool", name: item.description, probe: item.probe, scope: item.scope });
122
138
  }
123
139
  function requiredItemsTagged(items, tag) {
124
140
  const probed = items.filter(isProbed).filter((item) => item.tag === tag);
@@ -375,13 +391,341 @@ async function runPreflight(items, options) {
375
391
  knownMissing: Object.freeze(knownMissing)
376
392
  });
377
393
  }
378
- // src/utils/tracker.ts
379
- import fs from "fs";
394
+ // packages/rafa/src/core/plan/blocks.ts
395
+ var RAFA_BLOCK_KINDS = [
396
+ "plan",
397
+ "context",
398
+ "stage-context",
399
+ "report"
400
+ ];
401
+ var RAFA_PREFIX = "rafa:";
402
+ var FENCE_RUN = /^( {0,3})(`{3,}|~{3,})/;
403
+ var CLOSING_FENCE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
404
+ function isRafaBlockKind(kind) {
405
+ return RAFA_BLOCK_KINDS.includes(kind);
406
+ }
407
+ function linesOf(markdown) {
408
+ const lines = markdown.split(`
409
+ `);
410
+ if (lines[lines.length - 1] === "")
411
+ lines.pop();
412
+ return lines.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
413
+ }
414
+ function kindOf(info) {
415
+ const word = info.trim().split(/\s+/)[0] ?? "";
416
+ return word.startsWith(RAFA_PREFIX) ? word.slice(RAFA_PREFIX.length) : null;
417
+ }
418
+ function openingFence(line) {
419
+ const match = FENCE_RUN.exec(line);
420
+ const opener = match?.[0];
421
+ const indent = match?.[1];
422
+ const run = match?.[2];
423
+ if (opener === undefined || indent === undefined || run === undefined) {
424
+ return null;
425
+ }
426
+ const marker = run.charAt(0);
427
+ const info = line.slice(opener.length);
428
+ if (marker === "`" && info.includes("`"))
429
+ return null;
430
+ return { marker, length: run.length, indent: indent.length, kind: kindOf(info) };
431
+ }
432
+ function closes(line, fence) {
433
+ const run = CLOSING_FENCE.exec(line)?.[1];
434
+ return run !== undefined && run.charAt(0) === fence.marker && run.length >= fence.length;
435
+ }
436
+ function closingIndex(lines, from, fence) {
437
+ for (let index = from;index < lines.length; index += 1) {
438
+ if (closes(lines[index] ?? "", fence))
439
+ return index;
440
+ }
441
+ return lines.length;
442
+ }
443
+ function dedent(line, indent) {
444
+ let cut = 0;
445
+ while (cut < indent && line.charAt(cut) === " ")
446
+ cut += 1;
447
+ return line.slice(cut);
448
+ }
449
+ function readRafaBlocks(markdown) {
450
+ const lines = linesOf(markdown);
451
+ const blocks = [];
452
+ let index = 0;
453
+ while (index < lines.length) {
454
+ const fence = openingFence(lines[index] ?? "");
455
+ if (fence === null) {
456
+ index += 1;
457
+ continue;
458
+ }
459
+ const closing = closingIndex(lines, index + 1, fence);
460
+ const closed = closing < lines.length;
461
+ if (fence.kind !== null) {
462
+ const body = lines.slice(index + 1, closing).map((line) => dedent(line, fence.indent)).join(`
463
+ `);
464
+ const last = closed ? closing + 1 : lines.length;
465
+ blocks.push({ kind: fence.kind, body, span: { first: index + 1, last }, closed });
466
+ }
467
+ index = closing + 1;
468
+ }
469
+ return blocks;
470
+ }
471
+ // packages/rafa/src/report/parse.ts
472
+ var REPORT_STATUSES = ["done", "blocked"];
473
+ var FINDING_KINDS = ["gotcha", "pattern", "location", "skill-suggestion"];
474
+ var FINDING_SIGNALS = ["loud", "silent"];
475
+ var FINDING_DOMAINS = [
476
+ "code-style",
477
+ "testing",
478
+ "git",
479
+ "debugging",
480
+ "workflow",
481
+ "security",
482
+ "general-best-practices"
483
+ ];
484
+ var CHANGE_LEVELS = ["patch", "minor", "major", "none"];
485
+ var BUG_KINDS = ["bug", "own-fallout", "own-gap", "convention", "docs", "machine"];
486
+ var DEFAULT_BUG_KIND = "bug";
487
+ var REPORT_KIND = "report";
488
+ var REQUIRED = true;
489
+ var OPTIONAL = false;
490
+ var REPORT_KEYS = [
491
+ "status",
492
+ "feedback",
493
+ "findings",
494
+ "skills_used",
495
+ "blockers",
496
+ "out_of_scope_bugs",
497
+ "changes",
498
+ "stale_docs"
499
+ ];
500
+ var FINDING_KEYS = [
501
+ "trigger",
502
+ "kind",
503
+ "what",
504
+ "cause",
505
+ "resolution",
506
+ "artifact",
507
+ "signal",
508
+ "domain"
509
+ ];
510
+ var BLOCKER_KEYS = ["what", "artifact"];
511
+ var BUG_KEYS = ["what", "artifact", "security", "kind", "module", "action", "reproduce", "cause"];
512
+ var CHANGE_KEYS = ["level", "area", "summary"];
513
+ var STALE_DOC_KEYS = ["where", "what"];
514
+ function isMapping(value) {
515
+ return typeof value === "object" && value !== null && !Array.isArray(value);
516
+ }
517
+ function isOneOf(choices, value) {
518
+ return choices.includes(value);
519
+ }
520
+ function describeValue(value) {
521
+ if (typeof value === "string")
522
+ return JSON.stringify(value);
523
+ if (value === null)
524
+ return "nothing";
525
+ if (Array.isArray(value))
526
+ return "a list";
527
+ if (typeof value === "object")
528
+ return "a mapping";
529
+ return `the ${typeof value} ${String(value)}`;
530
+ }
531
+ function messageOf2(error) {
532
+ return error instanceof Error ? error.message : String(error);
533
+ }
534
+ function reportIssue(reason, field, text) {
535
+ return { reason, field, text };
536
+ }
537
+ function absent(reason, block, text) {
538
+ return { present: false, reason, block, text };
539
+ }
540
+ function scopeOf(mapping, keys, prefix, issues) {
541
+ const fields = new Map;
542
+ const extras = [];
543
+ for (const [key, value] of Object.entries(mapping)) {
544
+ if (keys.includes(key))
545
+ fields.set(key, value);
546
+ else
547
+ extras.push({ key, value });
548
+ }
549
+ return { fields, extras, prefix, issues };
550
+ }
551
+ function valueOf(scope, key, required) {
552
+ const value = scope.fields.get(key) ?? null;
553
+ if (value === null && required) {
554
+ const field = `${scope.prefix}${key}`;
555
+ scope.issues.push(reportIssue("missing-field", field, `${field} is missing`));
556
+ }
557
+ return value;
558
+ }
559
+ function unusable(scope, key, why) {
560
+ const field = `${scope.prefix}${key}`;
561
+ scope.issues.push(reportIssue("unusable-field", field, `${field} ${why}`));
562
+ return null;
563
+ }
564
+ function stringProblem(value) {
565
+ if (typeof value !== "string")
566
+ return `is ${describeValue(value)}, not a string; quote it`;
567
+ if (value.trim().length === 0)
568
+ return "is blank";
569
+ return null;
570
+ }
571
+ function stringField(scope, key, required) {
572
+ const value = valueOf(scope, key, required);
573
+ if (value === null)
574
+ return null;
575
+ const problem = stringProblem(value);
576
+ return problem === null ? value : unusable(scope, key, problem);
577
+ }
578
+ function choiceField(scope, key, choices, required) {
579
+ const value = valueOf(scope, key, required);
580
+ if (value === null || isOneOf(choices, value))
581
+ return value;
582
+ return unusable(scope, key, `is ${describeValue(value)}, not one of ${choices.join(", ")}`);
583
+ }
584
+ function booleanField(scope, key, required) {
585
+ const value = valueOf(scope, key, required);
586
+ if (value === null || typeof value === "boolean")
587
+ return value;
588
+ return unusable(scope, key, `is ${describeValue(value)}, not true or false; write it unquoted`);
589
+ }
590
+ function listField(scope, key, readEntry) {
591
+ const value = valueOf(scope, key, OPTIONAL);
592
+ if (value === null)
593
+ return [];
594
+ if (!Array.isArray(value)) {
595
+ unusable(scope, key, `is ${describeValue(value)}, not a list`);
596
+ return [];
597
+ }
598
+ const entries = [];
599
+ const items = value;
600
+ for (const [index, item] of items.entries()) {
601
+ const entry = readEntry(item, `${scope.prefix}${key}[${index}]`, scope.issues);
602
+ if (entry !== null)
603
+ entries.push(entry);
604
+ }
605
+ return entries;
606
+ }
607
+ function entryScope(item, field, keys, issues) {
608
+ if (isMapping(item))
609
+ return scopeOf(item, keys, `${field}.`, issues);
610
+ const text = `${field} is ${describeValue(item)}, not a mapping of ${keys.join(", ")}; dropped`;
611
+ issues.push(reportIssue("unusable-field", field, text));
612
+ return null;
613
+ }
614
+ function readSkill(item, field, issues) {
615
+ const problem = stringProblem(item);
616
+ if (problem === null)
617
+ return item;
618
+ issues.push(reportIssue("unusable-field", field, `${field} ${problem}; dropped`));
619
+ return null;
620
+ }
621
+ function readFinding(item, field, issues) {
622
+ const scope = entryScope(item, field, FINDING_KEYS, issues);
623
+ if (scope === null)
624
+ return null;
625
+ return {
626
+ trigger: stringField(scope, "trigger", REQUIRED),
627
+ kind: choiceField(scope, "kind", FINDING_KINDS, REQUIRED),
628
+ what: stringField(scope, "what", REQUIRED),
629
+ cause: stringField(scope, "cause", OPTIONAL),
630
+ resolution: stringField(scope, "resolution", OPTIONAL),
631
+ artifact: stringField(scope, "artifact", OPTIONAL),
632
+ signal: choiceField(scope, "signal", FINDING_SIGNALS, REQUIRED),
633
+ domain: choiceField(scope, "domain", FINDING_DOMAINS, OPTIONAL),
634
+ extras: scope.extras
635
+ };
636
+ }
637
+ function readBlocker(item, field, issues) {
638
+ const scope = entryScope(item, field, BLOCKER_KEYS, issues);
639
+ if (scope === null)
640
+ return null;
641
+ return {
642
+ what: stringField(scope, "what", REQUIRED),
643
+ artifact: stringField(scope, "artifact", OPTIONAL),
644
+ extras: scope.extras
645
+ };
646
+ }
647
+ function readBug(item, field, issues) {
648
+ const scope = entryScope(item, field, BUG_KEYS, issues);
649
+ if (scope === null)
650
+ return null;
651
+ return {
652
+ what: stringField(scope, "what", REQUIRED),
653
+ artifact: stringField(scope, "artifact", OPTIONAL),
654
+ security: booleanField(scope, "security", REQUIRED),
655
+ kind: choiceField(scope, "kind", BUG_KINDS, OPTIONAL) ?? DEFAULT_BUG_KIND,
656
+ module: stringField(scope, "module", OPTIONAL),
657
+ action: stringField(scope, "action", OPTIONAL),
658
+ reproduce: stringField(scope, "reproduce", OPTIONAL),
659
+ cause: stringField(scope, "cause", OPTIONAL),
660
+ extras: scope.extras
661
+ };
662
+ }
663
+ function readChange(item, field, issues) {
664
+ const scope = entryScope(item, field, CHANGE_KEYS, issues);
665
+ if (scope === null)
666
+ return null;
667
+ return {
668
+ level: choiceField(scope, "level", CHANGE_LEVELS, REQUIRED),
669
+ area: stringField(scope, "area", OPTIONAL),
670
+ summary: stringField(scope, "summary", REQUIRED),
671
+ extras: scope.extras
672
+ };
673
+ }
674
+ function readStaleDoc(item, field, issues) {
675
+ const scope = entryScope(item, field, STALE_DOC_KEYS, issues);
676
+ if (scope === null)
677
+ return null;
678
+ const where = stringField(scope, "where", OPTIONAL);
679
+ const what = stringField(scope, "what", REQUIRED);
680
+ if (what === null)
681
+ return null;
682
+ return { where, what, extras: scope.extras };
683
+ }
684
+ function readReport(document, issues) {
685
+ const scope = scopeOf(document, REPORT_KEYS, "", issues);
686
+ return {
687
+ status: choiceField(scope, "status", REPORT_STATUSES, REQUIRED),
688
+ feedback: stringField(scope, "feedback", OPTIONAL),
689
+ findings: listField(scope, "findings", readFinding),
690
+ skillsUsed: listField(scope, "skills_used", readSkill),
691
+ blockers: listField(scope, "blockers", readBlocker),
692
+ outOfScopeBugs: listField(scope, "out_of_scope_bugs", readBug),
693
+ changes: listField(scope, "changes", readChange),
694
+ staleDocs: listField(scope, "stale_docs", readStaleDoc),
695
+ extras: scope.extras
696
+ };
697
+ }
698
+ function parseReport(output) {
699
+ const reports = readRafaBlocks(output).filter((block2) => block2.kind === REPORT_KIND);
700
+ const block = reports.at(-1);
701
+ if (block === undefined) {
702
+ return absent("no-block", null, "the session output holds no rafa:report block");
703
+ }
704
+ const line = block.span.first;
705
+ if (!block.closed) {
706
+ return absent("unclosed-block", block, `rafa:report block at line ${line} is never closed, so its body may have been cut short ` + "and is not read");
707
+ }
708
+ let document;
709
+ try {
710
+ document = Bun.YAML.parse(block.body);
711
+ } catch (error) {
712
+ const text = `rafa:report block at line ${line} is not valid YAML (${messageOf2(error)})`;
713
+ return absent("malformed-block", block, text);
714
+ }
715
+ if (!isMapping(document)) {
716
+ const text = `rafa:report block at line ${line} holds ${describeValue(document)}, ` + "not a mapping of report fields";
717
+ return absent("malformed-block", block, text);
718
+ }
719
+ const issues = [];
720
+ const report = readReport(document, issues);
721
+ return { present: true, report, block, issues };
722
+ }
723
+ // packages/rafa/src/core/plan/blocker-comment.ts
380
724
  var BLOCKER_OPEN = "<!-- blocked: ";
725
+ var REPAIRS_OPEN = "<!-- repairs: ";
381
726
  var BLOCKER_CLOSE = " -->";
382
727
  var BLOCKER_GAP = " ";
383
- var BLOCKER_COMMENT = /\s<!-- blocked: ((?:(?!<!--|-->).)*) -->$/;
384
- var OPEN_TASK_PREFIX = /^- \[(?: |BLOCKED)\] /;
728
+ var TRAILING_COMMENT = /\s<!-- (blocked|repairs): ((?:(?!<!--|-->).)*) -->$/;
385
729
  var ESCAPE_SEQUENCE = /\\(u[0-9A-Fa-f]{4}|[\s\S])/g;
386
730
  var TAB = 9;
387
731
  var FIRST_PRINTABLE = 32;
@@ -418,163 +762,45 @@ function unescapeBlockerText(escaped) {
418
762
  function blockerComment(blocker) {
419
763
  return `${BLOCKER_OPEN}${escapeBlockerText(blocker)}${BLOCKER_CLOSE}`;
420
764
  }
421
- function findBlockerComment(text) {
765
+ function repairsComment(repaired) {
766
+ return `${REPAIRS_OPEN}${escapeBlockerText(repaired)}${BLOCKER_CLOSE}`;
767
+ }
768
+ function findTrailingComment(text) {
422
769
  const trimmed = text.trimEnd();
423
- const match = BLOCKER_COMMENT.exec(trimmed);
424
- const escaped = match?.[1];
425
- if (match === null || escaped === undefined)
770
+ const match = TRAILING_COMMENT.exec(trimmed);
771
+ const kind = match?.[1];
772
+ const escaped = match?.[2];
773
+ if (match === null || escaped === undefined || kind !== "blocked" && kind !== "repairs")
426
774
  return null;
427
775
  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);
776
+ return before.length === 0 ? null : { kind, before, escaped };
777
+ }
778
+ function splitTrailingComments(taskText) {
779
+ const last = findTrailingComment(taskText);
780
+ if (last === null)
781
+ return { text: taskText, blocked: null, repairs: null };
782
+ const earlier = findTrailingComment(last.before);
783
+ const found = earlier === null || earlier.kind === last.kind ? [last] : [last, earlier];
435
784
  return {
436
- text: found.before,
437
- blocker: blocker.trim().length === 0 ? null : blocker
785
+ text: found.at(-1)?.before ?? last.before,
786
+ blocked: found.find((comment) => comment.kind === "blocked")?.escaped ?? null,
787
+ repairs: found.find((comment) => comment.kind === "repairs")?.escaped ?? null
438
788
  };
439
789
  }
440
- function splitCarriageReturn(line) {
441
- return line.endsWith("\r") ? { body: line.slice(0, -1), cr: "\r" } : { body: line, cr: "" };
442
- }
443
- function withoutBlockerComment(line) {
444
- const { body, cr } = splitCarriageReturn(line);
445
- const prefix = OPEN_TASK_PREFIX.exec(body)?.[0];
446
- if (prefix === undefined)
447
- return line;
448
- const found = findBlockerComment(body.slice(prefix.length));
449
- return found === null ? line : `${prefix}${found.before}${cr}`;
450
- }
451
- function closedBlockLines(trackerContent) {
452
- const inside = new Set;
453
- for (const block of readRafaBlocks(trackerContent)) {
454
- if (!block.closed)
455
- continue;
456
- for (let line = block.span.first;line <= block.span.last; line += 1) {
457
- inside.add(line - 1);
458
- }
459
- }
460
- return inside;
461
- }
462
- 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 };
465
- }
466
- var TASK_LINE_STATUSES = [
467
- [/^- \[BLOCKED\] (.+)/, "blocked"],
468
- [/^- \[ \] (.+)/, "unchecked"],
469
- [/^- \[x\] (.+)/, "done"]
470
- ];
471
- var NO_LINES = new Set;
472
- function findNextTask(trackerContent, options = {}) {
473
- const skip = options.skipLines ?? NO_LINES;
474
- const open = listOpenTasks(trackerContent).filter((task) => !skip.has(task.lineNum));
475
- return open.find((task) => task.status === "blocked") ?? open.find((task) => task.status === "unchecked") ?? null;
476
- }
477
- function listTrackerTasks(trackerContent) {
478
- const lines = trackerContent.split(`
479
- `);
480
- const inBlock = closedBlockLines(trackerContent);
481
- const tasks = [];
482
- for (let i = 0;i < lines.length; i++) {
483
- if (inBlock.has(i))
484
- continue;
485
- for (const [pattern, status] of TASK_LINE_STATUSES) {
486
- const capture = lines[i].match(pattern)?.[1];
487
- if (!capture)
488
- continue;
489
- tasks.push(taskInfoOf(capture, i, status));
490
- break;
491
- }
492
- }
493
- return tasks;
494
- }
495
- function isOpenTask(task) {
496
- return task.status !== "done";
790
+ function commentText(escaped) {
791
+ if (escaped === null)
792
+ return null;
793
+ const text = unescapeBlockerText(escaped);
794
+ return text.trim().length === 0 ? null : text;
497
795
  }
498
- function listOpenTasks(trackerContent) {
499
- return listTrackerTasks(trackerContent).filter(isOpenTask);
796
+ function keptRepairs(escaped) {
797
+ return escaped === null ? "" : `${BLOCKER_GAP}${REPAIRS_OPEN}${escaped}${BLOCKER_CLOSE}`;
500
798
  }
501
- function updateTrackerLine(trackerPath, lineNum, newStatus) {
502
- const lines = fs.readFileSync(trackerPath, "utf8").split(`
503
- `);
504
- const line = lines[lineNum];
505
- if (!line)
506
- return;
507
- if (newStatus === "done") {
508
- lines[lineNum] = withoutBlockerComment(line).replace(/^- \[ \]/, "- [x]").replace(/^- \[BLOCKED\]/, "- [x]");
509
- } else {
510
- lines[lineNum] = line.replace(/^- \[ \]/, "- [BLOCKED]");
511
- }
512
- fs.writeFileSync(trackerPath, lines.join(`
513
- `), "utf8");
514
- }
515
- function writeTrackerBlocker(trackerPath, lineNum, blocker) {
516
- const lines = fs.readFileSync(trackerPath, "utf8").split(`
517
- `);
518
- const line = lines[lineNum];
519
- if (line === undefined)
520
- return false;
521
- const { body, cr } = splitCarriageReturn(line);
522
- const prefix = OPEN_TASK_PREFIX.exec(body)?.[0];
523
- if (prefix === undefined)
524
- return false;
525
- const rest = body.slice(prefix.length);
526
- const text = (findBlockerComment(rest)?.before ?? rest).trimEnd();
527
- if (text.trim().length === 0)
528
- return false;
529
- const comment = blocker.trim().length === 0 ? "" : `${BLOCKER_GAP}${blockerComment(blocker)}`;
530
- lines[lineNum] = `- [BLOCKED] ${text}${comment}${cr}`;
531
- fs.writeFileSync(trackerPath, lines.join(`
532
- `), "utf8");
533
- return true;
534
- }
535
- var ANY_TASK_PREFIX = /^- \[(?: |x|BLOCKED)\] /;
536
- var LINE_TERMINATOR = /[\n\r\u2028\u2029]/;
537
- function assertInsertable(entry) {
538
- if (entry.task.trim().length === 0 || LINE_TERMINATOR.test(entry.task)) {
539
- throw new Error("insertTrackerTask: the task text must be one line and not blank");
540
- }
541
- if (entry.declaration.trim().length === 0 || /[{}]/.test(entry.declaration) || LINE_TERMINATOR.test(entry.declaration)) {
542
- throw new Error("insertTrackerTask: the declaration must be one line of entries holding no brace");
543
- }
544
- }
545
- function endOfChecklist(lines, inBlock) {
546
- for (let i = lines.length - 1;i >= 0; i -= 1) {
547
- if (!inBlock.has(i) && ANY_TASK_PREFIX.test(lines[i]))
548
- return i + 1;
549
- }
550
- const last = lines.length - 1;
551
- return lines[last] === "" ? last : lines.length;
552
- }
553
- function insertTrackerTask(trackerPath, entry, beforeLine) {
554
- assertInsertable(entry);
555
- const content = fs.readFileSync(trackerPath, "utf8");
556
- const lines = content.split(`
557
- `);
558
- const inBlock = closedBlockLines(content);
559
- if (beforeLine !== null) {
560
- const target = lines[beforeLine];
561
- if (target === undefined || inBlock.has(beforeLine) || !ANY_TASK_PREFIX.test(target)) {
562
- throw new Error(`insertTrackerTask: line ${beforeLine} is no task line`);
563
- }
564
- }
565
- const at = beforeLine ?? endOfChecklist(lines, inBlock);
566
- const { cr } = splitCarriageReturn(lines[0] ?? "");
567
- const line = `- [BLOCKED] ${entry.task.trim()}${BLOCKER_GAP}{${entry.declaration.trim()}}` + `${BLOCKER_GAP}${blockerComment(entry.blocker)}${cr}`;
568
- const written = [...lines.slice(0, at), line, ...lines.slice(at)];
569
- fs.writeFileSync(trackerPath, written.join(`
570
- `), "utf8");
571
- return at;
572
- }
573
- function trackerPathFor(planPath) {
574
- return planPath.replace(/PLAN(-[^/]*)?\.md$/, (_m, stub) => `PLAN_TRACKER${stub ?? ""}.md`);
799
+ function splitBlockerComment(taskText) {
800
+ const { text, blocked } = splitTrailingComments(taskText);
801
+ return { text, blocker: commentText(blocked) };
575
802
  }
576
-
577
- // src/plan/parse.ts
803
+ // packages/rafa/src/core/plan/parse.ts
578
804
  var PLAN_BLOCK_KINDS = ["plan", "context", "stage-context"];
579
805
  var PLAN_HEADER_FIELDS = ["stub", "issue", "spec", "release"];
580
806
  var PLAN_RELEASE_LEVELS = ["patch", "minor", "major", "none"];
@@ -593,10 +819,10 @@ function isHeaderField(key) {
593
819
  function isReleaseLevel(value) {
594
820
  return PLAN_RELEASE_LEVELS.includes(value);
595
821
  }
596
- function isMapping(value) {
822
+ function isMapping2(value) {
597
823
  return typeof value === "object" && value !== null && !Array.isArray(value);
598
824
  }
599
- function describeValue(value) {
825
+ function describeValue2(value) {
600
826
  if (typeof value === "string")
601
827
  return JSON.stringify(value);
602
828
  if (value === null)
@@ -607,7 +833,7 @@ function describeValue(value) {
607
833
  return "a mapping";
608
834
  return String(value);
609
835
  }
610
- function messageOf2(error) {
836
+ function messageOf3(error) {
611
837
  return error instanceof Error ? error.message : String(error);
612
838
  }
613
839
  function planIssue(reason, line, text) {
@@ -739,10 +965,10 @@ function unusableText(field, value) {
739
965
  return `${subject} is empty; a value opening with # after a space is a YAML comment unless quoted`;
740
966
  }
741
967
  if (field === "release") {
742
- return `${subject} is ${describeValue(value)}, not one of ${PLAN_RELEASE_LEVELS.join(", ")}`;
968
+ return `${subject} is ${describeValue2(value)}, not one of ${PLAN_RELEASE_LEVELS.join(", ")}`;
743
969
  }
744
970
  if (typeof value !== "string") {
745
- return field === "issue" ? `${subject} is ${describeValue(value)}, not a string or a whole number; quote it` : `${subject} is ${describeValue(value)}, not a string; quote it`;
971
+ return field === "issue" ? `${subject} is ${describeValue2(value)}, not a string or a whole number; quote it` : `${subject} is ${describeValue2(value)}, not a string; quote it`;
746
972
  }
747
973
  if (value.trim().length === 0)
748
974
  return `${subject} is blank`;
@@ -786,13 +1012,13 @@ function readHeader(block) {
786
1012
  try {
787
1013
  document = Bun.YAML.parse(block.body);
788
1014
  } catch (error) {
789
- const text = `rafa:plan block is not valid YAML (${messageOf2(error)})`;
1015
+ const text = `rafa:plan block is not valid YAML (${messageOf3(error)})`;
790
1016
  return { header: emptyHeader(), issues: [planIssue("malformed-header", line, text)] };
791
1017
  }
792
1018
  if (document === null)
793
1019
  return { header: emptyHeader(), issues: [] };
794
- if (!isMapping(document)) {
795
- const text = `rafa:plan block holds ${describeValue(document)}, not a mapping of fields`;
1020
+ if (!isMapping2(document)) {
1021
+ const text = `rafa:plan block holds ${describeValue2(document)}, not a mapping of fields`;
796
1022
  return { header: emptyHeader(), issues: [planIssue("malformed-header", line, text)] };
797
1023
  }
798
1024
  return readFields(document, line);
@@ -818,7 +1044,42 @@ function parsePlan(markdown) {
818
1044
  issues
819
1045
  };
820
1046
  }
821
-
1047
+ // packages/rafa/src/core/plan/plan-files.ts
1048
+ import { statSync } from "fs";
1049
+ import { basename as basename2, dirname as dirname2, join as join2, resolve } from "path";
1050
+ function plansDirAt(root, planDir) {
1051
+ return { label: planDir, path: resolve(root, planDir) };
1052
+ }
1053
+ function countTasks(tasks) {
1054
+ const done = tasks.filter((task) => task.status === "done").length;
1055
+ const blocked = tasks.filter((task) => task.status === "blocked").length;
1056
+ return { total: tasks.length, done, blocked, open: tasks.length - done - blocked };
1057
+ }
1058
+ function planFileName(stub, tracker) {
1059
+ return tracker ? `PLAN_TRACKER-${stub}.md` : `PLAN-${stub}.md`;
1060
+ }
1061
+ function stubOfPlanFile(name) {
1062
+ const stub = /^PLAN-(.+)\.md$/.exec(name)?.[1];
1063
+ return stub !== undefined && isStampableStub(stub) ? stub : null;
1064
+ }
1065
+ function isFile(path) {
1066
+ return statSync(path, { throwIfNoEntry: false })?.isFile() ?? false;
1067
+ }
1068
+ var REJECTED_DIR = "rejected";
1069
+ function rejectedPath(path) {
1070
+ const parent = dirname2(path);
1071
+ return parent === "." ? join2(REJECTED_DIR, basename2(path)) : join2(parent, REJECTED_DIR, basename2(path));
1072
+ }
1073
+ function issueLine(file, issue) {
1074
+ return `${file}:${issue.line}: ${issue.reason}: ${issue.text}`;
1075
+ }
1076
+ function plural(count, noun) {
1077
+ return count === 1 ? `${count} ${noun}` : `${count} ${noun}s`;
1078
+ }
1079
+ // packages/rafa/src/core/plan/tracker-path.ts
1080
+ function trackerPathFor(planPath) {
1081
+ return planPath.replace(/PLAN(-[^/]*)?\.md$/, (_m, stub) => `PLAN_TRACKER${stub ?? ""}.md`);
1082
+ }
822
1083
  // src/plan/inject.ts
823
1084
  var PLAN_CONTEXT_HEADING = "## Plan context";
824
1085
  var STAGE_HEADING_PREFIX = "## Stage: ";
@@ -830,19 +1091,49 @@ var CHECKBOXES = {
830
1091
  blocked: "- [BLOCKED]",
831
1092
  done: "- [x]"
832
1093
  };
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 };
1094
+ var HANDED_OVER = ", so the whole plan is handed over";
1095
+ function placeOf(position) {
1096
+ return position !== undefined && Number.isInteger(position) && position >= 0 ? position : null;
1097
+ }
1098
+ var DISPATCHED_TEXT = "the dispatched text";
1099
+ var REPAIRED_TEXT = "the text the dispatched repair names";
1100
+ function noTaskWithText(place) {
1101
+ const beyond = place === null ? "" : ` and no task number ${place + 1}`;
1102
+ 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";
1103
+ return { found: false, fallback: { reason: "no-task-with-text", text } };
1104
+ }
1105
+ function severalTasksWithText(count, place, sought) {
1106
+ const which = place === null ? "the dispatch gives no position among its tasks" : `task number ${place + 1} is none of them`;
1107
+ const text = `the plan holds ${count} tasks with ${sought} and ${which}${HANDED_OVER}`;
1108
+ return { found: false, fallback: { reason: "several-tasks-with-text", text } };
1109
+ }
1110
+ function noRepairedTask() {
1111
+ const text = `the plan holds no task with ${REPAIRED_TEXT}${HANDED_OVER}; ` + "the plan may have changed since the repair was written";
1112
+ return { found: false, fallback: { reason: "no-task-with-text", text } };
1113
+ }
1114
+ function locate(model, wanted, position) {
1115
+ const place = placeOf(position);
1116
+ const atPlace = place === null ? undefined : model.tasks[place];
1117
+ const holders = model.tasks.filter((candidate) => candidate.task === wanted.task);
1118
+ const [first] = holders;
1119
+ if (first === undefined) {
1120
+ return atPlace === undefined ? noTaskWithText(place) : { found: true, task: atPlace };
1121
+ }
1122
+ if (holders.length === 1)
1123
+ return { found: true, task: first };
1124
+ return atPlace !== undefined && holders.includes(atPlace) ? { found: true, task: atPlace } : severalTasksWithText(holders.length, place, DISPATCHED_TEXT);
1125
+ }
1126
+ function locateRepaired(model, named, position) {
1127
+ const sentence = named.trim();
1128
+ const holders = model.tasks.filter((candidate) => candidate.text.trim() === sentence);
1129
+ const [first] = holders;
1130
+ if (first === undefined)
1131
+ return noRepairedTask();
1132
+ if (holders.length === 1)
1133
+ return { found: true, task: first };
1134
+ const place = placeOf(position);
1135
+ const atPlace = place === null ? undefined : model.tasks[place];
1136
+ return atPlace !== undefined && holders.includes(atPlace) ? { found: true, task: atPlace } : severalTasksWithText(holders.length, place, REPAIRED_TEXT);
846
1137
  }
847
1138
  function contextBody(body) {
848
1139
  if (body === null || body.trim().length === 0)
@@ -862,15 +1153,22 @@ function renderDocument(sections) {
862
1153
  `;
863
1154
  }
864
1155
  function shownStatus(task, dispatched) {
865
- if (task.lineNum < dispatched.lineNum)
1156
+ if (task.lineNum < dispatched.task.lineNum)
866
1157
  return "done";
867
- if (task.lineNum === dispatched.lineNum)
868
- return "unchecked";
869
- return task.status;
1158
+ if (task.lineNum > dispatched.task.lineNum)
1159
+ return task.status;
1160
+ return dispatched.repair === null ? "unchecked" : "done";
870
1161
  }
871
1162
  function taskLine(status, task) {
872
1163
  return `${CHECKBOXES[status]} ${task.text}`;
873
1164
  }
1165
+ function openLine(dispatched) {
1166
+ return taskLine("unchecked", { text: dispatched.repair ?? dispatched.task.text });
1167
+ }
1168
+ function checklistLines(task, dispatched) {
1169
+ const line = taskLine(shownStatus(task, dispatched), task);
1170
+ return task === dispatched.task && dispatched.repair !== null ? [line, openLine(dispatched)] : [line];
1171
+ }
874
1172
  function planContextSection(model) {
875
1173
  const body = contextBody(model.context);
876
1174
  return body === null ? null : section(PLAN_CONTEXT_HEADING, body);
@@ -894,17 +1192,17 @@ function stageListSection(model, dispatched) {
894
1192
  `));
895
1193
  }
896
1194
  function renderStage(model, dispatched) {
897
- const checklist = model.tasks.filter((task) => task.stage === dispatched.stage).map((task) => taskLine(shownStatus(task, dispatched), task));
1195
+ const checklist = model.tasks.filter((task) => task.stage === dispatched.task.stage).flatMap((task) => checklistLines(task, dispatched));
898
1196
  return renderDocument([
899
1197
  planContextSection(model),
900
- stageSection(model, dispatched, checklist),
901
- stageListSection(model, dispatched)
1198
+ stageSection(model, dispatched.task, checklist),
1199
+ stageListSection(model, dispatched.task)
902
1200
  ]);
903
1201
  }
904
1202
  function renderTask(model, dispatched) {
905
1203
  return renderDocument([
906
1204
  planContextSection(model),
907
- stageSection(model, dispatched, [taskLine("unchecked", dispatched)])
1205
+ stageSection(model, dispatched.task, [openLine(dispatched)])
908
1206
  ]);
909
1207
  }
910
1208
  var RENDERERS = {
@@ -912,14 +1210,15 @@ var RENDERERS = {
912
1210
  task: renderTask
913
1211
  };
914
1212
  function renderInjection(request) {
915
- const { mode, plan, task } = request;
916
- if (mode === "full")
917
- return { requested: mode, mode, text: plan, fallback: null };
1213
+ const { mode, plan, task, position, repairs } = request;
1214
+ if (mode === "full" || repairs === null)
1215
+ return { requested: mode, mode: "full", text: plan, fallback: null };
918
1216
  const model = parsePlan(plan);
919
- const location = locate(model, task);
1217
+ const location = repairs === undefined ? locate(model, task, position) : locateRepaired(model, repairs, position);
920
1218
  if (!location.found) {
921
1219
  return { requested: mode, mode: "full", text: plan, fallback: location.fallback };
922
1220
  }
923
- return { requested: mode, mode, text: RENDERERS[mode](model, location.task), fallback: null };
1221
+ const repair = repairs === undefined ? null : parseTaskDeclaration(task.task).text.trim();
1222
+ return { requested: mode, mode, text: RENDERERS[mode](model, { task: location.task, repair }), fallback: null };
924
1223
  }
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 };
1224
+ export { RAFA_BLOCK_KINDS, isRafaBlockKind, readRafaBlocks, REPORT_STATUSES, FINDING_KINDS, FINDING_SIGNALS, FINDING_DOMAINS, CHANGE_LEVELS, BUG_KINDS, parseReport, BLOCKER_GAP, escapeBlockerText, unescapeBlockerText, blockerComment, repairsComment, splitTrailingComments, commentText, keptRepairs, splitBlockerComment, PLAN_BLOCK_KINDS, PLAN_HEADER_FIELDS, PLAN_RELEASE_LEVELS, parsePlan, plansDirAt, countTasks, planFileName, stubOfPlanFile, isFile, rejectedPath, issueLine, plural, trackerPathFor, parsePrerequisites, planPrerequisites, malformedPrerequisiteLines, mergePlanPrerequisites, prerequisitesPathForPlan, loadPlanPrerequisites, PROBE_TIMEOUT_MS, runShellProbe, runPreflight, renderInjection };