@shiyuphan/follow-me 0.1.1 → 0.3.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 (150) hide show
  1. package/README.md +119 -69
  2. package/dist/src/cli.js +1069 -140
  3. package/dist/src/comments.js +361 -0
  4. package/dist/src/git.js +32 -3
  5. package/dist/src/host.js +106 -9
  6. package/dist/src/install.js +22 -1
  7. package/dist/src/naive.js +26 -0
  8. package/dist/src/plan.js +282 -26
  9. package/dist/src/prompt.js +711 -98
  10. package/dist/src/server.js +573 -140
  11. package/dist/src/session.js +106 -104
  12. package/dist/src/syntax.js +199 -0
  13. package/dist/src/types.js +3 -1
  14. package/dist/ui/dist/assets/abnfDiagram-WEYOIA3C-DcmzRkVZ.js +1 -0
  15. package/dist/ui/dist/assets/arc-T5QB0T1C.js +1 -0
  16. package/dist/ui/dist/assets/architecture-WOLXFQ4H-DUIkQTeE.js +1 -0
  17. package/dist/ui/dist/assets/architectureDiagram-ZHVYKT53-BUYi4-bA.js +36 -0
  18. package/dist/ui/dist/assets/array-BifhSqXX.js +1 -0
  19. package/dist/ui/dist/assets/blockDiagram-RJAE7QA3-dyJxtT9e.js +140 -0
  20. package/dist/ui/dist/assets/c4Diagram-XDPPPE54-CrAgiaZR.js +38 -0
  21. package/dist/ui/dist/assets/channel-wW7ebm0i.js +1 -0
  22. package/dist/ui/dist/assets/chunk-2E7WHVMD-C6fAf4Vj.js +88 -0
  23. package/dist/ui/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  24. package/dist/ui/dist/assets/chunk-4EMNKR2T-BLZjwZ4Q.js +1 -0
  25. package/dist/ui/dist/assets/chunk-55XCU76K-DGLK2Q-L.js +1 -0
  26. package/dist/ui/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  27. package/dist/ui/dist/assets/chunk-7S6XSTFO-BablszTX.js +2 -0
  28. package/dist/ui/dist/assets/chunk-AQDTSKZB-Cuk3B_vb.js +72 -0
  29. package/dist/ui/dist/assets/chunk-DSNLSHJZ-DiC78RqE.js +127 -0
  30. package/dist/ui/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  31. package/dist/ui/dist/assets/chunk-HAGFOTED-X_RbvixX.js +62 -0
  32. package/dist/ui/dist/assets/chunk-HNE4Q36P-C14_x5ck.js +1 -0
  33. package/dist/ui/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  34. package/dist/ui/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  35. package/dist/ui/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  36. package/dist/ui/dist/assets/chunk-NQOKRVIF-Dxd0dtYR.js +213 -0
  37. package/dist/ui/dist/assets/chunk-UHW7PFBP-BIxePNUS.js +1 -0
  38. package/dist/ui/dist/assets/chunk-VQ5W62QI-CKSKLbj4.js +1 -0
  39. package/dist/ui/dist/assets/chunk-WQQL4CDK-CWNmZGlR.js +10 -0
  40. package/dist/ui/dist/assets/chunk-XXDRQBXY-D-I5QHgG.js +1 -0
  41. package/dist/ui/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  42. package/dist/ui/dist/assets/chunk-YZO67HFG-DFP68CFd.js +1 -0
  43. package/dist/ui/dist/assets/chunk-ZAUX2LTX-DY90d4QG.js +1 -0
  44. package/dist/ui/dist/assets/chunk-ZCHMQSOR-Dlpr-5UC.js +2 -0
  45. package/dist/ui/dist/assets/classDiagram-v2-4MYWR3UA-CH9uhoLJ.js +217 -0
  46. package/dist/ui/dist/assets/cose-bilkent-JH36ORCC-DtYHrp8D.js +1 -0
  47. package/dist/ui/dist/assets/cynefin-EF2NZ3EQ-BtslbtrY.js +1 -0
  48. package/dist/ui/dist/assets/cynefinDiagram-WF24UDRP-DUU1Jcc7.js +62 -0
  49. package/dist/ui/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
  50. package/dist/ui/dist/assets/dagre-NK4GIZVX-CDlP-dRW.js +4 -0
  51. package/dist/ui/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  52. package/dist/ui/dist/assets/diagram-CBK3MWTC-DiV2fn-r.js +24 -0
  53. package/dist/ui/dist/assets/diagram-I5XIIV2D-Bg5RehGi.js +24 -0
  54. package/dist/ui/dist/assets/diagram-ODIL2UCW-CFmazz3f.js +30 -0
  55. package/dist/ui/dist/assets/diagram-PFBEJUXE-uw3ZBqDp.js +3 -0
  56. package/dist/ui/dist/assets/diagram-RJA3I4VV-uEOYYRYa.js +41 -0
  57. package/dist/ui/dist/assets/diagram-TYUNFSNR-BN58LF_3.js +200 -0
  58. package/dist/ui/dist/assets/dist-tWS_lXDS.js +1 -0
  59. package/dist/ui/dist/assets/ebnfDiagram-V4P3TTPT-B68Tz-w8.js +1 -0
  60. package/dist/ui/dist/assets/elk-QONTBCTQ-DZZohNea.js +27 -0
  61. package/dist/ui/dist/assets/erDiagram-KYQIGQAA-BH_m74pv.js +99 -0
  62. package/dist/ui/dist/assets/eventmodeling-K75KTNOO-CTp5FHos.js +1 -0
  63. package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-CsCvexry.js +1 -0
  64. package/dist/ui/dist/assets/ganttDiagram-R7GPNTJY-BDau7_0f.js +292 -0
  65. package/dist/ui/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  66. package/dist/ui/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  67. package/dist/ui/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  68. package/dist/ui/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  69. package/dist/ui/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  70. package/dist/ui/dist/assets/gitGraph-VSP46ZUC-Bv84IoCm.js +1 -0
  71. package/dist/ui/dist/assets/gitGraphDiagram-PDYL7XHY-DzXcMlm8.js +106 -0
  72. package/dist/ui/dist/assets/graphlib-DS17s2tU.js +1 -0
  73. package/dist/ui/dist/assets/index-BTJoqVVN.js +133 -0
  74. package/dist/ui/dist/assets/index-D60U3smc.css +1 -0
  75. package/dist/ui/dist/assets/info-OHQRW6UA-222J0O_j.js +1 -0
  76. package/dist/ui/dist/assets/infoDiagram-MVDRKAX5-DE2sa3Vz.js +2 -0
  77. package/dist/ui/dist/assets/init-C-OQMol4.js +1 -0
  78. package/dist/ui/dist/assets/ishikawaDiagram-HPEHV2GE-BJDk0Zcj.js +70 -0
  79. package/dist/ui/dist/assets/journeyDiagram-BOSHW7O7-JSUXX5Hb.js +139 -0
  80. package/dist/ui/dist/assets/kanban-definition-FNSA4HC5-Bg79b49M.js +89 -0
  81. package/dist/ui/dist/assets/katex-B_JcIwqj.js +257 -0
  82. package/dist/ui/dist/assets/line-Bk5qQSIa.js +1 -0
  83. package/dist/ui/dist/assets/linear-PFnVMru5.js +1 -0
  84. package/dist/ui/dist/assets/mermaid-parser.core-Dcxlzc-S.js +7 -0
  85. package/dist/ui/dist/assets/mermaid.core-TeOHQJ00.js +44 -0
  86. package/dist/ui/dist/assets/mindmap-definition-6OY57UG3-IaNk8NnW.js +98 -0
  87. package/dist/ui/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  88. package/dist/ui/dist/assets/packet-JDAUHWVQ-DMff5cVK.js +1 -0
  89. package/dist/ui/dist/assets/path-fybaL0A-.js +1 -0
  90. package/dist/ui/dist/assets/pegDiagram-ECIUJNBX-66Xu4YRc.js +1 -0
  91. package/dist/ui/dist/assets/pie-XZMESJXO-GEtzLCIM.js +1 -0
  92. package/dist/ui/dist/assets/pieDiagram-GNMPY74T-7jMBi-kU.js +39 -0
  93. package/dist/ui/dist/assets/prism-bash-D6zCJ74D.js +1 -0
  94. package/dist/ui/dist/assets/prism-c-04YixN25.js +1 -0
  95. package/dist/ui/dist/assets/prism-clike-DapgWyxx.js +1 -0
  96. package/dist/ui/dist/assets/prism-core-FDHlWQ43.js +1 -0
  97. package/dist/ui/dist/assets/prism-cpp-C-lJbC-6.js +1 -0
  98. package/dist/ui/dist/assets/prism-css-CPgOzxXp.js +1 -0
  99. package/dist/ui/dist/assets/prism-go-C_8qAH5n.js +1 -0
  100. package/dist/ui/dist/assets/prism-java-6wtusvIl.js +1 -0
  101. package/dist/ui/dist/assets/prism-javascript-BnO-swvr.js +1 -0
  102. package/dist/ui/dist/assets/prism-json-Dp_-W-Hv.js +1 -0
  103. package/dist/ui/dist/assets/prism-jsx-D-D0NWtC.js +1 -0
  104. package/dist/ui/dist/assets/prism-kotlin-BUlJgC-o.js +1 -0
  105. package/dist/ui/dist/assets/prism-markdown-Bc9-36OC.js +1 -0
  106. package/dist/ui/dist/assets/prism-markup-Cp04rIa1.js +1 -0
  107. package/dist/ui/dist/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
  108. package/dist/ui/dist/assets/prism-php-Co4fM40K.js +1 -0
  109. package/dist/ui/dist/assets/prism-python-CfiSVtM6.js +1 -0
  110. package/dist/ui/dist/assets/prism-ruby-DFCASkuD.js +1 -0
  111. package/dist/ui/dist/assets/prism-rust-Bu9N8X3N.js +1 -0
  112. package/dist/ui/dist/assets/prism-sql-CYhVDMEE.js +1 -0
  113. package/dist/ui/dist/assets/prism-swift-B0kWc-ne.js +1 -0
  114. package/dist/ui/dist/assets/prism-toml-DKb54GAT.js +1 -0
  115. package/dist/ui/dist/assets/prism-tsx-p6J2Kc_4.js +1 -0
  116. package/dist/ui/dist/assets/prism-typescript-BMqONzxf.js +1 -0
  117. package/dist/ui/dist/assets/prism-yaml-C0JHu8gr.js +1 -0
  118. package/dist/ui/dist/assets/quadrantDiagram-D57K4MC3-8hIWayC2.js +7 -0
  119. package/dist/ui/dist/assets/radar-ABXABTNO-BTdwUOOF.js +1 -0
  120. package/dist/ui/dist/assets/railroad-I3PHUGI6-DcMbDqBL.js +1 -0
  121. package/dist/ui/dist/assets/railroad-abnf-LNEFI6M7-gTzUTWeI.js +1 -0
  122. package/dist/ui/dist/assets/railroad-ebnf-DWYD2UWJ-CNtdUPtk.js +1 -0
  123. package/dist/ui/dist/assets/railroad-peg-JR7BVNR7-DOiExAtE.js +1 -0
  124. package/dist/ui/dist/assets/railroadDiagram-Y72NRBL7-2kE_ygvM.js +1 -0
  125. package/dist/ui/dist/assets/requirementDiagram-EQYSW5RA-CoIAHRq2.js +84 -0
  126. package/dist/ui/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  127. package/dist/ui/dist/assets/sankeyDiagram-SY24JHO5-7p8D3wim.js +40 -0
  128. package/dist/ui/dist/assets/sequenceDiagram-BLEHNHQU-C6QFnMvn.js +169 -0
  129. package/dist/ui/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  130. package/dist/ui/dist/assets/src-FgijoBVs.js +1 -0
  131. package/dist/ui/dist/assets/stateDiagram-v2-A6IESKWZ-n9eAb_bT.js +291 -0
  132. package/dist/ui/dist/assets/swimlanes-B3ENRKFQ-U18mtUzX.js +1 -0
  133. package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DX07QVy9.js +8 -0
  134. package/dist/ui/dist/assets/timeline-definition-JXYKGLGB-CD2Z8QGP.js +120 -0
  135. package/dist/ui/dist/assets/treeView-D4JQ5SDB-sqUjH5gB.js +1 -0
  136. package/dist/ui/dist/assets/treemap-SAJKECNS-DvZ38FP7.js +1 -0
  137. package/dist/ui/dist/assets/usecaseDiagram-WPOLBNNX-CqOGidaZ.js +328 -0
  138. package/dist/ui/dist/assets/vennDiagram-Q3Y3IDCW-D1SXSBQ5.js +34 -0
  139. package/dist/ui/dist/assets/wardley-7MLQ67FV-Bpwio-Dx.js +1 -0
  140. package/dist/ui/dist/assets/wardleyDiagram-IVAIDKWA-JSUXHMnk.js +78 -0
  141. package/dist/ui/dist/assets/xychartDiagram-4URNHDU4-B4IFqQTN.js +7 -0
  142. package/dist/ui/dist/index.html +15 -0
  143. package/dist/ui/dist/style.css +121 -0
  144. package/package.json +31 -8
  145. package/skills/follow-me/SKILL.md +15 -61
  146. package/dist/src/anthropic.js +0 -101
  147. package/dist/ui/app.js +0 -797
  148. package/dist/ui/index.html +0 -40
  149. package/dist/ui/playback.js +0 -175
  150. package/dist/ui/style.css +0 -466
@@ -1,65 +1,19 @@
1
1
  ---
2
2
  name: follow-me
3
- description: Split the current branch's net change into fine, story-ordered commits that read like the author's thought process, then hand them to a human for hunk-by-hunk review. Also plans a narrated walk-through of someone else's branch for a reviewer. Use when the user asks to clean up, reorder, split or "tell the story" of a branch's commits, to walk through or review a branch, or to act on a follow-me review or code review.
3
+ description: Review a branch with the user, hunk by hunk in a local page, and carry the review to the merge request. Use when the user asks to review, split, clean up or reorder a branch's commits, to read someone else's branch, or to post a follow-me review to GitLab or GitHub.
4
4
  ---
5
-
6
5
  # follow-me
7
-
8
- follow-me rewrites a branch as small commits ordered so a reader follows your thinking. It never
9
- touches your working tree or index. It writes a new branch and keeps state in `.follow-me/<slug>/`.
10
-
11
- Run every command from the repo root. If `follow-me` is not on PATH, ask the user how to run it.
12
-
13
- ## First pass
14
-
15
- 1. `follow-me extract [--base main]` snapshots the diff from the base branch to the tip.
16
- 2. `follow-me prompt` prints your instructions and every unit (hunk, file or mode change) with an id.
17
- Read all of it. It names the exact path to write, `.follow-me/<slug>/draft.json`.
18
- 3. Write that draft. Order commits so scaffolding and types come before their uses, a failing test
19
- comes before its fix, renames come before edits, and each commit carries one idea. Commits need
20
- not compile. Every change line is used exactly once, and the last commit must cover every unit.
21
- 4. `follow-me check` validates the draft and prints every issue at once. Fix the draft and re-run
22
- until it is green. Do not edit around an issue by hand-waving it; unassigned and duplicate
23
- lines are real mistakes.
24
- 5. `follow-me apply` freezes the plan as the next version and writes the branch.
25
- 6. Tell the user to run `follow-me review`. It opens a local page where they comment on messages,
26
- positions, hunks and single lines. If you start it yourself, use `follow-me review --detach`,
27
- which prints the page URL and returns; one server hosts every session the user has open.
28
-
29
- ## After a review
30
-
31
- When the user says they finished reviewing:
32
-
33
- 1. `follow-me feedback` prints the review as markdown. Every comment has an id like `c_abc234`.
34
- 2. `follow-me prompt` now includes the previous plan and the open comments. Write a new draft that
35
- changes the plan where you agree and lists one entry per comment in `responses`:
36
- `{ "comment": "c_abc234", "status": "addressed" | "declined" | "deferred", "note": "..." }`.
37
- Say why when you decline. Do not leave a comment unanswered.
38
- 3. `follow-me check`, repair until green, then `follow-me apply`, and ask the user to review again.
39
-
40
- ## As a reviewer
41
-
42
- `follow-me walk` is for someone reading a branch they did not write. It plays the branch back
43
- commit by commit in the browser, typing each line out while it reads your narration aloud, and the
44
- reviewer comments on the code. Nothing is applied and no branch is written.
45
-
46
- 1. `follow-me walk [--branch <name>]` opens the session. If there is no plan yet and no API key, it
47
- prints two commands and stops. Run the first, `follow-me prompt --walk`, and read all of it.
48
- 2. Write the draft it names. The order rules are the same, and every commit also gets a
49
- `narration`: what the author is thinking while writing it, first person, present tense, two or
50
- three sentences, no markdown. Give a unit narration as `{ "unit": "h_x:0-3", "narration": "..." }`
51
- when the why of that hunk is not obvious from the commit's narration.
52
- 3. `follow-me check` until green, then tell the user to run `follow-me walk` again. Do not apply.
53
-
54
- When you are the author's agent and `follow-me feedback` starts with `# Code review`, the review
55
- came from a walk. It quotes each commented line as a diff excerpt with its line number on the
56
- branch. Address every item in the code on the branch itself, then tell the user what you changed
57
- and what you declined and why. Do not write a follow-me draft or `responses` for it; those answer
58
- plan reviews only.
59
-
60
- ## Rules
61
-
62
- - The draft is the only file you write. Never run `git rebase`, `reset` or `commit` for this.
63
- - Use unit ids exactly as printed. `h_x:0-3,7` takes change lines 0 to 3 and 7 of that hunk.
64
- A bare `h_x` takes whatever lines no other commit claimed.
65
- - `f_` and `m_` units are whole; they never take line lists.
6
+ The user never types a follow-me command. If `follow-me` is not on PATH, tell them to run `npm i -g @shiyuphan/follow-me`; otherwise you run everything. They use the page, press Submit, and ask you to post the review.
7
+ 1. From the repo root run `follow-me`. It does the next missing thing, prints what happened, and ends with a `next:` line. Do what it says, then run `follow-me` again.
8
+ 2. When it says to write `.follow-me/<slug>/draft.json` from `prompt.md`, read the prompt, write the draft, and run `follow-me` (it checks the draft, prints every issue, and applies when valid). Put the why in each commit body.
9
+ 3. When it prints the review page URL, tell the user and keep running `follow-me`: each call listens up to 60 seconds and prints what the user flagged or asked, with comment ids.
10
+ 4. Answer a question in place with `follow-me reply <id> --body "<markdown>" [--diagram file]` (Mermaid source for a picture). Start a thread yourself with `follow-me explain [--commit n] [--unit id] [--lines a-b] --body "..."`. Flags the user is still collecting stay hidden from you until they press Submit.
11
+ 5. Repeat until it prints the report and `next: done`. Fix each issue in the code, or say why you decline. On someone else's branch you only report.
12
+ 6. Never run `git rebase`, `reset` or `commit` for this. The draft is the only file you write.
13
+ ## Porting a review
14
+ Only when the user asks to post it.
15
+ 1. `follow-me report --json` prints items with `path`, `oldPath`, `newLine`, `oldLine`, `startNewLine`, `startOldLine`, `severity`, `kind`, `body` (markdown, ready to post), `thread`, `resolved`, `excluded`. Skip `excluded: true`. Lines are the branch tip's, or the base's for a removed line. Tell the user the count and first lines, ask which merge request or pull request, and never post without a yes.
16
+ 2. Fetch the target with `glab mr view <iid> --output json` (`diff_refs`) or `gh pr view <n> --json headRefOid,baseRefName`. Its head must equal the tip on the first line of `follow-me status`; if not, say so and stop, because the line numbers would be wrong.
17
+ 3. GitHub: post one review with `gh api repos/{owner}/{repo}/pulls/<n>/reviews --input review.json`, `"event": "COMMENT"`, each item in `comments[]` as `path`, `body`, and `line`+`side` (`newLine` with `RIGHT`, or `oldLine` with `LEFT`), plus `start_line` and `start_side` for a range; items without `path` go in the review `body`. Never APPROVE or REQUEST_CHANGES unless asked.
18
+ 4. GitLab: one discussion per item with `glab api --method POST projects/:id/merge_requests/<iid>/discussions` and `position[position_type]=text`, `base_sha`, `start_sha`, `head_sha` from `diff_refs`, and `new_path`, `old_path`, `new_line` (added), `old_line` (removed) or both (context) from the item. Items without `path`: `glab mr note`.
19
+ 5. Report what was posted with links and what failed; do not retry blindly. follow-me never holds a token; `gh` and `glab` do, and you run them.
@@ -1,101 +0,0 @@
1
- import Anthropic from "@anthropic-ai/sdk";
2
- export const DEFAULT_MODEL = "claude-opus-5-5";
3
- const MAX_TOKENS = 64000;
4
- export class PlanRequestError extends Error {
5
- failure;
6
- constructor(failure, message) {
7
- super(message);
8
- this.name = "PlanRequestError";
9
- this.failure = failure;
10
- }
11
- }
12
- const narratedUnit = {
13
- type: "object",
14
- properties: { unit: { type: "string" }, narration: { type: "string" } },
15
- required: ["unit", "narration"],
16
- additionalProperties: false,
17
- };
18
- const unitList = { type: "array", items: { anyOf: [{ type: "string" }, narratedUnit] } };
19
- /**
20
- * Mirrors PlanFile on the wire. Structured outputs need every property required and closed
21
- * objects, so commit narration is nullable instead of optional; the plan parser reads null as none.
22
- */
23
- export const PLAN_SCHEMA = {
24
- type: "object",
25
- properties: {
26
- schema: { type: "string", enum: ["follow-me/plan@1"] },
27
- base: { type: "string" },
28
- tip: { type: "string" },
29
- commits: {
30
- type: "array",
31
- items: {
32
- type: "object",
33
- properties: {
34
- message: { type: "string" },
35
- narration: { anyOf: [{ type: "string" }, { type: "null" }] },
36
- units: unitList,
37
- },
38
- required: ["message", "narration", "units"],
39
- additionalProperties: false,
40
- },
41
- },
42
- responses: {
43
- type: "array",
44
- items: {
45
- type: "object",
46
- properties: {
47
- comment: { type: "string" },
48
- status: { type: "string", enum: ["addressed", "declined", "deferred"] },
49
- note: { type: "string" },
50
- },
51
- required: ["comment", "status", "note"],
52
- additionalProperties: false,
53
- },
54
- },
55
- },
56
- required: ["schema", "base", "tip", "commits", "responses"],
57
- additionalProperties: false,
58
- };
59
- const sendWithAnthropic = async ({ model, prompt }) => {
60
- const client = new Anthropic();
61
- // Streaming because a 64k-token reply outlives the SDK's non-streaming timeout.
62
- const stream = client.beta.messages.stream({
63
- model,
64
- max_tokens: MAX_TOKENS,
65
- output_config: { effort: "high", format: { type: "json_schema", schema: PLAN_SCHEMA } },
66
- betas: ["server-side-fallback-2026-07-01"],
67
- fallbacks: "default",
68
- messages: [{ role: "user", content: prompt }],
69
- });
70
- const message = await stream.finalMessage();
71
- const text = message.content.flatMap((block) => (block.type === "text" ? [block.text] : []));
72
- return {
73
- stopReason: message.stop_reason,
74
- text: text.join(""),
75
- refusalExplanation: message.stop_details?.explanation ?? null,
76
- };
77
- };
78
- /**
79
- * Asks the model for a plan and returns the parsed JSON. The caller validates it as a plan,
80
- * because a schema-conforming reply can still name units that do not exist.
81
- */
82
- export async function requestPlan(prompt, opts = {}) {
83
- const send = opts.send ?? sendWithAnthropic;
84
- const reply = await send({ model: opts.model ?? DEFAULT_MODEL, prompt });
85
- if (reply.stopReason === "refusal") {
86
- const why = reply.refusalExplanation ? `: ${reply.refusalExplanation}` : "";
87
- throw new PlanRequestError("refusal", `the model declined to write a plan${why}`);
88
- }
89
- if (reply.stopReason === "max_tokens") {
90
- throw new PlanRequestError("truncated", `the plan did not fit in ${MAX_TOKENS} tokens; try a smaller diff`);
91
- }
92
- if (reply.text.trim() === "") {
93
- throw new PlanRequestError("empty", "the model returned no text");
94
- }
95
- try {
96
- return JSON.parse(reply.text);
97
- }
98
- catch {
99
- throw new PlanRequestError("not-json", "the model's reply was not valid JSON");
100
- }
101
- }