heyquilt 0.3.122 → 0.3.140

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 (166) hide show
  1. package/RELEASES.md +279 -17
  2. package/bin/quilt.js +62 -152
  3. package/desktop/main.js +16 -2
  4. package/package.json +3 -2
  5. package/src/account.js +0 -29
  6. package/src/agent-task-workflow.js +2 -2
  7. package/src/agents/common.js +13 -0
  8. package/src/ai-vm.js +9 -1
  9. package/src/api/activity.js +5 -0
  10. package/src/api/memory-store.js +16 -38
  11. package/src/api/notify.js +134 -0
  12. package/src/api/routes/browsers.js +29 -0
  13. package/src/api/routes/relay.js +26 -3
  14. package/src/api/routes/session-migrate.js +7 -4
  15. package/src/api/routes/sessions.js +73 -1
  16. package/src/api/routes/teams.js +0 -23
  17. package/src/api/server.js +17 -25
  18. package/src/api/supabase-store.js +43 -56
  19. package/src/autowork.js +182 -16
  20. package/src/branch-view.js +6 -131
  21. package/src/connection.js +36 -9
  22. package/src/control.js +131 -48
  23. package/src/duties.js +1 -1
  24. package/src/force-date.js +27 -0
  25. package/src/fsutil.js +13 -3
  26. package/src/github-commit.js +3 -315
  27. package/src/history.js +13 -3
  28. package/src/mcp.js +93 -167
  29. package/src/merge3.js +11 -3
  30. package/src/now-summary.js +113 -0
  31. package/src/pathrules.js +12 -0
  32. package/src/protocol.js +10 -2
  33. package/src/quilt-default.js +84 -0
  34. package/src/quilt-history.js +106 -0
  35. package/src/quilt-remote.js +78 -0
  36. package/src/relay-ask.js +3 -66
  37. package/src/relay-cluster.js +3 -5
  38. package/src/relay-lease.js +6 -2
  39. package/src/relay-mcp.js +154 -432
  40. package/src/relay-prune.js +6 -145
  41. package/src/relay-turn-sync.js +1 -1
  42. package/src/release-words.js +2 -2
  43. package/src/runner.js +20 -7
  44. package/src/seam-access.js +97 -0
  45. package/src/seam-claims.js +53 -0
  46. package/src/seam-url.js +15 -0
  47. package/src/server.js +1516 -1383
  48. package/src/session-follow.js +461 -27
  49. package/src/session-layout3.js +410 -4
  50. package/src/session-migrate.js +15 -11
  51. package/src/session-quilt.js +1 -1
  52. package/src/session-seam.js +233 -20
  53. package/src/session.js +745 -3438
  54. package/src/source-live/bring-in.js +283 -0
  55. package/src/source-live/live-block.js +504 -33
  56. package/src/source-live/live-square.js +16 -5
  57. package/src/status.js +7 -58
  58. package/src/turn-sync.js +15 -2
  59. package/src/ui/agent-guide.js +1 -1
  60. package/src/ui/app.css +85 -20
  61. package/src/ui/app.js +4 -1
  62. package/src/ui/changes.js +5 -14
  63. package/src/ui/chat.js +9 -4
  64. package/src/ui/cloud-ai-model.js +13 -0
  65. package/src/ui/cloud-ai.js +10 -2
  66. package/src/ui/common.js +6 -4
  67. package/src/ui/fileview.js +20 -7
  68. package/src/ui/force-words.js +19 -0
  69. package/src/ui/git-rule.js +9 -28
  70. package/src/ui/home.js +3 -223
  71. package/src/ui/loom-model.js +15 -1
  72. package/src/ui/quiltview.js +213 -0
  73. package/src/ui/seam-model.js +251 -0
  74. package/src/ui/session.js +377 -489
  75. package/src/ui/tree.js +2 -8
  76. package/src/ui/voice-call.js +83 -14
  77. package/src/ui/voice-model.js +39 -0
  78. package/src/ui/voice.js +167 -17
  79. package/src/ui/workspaces.js +2 -122
  80. package/src/ui-server.js +81 -66
  81. package/src/update-check.js +25 -1
  82. package/src/voice.js +13 -6
  83. package/src/api/routes/github.js +0 -393
  84. package/src/branches.js +0 -194
  85. package/src/clash.js +0 -128
  86. package/src/commit-request.js +0 -59
  87. package/src/commit.js +0 -213
  88. package/src/git-hook.js +0 -48
  89. package/src/git.js +0 -147
  90. package/src/gitignore.js +0 -49
  91. package/src/gitstate.js +0 -1033
  92. package/src/quilts/AGENTS.md +0 -1
  93. package/src/quilts/CLAUDE.md +0 -1
  94. package/src/quilts/accounts.js +0 -136
  95. package/src/quilts/affected.js +0 -132
  96. package/src/quilts/archiver.js +0 -175
  97. package/src/quilts/autorevert.js +0 -49
  98. package/src/quilts/bisect-run.js +0 -31
  99. package/src/quilts/blame.js +0 -103
  100. package/src/quilts/block.js +0 -347
  101. package/src/quilts/bucket.js +0 -25
  102. package/src/quilts/cbor.js +0 -92
  103. package/src/quilts/checks.js +0 -308
  104. package/src/quilts/claims.js +0 -144
  105. package/src/quilts/cli.js +0 -536
  106. package/src/quilts/client.js +0 -137
  107. package/src/quilts/conflict-format.js +0 -190
  108. package/src/quilts/describe.js +0 -296
  109. package/src/quilts/dogfood.js +0 -82
  110. package/src/quilts/errors.js +0 -8
  111. package/src/quilts/events.js +0 -171
  112. package/src/quilts/export-git.js +0 -130
  113. package/src/quilts/export-remote.js +0 -156
  114. package/src/quilts/export.js +0 -81
  115. package/src/quilts/fastmerge.js +0 -41
  116. package/src/quilts/hash.js +0 -44
  117. package/src/quilts/history.js +0 -266
  118. package/src/quilts/ids.js +0 -12
  119. package/src/quilts/import-follow.js +0 -236
  120. package/src/quilts/import-git.js +0 -294
  121. package/src/quilts/import.js +0 -196
  122. package/src/quilts/index.js +0 -94
  123. package/src/quilts/keys.js +0 -186
  124. package/src/quilts/limits.js +0 -67
  125. package/src/quilts/lines.js +0 -49
  126. package/src/quilts/live.js +0 -50
  127. package/src/quilts/merge-smart.js +0 -114
  128. package/src/quilts/merge.js +0 -501
  129. package/src/quilts/oauth.js +0 -202
  130. package/src/quilts/objects.js +0 -144
  131. package/src/quilts/pack.js +0 -118
  132. package/src/quilts/records.js +0 -211
  133. package/src/quilts/resolutions.js +0 -69
  134. package/src/quilts/reviews.js +0 -208
  135. package/src/quilts/routes.js +0 -186
  136. package/src/quilts/runner.js +0 -95
  137. package/src/quilts/server.js +0 -144
  138. package/src/quilts/service.js +0 -963
  139. package/src/quilts/setup.js +0 -41
  140. package/src/quilts/signing.js +0 -149
  141. package/src/quilts/squares.js +0 -239
  142. package/src/quilts/store-memory.js +0 -844
  143. package/src/quilts/store-rpc.js +0 -90
  144. package/src/quilts/store-supabase.js +0 -13
  145. package/src/quilts/tags.js +0 -64
  146. package/src/quilts/textdiff.js +0 -96
  147. package/src/quilts/tokens.js +0 -66
  148. package/src/quilts/tools.js +0 -403
  149. package/src/quilts/tree.js +0 -351
  150. package/src/quilts/webhook.js +0 -116
  151. package/src/quilts/window.js +0 -121
  152. package/src/quilts/xdiff.js +0 -435
  153. package/src/relay-ci.js +0 -111
  154. package/src/relay-clash.js +0 -214
  155. package/src/relay-commit.js +0 -918
  156. package/src/relay-filekey.js +0 -46
  157. package/src/relay-git.js +0 -274
  158. package/src/relay-githttp.js +0 -166
  159. package/src/relay-github.js +0 -109
  160. package/src/relay-outbound.js +0 -1295
  161. package/src/relay-repo-moves.js +0 -43
  162. package/src/relay-sync.js +0 -795
  163. package/src/relay-upstream.js +0 -889
  164. package/src/sync-words.js +0 -42
  165. package/src/ui/branches.js +0 -111
  166. package/src/upstream.js +0 -192
@@ -1,403 +0,0 @@
1
- // Quilt source control's MCP tools (plans/quilt-source-control/12), served by `seam mcp` (seam/),
2
- // not by the Quilt app's MCP: one description and one schema per tool, answered by the same
3
- // server routes the CLI uses, so Claude Code, Cursor, Codex and every other tool get the same
4
- // behaviour and the same sentences.
5
- //
6
- // Locally, in a folder made by `seam checkout`, the quilt and square come from the folder and
7
- // seam_commit seals the folder's changes; anywhere else (and always for hosted agents) name the
8
- // quilt and square, and seam_commit takes the files' new contents.
9
- import path from 'node:path'
10
- import { z } from 'zod'
11
- import { describeLog, describeShow, describeDiff, describeBlame, describeStatus, describeMerge, describeWrite, describeSquare, describeSquareStatus, describeEvents, describeTag, describeRelease, describeImport, describeImportRecord, describeRestore, describeReset, describeClean, describeLive, describeClaims, describeResolutions, describeLiveSet, describeMergeBase, describeGrep, describeBisect, describeCheck, describeReviews, describeReviewed, describeSquareCheck, describePolicy, describeKeys, describeTokens, describeNewToken, describeCheckRuns, describeRerun } from './describe.js'
12
- import { signWithLocalKey, localKey, makeLocalKey, signingKeyFile } from './signing.js'
13
- import { status as blockStatus, commitFolder, amendFolder, syncTo, restore, reset, clean, localChanges } from './block.js'
14
-
15
- export const SEAM_TOOLS = ['seam_quilts', 'seam_open_square', 'seam_squares', 'seam_log', 'seam_show', 'seam_diff', 'seam_blame', 'seam_cat', 'seam_ls', 'seam_commit', 'seam_merge', 'seam_rebase', 'seam_revert', 'seam_cherry_pick', 'seam_resolve', 'seam_square_status', 'seam_square_handoff', 'seam_abandon', 'seam_square_merged', 'seam_reopen', 'seam_fold', 'seam_split', 'seam_events', 'seam_events_webhook', 'seam_line_list', 'seam_line_new', 'seam_line_freeze', 'seam_line_unfreeze', 'seam_line_require', 'seam_reviews', 'seam_review', 'seam_tag_list', 'seam_tag', 'seam_release_list', 'seam_release', 'seam_import_git', 'seam_import_remote', 'seam_import_status', 'seam_import_poke', 'seam_import_cutover', 'seam_export_remote', 'seam_export_status', 'seam_restore', 'seam_reset', 'seam_clean', 'seam_live', 'seam_live_set', 'seam_bisect', 'seam_grep', 'seam_search', 'seam_describe', 'seam_merge_base', 'seam_claims', 'seam_claim', 'seam_unclaim', 'seam_check', 'seam_resolutions', 'seam_forget_resolution', 'seam_sign', 'seam_keys', 'seam_key_add', 'seam_key_remove', 'seam_token_list', 'seam_token_new', 'seam_token_revoke', 'seam_checks', 'seam_check_rerun']
16
-
17
- /** What every agent is told about source control, in each MCP's instructions. */
18
- export const SEAM_GUIDE = 'Quilt source control (quilts): a quilt is a repository on the Quilt server; you work in a square (your own branch and working copy), commit save points in it, and merge it into main, where it becomes a patch. ' +
19
- 'Open a square with seam_open_square, commit with seam_commit, merge with seam_merge (into: "main"). A merge never stops half way: a real conflict is refused, kept in your square as conflict markers, and you settle it (edit the file and commit it without the markers, or seam_resolve) and merge again. ' +
20
- 'Read history with seam_log, seam_show, seam_diff, seam_blame, seam_cat and seam_grep (seam_bisect finds the commit that broke something); revisions are main, main~3, patch:12, sq:<id>, a commit id or git:<sha>. ' +
21
- 'In a folder checked out from a square (it holds .seam/block.json), edit the files with your own tools and check them with seam_square_status.'
22
-
23
- const QUILT = z.string().max(100).optional().describe('The quilt (its slug or id); in a folder made by seam checkout, that folder\'s quilt')
24
- const SQUARE = z.string().max(100).optional().describe('The square (sq:<id>); in a block folder, that folder\'s square')
25
- const REV = z.string().max(200).optional()
26
- const PATHS = z.array(z.string().max(1000)).max(200).optional().describe('Only these paths (files or directories)')
27
-
28
- export const SEAM_DESCRIPTIONS = {
29
- seam_live: 'A square\'s uncommitted work its holder shared (sq:<id>:live): each changed file, added, modified or deleted against the square\'s head. Read the files with seam_cat or seam_diff (rev sq:<id>:live).',
30
- seam_line_require: 'Reads, or (a person who is an admin) sets, what a line needs before a square merges into it: approvals of the square\'s latest commit (0: none) and checks that must be green on it (check names, reported with seam_check and square). Leave both out to read it.',
31
- seam_reviews: 'A square\'s reviews (comments, approvals, requests for changes) and where it stands: approvals it has against what its line needs, and whether it is ready to merge.',
32
- seam_review: 'Reviews a square: kind approve, request_changes (say what in body), comment (body; path and line put it on a line of a file at the square\'s head) or dismiss (takes back your verdict). Nobody approves a square they or the person they work for opened or hold. An approval counts for the square\'s latest commit only.',
33
- seam_claims: 'The claims that hold now: which square is working on which paths, who, and why (square: only that square\'s).',
34
- seam_claim: 'Claims paths (patterns: files, folders or globs like src/api/**) for your square, so writes to them from other squares on its line are refused until your square merges or you let go. note says what for. Claim before a larger change across several files.',
35
- seam_check_rerun: 'Asks a check\'s runners to run it again: with square, on that square\'s latest commit (pre_merge); with line, on the line\'s tip (post_merge); or on commit. A run of it still open there is cancelled (check.cancelled to its runners). Returns the new run_key.',
36
- seam_checks: 'The quilt\'s check runs, newest first (filter by state, name, tier, line, square). A runner polls state "queued" for the checks its line\'s policy names it for (or gets check.requested events), runs each on its commit and reports with seam_check and the run\'s run_key.',
37
- seam_check: 'Reports a post-merge check on a line (status red, green or flaky; a red check halves the line\'s merge window, green restores it). With patch (the patch number it failed at, found for example with seam_bisect) a red check reverts that patch on the line by itself and opens a square holding the change again for its holder to fix. With square instead, the result (green or red) is for that square\'s latest commit, which its line may require green before a merge.',
38
- seam_unclaim: 'Lets go of claims on your square (patterns: the patterns or claim ids).',
39
- seam_live_set: 'Shares your square\'s uncommitted work so others can read it as sq:<id>:live before you commit: in a block folder the folder\'s changed files; elsewhere files: [{ path, content }] and delete: [paths]. Each call replaces what was shared; none clears it. It is not a commit: seam_commit keeps work.',
40
- seam_restore: 'In a block folder: puts the named files (paths; "." for all) back as they were at the folder\'s last commit, throwing away their uncommitted edits and deletions. Files nobody committed stay (seam_clean removes them).',
41
- seam_reset: 'In a block folder: makes the folder\'s files those of a revision (rev). With no rev, throws away every uncommitted edit. The folder stays on its square: what differs from it shows as uncommitted changes, so seam_commit makes the square match rev (to undo its last commit: rev sq:<id>~1).',
42
- seam_clean: 'In a block folder: removes the files nobody committed and no .gitignore names (under paths, default all). dry_run: true lists them without removing.',
43
- seam_quilts: 'Lists the quilts (repositories) you can reach, or makes a new one (new: its slug).',
44
- seam_line_list: 'The quilt\'s lines: main and release/<name> lines, each with its patch count, tip and whether it is frozen.',
45
- seam_line_new: 'Makes a line release/<name> starting at a revision (from, default main): a release line takes merges of its own. Open squares on it with seam_open_square (line) and merge with seam_merge (into).',
46
- seam_line_freeze: 'Freezes a line (admins): merges into it are refused with line_frozen until seam_line_unfreeze.',
47
- seam_line_unfreeze: 'Unfreezes a line (admins): it takes merges again.',
48
- seam_tag_list: 'The quilt\'s tags: name, commit, the patch it is, and whether it is a release.',
49
- seam_import_remote: 'Imports a git remote (https:// or ssh) into a new quilt on the server. With follow, it keeps taking new commits on main and release/* every interval (and on seam_import_poke) and freezes those lines until cut-over, because git stays the authority. Returns the import id and its poke secret (shown once).',
50
- seam_export_remote: 'Exports lines of a quilt to a git remote (admins) after each merge on them, as fast-forwards that are never forced; releases: true pushes release tags too. While an import follows git, only to a scratch remote (scratch: true).',
51
- seam_export_status: 'The export to a git remote: its target, lines, state (idle, ok, retrying, refused), each line\'s last exported patch and sha, and the last error.',
52
- seam_import_cutover: 'Ends a following import for good (admins): a last pass, a check that the remote\'s tips equal the imported ones, then main and release/* take merges in Seam and main exports to the source after each merge. Refuses with remote_moved if git moved.',
53
- seam_import_status: 'An import from a git remote: its state (reading, following, done, failed), its lines with their git tips, and its last error.',
54
- seam_import_poke: 'Makes a following import fetch its git remote now instead of at its next interval.',
55
- seam_import_git: 'Imports a git repository on this computer into a quilt: HEAD\'s branch as main, every other branch as a line of its name, every tag, submodules as gitlink entries. Run it again into the same quilt to add only the new commits.',
56
- seam_sign: 'Signs a commit you made (rev, default your square\'s head) with your Ed25519 key, so seam_log with show_signature shows it as yours. With no signature, signs with this computer\'s key (seam_key_add makes one); a hosted agent signs the text "seam-commit <commit id>\\n" itself and passes key and signature (base64).',
57
- seam_keys: 'Your signing keys (or member\'s): their ids, names, and when each was added or removed.',
58
- seam_key_add: 'Adds an Ed25519 signing key to your account. With no public_key, makes one on this computer (kept in ~/.seam/signing-key.pem) or adds the one already there.',
59
- seam_token_list: 'The quilt\'s tokens (keys for one quilt, for CI and scripts): id, name, read or write, and whether revoked. Admins only.',
60
- seam_token_new: 'Makes a quilt token: a key that reaches only this quilt, at level read (default) or write, for a CI job or a script. The secret (sqt_…) is in this answer only; it is used as SEAM_TOKEN or Authorization: Bearer. Admins only.',
61
- seam_token_revoke: 'Revokes a quilt token (by id, or by name): it stops working at once. Admins only.',
62
- seam_key_remove: 'Removes one of your signing keys (key: ed25519:…). Commits it signed then show "removed_key" instead of "good".',
63
- seam_tag: 'Tags a revision (rev: patch:12, a commit, a line, sq:…; default main) with a name that never moves. Read it back as the revision tag:<name>.',
64
- seam_release_list: 'The quilt\'s releases, newest first, each with its notes.',
65
- seam_release: 'Makes a release: a tag (name) on a revision (rev, default main) with release notes (notes).',
66
- seam_open_square: 'Opens a square: your own unit of work, branched from the tip of main (or line). Returns its id (sq:…). Agents open squares, never lines. on: another open square (sq:…) to stack on: yours starts from its head, merges after it, and is rebased onto the line by itself when it merges.',
67
- seam_squares: 'The open squares of a quilt, with holder, head, how far behind main each is and its conflicted paths. all: true for merged and abandoned ones too.',
68
- seam_square_status: 'One square as the server sees it (head, behind_by, conflicted paths), the patches merged under it that changed its files, and which files would conflict if merged now; in a block folder also the files changed since the last commit.',
69
- seam_square_handoff: 'Hands your square to another member (to), with a note on what is done and what is left. You stop being able to commit to it.',
70
- seam_square_merged: 'Closes an open square whose work a person put into git by hand while an import follows git, when the follower could not match it (the diff was edited on the way). Admins; git_sha is the imported git commit that holds the work.',
71
- seam_abandon: 'Abandons a square whose work is not wanted. Its commits stay readable and seam_reopen brings it back.',
72
- seam_reopen: 'Reopens an abandoned square; you become its holder.',
73
- seam_events: 'What happened to your work since you last looked: patch.merged (a patch changed files your open square changed, and whether your merge would now conflict), conflict.added (a conflict was kept in your square), square.handoff, check.requested (you run a check a line names you for: run it on the commit and report with seam_check and its run_key). Pass after (the cursor from the last answer) to get only new ones.',
74
- seam_events_webhook: 'Has the quilt server POST your events to a URL (signed with HMAC-SHA256 over <timestamp>.<body>, retried 3 times), so you are woken instead of polling: url, events (default all), secret (made for you if left out). url: "off" removes it.',
75
- seam_fold: 'Folds a square (square) into another square you hold (into): its work becomes one commit there and the square closes. Overlapping changes become conflict markers in the target.',
76
- seam_split: 'Splits your square by paths: groups: [[paths], ...], each group moves to a new square (same base) as one commit; the largest group, and paths no group names, stay in this square.',
77
- seam_log: 'History, newest first: rev (default main, or your square, or a..b), paths to limit it, limit (default 20). follow: true with one path follows the file through its renames; search: "<text>" keeps only commits that changed how many times the text appears (git log -S). Each patch on main is one line\'s merge.',
78
- seam_describe: 'Names a revision (rev, default main) by the nearest tag behind it: v1.0 on the tag itself, v1.0-3-<commit> three commits past it, the commit alone when no tag is behind it.',
79
- seam_merge_base: 'Where two revisions (a, b: lines, squares, patches, commits) last shared history: the commit a merge of one into the other starts from.',
80
- seam_search: 'Code search: the lines matching a pattern in every quilt you can read (or the ones in quilts), at a revision (rev, default main; quilts without it are skipped), under paths. fixed: true for plain text; ignore_case, files_only, limit (default 200, across all quilts).',
81
- seam_grep: 'The lines matching a pattern (a JavaScript regular expression; fixed: true for plain text) in the files at a revision (rev, default main or your square; sq:<id>:live too), under paths. ignore_case, files_only, limit (default 200).',
82
- seam_bisect: 'Finds the commit that broke something between good (a revision without the problem) and bad (default main), along first parents (on a line: its patches). Without run, one step: pass marks { <commit>: "good" | "bad" | "skip" } for those tested so far and it names the next to test, or the first bad one. With run (a shell command) it checks out each commit into a fresh folder on this computer and runs it there: exit 0 good, 125 skip, other bad.',
83
- seam_show: 'One commit or patch: who, when, why, which square, and its diff. rev: patch:12, main~1, sq:…, a commit id, git:<sha>. format: unified (default), stat or names.',
84
- seam_diff: 'The difference between two revisions (from, to); with only to, against its parent (for a square: against where it branched). format: unified, stat or names.',
85
- seam_blame: 'Each line of a file at a revision, with the commit and patch that last changed it and who made it. Computed by the server.',
86
- seam_cat: 'A file\'s content at any revision (path, rev). To change a file, read it from your own square (rev sq:<id>), not from main: main moves while you work, and a commit built from a different version of main than your square started from can undo work that merged in between. A conflicted file comes back with its conflict markers: format diff (default: one side in full after +++++++, each other side as a diff from its base after %%%%%%%), snapshot (every side in full) or git (<<<<<<< ======= >>>>>>>, two sides only).',
87
- seam_ls: 'The files at a revision, under paths (directories).',
88
- seam_commit: 'Seals your square\'s changes as a commit (a save point). In a block folder: the folder\'s changed files (paths: only these). Elsewhere: files: [{ path, content }] (text; base64: true for bytes) and delete: [paths]. message says why. Committing a conflicted file without its markers settles the conflict. amend: true changes the square\'s last commit instead (its changes plus these, a new message if given), as git commit --amend does. sign: true signs the new commit with this computer\'s key.',
89
- seam_merge: 'Merges your square into a line (into: "main") or, with from, merges another square or revision into your square (a two-parent commit; conflicts stay in your square as markers). Into a line: the server rebases it onto the tip as one commit and moves main, retrying lost races itself. The result is a patch. A real conflict is refused: the square is rebased with the conflict kept in it (your folder gets the markers); settle it and merge again. dry_run: true says what would happen.',
90
- seam_rebase: 'Moves your square onto the tip of its line (or onto a revision). Conflicts become conflict markers in the square, never a stop.',
91
- seam_revert: 'Undoes a patch (or commit): a new square from the tip whose one commit inverts it. Merge that square with seam_merge.',
92
- seam_cherry_pick: 'Applies one commit\'s change (from) to your square as a new commit.',
93
- seam_resolve: 'Settles conflicted files in your square by choosing a side: entries [{ path, choose: ours | theirs | base | suggestion | delete }]; suggestion takes the word-level merge a conflict offers (its suggestion, shown when the merge is refused). (Or edit the file and commit it without the markers.) The answer is remembered: the same conflict met again in a rebase or merge is settled the same way, unless remember: false.',
94
- seam_resolutions: 'The remembered resolutions: conflicts settled once that seam settles the same way when they come back (path: only that file\'s).',
95
- seam_forget_resolution: 'Forgets a remembered resolution (key: its key or first 8+ characters), so that conflict is asked about again next time.'
96
- }
97
-
98
- export const SEAM_INPUTS = {
99
- seam_live: { quilt: QUILT, square: SQUARE },
100
- seam_line_require: { quilt: QUILT, line: z.string().max(120), approvals: z.number().int().min(0).max(20).optional(), checks: z.array(z.string().max(100)).max(20).optional().describe('Check names that must be green on a square\'s latest commit, e.g. ["ci"]; [] for none'), runners: z.record(z.string().max(100), z.array(z.string().max(200)).max(20)).optional().describe('Who runs each check, e.g. {"ci": ["agent:ci"]}: they are asked to run it (check.requested) and only their verdicts count'), tiers: z.record(z.string().max(100), z.array(z.enum(['pre_merge', 'post_merge'])).max(2)).optional().describe('Where each check runs, e.g. {"e2e": ["post_merge"]}; default: a required check runs pre_merge on the square, a check with runners runs post_merge on the line'), tests: z.array(z.string().max(300)).max(50).optional().describe('Globs of the line\'s test files, e.g. ["test/**"]: a pre-merge check.requested names the tests a change reaches (affected)'), ci_config: z.array(z.string().max(300)).max(50).optional().describe('Globs of ci config: a change there makes affected confidence "all"') },
101
- seam_reviews: { quilt: QUILT, square: SQUARE },
102
- seam_review: { quilt: QUILT, square: SQUARE, kind: z.enum(['approve', 'request_changes', 'comment', 'dismiss']), body: z.string().max(20000).optional(), path: z.string().max(1000).optional().describe('A file, for a comment on it'), line: z.number().int().min(1).optional().describe('A line of that file at the square\'s head') },
103
- seam_claims: { quilt: QUILT, square: SQUARE },
104
- seam_claim: { quilt: QUILT, square: SQUARE, patterns: z.array(z.string().max(500)).min(1).max(50).describe('Files, folders or globs, e.g. src/api/** or README.md'), note: z.string().max(300).optional().describe('What you are doing with them') },
105
- seam_check: { quilt: QUILT, check: z.string().max(200).describe('The check\'s name, e.g. ci'), status: z.enum(['red', 'green', 'flaky']), run_key: z.string().max(600).optional().describe('The run_key of the check.requested event or run being answered'), url: z.string().max(2000).optional().describe('A link to the run\'s log (http(s) or file://), kept on the run'), square: z.string().max(100).optional().describe('A square (sq:…): the result is for its latest commit, which a line may require green before a merge'), line: z.string().max(110).optional().describe('Default main'), patch: z.number().int().min(1).optional().describe('The patch number the check failed at: reverts it') },
106
- seam_unclaim: { quilt: QUILT, square: SQUARE, patterns: z.array(z.string().max(500)).min(1).max(50).describe('The claimed patterns (or claim ids)') },
107
- seam_live_set: { quilt: QUILT, square: SQUARE, files: z.array(z.object({ path: z.string().max(1000), content: z.string(), base64: z.boolean().optional(), mode: z.enum(['file', 'exec']).optional() })).max(2000).optional(), delete: z.array(z.string().max(1000)).max(2000).optional() },
108
- seam_restore: { paths: z.array(z.string().max(1000)).min(1).max(200).describe('Files or folders, relative to the block folder ("." for all)') },
109
- seam_reset: { rev: z.string().max(200).optional().describe('The revision: sq:<id>~1, patch:12, tag:v1.0, a commit; default the folder\'s last commit') },
110
- seam_clean: { paths: PATHS, dry_run: z.boolean().optional() },
111
- seam_quilts: { new: z.string().max(63).optional().describe('Make a quilt with this slug') },
112
- seam_line_list: { quilt: QUILT },
113
- seam_line_new: { quilt: QUILT, name: z.string().max(110).describe('release/<name> (or just <name>)'), from: z.string().max(200).optional().describe('The revision it starts at: main, patch:12, a commit, sq:…; default main') },
114
- seam_line_freeze: { quilt: QUILT, line: z.string().max(110) },
115
- seam_line_unfreeze: { quilt: QUILT, line: z.string().max(110) },
116
- seam_tag_list: { quilt: QUILT },
117
- seam_import_remote: { url: z.string().max(2000).describe('The git remote: https://, ssh:// or user@host:path'), into: z.string().max(63).describe('The new quilt\'s slug'), follow: z.boolean().optional().describe('Keep following main and release/* until cut-over'), interval: z.number().int().min(60).optional().describe('Seconds between follow passes (default 600)'), credential: z.string().max(100).optional().describe('The name of the server secret that reads the remote (user:token or a token)') },
118
- seam_export_remote: { quilt: z.string().max(100).optional().describe('The quilt'), target: z.string().max(2000).describe('The git remote'), lines: z.array(z.string().max(100)).max(20).optional().describe('Lines to export (default ["main"]); release/* for every release line'), releases: z.boolean().optional().describe('Push release tags too'), credential: z.string().max(100).optional().describe('The name of the server secret that may push'), scratch: z.boolean().optional().describe('A scratch remote nothing pins a sha from'), enabled: z.boolean().optional().describe('false turns the export off') },
119
- seam_export_status: { quilt: z.string().max(100).optional().describe('The quilt') },
120
- seam_import_cutover: { id: z.string().max(100).describe('The import id'), credential: z.string().max(100).optional().describe('The server secret that may push to the source (default the import\'s)') },
121
- seam_import_status: { id: z.string().max(100).describe('The import id') },
122
- seam_import_poke: { id: z.string().max(100).describe('The import id') },
123
- seam_import_git: { path: z.string().max(1000).describe('The git repository\'s folder'), into: z.string().max(63).optional().describe('The quilt\'s slug (default the folder\'s name); a quilt an import made takes only the new commits'), validate: z.boolean().optional().describe('Compare every patch written with git\'s tree') },
124
- seam_sign: { quilt: QUILT, rev: REV, key: z.string().max(40).optional().describe('Your key id (ed25519:…), with signature'), signature: z.string().max(200).optional().describe('Base64 Ed25519 signature of "seam-commit <commit id>\\n"') },
125
- seam_keys: { member: z.string().max(200).optional().describe('Whose keys (default yours)') },
126
- seam_key_add: { public_key: z.string().max(2000).optional().describe('Ed25519 public key (PEM, or base64 of its 32 bytes); none: this computer\'s'), name: z.string().max(100).optional() },
127
- seam_token_list: { quilt: QUILT },
128
- seam_token_new: { quilt: QUILT, name: z.string().max(100).describe('What it is for, e.g. ci'), level: z.enum(['read', 'write']).optional() },
129
- seam_token_revoke: { quilt: QUILT, token: z.string().max(100).describe('The token\'s id or name') },
130
- seam_key_remove: { key: z.string().max(40).describe('The key id, ed25519:…') },
131
- seam_tag: { quilt: QUILT, name: z.string().max(100).describe('The tag, e.g. v1.0'), rev: z.string().max(200).optional().describe('What it names: patch:12, a commit, a line, sq:…; default main') },
132
- seam_release_list: { quilt: QUILT },
133
- seam_release: { quilt: QUILT, name: z.string().max(100).describe('The release\'s tag, e.g. v1.0'), rev: z.string().max(200).optional().describe('default main'), notes: z.string().max(20000).describe('Release notes') },
134
- seam_open_square: { quilt: QUILT, title: z.string().max(200).describe('What the work is'), line: z.string().max(100).optional(), task_id: z.string().max(64).optional(), on: z.string().max(64).optional().describe('A square to stack on (sq:…)') },
135
- seam_squares: { quilt: QUILT, all: z.boolean().optional(), mine: z.boolean().optional() },
136
- seam_square_status: { quilt: QUILT, square: SQUARE },
137
- seam_log: { quilt: QUILT, rev: REV, paths: PATHS, limit: z.number().int().min(1).max(500).optional(), follow: z.boolean().optional().describe('Follow one file (paths: [it]) through its renames'), search: z.string().max(500).optional().describe('Only commits that changed how many times this text appears'), show_signature: z.boolean().optional().describe('Check and show each commit\'s signature') },
138
- seam_describe: { quilt: QUILT, rev: REV },
139
- seam_merge_base: { quilt: QUILT, a: z.string().max(200), b: z.string().max(200) },
140
- seam_search: { pattern: z.string().max(500), quilts: z.array(z.string().max(200)).max(100).optional(), rev: REV, paths: PATHS, ignore_case: z.boolean().optional(), fixed: z.boolean().optional(), files_only: z.boolean().optional(), limit: z.number().int().min(1).max(2000).optional() },
141
- seam_grep: { quilt: QUILT, pattern: z.string().max(500), rev: REV, paths: PATHS, ignore_case: z.boolean().optional(), fixed: z.boolean().optional(), files_only: z.boolean().optional(), limit: z.number().int().min(1).max(2000).optional() },
142
- seam_bisect: { quilt: QUILT, good: z.string().max(200).describe('A revision without the problem'), bad: z.string().max(200).optional().describe('A revision with it; default main'), marks: z.record(z.string().max(100), z.enum(['good', 'bad', 'skip'])).optional().describe('Commits tested so far'), run: z.string().max(4000).optional().describe('A check command, run in each checked-out commit: exit 0 good, 125 skip, other bad') },
143
- seam_show: { quilt: QUILT, rev: REV, format: z.enum(['unified', 'stat', 'names']).optional(), paths: PATHS },
144
- seam_diff: { quilt: QUILT, from: REV, to: REV, format: z.enum(['unified', 'stat', 'names']).optional(), paths: PATHS },
145
- seam_blame: { quilt: QUILT, path: z.string().max(1000), rev: REV },
146
- seam_cat: { quilt: QUILT, path: z.string().max(1000), rev: REV, format: z.enum(['diff', 'snapshot', 'git']).optional().describe('How a conflicted file is written (default diff)') },
147
- seam_ls: { quilt: QUILT, rev: REV, paths: PATHS },
148
- seam_commit: {
149
- quilt: QUILT, square: SQUARE, message: z.string().max(65536).optional().describe('Why (with amend, a new message; left out, the old one stays)'),
150
- amend: z.boolean().optional().describe('Change the square\'s last commit instead of adding one: its changes plus these, under the same parents'),
151
- files: z.array(z.object({ path: z.string().max(1000), content: z.string(), base64: z.boolean().optional(), mode: z.enum(['file', 'exec']).optional() })).max(2000).optional(),
152
- delete: z.array(z.string().max(1000)).max(2000).optional(),
153
- paths: PATHS,
154
- sign: z.boolean().optional().describe('Sign the new commit with this computer\'s key (seam_key_add)')
155
- },
156
- seam_square_handoff: { quilt: QUILT, square: SQUARE, to: z.string().max(200).describe('The member to hand it to'), note: z.string().max(2000).optional() },
157
- seam_abandon: { quilt: QUILT, square: SQUARE },
158
- seam_square_merged: { quilt: QUILT, square: SQUARE, git_sha: z.string().max(64).describe('The git commit (full sha) that holds the square\'s work') },
159
- seam_reopen: { quilt: QUILT, square: SQUARE },
160
- seam_check_rerun: { quilt: QUILT, check: z.string().max(200).describe('The check\'s name, e.g. ci'), square: z.string().max(100).optional().describe('A square (sq:…): re-run on its latest commit'), line: z.string().max(110).optional().describe('A line: re-run on its tip (default main when no square)'), commit: z.string().max(80).optional().describe('A commit instead of the head or tip') },
161
- seam_checks: { quilt: QUILT, state: z.enum(['queued', 'running', 'done', 'cancelled']).optional(), name: z.string().max(100).optional(), tier: z.enum(['pre_merge', 'post_merge']).optional(), line: z.string().max(120).optional(), square: z.string().max(100).optional() },
162
- seam_events: { quilt: QUILT, after: z.number().int().min(0).optional(), kinds: z.array(z.enum(['patch.merged', 'conflict.added', 'square.handoff', 'check.requested'])).optional() },
163
- seam_events_webhook: { quilt: QUILT, url: z.string().max(2000), events: z.array(z.enum(['patch.merged', 'conflict.added', 'square.handoff', 'check.requested'])).optional(), secret: z.string().min(16).max(200).optional() },
164
- seam_fold: { quilt: QUILT, square: SQUARE, into: z.string().max(100).describe('The square (sq:…) that takes the work'), message: z.string().max(65536).optional() },
165
- seam_split: { quilt: QUILT, square: SQUARE, groups: z.array(z.array(z.string().max(1000)).min(1).max(2000)).min(1).max(20).describe('Paths (files or directories) per new square'), titles: z.array(z.string().max(200)).max(20).optional() },
166
- seam_merge: { quilt: QUILT, square: SQUARE, from: z.string().max(200).optional().describe('Merge this square (sq:…) or revision into your square instead of merging your square into a line'), into: z.string().max(100).optional().describe('The line, default main'), message: z.string().max(65536).optional(), dry_run: z.boolean().optional(), on_conflict: z.enum(['rebase', 'leave']).optional() },
167
- seam_rebase: { quilt: QUILT, square: SQUARE, onto: REV },
168
- seam_revert: { quilt: QUILT, rev: z.string().max(200).describe('patch:12, a commit id, …'), line: z.string().max(100).optional() },
169
- seam_cherry_pick: { quilt: QUILT, square: SQUARE, from: z.string().max(200) },
170
- seam_resolve: { quilt: QUILT, square: SQUARE, entries: z.array(z.object({ path: z.string().max(1000), choose: z.union([z.enum(['ours', 'theirs', 'base', 'suggestion', 'delete', 'keep_edit', 'keep_delete']), z.number().int().min(0).max(8)]) })).min(1).max(500), remember: z.boolean().optional().describe('Remember the answer for the same conflict next time (default true)') },
171
- seam_resolutions: { quilt: QUILT, path: z.string().max(1000).optional() },
172
- seam_forget_resolution: { quilt: QUILT, key: z.string().min(8).max(64) }
173
- }
174
-
175
- /**
176
- * Runs one tool. `client` is a quilt client (client.js) acting as the agent; `found` is the
177
- * block folder the local MCP runs in ({ dir, block }) or null. Returns the text to answer.
178
- */
179
- export async function runSeamTool (name, args, { client, found = null }) {
180
- const block = found?.block || null
181
- const quilt = args.quilt || block?.quilt
182
- const square = args.square || block?.square
183
- const needQuilt = () => { if (!quilt) throw new Error('Name the quilt (quilt: its slug or id); seam_quilts lists yours.'); return quilt }
184
- const needSquare = () => { if (!square) throw new Error('Name the square (square: sq:…); seam_open_square opens one, seam_squares lists them.'); return square }
185
- const inFolder = found && block && square === block.square && quilt === block.quilt
186
- const sync = async (head, sq) => { if (inFolder && head && head !== block.head) { const s = await syncTo(client, found.dir, block, head, { square: sq }); return `\nThe folder now holds the square's files (${s.written} updated).` } return '' }
187
- try {
188
- switch (name) {
189
- case 'seam_line_require': {
190
- const extra = Object.fromEntries(['tiers', 'tests', 'ci_config'].filter((k) => args[k] !== undefined).map((k) => [k, args[k]]))
191
- if (args.approvals === undefined && args.checks === undefined && args.runners === undefined && !Object.keys(extra).length) return describePolicy(await client.linePolicy(needQuilt(), args.line))
192
- return describePolicy(await client.setLinePolicy(needQuilt(), args.line, { ...(args.approvals !== undefined ? { approvals: args.approvals } : {}), ...(args.checks ? { checks: args.checks } : {}), ...(args.runners ? { runners: args.runners } : {}), ...extra }))
193
- }
194
- case 'seam_reviews': return describeReviews(await client.reviews(needQuilt(), needSquare()))
195
- case 'seam_review': return describeReviewed(await client.review(needQuilt(), needSquare(), { kind: args.kind, body: args.body || '', ...(args.path ? { path: args.path } : {}), ...(args.line ? { line: args.line } : {}) }))
196
- case 'seam_claims': return describeClaims(await client.claims(needQuilt(), square))
197
- case 'seam_claim': {
198
- const made = []
199
- for (const p of args.patterns) made.push(await client.claim(needQuilt(), needSquare(), { pattern: p, note: args.note }))
200
- return made.map((c) => `Claimed ${c.pattern} for ${c.square}: writes to it from other squares on ${c.line} are refused until the square merges or seam_unclaim.`).join('\n')
201
- }
202
- case 'seam_check': if (args.square) return describeSquareCheck(await client.reportSquareCheck(needQuilt(), args.square, { check: args.check, status: args.status, ...(args.run_key ? { run_key: args.run_key } : {}), ...(args.url ? { url: args.url } : {}) }))
203
- return describeCheck(await client.reportCheck(needQuilt(), args.line || 'main', { check: args.check, status: args.status, ...(args.patch ? { patch: args.patch } : {}), ...(args.run_key ? { run_key: args.run_key } : {}), ...(args.url ? { url: args.url } : {}) }))
204
- case 'seam_check_rerun': return describeRerun(await client.rerunCheck(needQuilt(), { check: args.check, ...(args.square ? { square: args.square } : { line: args.line || 'main' }), ...(args.commit ? { commit: args.commit } : {}) }))
205
- case 'seam_checks': return describeCheckRuns(await client.checkRuns(needQuilt(), { state: args.state, name: args.name, tier: args.tier, line: args.line, square: args.square }))
206
- case 'seam_unclaim': {
207
- const out = []
208
- for (const p of args.patterns) out.push(await client.unclaim(needQuilt(), needSquare(), p))
209
- return out.map((r) => `Let go of ${r.released.pattern}.`).join('\n')
210
- }
211
- case 'seam_live': return describeLive(await client.live(needQuilt(), needSquare()))
212
- case 'seam_live_set': {
213
- const given = [...(args.files || []).map((f) => ({ path: f.path, op: 'put', content: f.base64 ? f.content : Buffer.from(f.content, 'utf8').toString('base64'), mode: f.mode || 'file' })), ...(args.delete || []).map((p) => ({ path: p, op: 'delete' }))]
214
- const changes = given.length || !inFolder ? given : localChanges(found.dir, block).map((c) => c.status === 'deleted' ? { path: c.path, op: 'delete' } : { path: c.path, op: 'put', content: c.bytes.toString('base64'), mode: c.mode })
215
- return describeLiveSet(await client.setLive(needQuilt(), needSquare(), { changes }))
216
- }
217
- case 'seam_restore':
218
- case 'seam_reset':
219
- case 'seam_clean': {
220
- if (!found) throw new Error(`${name} works on a block folder: run seam mcp in a folder made by seam checkout.`)
221
- const files = (args.paths || []).map((p) => path.resolve(found.dir, p))
222
- if (name === 'seam_restore') return describeRestore(await restore(client, found.dir, block, { paths: files }))
223
- if (name === 'seam_reset') return describeReset(await reset(client, found.dir, block, { rev: args.rev }), block)
224
- return describeClean(clean(found.dir, block, { paths: files, dryRun: !!args.dry_run }), 'Call seam_clean again without dry_run to remove them.')
225
- }
226
- case 'seam_quilts': {
227
- if (args.new) { const q = await client.createQuilt({ slug: args.new }); return `Made quilt ${q.slug} (${q.id}).` }
228
- const list = await client.quilts()
229
- return list.length ? list.map((q) => `${q.slug} (${q.id}), ${q.level}`).join('\n') : 'No quilts yet.'
230
- }
231
- case 'seam_open_square': {
232
- const sq = await client.openSquare(needQuilt(), { title: args.title, line: args.line, task_id: args.task_id, ...(args.on ? { on: args.on } : {}) })
233
- return `Opened ${describeSquare(sq)}.\nCommit in it with seam_commit (square: "${sq.short}") and merge it with seam_merge.`
234
- }
235
- case 'seam_squares': {
236
- const list = await client.squares(needQuilt(), { state: args.all ? undefined : 'open', mine: !!args.mine })
237
- return list.length ? list.map(describeSquare).join('\n') : 'No open squares.'
238
- }
239
- case 'seam_square_status': {
240
- if (inFolder) return describeStatus(await blockStatus(client, found.dir, block))
241
- return describeSquareStatus(await client.square(needQuilt(), needSquare()))
242
- }
243
- case 'seam_log': return describeLog(await client.log(needQuilt(), { rev: args.rev || (block?.square ? block.square_short : 'main'), paths: args.paths, limit: args.limit, follow: args.follow || undefined, search: args.search, show_signature: args.show_signature || undefined }), { oneline: !args.show_signature })
244
- case 'seam_describe': return (await client.describe(needQuilt(), args.rev || (block?.square ? block.square_short : 'main'))).describe
245
- case 'seam_merge_base': return describeMergeBase(await client.mergeBase(needQuilt(), args.a, args.b))
246
- case 'seam_search': return describeGrep(await client.search({ pattern: args.pattern, quilts: args.quilts, rev: args.rev, paths: args.paths, ignore_case: args.ignore_case || undefined, fixed: args.fixed || undefined, limit: args.limit }), { filesOnly: !!args.files_only })
247
- case 'seam_grep': return describeGrep(await client.grep(needQuilt(), { pattern: args.pattern, rev: args.rev || (block?.square ? block.square_short : 'main'), paths: args.paths, ignore_case: args.ignore_case || undefined, fixed: args.fixed || undefined, limit: args.limit }), { filesOnly: !!args.files_only })
248
- case 'seam_bisect': {
249
- const o = { bad: args.bad || 'main', good: args.good, marks: args.marks || {} }
250
- if (!args.run) return describeBisect(await client.bisect(needQuilt(), o))
251
- const { bisectRun } = await import('./bisect-run.js')
252
- return describeBisect(await bisectRun(client, { quilt: needQuilt(), ...o, run: args.run }))
253
- }
254
- case 'seam_show': return describeShow(await client.show(needQuilt(), args.rev || 'main', { format: args.format || 'unified', paths: args.paths }), { format: args.format || 'unified' })
255
- case 'seam_diff': return describeDiff(await client.diff(needQuilt(), { from: args.from, to: args.to, paths: args.paths, format: args.format || 'unified' }), { format: args.format || 'unified' })
256
- case 'seam_blame': return describeBlame(await client.blame(needQuilt(), args.rev || 'main', args.path))
257
- case 'seam_cat': { const r = await client.cat(needQuilt(), args.rev || 'main', args.path, { format: args.format || 'diff' }); return Buffer.from(r.content, 'base64').toString('utf8') }
258
- case 'seam_ls': { const r = await client.tree(needQuilt(), args.rev || 'main', args.paths); return r.entries.map((e) => `${e.mode}\t${e.path}`).join('\n') || 'No files.' }
259
- case 'seam_commit': {
260
- if (!args.amend && !args.message) return 'Say why: message.'
261
- const signed = async (r, verb) => {
262
- const text = describeWrite(r, verb)
263
- if (!args.sign || !r?.result?.id) return text
264
- const s = await signWithLocalKey(client, needQuilt(), r.result.id)
265
- return `${text}\nSigned with ${s.signature.key}.`
266
- }
267
- if (args.amend && inFolder && !args.files && !args.delete) {
268
- return signed(await amendFolder(client, found.dir, block, { message: args.message, paths: (args.paths || []).map((p) => `${found.dir}/${p}`) }), 'Amended the last commit as')
269
- }
270
- if (inFolder && !args.files && !args.delete) {
271
- const r = await commitFolder(client, found.dir, block, { message: args.message, paths: (args.paths || []).map((p) => `${found.dir}/${p}`) })
272
- return r.nothing ? 'Nothing to commit: no file in this folder changed since the last commit.' : signed(r, 'Committed')
273
- }
274
- const changes = [
275
- ...(args.files || []).map((f) => ({ path: f.path, op: 'put', content: f.base64 ? f.content : Buffer.from(f.content, 'utf8').toString('base64'), ...(f.mode ? { mode: f.mode } : {}) })),
276
- ...(args.delete || []).map((p) => ({ path: p, op: 'delete' }))
277
- ]
278
- const sq = await client.square(needQuilt(), needSquare())
279
- if (args.amend) return signed(await client.amend(quilt, sq.id, { expect_head: sq.head, message: args.message, changes }), 'Amended the last commit as')
280
- if (!changes.length) return 'Nothing to commit: give files (and delete), or run in a block folder.'
281
- return signed(await client.commit(quilt, sq.id, { expect_head: sq.head, message: args.message, changes }), 'Committed')
282
- }
283
- case 'seam_square_handoff': { const sq = await client.handoff(needQuilt(), needSquare(), { to: args.to, note: args.note }); return `Handed ${sq.short} to ${sq.holder}.` }
284
- case 'seam_square_merged': { const sq = await client.squareMerged(needQuilt(), needSquare(), { git_sha: args.git_sha }); return `${sq.short} is merged by hand: its work is patch ${sq.merged.patch_no} on ${sq.merged.line} (git ${sq.merged.git_sha.slice(0, 12)}).` }
285
- case 'seam_abandon': { const sq = await client.abandon(needQuilt(), needSquare(), inFolder ? { expect_head: block.head } : {}); return `Abandoned ${sq.short}; seam_reopen brings it back.` }
286
- case 'seam_reopen': { const sq = await client.reopen(needQuilt(), needSquare()); return `Reopened ${describeSquare(sq)}.` }
287
- case 'seam_events': return describeEvents(await client.events(needQuilt(), { after: args.after, kinds: args.kinds }))
288
- case 'seam_events_webhook': {
289
- if (args.url === 'off') { await client.subscribe(needQuilt(), { url: null }); return 'Webhook removed.' }
290
- const r = await client.subscribe(needQuilt(), { url: args.url, events: args.events, secret: args.secret })
291
- return `Events (${r.events.join(', ')}) now POST to ${r.url}.${r.secret ? ` Signing secret (shown once): ${r.secret}` : ''}`
292
- }
293
- case 'seam_fold': {
294
- const r = await client.fold(needQuilt(), needSquare(), { into: args.into, message: args.message, ...(inFolder ? { expect_head: block.head } : {}) })
295
- return describeWrite(r, `Folded ${r.folded.short} into ${r.square.short}:`)
296
- }
297
- case 'seam_split': {
298
- const r = await client.split(needQuilt(), needSquare(), { by: 'paths', groups: args.groups, titles: args.titles, ...(inFolder ? { expect_head: block.head } : {}) })
299
- return [`Kept in ${r.square.short}: ${r.kept.join(', ') || '(nothing)'}`, ...r.squares.map((q) => `New ${describeSquare(q)}: ${q.paths.join(', ')}`)].join('\n') + await sync(r.square.head, r.square)
300
- }
301
- case 'seam_line_list': {
302
- const ls = await client.lines(needQuilt())
303
- return ls.map((l) => `${l.name}: ${l.patch_count} patches, tip ${l.tip ? l.tip.slice(0, 12) : '(empty)'}${l.frozen ? ', frozen' : ''}`).join('\n')
304
- }
305
- case 'seam_line_new': {
306
- const l = await client.newLine(needQuilt(), { name: args.name, from: args.from })
307
- return `Made line ${l.name} at ${l.tip ? l.tip.slice(0, 12) : '(empty)'}. Open a square on it with seam_open_square (line: "${l.name}").`
308
- }
309
- case 'seam_line_freeze': { const l = await client.freezeLine(needQuilt(), args.line); return `${l.name} is frozen: merges into it are refused until seam_line_unfreeze.` }
310
- case 'seam_line_unfreeze': { const l = await client.unfreezeLine(needQuilt(), args.line); return `${l.name} takes merges again.` }
311
- case 'seam_tag_list': { const ts = await client.tags(needQuilt()); return ts.length ? ts.map(describeTag).join('\n') : 'No tags yet.' }
312
- case 'seam_release_list': { const rs = await client.releases(needQuilt()); return rs.length ? rs.map(describeRelease).join('\n') : 'No releases yet.' }
313
- case 'seam_import_remote': { const r = await client.startImport({ url: args.url, slug: args.into, follow: !!args.follow, interval: args.interval, credential: args.credential }); return `Importing ${args.url} into ${args.into} (import ${r.id})${r.follow ? `, following every ${r.interval_s} s; poke secret ${r.poke_secret} (shown once)` : ''}.` }
314
- case 'seam_export_remote': { const r = await client.setExport(needQuilt(), { target: args.target, lines: args.lines, releases: args.releases, credential: args.credential, scratch: args.scratch, enabled: args.enabled !== false }); return r.enabled ? `Exporting ${r.lines.join(', ')} to ${r.target} after each merge.` : `The export to ${r.target} is off.` }
315
- case 'seam_export_status': { const r = await client.exportStatus(needQuilt()); return JSON.stringify({ target: r.target, lines: r.lines, releases: r.releases, enabled: r.enabled, status: r.status }, null, 2) }
316
- case 'seam_import_cutover': { const r = await client.cutover(args.id, { credential: args.credential }); return `Cut over: import ${r.import.id} is done; main exports to ${r.export.target} after each merge.` }
317
- case 'seam_import_status': return describeImportRecord(await client.importStatus(args.id))
318
- case 'seam_import_poke': { const r = await client.pokeImport(args.id); return `Poked: import ${r.id} fetches the remote now.` }
319
- case 'seam_import_git': {
320
- const { importGit } = await import('./import-git.js')
321
- const { default: nodePath } = await import('node:path')
322
- const repo = nodePath.resolve(found?.dir || process.cwd(), args.path)
323
- const r = await importGit(client, { repo, slug: args.into || nodePath.basename(repo).toLowerCase().replace(/[^a-z0-9-]/g, '-'), validate: !!args.validate })
324
- return describeImport(r)
325
- }
326
- case 'seam_sign': {
327
- const rev = args.rev || (block?.square ? block.square_short : 'main')
328
- if (args.signature) { const id = (await client.describe(needQuilt(), rev)).commit; const r = await client.sign(needQuilt(), id, { key: args.key, signature: args.signature }); return `Signed ${r.short} with ${r.signature.key}.` }
329
- const r = await signWithLocalKey(client, needQuilt(), rev)
330
- return `Signed ${r.short} with ${r.signature.key}.`
331
- }
332
- case 'seam_keys': return describeKeys(await client.signingKeys({ member: args.member }))
333
- case 'seam_key_add': {
334
- let pub = args.public_key
335
- let made = false
336
- if (!pub) { const k = localKey(); if (k) pub = k.public_key; else { pub = makeLocalKey(); made = true } }
337
- const r = await client.addKey({ public_key: pub, name: args.name || '' })
338
- return `${made ? `Made a signing key at ${signingKeyFile()} and added` : 'Added'} ${r.key}. Sign with seam_commit { sign: true } or seam_sign.`
339
- }
340
- case 'seam_token_list': return describeTokens(await client.tokens(needQuilt()))
341
- case 'seam_token_new': return describeNewToken(await client.createToken(needQuilt(), { name: args.name, level: args.level || 'read' }))
342
- case 'seam_token_revoke': { const r = await client.revokeToken(needQuilt(), args.token); return `Revoked token ${r.name} (${r.id}): it no longer reaches the quilt.` }
343
- case 'seam_key_remove': { const r = await client.removeKey(args.key); return `Removed ${r.key}: commits it signed now show removed_key.` }
344
- case 'seam_tag':
345
- case 'seam_release': {
346
- const x = name === 'seam_tag' ? await client.createTag(needQuilt(), { name: args.name, rev: args.rev }) : await client.createRelease(needQuilt(), { name: args.name, rev: args.rev, notes: args.notes })
347
- return `${name === 'seam_tag' ? 'Tagged' : 'Released'} ${x.name} at ${x.short}${x.patch ? ` (${x.patch.line}:${x.patch.patch_no})` : ''}. Read it as tag:${x.name}.`
348
- }
349
- case 'seam_merge': {
350
- if (args.from) {
351
- const r = await client.mergeSquare(needQuilt(), needSquare(), { from: args.from, message: args.message, ...(inFolder ? { expect_head: block.head } : {}) })
352
- return r.noop ? `Already up to date: ${args.from} is in this square.` : describeWrite(r, `Merged ${args.from} in:`) + await sync(r.square.head, r.square)
353
- }
354
- const r = await client.merge(needQuilt(), args.into || block?.line || 'main', { square: needSquare(), ...(inFolder ? { expect_head: block.head } : {}), message: args.message, dry_run: !!args.dry_run, on_conflict: args.on_conflict })
355
- return describeMerge(r) + (!r.ok && r.head ? await sync(r.head, r.square) : '')
356
- }
357
- case 'seam_rebase': {
358
- const r = await client.rebase(needQuilt(), needSquare(), { ...(inFolder ? { expect_head: block.head } : {}), onto: args.onto })
359
- return (r.noop ? 'Already on the tip.' : describeWrite(r, 'Rebased')) + (r.square ? await sync(r.square.head || r.square.base, r.square) : '')
360
- }
361
- case 'seam_revert': {
362
- const r = await client.revert(needQuilt(), { rev: args.rev, line: args.line })
363
- return describeWrite(r, 'Reverted in a new square') + `\nMerge it with seam_merge (square: "${r.square.short}").`
364
- }
365
- case 'seam_cherry_pick': {
366
- const r = await client.cherryPick(needQuilt(), needSquare(), { from: args.from, ...(inFolder ? { expect_head: block.head } : {}) })
367
- return describeWrite(r, 'Picked') + await sync(r.square.head, r.square)
368
- }
369
- case 'seam_resolve': {
370
- const r = await client.resolve(needQuilt(), needSquare(), { ...(inFolder ? { expect_head: block.head } : {}), entries: args.entries, ...(args.remember === false ? { remember: false } : {}) })
371
- return describeWrite(r, 'Resolved') + await sync(r.square.head, r.square)
372
- }
373
- case 'seam_resolutions': return describeResolutions(await client.resolutions(needQuilt(), args.path))
374
- case 'seam_forget_resolution': {
375
- const r = await client.forgetResolution(needQuilt(), args.key)
376
- return `Forgot ${r.forgotten.short} (${r.forgotten.path}): that conflict will be asked about again.`
377
- }
378
- }
379
- return `Unknown tool ${name}.`
380
- } catch (e) {
381
- if (e.code === 'head_moved') return `${e.message}: the square's head is now ${String(e.head || '').slice(0, 12)}. Look again (seam_square_status) and retry.`
382
- throw e
383
- }
384
- }
385
-
386
-
387
- /**
388
- * Registers every tool on an MCP server. `client()` makes the quilt client for a call (it may
389
- * throw when the caller is not signed in); `found()` the block folder, or null.
390
- */
391
- export function registerSeamTools (server, { client, found = () => null, extraInput = {} }) {
392
- for (const name of SEAM_TOOLS) {
393
- server.registerTool(name, { description: SEAM_DESCRIPTIONS[name], inputSchema: { ...SEAM_INPUTS[name], ...extraInput } }, async (args = {}) => {
394
- try {
395
- const { session, ...rest } = args
396
- void session
397
- return { content: [{ type: 'text', text: await runSeamTool(name, rest, { client: client(), found: found() }) }] }
398
- } catch (e) {
399
- return { content: [{ type: 'text', text: e.message }], isError: true }
400
- }
401
- })
402
- }
403
- }