@yaawn/workflow 0.0.0-stage → 0.4.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 (101) hide show
  1. package/README.md +172 -2
  2. package/agents/instructions/output-styles/yaawn-eli5.instructions.md +45 -0
  3. package/agents/instructions/yaawn-accessibility.css.instructions.md +58 -0
  4. package/agents/instructions/yaawn-accessibility.html.instructions.md +90 -0
  5. package/agents/instructions/yaawn-accessibility.javascript.instructions.md +50 -0
  6. package/agents/instructions/yaawn-clean-code.typescript.instructions.md +74 -0
  7. package/agents/instructions/yaawn-function-coupling.typescript.instructions.md +100 -0
  8. package/agents/instructions/yaawn-html-css-js.html.instructions.md +32 -0
  9. package/agents/instructions/yaawn-open-source-first.instructions.md +38 -0
  10. package/agents/instructions/yaawn-personas.instructions.md +18 -0
  11. package/agents/instructions/yaawn-semantic-html.html.instructions.md +40 -0
  12. package/agents/instructions/yaawn-vanilla-css.css.instructions.md +50 -0
  13. package/agents/output-styles/yaawn-eli5.instructions.md +45 -0
  14. package/agents/personas/yaawn-developer.md +16 -0
  15. package/agents/personas/yaawn-product-owner.md +18 -0
  16. package/agents/personas/yaawn-reviewer.md +16 -0
  17. package/agents/skills/yaawn-clean-code/SKILL.md +61 -0
  18. package/agents/skills/yaawn-clean-code/references/classes.md +114 -0
  19. package/agents/skills/yaawn-clean-code/references/comments.md +164 -0
  20. package/agents/skills/yaawn-clean-code/references/error-handling.md +151 -0
  21. package/agents/skills/yaawn-clean-code/references/formatting.md +146 -0
  22. package/agents/skills/yaawn-clean-code/references/functions.md +261 -0
  23. package/agents/skills/yaawn-clean-code/references/meaningful-names.md +234 -0
  24. package/agents/skills/yaawn-clean-code/references/objects-and-data-structures.md +130 -0
  25. package/agents/skills/yaawn-clean-code/references/principles.md +76 -0
  26. package/agents/skills/yaawn-clean-code/references/simple-design-and-code-smells.md +130 -0
  27. package/agents/skills/yaawn-clean-code/references/unit-tests.md +114 -0
  28. package/agents/skills/yaawn-create-plan/SKILL.md +234 -0
  29. package/agents/skills/yaawn-grill-bug-report/SKILL.md +31 -0
  30. package/agents/skills/yaawn-grill-bug-report/references/bug-report-checklist.md +27 -0
  31. package/agents/skills/yaawn-grill-design/SKILL.md +68 -0
  32. package/agents/skills/yaawn-grill-design/references/design-checklist.md +36 -0
  33. package/agents/skills/yaawn-grill-other/SKILL.md +31 -0
  34. package/agents/skills/yaawn-grill-other/references/other-checklist.md +19 -0
  35. package/agents/skills/yaawn-grill-refactor/SKILL.md +31 -0
  36. package/agents/skills/yaawn-grill-refactor/references/refactor-checklist.md +26 -0
  37. package/agents/skills/yaawn-grill-spike/SKILL.md +31 -0
  38. package/agents/skills/yaawn-grill-spike/references/spike-checklist.md +25 -0
  39. package/agents/skills/yaawn-grill-story/SKILL.md +34 -0
  40. package/agents/skills/yaawn-grill-story/references/concept-challenge.md +77 -0
  41. package/agents/skills/yaawn-grill-story/references/story-checklist.md +44 -0
  42. package/agents/skills/yaawn-implement-plan/SKILL.md +196 -0
  43. package/agents/skills/yaawn-implement-plan/assets/fix-prompt.md +66 -0
  44. package/agents/skills/yaawn-implement-plan/assets/review-prompt.md +89 -0
  45. package/agents/skills/yaawn-implement-plan/assets/session-prompt.md +80 -0
  46. package/agents/skills/yaawn-implement-plan/scripts/agent.ts +218 -0
  47. package/agents/skills/yaawn-implement-plan/scripts/caffeinate.ts +56 -0
  48. package/agents/skills/yaawn-implement-plan/scripts/groups.ts +233 -0
  49. package/agents/skills/yaawn-implement-plan/scripts/overnight.ts +772 -0
  50. package/agents/skills/yaawn-implement-plan/scripts/parallel.ts +1176 -0
  51. package/agents/skills/yaawn-implement-plan/scripts/processes.ts +53 -0
  52. package/agents/skills/yaawn-implement-plan/scripts/stages.ts +65 -0
  53. package/agents/skills/yaawn-implement-plan/scripts/targets.ts +381 -0
  54. package/agents/skills/yaawn-implement-plan/scripts/worktrees.ts +431 -0
  55. package/agents/skills/yaawn-owasp-security/SKILL.md +106 -0
  56. package/agents/skills/yaawn-owasp-security/references/a01-broken-access-control.md +58 -0
  57. package/agents/skills/yaawn-owasp-security/references/a02-security-misconfiguration.md +56 -0
  58. package/agents/skills/yaawn-owasp-security/references/a03-software-supply-chain-failures.md +56 -0
  59. package/agents/skills/yaawn-owasp-security/references/a04-cryptographic-failures.md +67 -0
  60. package/agents/skills/yaawn-owasp-security/references/a05-injection.md +53 -0
  61. package/agents/skills/yaawn-owasp-security/references/a06-insecure-design.md +48 -0
  62. package/agents/skills/yaawn-owasp-security/references/a07-authentication-failures.md +68 -0
  63. package/agents/skills/yaawn-owasp-security/references/a08-software-data-integrity-failures.md +45 -0
  64. package/agents/skills/yaawn-owasp-security/references/a09-security-logging-alerting-failures.md +59 -0
  65. package/agents/skills/yaawn-owasp-security/references/a10-mishandling-exceptional-conditions.md +63 -0
  66. package/agents/skills/yaawn-prepare-plan/SKILL.md +110 -0
  67. package/agents/skills/yaawn-save-plan/SKILL.md +84 -0
  68. package/agents/skills/yaawn-save-plan/scripts/plan-body.ts +397 -0
  69. package/agents/skills/yaawn-save-plan/scripts/save-plan.ts +539 -0
  70. package/agents/skills/yaawn-split-story/SKILL.md +189 -0
  71. package/agents/skills/yaawn-split-story/references/spider.md +64 -0
  72. package/agents/skills/yaawn-split-story/scripts/create-issue.ts +162 -0
  73. package/agents/skills/yaawn-verify-executed-plan/SKILL.md +154 -0
  74. package/agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts +362 -0
  75. package/agents/skills/yaawn-verify-executed-plan/scripts/pull-request.ts +131 -0
  76. package/agents/skills/yaawn-write-bug-report/SKILL.md +87 -0
  77. package/agents/skills/yaawn-write-other/SKILL.md +90 -0
  78. package/agents/skills/yaawn-write-refactor/SKILL.md +94 -0
  79. package/agents/skills/yaawn-write-spike/SKILL.md +94 -0
  80. package/agents/skills/yaawn-write-user-story/SKILL.md +187 -0
  81. package/agents/skills/yaawn-write-user-story/references/acceptance-criteria.md +186 -0
  82. package/agents/skills/yaawn-write-user-story/references/five-types-of-value.md +82 -0
  83. package/agents/skills/yaawn-write-user-story/references/user-story-fundamentals.md +75 -0
  84. package/agents/yaawn-glossary.md +146 -0
  85. package/agents/yaawn-grilling.md +75 -0
  86. package/agents/yaawn-worktrees.md +69 -0
  87. package/config/install-tasks.json +35 -0
  88. package/config/settings.json +23 -0
  89. package/config/wizard.json +128 -0
  90. package/github/ISSUE_TEMPLATE/yaawn-bug-report.md +59 -0
  91. package/github/ISSUE_TEMPLATE/yaawn-other.md +38 -0
  92. package/github/ISSUE_TEMPLATE/yaawn-refactor.md +38 -0
  93. package/github/ISSUE_TEMPLATE/yaawn-spike.md +16 -0
  94. package/github/ISSUE_TEMPLATE/yaawn-story.md +56 -0
  95. package/package.json +33 -4
  96. package/scripts/install.js +73 -0
  97. package/yaawn/gitignore +1 -0
  98. package/yaawn/yaawn-definition-of-done.md +5 -0
  99. package/yaawn/yaawn-project-glossary.md +7 -0
  100. package/yaawn/yaawn-user-roles.md +3 -0
  101. package/yaawn/yaawn-verification-commands.md +7 -0
@@ -0,0 +1,431 @@
1
+ import { execFile, execFileSync } from "node:child_process";
2
+ import path from "node:path";
3
+ import { promisify } from "node:util";
4
+ import { uncommittedChangesAsync } from "./targets.js";
5
+
6
+ // 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
+ //
9
+ // Layout: the project root holds a bare `.git` repository, the main working
10
+ // copy is a normal checkout at `<project>/main`, and every issue worktree is
11
+ // a sibling of it at `<project>/yaawn-<issue>-<slug>`.
12
+
13
+ export type WorktreeCheckStatus = "ok" | "warn" | "fail";
14
+
15
+ export interface WorktreeCheck {
16
+ status: WorktreeCheckStatus;
17
+ message: string;
18
+ }
19
+
20
+ export interface WorktreeLayout {
21
+ issue: number;
22
+ mainCopy: string;
23
+ 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.
27
+ ownWorktree?: string | null;
28
+ }
29
+
30
+ export interface WorktreeEntry {
31
+ path: string;
32
+ branch: string | null;
33
+ bare: boolean;
34
+ }
35
+
36
+ export interface AheadBehind {
37
+ ahead: number;
38
+ behind: number;
39
+ }
40
+
41
+ // How the main copy's HEAD relates to its upstream branch.
42
+ export type MainCopyState =
43
+ | { kind: "up-to-date" }
44
+ | { kind: "ahead"; ahead: number }
45
+ | { kind: "behind"; behind: number }
46
+ | { kind: "diverged"; ahead: number; behind: number };
47
+
48
+ export type MainCopyCheck =
49
+ | { ok: true; state: MainCopyState }
50
+ | { ok: false; reason: string };
51
+
52
+ export type MainCopySync =
53
+ | { ok: true; state: MainCopyState; updated: boolean }
54
+ | { ok: false; reason: string };
55
+
56
+ const worktreePrefix = "worktree ";
57
+ const branchPrefix = "branch refs/heads/";
58
+ const maxSlugLength = 40;
59
+ const fallbackMainCopyName = "main";
60
+ const fallbackDefaultBranches = ["main", "master"];
61
+
62
+ const aheadBehindPattern = /^(\d+)\s+(\d+)$/;
63
+ const execFileAsync = promisify(execFile);
64
+
65
+ export const branchName = (issue: number): string => `yaawn/${issue}`;
66
+
67
+ export function git(args: string[]): string {
68
+ return execFileSync("git", args, {
69
+ encoding: "utf-8",
70
+ stdio: ["ignore", "pipe", "pipe"],
71
+ });
72
+ }
73
+
74
+ // --- names
75
+
76
+ function assertIssueNumber(issue: number): void {
77
+ if (!Number.isSafeInteger(issue) || issue <= 0) {
78
+ throw new Error(`issue must be a positive integer, got ${issue}`);
79
+ }
80
+ }
81
+
82
+ // Only [a-z0-9-] survives, so the title can never add `..`, `/`, or
83
+ // whitespace to a path.
84
+ export function titleSlug(title: string): string {
85
+ return title
86
+ .normalize("NFKD")
87
+ .replace(/\p{M}/gu, "")
88
+ .toLowerCase()
89
+ .replace(/[^a-z0-9]+/g, "-")
90
+ .replace(/^-+|-+$/g, "")
91
+ .slice(0, maxSlugLength)
92
+ .replace(/-+$/, "");
93
+ }
94
+
95
+ export function worktreeName(issue: number, title: string): string {
96
+ assertIssueNumber(issue);
97
+ const slug = titleSlug(title);
98
+ return slug ? `yaawn-${issue}-${slug}` : `yaawn-${issue}`;
99
+ }
100
+
101
+ // --- porcelain
102
+
103
+ function entryFromBlock(lines: string[]): WorktreeEntry | null {
104
+ const pathLine = lines.find((line) => line.startsWith(worktreePrefix));
105
+ if (pathLine === undefined) return null;
106
+ const branchLine = lines.find((line) => line.startsWith(branchPrefix));
107
+ return {
108
+ path: pathLine.slice(worktreePrefix.length),
109
+ branch:
110
+ branchLine === undefined ? null : branchLine.slice(branchPrefix.length),
111
+ bare: lines.includes("bare"),
112
+ };
113
+ }
114
+
115
+ export function entriesFromPorcelain(porcelain: string): WorktreeEntry[] {
116
+ return porcelain
117
+ .split(/\n\s*\n/)
118
+ .map((block) => entryFromBlock(block.split("\n")))
119
+ .filter((entry): entry is WorktreeEntry => entry !== null);
120
+ }
121
+
122
+ export function worktreesFromPorcelain(porcelain: string): string[] {
123
+ return entriesFromPorcelain(porcelain).map((entry) => entry.path);
124
+ }
125
+
126
+ // git lists the bare repository first, whichever worktree it runs in.
127
+ export function isBareLayout(porcelain: string): boolean {
128
+ return entriesFromPorcelain(porcelain)[0]?.bare === true;
129
+ }
130
+
131
+ // git names the bare repository after the project root; a `.git` suffix is
132
+ // stripped defensively.
133
+ export function projectRootFromPorcelain(porcelain: string): string {
134
+ const [first] = worktreesFromPorcelain(porcelain);
135
+ if (!first) throw new Error("git worktree list returned no worktree");
136
+ return path.basename(first) === ".git" ? path.dirname(first) : first;
137
+ }
138
+
139
+ // The main working copy is the registered worktree on the default branch.
140
+ export function mainCopyFromPorcelain(
141
+ porcelain: string,
142
+ defaultBranches: string[] = fallbackDefaultBranches,
143
+ ): string {
144
+ const checkouts = entriesFromPorcelain(porcelain).filter(
145
+ (entry) => !entry.bare,
146
+ );
147
+ const onDefault = defaultBranches
148
+ .map((branch) => checkouts.find((entry) => entry.branch === branch))
149
+ .find((entry) => entry !== undefined);
150
+ return (
151
+ onDefault?.path ??
152
+ path.join(projectRootFromPorcelain(porcelain), fallbackMainCopyName)
153
+ );
154
+ }
155
+
156
+ export function branchWorktreeFromPorcelain(
157
+ porcelain: string,
158
+ branch: string,
159
+ ): string | null {
160
+ const checkout = entriesFromPorcelain(porcelain).find(
161
+ (entry) => !entry.bare && entry.branch === branch,
162
+ );
163
+ return checkout?.path ?? null;
164
+ }
165
+
166
+ export function worktreePathFor(
167
+ projectRoot: string,
168
+ issue: number,
169
+ title: string,
170
+ ): string {
171
+ return path.join(projectRoot, worktreeName(issue, title));
172
+ }
173
+
174
+ // --- git
175
+
176
+ export const listPorcelain = (): string => git(["worktree", "list", "--porcelain"]);
177
+
178
+ export const currentToplevel = (): string =>
179
+ git(["rev-parse", "--show-toplevel"]).trim();
180
+
181
+ // The bare repository's HEAD names the default branch it was cloned with.
182
+ function bareHeadBranch(): string | null {
183
+ try {
184
+ const commonDir = git([
185
+ "rev-parse",
186
+ "--path-format=absolute",
187
+ "--git-common-dir",
188
+ ]).trim();
189
+ const head = git([
190
+ "--git-dir",
191
+ commonDir,
192
+ "symbolic-ref",
193
+ "--quiet",
194
+ "--short",
195
+ "HEAD",
196
+ ]).trim();
197
+ return head || null;
198
+ } catch {
199
+ return null;
200
+ }
201
+ }
202
+
203
+ export function defaultBranches(): string[] {
204
+ const head = bareHeadBranch();
205
+ return [...new Set([...(head ? [head] : []), ...fallbackDefaultBranches])];
206
+ }
207
+
208
+ export function usesBareLayout(): boolean {
209
+ return isBareLayout(listPorcelain());
210
+ }
211
+
212
+ export function worktreePath(issue: number, title: string): string {
213
+ return worktreePathFor(
214
+ projectRootFromPorcelain(listPorcelain()),
215
+ issue,
216
+ title,
217
+ );
218
+ }
219
+
220
+ // The branch's existing checkout wins over the title-derived path, so a
221
+ // relaunch resumes the earlier worktree even after the issue was renamed.
222
+ export function laneWorktreePath(issue: number, title: string): string {
223
+ const porcelain = listPorcelain();
224
+ return (
225
+ branchWorktreeFromPorcelain(porcelain, branchName(issue)) ??
226
+ worktreePathFor(projectRootFromPorcelain(porcelain), issue, title)
227
+ );
228
+ }
229
+
230
+ export function registeredWorktrees(): Set<string> {
231
+ return new Set(worktreesFromPorcelain(listPorcelain()));
232
+ }
233
+
234
+ const normalized = (worktree: string): string => path.resolve(worktree);
235
+
236
+ const samePath = (left: string, right: string): boolean =>
237
+ normalized(left) === normalized(right);
238
+
239
+ export function runsInOwnWorktree(issue: number, title: string): boolean {
240
+ return samePath(worktreePath(issue, title), currentToplevel());
241
+ }
242
+
243
+ // --- classification
244
+
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
+ };
252
+ }
253
+
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
+ };
268
+ }
269
+
270
+ 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);
280
+ }
281
+
282
+ export function checkWorktree(issue: number): WorktreeCheck {
283
+ const porcelain = listPorcelain();
284
+ return classifyWorktree({
285
+ issue,
286
+ mainCopy: mainCopyFromPorcelain(porcelain, defaultBranches()),
287
+ registered: worktreesFromPorcelain(porcelain),
288
+ currentToplevel: currentToplevel(),
289
+ ownWorktree: branchWorktreeFromPorcelain(porcelain, branchName(issue)),
290
+ });
291
+ }
292
+
293
+ // --- main copy sync
294
+
295
+ // Parses `git rev-list --left-right --count HEAD...@{u}`: "<ahead>\t<behind>".
296
+ export function parseAheadBehind(output: string): AheadBehind | null {
297
+ const match = aheadBehindPattern.exec(output.trim());
298
+ if (match === null) return null;
299
+ return { ahead: Number(match[1]), behind: Number(match[2]) };
300
+ }
301
+
302
+ export function classifyMainCopyState({
303
+ ahead,
304
+ behind,
305
+ }: AheadBehind): MainCopyState {
306
+ if (ahead > 0 && behind > 0) return { kind: "diverged", ahead, behind };
307
+ if (behind > 0) return { kind: "behind", behind };
308
+ if (ahead > 0) return { kind: "ahead", ahead };
309
+ return { kind: "up-to-date" };
310
+ }
311
+
312
+ const commits = (count: number): string =>
313
+ count === 1 ? "1 commit" : `${count} commits`;
314
+
315
+ export function describeMainCopyState(state: MainCopyState): string {
316
+ switch (state.kind) {
317
+ case "up-to-date":
318
+ return "main copy is up to date";
319
+ case "ahead":
320
+ return `main copy is ${commits(state.ahead)} ahead — lanes start from it`;
321
+ case "behind":
322
+ return `main copy is ${commits(state.behind)} behind — the launch will bring it up to date`;
323
+ case "diverged":
324
+ return `main copy has diverged (${state.ahead} ahead, ${state.behind} behind) — reconcile it by hand before launching`;
325
+ }
326
+ }
327
+
328
+ // git's stderr may echo a remote URL with credentials in it.
329
+ function redactedGitError(error: unknown): string {
330
+ const stderr =
331
+ typeof error === "object" && error !== null && "stderr" in error
332
+ ? String(error.stderr)
333
+ : "";
334
+ const line = stderr
335
+ .split("\n")
336
+ .map((text) => text.trim())
337
+ .find(Boolean);
338
+ return line ? `: ${line.replace(/\/\/[^/\s@]+@/g, "//***@")}` : "";
339
+ }
340
+
341
+ // Never waits for a credential prompt: an unattended launch would hang.
342
+ async function gitAsync(args: string[]): Promise<string> {
343
+ const { stdout } = await execFileAsync("git", args, {
344
+ encoding: "utf-8",
345
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
346
+ });
347
+ return stdout;
348
+ }
349
+
350
+ async function hasUpstream(): Promise<boolean> {
351
+ try {
352
+ await gitAsync(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
353
+ return true;
354
+ } catch {
355
+ return false;
356
+ }
357
+ }
358
+
359
+ async function mainCopyAheadBehind(): Promise<AheadBehind | null> {
360
+ try {
361
+ return parseAheadBehind(
362
+ await gitAsync(["rev-list", "--left-right", "--count", "HEAD...@{u}"]),
363
+ );
364
+ } catch {
365
+ return null;
366
+ }
367
+ }
368
+
369
+ // Fetches the main copy's upstream and compares; it never changes the main copy.
370
+ export async function fetchMainCopy(): Promise<MainCopyCheck> {
371
+ if (!(await hasUpstream())) {
372
+ return {
373
+ ok: false,
374
+ reason:
375
+ "main copy's branch has no upstream — set one with `git branch --set-upstream-to`",
376
+ };
377
+ }
378
+ try {
379
+ await gitAsync(["fetch", "--quiet"]);
380
+ } catch (error) {
381
+ return {
382
+ ok: false,
383
+ reason: `could not fetch the main copy's upstream — remote unreachable${redactedGitError(error)}`,
384
+ };
385
+ }
386
+ const counts = await mainCopyAheadBehind();
387
+ return counts === null
388
+ ? { ok: false, reason: "could not compare the main copy with its upstream" }
389
+ : { ok: true, state: classifyMainCopyState(counts) };
390
+ }
391
+
392
+ export function blockedUpdateReason(behind: number): string {
393
+ return `main copy is ${commits(behind)} behind and uncommitted changes block the update — commit or stash them first`;
394
+ }
395
+
396
+ async function uncommittedChangesFailure(behind: number): Promise<string | null> {
397
+ try {
398
+ return (await uncommittedChangesAsync()).trim()
399
+ ? blockedUpdateReason(behind)
400
+ : null;
401
+ } catch {
402
+ return "could not list uncommitted changes in the main copy";
403
+ }
404
+ }
405
+
406
+ async function fastForwardMainCopy(): Promise<string | null> {
407
+ try {
408
+ await gitAsync(["merge", "--ff-only", "--quiet", "@{u}"]);
409
+ return null;
410
+ } catch (error) {
411
+ return `could not fast-forward the main copy${redactedGitError(error)}`;
412
+ }
413
+ }
414
+
415
+ // Fails closed: the main copy is only fast-forwarded when it is behind and
416
+ // clean; any unknown state stops the launch before a worktree exists.
417
+ export async function syncMainCopy(): Promise<MainCopySync> {
418
+ const check = await fetchMainCopy();
419
+ if (!check.ok) return check;
420
+ const { state } = check;
421
+ if (state.kind === "diverged") {
422
+ return { ok: false, reason: describeMainCopyState(state) };
423
+ }
424
+ if (state.kind !== "behind") return { ok: true, state, updated: false };
425
+ const failure =
426
+ (await uncommittedChangesFailure(state.behind)) ??
427
+ (await fastForwardMainCopy());
428
+ return failure === null
429
+ ? { ok: true, state, updated: true }
430
+ : { ok: false, reason: failure };
431
+ }
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: yaawn-owasp-security
3
+ description: "Background security lens based on the OWASP Top 10:2025. Read silently when proposing designs, plans, or code — bake mitigations in autonomously. Only escalate to the developer when a risk cannot be mitigated without their input. Use when: creating an implementation plan (/yaawn-create-plan), grilling a design (/yaawn-grill-design), implementing a plan (/yaawn-implement-plan), reviewing code for security risks, or when the user asks to make something secure, check for vulnerabilities, harden, or audit code."
4
+ user-invocable: false
5
+ ---
6
+
7
+ # OWASP Top 10:2025 Security Lens
8
+
9
+ Applies the [OWASP Top 10:2025](https://top10.owasp.org/2025/) as a background security lens across the yaawn plan pipeline. This is **not** a checklist to grill through. The agent reads the relevant risks silently, bakes mitigations into every design decision, plan step, and line of code it proposes — and only surfaces a risk to the developer when it cannot resolve it on its own.
10
+
11
+ This skill **complements** the pipeline skills. Load it alongside `/yaawn-create-plan`, `/yaawn-grill-design`, or `/yaawn-implement-plan`.
12
+
13
+ ## How It Works
14
+
15
+ ```
16
+ Agent reads the issue / design / code being proposed
17
+ │
18
+ ▼
19
+ Match against the routing table below → load ONLY the relevant reference files
20
+ │
21
+ ▼
22
+ Silently check the proposal against each loaded risk
23
+ │
24
+ ┌────┴────┐
25
+ ▼ ▼
26
+ Covered Cannot mitigate alone
27
+ │ │
28
+ ▼ ▼
29
+ Bake the Escalate with a recommended
30
+ mitigation mitigation — never ask an
31
+ into the open-ended "how do you want
32
+ proposal to handle security?"
33
+ ```
34
+
35
+ ## Routing Table
36
+
37
+ Read the issue, design decisions, or code being proposed. Match what it touches against the keywords below. Load **only** the reference files that match. Each reference file is self-contained — it has the OWASP risk details, design-phase guidance, and implementation checks for that risk.
38
+
39
+ | What the plan or code touches | Keywords to match | Load |
40
+ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
41
+ | New API endpoints, routes, permissions, roles, data access by ID, cross-domain calls, CORS, redirects | endpoint, route, permission, role, authorize, IDOR, ownership, CORS, redirect, URL routing | [a01-broken-access-control.md](./references/a01-broken-access-control.md) |
42
+ | New config, env vars, dependencies, services, error messages, cookies, headers, deployment, debug | config, env, cookie, header, CSP, HSTS, debug, sample, default, hardening, secret | [a02-security-misconfiguration.md](./references/a02-security-misconfiguration.md) |
43
+ | Adding dependencies, CI/CD changes, build scripts, Docker images, package registries | dependency, npm, package, CI, pipeline, action, Docker, image, registry, lockfile, SBOM | [a03-software-supply-chain-failures.md](./references/a03-software-supply-chain-failures.md) |
44
+ | Passwords, tokens, encryption, hashing, randomness, sensitive data storage, TLS, keys | password, token, encrypt, hash, random, salt, key, TLS, HSTS, PII, sensitive data, credential | [a04-cryptographic-failures.md](./references/a04-cryptographic-failures.md) |
45
+ | User input, database queries, HTML rendering, file uploads, shell commands, redirects, ORM | input, query, SQL, ORM, render, XSS, upload, file, eval, shell, command, sanitize, escape, validate | [a05-injection.md](./references/a05-injection.md) |
46
+ | New trust boundaries, auth flows, payment flows, business logic, tenant isolation, client-side enforcement | trust boundary, auth flow, payment, business logic, tenant, isolation, client-side, misuse case, abuse case | [a06-insecure-design.md](./references/a06-insecure-design.md) |
47
+ | Login, registration, sessions, password reset, MFA, JWT, SSO, account enumeration | login, signin, register, session, password reset, MFA, 2FA, JWT, SSO, token, brute force, lockout | [a07-authentication-failures.md](./references/a07-authentication-failures.md) |
48
+ | External data sources, webhooks, deserialization, auto-updates, plugins, CI/CD integrity | webhook, callback, external data, deserialize, serialize, auto-update, plugin, extension, signature, checksum | [a08-software-data-integrity-failures.md](./references/a08-software-data-integrity-failures.md) |
49
+ | Any code path handling security events, errors, or transactions | log, audit, alert, monitor, security event, auth failure, access denied, validation failure | [a09-security-logging-alerting-failures.md](./references/a09-security-logging-alerting-failures.md) |
50
+ | Error handling, multi-step transactions, resource cleanup, rate limiting, concurrency | error, exception, catch, throw, rollback, transaction, finally, cleanup, rate limit, throttle, concurrency, race | [a10-mishandling-exceptional-conditions.md](./references/a10-mishandling-exceptional-conditions.md) |
51
+
52
+ ### Always consider
53
+
54
+ These risks apply broadly — load them whenever the plan or code has the corresponding shape, even if no specific keyword matches:
55
+
56
+ - **A06 (Insecure Design)** — load whenever the proposal introduces a trust boundary, a security-critical flow, or client-side enforcement of a security control.
57
+ - **A09 (Logging)** — load whenever the code handles authentication, authorization, input validation, or transactions. These events must be logged.
58
+ - **A10 (Exception Handling)** — load whenever the code has error paths, multi-step operations, or resource management. Errors must fail closed and not leak details.
59
+
60
+ ### Loading strategy
61
+
62
+ - **Plan creation / grilling:** scan the issue and design decisions, load 2–4 matching references, apply silently when proposing decisions.
63
+ - **Implementation:** scan the step being implemented, load 1–3 matching references, verify code against the implementation checks before marking done.
64
+ - **Code review:** scan the diff, load matching references, check the changed code.
65
+ - **Don't load all 10.** A plan that adds a search endpoint needs A01, A05, A09, and A10 — not A04 or A07.
66
+
67
+ ## The 10 Risks at a Glance
68
+
69
+ | # | Risk | One-line summary |
70
+ | --- | -------------------------------------- | ----------------------------------------------------------------------------------------- |
71
+ | A01 | Broken Access Control | Users can act outside their intended permissions — enforce deny-by-default on the server. |
72
+ | A02 | Security Misconfiguration | Unnecessary features, default credentials, missing headers, verbose errors. |
73
+ | A03 | Software Supply Chain Failures | Vulnerable, unmaintained, or untrusted dependencies and build tooling. |
74
+ | A04 | Cryptographic Failures | Weak algorithms, missing encryption, hard-coded keys, poor randomness. |
75
+ | A05 | Injection | Untrusted input executed as commands — SQL, OS, XSS, LDAP, expression language. |
76
+ | A06 | Insecure Design | Missing or ineffective security controls in the architecture and business logic. |
77
+ | A07 | Authentication Failures | Weak credentials, no MFA, session fixation, brute-force exposure. |
78
+ | A08 | Software or Data Integrity Failures | Unverified updates, unsigned artifacts, insecure deserialization. |
79
+ | A09 | Security Logging and Alerting Failures | Missing logs for security events, no alerting, sensitive data in logs. |
80
+ | A10 | Mishandling of Exceptional Conditions | Errors leak data, fail open, crash the system, or corrupt state. |
81
+
82
+ ## Rules
83
+
84
+ - **Silent by default.** The agent applies security autonomously. The developer should not be asked about risks the agent can handle.
85
+ - **Deny by default.** When in doubt, the secure option wins. The burden of proof is on the insecure option.
86
+ - **Bake, don't bolt on.** Mitigations go into the design and plan from the start — not as a post-hoc review layer.
87
+ - **Escalate with a recommendation.** When escalation is unavoidable, present the risk and a recommended mitigation. The developer confirms or adjusts — they are not asked to solve it from scratch.
88
+ - **Never log or commit secrets.** This applies to the agent's own output — no credentials, tokens, or keys in plan files, decision logs, code, or issue comments.
89
+ - **Record security decisions in the decision log.** When the agent bakes a mitigation into a proposal, log it briefly: `- Security (Axx): <what was done> — <why>`. One line. This keeps the plan auditable without bloating the conversation.
90
+
91
+ ## Gotchas
92
+
93
+ - **A06 Insecure Design is the meta-risk.** If the proposed design itself is insecure (missing trust boundaries, client-side-only enforcement, no threat model for the flow), no amount of careful coding fixes it. The agent should revise the design proposal, not just the implementation. Only escalate if the design constraint comes from the developer.
94
+ - **A03 and A08 overlap.** A03 covers the supply chain (dependencies, build tools, CI/CD). A08 covers integrity verification (signatures, deserialization, auto-updates). The agent checks both when a proposal adds a dependency.
95
+ - **A09 is easy to skip silently.** The agent should proactively include logging for security-relevant events in its proposals — not wait to be asked. Auth failures, access denials, and input validation failures are always in scope.
96
+ - **A10 is new in 2025.** Error handling is a security control: fail closed, don't leak stack traces, roll back partial transactions. The agent bakes this into every proposal that touches error paths.
97
+ - **Don't over-escalate.** If the agent can propose a secure default and move on, it should. Escalation is for genuine decisions, not for every risk that crosses its desk.
98
+
99
+ ## Done When
100
+
101
+ Before finishing a plan, grill, implementation step, or code review with this lens loaded:
102
+
103
+ - [ ] Every reference matching the routing table was loaded and checked against the proposal
104
+ - [ ] Mitigations are baked into the proposal itself — not listed as follow-up actions
105
+ - [ ] Each baked mitigation is recorded in the decision log as `- Security (Axx): <what was done> — <why>`
106
+ - [ ] Anything the agent cannot mitigate is escalated with a recommended mitigation
@@ -0,0 +1,58 @@
1
+ # A01:2025 — Broken Access Control
2
+
3
+ **Rank:** #1 (100% of applications tested had some form of broken access control)
4
+
5
+ Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification or destruction of all data, or performing a business function outside the user's limits.
6
+
7
+ ## You are vulnerable if
8
+
9
+ - Violation of the principle of least privilege (deny by default) — access available to anyone
10
+ - Bypassing access control checks by modifying the URL (parameter tampering, force browsing), internal application state, or the HTML page
11
+ - Permitting viewing or editing someone else's account by providing its unique identifier (insecure direct object references / IDOR)
12
+ - APIs with missing access controls for POST, PUT, and DELETE
13
+ - Elevation of privilege — acting as a user without being logged in, or gaining admin access
14
+ - Metadata manipulation — replaying or tampering with JWT tokens, cookies, or hidden fields to elevate privileges
15
+ - CORS misconfiguration allowing API access from unauthorized or untrusted origins
16
+ - Force browsing (guessing URLs) to authenticated or privileged pages
17
+
18
+ ## How to prevent (OWASP)
19
+
20
+ - Access control is only effective in trusted server-side code or serverless APIs
21
+ - Except for public resources, **deny by default**
22
+ - Implement access control mechanisms once and reuse them throughout the application; minimize CORS usage
23
+ - Enforce record ownership rather than allowing users to CRUD any record
24
+ - Enforce unique business limit requirements via domain models
25
+ - Disable web server directory listing; ensure file metadata (e.g. `.git`) and backup files are not in web roots
26
+ - Log access control failures; alert admins on repeated failures
27
+ - Implement rate limits on API and controller access
28
+ - Invalidate stateful session identifiers on logout; use short-lived JWTs
29
+ - Use well-established toolkits or patterns for simple, declarative access controls
30
+ - Include functional access control in unit and integration tests
31
+
32
+ ## Design phase — silently check and bake in
33
+
34
+ - Default to deny: new routes, endpoints, and data access are restricted unless the proposal explicitly makes them public.
35
+ - If the proposal reads or modifies records by ID, bake in an ownership check.
36
+ - If the proposal adds an API endpoint, bake in server-side authorization — not just authentication.
37
+ - If the proposal crosses a domain boundary, bake in the access control at the target domain's public API.
38
+ - If the proposal touches CORS, URL routing, or redirects, bake in a review step.
39
+ - If the proposal introduces client-side access control (hiding buttons, route guards), flag it as insufficient and bake in the server-side equivalent.
40
+
41
+ ## Design phase — escalate when
42
+
43
+ The proposal requires a permission model the agent cannot infer (e.g. a new role hierarchy, custom RBAC rules) — propose a default role model and ask for confirmation.
44
+
45
+ ## Implementation checks
46
+
47
+ - Every new endpoint/action checks authorization server-side — not just authentication
48
+ - Record access by ID includes an ownership check
49
+ - New routes, endpoints, and data access are denied by default unless explicitly public
50
+ - Privileged URLs return 403/404 for unauthorized users — no force-browsing
51
+ - CORS is not widened to untrusted origins
52
+ - Access control failures are logged
53
+ - Rate limits are in place on new API endpoints
54
+ - Tests cover: unauthorized access denied, cross-user access denied, privilege escalation blocked
55
+
56
+ ## Key CWEs
57
+
58
+ CWE-200, CWE-201, CWE-352 (CSRF), CWE-639 (IDOR), CWE-862 (Missing Authorization), CWE-863 (Incorrect Authorization), CWE-918 (SSRF), CWE-22 (Path Traversal), CWE-601 (Open Redirect)
@@ -0,0 +1,56 @@
1
+ # A02:2025 — Security Misconfiguration
2
+
3
+ **Rank:** #2 (moved up from #5; 100% of applications tested had some form of misconfiguration)
4
+
5
+ Security misconfiguration is when a system, application, or cloud service is set up incorrectly from a security perspective, creating vulnerabilities.
6
+
7
+ ## You are vulnerable if
8
+
9
+ - Missing appropriate security hardening across any part of the application stack, or improperly configured permissions on cloud services
10
+ - Unnecessary features are enabled or installed (ports, services, pages, accounts, testing frameworks, privileges)
11
+ - Default accounts and their passwords are still enabled and unchanged
12
+ - Error handling reveals stack traces or overly informative error messages to users
13
+ - For upgraded systems, the latest security features are disabled or not configured securely
14
+ - Excessive prioritization of backward compatibility leading to insecure configuration
15
+ - Security settings in application servers, frameworks, libraries, databases are not set to secure values
16
+ - The server does not send security headers or directives, or they are not set to secure values
17
+
18
+ ## How to prevent (OWASP)
19
+
20
+ - Implement a repeatable hardening process for fast, easy deployment of locked-down environments
21
+ - Use a minimal platform without unnecessary features, components, documentation, or samples
22
+ - Review and update configurations as part of patch management; review cloud storage permissions
23
+ - Use a segmented application architecture with separation between components or tenants
24
+ - Send security directives to clients (security headers)
25
+ - Automate verification of configurations and settings in all environments
26
+ - Proactively add central configuration to intercept excessive error messages
27
+ - Use identity federation, short-lived credentials, or role-based access — never embed static keys or secrets in code, configuration files, or pipelines
28
+
29
+ ## Design phase — silently check and bake in
30
+
31
+ - New env vars or config values: bake in secure defaults and document them.
32
+ - New error messages: bake in generic user-facing messages with detailed logs.
33
+ - New dependencies or services: bake in a hardening step (remove defaults, disable debug, set secure headers).
34
+ - Any secret the proposal needs: bake in environment variable or secret manager usage — never hard-coded.
35
+ - Cookie settings: bake in `Secure`, `HttpOnly`, `SameSite`.
36
+ - Security headers: if the proposal touches HTTP responses, bake in CSP, HSTS, X-Content-Type-Options.
37
+
38
+ ## Design phase — escalate when
39
+
40
+ The proposal requires infrastructure configuration the agent cannot make (e.g. cloud IAM, CDN settings) — list it as a manual step in the plan.
41
+
42
+ ## Implementation checks
43
+
44
+ - No default credentials in any environment, config file, or seed data
45
+ - No unnecessary features, debug endpoints, or sample data in the build
46
+ - Security headers are set (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy)
47
+ - Error responses do not include stack traces, file paths, or system details
48
+ - No secrets in source code, config files, or committed environment files
49
+ - Secrets come from environment variables or a secret manager
50
+ - Cookie settings: `Secure`, `HttpOnly`, `SameSite` set appropriately
51
+ - Directory listing is disabled
52
+ - `.git`, `.env`, and backup files are not accessible from the web root
53
+
54
+ ## Key CWEs
55
+
56
+ CWE-16 (Configuration), CWE-611 (XXE), CWE-260 (Password in Configuration File), CWE-547 (Hard-coded Constants), CWE-489 (Active Debug Code), CWE-942 (Permissive Cross-domain Policy)