passalong 0.17.0 → 0.19.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
@@ -297,13 +297,16 @@ export function buildServer() {
297
297
  "to act. It is what the person sees first in their hub; the document is behind it. A guide " +
298
298
  "with no summary is refused.\n" +
299
299
  "SAY WHICH KIND IT IS. There is no default: a guide with no `kind:` line is refused, and so " +
300
- "is a spelling that is not one of the three. " +
300
+ "is a spelling that is not one of the three. A NEW GUIDE IS READY: it reaches whoever it is " +
301
+ "for at once. Say `status: draft` only when the person should read it first — something you " +
302
+ "are unsure of — and tell them; plan_tasks drafts for you. Every kind moves the same way — " +
303
+ "Ready, taken, handed in, approved. " +
301
304
  `kind: task is work nobody has done yet. Sections: ${TASK_SECTIONS.join(", ")}. ` +
302
305
  "It has no Steps: work out how to reach Goal within Constraints, leave Out of scope " +
303
- "alone, and treat Acceptance as the definition of done. New tasks say `status: draft`; " +
304
- "a proposal awaiting a team decision stays Draft until a person makes it Ready. hand_in with `markdown`: a " +
305
- "transfer guide about what you did (guide_template kind transfer shows the shape) — its " +
306
- "author reviews it against Acceptance.\n" +
306
+ "alone, and treat Acceptance as the definition of done. A proposal awaiting a team decision " +
307
+ "stays Draft until a person makes it Ready. hand_in when every Acceptance line holds: it is " +
308
+ "final — the task goes to Done with what you showed, and nobody reviews it after you — so " +
309
+ "show it, and say anything you had to adapt in `writeup` (500 characters).\n" +
307
310
  `kind: transfer (or no kind: line) is a finished implementation to repeat here. Sections: ${SECTIONS.join(", ")}. ` +
308
311
  "Follow its Steps, adapting anything marked ASSUMES to this codebase; run its " +
309
312
  "Verification, then hand_in with ok and, when it failed, a note saying what went wrong.\n" +
@@ -346,6 +349,18 @@ export function buildServer() {
346
349
  "the user asks what they have been working on, or wants a standup or a summary of a " +
347
350
  "period, call log — but say that it holds what they passed along and not everything they " +
348
351
  "did. Gotchas are the highest-value section: record what failed and why.\n" +
352
+ "A FOLDER IS A PLACE; ITS DOCUMENTS ARE WHAT TO KNOW; GUIDES ARE WHAT TO DO. Sort every " +
353
+ "item before writing it into a folder. If someone still has to act on it — a defect that " +
354
+ "is still open, work not yet done, a check nobody has run — it is a guide: kind bug for " +
355
+ "something broken, kind task for work to do. Publish it and link_folder_guide it into the " +
356
+ "folder; a guide has an owner, a state and an answer. A document has none of those and " +
357
+ "tells nobody, so open work recorded only as a document is lost. If it is what someone " +
358
+ "needs to know — an overview, a decision, a finding, something already fixed with its " +
359
+ "commit — it is a document. An incident is both: one overview document, a document per " +
360
+ "resolved finding, and a bug or task guide per open item, all in the incident's folder. " +
361
+ "WHERE A FOLDER LIVES is in the `space` field of create_folder and get_folder: private, " +
362
+ "or a named team. Tell the person that, never what you meant to do; a folder made without " +
363
+ "`team` is private, and a subfolder takes its parent's space.\n" +
349
364
  "FOLDERS ARE REUSABLE PROJECT CONTEXT, NOT GUIDES. When the user asks for a folder for a " +
350
365
  "script, brief, assets or work spanning several guides, call list_folders first to avoid " +
351
366
  "making a duplicate. If none fits, call create_folder with a title and short description. " +
@@ -366,8 +381,11 @@ export function buildServer() {
366
381
  "unlink_folder_guide takes it out again. Delete a document or file with " +
367
382
  "delete_folder_document or delete_folder_asset only when the person asked for it to go: " +
368
383
  "neither can be undone. No tool deletes a whole folder; a person does that in the hub. " +
384
+ "share_folder returns a link anyone can read the folder by, like a guide's; use it only " +
385
+ "when the person asked to share the folder. " +
369
386
  "list_folders with query finds folders and documents by their words, and with guide the " +
370
- "folders a guide is in. rename_folder_document renames a document; " +
387
+ "folders a guide is in. rename_folder_document renames a document and move_folder_document " +
388
+ "moves it to another folder in the same space, keeping its history; " +
371
389
  "get_folder_document_history reads earlier versions, so a bad edit can be put back.\n" +
372
390
  "A FOLLOW-UP IS MORE CONTEXT FOR A GUIDE, WRITTEN AS ITS OWN GUIDE. When a guide needs " +
373
391
  "more context — a missing detail, a step that needed explaining, what changed since, what " +
@@ -404,15 +422,6 @@ export function buildServer() {
404
422
  async function doTake({ id, cwd, any }) {
405
423
  const dir = cwd || process.cwd();
406
424
  try {
407
- // Without sync there is no claim to make; a guide by id can still be read and worked from.
408
- if (!api.loggedIn() && id) {
409
- const r = await passalong.pull(id, { cwd: dir });
410
- return text(
411
- `${leadFor(parse(r.markdown).meta)}${r.markdown}\n\n<!-- passalong: not logged in, so ` +
412
- "nobody was told you took it; written to " +
413
- `${r.path} -->`,
414
- );
415
- }
416
425
  const r = await passalong.take(id, { cwd: dir, any: Boolean(any) });
417
426
  if (!r.guide) return text(nextNote(r) || "Nothing is waiting for this agent here.");
418
427
  const meta = parse(r.guide.markdown).meta;
@@ -421,8 +430,7 @@ export function buildServer() {
421
430
  const from = await passalong.parentGuide(meta);
422
431
  const held =
423
432
  `<!-- passalong: ${r.guide.id} is yours` +
424
- `${r.guide.resumed ? " (you already held it — carry on from where it was left)" : ""}; ` +
425
- `written to ${r.path}. -->`;
433
+ `${r.guide.resumed ? " (you already held it — carry on from where it was left)" : ""}. -->`;
426
434
  const said = repliesNote(r.replies);
427
435
  return text(
428
436
  `${said ? `${said}\n\n` : ""}${from}${leadFor(meta)}${r.guide.markdown}${siblings}${context ? `\n\n${context}` : ""}` +
@@ -559,7 +567,7 @@ export function buildServer() {
559
567
  title: "Create project folder",
560
568
  annotations: ADDS,
561
569
  description:
562
- "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.",
570
+ "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.",
563
571
  inputSchema: {
564
572
  title: z.string().describe("Name of the project or body of work"),
565
573
  description: z.string().optional().describe("Short purpose of the folder"),
@@ -702,7 +710,8 @@ export function buildServer() {
702
710
  {
703
711
  title: "Write new folder document",
704
712
  annotations: ADDS,
705
- description: "Add Markdown such as a brief or script to a folder.",
713
+ description:
714
+ "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.",
706
715
  inputSchema: { folder: z.string(), name: z.string(), body: z.string() },
707
716
  },
708
717
  async ({ folder, name, body }) => {
@@ -752,6 +761,45 @@ export function buildServer() {
752
761
  }
753
762
  },
754
763
  );
764
+ server.registerTool(
765
+ "share_folder",
766
+ {
767
+ title: "Share folder link",
768
+ annotations: ADDS,
769
+ description:
770
+ "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.",
771
+ inputSchema: { folder: z.string() },
772
+ },
773
+ async ({ folder }) => {
774
+ try {
775
+ return json(await api.shareFolder(folder));
776
+ } catch (err) {
777
+ return fail(err);
778
+ }
779
+ },
780
+ );
781
+ server.registerTool(
782
+ "move_folder_document",
783
+ {
784
+ title: "Move folder document",
785
+ annotations: ADDS,
786
+ description:
787
+ "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.",
788
+ inputSchema: {
789
+ folder: z.string().describe("The folder the document is in now"),
790
+ document: z.string(),
791
+ to: z.string().describe("The folder to move it into"),
792
+ name: z.string().optional().describe("A new name, if it should change too"),
793
+ },
794
+ },
795
+ async ({ folder, document, to, name }) => {
796
+ try {
797
+ return json(await api.moveFolderDocument(folder, document, to, name || ""));
798
+ } catch (err) {
799
+ return fail(err);
800
+ }
801
+ },
802
+ );
755
803
  server.registerTool(
756
804
  "get_folder_document_history",
757
805
  {
@@ -852,7 +900,7 @@ export function buildServer() {
852
900
  annotations: READS,
853
901
  description:
854
902
  "Search transfer guides by words in the title, tags, stack, or body: the user's own " +
855
- "(local and synced) plus every team they belong to. Empty query lists everything, newest first.",
903
+ "plus every team they belong to. Empty query lists everything, newest first.",
856
904
  inputSchema: {
857
905
  query: z.string().default(""),
858
906
  scope: z.string().optional().describe('"mine", "all" (default), or a team slug'),
@@ -861,7 +909,7 @@ export function buildServer() {
861
909
  async ({ query, scope }) => {
862
910
  try {
863
911
  const rows = await passalong.list(query, { scope: scope || "" });
864
- return json({ guides: rows, warning: rows.warning });
912
+ return json({ guides: rows });
865
913
  } catch (err) {
866
914
  return fail(err);
867
915
  }
@@ -1037,7 +1085,7 @@ export function buildServer() {
1037
1085
  const ids = await passalong.planTasks(steps, { cwd: cwd || process.cwd() });
1038
1086
  return json({
1039
1087
  drafts: ids.map((id, i) => ({ id, title: steps[i].title, after: steps[i].after || [] })),
1040
- next: "all in Draft: ask the user to read them, then run `passalong ready --all`",
1088
+ next: `all in Draft: ask the user to read them and mark them Ready on the board at ${api.baseUrl()}/hub/work, or run \`passalong ready --all\``,
1041
1089
  });
1042
1090
  } catch (err) {
1043
1091
  return fail(err);
@@ -1110,7 +1158,7 @@ export function buildServer() {
1110
1158
  outputSchema: handInOut,
1111
1159
  annotations: ADDS,
1112
1160
  description:
1113
- "Done here, with proof, sorted against the line it answers. Send `checks`: one entry per " +
1161
+ "Finished here, with proof. IT IS FINAL: the work goes to Done with what you showed, and nobody reviews it after you (anyone can reopen it with a reason). So SHOW IT: a screenshot for anything with a screen — before and after, for a bug — or, for backend work, the one command and the line of its output that shows it works. Send `checks`: one entry per " +
1114
1162
  "line the guide asks for — `## Acceptance` on a task, `## Verification` on a handoff or a " +
1115
1163
  "bug — each with that line and the evidence for it. That is what its author reads, line " +
1116
1164
  "against line, instead of hunting through a wall of output for the part that answers each " +
@@ -1118,8 +1166,7 @@ export function buildServer() {
1118
1166
  "command proves it: the command is run here before the hand-in lands, a non-zero exit " +
1119
1167
  "refuses it, and what it printed is recorded instead of your account of it. Use `ran` for " +
1120
1168
  "a check no command can settle. `evidence` as one block is the older shape and still " +
1121
- "works. A task also takes `markdown`, a transfer guide about what you did and decided — " +
1122
- "this publishes it and attaches it. A handoff or a bug takes `ok`, whether its " +
1169
+ "works. A task needs no write-up guide any more. A handoff or a bug takes `ok`, whether its " +
1123
1170
  "Verification held here, and `note` saying what went wrong when it did not. DO NOT " +
1124
1171
  "publish a guide to carry your evidence: it belongs on this call, and a second document " +
1125
1172
  'titled "Hand-in evidence" is the thing `checks` exists to replace. If it worked but you ' +
@@ -1195,10 +1242,10 @@ export function buildServer() {
1195
1242
  .string()
1196
1243
  .optional()
1197
1244
  .describe(
1198
- "handoff or bug: what you had to adapt to make it work here — a version, a name, a " +
1199
- "step that needed something the guide does not mention. Prose, and optional: leave " +
1200
- "it out when it worked as written. The next person to open the guide is shown it, " +
1201
- "which is why it does not need to be a guide of its own",
1245
+ "what you had to adapt to make it work here — a version, a name, a step that needed " +
1246
+ "something the guide does not mention. 500 characters at most; anything longer " +
1247
+ "belongs in the change. Optional: leave it out when it worked as written. The next " +
1248
+ "person to open the guide is shown it",
1202
1249
  ),
1203
1250
  risk: z
1204
1251
  .string()
@@ -1212,11 +1259,11 @@ export function buildServer() {
1212
1259
  markdown: z
1213
1260
  .string()
1214
1261
  .optional()
1215
- .describe("task: the write-up; start from guide_template kind transfer"),
1262
+ .describe("older shape, not needed: say what you adapted in `writeup` instead"),
1216
1263
  report: z
1217
1264
  .string()
1218
1265
  .optional()
1219
- .describe("task, instead of markdown: id of a write-up already published"),
1266
+ .describe("older shape, not needed: say what you adapted in `writeup` instead"),
1220
1267
  pr: z.string().optional().describe("task: PR or branch link, when there is one"),
1221
1268
  cwd: z
1222
1269
  .string()
@@ -1288,18 +1335,13 @@ export function buildServer() {
1288
1335
  description:
1289
1336
  "READ a transfer guide by passalong id or share link and return its markdown. A guide over " +
1290
1337
  "20,000 characters comes back as an outline (its headings and their sizes) unless you ask " +
1291
- "for `section`, for one part, or `full`, for all of it. Also " +
1292
- "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. " +
1293
1339
  "Pulling a teammate's guide tells them the transfer landed. Use this to look at a guide " +
1294
1340
  "you have not committed to. If you are about to DO the work, call take instead: " +
1295
1341
  "it does this and takes it in one call, which is the only way the sender learns " +
1296
1342
  "somebody picked it up.",
1297
1343
  inputSchema: {
1298
1344
  ref: z.string().describe("passalong id (e.g. k3mq2xa7) or share URL"),
1299
- cwd: z
1300
- .string()
1301
- .optional()
1302
- .describe("directory to write .passalong/<id>.md into; default is the server's cwd"),
1303
1345
  section: z
1304
1346
  .string()
1305
1347
  .optional()
@@ -1314,23 +1356,23 @@ export function buildServer() {
1314
1356
  .describe("the whole document, even one over 20,000 characters"),
1315
1357
  },
1316
1358
  },
1317
- async ({ ref, cwd, section, outline, full }) => {
1359
+ async ({ ref, section, outline, full }) => {
1318
1360
  try {
1319
- const { markdown, path, from } = await passalong.pull(ref, { cwd: cwd || process.cwd() });
1361
+ const { markdown, from } = await passalong.pull(ref);
1320
1362
  const meta = parse(markdown).meta;
1321
1363
  if (section) {
1322
1364
  const part = sectionOf(markdown, section);
1323
1365
  if (!part)
1324
1366
  return text(
1325
- `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 })}`,
1326
1368
  );
1327
1369
  return text(
1328
- `${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 -->`,
1329
1371
  );
1330
1372
  }
1331
1373
  if (outline || (!full && markdown.length > GUIDE_WARN))
1332
1374
  return text(
1333
- `${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} -->`,
1334
1376
  );
1335
1377
  const lead = leadFor(meta);
1336
1378
  const siblings = await related(meta);
@@ -1347,7 +1389,7 @@ export function buildServer() {
1347
1389
  // warning against executing the document, so being read first is its whole job.
1348
1390
  return text(
1349
1391
  `${follows}${lead}${markdown}${siblings}${context ? `\n\n${context}` : ""}` +
1350
- `\n\n<!-- passalong: ${from}; written to ${path} -->` +
1392
+ `\n\n<!-- passalong: ${from} -->` +
1351
1393
  `\n${passalong.handoffNudge(meta)}` +
1352
1394
  `\n${passalong.followUpNote(meta)}`,
1353
1395
  );
@@ -1517,7 +1559,7 @@ export function buildServer() {
1517
1559
  },
1518
1560
  async ({ markdown, to, cwd, parent }) => {
1519
1561
  try {
1520
- const { guide, url, synced, notified, path } = await passalong.share(markdown, {
1562
+ const { guide, url, synced, notified } = await passalong.share(markdown, {
1521
1563
  cwd: cwd || process.cwd(),
1522
1564
  to,
1523
1565
  follows: parent,
@@ -1531,14 +1573,14 @@ export function buildServer() {
1531
1573
  to: guide.meta.to || "",
1532
1574
  parent: guide.meta.parent || "",
1533
1575
  notified,
1534
- path,
1535
1576
  ...(sizeWarning(markdown.length) ? { warning: sizeWarning(markdown.length) } : {}),
1536
- // A task does not go live on publish, and an agent that reports "queued it" has told
1537
- // the user something untrue. Said in the result so it is said to them.
1538
- ...(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"
1539
1580
  ? {
1540
1581
  status: "draft",
1541
- next: `in Draft: ask the user to read it, then run \`passalong ready ${guide.meta.id}\``,
1582
+ // The hub page, never the share link: the share page has no Ready button.
1583
+ next: `in Draft: ask the user to read it and mark it Ready at ${api.baseUrl()}/hub/g/${guide.meta.id}, or run \`passalong ready ${guide.meta.id}\``,
1542
1584
  }
1543
1585
  : {}),
1544
1586
  });
@@ -1745,5 +1787,6 @@ export function buildServer() {
1745
1787
 
1746
1788
  /** The stdio server: `passalong mcp`, and what `passalong setup` registers with a client. */
1747
1789
  export async function serve() {
1790
+ passalong.tidy().catch(() => {});
1748
1791
  await buildServer().connect(new StdioServerTransport());
1749
1792
  }