passalong 0.3.0 → 0.3.2
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 +1 -0
- package/bin/passalong +22 -17
- package/package.json +1 -1
- package/skill/SKILL.md +7 -0
- package/src/guide.js +67 -7
- package/src/mcp.js +50 -11
- package/src/passalong.js +33 -2
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ passalong team join <link> accept an invite
|
|
|
48
48
|
passalong team use <slug> switch the current team
|
|
49
49
|
passalong setup install the Claude Code capture skill + register the MCP server
|
|
50
50
|
passalong mcp run the MCP server over stdio
|
|
51
|
+
passalong version print the installed version (also -v, --version)
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## Teams
|
package/bin/passalong
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Passalong CLI. Few flags on purpose: the defaults are the product.
|
|
3
3
|
//
|
|
4
|
-
// passalong share [file] [--to TARGET] publish a guide → id + link
|
|
4
|
+
// passalong share [file] [--to TARGET] [--follows ID] publish a guide → id + link
|
|
5
5
|
// TARGET is a team, team/@handle for one person, or "team/#group" for the people who do a
|
|
6
6
|
// thing — quote that one, since a shell may read the # as a comment
|
|
7
7
|
// passalong attach <image> upload evidence and print the markdown that points at it
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
// passalong works <id> you tried it and it holds up
|
|
20
20
|
// passalong broken <id> <why> you tried it and it does not — the author is told, with your reason
|
|
21
21
|
// passalong done <id> archive it: off your board and out of the free tier's count
|
|
22
|
-
// passalong promote <id> deprecated: mark promoted
|
|
23
22
|
// passalong rm <id> delete a guide locally and from sync
|
|
24
23
|
// passalong export [dir] dump every guide as plain markdown (default ./passalong-export)
|
|
25
24
|
// passalong login [token] create an account, or attach this machine with a token from your hub
|
|
@@ -31,6 +30,7 @@
|
|
|
31
30
|
// passalong team use <slug> switch the current team
|
|
32
31
|
// passalong setup install the Claude Code capture skill + register the MCP server
|
|
33
32
|
// passalong mcp run the MCP server over stdio
|
|
33
|
+
// passalong version print the installed version (also -v, --version)
|
|
34
34
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
35
35
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
36
36
|
import { homedir } from "node:os";
|
|
@@ -93,8 +93,7 @@ function guideLine(r) {
|
|
|
93
93
|
const p = r.pulled_by[0];
|
|
94
94
|
pulled = ` ← ${p.handle ? `@${p.handle}` : "link"} ${relDate(p.at)}${r.pulled_by.length > 1 ? ` +${r.pulled_by.length - 1}` : ""}`;
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
return `${r.id} ${r.status.padEnd(9)} ${relDate(r.created).padEnd(10)} ${r.title}${who}${tags}${pulled}${nudge}`;
|
|
96
|
+
return `${r.id} ${r.status.padEnd(9)} ${relDate(r.created).padEnd(10)} ${r.title}${who}${tags}${pulled}`;
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
function activityLine(n) {
|
|
@@ -127,12 +126,14 @@ const commands = {
|
|
|
127
126
|
if (!existsSync(path)) die(`no such file: ${path}`);
|
|
128
127
|
if (tty && !f["no-edit"]) edit(path);
|
|
129
128
|
const to = typeof f.to === "string" ? f.to : undefined;
|
|
129
|
+
const follows = typeof f.follows === "string" ? f.follows : undefined;
|
|
130
130
|
const { guide, url, synced, notified, path: saved } = await passalong
|
|
131
|
-
.share(readFileSync(path, "utf8"), { to })
|
|
131
|
+
.share(readFileSync(path, "utf8"), { to, follows })
|
|
132
132
|
.catch((e) => die(e.message));
|
|
133
133
|
if (fromDrafts) store.removeDraft(path);
|
|
134
134
|
console.log(guide.meta.id);
|
|
135
135
|
console.error(`shared "${guide.meta.title}" → ${saved}`);
|
|
136
|
+
if (guide.meta.parent) console.error(`follows ${guide.meta.parent}`);
|
|
136
137
|
if (guide.meta.team)
|
|
137
138
|
console.error(
|
|
138
139
|
`handed to ${guide.meta.team}${guide.meta.to ? `/@${guide.meta.to}${notified ? " (emailed)" : " (in their inbox)"}` : " (team inbox)"}`,
|
|
@@ -334,16 +335,6 @@ const commands = {
|
|
|
334
335
|
console.error(`${g.meta.id} archived — off your board, and it no longer counts against sync`);
|
|
335
336
|
},
|
|
336
337
|
|
|
337
|
-
// Still on the way out: it sets an author lifecycle nothing reads, and the board reports pull
|
|
338
|
-
// counts on its own. It stays, and warns, because removing a command from a published package
|
|
339
|
-
// is a breaking change.
|
|
340
|
-
async promote(f) {
|
|
341
|
-
const id = f._[0] || die("usage: passalong promote <id>");
|
|
342
|
-
const g = await passalong.setStatus(id, "promoted").catch((e) => die(e.message));
|
|
343
|
-
console.error(`${g.meta.id} marked promoted`);
|
|
344
|
-
console.error("note: `promote` is deprecated — the board reports pull counts on their own");
|
|
345
|
-
},
|
|
346
|
-
|
|
347
338
|
async rm(f) {
|
|
348
339
|
const id = f._[0] || die("usage: passalong rm <id>");
|
|
349
340
|
await passalong.remove(id).catch((e) => die(e.message));
|
|
@@ -392,7 +383,15 @@ const commands = {
|
|
|
392
383
|
console.log(`handle ${who.handle ? `@${who.handle}` : "(none — set one: passalong me --handle <you>)"}`);
|
|
393
384
|
if (who.name) console.log(`name ${who.name}`);
|
|
394
385
|
if (who.email) console.log(`email ${who.email}`);
|
|
395
|
-
|
|
386
|
+
// The server sends the state by name, because a number cannot tell "no ceiling" apart from
|
|
387
|
+
// "may sync nothing" — both of which arrive as a falsy limit.
|
|
388
|
+
const ceiling =
|
|
389
|
+
who.sync === "free"
|
|
390
|
+
? ` (${who.limit} active on your plan)`
|
|
391
|
+
: who.sync === "none"
|
|
392
|
+
? " (no plan — nothing is syncing)"
|
|
393
|
+
: " (no limit on your plan)";
|
|
394
|
+
console.log(`guides ${who.guides} synced${ceiling}`);
|
|
396
395
|
const current = store.readConfig().team;
|
|
397
396
|
console.log(
|
|
398
397
|
`teams ${who.teams.length ? who.teams.map((t) => `${t.slug}${t.slug === current ? "*" : ""} (${t.role})`).join(", ") : "(none — passalong team create <name>)"}`,
|
|
@@ -492,5 +491,11 @@ const commands = {
|
|
|
492
491
|
|
|
493
492
|
const argv = flags(process.argv.slice(2));
|
|
494
493
|
const cmd = argv._.shift() || "help";
|
|
495
|
-
|
|
494
|
+
// The first thing anyone runs when something is off, and until now it printed the help page. The
|
|
495
|
+
// parser only knows `--x`, so `--version` arrives as a flag on an empty command and `-v` as a word;
|
|
496
|
+
// both are caught here, with `version`, before dispatch. Only the number goes to stdout, so
|
|
497
|
+
// `passalong -v` can be read by a script comparing it against the registry.
|
|
498
|
+
if (cmd === "-v" || cmd === "version" || (cmd === "help" && argv.version === true)) {
|
|
499
|
+
console.log(JSON.parse(readFileSync(join(PKG, "package.json"), "utf8")).version);
|
|
500
|
+
} else if (cmd === "--help" || cmd === "-h" || !commands[cmd]) commands.help();
|
|
496
501
|
else await commands[cmd](argv).catch((e) => die(e.message));
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -33,6 +33,13 @@ session's history. Write for execution, not for permanence.
|
|
|
33
33
|
team"), add `--to <team>` or `--to <team>/<handle>`; `passalong team` lists the current
|
|
34
34
|
team's members and their handles. It prints the guide id on stdout and the share link (and
|
|
35
35
|
who it was handed to) on stderr.
|
|
36
|
+
|
|
37
|
+
**If this session started from a passalong guide** — you ran `passalong start` or `pull` on an
|
|
38
|
+
id, or an agent opened one — and what you are capturing is what doing it taught you, add
|
|
39
|
+
`--follows <that id>`. The original then lists this one as a follow-up. That is the case when
|
|
40
|
+
you departed from it: changed or skipped a Step, adapted an `ASSUMES` for this stack, found the
|
|
41
|
+
fix a failing guide did not have, or hit a Gotcha it does not list. If it worked exactly as
|
|
42
|
+
written there is nothing to capture — say so with `passalong works <id>` instead.
|
|
36
43
|
6. **Report** the id (and link if synced) and one line on how to use it on the other side:
|
|
37
44
|
`passalong start <id>` in the target repo, or "start passalong <id>" to an agent with the
|
|
38
45
|
Passalong MCP server. `start` rather than `pull` on purpose: it takes the handoff as well as
|
package/src/guide.js
CHANGED
|
@@ -5,16 +5,30 @@
|
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* A guide is a draft or it is published
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* A guide is a draft or it is published, and `consumed` is the author's shelf — off the board, out
|
|
9
|
+
* of the free tier's count, reversible. `promoted` is the one true legacy: an author lifecycle laid
|
|
10
|
+
* over a transfer that already reports itself, since a pull count says how travelled a guide is
|
|
11
|
+
* without anyone having to grade it.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* This list is what is **accepted**, and it is not what may be **set** — that is `SETTABLE` below.
|
|
14
|
+
* The distinction is load-bearing. A status lives in frontmatter, inside markdown files in other
|
|
15
|
+
* people's repositories, and `validate()` rejects a status it does not know, so dropping a value
|
|
16
|
+
* from here would make a guide shared a month ago fail to re-share today.
|
|
15
17
|
*/
|
|
16
18
|
export const STATUSES = ["draft", "published", "consumed", "promoted"];
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* What a write may choose. `promoted` is absent: it cannot be set on a guide that does not already
|
|
22
|
+
* carry it, from any surface.
|
|
23
|
+
*
|
|
24
|
+
* Two reasons, and the second is the sharper one. A status that can still be set reads as a
|
|
25
|
+
* feature to whoever meets it next, however many deprecation notices sit around it. And
|
|
26
|
+
* `quota.ts` counts `published` and `promoted` as the statuses that occupy room — so while
|
|
27
|
+
* `promoted` remains settable, promoting every guide is an unlimited free tier. Closing the write
|
|
28
|
+
* closes that, and does it without touching what the free tier counts, which still has to include
|
|
29
|
+
* the guides already carrying the status.
|
|
30
|
+
*/
|
|
31
|
+
export const SETTABLE = ["draft", "published", "consumed"];
|
|
18
32
|
/**
|
|
19
33
|
* What a guide is for. The receiving agent behaves completely differently depending on the
|
|
20
34
|
* answer, so it is a field rather than something inferred from the body.
|
|
@@ -291,6 +305,24 @@ export function sections(body) {
|
|
|
291
305
|
return out;
|
|
292
306
|
}
|
|
293
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Image targets no reader will ever load. Mirrors `unreachableImages` in apps/api/src/guide.ts,
|
|
310
|
+
* which is the authority and refuses the publish; this lets a local share fail before the network.
|
|
311
|
+
*
|
|
312
|
+
* Only a foreign scheme counts — `attachment://`, `blob:`, `file:`, `data:`. `https:` is left alone
|
|
313
|
+
* because embedding an image you host is legitimate, and a relative path is left alone because it
|
|
314
|
+
* has always been accepted.
|
|
315
|
+
*/
|
|
316
|
+
export function unreachableImages(markdown) {
|
|
317
|
+
const bad = [...String(markdown).matchAll(/!\[[^\]]*\]\(\s*([^)\s]+)/g)]
|
|
318
|
+
.map((m) => m[1].trim())
|
|
319
|
+
.filter((target) => {
|
|
320
|
+
const scheme = /^([a-z][a-z0-9+.\-]*):/i.exec(target);
|
|
321
|
+
return Boolean(scheme) && !/^https?$/i.test(scheme[1]);
|
|
322
|
+
});
|
|
323
|
+
return [...new Set(bad)];
|
|
324
|
+
}
|
|
325
|
+
|
|
294
326
|
/** Problems that make a guide unfit to publish. Empty array means it is fine. */
|
|
295
327
|
export function validate({ meta, body }) {
|
|
296
328
|
const errors = [];
|
|
@@ -321,6 +353,12 @@ export function validate({ meta, body }) {
|
|
|
321
353
|
errors.push('a bug uses "## Reproduce", not "## Steps" — an agent executes Steps');
|
|
322
354
|
}
|
|
323
355
|
if (body.includes("<!-- passalong:")) errors.push("template placeholders are still in the body");
|
|
356
|
+
// An agent that has a file id but never called attach_screenshot writes the id as if it were a
|
|
357
|
+
// link. It publishes, and stores a guide whose screenshot was never uploaded anywhere.
|
|
358
|
+
for (const target of unreachableImages(body))
|
|
359
|
+
errors.push(
|
|
360
|
+
`${target} is not a URL a reader can load — attach the image first and use the URL it returns`,
|
|
361
|
+
);
|
|
324
362
|
return errors;
|
|
325
363
|
}
|
|
326
364
|
|
|
@@ -354,7 +392,26 @@ export function bugGuide({
|
|
|
354
392
|
report = "",
|
|
355
393
|
environment = "",
|
|
356
394
|
status = "published",
|
|
395
|
+
evidence = [],
|
|
357
396
|
} = {}) {
|
|
397
|
+
/**
|
|
398
|
+
* Screenshots, as markdown inside the document.
|
|
399
|
+
*
|
|
400
|
+
* They go under Problem rather than a heading of their own, because for a visual defect the
|
|
401
|
+
* picture *is* the problem statement and a bug's sections are a fixed set — a seventh heading
|
|
402
|
+
* would change the shape every reader keys on to add a line that already belongs in the first
|
|
403
|
+
* one.
|
|
404
|
+
*
|
|
405
|
+
* Naming them in the body is also the only thing that binds them: `claimShots` claims whatever
|
|
406
|
+
* the markdown points at, scoped to the author, so a URL listed anywhere else would upload
|
|
407
|
+
* evidence that no guide owns and the nightly sweep deletes.
|
|
408
|
+
*/
|
|
409
|
+
const shots = (Array.isArray(evidence) ? evidence : [evidence])
|
|
410
|
+
.map((s) => String(s ?? "").trim())
|
|
411
|
+
.filter(Boolean)
|
|
412
|
+
// A caller that already has the markdown line from `attach_screenshot` passes it through
|
|
413
|
+
// whole; one holding just the URL gets it wrapped. Both reach the same document.
|
|
414
|
+
.map((s) => (s.startsWith("`));
|
|
358
415
|
const body = [
|
|
359
416
|
// Written into the document, not added by whatever served it.
|
|
360
417
|
//
|
|
@@ -367,10 +424,13 @@ export function bugGuide({
|
|
|
367
424
|
"",
|
|
368
425
|
"## Problem",
|
|
369
426
|
problem.trim() || "_No description given._",
|
|
427
|
+
];
|
|
428
|
+
if (shots.length) body.push("", ...shots);
|
|
429
|
+
body.push(
|
|
370
430
|
"",
|
|
371
431
|
"## Reproduce",
|
|
372
432
|
reproduce.trim() || "_Not recorded — the description above is what there is._",
|
|
373
|
-
|
|
433
|
+
);
|
|
374
434
|
// Both are optional and both are worse than absent when empty: a Verification heading with
|
|
375
435
|
// nothing under it says nobody knows what fixed looks like, on the section a fixer reads first.
|
|
376
436
|
if (verification.trim()) body.push("", "## Verification", verification.trim());
|
package/src/mcp.js
CHANGED
|
@@ -81,6 +81,11 @@ export async function serve() {
|
|
|
81
81
|
"because you reproduced it. " +
|
|
82
82
|
"When you find defects you are not fixing — a test run, a QA pass, a review — call " +
|
|
83
83
|
"file_bugs with all of them at once; each becomes a guide someone can take on its own. " +
|
|
84
|
+
"AN IMAGE THE USER SHOWED YOU IS EVIDENCE, NOT CONTEXT. Before filing or publishing, " +
|
|
85
|
+
"attach it with attach_screenshot and pass what it returns as `evidence` — a screenshot " +
|
|
86
|
+
"you described instead of attaching is the most useful thing in the report, thrown away. " +
|
|
87
|
+
"A guide already filed without one is not stuck: get_guide it, add the markdown line to " +
|
|
88
|
+
"the body, and publish_guide the same id — publishing claims whatever the markdown names. " +
|
|
84
89
|
"When the user asks to pass along, hand off, or " +
|
|
85
90
|
"share what was just done, distill the session into a guide (guide_template shows the " +
|
|
86
91
|
"shape) and call publish_guide, with `to` as team, team/@handle for one teammate, or " +
|
|
@@ -90,7 +95,13 @@ export async function serve() {
|
|
|
90
95
|
"shows whether the guides they handed off have landed. When the user asks what they have " +
|
|
91
96
|
"been working on, or wants a standup or a summary of a period, call log — but say that it " +
|
|
92
97
|
"holds what they passed along and not everything they did. Gotchas are " +
|
|
93
|
-
"the highest-value section: record what failed and why
|
|
98
|
+
"the highest-value section: record what failed and why.\n" +
|
|
99
|
+
"A FOLLOW-UP IS A GUIDE, NOT A NOTE. When you worked from a guide and departed from it — " +
|
|
100
|
+
"changed or skipped a Step, adapted an ASSUMES for this stack, found the fix a failing " +
|
|
101
|
+
"guide did not have, or hit a Gotcha it does not list — publish what you learned as its " +
|
|
102
|
+
"own guide with publish_guide `parent` set to that guide's id; the original then lists it " +
|
|
103
|
+
"as a follow-up. If it worked exactly as written, do not: answer with verify_guide, or " +
|
|
104
|
+
"every guide collects copies that say nothing new.",
|
|
94
105
|
},
|
|
95
106
|
);
|
|
96
107
|
|
|
@@ -218,7 +229,10 @@ export async function serve() {
|
|
|
218
229
|
description:
|
|
219
230
|
"After following a guide's Verification section, report the result. This is the only way " +
|
|
220
231
|
"the author learns their handoff did not land — `set_guide_status consumed` says it was " +
|
|
221
|
-
"implemented, this says it actually works. A failing verdict must say what went wrong."
|
|
232
|
+
"implemented, this says it actually works. A failing verdict must say what went wrong. " +
|
|
233
|
+
"If doing it taught you something the guide did not say — a flag it needed, a step that " +
|
|
234
|
+
"was wrong for this stack — publish that as its own guide with publish_guide `parent` set " +
|
|
235
|
+
"to this id. The author sees it as a follow-up; a verdict note is one line and gets lost.",
|
|
222
236
|
inputSchema: {
|
|
223
237
|
id: z.string().describe("passalong id"),
|
|
224
238
|
ok: z.boolean().describe("true if the Verification steps passed"),
|
|
@@ -302,7 +316,8 @@ export async function serve() {
|
|
|
302
316
|
const siblings = await related(meta);
|
|
303
317
|
return text(
|
|
304
318
|
`${lead}${r.markdown}${siblings}` +
|
|
305
|
-
`\n\n<!-- passalong: ${r.from}; written to ${r.path}; ${took}
|
|
319
|
+
`\n\n<!-- passalong: ${r.from}; written to ${r.path}; ${took} -->` +
|
|
320
|
+
`\n${passalong.followUpNote(meta)}`,
|
|
306
321
|
);
|
|
307
322
|
} catch (err) {
|
|
308
323
|
return fail(err);
|
|
@@ -351,7 +366,8 @@ export async function serve() {
|
|
|
351
366
|
return text(
|
|
352
367
|
`${lead}${markdown}${siblings}` +
|
|
353
368
|
`\n\n<!-- passalong: ${from}; written to ${path} -->` +
|
|
354
|
-
`\n${passalong.handoffNudge(meta)}
|
|
369
|
+
`\n${passalong.handoffNudge(meta)}` +
|
|
370
|
+
`\n${passalong.followUpNote(meta)}`,
|
|
355
371
|
);
|
|
356
372
|
} catch (err) {
|
|
357
373
|
return fail(err);
|
|
@@ -394,7 +410,10 @@ export async function serve() {
|
|
|
394
410
|
description:
|
|
395
411
|
"Publish a guide from markdown (frontmatter + sections) — a transfer guide by default, or " +
|
|
396
412
|
"a single bug with `kind: bug`; use file_bugs for more than one. Missing id, created, " +
|
|
397
|
-
|
|
413
|
+
"author, and source_context are filled in. A screenshot belongs in the markdown: attach it " +
|
|
414
|
+
"with attach_screenshot and put the line it returns in the body, because publishing claims " +
|
|
415
|
+
"whatever the markdown names. " +
|
|
416
|
+
'`to` addresses it to a team ("khaime") or a ' +
|
|
398
417
|
'teammate ("khaime/lukman"), who is notified. Returns the id and share link.',
|
|
399
418
|
inputSchema: {
|
|
400
419
|
markdown: z.string().describe("full guide markdown; start from guide_template"),
|
|
@@ -406,13 +425,21 @@ export async function serve() {
|
|
|
406
425
|
.string()
|
|
407
426
|
.optional()
|
|
408
427
|
.describe("directory the work happened in, used to infer source_context"),
|
|
428
|
+
parent: z
|
|
429
|
+
.string()
|
|
430
|
+
.optional()
|
|
431
|
+
.describe(
|
|
432
|
+
"id or share link of the guide this one came out of — set it when this is what you " +
|
|
433
|
+
"learned doing someone else's guide, so theirs lists it as a follow-up",
|
|
434
|
+
),
|
|
409
435
|
},
|
|
410
436
|
},
|
|
411
|
-
async ({ markdown, to, cwd }) => {
|
|
437
|
+
async ({ markdown, to, cwd, parent }) => {
|
|
412
438
|
try {
|
|
413
439
|
const { guide, url, synced, notified, path } = await passalong.share(markdown, {
|
|
414
440
|
cwd: cwd || process.cwd(),
|
|
415
441
|
to,
|
|
442
|
+
follows: parent,
|
|
416
443
|
});
|
|
417
444
|
return json({
|
|
418
445
|
id: guide.meta.id,
|
|
@@ -421,6 +448,7 @@ export async function serve() {
|
|
|
421
448
|
synced,
|
|
422
449
|
team: guide.meta.team || "",
|
|
423
450
|
to: guide.meta.to || "",
|
|
451
|
+
parent: guide.meta.parent || "",
|
|
424
452
|
notified,
|
|
425
453
|
path,
|
|
426
454
|
});
|
|
@@ -439,7 +467,10 @@ export async function serve() {
|
|
|
439
467
|
"publishes each issue as its own guide — its own id, share link, and verdict — so a " +
|
|
440
468
|
"reviewer can hand any one of them to whoever fixes it. Use this after a test run, a QA " +
|
|
441
469
|
"pass, or a review that turned up defects; use publish_guide instead for work you " +
|
|
442
|
-
"finished and want repeated elsewhere.
|
|
470
|
+
"finished and want repeated elsewhere. If there is a screenshot of any of this, it is " +
|
|
471
|
+
"evidence: call attach_screenshot first and pass what it returns as that issue's " +
|
|
472
|
+
"`evidence`. Describing a screenshot instead of attaching it throws away the most useful " +
|
|
473
|
+
"thing in the report. Needs sync (`passalong login`).",
|
|
443
474
|
inputSchema: {
|
|
444
475
|
title: z
|
|
445
476
|
.string()
|
|
@@ -456,6 +487,13 @@ export async function serve() {
|
|
|
456
487
|
issues: z
|
|
457
488
|
.array(
|
|
458
489
|
z.object({
|
|
490
|
+
evidence: z
|
|
491
|
+
.array(z.string())
|
|
492
|
+
.default([])
|
|
493
|
+
.describe(
|
|
494
|
+
"screenshot URLs from attach_screenshot, or the markdown lines it returned; " +
|
|
495
|
+
"they go under Problem, where a reader looks first",
|
|
496
|
+
),
|
|
459
497
|
title: z.string().describe("what is broken, in one line"),
|
|
460
498
|
problem: z
|
|
461
499
|
.string()
|
|
@@ -527,10 +565,11 @@ export async function serve() {
|
|
|
527
565
|
{
|
|
528
566
|
title: "Set guide status",
|
|
529
567
|
description:
|
|
530
|
-
"
|
|
531
|
-
"
|
|
532
|
-
"
|
|
533
|
-
|
|
568
|
+
"Archive a guide (`consumed`) or put it back on the board (`published`). Archiving is the " +
|
|
569
|
+
"author's shelf: off the board, out of the free tier's count, reversible — it is not a " +
|
|
570
|
+
"judgement that the work landed, which is what verify_guide reports. `promoted` was " +
|
|
571
|
+
"retired and the server refuses it.",
|
|
572
|
+
inputSchema: { id: z.string(), status: z.enum(["published", "consumed"]) },
|
|
534
573
|
},
|
|
535
574
|
async ({ id, status }) => {
|
|
536
575
|
try {
|
package/src/passalong.js
CHANGED
|
@@ -37,7 +37,7 @@ export function parseTarget(to) {
|
|
|
37
37
|
* logged in. `to` addresses it: "team", "team/@handle", or "team/#group". Returns
|
|
38
38
|
* { guide, path, url, synced }.
|
|
39
39
|
*/
|
|
40
|
-
export async function share(markdown, { cwd = process.cwd(), to } = {}) {
|
|
40
|
+
export async function share(markdown, { cwd = process.cwd(), to, follows } = {}) {
|
|
41
41
|
const c = context(cwd);
|
|
42
42
|
const raw = parse(markdown);
|
|
43
43
|
let guide = stamp(
|
|
@@ -50,6 +50,10 @@ export async function share(markdown, { cwd = process.cwd(), to } = {}) {
|
|
|
50
50
|
const target = parseTarget(to);
|
|
51
51
|
if (target.team) guide.meta.team = target.team;
|
|
52
52
|
if (target.to) guide.meta.to = target.to;
|
|
53
|
+
// `follows` names the guide this one came out of: an id, a share link, or a pulled .md file. An id
|
|
54
|
+
// is used as it is; a link or a file is read for the id in its own frontmatter. `validate()` below
|
|
55
|
+
// is what refuses a guide that names itself.
|
|
56
|
+
if (follows) guide.meta.parent = await resolveId(String(follows).trim());
|
|
53
57
|
if (guide.meta.to && !guide.meta.team)
|
|
54
58
|
throw new PassalongError("`to:` needs a team — address a handoff as team/handle");
|
|
55
59
|
const errors = validate(guide);
|
|
@@ -292,6 +296,33 @@ export async function attach(file, { name = "" } = {}) {
|
|
|
292
296
|
return { ...shot, markdown: `` };
|
|
293
297
|
}
|
|
294
298
|
|
|
299
|
+
/**
|
|
300
|
+
* The line a guide arrives with, saying when what you do next deserves to be a guide of its own.
|
|
301
|
+
*
|
|
302
|
+
* Trailing, like the handoff nudge, so it never sits in front of the frontmatter. It is here and
|
|
303
|
+
* not only in the server's instructions because an agent reads the payload it is working from and
|
|
304
|
+
* skims everything else — the same reason the bug lead is inside the document.
|
|
305
|
+
*
|
|
306
|
+
* It says when *not* to as well. A follow-up for a guide that worked exactly as written is a copy
|
|
307
|
+
* that says nothing new, and a guide collecting those is worse than one with none; that answer is a
|
|
308
|
+
* verdict. For a bug the question is different — nothing to depart from, but a fix worth repeating
|
|
309
|
+
* elsewhere is a transfer guide, and the bug is where it came from.
|
|
310
|
+
*/
|
|
311
|
+
export function followUpNote(meta = {}) {
|
|
312
|
+
if (!meta.id) return "";
|
|
313
|
+
if (meta.kind === "bug")
|
|
314
|
+
return (
|
|
315
|
+
"<!-- passalong: once this is fixed, if the fix is worth repeating somewhere else, publish " +
|
|
316
|
+
`it as a transfer guide with publish_guide parent=${meta.id} so this bug lists it. -->`
|
|
317
|
+
);
|
|
318
|
+
return (
|
|
319
|
+
"<!-- passalong: if you depart from this guide — change or skip a Step, adapt an ASSUMES for " +
|
|
320
|
+
"this stack, find a fix it did not have, hit a Gotcha it does not list — publish what you " +
|
|
321
|
+
`learned as its own guide with publish_guide parent=${meta.id}. If it worked exactly as ` +
|
|
322
|
+
"written, answer with verify_guide instead. -->"
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
295
326
|
/** Local guides merged with synced ones (by id), newest first, optionally filtered. */
|
|
296
327
|
export async function list(query = "", { remote = true, scope = "" } = {}) {
|
|
297
328
|
const local = scope && scope !== "all" && scope !== "mine" ? [] : store.search(query);
|
|
@@ -390,7 +421,7 @@ export function summary(g) {
|
|
|
390
421
|
};
|
|
391
422
|
}
|
|
392
423
|
|
|
393
|
-
/**
|
|
424
|
+
/** Archive a guide (`consumed`) or put it back (`published`). The server refuses `promoted`. */
|
|
394
425
|
export async function setStatus(id, status) {
|
|
395
426
|
if (!STATUSES.includes(status))
|
|
396
427
|
throw new PassalongError(`status must be one of ${STATUSES.join(", ")}`);
|