@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.
- package/README.md +22 -43
- package/agents/personas/yaawn-product-owner.md +3 -3
- package/agents/skills/yaawn-create-plan/SKILL.md +38 -11
- package/agents/skills/yaawn-extract-learnings/SKILL.md +138 -0
- package/agents/skills/yaawn-grill-bug-report/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-other/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-prototype/SKILL.md +38 -0
- package/agents/skills/yaawn-grill-prototype/references/prototype-checklist.md +23 -0
- package/agents/skills/yaawn-grill-refactor/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-spike/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-story/SKILL.md +7 -0
- package/agents/skills/yaawn-implement-plan/SKILL.md +10 -8
- package/agents/skills/yaawn-implement-plan/scripts/agent.ts +1 -1
- package/agents/skills/yaawn-implement-plan/scripts/overnight.ts +7 -4
- package/agents/skills/yaawn-implement-plan/scripts/parallel.ts +155 -12
- package/agents/skills/yaawn-implement-plan/scripts/processes.ts +161 -1
- package/agents/skills/yaawn-implement-plan/scripts/targets.ts +13 -2
- package/agents/skills/yaawn-implement-plan/scripts/worktrees.ts +219 -52
- package/agents/skills/yaawn-prepare-plan/SKILL.md +20 -19
- package/agents/skills/yaawn-verify-executed-plan/SKILL.md +28 -13
- package/agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts +44 -12
- package/agents/skills/yaawn-write-bug-report/SKILL.md +6 -5
- package/agents/skills/yaawn-write-other/SKILL.md +6 -5
- package/agents/skills/yaawn-write-prototype/SKILL.md +96 -0
- package/agents/skills/yaawn-write-refactor/SKILL.md +6 -5
- package/agents/skills/yaawn-write-spike/SKILL.md +1 -1
- package/agents/skills/yaawn-write-user-story/SKILL.md +3 -2
- package/agents/yaawn-glossary.md +4 -3
- package/config/install-tasks.json +0 -1
- package/config/wizard.json +3 -8
- package/dist/cli/args.js +66 -0
- package/dist/cli/errors.js +103 -0
- package/dist/cli/exit-codes.js +3 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/install-report.js +92 -0
- package/dist/cli/install.js +124 -0
- package/dist/cli/main.js +17 -0
- package/dist/cli/package-root.js +17 -0
- package/dist/cli/project-root.js +11 -0
- package/dist/cli/run.js +141 -0
- package/dist/cli/session.js +1 -0
- package/dist/cli/terminal.js +5 -0
- package/dist/cli/uninstall.js +89 -0
- package/dist/cli/version.js +7 -0
- package/dist/config/agents.js +108 -0
- package/dist/config/config.js +118 -0
- package/dist/config/errors.js +45 -0
- package/dist/config/index.js +11 -0
- package/dist/config/manifest.js +28 -0
- package/dist/config/messages.js +86 -0
- package/dist/config/names.js +40 -0
- package/dist/config/paths.js +73 -0
- package/dist/config/settings.js +101 -0
- package/dist/config/strict-json.js +119 -0
- package/dist/config/tasks.js +77 -0
- package/dist/config/wizard.js +139 -0
- package/dist/display/index.js +3 -0
- package/dist/display/palette.js +61 -0
- package/dist/display/renderer.js +169 -0
- package/dist/display/report.js +15 -0
- package/dist/display/sanitize.js +10 -0
- package/dist/link/errors.js +34 -0
- package/dist/link/fs-utils.js +79 -0
- package/dist/link/index.js +4 -0
- package/dist/link/install.js +20 -0
- package/dist/link/linker.js +137 -0
- package/dist/link/memory.js +221 -0
- package/dist/link/names.js +30 -0
- package/dist/link/types.js +30 -0
- package/dist/link/uninstall.js +90 -0
- package/dist/sync/apply.js +74 -0
- package/dist/sync/changes.js +31 -0
- package/dist/sync/fs-utils.js +63 -0
- package/dist/sync/index.js +7 -0
- package/dist/sync/install.js +57 -0
- package/dist/sync/languages.js +80 -0
- package/dist/sync/plan.js +146 -0
- package/dist/sync/projects.js +294 -0
- package/dist/sync/uninstall.js +234 -0
- package/dist/wizard/build.js +89 -0
- package/dist/wizard/index.js +5 -0
- package/dist/wizard/items.js +83 -0
- package/dist/wizard/prompts.js +167 -0
- package/dist/wizard/section.js +143 -0
- package/dist/wizard/wizard.js +73 -0
- package/github/ISSUE_TEMPLATE/yaawn-prototype.md +48 -0
- package/package.json +11 -10
- package/agents/instructions/output-styles/yaawn-eli5.instructions.md +0 -45
- 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 (
|
|
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
|
-
|
|
25
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
240
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
256
|
-
mainCopy
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
: {
|
|
265
|
-
|
|
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 {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
423
|
+
function layoutFromGit(
|
|
424
|
+
issue: number,
|
|
425
|
+
labels: readonly string[],
|
|
426
|
+
title: string,
|
|
427
|
+
): WorktreeLayout {
|
|
283
428
|
const porcelain = listPorcelain();
|
|
284
|
-
|
|
429
|
+
const entries = entriesFromPorcelain(porcelain);
|
|
430
|
+
return {
|
|
285
431
|
issue,
|
|
286
432
|
mainCopy: mainCopyFromPorcelain(porcelain, defaultBranches()),
|
|
287
|
-
registered:
|
|
288
|
-
|
|
289
|
-
|
|
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…
|
|
37
|
-
| ------------ |
|
|
38
|
-
| Story | adds or changes behavior users see
|
|
39
|
-
| Refactor | changes the inside, with no user-visible change
|
|
40
|
-
| Spike | must answer one open question before work starts
|
|
41
|
-
| Bug report | fixes behavior that is wrong today
|
|
42
|
-
|
|
|
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
|
|
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),
|
|
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"
|
|
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
|
-
|
|
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
|
|
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
|
|
30
|
-
- The issue has no worktree of its own
|
|
31
|
-
- The
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|