passalong 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mcp.js CHANGED
@@ -219,6 +219,8 @@ const passOut = openObject({ id: z.string(), passed: z.boolean() });
219
219
 
220
220
  const READS = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
221
221
  const ADDS = { readOnlyHint: false, destructiveHint: false, openWorldHint: false };
222
+ // Removes something that cannot be brought back. Only a person's request should lead here.
223
+ const REMOVES = { readOnlyHint: false, destructiveHint: true, openWorldHint: false };
222
224
  const fail = (err) => ({ content: [{ type: "text", text: err.message }], isError: true });
223
225
 
224
226
  /**
@@ -295,11 +297,13 @@ export function buildServer() {
295
297
  "to act. It is what the person sees first in their hub; the document is behind it. A guide " +
296
298
  "with no summary is refused.\n" +
297
299
  "SAY WHICH KIND IT IS. There is no default: a guide with no `kind:` line is refused, and so " +
298
- "is a spelling that is not one of the three. " +
300
+ "is a spelling that is not one of the three. EVERY NEW GUIDE IS A DRAFT, whatever its kind: " +
301
+ "it reaches nobody until a person reads it and marks it Ready, and every kind then moves the " +
302
+ "same way — Ready, taken, handed in, approved. " +
299
303
  `kind: task is work nobody has done yet. Sections: ${TASK_SECTIONS.join(", ")}. ` +
300
304
  "It has no Steps: work out how to reach Goal within Constraints, leave Out of scope " +
301
- "alone, and treat Acceptance as the definition of done. New tasks say `status: draft`; " +
302
- "a proposal awaiting a team decision stays Draft until a person makes it Ready. hand_in with `markdown`: a " +
305
+ "alone, and treat Acceptance as the definition of done. A proposal awaiting a team decision " +
306
+ "stays Draft until a person makes it Ready. hand_in with `markdown`: a " +
303
307
  "transfer guide about what you did (guide_template kind transfer shows the shape) — its " +
304
308
  "author reviews it against Acceptance.\n" +
305
309
  `kind: transfer (or no kind: line) is a finished implementation to repeat here. Sections: ${SECTIONS.join(", ")}. ` +
@@ -344,9 +348,23 @@ export function buildServer() {
344
348
  "the user asks what they have been working on, or wants a standup or a summary of a " +
345
349
  "period, call log — but say that it holds what they passed along and not everything they " +
346
350
  "did. Gotchas are the highest-value section: record what failed and why.\n" +
351
+ "A FOLDER IS A PLACE; ITS DOCUMENTS ARE WHAT TO KNOW; GUIDES ARE WHAT TO DO. Sort every " +
352
+ "item before writing it into a folder. If someone still has to act on it — a defect that " +
353
+ "is still open, work not yet done, a check nobody has run — it is a guide: kind bug for " +
354
+ "something broken, kind task for work to do. Publish it and link_folder_guide it into the " +
355
+ "folder; a guide has an owner, a state and an answer. A document has none of those and " +
356
+ "tells nobody, so open work recorded only as a document is lost. If it is what someone " +
357
+ "needs to know — an overview, a decision, a finding, something already fixed with its " +
358
+ "commit — it is a document. An incident is both: one overview document, a document per " +
359
+ "resolved finding, and a bug or task guide per open item, all in the incident's folder. " +
360
+ "WHERE A FOLDER LIVES is in the `space` field of create_folder and get_folder: private, " +
361
+ "or a named team. Tell the person that, never what you meant to do; a folder made without " +
362
+ "`team` is private, and a subfolder takes its parent's space.\n" +
347
363
  "FOLDERS ARE REUSABLE PROJECT CONTEXT, NOT GUIDES. When the user asks for a folder for a " +
348
364
  "script, brief, assets or work spanning several guides, call list_folders first to avoid " +
349
365
  "making a duplicate. If none fits, call create_folder with a title and short description. " +
366
+ "Use its parent id to nest a subfolder at any depth; keep a related project together. " +
367
+ "A folder's color can mark an area, and update_folder can recolor or move it later. " +
350
368
  "A short request is enough: name it from the person's purpose and draft the first Markdown " +
351
369
  "document with create_folder_document. Do not leave an empty folder or make them fill a form. " +
352
370
  "Pass `team` only when the user wants it shared with that team; use the slug they named " +
@@ -355,8 +373,19 @@ export function buildServer() {
355
373
  "get_folder_document to read text and its version, and save_folder_document with that " +
356
374
  "version when editing. On a stale-version refusal, reread before retrying so another " +
357
375
  "person's work is not lost. Use add_folder_asset for a screenshot or file available on " +
358
- "this machine, get_folder_asset to read one already there, and link_folder_guide only " +
359
- "when an existing guide uses that folder.\n" +
376
+ "this machine, and get_folder_asset to read one already there. " +
377
+ "To put a guide in a folder — a task, bug or handoff, new or already published — publish it " +
378
+ "as usual (for a team folder, to that folder's team, or its members cannot see it), then call " +
379
+ "link_folder_guide with the folder id and the guide id. Linking leaves the guide unchanged; " +
380
+ "unlink_folder_guide takes it out again. Delete a document or file with " +
381
+ "delete_folder_document or delete_folder_asset only when the person asked for it to go: " +
382
+ "neither can be undone. No tool deletes a whole folder; a person does that in the hub. " +
383
+ "share_folder returns a link anyone can read the folder by, like a guide's; use it only " +
384
+ "when the person asked to share the folder. " +
385
+ "list_folders with query finds folders and documents by their words, and with guide the " +
386
+ "folders a guide is in. rename_folder_document renames a document and move_folder_document " +
387
+ "moves it to another folder in the same space, keeping its history; " +
388
+ "get_folder_document_history reads earlier versions, so a bad edit can be put back.\n" +
360
389
  "A FOLLOW-UP IS MORE CONTEXT FOR A GUIDE, WRITTEN AS ITS OWN GUIDE. When a guide needs " +
361
390
  "more context — a missing detail, a step that needed explaining, what changed since, what " +
362
391
  "you found doing it — publish that context with publish_guide `parent` set to the guide's " +
@@ -392,15 +421,6 @@ export function buildServer() {
392
421
  async function doTake({ id, cwd, any }) {
393
422
  const dir = cwd || process.cwd();
394
423
  try {
395
- // Without sync there is no claim to make; a guide by id can still be read and worked from.
396
- if (!api.loggedIn() && id) {
397
- const r = await passalong.pull(id, { cwd: dir });
398
- return text(
399
- `${leadFor(parse(r.markdown).meta)}${r.markdown}\n\n<!-- passalong: not logged in, so ` +
400
- "nobody was told you took it; written to " +
401
- `${r.path} -->`,
402
- );
403
- }
404
424
  const r = await passalong.take(id, { cwd: dir, any: Boolean(any) });
405
425
  if (!r.guide) return text(nextNote(r) || "Nothing is waiting for this agent here.");
406
426
  const meta = parse(r.guide.markdown).meta;
@@ -409,8 +429,7 @@ export function buildServer() {
409
429
  const from = await passalong.parentGuide(meta);
410
430
  const held =
411
431
  `<!-- passalong: ${r.guide.id} is yours` +
412
- `${r.guide.resumed ? " (you already held it — carry on from where it was left)" : ""}; ` +
413
- `written to ${r.path}. -->`;
432
+ `${r.guide.resumed ? " (you already held it — carry on from where it was left)" : ""}. -->`;
414
433
  const said = repliesNote(r.replies);
415
434
  return text(
416
435
  `${said ? `${said}\n\n` : ""}${from}${leadFor(meta)}${r.guide.markdown}${siblings}${context ? `\n\n${context}` : ""}` +
@@ -526,12 +545,16 @@ export function buildServer() {
526
545
  {
527
546
  title: "List project folders",
528
547
  annotations: READS,
529
- description: "Find project folders of documents, assets and guides. Requires sync sign-in.",
530
- inputSchema: { scope: z.string().optional().describe('"all", "mine", or a team slug') },
548
+ description: `Find project folders of documents, assets and guides. Pass query to find the folders whose name, description, or any document's name or text mention those words; each then names its matching documents. Pass guide to find the folders a guide is in. Requires sync sign-in.`,
549
+ inputSchema: {
550
+ scope: z.string().optional().describe('"all", "mine", or a team slug'),
551
+ query: z.string().optional().describe("Words to look for in folders and their documents"),
552
+ guide: z.string().optional().describe("A guide id: only the folders it is linked in"),
553
+ },
531
554
  },
532
- async ({ scope }) => {
555
+ async ({ scope, query, guide }) => {
533
556
  try {
534
- return json(await api.folders(scope || "all"));
557
+ return json(await api.folders(scope || "all", { query, guide }));
535
558
  } catch (err) {
536
559
  return fail(err);
537
560
  }
@@ -543,16 +566,57 @@ export function buildServer() {
543
566
  title: "Create project folder",
544
567
  annotations: ADDS,
545
568
  description:
546
- "Create a folder for project documents and assets after checking list_folders. Omit team for a private folder; pass a team slug only when sharing was requested.",
569
+ "Create a folder or nested subfolder after checking list_folders. Pass parent to nest it; omit team for a private root, and pass a team slug only when sharing was requested. A subfolder inherits its parent's space. The answer's `space` says where it landed: report that to the person.",
547
570
  inputSchema: {
548
571
  title: z.string().describe("Name of the project or body of work"),
549
572
  description: z.string().optional().describe("Short purpose of the folder"),
550
573
  team: z.string().optional().describe("Team slug; omit for a private folder"),
574
+ parent: z
575
+ .string()
576
+ .optional()
577
+ .describe("Parent folder id for a nested subfolder; omit for a top-level folder"),
578
+ color: z
579
+ .enum(["coral", "amber", "green", "blue", "violet"])
580
+ .optional()
581
+ .describe("Optional grouping color"),
551
582
  },
552
583
  },
553
- async ({ title, description, team }) => {
584
+ async ({ title, description, team, parent, color }) => {
554
585
  try {
555
- return json(await api.createFolder(title, description || "", team || ""));
586
+ return json(
587
+ await api.createFolder(title, description || "", team || "", parent || "", color || ""),
588
+ );
589
+ } catch (err) {
590
+ return fail(err);
591
+ }
592
+ },
593
+ );
594
+ server.registerTool(
595
+ "update_folder",
596
+ {
597
+ title: "Organize project folder",
598
+ annotations: ADDS,
599
+ description:
600
+ "Rename, describe, recolor or move a folder. Pass parent as an empty string to move it to the top level. A folder cannot move inside itself or a descendant, or into another team/private space.",
601
+ inputSchema: {
602
+ folder: z.string(),
603
+ title: z.string().optional(),
604
+ description: z.string().optional(),
605
+ parent: z
606
+ .string()
607
+ .optional()
608
+ .describe("New parent folder id; empty string means top level"),
609
+ color: z.enum(["", "coral", "amber", "green", "blue", "violet"]).optional(),
610
+ },
611
+ },
612
+ async ({ folder, title, description, parent, color }) => {
613
+ try {
614
+ const changes = Object.fromEntries(
615
+ Object.entries({ title, description, parent, color }).filter(
616
+ ([, value]) => value !== undefined,
617
+ ),
618
+ );
619
+ return json(await api.updateFolder(folder, changes));
556
620
  } catch (err) {
557
621
  return fail(err);
558
622
  }
@@ -645,7 +709,8 @@ export function buildServer() {
645
709
  {
646
710
  title: "Write new folder document",
647
711
  annotations: ADDS,
648
- description: "Add Markdown such as a brief or script to a folder.",
712
+ description:
713
+ "Add a Markdown document to a folder: reference to read, such as a brief, script, overview or a finding already resolved. It has no owner and no state and notifies nobody, so anything someone must still act on (an open bug, work to do) belongs in a bug or task guide linked to the folder instead.",
649
714
  inputSchema: { folder: z.string(), name: z.string(), body: z.string() },
650
715
  },
651
716
  async ({ folder, name, body }) => {
@@ -678,13 +743,90 @@ export function buildServer() {
678
743
  }
679
744
  },
680
745
  );
746
+ server.registerTool(
747
+ "rename_folder_document",
748
+ {
749
+ title: "Rename folder document",
750
+ annotations: ADDS,
751
+ description:
752
+ "Rename a folder document. Its id, text and history stay; no version is needed, because a rename cannot overwrite anyone's text.",
753
+ inputSchema: { folder: z.string(), document: z.string(), name: z.string() },
754
+ },
755
+ async ({ folder, document, name }) => {
756
+ try {
757
+ return json(await api.renameFolderDocument(folder, document, name));
758
+ } catch (err) {
759
+ return fail(err);
760
+ }
761
+ },
762
+ );
763
+ server.registerTool(
764
+ "share_folder",
765
+ {
766
+ title: "Share folder link",
767
+ annotations: ADDS,
768
+ description:
769
+ "Get the folder's share link, making one if it has none: like a guide's link, anyone holding it can read the folder and the folders inside it and download its files, with no account. Linked guides are not included. Call it only when the person asked to share the folder or for its link; stopping sharing is done in the hub.",
770
+ inputSchema: { folder: z.string() },
771
+ },
772
+ async ({ folder }) => {
773
+ try {
774
+ return json(await api.shareFolder(folder));
775
+ } catch (err) {
776
+ return fail(err);
777
+ }
778
+ },
779
+ );
780
+ server.registerTool(
781
+ "move_folder_document",
782
+ {
783
+ title: "Move folder document",
784
+ annotations: ADDS,
785
+ description:
786
+ "Move a folder document into another folder in the same space, private or the same team. It keeps its id, text and history; pass name too to rename it on the way. Refused if the other folder already has a document by that name.",
787
+ inputSchema: {
788
+ folder: z.string().describe("The folder the document is in now"),
789
+ document: z.string(),
790
+ to: z.string().describe("The folder to move it into"),
791
+ name: z.string().optional().describe("A new name, if it should change too"),
792
+ },
793
+ },
794
+ async ({ folder, document, to, name }) => {
795
+ try {
796
+ return json(await api.moveFolderDocument(folder, document, to, name || ""));
797
+ } catch (err) {
798
+ return fail(err);
799
+ }
800
+ },
801
+ );
802
+ server.registerTool(
803
+ "get_folder_document_history",
804
+ {
805
+ title: "Read folder document history",
806
+ annotations: READS,
807
+ description:
808
+ "Read a folder document's earlier versions. Without version, lists them newest first; with version, returns that version's text. To bring one back, save it with save_folder_document over the current version.",
809
+ inputSchema: {
810
+ folder: z.string(),
811
+ document: z.string(),
812
+ version: z.number().int().positive().optional(),
813
+ },
814
+ },
815
+ async ({ folder, document, version }) => {
816
+ try {
817
+ return json(await api.folderDocumentHistory(folder, document, version || 0));
818
+ } catch (err) {
819
+ return fail(err);
820
+ }
821
+ },
822
+ );
681
823
  server.registerTool(
682
824
  "link_folder_guide",
683
825
  {
684
826
  title: "Link guide to folder",
685
827
  annotations: ADDS,
686
828
  description:
687
- "Put an existing task or handoff beside the folder documents and assets it uses.",
829
+ "Put a task, bug or handoff guide in the folder, beside the documents and assets it uses: one just published or one that already existed.",
688
830
  inputSchema: { folder: z.string(), guide: z.string() },
689
831
  },
690
832
  async ({ folder, guide }) => {
@@ -696,6 +838,60 @@ export function buildServer() {
696
838
  },
697
839
  );
698
840
 
841
+ server.registerTool(
842
+ "unlink_folder_guide",
843
+ {
844
+ title: "Unlink guide from folder",
845
+ annotations: ADDS,
846
+ description:
847
+ "Take a guide out of a folder. Only the link goes: the guide itself is unchanged, and link_folder_guide puts it back.",
848
+ inputSchema: { folder: z.string(), guide: z.string() },
849
+ },
850
+ async ({ folder, guide }) => {
851
+ try {
852
+ return json(await api.unlinkFolderGuide(folder, guide));
853
+ } catch (err) {
854
+ return fail(err);
855
+ }
856
+ },
857
+ );
858
+
859
+ server.registerTool(
860
+ "delete_folder_document",
861
+ {
862
+ title: "Delete folder document",
863
+ annotations: REMOVES,
864
+ description:
865
+ "Delete one document from a folder, with its earlier versions. It cannot be brought back, so do it only when the person asked for that document to go; to change what it says, use save_folder_document instead.",
866
+ inputSchema: { folder: z.string(), document: z.string() },
867
+ },
868
+ async ({ folder, document }) => {
869
+ try {
870
+ return json(await api.deleteFolderDocument(folder, document));
871
+ } catch (err) {
872
+ return fail(err);
873
+ }
874
+ },
875
+ );
876
+
877
+ server.registerTool(
878
+ "delete_folder_asset",
879
+ {
880
+ title: "Delete folder file",
881
+ annotations: REMOVES,
882
+ description:
883
+ "Delete one file from a folder. It cannot be brought back, so do it only when the person asked for that file to go.",
884
+ inputSchema: { folder: z.string(), asset: z.string() },
885
+ },
886
+ async ({ folder, asset }) => {
887
+ try {
888
+ return json(await api.deleteFolderAsset(folder, asset));
889
+ } catch (err) {
890
+ return fail(err);
891
+ }
892
+ },
893
+ );
894
+
699
895
  server.registerTool(
700
896
  "search_guides",
701
897
  {
@@ -703,7 +899,7 @@ export function buildServer() {
703
899
  annotations: READS,
704
900
  description:
705
901
  "Search transfer guides by words in the title, tags, stack, or body: the user's own " +
706
- "(local and synced) plus every team they belong to. Empty query lists everything, newest first.",
902
+ "plus every team they belong to. Empty query lists everything, newest first.",
707
903
  inputSchema: {
708
904
  query: z.string().default(""),
709
905
  scope: z.string().optional().describe('"mine", "all" (default), or a team slug'),
@@ -712,7 +908,7 @@ export function buildServer() {
712
908
  async ({ query, scope }) => {
713
909
  try {
714
910
  const rows = await passalong.list(query, { scope: scope || "" });
715
- return json({ guides: rows, warning: rows.warning });
911
+ return json({ guides: rows });
716
912
  } catch (err) {
717
913
  return fail(err);
718
914
  }
@@ -769,8 +965,8 @@ export function buildServer() {
769
965
  "delivery of, and every verdict and ack they gave. Each item has a rendered `text` line " +
770
966
  "and the guide's repo. Use it to answer 'what have I been working on', to write a " +
771
967
  "standup or a weekly summary, or to find work from a repo by when it happened rather " +
772
- "than by what it was called. This is the opposite of `activity`, which is what other " +
773
- "people did. IMPORTANT: it records what was passed along, not what was worked on — work " +
968
+ "than by what it was called. The hub calls this page Activities, so 'my activities' means " +
969
+ "this tool. It is the opposite of `activity`, which is what other people did. IMPORTANT: it records what was passed along, not what was worked on — work " +
774
970
  "that never became a guide has no entry, so never present it as a complete record of " +
775
971
  "this user's work, and never infer that a quiet period was an idle one.",
776
972
  inputSchema: {
@@ -797,8 +993,9 @@ export function buildServer() {
797
993
  annotations: READS,
798
994
  description:
799
995
  "What has happened to this user's guides and handoffs: who pulled one, who archived one, " +
800
- "who was handed what, who joined a team. Each item has a ready-made `text` line. This only " +
801
- "reads; clear_activity is what marks the feed seen.",
996
+ "who was handed what, who joined a team. Each item has a ready-made `text` line. This is " +
997
+ "the hub's notifications, not its Activities page — for the user's own acts ('my " +
998
+ "activities'), use log. This only reads; clear_activity is what marks the feed seen.",
802
999
  inputSchema: {
803
1000
  all: z.boolean().default(false).describe("include what the user has already seen"),
804
1001
  },
@@ -1138,18 +1335,13 @@ export function buildServer() {
1138
1335
  description:
1139
1336
  "READ a transfer guide by passalong id or share link and return its markdown. A guide over " +
1140
1337
  "20,000 characters comes back as an outline (its headings and their sizes) unless you ask " +
1141
- "for `section`, for one part, or `full`, for all of it. Also " +
1142
- "writes it to .passalong/<id>.md in the working directory so it survives the session. " +
1338
+ "for `section`, for one part, or `full`, for all of it. Nothing is written to disk. " +
1143
1339
  "Pulling a teammate's guide tells them the transfer landed. Use this to look at a guide " +
1144
1340
  "you have not committed to. If you are about to DO the work, call take instead: " +
1145
1341
  "it does this and takes it in one call, which is the only way the sender learns " +
1146
1342
  "somebody picked it up.",
1147
1343
  inputSchema: {
1148
1344
  ref: z.string().describe("passalong id (e.g. k3mq2xa7) or share URL"),
1149
- cwd: z
1150
- .string()
1151
- .optional()
1152
- .describe("directory to write .passalong/<id>.md into; default is the server's cwd"),
1153
1345
  section: z
1154
1346
  .string()
1155
1347
  .optional()
@@ -1164,23 +1356,23 @@ export function buildServer() {
1164
1356
  .describe("the whole document, even one over 20,000 characters"),
1165
1357
  },
1166
1358
  },
1167
- async ({ ref, cwd, section, outline, full }) => {
1359
+ async ({ ref, section, outline, full }) => {
1168
1360
  try {
1169
- const { markdown, path, from } = await passalong.pull(ref, { cwd: cwd || process.cwd() });
1361
+ const { markdown, from } = await passalong.pull(ref);
1170
1362
  const meta = parse(markdown).meta;
1171
1363
  if (section) {
1172
1364
  const part = sectionOf(markdown, section);
1173
1365
  if (!part)
1174
1366
  return text(
1175
- `No heading matching "${section}".\n\n${outlineText(markdown, { id: meta.id || ref, path })}`,
1367
+ `No heading matching "${section}".\n\n${outlineText(markdown, { id: meta.id || ref })}`,
1176
1368
  );
1177
1369
  return text(
1178
- `${part.text}\n\n<!-- passalong: ${meta.id || ref}, "${part.heading}" only; the whole guide is at ${path} -->`,
1370
+ `${part.text}\n\n<!-- passalong: ${meta.id || ref}, "${part.heading}" only; ask with full: true for the whole guide -->`,
1179
1371
  );
1180
1372
  }
1181
1373
  if (outline || (!full && markdown.length > GUIDE_WARN))
1182
1374
  return text(
1183
- `${outlineText(markdown, { id: meta.id || ref, path })}\n\n<!-- passalong: ${from}; written to ${path} -->`,
1375
+ `${outlineText(markdown, { id: meta.id || ref })}\n\n<!-- passalong: ${from} -->`,
1184
1376
  );
1185
1377
  const lead = leadFor(meta);
1186
1378
  const siblings = await related(meta);
@@ -1197,7 +1389,7 @@ export function buildServer() {
1197
1389
  // warning against executing the document, so being read first is its whole job.
1198
1390
  return text(
1199
1391
  `${follows}${lead}${markdown}${siblings}${context ? `\n\n${context}` : ""}` +
1200
- `\n\n<!-- passalong: ${from}; written to ${path} -->` +
1392
+ `\n\n<!-- passalong: ${from} -->` +
1201
1393
  `\n${passalong.handoffNudge(meta)}` +
1202
1394
  `\n${passalong.followUpNote(meta)}`,
1203
1395
  );
@@ -1367,7 +1559,7 @@ export function buildServer() {
1367
1559
  },
1368
1560
  async ({ markdown, to, cwd, parent }) => {
1369
1561
  try {
1370
- const { guide, url, synced, notified, path } = await passalong.share(markdown, {
1562
+ const { guide, url, synced, notified } = await passalong.share(markdown, {
1371
1563
  cwd: cwd || process.cwd(),
1372
1564
  to,
1373
1565
  follows: parent,
@@ -1381,11 +1573,10 @@ export function buildServer() {
1381
1573
  to: guide.meta.to || "",
1382
1574
  parent: guide.meta.parent || "",
1383
1575
  notified,
1384
- path,
1385
1576
  ...(sizeWarning(markdown.length) ? { warning: sizeWarning(markdown.length) } : {}),
1386
- // A task does not go live on publish, and an agent that reports "queued it" has told
1387
- // the user something untrue. Said in the result so it is said to them.
1388
- ...(guide.meta.kind === "task" && guide.meta.status === "draft"
1577
+ // Nothing goes live on publish, and an agent that reports "sent it" or "queued it" has
1578
+ // told the user something untrue. Said in the result so it is said to them.
1579
+ ...(guide.meta.status === "draft"
1389
1580
  ? {
1390
1581
  status: "draft",
1391
1582
  next: `in Draft: ask the user to read it, then run \`passalong ready ${guide.meta.id}\``,
@@ -1486,7 +1677,11 @@ export function buildServer() {
1486
1677
  { title: title || "", environment: environment || "", issues },
1487
1678
  { cwd: cwd || process.cwd(), to },
1488
1679
  );
1489
- return json(filed);
1680
+ return json({
1681
+ ...filed,
1682
+ // Filed is not sent: every bug is a draft until a person reads it.
1683
+ next: "all in Draft: ask the user to read them, then run `passalong ready --all`",
1684
+ });
1490
1685
  } catch (err) {
1491
1686
  return fail(err);
1492
1687
  }
@@ -1595,5 +1790,6 @@ export function buildServer() {
1595
1790
 
1596
1791
  /** The stdio server: `passalong mcp`, and what `passalong setup` registers with a client. */
1597
1792
  export async function serve() {
1793
+ passalong.tidy().catch(() => {});
1598
1794
  await buildServer().connect(new StdioServerTransport());
1599
1795
  }