@yaawn/workflow 0.4.0 → 0.5.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.
Files changed (89) hide show
  1. package/README.md +22 -43
  2. package/agents/personas/yaawn-product-owner.md +3 -3
  3. package/agents/skills/yaawn-create-plan/SKILL.md +38 -11
  4. package/agents/skills/yaawn-extract-learnings/SKILL.md +138 -0
  5. package/agents/skills/yaawn-grill-bug-report/SKILL.md +7 -0
  6. package/agents/skills/yaawn-grill-other/SKILL.md +7 -0
  7. package/agents/skills/yaawn-grill-prototype/SKILL.md +38 -0
  8. package/agents/skills/yaawn-grill-prototype/references/prototype-checklist.md +23 -0
  9. package/agents/skills/yaawn-grill-refactor/SKILL.md +7 -0
  10. package/agents/skills/yaawn-grill-spike/SKILL.md +7 -0
  11. package/agents/skills/yaawn-grill-story/SKILL.md +7 -0
  12. package/agents/skills/yaawn-implement-plan/SKILL.md +10 -8
  13. package/agents/skills/yaawn-implement-plan/scripts/agent.ts +1 -1
  14. package/agents/skills/yaawn-implement-plan/scripts/overnight.ts +7 -4
  15. package/agents/skills/yaawn-implement-plan/scripts/parallel.ts +155 -12
  16. package/agents/skills/yaawn-implement-plan/scripts/processes.ts +161 -1
  17. package/agents/skills/yaawn-implement-plan/scripts/targets.ts +13 -2
  18. package/agents/skills/yaawn-implement-plan/scripts/worktrees.ts +219 -52
  19. package/agents/skills/yaawn-prepare-plan/SKILL.md +20 -19
  20. package/agents/skills/yaawn-verify-executed-plan/SKILL.md +28 -13
  21. package/agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts +44 -12
  22. package/agents/skills/yaawn-write-bug-report/SKILL.md +6 -5
  23. package/agents/skills/yaawn-write-other/SKILL.md +6 -5
  24. package/agents/skills/yaawn-write-prototype/SKILL.md +96 -0
  25. package/agents/skills/yaawn-write-refactor/SKILL.md +6 -5
  26. package/agents/skills/yaawn-write-spike/SKILL.md +1 -1
  27. package/agents/skills/yaawn-write-user-story/SKILL.md +3 -2
  28. package/agents/yaawn-glossary.md +4 -3
  29. package/config/install-tasks.json +0 -1
  30. package/config/wizard.json +3 -8
  31. package/dist/cli/args.js +66 -0
  32. package/dist/cli/errors.js +103 -0
  33. package/dist/cli/exit-codes.js +3 -0
  34. package/dist/cli/index.js +11 -0
  35. package/dist/cli/install-report.js +92 -0
  36. package/dist/cli/install.js +124 -0
  37. package/dist/cli/main.js +17 -0
  38. package/dist/cli/package-root.js +17 -0
  39. package/dist/cli/project-root.js +11 -0
  40. package/dist/cli/run.js +141 -0
  41. package/dist/cli/session.js +1 -0
  42. package/dist/cli/terminal.js +5 -0
  43. package/dist/cli/uninstall.js +89 -0
  44. package/dist/cli/version.js +7 -0
  45. package/dist/config/agents.js +108 -0
  46. package/dist/config/config.js +118 -0
  47. package/dist/config/errors.js +45 -0
  48. package/dist/config/index.js +11 -0
  49. package/dist/config/manifest.js +28 -0
  50. package/dist/config/messages.js +86 -0
  51. package/dist/config/names.js +40 -0
  52. package/dist/config/paths.js +73 -0
  53. package/dist/config/settings.js +101 -0
  54. package/dist/config/strict-json.js +119 -0
  55. package/dist/config/tasks.js +77 -0
  56. package/dist/config/wizard.js +139 -0
  57. package/dist/display/index.js +3 -0
  58. package/dist/display/palette.js +61 -0
  59. package/dist/display/renderer.js +169 -0
  60. package/dist/display/report.js +15 -0
  61. package/dist/display/sanitize.js +10 -0
  62. package/dist/link/errors.js +34 -0
  63. package/dist/link/fs-utils.js +79 -0
  64. package/dist/link/index.js +4 -0
  65. package/dist/link/install.js +20 -0
  66. package/dist/link/linker.js +137 -0
  67. package/dist/link/memory.js +221 -0
  68. package/dist/link/names.js +30 -0
  69. package/dist/link/types.js +30 -0
  70. package/dist/link/uninstall.js +90 -0
  71. package/dist/sync/apply.js +74 -0
  72. package/dist/sync/changes.js +31 -0
  73. package/dist/sync/fs-utils.js +63 -0
  74. package/dist/sync/index.js +7 -0
  75. package/dist/sync/install.js +57 -0
  76. package/dist/sync/languages.js +80 -0
  77. package/dist/sync/plan.js +146 -0
  78. package/dist/sync/projects.js +294 -0
  79. package/dist/sync/uninstall.js +234 -0
  80. package/dist/wizard/build.js +89 -0
  81. package/dist/wizard/index.js +5 -0
  82. package/dist/wizard/items.js +83 -0
  83. package/dist/wizard/prompts.js +167 -0
  84. package/dist/wizard/section.js +143 -0
  85. package/dist/wizard/wizard.js +73 -0
  86. package/github/ISSUE_TEMPLATE/yaawn-prototype.md +48 -0
  87. package/package.json +11 -10
  88. package/agents/instructions/output-styles/yaawn-eli5.instructions.md +0 -45
  89. package/scripts/install.js +0 -73
@@ -1,19 +1,25 @@
1
1
  import { execFile, execFileSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
2
3
  import path from "node:path";
3
4
  import { promisify } from "node:util";
4
5
  import { uncommittedChangesAsync } from "./targets.js";
5
6
 
6
7
  // Shared by yaawn-implement-plan (creates one git worktree per issue) and
7
- // yaawn-verify-executed-plan (checks it verifies in the issue's worktree).
8
+ // yaawn-verify-executed-plan (resolves the issue's worktree to verify in).
8
9
  //
9
10
  // Layout: the project root holds a bare `.git` repository, the main working
10
11
  // copy is a normal checkout at `<project>/main`, and every issue worktree is
11
- // a sibling of it at `<project>/yaawn-<issue>-<slug>`.
12
+ // a sibling of it at `<project>/yaawn-<issue>-<slug>`. A prototype issue
13
+ // (labelled `Yaawn: Prototype`) gets its worktree at
14
+ // `<project>/prototype/yaawn-<issue>-<slug>` on branch
15
+ // `prototype/yaawn-<issue>-<slug>`, which is never merged.
12
16
 
13
17
  export type WorktreeCheckStatus = "ok" | "warn" | "fail";
14
18
 
19
+ // `path` is the worktree to verify in; null only when the check fails.
15
20
  export interface WorktreeCheck {
16
21
  status: WorktreeCheckStatus;
22
+ path: string | null;
17
23
  message: string;
18
24
  }
19
25
 
@@ -21,16 +27,20 @@ export interface WorktreeLayout {
21
27
  issue: number;
22
28
  mainCopy: string;
23
29
  registered: Iterable<string>;
24
- currentToplevel: string;
25
- // Resolved by branch (yaawn/<issue>), not by the title slug — the slug in
26
- // the directory name is for humans only.
30
+ // Resolved by branch (yaawn/<issue>, or prototype/yaawn-<issue>-*), not by
31
+ // the title-derived path.
27
32
  ownWorktree?: string | null;
33
+ locked?: Iterable<string>;
34
+ // Registered, but git marks them prunable or their directory is gone.
35
+ stale?: Iterable<string>;
28
36
  }
29
37
 
30
38
  export interface WorktreeEntry {
31
39
  path: string;
32
40
  branch: string | null;
33
41
  bare: boolean;
42
+ locked: boolean;
43
+ prunable: boolean;
34
44
  }
35
45
 
36
46
  export interface AheadBehind {
@@ -58,12 +68,14 @@ const branchPrefix = "branch refs/heads/";
58
68
  const maxSlugLength = 40;
59
69
  const fallbackMainCopyName = "main";
60
70
  const fallbackDefaultBranches = ["main", "master"];
71
+ const prototypeDir = "prototype";
72
+ const fallbackPrototypeSlug = "prototype";
73
+
74
+ export const prototypeLabel = "Yaawn: Prototype";
61
75
 
62
76
  const aheadBehindPattern = /^(\d+)\s+(\d+)$/;
63
77
  const execFileAsync = promisify(execFile);
64
78
 
65
- export const branchName = (issue: number): string => `yaawn/${issue}`;
66
-
67
79
  export function git(args: string[]): string {
68
80
  return execFileSync("git", args, {
69
81
  encoding: "utf-8",
@@ -92,9 +104,37 @@ export function titleSlug(title: string): string {
92
104
  .replace(/-+$/, "");
93
105
  }
94
106
 
95
- export function worktreeName(issue: number, title: string): string {
107
+ // Exact label-name match only: a look-alike label never makes a prototype.
108
+ export const isPrototype = (labels: readonly string[]): boolean =>
109
+ labels.includes(prototypeLabel);
110
+
111
+ // A prototype branch always carries a slug, so `prototype/yaawn-<issue>-*`
112
+ // finds it.
113
+ const prototypeSlug = (title: string): string =>
114
+ titleSlug(title) || fallbackPrototypeSlug;
115
+
116
+ export function prototypeBranchPrefix(issue: number): string {
96
117
  assertIssueNumber(issue);
97
- const slug = titleSlug(title);
118
+ return `${prototypeDir}/yaawn-${issue}-`;
119
+ }
120
+
121
+ export function branchName(
122
+ issue: number,
123
+ labels: readonly string[] = [],
124
+ title = "",
125
+ ): string {
126
+ return isPrototype(labels)
127
+ ? `${prototypeBranchPrefix(issue)}${prototypeSlug(title)}`
128
+ : `yaawn/${issue}`;
129
+ }
130
+
131
+ export function worktreeName(
132
+ issue: number,
133
+ title: string,
134
+ labels: readonly string[] = [],
135
+ ): string {
136
+ assertIssueNumber(issue);
137
+ const slug = isPrototype(labels) ? prototypeSlug(title) : titleSlug(title);
98
138
  return slug ? `yaawn-${issue}-${slug}` : `yaawn-${issue}`;
99
139
  }
100
140
 
@@ -109,6 +149,8 @@ function entryFromBlock(lines: string[]): WorktreeEntry | null {
109
149
  branch:
110
150
  branchLine === undefined ? null : branchLine.slice(branchPrefix.length),
111
151
  bare: lines.includes("bare"),
152
+ locked: lines.some((line) => line === "locked" || line.startsWith("locked ")),
153
+ prunable: lines.some((line) => line === "prunable" || line.startsWith("prunable ")),
112
154
  };
113
155
  }
114
156
 
@@ -163,12 +205,56 @@ export function branchWorktreeFromPorcelain(
163
205
  return checkout?.path ?? null;
164
206
  }
165
207
 
208
+ // A prototype's checkout on any `prototype/yaawn-<issue>-*` branch counts, so
209
+ // a relaunch resumes it even after the issue was renamed.
210
+ export function issueWorktreeFromPorcelain(
211
+ porcelain: string,
212
+ issue: number,
213
+ labels: readonly string[] = [],
214
+ title = "",
215
+ ): string | null {
216
+ const exact = branchWorktreeFromPorcelain(
217
+ porcelain,
218
+ branchName(issue, labels, title),
219
+ );
220
+ if (exact !== null || !isPrototype(labels)) return exact;
221
+ const prefix = prototypeBranchPrefix(issue);
222
+ const checkout = entriesFromPorcelain(porcelain).find(
223
+ (entry) => !entry.bare && entry.branch?.startsWith(prefix) === true,
224
+ );
225
+ return checkout?.path ?? null;
226
+ }
227
+
228
+ // Both the worktree path and its actual branch, so a relaunch after a
229
+ // rename keeps reporting the branch that is really checked out.
230
+ export function issueWorktreeAndBranchFromPorcelain(
231
+ porcelain: string,
232
+ issue: number,
233
+ labels: readonly string[] = [],
234
+ title = "",
235
+ ): { path: string; branch: string } | null {
236
+ const entry = entriesFromPorcelain(porcelain).find((candidate) => {
237
+ if (candidate.bare || candidate.branch === null) return false;
238
+ if (candidate.branch === branchName(issue, labels, title)) return true;
239
+ return (
240
+ isPrototype(labels) &&
241
+ candidate.branch.startsWith(prototypeBranchPrefix(issue))
242
+ );
243
+ });
244
+ if (entry === undefined || entry.branch === null) return null;
245
+ return { path: entry.path, branch: entry.branch };
246
+ }
247
+
166
248
  export function worktreePathFor(
167
249
  projectRoot: string,
168
250
  issue: number,
169
251
  title: string,
252
+ labels: readonly string[] = [],
170
253
  ): string {
171
- return path.join(projectRoot, worktreeName(issue, title));
254
+ const name = worktreeName(issue, title, labels);
255
+ return isPrototype(labels)
256
+ ? path.join(projectRoot, prototypeDir, name)
257
+ : path.join(projectRoot, name);
172
258
  }
173
259
 
174
260
  // --- git
@@ -209,22 +295,58 @@ export function usesBareLayout(): boolean {
209
295
  return isBareLayout(listPorcelain());
210
296
  }
211
297
 
212
- export function worktreePath(issue: number, title: string): string {
298
+ export function worktreePath(
299
+ issue: number,
300
+ title: string,
301
+ labels: readonly string[] = [],
302
+ ): string {
213
303
  return worktreePathFor(
214
304
  projectRootFromPorcelain(listPorcelain()),
215
305
  issue,
216
306
  title,
307
+ labels,
217
308
  );
218
309
  }
219
310
 
220
311
  // The branch's existing checkout wins over the title-derived path, so a
221
312
  // relaunch resumes the earlier worktree even after the issue was renamed.
222
- export function laneWorktreePath(issue: number, title: string): string {
313
+ export function laneWorktreePath(
314
+ issue: number,
315
+ title: string,
316
+ labels: readonly string[] = [],
317
+ ): string {
223
318
  const porcelain = listPorcelain();
224
319
  return (
225
- branchWorktreeFromPorcelain(porcelain, branchName(issue)) ??
226
- worktreePathFor(projectRootFromPorcelain(porcelain), issue, title)
320
+ issueWorktreeFromPorcelain(porcelain, issue, labels, title) ??
321
+ worktreePathFor(projectRootFromPorcelain(porcelain), issue, title, labels)
322
+ );
323
+ }
324
+
325
+ // Resolves both the worktree path and branch for a lane: the existing
326
+ // checkout's actual branch when the worktree is reused, otherwise the
327
+ // title-derived values for a fresh worktree.
328
+ export function laneWorktreeAndBranch(
329
+ issue: number,
330
+ title: string,
331
+ labels: readonly string[] = [],
332
+ ): { path: string; branch: string } {
333
+ const porcelain = listPorcelain();
334
+ const existing = issueWorktreeAndBranchFromPorcelain(
335
+ porcelain,
336
+ issue,
337
+ labels,
338
+ title,
227
339
  );
340
+ if (existing !== null) return existing;
341
+ return {
342
+ path: worktreePathFor(
343
+ projectRootFromPorcelain(porcelain),
344
+ issue,
345
+ title,
346
+ labels,
347
+ ),
348
+ branch: branchName(issue, labels, title),
349
+ };
228
350
  }
229
351
 
230
352
  export function registeredWorktrees(): Set<string> {
@@ -236,58 +358,96 @@ const normalized = (worktree: string): string => path.resolve(worktree);
236
358
  const samePath = (left: string, right: string): boolean =>
237
359
  normalized(left) === normalized(right);
238
360
 
239
- export function runsInOwnWorktree(issue: number, title: string): boolean {
240
- return samePath(worktreePath(issue, title), currentToplevel());
361
+ export function runsInOwnWorktree(
362
+ issue: number,
363
+ title: string,
364
+ labels: readonly string[] = [],
365
+ ): boolean {
366
+ return samePath(worktreePath(issue, title, labels), currentToplevel());
241
367
  }
242
368
 
243
369
  // --- classification
244
370
 
245
- function checkOwnWorktree(own: string, toplevel: string): WorktreeCheck {
246
- return samePath(own, toplevel)
247
- ? { status: "ok", message: `verifying in the issue's worktree ${own}` }
248
- : {
249
- status: "fail",
250
- message: `run verification in the issue's worktree ${own}, not in ${toplevel}`,
251
- };
371
+ const includesPath = (paths: Iterable<string> = [], target: string): boolean =>
372
+ [...paths].some((candidate) => samePath(candidate, target));
373
+
374
+ const failedCheck = (message: string): WorktreeCheck => ({
375
+ status: "fail",
376
+ path: null,
377
+ message,
378
+ });
379
+
380
+ function checkOwnWorktree(layout: WorktreeLayout, own: string): WorktreeCheck {
381
+ const { issue, locked, stale } = layout;
382
+ if (includesPath(stale, own)) {
383
+ return failedCheck(
384
+ `#${issue}'s worktree ${own} is stale — its directory is missing; restore it or run \`git worktree prune\``,
385
+ );
386
+ }
387
+ if (includesPath(locked, own)) {
388
+ return failedCheck(
389
+ `#${issue}'s worktree ${own} is locked — unlock it with \`git worktree unlock\` first`,
390
+ );
391
+ }
392
+ return { status: "ok", path: own, message: `verifying in the issue's worktree ${own}` };
252
393
  }
253
394
 
254
- function checkMainCopy(
255
- issue: number,
256
- mainCopy: string,
257
- toplevel: string,
258
- ): WorktreeCheck {
259
- return samePath(mainCopy, toplevel)
260
- ? {
261
- status: "warn",
262
- message: `#${issue} has no git worktree of its own — verifying the main copy`,
263
- }
264
- : {
265
- status: "fail",
266
- message: `#${issue} has no git worktree of its own — run verification in the main copy ${mainCopy}, not in ${toplevel}`,
267
- };
395
+ function checkMainCopy(layout: WorktreeLayout): WorktreeCheck {
396
+ const { issue, mainCopy, registered, stale } = layout;
397
+ if (!includesPath(registered, mainCopy) || includesPath(stale, mainCopy)) {
398
+ return failedCheck(
399
+ `#${issue} has no git worktree of its own, and the main copy ${mainCopy} is missing`,
400
+ );
401
+ }
402
+ return {
403
+ status: "warn",
404
+ path: mainCopy,
405
+ message: `#${issue} has no git worktree of its own — verifying the main copy`,
406
+ };
268
407
  }
269
408
 
409
+ // Resolves where to verify, wherever the session runs.
270
410
  export function classifyWorktree(layout: WorktreeLayout): WorktreeCheck {
271
- const { issue, mainCopy, registered, ownWorktree, currentToplevel } = layout;
272
- const own =
273
- ownWorktree != null &&
274
- [...registered].some((worktree) => samePath(worktree, ownWorktree))
275
- ? ownWorktree
276
- : null;
277
- return own !== null
278
- ? checkOwnWorktree(own, currentToplevel)
279
- : checkMainCopy(issue, mainCopy, currentToplevel);
411
+ const { registered, ownWorktree } = layout;
412
+ return ownWorktree != null && includesPath(registered, ownWorktree)
413
+ ? checkOwnWorktree(layout, ownWorktree)
414
+ : checkMainCopy(layout);
415
+ }
416
+
417
+ function staleWorktrees(entries: WorktreeEntry[]): string[] {
418
+ return entries
419
+ .filter((entry) => entry.prunable || !existsSync(entry.path))
420
+ .map((entry) => entry.path);
280
421
  }
281
422
 
282
- export function checkWorktree(issue: number): WorktreeCheck {
423
+ function layoutFromGit(
424
+ issue: number,
425
+ labels: readonly string[],
426
+ title: string,
427
+ ): WorktreeLayout {
283
428
  const porcelain = listPorcelain();
284
- return classifyWorktree({
429
+ const entries = entriesFromPorcelain(porcelain);
430
+ return {
285
431
  issue,
286
432
  mainCopy: mainCopyFromPorcelain(porcelain, defaultBranches()),
287
- registered: worktreesFromPorcelain(porcelain),
288
- currentToplevel: currentToplevel(),
289
- ownWorktree: branchWorktreeFromPorcelain(porcelain, branchName(issue)),
290
- });
433
+ registered: entries.map((entry) => entry.path),
434
+ ownWorktree: issueWorktreeFromPorcelain(porcelain, issue, labels, title),
435
+ locked: entries.filter((entry) => entry.locked).map((entry) => entry.path),
436
+ stale: staleWorktrees(entries),
437
+ };
438
+ }
439
+
440
+ // Never throws: a git failure becomes a failed check.
441
+ export function checkWorktree(
442
+ issue: number,
443
+ labels: readonly string[] = [],
444
+ title = "",
445
+ ): WorktreeCheck {
446
+ try {
447
+ return classifyWorktree(layoutFromGit(issue, labels, title));
448
+ } catch (error) {
449
+ return failedCheck(`could not resolve the worktree: ${gitErrorReason(error)}`);
450
+ }
291
451
  }
292
452
 
293
453
  // --- main copy sync
@@ -338,6 +498,13 @@ function redactedGitError(error: unknown): string {
338
498
  return line ? `: ${line.replace(/\/\/[^/\s@]+@/g, "//***@")}` : "";
339
499
  }
340
500
 
501
+ function gitErrorReason(error: unknown): string {
502
+ const stderr = redactedGitError(error);
503
+ if (stderr) return stderr.slice(": ".length);
504
+ const message = error instanceof Error ? error.message : String(error);
505
+ return message.split("\n")[0];
506
+ }
507
+
341
508
  // Never waits for a credential prompt: an unattended launch would hang.
342
509
  async function gitAsync(args: string[]): Promise<string> {
343
510
  const { stdout } = await execFileAsync("git", args, {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yaawn-prepare-plan
3
- description: "Turn a new idea or an existing GitHub issue into an approved work item — a story, refactor, spike, bug report, or other change — pick or confirm its type, draft or reconsider it, grill it, and save it to the issue — ready for /yaawn-create-plan (spikes go to a human instead). Use when: preparing work for planning, new idea, refine issue #N, write and grill a story, write a refactor, write a spike, write a bug report, write an other change, check if a story needs splitting, get a work item ready for planning."
3
+ description: "Turn a new idea or an existing GitHub issue into an approved work item — a story, refactor, spike, bug report, prototype, or other change — pick or confirm its type, draft or reconsider it, grill it, and save it to the issue — ready for /yaawn-create-plan (spikes go to a human instead). Use when: preparing work for planning, new idea, refine issue #N, write and grill a story, write a refactor, write a spike, write a bug report, write a prototype, write an other change, check if a story needs splitting, get a work item ready for planning."
4
4
  argument-hint: "[<issue>]"
5
5
  disable-model-invocation: true
6
6
  metadata:
@@ -10,7 +10,7 @@ metadata:
10
10
 
11
11
  # Prepare Plan
12
12
 
13
- Turn a new idea or an existing issue into an approved work item on GitHub — a story, refactor, spike, bug report, or other change. Run it in a **new session**, in the product owner persona (`.agents/personas/yaawn-product-owner.md`).
13
+ Turn a new idea or an existing issue into an approved work item on GitHub — a story, refactor, spike, bug report, prototype, or other change. Run it in a **new session**, in the product owner persona (`.agents/personas/yaawn-product-owner.md`).
14
14
 
15
15
  This skill is a thin orchestrator. It owns only the greeting, the choice of work item type, the order of the steps, the handover between them, the final approval, and the routing. All drafting is done by the skills it calls, by step number — never duplicate their rules here.
16
16
 
@@ -22,7 +22,7 @@ Out of scope: technical or design grilling, implementation plans, and code or an
22
22
 
23
23
  This is the first of the plan skills: `/yaawn-prepare-plan` → `/yaawn-create-plan` → `/yaawn-save-plan` → `/yaawn-implement-plan` → `/yaawn-verify-executed-plan`. Every skill starts in a new session and works only from files and the issue — never from an earlier conversation.
24
24
 
25
- - The issue is the only artifact. This skill writes no decision log and no plan file.
25
+ - The issue is the only artifact. This skill writes no decision log and no plan file — the grill's decisions and open questions are saved into the issue's `### Design Decisions` and `### Open Questions` tables by the writer skill, so `/yaawn-create-plan` does not re-ask them.
26
26
  - Leave the `Yaawn: To Be Refined` label in place; `/yaawn-save-plan` removes it.
27
27
  - `<issue>` is optional. If given, it must be a positive integer (digits only, no `#`, URL, spaces, or other characters); otherwise stop and ask for a valid number. Use it in commands only after this check.
28
28
  - Never interpolate issue bodies, comments, titles, or other user text into a shell command. Write them to a file and pass it with `--body-file`.
@@ -33,37 +33,38 @@ This is the first of the plan skills: `/yaawn-prepare-plan` → `/yaawn-create-p
33
33
 
34
34
  There is one type per template in `.github/ISSUE_TEMPLATE/`. The type label comes from the template's frontmatter `labels`.
35
35
 
36
- | Type | Use when the work… | Type label | Route | Grill | After save |
37
- | ------------ | ------------------------------------------------ | ----------------- | ------------------------- | ------------------------- | -------------------- |
38
- | Story | adds or changes behavior users see | `Yaawn: Story` | [Story path](#story-path) | `/yaawn-grill-story` | `/yaawn-create-plan` |
39
- | Refactor | changes the inside, with no user-visible change | `Yaawn: Refactor` | `/yaawn-write-refactor` | `/yaawn-grill-refactor` | `/yaawn-create-plan` |
40
- | Spike | must answer one open question before work starts | `Yaawn: Spike` | `/yaawn-write-spike` | `/yaawn-grill-spike` | a human answers it |
41
- | Bug report | fixes behavior that is wrong today | `Yaawn: Bug` | `/yaawn-write-bug-report` | `/yaawn-grill-bug-report` | `/yaawn-create-plan` |
42
- | Other change | matches none of the four types | `Yaawn: Other` | `/yaawn-write-other` | `/yaawn-grill-other` | `/yaawn-create-plan` |
36
+ | Type | Use when the work… | Type label | Route | Grill | After save |
37
+ | ------------ | ------------------------------------------------- | ------------------ | ------------------------- | ------------------------- | -------------------- |
38
+ | Story | adds or changes behavior users see | `Yaawn: Story` | [Story path](#story-path) | `/yaawn-grill-story` | `/yaawn-create-plan` |
39
+ | Refactor | changes the inside, with no user-visible change | `Yaawn: Refactor` | `/yaawn-write-refactor` | `/yaawn-grill-refactor` | `/yaawn-create-plan` |
40
+ | Spike | must answer one open question before work starts | `Yaawn: Spike` | `/yaawn-write-spike` | `/yaawn-grill-spike` | a human answers it |
41
+ | Bug report | fixes behavior that is wrong today | `Yaawn: Bug` | `/yaawn-write-bug-report` | `/yaawn-grill-bug-report` | `/yaawn-create-plan` |
42
+ | Prototype | builds a throwaway version to test with customers | `Yaawn: Prototype` | `/yaawn-write-prototype` | `/yaawn-grill-prototype` | `/yaawn-create-plan` |
43
+ | Other change | matches none of the five types | `Yaawn: Other` | `/yaawn-write-other` | `/yaawn-grill-other` | `/yaawn-create-plan` |
43
44
 
44
45
  ## 1. Greet and choose the type
45
46
 
46
- - **New idea (no `<issue>`):** greet the user and offer the five types through the ask tool, each with its one-line "use when", plus a last option: **"Not sure / none of these"**.
47
+ - **New idea (no `<issue>`):** greet the user and offer the six types through the ask tool, each with its one-line "use when" — for a prototype: "Prototype — build a throwaway version of a feature idea to test with customers and learn what works" — plus a last option: **"Not sure / none of these"**.
47
48
  - **Existing issue:** read it with `gh issue view <issue> --json title,body,labels,comments`. Infer the type from its type label (table above) and ask the user to confirm it.
48
49
  - **Confirmed:** continue with that type.
49
50
  - **Override:** warn that re-typing rewrites the issue's body to the new type's template — the current body's sections are replaced — and ask for explicit confirmation. Only after it, continue with the new type; content worth keeping is carried over only with the user's agreement. Without confirmation, keep the inferred type.
50
51
  - **No type label:** fall back to the picker, as for a new idea.
51
- - **"Not sure / none of these":** help find the right shape — ask what the work changes: behavior users see (story), the inside without user-visible change (refactor), wrong behavior today (bug report), or nothing yet because a question must be answered first (spike). Offer the matching type. If nothing fits, offer **other change** as the last resort; if the user declines it too, say so and stop — never save anything on this path.
52
+ - **"Not sure / none of these":** help find the right shape — ask what the work changes: behavior users see (story), the inside without user-visible change (refactor), wrong behavior today (bug report), nothing yet because a question must be answered first (spike), or a throwaway version of a feature idea to test with customers before committing to it (prototype). Offer the matching type. If nothing fits, offer **other change** as the last resort; if the user declines it too, say so and stop — never save anything on this path.
52
53
 
53
54
  If a writer skill later finds the item is a different type (for example a refactor that changes user-visible behavior), return here and re-offer the type choice.
54
55
 
55
56
  ## 2. Route
56
57
 
57
58
  - **Story:** follow the [Story path](#story-path). For a confirmed re-type, tell `/yaawn-write-user-story` to draft the body from the story template as for a new story, and swap the type label after the save as in [Writer path](#writer-path) step 3.
58
- - **Refactor, spike, bug report, other change:** follow the [Writer path](#writer-path) with the routed writer and grill skills from the table.
59
+ - **Refactor, spike, bug report, prototype, other change:** follow the [Writer path](#writer-path) with the routed writer and grill skills from the table.
59
60
 
60
61
  ## Writer path
61
62
 
62
- 1. **Draft, grill, check:** run step 1 of the routed writer skill (draft or refine), then the routed type's `/yaawn-grill-*` skill from the table — tell it to skip its scope check and that this skill defines what happens when the grill ends: continue with the writer's step 3 (quality check). The grill replaces the writer's step 2 (light grill) in this session only. For an existing issue, the writer reads the issue itself; for a confirmed re-type, tell it to build the body from its own template as for a new issue.
63
- 2. **Approve:** present the finished fields and ask the user to approve them through the ask tool. Wait for it — never create or edit the issue before.
64
- 3. **Save:** run the writer's step 4 ("Saving the issue"). Tell it this approval is the one its saving step 1 asks for — do not ask again. It reads the template frontmatter and saves with `--body-file`. For a confirmed re-type, also swap the old type label for the new one: `gh issue edit <issue> --remove-label "<old type label>" --add-label "<new type label>"`, with both labels taken from the template frontmatters.
63
+ 1. **Draft, grill, check:** run step 1 of the routed writer skill (draft or refine), then the routed type's `/yaawn-grill-*` skill from the table — tell it to skip its scope check and that this skill defines what happens when the grill ends: continue with the writer's step 3 (quality check). The grill replaces the writer's step 2 (light grill) in this session only. When the grill ends, keep the `## Decisions` and `## Open questions` sections from its Result for the save. For an existing issue, the writer reads the issue itself; for a confirmed re-type, tell it to build the body from its own template as for a new issue.
64
+ 2. **Approve:** present the finished fields, together with the grill's decisions and open questions, and ask the user to approve them through the ask tool. Wait for it — never create or edit the issue before.
65
+ 3. **Save:** run the writer's step 4 ("Approve and save"), which follows its "Saving the issue". Tell it this approval is the one its saving step 1 asks for — do not ask again. Pass it the grill's `## Decisions` and `## Open questions` sections as they are, so it fills the issue's `### Design Decisions` and `### Open Questions` tables in the same save — except for a spike, whose template has neither table, so nothing is passed. It reads the template frontmatter and saves with `--body-file`. For a confirmed re-type, also swap the old type label for the new one: `gh issue edit <issue> --remove-label "<old type label>" --add-label "<new type label>"`, with both labels taken from the template frontmatters.
65
66
  4. **Hand off:**
66
- - **Refactor, bug report, other change:** tell the user to start a **new session** and run `/yaawn-create-plan <issue>` there.
67
+ - **Refactor, bug report, prototype, other change:** tell the user to start a **new session** and run `/yaawn-create-plan <issue>` there. For a prototype, add that it is built on a never-merged `prototype/` branch and that `/yaawn-extract-learnings <issue>` replaces `/yaawn-verify-executed-plan` after `/yaawn-implement-plan`.
67
68
  - **Spike:** the session ends here — a human answers the spike. There is no handoff to `/yaawn-create-plan`; tell the user so.
68
69
 
69
70
  ## Story path
@@ -97,7 +98,7 @@ Then, in this session:
97
98
 
98
99
  ### 4. Grill the story
99
100
 
100
- Run `/yaawn-grill-story` on the story. Tell it to skip its scope check — the sprint-fit check or the split assessment already covered scope — and that this skill defines what happens when the grill ends: continue with step 5.
101
+ Run `/yaawn-grill-story` on the story. Tell it to skip its scope check — the sprint-fit check or the split assessment already covered scope — and that this skill defines what happens when the grill ends: continue with step 5. Keep the `## Decisions` and `## Open questions` sections from its Result for step 6.
101
102
 
102
103
  ### 5. Quality check
103
104
 
@@ -105,6 +106,6 @@ Run the quality check from `/yaawn-write-user-story` step 6 on the grilled story
105
106
 
106
107
  ### 6. Approve and save
107
108
 
108
- Ask the user to approve the finished story, then create or update the issue with `/yaawn-write-user-story` step 8. Leave the `Yaawn: To Be Refined` label in place.
109
+ Ask the user to approve the finished story, together with the grill's decisions and open questions, then create or update the issue with `/yaawn-write-user-story` step 8. Pass it the grill's `## Decisions` and `## Open questions` sections as they are, so it fills the issue's `### Design Decisions` and `### Open Questions` tables in the same save. Leave the `Yaawn: To Be Refined` label in place.
109
110
 
110
111
  End by telling the user to start a **new session** and run `/yaawn-create-plan <issue>` there.
@@ -16,23 +16,38 @@ It takes the same issue number as `/yaawn-implement-plan` and resolves the same
16
16
 
17
17
  Out of scope: implementing or fixing anything, committing or pushing, writing or editing plans, editing the issue — step 7 is the only exception: it opens a pull request for the issue's branch and removes the issue's `Yaawn: Ready To Implement` and `Yaawn: implementation-group-<n>` labels. On failure, report and stop — fixing is `/yaawn-implement-plan`'s job.
18
18
 
19
- Ask for the issue number if it is not given. Run all commands from the repo root.
19
+ Prototypes (label `Yaawn: Prototype`) are never verified or merged — after `/yaawn-implement-plan`, they go to `/yaawn-extract-learnings` instead; pre-flight stops on them.
20
+
21
+ It works from any worktree of the project — like `/yaawn-save-plan`, the session does not have to run in the issue's worktree. The script resolves the issue's worktree itself and does all its work there.
22
+
23
+ Ask for the issue number if it is not given. Run all commands from the root of the worktree the session runs in.
20
24
 
21
25
  ## 1. Pre-flight
22
26
 
27
+ First — before any worktree or branch lookup or other work — read the issue's labels:
28
+
29
+ ```sh
30
+ gh issue view <issue> --json labels --jq '.labels[].name'
31
+ ```
32
+
33
+ - The labels include exactly `Yaawn: Prototype`: stop — `#<issue> is a prototype — prototypes are never verified or merged. Extract its learnings with /yaawn-extract-learnings <issue> instead.` Do not offer to continue anyway, and never open a pull request.
34
+ - The labels cannot be read (`gh` fails, the issue is not found): stop and report the error — never continue without knowing the issue is not a prototype.
35
+
36
+ Then run the pre-flight script:
37
+
23
38
  ```sh
24
39
  npx tsx .agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts preflight <issue>
25
40
  ```
26
41
 
27
- It first checks where it runs. `/yaawn-implement-plan` runs every issue in its own git worktree inside the project (`<project>/yaawn-<issue>-<slug>`, branch `yaawn/<issue>`), next to the main copy `<project>/main` — the bare-repo layout described in [`.agents/yaawn-worktrees.md`](../../yaawn-worktrees.md):
42
+ It first resolves where to verify, wherever the session runs, and moves there. `/yaawn-implement-plan` runs every issue in its own git worktree inside the project (`<project>/yaawn-<issue>-<slug>`, branch `yaawn/<issue>`), next to the main copy `<project>/main` — the bare-repo layout described in [`.agents/yaawn-worktrees.md`](../../yaawn-worktrees.md):
28
43
 
29
- - The issue has its own worktree and the session runs elsewhere: `FAIL` naming the worktree's path.
30
- - The issue has no worktree of its own and the session runs in another issue's worktree: `FAIL` pointing to the main copy `<project>/main`.
31
- - The issue has no worktree of its own (implemented before every run got one, or by hand) and the session runs in the main copy: `WARN #<issue> has no git worktree of its own — verifying the main copy`. Mention the warning to the developer and continue.
44
+ - The issue has its own worktree: `worktree: verifying in the issue's worktree <path>`.
45
+ - The issue has no worktree of its own (implemented before every run got one, or by hand): `WARN #<issue> has no git worktree of its own — verifying the main copy`. Mention the warning to the developer and continue.
46
+ - The target cannot be resolved — the issue's worktree is locked or stale, the main copy is missing, or git fails: `FAIL` with the reason.
32
47
 
33
- It prints the target, the verification commands — or a note when none are configured — and checks `gh`, `caffeinate`, that no verification is already running for the issue, and that the target's plan is fully executed — every step in `.yaawn/plans/yaawn-<target>-progress.md` is `- [x]`. It also lists uncommitted changes (files under `.yaawn/plans/` are left out), because the suites run against the working tree.
48
+ Everything after that — the progress log, uncommitted changes, the suites — is read and run in the resolved worktree. It prints the target, the verification commands — or a note when none are configured — and checks `gh`, `caffeinate`, that no verification is already running for the issue, and that the target's plan is fully executed — every step in `.yaawn/plans/yaawn-<target>-progress.md` is `- [x]`. It also lists uncommitted changes (files under `.yaawn/plans/` are left out), because the suites run against the working tree.
34
49
 
35
- - Any `FAIL` line: stop and report it. A worktree `FAIL` means: start a new session in the named directory and run this skill there. A plan that is not fully executed means `/yaawn-implement-plan` has not finished — finish it first.
50
+ - Any `FAIL` line: stop and report it. A worktree `FAIL` means: fix what it names (unlock, restore, or prune the worktree), then run this skill again. A plan that is not fully executed means `/yaawn-implement-plan` has not finished — finish it first.
36
51
  - Uncommitted changes listed: show them and ask whether to continue.
37
52
  - No verification commands configured: skip steps 3 and 4 — the manual checks are the verification.
38
53
 
@@ -40,7 +55,7 @@ It prints the target, the verification commands — or a note when none are conf
40
55
 
41
56
  `.yaawn/yaawn-definition-of-done.md` is the single source of truth for the standard steps — never read Definition of Done steps from the llm-plan file or the issue. If the file is missing or lists no steps, there are no standard steps to assign.
42
57
 
43
- For each target, read `.yaawn/plans/yaawn-<target>-progress.md` to see which steps the overnight session already checked off or listed as manual checks.
58
+ For each target, read `.yaawn/plans/yaawn-<target>-progress.md` in the worktree pre-flight resolved to see which steps the overnight session already checked off or listed as manual checks.
44
59
 
45
60
  Every standard Definition of Done step must, for every target, be accounted for in exactly one of three places:
46
61
 
@@ -109,17 +124,17 @@ Keep the Mac on power and the editor open. I will report the results here when t
109
124
 
110
125
  Then end your turn and wait. Do not poll the terminal or the summary file — the harness notifies you when the background process ends. Then continue with step 4.
111
126
 
112
- **When the developer asks about the verification later**, in this chat or a new one, check in this order and stop at the first match:
127
+ **When the developer asks about the verification later**, in this chat or a new one, first resolve the issue's worktree: the path pre-flight printed, or — in a new chat — the entry on branch `yaawn/<issue>` in `git worktree list`, falling back to the main copy `<project>/main` when the issue has none. Read every file below from there, never from the session's worktree. Then check in this order and stop at the first match:
113
128
 
114
129
  1. `pgrep -f "yaawn-verify-executed-plan/scripts/overnight.ts run <issue>( |$)"` finds a process → say the verification is still running. Never report a summary while the run is active.
115
- 2. `.yaawn/plans/yaawn-<issue>-verify-summary.md` exists → continue with step 4.
130
+ 2. `<resolved worktree>/.yaawn/plans/yaawn-<issue>-verify-summary.md` exists → continue with step 4.
116
131
  3. Otherwise → say the verification was stopped before it ended and that launching it again reruns it.
117
132
 
118
133
  ## 4. Report the results
119
134
 
120
135
  This step only applies when the overnight verification ran.
121
136
 
122
- Read `.yaawn/plans/yaawn-<issue>-verify-summary.md`. Report one line per suite — pass/fail and the failing specs only. Do not paste full logs; they are in the per-suite log files.
137
+ Read `.yaawn/plans/yaawn-<issue>-verify-summary.md` in the resolved worktree. Report one line per suite — pass/fail and the failing specs only. Do not paste full logs; they are in the per-suite log files.
123
138
 
124
139
  If a suite failed: keep the working files, report the failures, and tell the developer to fix them (resuming `/yaawn-implement-plan` if a step has to be redone) and then start a new session and run this skill again. Do not continue to the manual checks.
125
140
 
@@ -144,11 +159,11 @@ Tell the developer to commit and push the issue's branch, then — once they con
144
159
  npx tsx .agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts finish <issue>
145
160
  ```
146
161
 
147
- It repeats the worktree check, requires the summary's `Result: passed` when verification commands are configured, and requires a branch other than the default branch, no uncommitted changes outside `.yaawn/plans/`, an upstream, and nothing unpushed. Then it finds the branch's open pull request or opens one (`Closes #<issue>`, assigned to the developer), removes the issue's `Yaawn: Ready To Implement` label together with its `Yaawn: implementation-group-<n>` labels (through `/yaawn-implement-plan`'s shared group-label reset — a failed reset only warns), and prints the link. It never commits or pushes.
162
+ It works from any worktree: it repeats the worktree check, moves into the issue's worktree (or the main copy), and checks the issue's branch there. It requires the summary's `Result: passed` when verification commands are configured, and requires a branch other than the default branch, no uncommitted changes outside `.yaawn/plans/`, an upstream, and nothing unpushed. Then it finds the open pull request for the issue's branch or opens one (`Closes #<issue>`, assigned to the developer), removes the issue's `Yaawn: Ready To Implement` label together with its `Yaawn: implementation-group-<n>` labels (through `/yaawn-implement-plan`'s shared group-label reset — a failed reset only warns), and prints the link. It never commits or pushes.
148
163
 
149
164
  Relay its output:
150
165
 
151
166
  - `pull request opened: <url>` or `pull request already open: <url>`: report the link. The issue closes when the pull request is merged.
152
- - Any `FAIL` line: report it and tell the developer what to do — commit and push, or switch to the issue's branch or worktree — and then run this step again.
167
+ - Any `FAIL` line: report it and tell the developer what to do — commit and push in the issue's worktree, or fix the worktree the check names — and then run this step again.
153
168
 
154
169
  Never commit or push yourself, and never offer to close the issue.