@pmelab/gtd 59.0.0 → 60.0.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 (90) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +42 -38
  3. package/claude/hooks/doors.ts +8 -4
  4. package/dist/chunks/flows-DENt0uWV.mjs +707 -0
  5. package/dist/chunks/{steering-CCNvngSH.mjs → steering-DF_BMadH.mjs} +292 -289
  6. package/dist/chunks/{testkit-CMBAAyRK.mjs → testkit-BvecVjQ0.mjs} +937 -772
  7. package/dist/chunks/{wire-C12dahrH.mjs → wire-DZ8R1RU9.mjs} +9 -2
  8. package/dist/flows.mjs +2 -676
  9. package/dist/gtd.bundle.mjs +1101 -1025
  10. package/dist/steering.mjs +2 -2
  11. package/dist/testkit/gherkin.mjs +371 -82
  12. package/dist/testkit.mjs +3 -2
  13. package/dist/web/main.js +234 -232
  14. package/dist/wire.mjs +2 -2
  15. package/dist/workflow-kit.mjs +3 -3
  16. package/package.json +1 -1
  17. package/skills/authoring/SKILL.md +68 -61
  18. package/src/cli/Cli.test.ts +311 -229
  19. package/src/cli/Cli.ts +486 -585
  20. package/src/cli/Help.test.ts +47 -0
  21. package/src/cli/Help.ts +70 -0
  22. package/src/cli/History.ts +9 -4
  23. package/src/cli/Judges.ts +1 -1
  24. package/src/cli/Output.test.ts +35 -0
  25. package/src/cli/Output.ts +31 -1
  26. package/src/cli/Program.test.ts +131 -82
  27. package/src/cli/Program.ts +197 -111
  28. package/src/edge/Edge.test.ts +64 -56
  29. package/src/edge/Edge.ts +4 -4
  30. package/src/emit/Emit.test.ts +1 -1
  31. package/src/emit/Emit.ts +1 -1
  32. package/src/flows/factory.test.ts +14 -0
  33. package/src/flows/factory.ts +25 -6
  34. package/src/flows/helpers.test.ts +35 -0
  35. package/src/flows/helpers.ts +19 -10
  36. package/src/flows/index.test.ts +66 -0
  37. package/src/flows/index.ts +109 -5
  38. package/src/flows/internal.ts +7 -0
  39. package/src/flows/runtime.test.ts +192 -1
  40. package/src/flows/runtime.ts +188 -54
  41. package/src/gherkin/git-control.feature +6 -6
  42. package/src/gherkin/index.test.ts +127 -0
  43. package/src/gherkin/index.ts +481 -98
  44. package/src/gherkin/messages.test.ts +124 -0
  45. package/src/gherkin/messages.ts +101 -0
  46. package/src/gherkin/more-steps.feature +170 -0
  47. package/src/gherkin/scenario-end.feature +101 -0
  48. package/src/gherkin/testkit.feature +83 -7
  49. package/src/install/Install.test.ts +7 -7
  50. package/src/install/Install.ts +8 -8
  51. package/src/replay/Replay.test.ts +13 -2
  52. package/src/replay/Replay.ts +19 -30
  53. package/src/replay/judgeDiffShape.test.ts +1 -1
  54. package/src/steering/Footnotes.test.ts +1 -1
  55. package/src/steering/SteeringFormats.test.ts +1 -1
  56. package/src/steering/freeform.test.ts +1 -1
  57. package/src/steering/index.test.ts +27 -1
  58. package/src/steering/index.ts +6 -104
  59. package/src/steering/internal.ts +11 -0
  60. package/src/steering/qa.test.ts +7 -2
  61. package/src/steering/reads.test.ts +42 -0
  62. package/src/steering/reads.ts +43 -0
  63. package/src/steering/registry.ts +93 -0
  64. package/src/steering/review.test.ts +2 -2
  65. package/src/step/planStart.test.ts +18 -16
  66. package/src/step/planStart.ts +9 -9
  67. package/src/testkit/TestRepo.test.ts +289 -6
  68. package/src/testkit/TestRepo.ts +317 -41
  69. package/src/testkit/imports.test.ts +58 -0
  70. package/src/testkit/imports.ts +102 -0
  71. package/src/testkit/index.test.ts +28 -0
  72. package/src/testkit/index.ts +14 -2
  73. package/src/twins/EmittedScriptRecognizer.ts +7 -14
  74. package/src/twins/EmittedScripts.test.ts +4 -2
  75. package/src/twins/InMemRepo.ts +1 -0
  76. package/src/wire/Land.ts +1 -1
  77. package/src/wire/Script.ts +11 -0
  78. package/src/wire/WireSchema.test.ts +3 -0
  79. package/src/wire/golden/1/script.json +4 -0
  80. package/src/wire/index.ts +1 -0
  81. package/src/workflow/SteeringFormats.test.ts +1 -1
  82. package/src/workflow/compile.ts +2 -2
  83. package/src/workflow/discovery.test.ts +3 -3
  84. package/src/workflow/load.test.ts +256 -116
  85. package/src/workflow/load.ts +175 -152
  86. package/src/workflow/skills.test.ts +5 -5
  87. package/src/workflow/skills.ts +1 -1
  88. package/src/workflow-kit/index.test.ts +25 -0
  89. package/src/workflow-kit/index.ts +37 -4
  90. package/src/workflow-kit/review.ts +2 -2
package/dist/web/main.js CHANGED
@@ -30764,6 +30764,238 @@ const blockRunInline = (content, nodes, options) => {
30764
30764
  return joinInlineRuns(visibleNodes(content, nodes, options).map((node) => nodeInline(content, node, options)));
30765
30765
  };
30766
30766
  //#endregion
30767
+ //#region src/steering/findings.ts
30768
+ /** The `# ` heading the findings a judge called noise sit under. */
30769
+ const NOISE_HEADING = "Dropped as noise";
30770
+ const OPEN_GROUP = "Open";
30771
+ const MARKER = /\[\^[^\]\s]+\]/g;
30772
+ const isBox = (item, content) => {
30773
+ if (typeof item.checked !== "boolean") return false;
30774
+ const first = item.children[0];
30775
+ if (first?.type !== "paragraph" || first.position === void 0) return false;
30776
+ const text = content.slice(first.position.start.offset, first.position.end.offset);
30777
+ return new RegExp(`^fix(?![\\w-])`).test(text);
30778
+ };
30779
+ const slice = (content, node) => content.slice(node.position.start.offset, node.position.end.offset);
30780
+ const lineOf = (node, end = false) => toLspPosition(end ? node.position.end : node.position.start).line;
30781
+ /** The box item's own text past its label: a formatter may fold a guidance line into it. */
30782
+ const inlineGuidance = (content, item) => {
30783
+ const first = item.children[0];
30784
+ return content.slice(first.position.start.offset, item.position.end.offset).slice(3).replace(/^[\s:—–-]+/, "").trim();
30785
+ };
30786
+ const guidanceOf = (parts) => parts.map((p) => p.replace(MARKER, "").trim()).filter((p) => p !== "").join("\n\n");
30787
+ /** `nodes` split at `box`: the body before it, the guidance after it. */
30788
+ const splitAtBox = (content, nodes, box) => {
30789
+ const at = nodes.findIndex((n) => n.type === "list" && n.children.includes(box));
30790
+ const list = nodes[at];
30791
+ const boxAt = list.children.indexOf(box);
30792
+ const bodyNodes = nodes.slice(0, boxAt > 0 ? at + 1 : at);
30793
+ const trailing = list.children.slice(boxAt + 1);
30794
+ const after = nodes.slice(at + 1);
30795
+ return {
30796
+ bodyNodes,
30797
+ body: bodyNodes.map((n) => n === list ? content.slice(n.position.start.offset, box.position.start.offset).trimEnd() : slice(content, n)).join("\n\n"),
30798
+ notBody: list.children.slice(boxAt),
30799
+ guidance: guidanceOf([inlineGuidance(content, box), ...[...trailing, ...after].map((n) => slice(content, n))]),
30800
+ last: after.at(-1) ?? trailing.at(-1) ?? box
30801
+ };
30802
+ };
30803
+ const entryOf = (content, title, noise, headingLine, nodes, endLine) => {
30804
+ const notes = nodes.filter((n) => n.type !== "footnoteDefinition");
30805
+ const boxes = notes.flatMap((n) => n.type === "list" ? n.children.filter((i) => isBox(i, content)) : []);
30806
+ const box = boxes[0];
30807
+ const split = box === void 0 ? {
30808
+ bodyNodes: notes,
30809
+ body: notes.map((n) => slice(content, n)).join("\n\n"),
30810
+ notBody: [],
30811
+ guidance: ""
30812
+ } : splitAtBox(content, notes, box);
30813
+ return {
30814
+ title,
30815
+ body: split.body,
30816
+ noise,
30817
+ fix: box?.checked === true,
30818
+ guidance: split.guidance,
30819
+ boxes: boxes.length,
30820
+ headingLine,
30821
+ bodyNodes: split.bodyNodes,
30822
+ ...box === void 0 ? {} : { box },
30823
+ notBody: new Set(split.notBody),
30824
+ lastLine: split.last === void 0 ? headingLine : lineOf(split.last, true),
30825
+ endLine
30826
+ };
30827
+ };
30828
+ const parse = (content) => {
30829
+ const tree = parseMarkdown(content);
30830
+ const entries = [];
30831
+ let noise = false;
30832
+ let open;
30833
+ const close = (endLine) => {
30834
+ if (open !== void 0) entries.push(entryOf(content, open.title, noise, open.line, open.nodes, endLine));
30835
+ open = void 0;
30836
+ };
30837
+ for (const node of tree.children) {
30838
+ if (node.type === "heading" && node.depth <= 2) {
30839
+ close(lineOf(node) - 1);
30840
+ const text = headingText$1(content, node, (t) => t.replace(MARKER, ""));
30841
+ if (node.depth === 1) noise = text === NOISE_HEADING;
30842
+ else open = {
30843
+ title: text,
30844
+ line: lineOf(node),
30845
+ nodes: []
30846
+ };
30847
+ continue;
30848
+ }
30849
+ open?.nodes.push(node);
30850
+ }
30851
+ close(content.split(/\r?\n/).length - 1);
30852
+ return entries;
30853
+ };
30854
+ const entryFindings = (content) => parse(content).flatMap((e) => {
30855
+ if (e.boxes === 0) return [{
30856
+ message: `"${e.title}" has no \`- [ ] fix\` box — restore it, ticked to fix the finding or unticked to drop it`,
30857
+ line: e.headingLine
30858
+ }];
30859
+ if (e.boxes > 1) return [{
30860
+ message: `"${e.title}" has ${e.boxes} \`fix\` boxes — keep exactly one`,
30861
+ line: e.headingLine
30862
+ }];
30863
+ if (!e.fix && e.guidance !== "") return [{
30864
+ message: `"${e.title}" has guidance under an unticked box — tick it to fix the finding, or delete the text to drop it`,
30865
+ line: lineOf(e.box)
30866
+ }];
30867
+ return [];
30868
+ });
30869
+ const findingsView = (content) => {
30870
+ const noteAt = noteLookup(content);
30871
+ return { nodes: parse(content).map((e, index) => {
30872
+ const note = noteAt(e.headingLine);
30873
+ return {
30874
+ title: e.title,
30875
+ anchor: {
30876
+ kind: "chunk",
30877
+ index
30878
+ },
30879
+ group: e.noise ? NOISE_HEADING : OPEN_GROUP,
30880
+ ...e.boxes === 1 ? { checked: e.fix } : {},
30881
+ ...e.guidance === "" ? {} : { note: e.guidance },
30882
+ ...note?.thread !== void 0 ? { thread: note.thread } : {},
30883
+ body: blockNodesOfRun(content, e.bodyNodes, { skipListItem: (i) => e.notBody.has(i) })
30884
+ };
30885
+ }) };
30886
+ };
30887
+ const findingsOutline = (content) => {
30888
+ const lines = content.split(/\r?\n/);
30889
+ return [...parse(content).map((e) => ({
30890
+ name: e.title,
30891
+ detail: e.noise ? "noise" : e.fix ? "fix" : "drop",
30892
+ range: spanRange(lines, e.headingLine, Math.max(e.headingLine, e.endLine)),
30893
+ selectionRange: lineRange(lines, e.headingLine)
30894
+ })), ...threadOutlineNodes(content).map((t) => t.node)];
30895
+ };
30896
+ const findingsAnnotate = (content, anchor, text) => {
30897
+ const entries = parse(content);
30898
+ const lines = content.split(/\r?\n/);
30899
+ const resolved = (() => {
30900
+ if (anchor.kind === "chunk") {
30901
+ const e = entries[anchor.index];
30902
+ return e && {
30903
+ line: e.headingLine,
30904
+ blockEndLine: e.headingLine,
30905
+ key: `chunk:${anchor.index}`
30906
+ };
30907
+ }
30908
+ if (anchor.kind !== "paragraph") return void 0;
30909
+ const block = entries.flatMap((e) => e.bodyNodes).find((n) => anchor.line >= lineOf(n) && anchor.line <= lineOf(n, true));
30910
+ return block && {
30911
+ line: anchor.line,
30912
+ blockEndLine: lineOf(block, true),
30913
+ key: `paragraph:${anchor.line}`
30914
+ };
30915
+ })();
30916
+ if (resolved === void 0) return {
30917
+ ok: false,
30918
+ reason: "anchor-not-found"
30919
+ };
30920
+ const result = footnoteAttachEdits(content, {
30921
+ ...resolved,
30922
+ endCharacter: (lines[resolved.line] ?? "").length
30923
+ }, text);
30924
+ return result.ok ? {
30925
+ ok: true,
30926
+ edits: result.edits
30927
+ } : result;
30928
+ };
30929
+ /** Ticks or unticks a finding's box; `text` replaces its guidance, and unticking drops it. */
30930
+ const findingsApply = (content, anchor, opts) => {
30931
+ if (anchor.kind !== "chunk") return {
30932
+ ok: false,
30933
+ reason: "anchor-not-found"
30934
+ };
30935
+ const e = parse(content)[anchor.index];
30936
+ if (e?.box === void 0 || e.boxes !== 1) return {
30937
+ ok: false,
30938
+ reason: "anchor-not-found"
30939
+ };
30940
+ const fix = opts.checked ?? e.fix;
30941
+ const guidance = fix ? (opts.text ?? e.guidance).trim() : "";
30942
+ if (fix === e.fix && guidance === e.guidance) return {
30943
+ ok: true,
30944
+ edits: []
30945
+ };
30946
+ const lines = content.split(/\r?\n/);
30947
+ const eol = eolOf(content);
30948
+ const start = lineOf(e.box);
30949
+ const box = `${lines[start].match(/^\s*[-*+]\s+/)[0]}[${fix ? "x" : " "}] fix`;
30950
+ const text = guidance === "" ? box : `${box}${eol}${eol}${guidance.split(/\r?\n/).join(eol)}`;
30951
+ return {
30952
+ ok: true,
30953
+ edits: [{
30954
+ range: {
30955
+ start: {
30956
+ line: start,
30957
+ character: 0
30958
+ },
30959
+ end: lineRange(lines, e.lastLine).end
30960
+ },
30961
+ newText: text
30962
+ }]
30963
+ };
30964
+ };
30965
+ /** The finding gate's file: one `## ` finding each, with one `fix` box and optional guidance below it. */
30966
+ const findingsDescriptor = {
30967
+ sample: `# Quality
30968
+
30969
+ ## A finding a lens wrote[^nafind1]
30970
+
30971
+ [^nafind1]:
30972
+ Attached via the phone UI, this note shows a comment on a finding with a
30973
+ \`multi word code span\` that runs past eighty characters in total length.
30974
+
30975
+ What the lens found, and where.
30976
+
30977
+ - [x] fix
30978
+
30979
+ Fix it the simple way.
30980
+
30981
+ # Dropped as noise
30982
+
30983
+ ## A finding the judge called noise
30984
+
30985
+ Why the judge dropped it.
30986
+
30987
+ - [ ] fix
30988
+ `,
30989
+ validate: (content) => [...entryFindings(content), ...threadFindings(content)],
30990
+ outline: findingsOutline,
30991
+ actions: (content, range) => threadReplyActions(content, range.start),
30992
+ view: findingsView,
30993
+ annotate: findingsAnnotate,
30994
+ apply: findingsApply,
30995
+ clearTicks: (content) => content,
30996
+ openThreads: openThreadsOf
30997
+ };
30998
+ //#endregion
30767
30999
  //#region src/steering/qa.ts
30768
31000
  /**
30769
31001
  * A marker as plain text. Local to this module because `headingText`/
@@ -32566,242 +32798,12 @@ const decisionOf$1 = (chunk) => {
32566
32798
  };
32567
32799
  };
32568
32800
  //#endregion
32569
- //#region src/steering/findings.ts
32570
- /** The `# ` heading the findings a judge called noise sit under. */
32571
- const NOISE_HEADING = "Dropped as noise";
32572
- const OPEN_GROUP = "Open";
32573
- const MARKER = /\[\^[^\]\s]+\]/g;
32574
- const isBox = (item, content) => {
32575
- if (typeof item.checked !== "boolean") return false;
32576
- const first = item.children[0];
32577
- if (first?.type !== "paragraph" || first.position === void 0) return false;
32578
- const text = content.slice(first.position.start.offset, first.position.end.offset);
32579
- return new RegExp(`^fix(?![\\w-])`).test(text);
32580
- };
32581
- const slice = (content, node) => content.slice(node.position.start.offset, node.position.end.offset);
32582
- const lineOf = (node, end = false) => toLspPosition(end ? node.position.end : node.position.start).line;
32583
- /** The box item's own text past its label: a formatter may fold a guidance line into it. */
32584
- const inlineGuidance = (content, item) => {
32585
- const first = item.children[0];
32586
- return content.slice(first.position.start.offset, item.position.end.offset).slice(3).replace(/^[\s:—–-]+/, "").trim();
32587
- };
32588
- const guidanceOf = (parts) => parts.map((p) => p.replace(MARKER, "").trim()).filter((p) => p !== "").join("\n\n");
32589
- /** `nodes` split at `box`: the body before it, the guidance after it. */
32590
- const splitAtBox = (content, nodes, box) => {
32591
- const at = nodes.findIndex((n) => n.type === "list" && n.children.includes(box));
32592
- const list = nodes[at];
32593
- const boxAt = list.children.indexOf(box);
32594
- const bodyNodes = nodes.slice(0, boxAt > 0 ? at + 1 : at);
32595
- const trailing = list.children.slice(boxAt + 1);
32596
- const after = nodes.slice(at + 1);
32597
- return {
32598
- bodyNodes,
32599
- body: bodyNodes.map((n) => n === list ? content.slice(n.position.start.offset, box.position.start.offset).trimEnd() : slice(content, n)).join("\n\n"),
32600
- notBody: list.children.slice(boxAt),
32601
- guidance: guidanceOf([inlineGuidance(content, box), ...[...trailing, ...after].map((n) => slice(content, n))]),
32602
- last: after.at(-1) ?? trailing.at(-1) ?? box
32603
- };
32604
- };
32605
- const entryOf = (content, title, noise, headingLine, nodes, endLine) => {
32606
- const notes = nodes.filter((n) => n.type !== "footnoteDefinition");
32607
- const boxes = notes.flatMap((n) => n.type === "list" ? n.children.filter((i) => isBox(i, content)) : []);
32608
- const box = boxes[0];
32609
- const split = box === void 0 ? {
32610
- bodyNodes: notes,
32611
- body: notes.map((n) => slice(content, n)).join("\n\n"),
32612
- notBody: [],
32613
- guidance: ""
32614
- } : splitAtBox(content, notes, box);
32615
- return {
32616
- title,
32617
- body: split.body,
32618
- noise,
32619
- fix: box?.checked === true,
32620
- guidance: split.guidance,
32621
- boxes: boxes.length,
32622
- headingLine,
32623
- bodyNodes: split.bodyNodes,
32624
- ...box === void 0 ? {} : { box },
32625
- notBody: new Set(split.notBody),
32626
- lastLine: split.last === void 0 ? headingLine : lineOf(split.last, true),
32627
- endLine
32628
- };
32629
- };
32630
- const parse = (content) => {
32631
- const tree = parseMarkdown(content);
32632
- const entries = [];
32633
- let noise = false;
32634
- let open;
32635
- const close = (endLine) => {
32636
- if (open !== void 0) entries.push(entryOf(content, open.title, noise, open.line, open.nodes, endLine));
32637
- open = void 0;
32638
- };
32639
- for (const node of tree.children) {
32640
- if (node.type === "heading" && node.depth <= 2) {
32641
- close(lineOf(node) - 1);
32642
- const text = headingText$1(content, node, (t) => t.replace(MARKER, ""));
32643
- if (node.depth === 1) noise = text === NOISE_HEADING;
32644
- else open = {
32645
- title: text,
32646
- line: lineOf(node),
32647
- nodes: []
32648
- };
32649
- continue;
32650
- }
32651
- open?.nodes.push(node);
32652
- }
32653
- close(content.split(/\r?\n/).length - 1);
32654
- return entries;
32655
- };
32656
- const entryFindings = (content) => parse(content).flatMap((e) => {
32657
- if (e.boxes === 0) return [{
32658
- message: `"${e.title}" has no \`- [ ] fix\` box — restore it, ticked to fix the finding or unticked to drop it`,
32659
- line: e.headingLine
32660
- }];
32661
- if (e.boxes > 1) return [{
32662
- message: `"${e.title}" has ${e.boxes} \`fix\` boxes — keep exactly one`,
32663
- line: e.headingLine
32664
- }];
32665
- if (!e.fix && e.guidance !== "") return [{
32666
- message: `"${e.title}" has guidance under an unticked box — tick it to fix the finding, or delete the text to drop it`,
32667
- line: lineOf(e.box)
32668
- }];
32669
- return [];
32670
- });
32671
- const findingsView = (content) => {
32672
- const noteAt = noteLookup(content);
32673
- return { nodes: parse(content).map((e, index) => {
32674
- const note = noteAt(e.headingLine);
32675
- return {
32676
- title: e.title,
32677
- anchor: {
32678
- kind: "chunk",
32679
- index
32680
- },
32681
- group: e.noise ? NOISE_HEADING : OPEN_GROUP,
32682
- ...e.boxes === 1 ? { checked: e.fix } : {},
32683
- ...e.guidance === "" ? {} : { note: e.guidance },
32684
- ...note?.thread !== void 0 ? { thread: note.thread } : {},
32685
- body: blockNodesOfRun(content, e.bodyNodes, { skipListItem: (i) => e.notBody.has(i) })
32686
- };
32687
- }) };
32688
- };
32689
- const findingsOutline = (content) => {
32690
- const lines = content.split(/\r?\n/);
32691
- return [...parse(content).map((e) => ({
32692
- name: e.title,
32693
- detail: e.noise ? "noise" : e.fix ? "fix" : "drop",
32694
- range: spanRange(lines, e.headingLine, Math.max(e.headingLine, e.endLine)),
32695
- selectionRange: lineRange(lines, e.headingLine)
32696
- })), ...threadOutlineNodes(content).map((t) => t.node)];
32697
- };
32698
- const findingsAnnotate = (content, anchor, text) => {
32699
- const entries = parse(content);
32700
- const lines = content.split(/\r?\n/);
32701
- const resolved = (() => {
32702
- if (anchor.kind === "chunk") {
32703
- const e = entries[anchor.index];
32704
- return e && {
32705
- line: e.headingLine,
32706
- blockEndLine: e.headingLine,
32707
- key: `chunk:${anchor.index}`
32708
- };
32709
- }
32710
- if (anchor.kind !== "paragraph") return void 0;
32711
- const block = entries.flatMap((e) => e.bodyNodes).find((n) => anchor.line >= lineOf(n) && anchor.line <= lineOf(n, true));
32712
- return block && {
32713
- line: anchor.line,
32714
- blockEndLine: lineOf(block, true),
32715
- key: `paragraph:${anchor.line}`
32716
- };
32717
- })();
32718
- if (resolved === void 0) return {
32719
- ok: false,
32720
- reason: "anchor-not-found"
32721
- };
32722
- const result = footnoteAttachEdits(content, {
32723
- ...resolved,
32724
- endCharacter: (lines[resolved.line] ?? "").length
32725
- }, text);
32726
- return result.ok ? {
32727
- ok: true,
32728
- edits: result.edits
32729
- } : result;
32730
- };
32731
- /** Ticks or unticks a finding's box; `text` replaces its guidance, and unticking drops it. */
32732
- const findingsApply = (content, anchor, opts) => {
32733
- if (anchor.kind !== "chunk") return {
32734
- ok: false,
32735
- reason: "anchor-not-found"
32736
- };
32737
- const e = parse(content)[anchor.index];
32738
- if (e?.box === void 0 || e.boxes !== 1) return {
32739
- ok: false,
32740
- reason: "anchor-not-found"
32741
- };
32742
- const fix = opts.checked ?? e.fix;
32743
- const guidance = fix ? (opts.text ?? e.guidance).trim() : "";
32744
- if (fix === e.fix && guidance === e.guidance) return {
32745
- ok: true,
32746
- edits: []
32747
- };
32748
- const lines = content.split(/\r?\n/);
32749
- const eol = eolOf(content);
32750
- const start = lineOf(e.box);
32751
- const box = `${lines[start].match(/^\s*[-*+]\s+/)[0]}[${fix ? "x" : " "}] fix`;
32752
- const text = guidance === "" ? box : `${box}${eol}${eol}${guidance.split(/\r?\n/).join(eol)}`;
32753
- return {
32754
- ok: true,
32755
- edits: [{
32756
- range: {
32757
- start: {
32758
- line: start,
32759
- character: 0
32760
- },
32761
- end: lineRange(lines, e.lastLine).end
32762
- },
32763
- newText: text
32764
- }]
32765
- };
32766
- };
32767
- //#endregion
32768
- //#region src/steering/index.ts
32801
+ //#region src/steering/registry.ts
32769
32802
  /** The built-in registry: mode name → its format, in registry order. The single place the built-in formats are wired into names — everything else in this module dispatches over a resolved `SteeringFormat`, never a mode-name string. */
32770
32803
  const REGISTRY = /* @__PURE__ */ new Map([
32771
32804
  ["qa", qaDescriptor],
32772
32805
  ["review", reviewDescriptor],
32773
- ["findings", {
32774
- sample: `# Quality
32775
-
32776
- ## A finding a lens wrote[^nafind1]
32777
-
32778
- [^nafind1]:
32779
- Attached via the phone UI, this note shows a comment on a finding with a
32780
- \`multi word code span\` that runs past eighty characters in total length.
32781
-
32782
- What the lens found, and where.
32783
-
32784
- - [x] fix
32785
-
32786
- Fix it the simple way.
32787
-
32788
- # Dropped as noise
32789
-
32790
- ## A finding the judge called noise
32791
-
32792
- Why the judge dropped it.
32793
-
32794
- - [ ] fix
32795
- `,
32796
- validate: (content) => [...entryFindings(content), ...threadFindings(content)],
32797
- outline: findingsOutline,
32798
- actions: (content, range) => threadReplyActions(content, range.start),
32799
- view: findingsView,
32800
- annotate: findingsAnnotate,
32801
- apply: findingsApply,
32802
- clearTicks: (content) => content,
32803
- openThreads: openThreadsOf
32804
- }]
32806
+ ["findings", findingsDescriptor]
32805
32807
  ]);
32806
32808
  [...REGISTRY.keys()];
32807
32809
  /** The built-in `SteeringFormat` registered under `mode`'s name, or `undefined` when it isn't a built-in mode at all. The one place a bare mode-name string is ever looked up — every consumer takes the RESOLVED value this returns, never a name, so "mode is required, never inferred from a file's basename" is a type, not a rule to remember. */
package/dist/wire.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { C as EXIT_SIGTERM, D as UNATTRIBUTED_MODEL, E as ACCESS_REFUSAL, O as WIRE_SCHEMA, S as EXIT_SIGINT, T as EXIT_USAGE_ERROR, _ as demandOf, a as landFields, b as EXIT_OK, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, h as BEAT_KINDS, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, o as landProseText, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches, u as DEMAND_BRIEFING, v as stallDiagnosis, w as EXIT_STRAYS_FOUND, x as EXIT_RUNTIME_ERROR, y as EXIT_CODES } from "./chunks/wire-C12dahrH.mjs";
2
- export { ACCESS_REFUSAL, BEAT_KINDS, DEMAND_BRIEFING, EXIT_CODES, EXIT_OK, EXIT_RUNTIME_ERROR, EXIT_SIGINT, EXIT_SIGTERM, EXIT_STRAYS_FOUND, EXIT_USAGE_ERROR, UNATTRIBUTED_MODEL, WIRE_SCHEMA, beatDocument, beatKindOf, demandOf, globMatches, judgeJson, landFields, landProseText, noopText, renderBeatJson, renderBeatPlain, renderFormat, renderLandJson, renderLandPlain, resolveSession, selectPath, stallDiagnosis, statusOf };
1
+ import { A as WIRE_SCHEMA, C as EXIT_RUNTIME_ERROR, D as EXIT_USAGE_ERROR, E as EXIT_STRAYS_FOUND, O as ACCESS_REFUSAL, S as EXIT_OK, T as EXIT_SIGTERM, _ as BEAT_KINDS, a as renderScriptJson, b as stallDiagnosis, c as landProseText, d as renderLandPlain, f as DEMAND_BRIEFING, g as statusOf, h as renderBeatJson, i as judgeJson, j as renderFormat, k as UNATTRIBUTED_MODEL, l as noopText, m as beatDocument, n as resolveSession, o as scriptFields, p as renderBeatPlain, r as selectPath, s as landFields, t as globMatches, u as renderLandJson, v as beatKindOf, w as EXIT_SIGINT, x as EXIT_CODES, y as demandOf } from "./chunks/wire-DZ8R1RU9.mjs";
2
+ export { ACCESS_REFUSAL, BEAT_KINDS, DEMAND_BRIEFING, EXIT_CODES, EXIT_OK, EXIT_RUNTIME_ERROR, EXIT_SIGINT, EXIT_SIGTERM, EXIT_STRAYS_FOUND, EXIT_USAGE_ERROR, UNATTRIBUTED_MODEL, WIRE_SCHEMA, beatDocument, beatKindOf, demandOf, globMatches, judgeJson, landFields, landProseText, noopText, renderBeatJson, renderBeatPlain, renderFormat, renderLandJson, renderLandPlain, renderScriptJson, resolveSession, scriptFields, selectPath, stallDiagnosis, statusOf };
@@ -1,5 +1,5 @@
1
- import { changes, changesSince, hasThreadFor, head, mark, read, refuse, requireReplies, requireThreadsClosed, scope } from "./flows.mjs";
2
- import { A as parseThreads, C as unansweredNotes, b as reviewNotes, c as stripCodeThreads, x as reviewRisks, y as reviewDecisions } from "./chunks/steering-CCNvngSH.mjs";
1
+ import { B as head, F as changesSince, G as mark, P as changes, X as read, Z as refuse, d as hasThreadFor, et as scope, g as requireReplies, v as requireThreadsClosed } from "./chunks/flows-DENt0uWV.mjs";
2
+ import { A as parseThreads, S as unansweredNotes, b as reviewRisks, n as stripCodeThreads, v as reviewDecisions, y as reviewNotes } from "./chunks/steering-DF_BMadH.mjs";
3
3
  //#region src/workflow-kit/health.ts
4
4
  /** Hand a red suite that repeated attempts could not fix to a person, until `limit` rounds are spent. */
5
5
  const escalation = async (count, steps) => {
@@ -427,4 +427,4 @@ const reviewToHandOff = async (base, p) => {
427
427
  }
428
428
  };
429
429
  //#endregion
430
- export { assertionLines, escalation, fixFindings, gate, healthy, lensEntriesOf, lensOf, lensScopes, lensesOf, qualityLap, removedAssertions, review, reviewToHandOff, routeNotes, untilSettled };
430
+ export { assertionLines, escalation, fixFindings, gate, healthy, lensEntriesOf, lensOf, lensScopes, lensesOf, qualityLap, removedAssertions, review, reviewToHandOff, untilSettled };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "59.0.0",
3
+ "version": "60.0.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {