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/README.md +13 -15
- package/bin/passalong +71 -150
- package/package.json +1 -1
- package/skill/SKILL.md +6 -7
- package/src/api.js +18 -1
- package/src/guide.js +6 -4
- package/src/mcp.js +92 -49
- package/src/passalong.js +121 -182
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.
|
|
304
|
-
"
|
|
305
|
-
"
|
|
306
|
-
"
|
|
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:
|
|
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
|
-
"
|
|
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
|
|
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:
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
1199
|
-
"
|
|
1200
|
-
"it out when it worked as written. The next
|
|
1201
|
-
"
|
|
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("
|
|
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("
|
|
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.
|
|
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,
|
|
1359
|
+
async ({ ref, section, outline, full }) => {
|
|
1318
1360
|
try {
|
|
1319
|
-
const { markdown,
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
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
|
-
//
|
|
1537
|
-
// the user something untrue. Said in the result so it is said to them.
|
|
1538
|
-
...(guide.meta.
|
|
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
|
-
|
|
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
|
}
|