passalong 0.18.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 CHANGED
@@ -28,7 +28,7 @@ codex mcp add passalong -- passalong mcp # Codex: the same MCP server
28
28
  ## Commands
29
29
 
30
30
  ```
31
- passalong share [file] [--to team[/handle]] publish a guide as a draft → id + link; --to addresses it
31
+ passalong share [file] [--to team[/handle]] publish a guide → id + link, Ready; --to addresses it
32
32
  passalong pull <id|link> print a guide; --save also writes ./.passalong/<id>.md (git-ignored)
33
33
  passalong inbox guides handed to you that you have not pulled yet
34
34
  passalong board waiting on you, in flight, landed, worth keeping
@@ -73,8 +73,8 @@ passalong inbox # on Ada's side: what was handed to you
73
73
  passalong activity # back on your side: it landed, and who pulled it
74
74
  ```
75
75
 
76
- Every guide, whatever its kind, moves the same way: it is a draft until a person marks it Ready
77
- (`passalong ready <id>`), then whoever takes it (`passalong take <id>`) holds it, hands it in with
76
+ Every guide, whatever its kind, moves the same way: it is Ready when shared (or a draft, with
77
+ `status: draft`, until a person runs `passalong ready <id>`), then whoever takes it (`passalong take <id>`) holds it, hands it in with
78
78
  the evidence, and a person approves it or sends it back with the reason (`passalong approve` /
79
79
  `passalong reject`).
80
80
 
package/bin/passalong CHANGED
@@ -6,12 +6,11 @@
6
6
  // handed to whoever pulls or starts that guide
7
7
  // TARGET is a team, team/@handle for one person, or "team/#group" for the people who do a
8
8
  // thing — quote that one, since a shell may read the # as a comment
9
- // passalong task "<what needs doing>" write a task for an agent; it waits in Draft
9
+ // passalong task "<what needs doing>" write a task for an agent; Ready at once
10
10
  // passalong ready <id...|--all> guides you have read are ready — whoever they are for may take them now
11
11
  // passalong tasks every task and where it is: draft, ready, claimed, stalled, review
12
12
  // passalong work [--once] [--agent CMD] run an agent on each ready task here, one after another
13
- // passalong approve <id> the work in review is done, of any kind
14
- // passalong reject <id> <why> not done: back to Ready, and whoever takes it next reads why
13
+ // passalong reopen <id> <why> finished work isn't right: back to To do, and whoever takes it next reads why
15
14
  // passalong release <id> take work back from whoever holds it, of any kind
16
15
  // passalong attach <file> upload a picture or a file and print the markdown that points at it
17
16
  // passalong reply <id> <text> write to whoever holds it (--file PATH to attach); or leave a note
@@ -337,9 +336,8 @@ const commands = {
337
336
  const id = guide.meta.id;
338
337
  console.log(id);
339
338
  const target = guide.meta.target_context;
340
- console.error(`task "${guide.meta.title}" is in Draft${target ? ` for ${target}` : " (no repo)"}`);
339
+ console.error(`task "${guide.meta.title}" is Ready${target ? ` for ${target}` : " (no repo)"}`);
341
340
  if (guide.meta.team) console.error(`handed to ${guide.meta.team} (team inbox)`);
342
- console.error(`when an agent may take it: passalong ready ${id}`);
343
341
  },
344
342
 
345
343
  async tasks() {
@@ -384,16 +382,25 @@ const commands = {
384
382
  if (n) console.error("review them with: passalong tasks");
385
383
  },
386
384
 
387
- async approve(f) {
388
- const id = f._[0] || die("usage: passalong approve <id>");
389
- await passalong.approveTask(id).catch((e) => die(e.message));
390
- console.error(`${id} is done`);
385
+ // Nothing waits for approval now (docs/REVIEW.md): finishing is final. Kept so a script that
386
+ // calls it is told, not refused.
387
+ async approve() {
388
+ console.error(
389
+ "nothing to approve: finished work is already Done. If it isn't right: passalong reopen <id> <why>",
390
+ );
391
+ },
392
+
393
+ /** Finished work back to To do, with why at its top; whoever finished it is told. */
394
+ async reopen(f) {
395
+ const id = f._[0] || die("usage: passalong reopen <id> <why>");
396
+ const why = f._.slice(1).join(" ");
397
+ if (!why) die("say why: passalong reopen <id> <what is wrong>");
398
+ await passalong.reopen(id, why).catch((e) => die(e.message));
399
+ console.error(`${id} is back in To do — whoever takes it next reads why`);
391
400
  },
392
401
 
393
402
  async reject(f) {
394
- const id = f._[0] || die("usage: passalong reject <id> <why>");
395
- await passalong.rejectTask(id, f._.slice(1).join(" ")).catch((e) => die(e.message));
396
- console.error(`${id} is back in Ready — the next agent reads why`);
403
+ return commands.reopen(f);
397
404
  },
398
405
 
399
406
  async release(f) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passalong",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Hand tasks, bugs and finished work to AI coding agents, and get back a write-up with evidence.",
5
5
  "mcpName": "io.github.codellyson/passalong",
6
6
  "keywords": [
package/skill/SKILL.md CHANGED
@@ -67,8 +67,7 @@ machine, another agent session, or a teammate without your branch.
67
67
  follow-up gets the guide it came out of, and where that guide has got to — so write it as a
68
68
  note on that work rather than as a standalone brief. What you did or found while working on a
69
69
  guide belongs in its `hand_in`, not a follow-up.
70
- 6. **Report** the id (and link) and say it is a **draft**: it reaches nobody until the person reads
71
- it and marks it Ready (`passalong ready <id>`, or Ready in the hub). Then one line on how to use
70
+ 6. **Report** the id (and link). It is Ready and has reached whoever it is for. Then one line on how to use
72
71
  it on the other side: `passalong take <id>` in the target repo, or "take passalong <id>" to an
73
72
  agent with the Passalong MCP server. `take` rather than `pull` on purpose: it holds the work as
74
73
  well as fetching it, and until somebody takes it the sender cannot tell a guide nobody noticed
package/src/api.js CHANGED
@@ -429,6 +429,8 @@ export const approveTask = (id) =>
429
429
  call(`/v1/tasks/${encodeURIComponent(id)}/approve`, { method: "POST", body: {} });
430
430
  export const rejectTask = (id, why) =>
431
431
  call(`/v1/tasks/${encodeURIComponent(id)}/reject`, { method: "POST", body: { why } });
432
+ export const reopen = (id, why) =>
433
+ call(`/v1/guides/${encodeURIComponent(id)}/reopen`, { method: "POST", body: { why } });
432
434
  /** A handoff's or bug's approve and send-back. No `place` names the only hand-in there is. */
433
435
  export const closeGuide = (id) =>
434
436
  call(`/v1/guides/${encodeURIComponent(id)}/close`, { method: "POST", body: {} });
package/src/guide.js CHANGED
@@ -497,7 +497,9 @@ export function stamp(guide, defaults = {}) {
497
497
  const meta = { ...guide.meta };
498
498
  if (!meta.id) meta.id = newId();
499
499
  if (!meta.created) meta.created = new Date().toISOString();
500
- if (!meta.status) meta.status = "draft";
500
+ // Ready unless whoever wrote it said draft: a guide sent to someone is meant to reach them, and
501
+ // drafts were where addressed work sat forgotten (every draft in production on 2026-10-15).
502
+ if (!meta.status) meta.status = "published";
501
503
  for (const [k, v] of Object.entries(defaults))
502
504
  if (meta[k] === undefined || meta[k] === "") meta[k] = v;
503
505
  return { meta, body: guide.body };
@@ -525,8 +527,7 @@ export function bugGuide({
525
527
  area = "",
526
528
  report = "",
527
529
  environment = "",
528
- // A draft like every new guide: it reaches people when a person marks it Ready.
529
- status = "draft",
530
+ status = "published",
530
531
  evidence = [],
531
532
  } = {}) {
532
533
  /**
@@ -617,7 +618,7 @@ export function template(meta = {}) {
617
618
  summary: "",
618
619
  author: "",
619
620
  source_context: "",
620
- status: "draft",
621
+ status: "published",
621
622
  stack_assumptions: [],
622
623
  tags: [],
623
624
  ...meta,
@@ -655,7 +656,7 @@ function bugTemplate(meta = {}) {
655
656
  kind: "bug",
656
657
  author: "",
657
658
  source_context: "",
658
- status: "draft",
659
+ status: "published",
659
660
  severity: "s3",
660
661
  stack_assumptions: [],
661
662
  tags: ["bug"],
@@ -697,7 +698,7 @@ function taskTemplate(meta = {}) {
697
698
  author: "",
698
699
  source_context: "",
699
700
  target_context: "",
700
- status: "draft",
701
+ status: "published",
701
702
  stack_assumptions: [],
702
703
  tags: [],
703
704
  ...meta,
package/src/mcp.js CHANGED
@@ -297,15 +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. 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. " +
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. " +
303
304
  `kind: task is work nobody has done yet. Sections: ${TASK_SECTIONS.join(", ")}. ` +
304
305
  "It has no Steps: work out how to reach Goal within Constraints, leave Out of scope " +
305
306
  "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 " +
307
- "transfer guide about what you did (guide_template kind transfer shows the shape) — its " +
308
- "author reviews it against Acceptance.\n" +
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" +
309
310
  `kind: transfer (or no kind: line) is a finished implementation to repeat here. Sections: ${SECTIONS.join(", ")}. ` +
310
311
  "Follow its Steps, adapting anything marked ASSUMES to this codebase; run its " +
311
312
  "Verification, then hand_in with ok and, when it failed, a note saying what went wrong.\n" +
@@ -1084,7 +1085,7 @@ export function buildServer() {
1084
1085
  const ids = await passalong.planTasks(steps, { cwd: cwd || process.cwd() });
1085
1086
  return json({
1086
1087
  drafts: ids.map((id, i) => ({ id, title: steps[i].title, after: steps[i].after || [] })),
1087
- 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\``,
1088
1089
  });
1089
1090
  } catch (err) {
1090
1091
  return fail(err);
@@ -1157,7 +1158,7 @@ export function buildServer() {
1157
1158
  outputSchema: handInOut,
1158
1159
  annotations: ADDS,
1159
1160
  description:
1160
- "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 " +
1161
1162
  "line the guide asks for — `## Acceptance` on a task, `## Verification` on a handoff or a " +
1162
1163
  "bug — each with that line and the evidence for it. That is what its author reads, line " +
1163
1164
  "against line, instead of hunting through a wall of output for the part that answers each " +
@@ -1165,8 +1166,7 @@ export function buildServer() {
1165
1166
  "command proves it: the command is run here before the hand-in lands, a non-zero exit " +
1166
1167
  "refuses it, and what it printed is recorded instead of your account of it. Use `ran` for " +
1167
1168
  "a check no command can settle. `evidence` as one block is the older shape and still " +
1168
- "works. A task also takes `markdown`, a transfer guide about what you did and decided — " +
1169
- "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 " +
1170
1170
  "Verification held here, and `note` saying what went wrong when it did not. DO NOT " +
1171
1171
  "publish a guide to carry your evidence: it belongs on this call, and a second document " +
1172
1172
  'titled "Hand-in evidence" is the thing `checks` exists to replace. If it worked but you ' +
@@ -1242,10 +1242,10 @@ export function buildServer() {
1242
1242
  .string()
1243
1243
  .optional()
1244
1244
  .describe(
1245
- "handoff or bug: what you had to adapt to make it work here — a version, a name, a " +
1246
- "step that needed something the guide does not mention. Prose, and optional: leave " +
1247
- "it out when it worked as written. The next person to open the guide is shown it, " +
1248
- "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",
1249
1249
  ),
1250
1250
  risk: z
1251
1251
  .string()
@@ -1259,11 +1259,11 @@ export function buildServer() {
1259
1259
  markdown: z
1260
1260
  .string()
1261
1261
  .optional()
1262
- .describe("task: the write-up; start from guide_template kind transfer"),
1262
+ .describe("older shape, not needed: say what you adapted in `writeup` instead"),
1263
1263
  report: z
1264
1264
  .string()
1265
1265
  .optional()
1266
- .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"),
1267
1267
  pr: z.string().optional().describe("task: PR or branch link, when there is one"),
1268
1268
  cwd: z
1269
1269
  .string()
@@ -1579,7 +1579,8 @@ export function buildServer() {
1579
1579
  ...(guide.meta.status === "draft"
1580
1580
  ? {
1581
1581
  status: "draft",
1582
- 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}\``,
1583
1584
  }
1584
1585
  : {}),
1585
1586
  });
@@ -1677,11 +1678,7 @@ export function buildServer() {
1677
1678
  { title: title || "", environment: environment || "", issues },
1678
1679
  { cwd: cwd || process.cwd(), to },
1679
1680
  );
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
- });
1681
+ return json(filed);
1685
1682
  } catch (err) {
1686
1683
  return fail(err);
1687
1684
  }
package/src/passalong.js CHANGED
@@ -78,12 +78,6 @@ export async function share(markdown, { cwd = process.cwd(), to, follows } = {})
78
78
  ];
79
79
  if (errors.length)
80
80
  throw new PassalongError(`guide is not ready to share:\n - ${errors.join("\n - ")}`);
81
- // Every guide is a draft when it is first shared, whatever its kind: Draft is where it waits
82
- // until a person says it is ready — `ready()` — and an agent that writes one over MCP comes
83
- // through here too, so publishing on share would let it reach people with nobody reading it.
84
- // Sharing *is* putting it in Draft, so a new guide that names no status says so here; one already
85
- // published keeps what its document says.
86
- if (!guide.meta.status) guide = { ...guide, meta: { ...guide.meta, status: "draft" } };
87
81
 
88
82
  // The server is where a guide lives; nothing is kept on this machine.
89
83
  needsSync("sharing a guide");
@@ -583,6 +577,14 @@ export async function now({ cwd = process.cwd() } = {}) {
583
577
  return { held, waiting, needs: review + handed + waiting.inbox, agent: who.agent };
584
578
  }
585
579
 
580
+ /** Finished work back to To do, with why (docs/REVIEW.md §4). */
581
+ export async function reopen(id, why) {
582
+ needsSync("reopening work");
583
+ if (!String(why || "").trim())
584
+ throw new PassalongError("say why: whoever takes it next reads it");
585
+ return api.reopen(await resolveId(id), why);
586
+ }
587
+
586
588
  /**
587
589
  * The gate, a person's three answers to a task. Approve: the work in review is done. Reject: it
588
590
  * goes back to Ready with `why` on it for the next agent. Release: a task an agent holds, live or
@@ -1046,10 +1048,13 @@ export async function planTasks(steps, { cwd = process.cwd() } = {}) {
1046
1048
  ].join("\n");
1047
1049
  // The summary is the step's own, said by whoever planned it: it is what a person reads about the
1048
1050
  // task, and this builds the document without guessing one (the server refuses a task without).
1051
+ // A plan is the case Draft is for: several tasks at once, which a person reads before any of
1052
+ // them runs. Stated here, by the code that knows it is building one.
1049
1053
  const meta = {
1050
1054
  title: step.title,
1051
1055
  summary: step.summary,
1052
1056
  kind: "task",
1057
+ status: "draft",
1053
1058
  target_context: step.target_context ?? repo,
1054
1059
  };
1055
1060
  const after = (step.after || []).map((j) => ids[j]);