passalong 0.8.0 → 0.9.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/bin/passalong CHANGED
@@ -12,7 +12,7 @@
12
12
  // passalong work [--once] [--agent CMD] run an agent on each ready task here, one after another
13
13
  // passalong approve <id> the work in review is done
14
14
  // passalong reject <id> <why> not done: back to Ready, and the next agent reads why
15
- // passalong release <id> take a task back from the agent holding it, stalled or not
15
+ // passalong release <id> take work back from whoever holds it, of any kind
16
16
  // passalong attach <image> upload evidence and print the markdown that points at it
17
17
  // passalong pull <id|link> fetch a guide into ./.passalong/ and print it
18
18
  // passalong start <id|link> pull it and take it — one command for "I am doing this"
@@ -332,8 +332,12 @@ const commands = {
332
332
 
333
333
  async release(f) {
334
334
  const id = f._[0] || die("usage: passalong release <id>");
335
- await passalong.releaseTask(id).catch((e) => die(e.message));
336
- console.error(`${id} is back in Ready — it says where the last agent left it`);
335
+ const r = await passalong.release(id).catch((e) => die(e.message));
336
+ const from = Number(r?.from) || 1;
337
+ console.error(
338
+ `${id} is back in Ready${from > 1 ? `, taken back from ${from} places` : ""}` +
339
+ " — whoever had it has been told",
340
+ );
337
341
  },
338
342
 
339
343
  async ready(f) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "passalong",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Hand a finished implementation from one context to another, in a form an AI agent can act on.",
5
5
  "keywords": [
6
6
  "mcp",
package/src/api.js CHANGED
@@ -176,8 +176,11 @@ export const setStatus = (id, status) =>
176
176
  call(`/v1/guides/${id}/status`, { method: "PATCH", body: { status } });
177
177
  export const remove = (id) => call(`/v1/guides/${id}`, { method: "DELETE" });
178
178
  /** Does it actually work? `note` is required when it does not. */
179
- export const verdict = (id, ok, note = "") =>
180
- call(`/v1/guides/${id}/verdict`, { method: "PUT", body: { ok, note } });
179
+ export const verdict = (id, ok, note = "", detail = "") =>
180
+ call(`/v1/guides/${id}/verdict`, {
181
+ method: "PUT",
182
+ body: { ok, note, ...(detail ? { detail } : {}) },
183
+ });
181
184
 
182
185
  export const tasks = () => call("/v1/tasks");
183
186
  export const nextTask = (body) => call("/v1/tasks/next", { method: "POST", body });
@@ -189,8 +192,9 @@ export const approveTask = (id) =>
189
192
  call(`/v1/tasks/${encodeURIComponent(id)}/approve`, { method: "POST", body: {} });
190
193
  export const rejectTask = (id, why) =>
191
194
  call(`/v1/tasks/${encodeURIComponent(id)}/reject`, { method: "POST", body: { why } });
192
- export const releaseTask = (id) =>
193
- call(`/v1/tasks/${encodeURIComponent(id)}/release`, { method: "POST", body: {} });
195
+ /** Take work back from whoever holds it, of any kind. `/v1/tasks/…/release` is the old path. */
196
+ export const release = (id) =>
197
+ call(`/v1/guides/${encodeURIComponent(id)}/release`, { method: "POST", body: {} });
194
198
  /** The four verbs every guide answers to. See docs/V2.md §11. */
195
199
  export const working = () => call("/v1/working");
196
200
  export const handedIn = () => call("/v1/handed_in");
package/src/mcp.js CHANGED
@@ -326,7 +326,7 @@ export function buildServer() {
326
326
  }
327
327
  }
328
328
 
329
- async function doHandIn({ id, ok, note, evidence, checks, markdown, report, pr, cwd }) {
329
+ async function doHandIn({ id, ok, note, evidence, checks, writeup, markdown, report, pr, cwd }) {
330
330
  try {
331
331
  // A check that names a command is run here, before anything is recorded, and its exit code
332
332
  // decides it rather than the agent's account of it. A failure is refused with the command's
@@ -346,6 +346,7 @@ export function buildServer() {
346
346
  note: note || "",
347
347
  evidence: evidence || "",
348
348
  checks: done.checks,
349
+ writeup: writeup || "",
349
350
  markdown,
350
351
  report,
351
352
  pr: pr || "",
@@ -640,18 +641,22 @@ export function buildServer() {
640
641
  outputSchema: handInOut,
641
642
  annotations: ADDS,
642
643
  description:
643
- "Done here, with proof. Every hand-in carries what you ran and what came back. On a TASK " +
644
- "send `checks`: one entry per Acceptance line, each with that line and the evidence for " +
645
- "it — that is what its author reads, line against line, instead of hunting through a wall " +
646
- "of output for the part that answers each one. Give a check `cmd` when a command proves " +
647
- "it: the command is run here before the hand-in lands, a non-zero exit refuses it, and " +
648
- "what it printed is recorded instead of your account of it. Use `ran` for a check no " +
649
- "command can settle. `evidence` is the same thing as one block, " +
650
- "for a handoff or a bug, which have no Acceptance lines. A task also takes `markdown`, a " +
651
- "transfer guide about what you did and decided — this publishes it and attaches it. A " +
652
- "handoff or a bug takes `ok`, whether its Verification held here, and `note` saying what " +
653
- "went wrong when it did not. More context than a note holds goes in a follow-up: " +
654
- "publish_guide with `parent` set to this id.",
644
+ "Done here, with proof, sorted against the line it answers. Send `checks`: one entry per " +
645
+ "line the guide asks for — `## Acceptance` on a task, `## Verification` on a handoff or a " +
646
+ "bug — each with that line and the evidence for it. That is what its author reads, line " +
647
+ "against line, instead of hunting through a wall of output for the part that answers each " +
648
+ "one, AND IT IS HOW YOU KNOW YOU ARE DONE: every line has one. Give a check `cmd` when a " +
649
+ "command proves it: the command is run here before the hand-in lands, a non-zero exit " +
650
+ "refuses it, and what it printed is recorded instead of your account of it. Use `ran` for " +
651
+ "a check no command can settle. `evidence` as one block is the older shape and still " +
652
+ "works. A task also takes `markdown`, a transfer guide about what you did and decided — " +
653
+ "this publishes it and attaches it. A handoff or a bug takes `ok`, whether its " +
654
+ "Verification held here, and `note` saying what went wrong when it did not. DO NOT " +
655
+ "publish a guide to carry your evidence: it belongs on this call, and a second document " +
656
+ 'titled "Hand-in evidence" is the thing `checks` exists to replace. If it worked but you ' +
657
+ "had to adapt something, that goes in `writeup` and the next reader of the guide is shown " +
658
+ "it — that is its home. A follow-up guide is for work somebody else should now do, not " +
659
+ "for answering.",
655
660
  inputSchema: {
656
661
  id: z.string().describe("passalong id"),
657
662
  evidence: z
@@ -664,7 +669,12 @@ export function buildServer() {
664
669
  checks: z
665
670
  .array(
666
671
  z.object({
667
- check: z.string().describe("the Acceptance line this answers, in the task's words"),
672
+ check: z
673
+ .string()
674
+ .describe(
675
+ "the line this answers, in the guide's own words: an Acceptance line on a task, " +
676
+ "a Verification line on a handoff or a bug",
677
+ ),
668
678
  ran: z
669
679
  .string()
670
680
  .optional()
@@ -681,9 +691,21 @@ export function buildServer() {
681
691
  }),
682
692
  )
683
693
  .optional()
684
- .describe("task: one entry per Acceptance line, in the order you worked them"),
694
+ .describe(
695
+ "one entry per line the guide asks for — Acceptance on a task, Verification on a " +
696
+ "handoff or a bug — in the order you worked them",
697
+ ),
685
698
  ok: z.boolean().optional().describe("handoff or bug: did its Verification hold"),
686
699
  note: z.string().optional().describe("one line; required when ok is false"),
700
+ writeup: z
701
+ .string()
702
+ .optional()
703
+ .describe(
704
+ "handoff or bug: what you had to adapt to make it work here — a version, a name, a " +
705
+ "step that needed something the guide does not mention. Prose, and optional: leave " +
706
+ "it out when it worked as written. The next person to open the guide is shown it, " +
707
+ "which is why it does not need to be a guide of its own",
708
+ ),
687
709
  markdown: z
688
710
  .string()
689
711
  .optional()
package/src/passalong.js CHANGED
@@ -412,6 +412,7 @@ export async function handIn(
412
412
  note = "",
413
413
  evidence = "",
414
414
  checks = [],
415
+ writeup = "",
415
416
  markdown,
416
417
  report,
417
418
  pr = "",
@@ -446,6 +447,9 @@ export async function handIn(
446
447
  }
447
448
  : {}),
448
449
  ...(report ? { report, pr } : {}),
450
+ // Left out when there is nothing to say, rather than sent as "": most hand-ins have nothing
451
+ // to adapt, and the field is for the ones that do.
452
+ ...(writeup ? { writeup } : {}),
449
453
  ...(typeof ok === "boolean" ? { ok } : {}),
450
454
  });
451
455
  }
@@ -539,9 +543,16 @@ export async function rejectTask(id, why) {
539
543
  return r;
540
544
  }
541
545
 
542
- export async function releaseTask(id) {
543
- needsSync("releasing a task");
544
- const r = await api.releaseTask(id);
546
+ /**
547
+ * Take it back from whoever is holding it — a task, a handoff or a bug, and from all of them at
548
+ * once when several people hold the same handoff in different repos.
549
+ *
550
+ * It was tasks only, which left an asymmetry nobody chose: the author of a handoff could mark it
551
+ * done or give it to somebody else, and had no way to simply have it back.
552
+ */
553
+ export async function release(id) {
554
+ needsSync("taking work back");
555
+ const r = await api.release(id);
545
556
  store.remove(id);
546
557
  return r;
547
558
  }
@@ -808,11 +819,21 @@ export async function activity({ all = false, limit = 50 } = {}) {
808
819
  * `done` says "I implemented it", a verdict says "I tried it and it holds up" — or does not.
809
820
  * A failing verdict needs a note; "it doesn't work" without a reason helps nobody.
810
821
  */
822
+ /**
823
+ * Say whether a guide held, and why at the length that takes.
824
+ *
825
+ * The reason used to be one field, capped at 280 characters by the row it was written for — so an
826
+ * agent that had run the Verification and found it did not hold published a guide called
827
+ * "Correction: …" instead, because measurements, commands and commit ids do not fit in a tweet.
828
+ * The first line is the summary a row shows; the whole thing is what the guide shows.
829
+ */
811
830
  export async function verdict(id, ok, note = "") {
812
831
  if (!api.loggedIn()) throw new PassalongError("verdicts need sync — run `passalong login` first");
813
- if (!ok && !note.trim())
814
- throw new PassalongError("say what went wrong: passalong failed <id> <what happened>");
815
- return api.verdict(await resolveId(id), ok, note.trim());
832
+ const said = note.trim();
833
+ if (!ok && !said)
834
+ throw new PassalongError("say what went wrong: passalong broken <id> <what happened>");
835
+ const line = said.split("\n")[0].slice(0, 280);
836
+ return api.verdict(await resolveId(id), ok, line, said.length > line.length ? said : "");
816
837
  }
817
838
 
818
839
  /**