@runchr/gstack-antigravity 0.1.1 → 0.1.3

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.

Potentially problematic release.


This version of @runchr/gstack-antigravity might be problematic. Click here for more details.

Files changed (229) hide show
  1. package/.agents/skills/gstack/.agents/skills/gstack/SKILL.md +651 -0
  2. package/.agents/skills/gstack/.agents/skills/gstack-autoplan/SKILL.md +678 -0
  3. package/.agents/skills/gstack/.agents/skills/gstack-benchmark/SKILL.md +482 -0
  4. package/.agents/skills/gstack/.agents/skills/gstack-browse/SKILL.md +511 -0
  5. package/.agents/skills/gstack/.agents/skills/gstack-canary/SKILL.md +486 -0
  6. package/.agents/skills/gstack/.agents/skills/gstack-careful/SKILL.md +50 -0
  7. package/.agents/skills/gstack/.agents/skills/gstack-cso/SKILL.md +607 -0
  8. package/.agents/skills/gstack/.agents/skills/gstack-design-consultation/SKILL.md +615 -0
  9. package/.agents/skills/gstack/.agents/skills/gstack-design-review/SKILL.md +988 -0
  10. package/.agents/skills/gstack/.agents/skills/gstack-document-release/SKILL.md +604 -0
  11. package/.agents/skills/gstack/.agents/skills/gstack-freeze/SKILL.md +67 -0
  12. package/.agents/skills/gstack/.agents/skills/gstack-guard/SKILL.md +62 -0
  13. package/.agents/skills/gstack/.agents/skills/gstack-investigate/SKILL.md +415 -0
  14. package/.agents/skills/gstack/.agents/skills/gstack-land-and-deploy/SKILL.md +873 -0
  15. package/.agents/skills/gstack/.agents/skills/gstack-office-hours/SKILL.md +986 -0
  16. package/.agents/skills/gstack/.agents/skills/gstack-plan-ceo-review/SKILL.md +1268 -0
  17. package/.agents/skills/gstack/.agents/skills/gstack-plan-design-review/SKILL.md +668 -0
  18. package/.agents/skills/gstack/.agents/skills/gstack-plan-eng-review/SKILL.md +826 -0
  19. package/.agents/skills/gstack/.agents/skills/gstack-qa/SKILL.md +1006 -0
  20. package/.agents/skills/gstack/.agents/skills/gstack-qa-only/SKILL.md +626 -0
  21. package/.agents/skills/gstack/.agents/skills/gstack-retro/SKILL.md +1065 -0
  22. package/.agents/skills/gstack/.agents/skills/gstack-review/SKILL.md +704 -0
  23. package/.agents/skills/gstack/.agents/skills/gstack-setup-browser-cookies/SKILL.md +325 -0
  24. package/.agents/skills/gstack/.agents/skills/gstack-setup-deploy/SKILL.md +450 -0
  25. package/.agents/skills/gstack/.agents/skills/gstack-ship/SKILL.md +1312 -0
  26. package/.agents/skills/gstack/.agents/skills/gstack-unfreeze/SKILL.md +36 -0
  27. package/.agents/skills/gstack/.agents/skills/gstack-upgrade/SKILL.md +220 -0
  28. package/.agents/skills/gstack/.env.example +5 -0
  29. package/.agents/skills/gstack/.github/workflows/skill-docs.yml +17 -0
  30. package/.agents/skills/gstack/AGENTS.md +49 -0
  31. package/.agents/skills/gstack/ARCHITECTURE.md +359 -0
  32. package/.agents/skills/gstack/BROWSER.md +271 -0
  33. package/.agents/skills/gstack/CHANGELOG.md +800 -0
  34. package/.agents/skills/gstack/CLAUDE.md +284 -0
  35. package/.agents/skills/gstack/CONTRIBUTING.md +370 -0
  36. package/.agents/skills/gstack/ETHOS.md +129 -0
  37. package/.agents/skills/gstack/LICENSE +21 -0
  38. package/.agents/skills/gstack/README.md +228 -0
  39. package/.agents/skills/gstack/SKILL.md +657 -0
  40. package/.agents/skills/gstack/SKILL.md.tmpl +281 -0
  41. package/.agents/skills/gstack/TODOS.md +564 -0
  42. package/.agents/skills/gstack/VERSION +1 -0
  43. package/.agents/skills/gstack/autoplan/SKILL.md +689 -0
  44. package/.agents/skills/gstack/autoplan/SKILL.md.tmpl +416 -0
  45. package/.agents/skills/gstack/benchmark/SKILL.md +489 -0
  46. package/.agents/skills/gstack/benchmark/SKILL.md.tmpl +233 -0
  47. package/.agents/skills/gstack/bin/dev-setup +68 -0
  48. package/.agents/skills/gstack/bin/dev-teardown +56 -0
  49. package/.agents/skills/gstack/bin/gstack-analytics +191 -0
  50. package/.agents/skills/gstack/bin/gstack-community-dashboard +113 -0
  51. package/.agents/skills/gstack/bin/gstack-config +38 -0
  52. package/.agents/skills/gstack/bin/gstack-diff-scope +71 -0
  53. package/.agents/skills/gstack/bin/gstack-global-discover.ts +591 -0
  54. package/.agents/skills/gstack/bin/gstack-repo-mode +93 -0
  55. package/.agents/skills/gstack/bin/gstack-review-log +9 -0
  56. package/.agents/skills/gstack/bin/gstack-review-read +12 -0
  57. package/.agents/skills/gstack/bin/gstack-slug +15 -0
  58. package/.agents/skills/gstack/bin/gstack-telemetry-log +158 -0
  59. package/.agents/skills/gstack/bin/gstack-telemetry-sync +127 -0
  60. package/.agents/skills/gstack/bin/gstack-update-check +196 -0
  61. package/.agents/skills/gstack/browse/SKILL.md +517 -0
  62. package/.agents/skills/gstack/browse/SKILL.md.tmpl +141 -0
  63. package/.agents/skills/gstack/browse/bin/find-browse +21 -0
  64. package/.agents/skills/gstack/browse/bin/remote-slug +14 -0
  65. package/.agents/skills/gstack/browse/scripts/build-node-server.sh +48 -0
  66. package/.agents/skills/gstack/browse/src/browser-manager.ts +634 -0
  67. package/.agents/skills/gstack/browse/src/buffers.ts +137 -0
  68. package/.agents/skills/gstack/browse/src/bun-polyfill.cjs +109 -0
  69. package/.agents/skills/gstack/browse/src/cli.ts +420 -0
  70. package/.agents/skills/gstack/browse/src/commands.ts +111 -0
  71. package/.agents/skills/gstack/browse/src/config.ts +150 -0
  72. package/.agents/skills/gstack/browse/src/cookie-import-browser.ts +417 -0
  73. package/.agents/skills/gstack/browse/src/cookie-picker-routes.ts +207 -0
  74. package/.agents/skills/gstack/browse/src/cookie-picker-ui.ts +541 -0
  75. package/.agents/skills/gstack/browse/src/find-browse.ts +61 -0
  76. package/.agents/skills/gstack/browse/src/meta-commands.ts +269 -0
  77. package/.agents/skills/gstack/browse/src/platform.ts +17 -0
  78. package/.agents/skills/gstack/browse/src/read-commands.ts +335 -0
  79. package/.agents/skills/gstack/browse/src/server.ts +369 -0
  80. package/.agents/skills/gstack/browse/src/snapshot.ts +398 -0
  81. package/.agents/skills/gstack/browse/src/url-validation.ts +91 -0
  82. package/.agents/skills/gstack/browse/src/write-commands.ts +352 -0
  83. package/.agents/skills/gstack/browse/test/bun-polyfill.test.ts +72 -0
  84. package/.agents/skills/gstack/browse/test/commands.test.ts +1836 -0
  85. package/.agents/skills/gstack/browse/test/config.test.ts +250 -0
  86. package/.agents/skills/gstack/browse/test/cookie-import-browser.test.ts +397 -0
  87. package/.agents/skills/gstack/browse/test/cookie-picker-routes.test.ts +205 -0
  88. package/.agents/skills/gstack/browse/test/find-browse.test.ts +50 -0
  89. package/.agents/skills/gstack/browse/test/fixtures/basic.html +33 -0
  90. package/.agents/skills/gstack/browse/test/fixtures/cursor-interactive.html +22 -0
  91. package/.agents/skills/gstack/browse/test/fixtures/dialog.html +15 -0
  92. package/.agents/skills/gstack/browse/test/fixtures/empty.html +2 -0
  93. package/.agents/skills/gstack/browse/test/fixtures/forms.html +55 -0
  94. package/.agents/skills/gstack/browse/test/fixtures/qa-eval-checkout.html +108 -0
  95. package/.agents/skills/gstack/browse/test/fixtures/qa-eval-spa.html +98 -0
  96. package/.agents/skills/gstack/browse/test/fixtures/qa-eval.html +51 -0
  97. package/.agents/skills/gstack/browse/test/fixtures/responsive.html +49 -0
  98. package/.agents/skills/gstack/browse/test/fixtures/snapshot.html +55 -0
  99. package/.agents/skills/gstack/browse/test/fixtures/spa.html +24 -0
  100. package/.agents/skills/gstack/browse/test/fixtures/states.html +17 -0
  101. package/.agents/skills/gstack/browse/test/fixtures/upload.html +25 -0
  102. package/.agents/skills/gstack/browse/test/gstack-config.test.ts +125 -0
  103. package/.agents/skills/gstack/browse/test/gstack-update-check.test.ts +467 -0
  104. package/.agents/skills/gstack/browse/test/handoff.test.ts +235 -0
  105. package/.agents/skills/gstack/browse/test/path-validation.test.ts +63 -0
  106. package/.agents/skills/gstack/browse/test/platform.test.ts +37 -0
  107. package/.agents/skills/gstack/browse/test/snapshot.test.ts +467 -0
  108. package/.agents/skills/gstack/browse/test/test-server.ts +57 -0
  109. package/.agents/skills/gstack/browse/test/url-validation.test.ts +72 -0
  110. package/.agents/skills/gstack/canary/SKILL.md +493 -0
  111. package/.agents/skills/gstack/canary/SKILL.md.tmpl +220 -0
  112. package/.agents/skills/gstack/careful/SKILL.md +59 -0
  113. package/.agents/skills/gstack/careful/SKILL.md.tmpl +57 -0
  114. package/.agents/skills/gstack/careful/bin/check-careful.sh +112 -0
  115. package/.agents/skills/gstack/codex/SKILL.md +677 -0
  116. package/.agents/skills/gstack/codex/SKILL.md.tmpl +356 -0
  117. package/.agents/skills/gstack/conductor.json +6 -0
  118. package/.agents/skills/gstack/cso/SKILL.md +615 -0
  119. package/.agents/skills/gstack/cso/SKILL.md.tmpl +376 -0
  120. package/.agents/skills/gstack/design-consultation/SKILL.md +625 -0
  121. package/.agents/skills/gstack/design-consultation/SKILL.md.tmpl +369 -0
  122. package/.agents/skills/gstack/design-review/SKILL.md +998 -0
  123. package/.agents/skills/gstack/design-review/SKILL.md.tmpl +262 -0
  124. package/.agents/skills/gstack/docs/images/github-2013.png +0 -0
  125. package/.agents/skills/gstack/docs/images/github-2026.png +0 -0
  126. package/.agents/skills/gstack/docs/skills.md +877 -0
  127. package/.agents/skills/gstack/document-release/SKILL.md +613 -0
  128. package/.agents/skills/gstack/document-release/SKILL.md.tmpl +357 -0
  129. package/.agents/skills/gstack/freeze/SKILL.md +82 -0
  130. package/.agents/skills/gstack/freeze/SKILL.md.tmpl +80 -0
  131. package/.agents/skills/gstack/freeze/bin/check-freeze.sh +68 -0
  132. package/.agents/skills/gstack/gstack-upgrade/SKILL.md +226 -0
  133. package/.agents/skills/gstack/gstack-upgrade/SKILL.md.tmpl +224 -0
  134. package/.agents/skills/gstack/guard/SKILL.md +82 -0
  135. package/.agents/skills/gstack/guard/SKILL.md.tmpl +80 -0
  136. package/.agents/skills/gstack/investigate/SKILL.md +435 -0
  137. package/.agents/skills/gstack/investigate/SKILL.md.tmpl +196 -0
  138. package/.agents/skills/gstack/land-and-deploy/SKILL.md +880 -0
  139. package/.agents/skills/gstack/land-and-deploy/SKILL.md.tmpl +575 -0
  140. package/.agents/skills/gstack/office-hours/SKILL.md +996 -0
  141. package/.agents/skills/gstack/office-hours/SKILL.md.tmpl +624 -0
  142. package/.agents/skills/gstack/package.json +55 -0
  143. package/.agents/skills/gstack/plan-ceo-review/SKILL.md +1277 -0
  144. package/.agents/skills/gstack/plan-ceo-review/SKILL.md.tmpl +838 -0
  145. package/.agents/skills/gstack/plan-design-review/SKILL.md +676 -0
  146. package/.agents/skills/gstack/plan-design-review/SKILL.md.tmpl +314 -0
  147. package/.agents/skills/gstack/plan-eng-review/SKILL.md +836 -0
  148. package/.agents/skills/gstack/plan-eng-review/SKILL.md.tmpl +279 -0
  149. package/.agents/skills/gstack/qa/SKILL.md +1016 -0
  150. package/.agents/skills/gstack/qa/SKILL.md.tmpl +316 -0
  151. package/.agents/skills/gstack/qa/references/issue-taxonomy.md +85 -0
  152. package/.agents/skills/gstack/qa/templates/qa-report-template.md +126 -0
  153. package/.agents/skills/gstack/qa-only/SKILL.md +633 -0
  154. package/.agents/skills/gstack/qa-only/SKILL.md.tmpl +101 -0
  155. package/.agents/skills/gstack/retro/SKILL.md +1072 -0
  156. package/.agents/skills/gstack/retro/SKILL.md.tmpl +833 -0
  157. package/.agents/skills/gstack/review/SKILL.md +849 -0
  158. package/.agents/skills/gstack/review/SKILL.md.tmpl +259 -0
  159. package/.agents/skills/gstack/review/TODOS-format.md +62 -0
  160. package/.agents/skills/gstack/review/checklist.md +190 -0
  161. package/.agents/skills/gstack/review/design-checklist.md +132 -0
  162. package/.agents/skills/gstack/review/greptile-triage.md +220 -0
  163. package/.agents/skills/gstack/scripts/analytics.ts +190 -0
  164. package/.agents/skills/gstack/scripts/dev-skill.ts +82 -0
  165. package/.agents/skills/gstack/scripts/eval-compare.ts +96 -0
  166. package/.agents/skills/gstack/scripts/eval-list.ts +116 -0
  167. package/.agents/skills/gstack/scripts/eval-select.ts +86 -0
  168. package/.agents/skills/gstack/scripts/eval-summary.ts +187 -0
  169. package/.agents/skills/gstack/scripts/eval-watch.ts +172 -0
  170. package/.agents/skills/gstack/scripts/gen-skill-docs.ts +2414 -0
  171. package/.agents/skills/gstack/scripts/skill-check.ts +167 -0
  172. package/.agents/skills/gstack/setup +269 -0
  173. package/.agents/skills/gstack/setup-browser-cookies/SKILL.md +330 -0
  174. package/.agents/skills/gstack/setup-browser-cookies/SKILL.md.tmpl +74 -0
  175. package/.agents/skills/gstack/setup-deploy/SKILL.md +459 -0
  176. package/.agents/skills/gstack/setup-deploy/SKILL.md.tmpl +220 -0
  177. package/.agents/skills/gstack/ship/SKILL.md +1457 -0
  178. package/.agents/skills/gstack/ship/SKILL.md.tmpl +528 -0
  179. package/.agents/skills/gstack/supabase/config.sh +10 -0
  180. package/.agents/skills/gstack/supabase/functions/community-pulse/index.ts +59 -0
  181. package/.agents/skills/gstack/supabase/functions/telemetry-ingest/index.ts +135 -0
  182. package/.agents/skills/gstack/supabase/functions/update-check/index.ts +37 -0
  183. package/.agents/skills/gstack/supabase/migrations/001_telemetry.sql +89 -0
  184. package/.agents/skills/gstack/test/analytics.test.ts +277 -0
  185. package/.agents/skills/gstack/test/codex-e2e.test.ts +197 -0
  186. package/.agents/skills/gstack/test/fixtures/coverage-audit-fixture.ts +76 -0
  187. package/.agents/skills/gstack/test/fixtures/eval-baselines.json +7 -0
  188. package/.agents/skills/gstack/test/fixtures/qa-eval-checkout-ground-truth.json +43 -0
  189. package/.agents/skills/gstack/test/fixtures/qa-eval-ground-truth.json +43 -0
  190. package/.agents/skills/gstack/test/fixtures/qa-eval-spa-ground-truth.json +43 -0
  191. package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.css +86 -0
  192. package/.agents/skills/gstack/test/fixtures/review-eval-design-slop.html +41 -0
  193. package/.agents/skills/gstack/test/fixtures/review-eval-enum-diff.rb +30 -0
  194. package/.agents/skills/gstack/test/fixtures/review-eval-enum.rb +27 -0
  195. package/.agents/skills/gstack/test/fixtures/review-eval-vuln.rb +14 -0
  196. package/.agents/skills/gstack/test/gemini-e2e.test.ts +173 -0
  197. package/.agents/skills/gstack/test/gen-skill-docs.test.ts +1049 -0
  198. package/.agents/skills/gstack/test/global-discover.test.ts +187 -0
  199. package/.agents/skills/gstack/test/helpers/codex-session-runner.ts +282 -0
  200. package/.agents/skills/gstack/test/helpers/e2e-helpers.ts +239 -0
  201. package/.agents/skills/gstack/test/helpers/eval-store.test.ts +548 -0
  202. package/.agents/skills/gstack/test/helpers/eval-store.ts +689 -0
  203. package/.agents/skills/gstack/test/helpers/gemini-session-runner.test.ts +104 -0
  204. package/.agents/skills/gstack/test/helpers/gemini-session-runner.ts +201 -0
  205. package/.agents/skills/gstack/test/helpers/llm-judge.ts +130 -0
  206. package/.agents/skills/gstack/test/helpers/observability.test.ts +283 -0
  207. package/.agents/skills/gstack/test/helpers/session-runner.test.ts +96 -0
  208. package/.agents/skills/gstack/test/helpers/session-runner.ts +357 -0
  209. package/.agents/skills/gstack/test/helpers/skill-parser.ts +206 -0
  210. package/.agents/skills/gstack/test/helpers/touchfiles.ts +260 -0
  211. package/.agents/skills/gstack/test/hook-scripts.test.ts +373 -0
  212. package/.agents/skills/gstack/test/skill-e2e-browse.test.ts +293 -0
  213. package/.agents/skills/gstack/test/skill-e2e-deploy.test.ts +279 -0
  214. package/.agents/skills/gstack/test/skill-e2e-design.test.ts +614 -0
  215. package/.agents/skills/gstack/test/skill-e2e-plan.test.ts +538 -0
  216. package/.agents/skills/gstack/test/skill-e2e-qa-bugs.test.ts +194 -0
  217. package/.agents/skills/gstack/test/skill-e2e-qa-workflow.test.ts +412 -0
  218. package/.agents/skills/gstack/test/skill-e2e-review.test.ts +535 -0
  219. package/.agents/skills/gstack/test/skill-e2e-workflow.test.ts +586 -0
  220. package/.agents/skills/gstack/test/skill-e2e.test.ts +3325 -0
  221. package/.agents/skills/gstack/test/skill-llm-eval.test.ts +787 -0
  222. package/.agents/skills/gstack/test/skill-parser.test.ts +179 -0
  223. package/.agents/skills/gstack/test/skill-routing-e2e.test.ts +605 -0
  224. package/.agents/skills/gstack/test/skill-validation.test.ts +1520 -0
  225. package/.agents/skills/gstack/test/telemetry.test.ts +278 -0
  226. package/.agents/skills/gstack/test/touchfiles.test.ts +262 -0
  227. package/.agents/skills/gstack/unfreeze/SKILL.md +40 -0
  228. package/.agents/skills/gstack/unfreeze/SKILL.md.tmpl +38 -0
  229. package/package.json +2 -1
@@ -0,0 +1,2414 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Generate SKILL.md files from .tmpl templates.
4
+ *
5
+ * Pipeline:
6
+ * read .tmpl → find {{PLACEHOLDERS}} → resolve from source → format → write .md
7
+ *
8
+ * Supports --dry-run: generate to memory, exit 1 if different from committed file.
9
+ * Used by skill:check and CI freshness checks.
10
+ */
11
+
12
+ import { COMMAND_DESCRIPTIONS } from '../browse/src/commands';
13
+ import { SNAPSHOT_FLAGS } from '../browse/src/snapshot';
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+
17
+ const ROOT = path.resolve(import.meta.dir, '..');
18
+ const DRY_RUN = process.argv.includes('--dry-run');
19
+
20
+ // ─── Template Context ───────────────────────────────────────
21
+
22
+ type Host = 'claude' | 'codex';
23
+
24
+ const HOST_ARG = process.argv.find(a => a.startsWith('--host'));
25
+ const HOST: Host = (() => {
26
+ if (!HOST_ARG) return 'claude';
27
+ const val = HOST_ARG.includes('=') ? HOST_ARG.split('=')[1] : process.argv[process.argv.indexOf(HOST_ARG) + 1];
28
+ if (val === 'codex' || val === 'agents') return 'codex';
29
+ if (val === 'claude') return 'claude';
30
+ throw new Error(`Unknown host: ${val}. Use claude, codex, or agents.`);
31
+ })();
32
+
33
+ interface HostPaths {
34
+ skillRoot: string;
35
+ localSkillRoot: string;
36
+ binDir: string;
37
+ browseDir: string;
38
+ }
39
+
40
+ const HOST_PATHS: Record<Host, HostPaths> = {
41
+ claude: {
42
+ skillRoot: '~/.claude/skills/gstack',
43
+ localSkillRoot: '.claude/skills/gstack',
44
+ binDir: '~/.claude/skills/gstack/bin',
45
+ browseDir: '~/.claude/skills/gstack/browse/dist',
46
+ },
47
+ codex: {
48
+ skillRoot: '~/.codex/skills/gstack',
49
+ localSkillRoot: '.agents/skills/gstack',
50
+ binDir: '~/.codex/skills/gstack/bin',
51
+ browseDir: '~/.codex/skills/gstack/browse/dist',
52
+ },
53
+ };
54
+
55
+ interface TemplateContext {
56
+ skillName: string;
57
+ tmplPath: string;
58
+ benefitsFrom?: string[];
59
+ host: Host;
60
+ paths: HostPaths;
61
+ }
62
+
63
+ // ─── Placeholder Resolvers ──────────────────────────────────
64
+
65
+ function generateCommandReference(_ctx: TemplateContext): string {
66
+ // Group commands by category
67
+ const groups = new Map<string, Array<{ command: string; description: string; usage?: string }>>();
68
+ for (const [cmd, meta] of Object.entries(COMMAND_DESCRIPTIONS)) {
69
+ const list = groups.get(meta.category) || [];
70
+ list.push({ command: cmd, description: meta.description, usage: meta.usage });
71
+ groups.set(meta.category, list);
72
+ }
73
+
74
+ // Category display order
75
+ const categoryOrder = [
76
+ 'Navigation', 'Reading', 'Interaction', 'Inspection',
77
+ 'Visual', 'Snapshot', 'Meta', 'Tabs', 'Server',
78
+ ];
79
+
80
+ const sections: string[] = [];
81
+ for (const category of categoryOrder) {
82
+ const commands = groups.get(category);
83
+ if (!commands || commands.length === 0) continue;
84
+
85
+ // Sort alphabetically within category
86
+ commands.sort((a, b) => a.command.localeCompare(b.command));
87
+
88
+ sections.push(`### ${category}`);
89
+ sections.push('| Command | Description |');
90
+ sections.push('|---------|-------------|');
91
+ for (const cmd of commands) {
92
+ const display = cmd.usage ? `\`${cmd.usage}\`` : `\`${cmd.command}\``;
93
+ sections.push(`| ${display} | ${cmd.description} |`);
94
+ }
95
+ sections.push('');
96
+ }
97
+
98
+ return sections.join('\n').trimEnd();
99
+ }
100
+
101
+ function generateSnapshotFlags(_ctx: TemplateContext): string {
102
+ const lines: string[] = [
103
+ 'The snapshot is your primary tool for understanding and interacting with pages.',
104
+ '',
105
+ '```',
106
+ ];
107
+
108
+ for (const flag of SNAPSHOT_FLAGS) {
109
+ const label = flag.valueHint ? `${flag.short} ${flag.valueHint}` : flag.short;
110
+ lines.push(`${label.padEnd(10)}${flag.long.padEnd(24)}${flag.description}`);
111
+ }
112
+
113
+ lines.push('```');
114
+ lines.push('');
115
+ lines.push('All flags can be combined freely. `-o` only applies when `-a` is also used.');
116
+ lines.push('Example: `$B snapshot -i -a -C -o /tmp/annotated.png`');
117
+ lines.push('');
118
+ lines.push('**Ref numbering:** @e refs are assigned sequentially (@e1, @e2, ...) in tree order.');
119
+ lines.push('@c refs from `-C` are numbered separately (@c1, @c2, ...).');
120
+ lines.push('');
121
+ lines.push('After snapshot, use @refs as selectors in any command:');
122
+ lines.push('```bash');
123
+ lines.push('$B click @e3 $B fill @e4 "value" $B hover @e1');
124
+ lines.push('$B html @e2 $B css @e5 "color" $B attrs @e6');
125
+ lines.push('$B click @c1 # cursor-interactive ref (from -C)');
126
+ lines.push('```');
127
+ lines.push('');
128
+ lines.push('**Output format:** indented accessibility tree with @ref IDs, one element per line.');
129
+ lines.push('```');
130
+ lines.push(' @e1 [heading] "Welcome" [level=1]');
131
+ lines.push(' @e2 [textbox] "Email"');
132
+ lines.push(' @e3 [button] "Submit"');
133
+ lines.push('```');
134
+ lines.push('');
135
+ lines.push('Refs are invalidated on navigation — run `snapshot` again after `goto`.');
136
+
137
+ return lines.join('\n');
138
+ }
139
+
140
+ function generatePreambleBash(ctx: TemplateContext): string {
141
+ return `## Preamble (run first)
142
+
143
+ \`\`\`bash
144
+ _UPD=$(${ctx.paths.binDir}/gstack-update-check 2>/dev/null || ${ctx.paths.localSkillRoot}/bin/gstack-update-check 2>/dev/null || true)
145
+ [ -n "$_UPD" ] && echo "$_UPD" || true
146
+ mkdir -p ~/.gstack/sessions
147
+ touch ~/.gstack/sessions/"$PPID"
148
+ _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
149
+ find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true
150
+ _CONTRIB=$(${ctx.paths.binDir}/gstack-config get gstack_contributor 2>/dev/null || true)
151
+ _PROACTIVE=$(${ctx.paths.binDir}/gstack-config get proactive 2>/dev/null || echo "true")
152
+ _BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
153
+ echo "BRANCH: $_BRANCH"
154
+ echo "PROACTIVE: $_PROACTIVE"
155
+ source <(${ctx.paths.binDir}/gstack-repo-mode 2>/dev/null) || true
156
+ REPO_MODE=\${REPO_MODE:-unknown}
157
+ echo "REPO_MODE: $REPO_MODE"
158
+ _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
159
+ echo "LAKE_INTRO: $_LAKE_SEEN"
160
+ _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true)
161
+ _TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no")
162
+ _TEL_START=$(date +%s)
163
+ _SESSION_ID="$$-$(date +%s)"
164
+ echo "TELEMETRY: \${_TEL:-off}"
165
+ echo "TEL_PROMPTED: $_TEL_PROMPTED"
166
+ mkdir -p ~/.gstack/analytics
167
+ echo '{"skill":"${ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
168
+ for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ${ctx.paths.binDir}/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
169
+ \`\`\``;
170
+ }
171
+
172
+ function generateUpgradeCheck(ctx: TemplateContext): string {
173
+ return `If \`PROACTIVE\` is \`"false"\`, do not proactively suggest gstack skills — only invoke
174
+ them when the user explicitly asks. The user opted out of proactive suggestions.
175
+
176
+ If output shows \`UPGRADE_AVAILABLE <old> <new>\`: read \`${ctx.paths.skillRoot}/gstack-upgrade/SKILL.md\` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If \`JUST_UPGRADED <from> <to>\`: tell user "Running gstack v{to} (just updated!)" and continue.`;
177
+ }
178
+
179
+ function generateLakeIntro(): string {
180
+ return `If \`LAKE_INTRO\` is \`no\`: Before continuing, introduce the Completeness Principle.
181
+ Tell the user: "gstack follows the **Boil the Lake** principle — always do the complete
182
+ thing when AI makes the marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean"
183
+ Then offer to open the essay in their default browser:
184
+
185
+ \`\`\`bash
186
+ open https://garryslist.org/posts/boil-the-ocean
187
+ touch ~/.gstack/.completeness-intro-seen
188
+ \`\`\`
189
+
190
+ Only run \`open\` if the user says yes. Always run \`touch\` to mark as seen. This only happens once.`;
191
+ }
192
+
193
+ function generateTelemetryPrompt(ctx: TemplateContext): string {
194
+ return `If \`TEL_PROMPTED\` is \`no\` AND \`LAKE_INTRO\` is \`yes\`: After the lake intro is handled,
195
+ ask the user about telemetry. Use AskUserQuestion:
196
+
197
+ > Help gstack get better! Community mode shares usage data (which skills you use, how long
198
+ > they take, crash info) with a stable device ID so we can track trends and fix bugs faster.
199
+ > No code, file paths, or repo names are ever sent.
200
+ > Change anytime with \`gstack-config set telemetry off\`.
201
+
202
+ Options:
203
+ - A) Help gstack get better! (recommended)
204
+ - B) No thanks
205
+
206
+ If A: run \`${ctx.paths.binDir}/gstack-config set telemetry community\`
207
+
208
+ If B: ask a follow-up AskUserQuestion:
209
+
210
+ > How about anonymous mode? We just learn that *someone* used gstack — no unique ID,
211
+ > no way to connect sessions. Just a counter that helps us know if anyone's out there.
212
+
213
+ Options:
214
+ - A) Sure, anonymous is fine
215
+ - B) No thanks, fully off
216
+
217
+ If B→A: run \`${ctx.paths.binDir}/gstack-config set telemetry anonymous\`
218
+ If B→B: run \`${ctx.paths.binDir}/gstack-config set telemetry off\`
219
+
220
+ Always run:
221
+ \`\`\`bash
222
+ touch ~/.gstack/.telemetry-prompted
223
+ \`\`\`
224
+
225
+ This only happens once. If \`TEL_PROMPTED\` is \`yes\`, skip this entirely.`;
226
+ }
227
+
228
+ function generateAskUserFormat(_ctx: TemplateContext): string {
229
+ return `## AskUserQuestion Format
230
+
231
+ **ALWAYS follow this structure for every AskUserQuestion call:**
232
+ 1. **Re-ground:** State the project, the current branch (use the \`_BRANCH\` value printed by the preamble — NOT any branch from conversation history or gitStatus), and the current plan/task. (1-2 sentences)
233
+ 2. **Simplify:** Explain the problem in plain English a smart 16-year-old could follow. No raw function names, no internal jargon, no implementation details. Use concrete examples and analogies. Say what it DOES, not what it's called.
234
+ 3. **Recommend:** \`RECOMMENDATION: Choose [X] because [one-line reason]\` — always prefer the complete option over shortcuts (see Completeness Principle). Include \`Completeness: X/10\` for each option. Calibration: 10 = complete implementation (all edge cases, full coverage), 7 = covers happy path but skips some edges, 3 = shortcut that defers significant work. If both options are 8+, pick the higher; if one is ≤5, flag it.
235
+ 4. **Options:** Lettered options: \`A) ... B) ... C) ...\` — when an option involves effort, show both scales: \`(human: ~X / CC: ~Y)\`
236
+
237
+ Assume the user hasn't looked at this window in 20 minutes and doesn't have the code open. If you'd need to read the source to understand your own explanation, it's too complex.
238
+
239
+ Per-skill instructions may add additional formatting rules on top of this baseline.`;
240
+ }
241
+
242
+ function generateCompletenessSection(): string {
243
+ return `## Completeness Principle — Boil the Lake
244
+
245
+ AI-assisted coding makes the marginal cost of completeness near-zero. When you present options:
246
+
247
+ - If Option A is the complete implementation (full parity, all edge cases, 100% coverage) and Option B is a shortcut that saves modest effort — **always recommend A**. The delta between 80 lines and 150 lines is meaningless with CC+gstack. "Good enough" is the wrong instinct when "complete" costs minutes more.
248
+ - **Lake vs. ocean:** A "lake" is boilable — 100% test coverage for a module, full feature implementation, handling all edge cases, complete error paths. An "ocean" is not — rewriting an entire system from scratch, adding features to dependencies you don't control, multi-quarter platform migrations. Recommend boiling lakes. Flag oceans as out of scope.
249
+ - **When estimating effort**, always show both scales: human team time and CC+gstack time. The compression ratio varies by task type — use this reference:
250
+
251
+ | Task type | Human team | CC+gstack | Compression |
252
+ |-----------|-----------|-----------|-------------|
253
+ | Boilerplate / scaffolding | 2 days | 15 min | ~100x |
254
+ | Test writing | 1 day | 15 min | ~50x |
255
+ | Feature implementation | 1 week | 30 min | ~30x |
256
+ | Bug fix + regression test | 4 hours | 15 min | ~20x |
257
+ | Architecture / design | 2 days | 4 hours | ~5x |
258
+ | Research / exploration | 1 day | 3 hours | ~3x |
259
+
260
+ - This principle applies to test coverage, error handling, documentation, edge cases, and feature completeness. Don't skip the last 10% to "save time" — with AI, that 10% costs seconds.
261
+
262
+ **Anti-patterns — DON'T do this:**
263
+ - BAD: "Choose B — it covers 90% of the value with less code." (If A is only 70 lines more, choose A.)
264
+ - BAD: "We can skip edge case handling to save time." (Edge case handling costs minutes with CC.)
265
+ - BAD: "Let's defer test coverage to a follow-up PR." (Tests are the cheapest lake to boil.)
266
+ - BAD: Quoting only human-team effort: "This would take 2 weeks." (Say: "2 weeks human / ~1 hour CC.")`;
267
+ }
268
+
269
+ function generateRepoModeSection(): string {
270
+ return `## Repo Ownership Mode — See Something, Say Something
271
+
272
+ \`REPO_MODE\` from the preamble tells you who owns issues in this repo:
273
+
274
+ - **\`solo\`** — One person does 80%+ of the work. They own everything. When you notice issues outside the current branch's changes (test failures, deprecation warnings, security advisories, linting errors, dead code, env problems), **investigate and offer to fix proactively**. The solo dev is the only person who will fix it. Default to action.
275
+ - **\`collaborative\`** — Multiple active contributors. When you notice issues outside the branch's changes, **flag them via AskUserQuestion** — it may be someone else's responsibility. Default to asking, not fixing.
276
+ - **\`unknown\`** — Treat as collaborative (safer default — ask before fixing).
277
+
278
+ **See Something, Say Something:** Whenever you notice something that looks wrong during ANY workflow step — not just test failures — flag it briefly. One sentence: what you noticed and its impact. In solo mode, follow up with "Want me to fix it?" In collaborative mode, just flag it and move on.
279
+
280
+ Never let a noticed issue silently pass. The whole point is proactive communication.`;
281
+ }
282
+
283
+ function generateTestFailureTriage(): string {
284
+ return `## Test Failure Ownership Triage
285
+
286
+ When tests fail, do NOT immediately stop. First, determine ownership:
287
+
288
+ ### Step T1: Classify each failure
289
+
290
+ For each failing test:
291
+
292
+ 1. **Get the files changed on this branch:**
293
+ \`\`\`bash
294
+ git diff origin/<base>...HEAD --name-only
295
+ \`\`\`
296
+
297
+ 2. **Classify the failure:**
298
+ - **In-branch** if: the failing test file itself was modified on this branch, OR the test output references code that was changed on this branch, OR you can trace the failure to a change in the branch diff.
299
+ - **Likely pre-existing** if: neither the test file nor the code it tests was modified on this branch, AND the failure is unrelated to any branch change you can identify.
300
+ - **When ambiguous, default to in-branch.** It is safer to stop the developer than to let a broken test ship. Only classify as pre-existing when you are confident.
301
+
302
+ This classification is heuristic — use your judgment reading the diff and the test output. You do not have a programmatic dependency graph.
303
+
304
+ ### Step T2: Handle in-branch failures
305
+
306
+ **STOP.** These are your failures. Show them and do not proceed. The developer must fix their own broken tests before shipping.
307
+
308
+ ### Step T3: Handle pre-existing failures
309
+
310
+ Check \`REPO_MODE\` from the preamble output.
311
+
312
+ **If REPO_MODE is \`solo\`:**
313
+
314
+ Use AskUserQuestion:
315
+
316
+ > These test failures appear pre-existing (not caused by your branch changes):
317
+ >
318
+ > [list each failure with file:line and brief error description]
319
+ >
320
+ > Since this is a solo repo, you're the only one who will fix these.
321
+ >
322
+ > RECOMMENDATION: Choose A — fix now while the context is fresh. Completeness: 9/10.
323
+ > A) Investigate and fix now (human: ~2-4h / CC: ~15min) — Completeness: 10/10
324
+ > B) Add as P0 TODO — fix after this branch lands — Completeness: 7/10
325
+ > C) Skip — I know about this, ship anyway — Completeness: 3/10
326
+
327
+ **If REPO_MODE is \`collaborative\` or \`unknown\`:**
328
+
329
+ Use AskUserQuestion:
330
+
331
+ > These test failures appear pre-existing (not caused by your branch changes):
332
+ >
333
+ > [list each failure with file:line and brief error description]
334
+ >
335
+ > This is a collaborative repo — these may be someone else's responsibility.
336
+ >
337
+ > RECOMMENDATION: Choose B — assign it to whoever broke it so the right person fixes it. Completeness: 9/10.
338
+ > A) Investigate and fix now anyway — Completeness: 10/10
339
+ > B) Blame + assign GitHub issue to the author — Completeness: 9/10
340
+ > C) Add as P0 TODO — Completeness: 7/10
341
+ > D) Skip — ship anyway — Completeness: 3/10
342
+
343
+ ### Step T4: Execute the chosen action
344
+
345
+ **If "Investigate and fix now":**
346
+ - Switch to /investigate mindset: root cause first, then minimal fix.
347
+ - Fix the pre-existing failure.
348
+ - Commit the fix separately from the branch's changes: \`git commit -m "fix: pre-existing test failure in <test-file>"\`
349
+ - Continue with the workflow.
350
+
351
+ **If "Add as P0 TODO":**
352
+ - If \`TODOS.md\` exists, add the entry following the format in \`review/TODOS-format.md\` (or \`.claude/skills/review/TODOS-format.md\`).
353
+ - If \`TODOS.md\` does not exist, create it with the standard header and add the entry.
354
+ - Entry should include: title, the error output, which branch it was noticed on, and priority P0.
355
+ - Continue with the workflow — treat the pre-existing failure as non-blocking.
356
+
357
+ **If "Blame + assign GitHub issue" (collaborative only):**
358
+ - Find who likely broke it. Check BOTH the test file AND the production code it tests:
359
+ \`\`\`bash
360
+ # Who last touched the failing test?
361
+ git log --format="%an (%ae)" -1 -- <failing-test-file>
362
+ # Who last touched the production code the test covers? (often the actual breaker)
363
+ git log --format="%an (%ae)" -1 -- <source-file-under-test>
364
+ \`\`\`
365
+ If these are different people, prefer the production code author — they likely introduced the regression.
366
+ - Create a GitHub issue assigned to that person:
367
+ \`\`\`bash
368
+ gh issue create \\
369
+ --title "Pre-existing test failure: <test-name>" \\
370
+ --body "Found failing on branch <current-branch>. Failure is pre-existing.\\n\\n**Error:**\\n\`\`\`\\n<first 10 lines>\\n\`\`\`\\n\\n**Last modified by:** <author>\\n**Noticed by:** gstack /ship on <date>" \\
371
+ --assignee "<github-username>"
372
+ \`\`\`
373
+ - If \`gh\` is not available or \`--assignee\` fails (user not in org, etc.), create the issue without assignee and note who should look at it in the body.
374
+ - Continue with the workflow.
375
+
376
+ **If "Skip":**
377
+ - Continue with the workflow.
378
+ - Note in output: "Pre-existing test failure skipped: <test-name>"`;
379
+ }
380
+
381
+ function generateSearchBeforeBuildingSection(ctx: TemplateContext): string {
382
+ return `## Search Before Building
383
+
384
+ Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read \`${ctx.paths.skillRoot}/ETHOS.md\` for the full philosophy.
385
+
386
+ **Three layers of knowledge:**
387
+ - **Layer 1** (tried and true — in distribution). Don't reinvent the wheel. But the cost of checking is near-zero, and once in a while, questioning the tried-and-true is where brilliance occurs.
388
+ - **Layer 2** (new and popular — search for these). But scrutinize: humans are subject to mania. Search results are inputs to your thinking, not answers.
389
+ - **Layer 3** (first principles — prize these above all). Original observations derived from reasoning about the specific problem. The most valuable of all.
390
+
391
+ **Eureka moment:** When first-principles reasoning reveals conventional wisdom is wrong, name it:
392
+ "EUREKA: Everyone does X because [assumption]. But [evidence] shows this is wrong. Y is better because [reasoning]."
393
+
394
+ Log eureka moments:
395
+ \`\`\`bash
396
+ jq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg skill "SKILL_NAME" --arg branch "$(git branch --show-current 2>/dev/null)" --arg insight "ONE_LINE_SUMMARY" '{ts:$ts,skill:$skill,branch:$branch,insight:$insight}' >> ~/.gstack/analytics/eureka.jsonl 2>/dev/null || true
397
+ \`\`\`
398
+ Replace SKILL_NAME and ONE_LINE_SUMMARY. Runs inline — don't stop the workflow.
399
+
400
+ **WebSearch fallback:** If WebSearch is unavailable, skip the search step and note: "Search unavailable — proceeding with in-distribution knowledge only."`;
401
+ }
402
+
403
+ function generateContributorMode(): string {
404
+ return `## Contributor Mode
405
+
406
+ If \`_CONTRIB\` is \`true\`: you are in **contributor mode**. You're a gstack user who also helps make it better.
407
+
408
+ **At the end of each major workflow step** (not after every single command), reflect on the gstack tooling you used. Rate your experience 0 to 10. If it wasn't a 10, think about why. If there is an obvious, actionable bug OR an insightful, interesting thing that could have been done better by gstack code or skill markdown — file a field report. Maybe our contributor will help make us better!
409
+
410
+ **Calibration — this is the bar:** For example, \`$B js "await fetch(...)"\` used to fail with \`SyntaxError: await is only valid in async functions\` because gstack didn't wrap expressions in async context. Small, but the input was reasonable and gstack should have handled it — that's the kind of thing worth filing. Things less consequential than this, ignore.
411
+
412
+ **NOT worth filing:** user's app bugs, network errors to user's URL, auth failures on user's site, user's own JS logic bugs.
413
+
414
+ **To file:** write \`~/.gstack/contributor-logs/{slug}.md\` with **all sections below** (do not truncate — include every section through the Date/Version footer):
415
+
416
+ \`\`\`
417
+ # {Title}
418
+
419
+ Hey gstack team — ran into this while using /{skill-name}:
420
+
421
+ **What I was trying to do:** {what the user/agent was attempting}
422
+ **What happened instead:** {what actually happened}
423
+ **My rating:** {0-10} — {one sentence on why it wasn't a 10}
424
+
425
+ ## Steps to reproduce
426
+ 1. {step}
427
+
428
+ ## Raw output
429
+ \`\`\`
430
+ {paste the actual error or unexpected output here}
431
+ \`\`\`
432
+
433
+ ## What would make this a 10
434
+ {one sentence: what gstack should have done differently}
435
+
436
+ **Date:** {YYYY-MM-DD} | **Version:** {gstack version} | **Skill:** /{skill}
437
+ \`\`\`
438
+
439
+ Slug: lowercase, hyphens, max 60 chars (e.g. \`browse-js-no-await\`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"`;
440
+ }
441
+
442
+ function generateCompletionStatus(): string {
443
+ return `## Completion Status Protocol
444
+
445
+ When completing a skill workflow, report status using one of:
446
+ - **DONE** — All steps completed successfully. Evidence provided for each claim.
447
+ - **DONE_WITH_CONCERNS** — Completed, but with issues the user should know about. List each concern.
448
+ - **BLOCKED** — Cannot proceed. State what is blocking and what was tried.
449
+ - **NEEDS_CONTEXT** — Missing information required to continue. State exactly what you need.
450
+
451
+ ### Escalation
452
+
453
+ It is always OK to stop and say "this is too hard for me" or "I'm not confident in this result."
454
+
455
+ Bad work is worse than no work. You will not be penalized for escalating.
456
+ - If you have attempted a task 3 times without success, STOP and escalate.
457
+ - If you are uncertain about a security-sensitive change, STOP and escalate.
458
+ - If the scope of work exceeds what you can verify, STOP and escalate.
459
+
460
+ Escalation format:
461
+ \`\`\`
462
+ STATUS: BLOCKED | NEEDS_CONTEXT
463
+ REASON: [1-2 sentences]
464
+ ATTEMPTED: [what you tried]
465
+ RECOMMENDATION: [what the user should do next]
466
+ \`\`\`
467
+
468
+ ## Telemetry (run last)
469
+
470
+ After the skill workflow completes (success, error, or abort), log the telemetry event.
471
+ Determine the skill name from the \`name:\` field in this file's YAML frontmatter.
472
+ Determine the outcome from the workflow result (success if completed normally, error
473
+ if it failed, abort if the user interrupted).
474
+
475
+ **PLAN MODE EXCEPTION — ALWAYS RUN:** This command writes telemetry to
476
+ \`~/.gstack/analytics/\` (user config directory, not project files). The skill
477
+ preamble already writes to the same directory — this is the same pattern.
478
+ Skipping this command loses session duration and outcome data.
479
+
480
+ Run this bash:
481
+
482
+ \`\`\`bash
483
+ _TEL_END=$(date +%s)
484
+ _TEL_DUR=$(( _TEL_END - _TEL_START ))
485
+ rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
486
+ ~/.claude/skills/gstack/bin/gstack-telemetry-log \\
487
+ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \\
488
+ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null &
489
+ \`\`\`
490
+
491
+ Replace \`SKILL_NAME\` with the actual skill name from frontmatter, \`OUTCOME\` with
492
+ success/error/abort, and \`USED_BROWSE\` with true/false based on whether \`$B\` was used.
493
+ If you cannot determine the outcome, use "unknown". This runs in the background and
494
+ never blocks the user.`;
495
+ }
496
+
497
+ function generatePreamble(ctx: TemplateContext): string {
498
+ return [
499
+ generatePreambleBash(ctx),
500
+ generateUpgradeCheck(ctx),
501
+ generateLakeIntro(),
502
+ generateTelemetryPrompt(ctx),
503
+ generateAskUserFormat(ctx),
504
+ generateCompletenessSection(),
505
+ generateRepoModeSection(),
506
+ generateSearchBeforeBuildingSection(ctx),
507
+ generateContributorMode(),
508
+ generateCompletionStatus(),
509
+ ].join('\n\n');
510
+ }
511
+
512
+ function generateBrowseSetup(ctx: TemplateContext): string {
513
+ return `## SETUP (run this check BEFORE any browse command)
514
+
515
+ \`\`\`bash
516
+ _ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
517
+ B=""
518
+ [ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse"
519
+ [ -z "$B" ] && B=${ctx.paths.browseDir}/browse
520
+ if [ -x "$B" ]; then
521
+ echo "READY: $B"
522
+ else
523
+ echo "NEEDS_SETUP"
524
+ fi
525
+ \`\`\`
526
+
527
+ If \`NEEDS_SETUP\`:
528
+ 1. Tell the user: "gstack browse needs a one-time build (~10 seconds). OK to proceed?" Then STOP and wait.
529
+ 2. Run: \`cd <SKILL_DIR> && ./setup\`
530
+ 3. If \`bun\` is not installed: \`curl -fsSL https://bun.sh/install | bash\``;
531
+ }
532
+
533
+ function generateBaseBranchDetect(_ctx: TemplateContext): string {
534
+ return `## Step 0: Detect base branch
535
+
536
+ Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
537
+
538
+ 1. Check if a PR already exists for this branch:
539
+ \`gh pr view --json baseRefName -q .baseRefName\`
540
+ If this succeeds, use the printed branch name as the base branch.
541
+
542
+ 2. If no PR exists (command fails), detect the repo's default branch:
543
+ \`gh repo view --json defaultBranchRef -q .defaultBranchRef.name\`
544
+
545
+ 3. If both commands fail, fall back to \`main\`.
546
+
547
+ Print the detected base branch name. In every subsequent \`git diff\`, \`git log\`,
548
+ \`git fetch\`, \`git merge\`, and \`gh pr create\` command, substitute the detected
549
+ branch name wherever the instructions say "the base branch."
550
+
551
+ ---`;
552
+ }
553
+
554
+ function generateQAMethodology(_ctx: TemplateContext): string {
555
+ return `## Modes
556
+
557
+ ### Diff-aware (automatic when on a feature branch with no URL)
558
+
559
+ This is the **primary mode** for developers verifying their work. When the user says \`/qa\` without a URL and the repo is on a feature branch, automatically:
560
+
561
+ 1. **Analyze the branch diff** to understand what changed:
562
+ \`\`\`bash
563
+ git diff main...HEAD --name-only
564
+ git log main..HEAD --oneline
565
+ \`\`\`
566
+
567
+ 2. **Identify affected pages/routes** from the changed files:
568
+ - Controller/route files → which URL paths they serve
569
+ - View/template/component files → which pages render them
570
+ - Model/service files → which pages use those models (check controllers that reference them)
571
+ - CSS/style files → which pages include those stylesheets
572
+ - API endpoints → test them directly with \`$B js "await fetch('/api/...')"\`
573
+ - Static pages (markdown, HTML) → navigate to them directly
574
+
575
+ **If no obvious pages/routes are identified from the diff:** Do not skip browser testing. The user invoked /qa because they want browser-based verification. Fall back to Quick mode — navigate to the homepage, follow the top 5 navigation targets, check console for errors, and test any interactive elements found. Backend, config, and infrastructure changes affect app behavior — always verify the app still works.
576
+
577
+ 3. **Detect the running app** — check common local dev ports:
578
+ \`\`\`bash
579
+ $B goto http://localhost:3000 2>/dev/null && echo "Found app on :3000" || \\
580
+ $B goto http://localhost:4000 2>/dev/null && echo "Found app on :4000" || \\
581
+ $B goto http://localhost:8080 2>/dev/null && echo "Found app on :8080"
582
+ \`\`\`
583
+ If no local app is found, check for a staging/preview URL in the PR or environment. If nothing works, ask the user for the URL.
584
+
585
+ 4. **Test each affected page/route:**
586
+ - Navigate to the page
587
+ - Take a screenshot
588
+ - Check console for errors
589
+ - If the change was interactive (forms, buttons, flows), test the interaction end-to-end
590
+ - Use \`snapshot -D\` before and after actions to verify the change had the expected effect
591
+
592
+ 5. **Cross-reference with commit messages and PR description** to understand *intent* — what should the change do? Verify it actually does that.
593
+
594
+ 6. **Check TODOS.md** (if it exists) for known bugs or issues related to the changed files. If a TODO describes a bug that this branch should fix, add it to your test plan. If you find a new bug during QA that isn't in TODOS.md, note it in the report.
595
+
596
+ 7. **Report findings** scoped to the branch changes:
597
+ - "Changes tested: N pages/routes affected by this branch"
598
+ - For each: does it work? Screenshot evidence.
599
+ - Any regressions on adjacent pages?
600
+
601
+ **If the user provides a URL with diff-aware mode:** Use that URL as the base but still scope testing to the changed files.
602
+
603
+ ### Full (default when URL is provided)
604
+ Systematic exploration. Visit every reachable page. Document 5-10 well-evidenced issues. Produce health score. Takes 5-15 minutes depending on app size.
605
+
606
+ ### Quick (\`--quick\`)
607
+ 30-second smoke test. Visit homepage + top 5 navigation targets. Check: page loads? Console errors? Broken links? Produce health score. No detailed issue documentation.
608
+
609
+ ### Regression (\`--regression <baseline>\`)
610
+ Run full mode, then load \`baseline.json\` from a previous run. Diff: which issues are fixed? Which are new? What's the score delta? Append regression section to report.
611
+
612
+ ---
613
+
614
+ ## Workflow
615
+
616
+ ### Phase 1: Initialize
617
+
618
+ 1. Find browse binary (see Setup above)
619
+ 2. Create output directories
620
+ 3. Copy report template from \`qa/templates/qa-report-template.md\` to output dir
621
+ 4. Start timer for duration tracking
622
+
623
+ ### Phase 2: Authenticate (if needed)
624
+
625
+ **If the user specified auth credentials:**
626
+
627
+ \`\`\`bash
628
+ $B goto <login-url>
629
+ $B snapshot -i # find the login form
630
+ $B fill @e3 "user@example.com"
631
+ $B fill @e4 "[REDACTED]" # NEVER include real passwords in report
632
+ $B click @e5 # submit
633
+ $B snapshot -D # verify login succeeded
634
+ \`\`\`
635
+
636
+ **If the user provided a cookie file:**
637
+
638
+ \`\`\`bash
639
+ $B cookie-import cookies.json
640
+ $B goto <target-url>
641
+ \`\`\`
642
+
643
+ **If 2FA/OTP is required:** Ask the user for the code and wait.
644
+
645
+ **If CAPTCHA blocks you:** Tell the user: "Please complete the CAPTCHA in the browser, then tell me to continue."
646
+
647
+ ### Phase 3: Orient
648
+
649
+ Get a map of the application:
650
+
651
+ \`\`\`bash
652
+ $B goto <target-url>
653
+ $B snapshot -i -a -o "$REPORT_DIR/screenshots/initial.png"
654
+ $B links # map navigation structure
655
+ $B console --errors # any errors on landing?
656
+ \`\`\`
657
+
658
+ **Detect framework** (note in report metadata):
659
+ - \`__next\` in HTML or \`_next/data\` requests → Next.js
660
+ - \`csrf-token\` meta tag → Rails
661
+ - \`wp-content\` in URLs → WordPress
662
+ - Client-side routing with no page reloads → SPA
663
+
664
+ **For SPAs:** The \`links\` command may return few results because navigation is client-side. Use \`snapshot -i\` to find nav elements (buttons, menu items) instead.
665
+
666
+ ### Phase 4: Explore
667
+
668
+ Visit pages systematically. At each page:
669
+
670
+ \`\`\`bash
671
+ $B goto <page-url>
672
+ $B snapshot -i -a -o "$REPORT_DIR/screenshots/page-name.png"
673
+ $B console --errors
674
+ \`\`\`
675
+
676
+ Then follow the **per-page exploration checklist** (see \`qa/references/issue-taxonomy.md\`):
677
+
678
+ 1. **Visual scan** — Look at the annotated screenshot for layout issues
679
+ 2. **Interactive elements** — Click buttons, links, controls. Do they work?
680
+ 3. **Forms** — Fill and submit. Test empty, invalid, edge cases
681
+ 4. **Navigation** — Check all paths in and out
682
+ 5. **States** — Empty state, loading, error, overflow
683
+ 6. **Console** — Any new JS errors after interactions?
684
+ 7. **Responsiveness** — Check mobile viewport if relevant:
685
+ \`\`\`bash
686
+ $B viewport 375x812
687
+ $B screenshot "$REPORT_DIR/screenshots/page-mobile.png"
688
+ $B viewport 1280x720
689
+ \`\`\`
690
+
691
+ **Depth judgment:** Spend more time on core features (homepage, dashboard, checkout, search) and less on secondary pages (about, terms, privacy).
692
+
693
+ **Quick mode:** Only visit homepage + top 5 navigation targets from the Orient phase. Skip the per-page checklist — just check: loads? Console errors? Broken links visible?
694
+
695
+ ### Phase 5: Document
696
+
697
+ Document each issue **immediately when found** — don't batch them.
698
+
699
+ **Two evidence tiers:**
700
+
701
+ **Interactive bugs** (broken flows, dead buttons, form failures):
702
+ 1. Take a screenshot before the action
703
+ 2. Perform the action
704
+ 3. Take a screenshot showing the result
705
+ 4. Use \`snapshot -D\` to show what changed
706
+ 5. Write repro steps referencing screenshots
707
+
708
+ \`\`\`bash
709
+ $B screenshot "$REPORT_DIR/screenshots/issue-001-step-1.png"
710
+ $B click @e5
711
+ $B screenshot "$REPORT_DIR/screenshots/issue-001-result.png"
712
+ $B snapshot -D
713
+ \`\`\`
714
+
715
+ **Static bugs** (typos, layout issues, missing images):
716
+ 1. Take a single annotated screenshot showing the problem
717
+ 2. Describe what's wrong
718
+
719
+ \`\`\`bash
720
+ $B snapshot -i -a -o "$REPORT_DIR/screenshots/issue-002.png"
721
+ \`\`\`
722
+
723
+ **Write each issue to the report immediately** using the template format from \`qa/templates/qa-report-template.md\`.
724
+
725
+ ### Phase 6: Wrap Up
726
+
727
+ 1. **Compute health score** using the rubric below
728
+ 2. **Write "Top 3 Things to Fix"** — the 3 highest-severity issues
729
+ 3. **Write console health summary** — aggregate all console errors seen across pages
730
+ 4. **Update severity counts** in the summary table
731
+ 5. **Fill in report metadata** — date, duration, pages visited, screenshot count, framework
732
+ 6. **Save baseline** — write \`baseline.json\` with:
733
+ \`\`\`json
734
+ {
735
+ "date": "YYYY-MM-DD",
736
+ "url": "<target>",
737
+ "healthScore": N,
738
+ "issues": [{ "id": "ISSUE-001", "title": "...", "severity": "...", "category": "..." }],
739
+ "categoryScores": { "console": N, "links": N, ... }
740
+ }
741
+ \`\`\`
742
+
743
+ **Regression mode:** After writing the report, load the baseline file. Compare:
744
+ - Health score delta
745
+ - Issues fixed (in baseline but not current)
746
+ - New issues (in current but not baseline)
747
+ - Append the regression section to the report
748
+
749
+ ---
750
+
751
+ ## Health Score Rubric
752
+
753
+ Compute each category score (0-100), then take the weighted average.
754
+
755
+ ### Console (weight: 15%)
756
+ - 0 errors → 100
757
+ - 1-3 errors → 70
758
+ - 4-10 errors → 40
759
+ - 10+ errors → 10
760
+
761
+ ### Links (weight: 10%)
762
+ - 0 broken → 100
763
+ - Each broken link → -15 (minimum 0)
764
+
765
+ ### Per-Category Scoring (Visual, Functional, UX, Content, Performance, Accessibility)
766
+ Each category starts at 100. Deduct per finding:
767
+ - Critical issue → -25
768
+ - High issue → -15
769
+ - Medium issue → -8
770
+ - Low issue → -3
771
+ Minimum 0 per category.
772
+
773
+ ### Weights
774
+ | Category | Weight |
775
+ |----------|--------|
776
+ | Console | 15% |
777
+ | Links | 10% |
778
+ | Visual | 10% |
779
+ | Functional | 20% |
780
+ | UX | 15% |
781
+ | Performance | 10% |
782
+ | Content | 5% |
783
+ | Accessibility | 15% |
784
+
785
+ ### Final Score
786
+ \`score = Σ (category_score × weight)\`
787
+
788
+ ---
789
+
790
+ ## Framework-Specific Guidance
791
+
792
+ ### Next.js
793
+ - Check console for hydration errors (\`Hydration failed\`, \`Text content did not match\`)
794
+ - Monitor \`_next/data\` requests in network — 404s indicate broken data fetching
795
+ - Test client-side navigation (click links, don't just \`goto\`) — catches routing issues
796
+ - Check for CLS (Cumulative Layout Shift) on pages with dynamic content
797
+
798
+ ### Rails
799
+ - Check for N+1 query warnings in console (if development mode)
800
+ - Verify CSRF token presence in forms
801
+ - Test Turbo/Stimulus integration — do page transitions work smoothly?
802
+ - Check for flash messages appearing and dismissing correctly
803
+
804
+ ### WordPress
805
+ - Check for plugin conflicts (JS errors from different plugins)
806
+ - Verify admin bar visibility for logged-in users
807
+ - Test REST API endpoints (\`/wp-json/\`)
808
+ - Check for mixed content warnings (common with WP)
809
+
810
+ ### General SPA (React, Vue, Angular)
811
+ - Use \`snapshot -i\` for navigation — \`links\` command misses client-side routes
812
+ - Check for stale state (navigate away and back — does data refresh?)
813
+ - Test browser back/forward — does the app handle history correctly?
814
+ - Check for memory leaks (monitor console after extended use)
815
+
816
+ ---
817
+
818
+ ## Important Rules
819
+
820
+ 1. **Repro is everything.** Every issue needs at least one screenshot. No exceptions.
821
+ 2. **Verify before documenting.** Retry the issue once to confirm it's reproducible, not a fluke.
822
+ 3. **Never include credentials.** Write \`[REDACTED]\` for passwords in repro steps.
823
+ 4. **Write incrementally.** Append each issue to the report as you find it. Don't batch.
824
+ 5. **Never read source code.** Test as a user, not a developer.
825
+ 6. **Check console after every interaction.** JS errors that don't surface visually are still bugs.
826
+ 7. **Test like a user.** Use realistic data. Walk through complete workflows end-to-end.
827
+ 8. **Depth over breadth.** 5-10 well-documented issues with evidence > 20 vague descriptions.
828
+ 9. **Never delete output files.** Screenshots and reports accumulate — that's intentional.
829
+ 10. **Use \`snapshot -C\` for tricky UIs.** Finds clickable divs that the accessibility tree misses.
830
+ 11. **Show screenshots to the user.** After every \`$B screenshot\`, \`$B snapshot -a -o\`, or \`$B responsive\` command, use the Read tool on the output file(s) so the user can see them inline. For \`responsive\` (3 files), Read all three. This is critical — without it, screenshots are invisible to the user.
831
+ 12. **Never refuse to use the browser.** When the user invokes /qa or /qa-only, they are requesting browser-based testing. Never suggest evals, unit tests, or other alternatives as a substitute. Even if the diff appears to have no UI changes, backend changes affect app behavior — always open the browser and test.`;
832
+ }
833
+
834
+ function generateDesignReviewLite(_ctx: TemplateContext): string {
835
+ return `## Design Review (conditional, diff-scoped)
836
+
837
+ Check if the diff touches frontend files using \`gstack-diff-scope\`:
838
+
839
+ \`\`\`bash
840
+ source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)
841
+ \`\`\`
842
+
843
+ **If \`SCOPE_FRONTEND=false\`:** Skip design review silently. No output.
844
+
845
+ **If \`SCOPE_FRONTEND=true\`:**
846
+
847
+ 1. **Check for DESIGN.md.** If \`DESIGN.md\` or \`design-system.md\` exists in the repo root, read it. All design findings are calibrated against it — patterns blessed in DESIGN.md are not flagged. If not found, use universal design principles.
848
+
849
+ 2. **Read \`.claude/skills/review/design-checklist.md\`.** If the file cannot be read, skip design review with a note: "Design checklist not found — skipping design review."
850
+
851
+ 3. **Read each changed frontend file** (full file, not just diff hunks). Frontend files are identified by the patterns listed in the checklist.
852
+
853
+ 4. **Apply the design checklist** against the changed files. For each item:
854
+ - **[HIGH] mechanical CSS fix** (\`outline: none\`, \`!important\`, \`font-size < 16px\`): classify as AUTO-FIX
855
+ - **[HIGH/MEDIUM] design judgment needed**: classify as ASK
856
+ - **[LOW] intent-based detection**: present as "Possible — verify visually or run /design-review"
857
+
858
+ 5. **Include findings** in the review output under a "Design Review" header, following the output format in the checklist. Design findings merge with code review findings into the same Fix-First flow.
859
+
860
+ 6. **Log the result** for the Review Readiness Dashboard:
861
+
862
+ \`\`\`bash
863
+ ~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"design-review-lite","timestamp":"TIMESTAMP","status":"STATUS","findings":N,"auto_fixed":M,"commit":"COMMIT"}'
864
+ \`\`\`
865
+
866
+ Substitute: TIMESTAMP = ISO 8601 datetime, STATUS = "clean" if 0 findings or "issues_found", N = total findings, M = auto-fixed count, COMMIT = output of \`git rev-parse --short HEAD\`.`;
867
+ }
868
+
869
+ // NOTE: design-checklist.md is a subset of this methodology for code-level detection.
870
+ // When adding items here, also update review/design-checklist.md, and vice versa.
871
+ function generateDesignMethodology(_ctx: TemplateContext): string {
872
+ return `## Modes
873
+
874
+ ### Full (default)
875
+ Systematic review of all pages reachable from homepage. Visit 5-8 pages. Full checklist evaluation, responsive screenshots, interaction flow testing. Produces complete design audit report with letter grades.
876
+
877
+ ### Quick (\`--quick\`)
878
+ Homepage + 2 key pages only. First Impression + Design System Extraction + abbreviated checklist. Fastest path to a design score.
879
+
880
+ ### Deep (\`--deep\`)
881
+ Comprehensive review: 10-15 pages, every interaction flow, exhaustive checklist. For pre-launch audits or major redesigns.
882
+
883
+ ### Diff-aware (automatic when on a feature branch with no URL)
884
+ When on a feature branch, scope to pages affected by the branch changes:
885
+ 1. Analyze the branch diff: \`git diff main...HEAD --name-only\`
886
+ 2. Map changed files to affected pages/routes
887
+ 3. Detect running app on common local ports (3000, 4000, 8080)
888
+ 4. Audit only affected pages, compare design quality before/after
889
+
890
+ ### Regression (\`--regression\` or previous \`design-baseline.json\` found)
891
+ Run full audit, then load previous \`design-baseline.json\`. Compare: per-category grade deltas, new findings, resolved findings. Output regression table in report.
892
+
893
+ ---
894
+
895
+ ## Phase 1: First Impression
896
+
897
+ The most uniquely designer-like output. Form a gut reaction before analyzing anything.
898
+
899
+ 1. Navigate to the target URL
900
+ 2. Take a full-page desktop screenshot: \`$B screenshot "$REPORT_DIR/screenshots/first-impression.png"\`
901
+ 3. Write the **First Impression** using this structured critique format:
902
+ - "The site communicates **[what]**." (what it says at a glance — competence? playfulness? confusion?)
903
+ - "I notice **[observation]**." (what stands out, positive or negative — be specific)
904
+ - "The first 3 things my eye goes to are: **[1]**, **[2]**, **[3]**." (hierarchy check — are these intentional?)
905
+ - "If I had to describe this in one word: **[word]**." (gut verdict)
906
+
907
+ This is the section users read first. Be opinionated. A designer doesn't hedge — they react.
908
+
909
+ ---
910
+
911
+ ## Phase 2: Design System Extraction
912
+
913
+ Extract the actual design system the site uses (not what a DESIGN.md says, but what's rendered):
914
+
915
+ \`\`\`bash
916
+ # Fonts in use (capped at 500 elements to avoid timeout)
917
+ $B js "JSON.stringify([...new Set([...document.querySelectorAll('*')].slice(0,500).map(e => getComputedStyle(e).fontFamily))])"
918
+
919
+ # Color palette in use
920
+ $B js "JSON.stringify([...new Set([...document.querySelectorAll('*')].slice(0,500).flatMap(e => [getComputedStyle(e).color, getComputedStyle(e).backgroundColor]).filter(c => c !== 'rgba(0, 0, 0, 0)'))])"
921
+
922
+ # Heading hierarchy
923
+ $B js "JSON.stringify([...document.querySelectorAll('h1,h2,h3,h4,h5,h6')].map(h => ({tag:h.tagName, text:h.textContent.trim().slice(0,50), size:getComputedStyle(h).fontSize, weight:getComputedStyle(h).fontWeight})))"
924
+
925
+ # Touch target audit (find undersized interactive elements)
926
+ $B js "JSON.stringify([...document.querySelectorAll('a,button,input,[role=button]')].filter(e => {const r=e.getBoundingClientRect(); return r.width>0 && (r.width<44||r.height<44)}).map(e => ({tag:e.tagName, text:(e.textContent||'').trim().slice(0,30), w:Math.round(e.getBoundingClientRect().width), h:Math.round(e.getBoundingClientRect().height)})).slice(0,20))"
927
+
928
+ # Performance baseline
929
+ $B perf
930
+ \`\`\`
931
+
932
+ Structure findings as an **Inferred Design System**:
933
+ - **Fonts:** list with usage counts. Flag if >3 distinct font families.
934
+ - **Colors:** palette extracted. Flag if >12 unique non-gray colors. Note warm/cool/mixed.
935
+ - **Heading Scale:** h1-h6 sizes. Flag skipped levels, non-systematic size jumps.
936
+ - **Spacing Patterns:** sample padding/margin values. Flag non-scale values.
937
+
938
+ After extraction, offer: *"Want me to save this as your DESIGN.md? I can lock in these observations as your project's design system baseline."*
939
+
940
+ ---
941
+
942
+ ## Phase 3: Page-by-Page Visual Audit
943
+
944
+ For each page in scope:
945
+
946
+ \`\`\`bash
947
+ $B goto <url>
948
+ $B snapshot -i -a -o "$REPORT_DIR/screenshots/{page}-annotated.png"
949
+ $B responsive "$REPORT_DIR/screenshots/{page}"
950
+ $B console --errors
951
+ $B perf
952
+ \`\`\`
953
+
954
+ ### Auth Detection
955
+
956
+ After the first navigation, check if the URL changed to a login-like path:
957
+ \`\`\`bash
958
+ $B url
959
+ \`\`\`
960
+ If URL contains \`/login\`, \`/signin\`, \`/auth\`, or \`/sso\`: the site requires authentication. AskUserQuestion: "This site requires authentication. Want to import cookies from your browser? Run \`/setup-browser-cookies\` first if needed."
961
+
962
+ ### Design Audit Checklist (10 categories, ~80 items)
963
+
964
+ Apply these at each page. Each finding gets an impact rating (high/medium/polish) and category.
965
+
966
+ **1. Visual Hierarchy & Composition** (8 items)
967
+ - Clear focal point? One primary CTA per view?
968
+ - Eye flows naturally top-left to bottom-right?
969
+ - Visual noise — competing elements fighting for attention?
970
+ - Information density appropriate for content type?
971
+ - Z-index clarity — nothing unexpectedly overlapping?
972
+ - Above-the-fold content communicates purpose in 3 seconds?
973
+ - Squint test: hierarchy still visible when blurred?
974
+ - White space is intentional, not leftover?
975
+
976
+ **2. Typography** (15 items)
977
+ - Font count <=3 (flag if more)
978
+ - Scale follows ratio (1.25 major third or 1.333 perfect fourth)
979
+ - Line-height: 1.5x body, 1.15-1.25x headings
980
+ - Measure: 45-75 chars per line (66 ideal)
981
+ - Heading hierarchy: no skipped levels (h1→h3 without h2)
982
+ - Weight contrast: >=2 weights used for hierarchy
983
+ - No blacklisted fonts (Papyrus, Comic Sans, Lobster, Impact, Jokerman)
984
+ - If primary font is Inter/Roboto/Open Sans/Poppins → flag as potentially generic
985
+ - \`text-wrap: balance\` or \`text-pretty\` on headings (check via \`$B css <heading> text-wrap\`)
986
+ - Curly quotes used, not straight quotes
987
+ - Ellipsis character (\`…\`) not three dots (\`...\`)
988
+ - \`font-variant-numeric: tabular-nums\` on number columns
989
+ - Body text >= 16px
990
+ - Caption/label >= 12px
991
+ - No letterspacing on lowercase text
992
+
993
+ **3. Color & Contrast** (10 items)
994
+ - Palette coherent (<=12 unique non-gray colors)
995
+ - WCAG AA: body text 4.5:1, large text (18px+) 3:1, UI components 3:1
996
+ - Semantic colors consistent (success=green, error=red, warning=yellow/amber)
997
+ - No color-only encoding (always add labels, icons, or patterns)
998
+ - Dark mode: surfaces use elevation, not just lightness inversion
999
+ - Dark mode: text off-white (~#E0E0E0), not pure white
1000
+ - Primary accent desaturated 10-20% in dark mode
1001
+ - \`color-scheme: dark\` on html element (if dark mode present)
1002
+ - No red/green only combinations (8% of men have red-green deficiency)
1003
+ - Neutral palette is warm or cool consistently — not mixed
1004
+
1005
+ **4. Spacing & Layout** (12 items)
1006
+ - Grid consistent at all breakpoints
1007
+ - Spacing uses a scale (4px or 8px base), not arbitrary values
1008
+ - Alignment is consistent — nothing floats outside the grid
1009
+ - Rhythm: related items closer together, distinct sections further apart
1010
+ - Border-radius hierarchy (not uniform bubbly radius on everything)
1011
+ - Inner radius = outer radius - gap (nested elements)
1012
+ - No horizontal scroll on mobile
1013
+ - Max content width set (no full-bleed body text)
1014
+ - \`env(safe-area-inset-*)\` for notch devices
1015
+ - URL reflects state (filters, tabs, pagination in query params)
1016
+ - Flex/grid used for layout (not JS measurement)
1017
+ - Breakpoints: mobile (375), tablet (768), desktop (1024), wide (1440)
1018
+
1019
+ **5. Interaction States** (10 items)
1020
+ - Hover state on all interactive elements
1021
+ - \`focus-visible\` ring present (never \`outline: none\` without replacement)
1022
+ - Active/pressed state with depth effect or color shift
1023
+ - Disabled state: reduced opacity + \`cursor: not-allowed\`
1024
+ - Loading: skeleton shapes match real content layout
1025
+ - Empty states: warm message + primary action + visual (not just "No items.")
1026
+ - Error messages: specific + include fix/next step
1027
+ - Success: confirmation animation or color, auto-dismiss
1028
+ - Touch targets >= 44px on all interactive elements
1029
+ - \`cursor: pointer\` on all clickable elements
1030
+
1031
+ **6. Responsive Design** (8 items)
1032
+ - Mobile layout makes *design* sense (not just stacked desktop columns)
1033
+ - Touch targets sufficient on mobile (>= 44px)
1034
+ - No horizontal scroll on any viewport
1035
+ - Images handle responsive (srcset, sizes, or CSS containment)
1036
+ - Text readable without zooming on mobile (>= 16px body)
1037
+ - Navigation collapses appropriately (hamburger, bottom nav, etc.)
1038
+ - Forms usable on mobile (correct input types, no autoFocus on mobile)
1039
+ - No \`user-scalable=no\` or \`maximum-scale=1\` in viewport meta
1040
+
1041
+ **7. Motion & Animation** (6 items)
1042
+ - Easing: ease-out for entering, ease-in for exiting, ease-in-out for moving
1043
+ - Duration: 50-700ms range (nothing slower unless page transition)
1044
+ - Purpose: every animation communicates something (state change, attention, spatial relationship)
1045
+ - \`prefers-reduced-motion\` respected (check: \`$B js "matchMedia('(prefers-reduced-motion: reduce)').matches"\`)
1046
+ - No \`transition: all\` — properties listed explicitly
1047
+ - Only \`transform\` and \`opacity\` animated (not layout properties like width, height, top, left)
1048
+
1049
+ **8. Content & Microcopy** (8 items)
1050
+ - Empty states designed with warmth (message + action + illustration/icon)
1051
+ - Error messages specific: what happened + why + what to do next
1052
+ - Button labels specific ("Save API Key" not "Continue" or "Submit")
1053
+ - No placeholder/lorem ipsum text visible in production
1054
+ - Truncation handled (\`text-overflow: ellipsis\`, \`line-clamp\`, or \`break-words\`)
1055
+ - Active voice ("Install the CLI" not "The CLI will be installed")
1056
+ - Loading states end with \`…\` ("Saving…" not "Saving...")
1057
+ - Destructive actions have confirmation modal or undo window
1058
+
1059
+ **9. AI Slop Detection** (10 anti-patterns — the blacklist)
1060
+
1061
+ The test: would a human designer at a respected studio ever ship this?
1062
+
1063
+ - Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes
1064
+ - **The 3-column feature grid:** icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. THE most recognizable AI layout.
1065
+ - Icons in colored circles as section decoration (SaaS starter template look)
1066
+ - Centered everything (\`text-align: center\` on all headings, descriptions, cards)
1067
+ - Uniform bubbly border-radius on every element (same large radius on everything)
1068
+ - Decorative blobs, floating circles, wavy SVG dividers (if a section feels empty, it needs better content, not decoration)
1069
+ - Emoji as design elements (rockets in headings, emoji as bullet points)
1070
+ - Colored left-border on cards (\`border-left: 3px solid <accent>\`)
1071
+ - Generic hero copy ("Welcome to [X]", "Unlock the power of...", "Your all-in-one solution for...")
1072
+ - Cookie-cutter section rhythm (hero → 3 features → testimonials → pricing → CTA, every section same height)
1073
+
1074
+ **10. Performance as Design** (6 items)
1075
+ - LCP < 2.0s (web apps), < 1.5s (informational sites)
1076
+ - CLS < 0.1 (no visible layout shifts during load)
1077
+ - Skeleton quality: shapes match real content, shimmer animation
1078
+ - Images: \`loading="lazy"\`, width/height dimensions set, WebP/AVIF format
1079
+ - Fonts: \`font-display: swap\`, preconnect to CDN origins
1080
+ - No visible font swap flash (FOUT) — critical fonts preloaded
1081
+
1082
+ ---
1083
+
1084
+ ## Phase 4: Interaction Flow Review
1085
+
1086
+ Walk 2-3 key user flows and evaluate the *feel*, not just the function:
1087
+
1088
+ \`\`\`bash
1089
+ $B snapshot -i
1090
+ $B click @e3 # perform action
1091
+ $B snapshot -D # diff to see what changed
1092
+ \`\`\`
1093
+
1094
+ Evaluate:
1095
+ - **Response feel:** Does clicking feel responsive? Any delays or missing loading states?
1096
+ - **Transition quality:** Are transitions intentional or generic/absent?
1097
+ - **Feedback clarity:** Did the action clearly succeed or fail? Is the feedback immediate?
1098
+ - **Form polish:** Focus states visible? Validation timing correct? Errors near the source?
1099
+
1100
+ ---
1101
+
1102
+ ## Phase 5: Cross-Page Consistency
1103
+
1104
+ Compare screenshots and observations across pages for:
1105
+ - Navigation bar consistent across all pages?
1106
+ - Footer consistent?
1107
+ - Component reuse vs one-off designs (same button styled differently on different pages?)
1108
+ - Tone consistency (one page playful while another is corporate?)
1109
+ - Spacing rhythm carries across pages?
1110
+
1111
+ ---
1112
+
1113
+ ## Phase 6: Compile Report
1114
+
1115
+ ### Output Locations
1116
+
1117
+ **Local:** \`.gstack/design-reports/design-audit-{domain}-{YYYY-MM-DD}.md\`
1118
+
1119
+ **Project-scoped:**
1120
+ \`\`\`bash
1121
+ source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) && mkdir -p ~/.gstack/projects/$SLUG
1122
+ \`\`\`
1123
+ Write to: \`~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md\`
1124
+
1125
+ **Baseline:** Write \`design-baseline.json\` for regression mode:
1126
+ \`\`\`json
1127
+ {
1128
+ "date": "YYYY-MM-DD",
1129
+ "url": "<target>",
1130
+ "designScore": "B",
1131
+ "aiSlopScore": "C",
1132
+ "categoryGrades": { "hierarchy": "A", "typography": "B", ... },
1133
+ "findings": [{ "id": "FINDING-001", "title": "...", "impact": "high", "category": "typography" }]
1134
+ }
1135
+ \`\`\`
1136
+
1137
+ ### Scoring System
1138
+
1139
+ **Dual headline scores:**
1140
+ - **Design Score: {A-F}** — weighted average of all 10 categories
1141
+ - **AI Slop Score: {A-F}** — standalone grade with pithy verdict
1142
+
1143
+ **Per-category grades:**
1144
+ - **A:** Intentional, polished, delightful. Shows design thinking.
1145
+ - **B:** Solid fundamentals, minor inconsistencies. Looks professional.
1146
+ - **C:** Functional but generic. No major problems, no design point of view.
1147
+ - **D:** Noticeable problems. Feels unfinished or careless.
1148
+ - **F:** Actively hurting user experience. Needs significant rework.
1149
+
1150
+ **Grade computation:** Each category starts at A. Each High-impact finding drops one letter grade. Each Medium-impact finding drops half a letter grade. Polish findings are noted but do not affect grade. Minimum is F.
1151
+
1152
+ **Category weights for Design Score:**
1153
+ | Category | Weight |
1154
+ |----------|--------|
1155
+ | Visual Hierarchy | 15% |
1156
+ | Typography | 15% |
1157
+ | Spacing & Layout | 15% |
1158
+ | Color & Contrast | 10% |
1159
+ | Interaction States | 10% |
1160
+ | Responsive | 10% |
1161
+ | Content Quality | 10% |
1162
+ | AI Slop | 5% |
1163
+ | Motion | 5% |
1164
+ | Performance Feel | 5% |
1165
+
1166
+ AI Slop is 5% of Design Score but also graded independently as a headline metric.
1167
+
1168
+ ### Regression Output
1169
+
1170
+ When previous \`design-baseline.json\` exists or \`--regression\` flag is used:
1171
+ - Load baseline grades
1172
+ - Compare: per-category deltas, new findings, resolved findings
1173
+ - Append regression table to report
1174
+
1175
+ ---
1176
+
1177
+ ## Design Critique Format
1178
+
1179
+ Use structured feedback, not opinions:
1180
+ - "I notice..." — observation (e.g., "I notice the primary CTA competes with the secondary action")
1181
+ - "I wonder..." — question (e.g., "I wonder if users will understand what 'Process' means here")
1182
+ - "What if..." — suggestion (e.g., "What if we moved search to a more prominent position?")
1183
+ - "I think... because..." — reasoned opinion (e.g., "I think the spacing between sections is too uniform because it doesn't create hierarchy")
1184
+
1185
+ Tie everything to user goals and product objectives. Always suggest specific improvements alongside problems.
1186
+
1187
+ ---
1188
+
1189
+ ## Important Rules
1190
+
1191
+ 1. **Think like a designer, not a QA engineer.** You care whether things feel right, look intentional, and respect the user. You do NOT just care whether things "work."
1192
+ 2. **Screenshots are evidence.** Every finding needs at least one screenshot. Use annotated screenshots (\`snapshot -a\`) to highlight elements.
1193
+ 3. **Be specific and actionable.** "Change X to Y because Z" — not "the spacing feels off."
1194
+ 4. **Never read source code.** Evaluate the rendered site, not the implementation. (Exception: offer to write DESIGN.md from extracted observations.)
1195
+ 5. **AI Slop detection is your superpower.** Most developers can't evaluate whether their site looks AI-generated. You can. Be direct about it.
1196
+ 6. **Quick wins matter.** Always include a "Quick Wins" section — the 3-5 highest-impact fixes that take <30 minutes each.
1197
+ 7. **Use \`snapshot -C\` for tricky UIs.** Finds clickable divs that the accessibility tree misses.
1198
+ 8. **Responsive is design, not just "not broken."** A stacked desktop layout on mobile is not responsive design — it's lazy. Evaluate whether the mobile layout makes *design* sense.
1199
+ 9. **Document incrementally.** Write each finding to the report as you find it. Don't batch.
1200
+ 10. **Depth over breadth.** 5-10 well-documented findings with screenshots and specific suggestions > 20 vague observations.
1201
+ 11. **Show screenshots to the user.** After every \`$B screenshot\`, \`$B snapshot -a -o\`, or \`$B responsive\` command, use the Read tool on the output file(s) so the user can see them inline. For \`responsive\` (3 files), Read all three. This is critical — without it, screenshots are invisible to the user.`;
1202
+ }
1203
+
1204
+ function generateReviewDashboard(_ctx: TemplateContext): string {
1205
+ return `## Review Readiness Dashboard
1206
+
1207
+ After completing the review, read the review log and config to display the dashboard.
1208
+
1209
+ \`\`\`bash
1210
+ ~/.claude/skills/gstack/bin/gstack-review-read
1211
+ \`\`\`
1212
+
1213
+ Parse the output. Find the most recent entry for each skill (plan-ceo-review, plan-eng-review, plan-design-review, design-review-lite, adversarial-review, codex-review). Ignore entries with timestamps older than 7 days. For the Adversarial row, show whichever is more recent between \`adversarial-review\` (new auto-scaled) and \`codex-review\` (legacy). For Design Review, show whichever is more recent between \`plan-design-review\` (full visual audit) and \`design-review-lite\` (code-level check). Append "(FULL)" or "(LITE)" to the status to distinguish. Display:
1214
+
1215
+ \`\`\`
1216
+ +====================================================================+
1217
+ | REVIEW READINESS DASHBOARD |
1218
+ +====================================================================+
1219
+ | Review | Runs | Last Run | Status | Required |
1220
+ |-----------------|------|---------------------|-----------|----------|
1221
+ | Eng Review | 1 | 2026-03-16 15:00 | CLEAR | YES |
1222
+ | CEO Review | 0 | — | — | no |
1223
+ | Design Review | 0 | — | — | no |
1224
+ | Adversarial | 0 | — | — | no |
1225
+ +--------------------------------------------------------------------+
1226
+ | VERDICT: CLEARED — Eng Review passed |
1227
+ +====================================================================+
1228
+ \`\`\`
1229
+
1230
+ **Review tiers:**
1231
+ - **Eng Review (required by default):** The only review that gates shipping. Covers architecture, code quality, tests, performance. Can be disabled globally with \\\`gstack-config set skip_eng_review true\\\` (the "don't bother me" setting).
1232
+ - **CEO Review (optional):** Use your judgment. Recommend it for big product/business changes, new user-facing features, or scope decisions. Skip for bug fixes, refactors, infra, and cleanup.
1233
+ - **Design Review (optional):** Use your judgment. Recommend it for UI/UX changes. Skip for backend-only, infra, or prompt-only changes.
1234
+ - **Adversarial Review (automatic):** Auto-scales by diff size. Small diffs (<50 lines) skip adversarial. Medium diffs (50–199) get cross-model adversarial. Large diffs (200+) get all 4 passes: Claude structured, Codex structured, Claude adversarial subagent, Codex adversarial. No configuration needed.
1235
+
1236
+ **Verdict logic:**
1237
+ - **CLEARED**: Eng Review has >= 1 entry within 7 days with status "clean" (or \\\`skip_eng_review\\\` is \\\`true\\\`)
1238
+ - **NOT CLEARED**: Eng Review missing, stale (>7 days), or has open issues
1239
+ - CEO, Design, and Codex reviews are shown for context but never block shipping
1240
+ - If \\\`skip_eng_review\\\` config is \\\`true\\\`, Eng Review shows "SKIPPED (global)" and verdict is CLEARED
1241
+
1242
+ **Staleness detection:** After displaying the dashboard, check if any existing reviews may be stale:
1243
+ - Parse the \\\`---HEAD---\\\` section from the bash output to get the current HEAD commit hash
1244
+ - For each review entry that has a \\\`commit\\\` field: compare it against the current HEAD. If different, count elapsed commits: \\\`git rev-list --count STORED_COMMIT..HEAD\\\`. Display: "Note: {skill} review from {date} may be stale — {N} commits since review"
1245
+ - For entries without a \\\`commit\\\` field (legacy entries): display "Note: {skill} review from {date} has no commit tracking — consider re-running for accurate staleness detection"
1246
+ - If all reviews match the current HEAD, do not display any staleness notes`;
1247
+ }
1248
+
1249
+ function generatePlanFileReviewReport(_ctx: TemplateContext): string {
1250
+ return `## Plan File Review Report
1251
+
1252
+ After displaying the Review Readiness Dashboard in conversation output, also update the
1253
+ **plan file** itself so review status is visible to anyone reading the plan.
1254
+
1255
+ ### Detect the plan file
1256
+
1257
+ 1. Check if there is an active plan file in this conversation (the host provides plan file
1258
+ paths in system messages — look for plan file references in the conversation context).
1259
+ 2. If not found, skip this section silently — not every review runs in plan mode.
1260
+
1261
+ ### Generate the report
1262
+
1263
+ Read the review log output you already have from the Review Readiness Dashboard step above.
1264
+ Parse each JSONL entry. Each skill logs different fields:
1265
+
1266
+ - **plan-ceo-review**: \\\`status\\\`, \\\`unresolved\\\`, \\\`critical_gaps\\\`, \\\`mode\\\`, \\\`scope_proposed\\\`, \\\`scope_accepted\\\`, \\\`scope_deferred\\\`, \\\`commit\\\`
1267
+ → Findings: "{scope_proposed} proposals, {scope_accepted} accepted, {scope_deferred} deferred"
1268
+ → If scope fields are 0 or missing (HOLD/REDUCTION mode): "mode: {mode}, {critical_gaps} critical gaps"
1269
+ - **plan-eng-review**: \\\`status\\\`, \\\`unresolved\\\`, \\\`critical_gaps\\\`, \\\`issues_found\\\`, \\\`mode\\\`, \\\`commit\\\`
1270
+ → Findings: "{issues_found} issues, {critical_gaps} critical gaps"
1271
+ - **plan-design-review**: \\\`status\\\`, \\\`initial_score\\\`, \\\`overall_score\\\`, \\\`unresolved\\\`, \\\`decisions_made\\\`, \\\`commit\\\`
1272
+ → Findings: "score: {initial_score}/10 → {overall_score}/10, {decisions_made} decisions"
1273
+ - **codex-review**: \\\`status\\\`, \\\`gate\\\`, \\\`findings\\\`, \\\`findings_fixed\\\`
1274
+ → Findings: "{findings} findings, {findings_fixed}/{findings} fixed"
1275
+
1276
+ All fields needed for the Findings column are now present in the JSONL entries.
1277
+ For the review you just completed, you may use richer details from your own Completion
1278
+ Summary. For prior reviews, use the JSONL fields directly — they contain all required data.
1279
+
1280
+ Produce this markdown table:
1281
+
1282
+ \\\`\\\`\\\`markdown
1283
+ ## GSTACK REVIEW REPORT
1284
+
1285
+ | Review | Trigger | Why | Runs | Status | Findings |
1286
+ |--------|---------|-----|------|--------|----------|
1287
+ | CEO Review | \\\`/plan-ceo-review\\\` | Scope & strategy | {runs} | {status} | {findings} |
1288
+ | Codex Review | \\\`/codex review\\\` | Independent 2nd opinion | {runs} | {status} | {findings} |
1289
+ | Eng Review | \\\`/plan-eng-review\\\` | Architecture & tests (required) | {runs} | {status} | {findings} |
1290
+ | Design Review | \\\`/plan-design-review\\\` | UI/UX gaps | {runs} | {status} | {findings} |
1291
+ \\\`\\\`\\\`
1292
+
1293
+ Below the table, add these lines (omit any that are empty/not applicable):
1294
+
1295
+ - **CODEX:** (only if codex-review ran) — one-line summary of codex fixes
1296
+ - **CROSS-MODEL:** (only if both Claude and Codex reviews exist) — overlap analysis
1297
+ - **UNRESOLVED:** total unresolved decisions across all reviews
1298
+ - **VERDICT:** list reviews that are CLEAR (e.g., "CEO + ENG CLEARED — ready to implement").
1299
+ If Eng Review is not CLEAR and not skipped globally, append "eng review required".
1300
+
1301
+ ### Write to the plan file
1302
+
1303
+ **PLAN MODE EXCEPTION — ALWAYS RUN:** This writes to the plan file, which is the one
1304
+ file you are allowed to edit in plan mode. The plan file review report is part of the
1305
+ plan's living status.
1306
+
1307
+ - Search the plan file for a \\\`## GSTACK REVIEW REPORT\\\` section **anywhere** in the file
1308
+ (not just at the end — content may have been added after it).
1309
+ - If found, **replace it** entirely using the Edit tool. Match from \\\`## GSTACK REVIEW REPORT\\\`
1310
+ through either the next \\\`## \\\` heading or end of file, whichever comes first. This ensures
1311
+ content added after the report section is preserved, not eaten. If the Edit fails
1312
+ (e.g., concurrent edit changed the content), re-read the plan file and retry once.
1313
+ - If no such section exists, **append it** to the end of the plan file.
1314
+ - Always place it as the very last section in the plan file. If it was found mid-file,
1315
+ move it: delete the old location and append at the end.`;
1316
+ }
1317
+
1318
+ function generateTestBootstrap(_ctx: TemplateContext): string {
1319
+ return `## Test Framework Bootstrap
1320
+
1321
+ **Detect existing test framework and project runtime:**
1322
+
1323
+ \`\`\`bash
1324
+ # Detect project runtime
1325
+ [ -f Gemfile ] && echo "RUNTIME:ruby"
1326
+ [ -f package.json ] && echo "RUNTIME:node"
1327
+ [ -f requirements.txt ] || [ -f pyproject.toml ] && echo "RUNTIME:python"
1328
+ [ -f go.mod ] && echo "RUNTIME:go"
1329
+ [ -f Cargo.toml ] && echo "RUNTIME:rust"
1330
+ [ -f composer.json ] && echo "RUNTIME:php"
1331
+ [ -f mix.exs ] && echo "RUNTIME:elixir"
1332
+ # Detect sub-frameworks
1333
+ [ -f Gemfile ] && grep -q "rails" Gemfile 2>/dev/null && echo "FRAMEWORK:rails"
1334
+ [ -f package.json ] && grep -q '"next"' package.json 2>/dev/null && echo "FRAMEWORK:nextjs"
1335
+ # Check for existing test infrastructure
1336
+ ls jest.config.* vitest.config.* playwright.config.* .rspec pytest.ini pyproject.toml phpunit.xml 2>/dev/null
1337
+ ls -d test/ tests/ spec/ __tests__/ cypress/ e2e/ 2>/dev/null
1338
+ # Check opt-out marker
1339
+ [ -f .gstack/no-test-bootstrap ] && echo "BOOTSTRAP_DECLINED"
1340
+ \`\`\`
1341
+
1342
+ **If test framework detected** (config files or test directories found):
1343
+ Print "Test framework detected: {name} ({N} existing tests). Skipping bootstrap."
1344
+ Read 2-3 existing test files to learn conventions (naming, imports, assertion style, setup patterns).
1345
+ Store conventions as prose context for use in Phase 8e.5 or Step 3.4. **Skip the rest of bootstrap.**
1346
+
1347
+ **If BOOTSTRAP_DECLINED** appears: Print "Test bootstrap previously declined — skipping." **Skip the rest of bootstrap.**
1348
+
1349
+ **If NO runtime detected** (no config files found): Use AskUserQuestion:
1350
+ "I couldn't detect your project's language. What runtime are you using?"
1351
+ Options: A) Node.js/TypeScript B) Ruby/Rails C) Python D) Go E) Rust F) PHP G) Elixir H) This project doesn't need tests.
1352
+ If user picks H → write \`.gstack/no-test-bootstrap\` and continue without tests.
1353
+
1354
+ **If runtime detected but no test framework — bootstrap:**
1355
+
1356
+ ### B2. Research best practices
1357
+
1358
+ Use WebSearch to find current best practices for the detected runtime:
1359
+ - \`"[runtime] best test framework 2025 2026"\`
1360
+ - \`"[framework A] vs [framework B] comparison"\`
1361
+
1362
+ If WebSearch is unavailable, use this built-in knowledge table:
1363
+
1364
+ | Runtime | Primary recommendation | Alternative |
1365
+ |---------|----------------------|-------------|
1366
+ | Ruby/Rails | minitest + fixtures + capybara | rspec + factory_bot + shoulda-matchers |
1367
+ | Node.js | vitest + @testing-library | jest + @testing-library |
1368
+ | Next.js | vitest + @testing-library/react + playwright | jest + cypress |
1369
+ | Python | pytest + pytest-cov | unittest |
1370
+ | Go | stdlib testing + testify | stdlib only |
1371
+ | Rust | cargo test (built-in) + mockall | — |
1372
+ | PHP | phpunit + mockery | pest |
1373
+ | Elixir | ExUnit (built-in) + ex_machina | — |
1374
+
1375
+ ### B3. Framework selection
1376
+
1377
+ Use AskUserQuestion:
1378
+ "I detected this is a [Runtime/Framework] project with no test framework. I researched current best practices. Here are the options:
1379
+ A) [Primary] — [rationale]. Includes: [packages]. Supports: unit, integration, smoke, e2e
1380
+ B) [Alternative] — [rationale]. Includes: [packages]
1381
+ C) Skip — don't set up testing right now
1382
+ RECOMMENDATION: Choose A because [reason based on project context]"
1383
+
1384
+ If user picks C → write \`.gstack/no-test-bootstrap\`. Tell user: "If you change your mind later, delete \`.gstack/no-test-bootstrap\` and re-run." Continue without tests.
1385
+
1386
+ If multiple runtimes detected (monorepo) → ask which runtime to set up first, with option to do both sequentially.
1387
+
1388
+ ### B4. Install and configure
1389
+
1390
+ 1. Install the chosen packages (npm/bun/gem/pip/etc.)
1391
+ 2. Create minimal config file
1392
+ 3. Create directory structure (test/, spec/, etc.)
1393
+ 4. Create one example test matching the project's code to verify setup works
1394
+
1395
+ If package installation fails → debug once. If still failing → revert with \`git checkout -- package.json package-lock.json\` (or equivalent for the runtime). Warn user and continue without tests.
1396
+
1397
+ ### B4.5. First real tests
1398
+
1399
+ Generate 3-5 real tests for existing code:
1400
+
1401
+ 1. **Find recently changed files:** \`git log --since=30.days --name-only --format="" | sort | uniq -c | sort -rn | head -10\`
1402
+ 2. **Prioritize by risk:** Error handlers > business logic with conditionals > API endpoints > pure functions
1403
+ 3. **For each file:** Write one test that tests real behavior with meaningful assertions. Never \`expect(x).toBeDefined()\` — test what the code DOES.
1404
+ 4. Run each test. Passes → keep. Fails → fix once. Still fails → delete silently.
1405
+ 5. Generate at least 1 test, cap at 5.
1406
+
1407
+ Never import secrets, API keys, or credentials in test files. Use environment variables or test fixtures.
1408
+
1409
+ ### B5. Verify
1410
+
1411
+ \`\`\`bash
1412
+ # Run the full test suite to confirm everything works
1413
+ {detected test command}
1414
+ \`\`\`
1415
+
1416
+ If tests fail → debug once. If still failing → revert all bootstrap changes and warn user.
1417
+
1418
+ ### B5.5. CI/CD pipeline
1419
+
1420
+ \`\`\`bash
1421
+ # Check CI provider
1422
+ ls -d .github/ 2>/dev/null && echo "CI:github"
1423
+ ls .gitlab-ci.yml .circleci/ bitrise.yml 2>/dev/null
1424
+ \`\`\`
1425
+
1426
+ If \`.github/\` exists (or no CI detected — default to GitHub Actions):
1427
+ Create \`.github/workflows/test.yml\` with:
1428
+ - \`runs-on: ubuntu-latest\`
1429
+ - Appropriate setup action for the runtime (setup-node, setup-ruby, setup-python, etc.)
1430
+ - The same test command verified in B5
1431
+ - Trigger: push + pull_request
1432
+
1433
+ If non-GitHub CI detected → skip CI generation with note: "Detected {provider} — CI pipeline generation supports GitHub Actions only. Add test step to your existing pipeline manually."
1434
+
1435
+ ### B6. Create TESTING.md
1436
+
1437
+ First check: If TESTING.md already exists → read it and update/append rather than overwriting. Never destroy existing content.
1438
+
1439
+ Write TESTING.md with:
1440
+ - Philosophy: "100% test coverage is the key to great vibe coding. Tests let you move fast, trust your instincts, and ship with confidence — without them, vibe coding is just yolo coding. With tests, it's a superpower."
1441
+ - Framework name and version
1442
+ - How to run tests (the verified command from B5)
1443
+ - Test layers: Unit tests (what, where, when), Integration tests, Smoke tests, E2E tests
1444
+ - Conventions: file naming, assertion style, setup/teardown patterns
1445
+
1446
+ ### B7. Update CLAUDE.md
1447
+
1448
+ First check: If CLAUDE.md already has a \`## Testing\` section → skip. Don't duplicate.
1449
+
1450
+ Append a \`## Testing\` section:
1451
+ - Run command and test directory
1452
+ - Reference to TESTING.md
1453
+ - Test expectations:
1454
+ - 100% test coverage is the goal — tests make vibe coding safe
1455
+ - When writing new functions, write a corresponding test
1456
+ - When fixing a bug, write a regression test
1457
+ - When adding error handling, write a test that triggers the error
1458
+ - When adding a conditional (if/else, switch), write tests for BOTH paths
1459
+ - Never commit code that makes existing tests fail
1460
+
1461
+ ### B8. Commit
1462
+
1463
+ \`\`\`bash
1464
+ git status --porcelain
1465
+ \`\`\`
1466
+
1467
+ Only commit if there are changes. Stage all bootstrap files (config, test directory, TESTING.md, CLAUDE.md, .github/workflows/test.yml if created):
1468
+ \`git commit -m "chore: bootstrap test framework ({framework name})"\`
1469
+
1470
+ ---`;
1471
+ }
1472
+
1473
+ // ─── Test Coverage Audit ────────────────────────────────────
1474
+ //
1475
+ // Shared methodology for codepath tracing, ASCII diagrams, and test gap analysis.
1476
+ // Three modes, three placeholders, one inner function:
1477
+ //
1478
+ // {{TEST_COVERAGE_AUDIT_PLAN}} → plan-eng-review: adds missing tests to the plan
1479
+ // {{TEST_COVERAGE_AUDIT_SHIP}} → ship: auto-generates tests, coverage summary
1480
+ // {{TEST_COVERAGE_AUDIT_REVIEW}} → review: generates tests via Fix-First (ASK)
1481
+ //
1482
+ // ┌────────────────────────────────────────────────┐
1483
+ // │ generateTestCoverageAuditInner(mode) │
1484
+ // │ │
1485
+ // │ SHARED: framework detect, codepath trace, │
1486
+ // │ ASCII diagram, quality rubric, E2E matrix, │
1487
+ // │ regression rule │
1488
+ // │ │
1489
+ // │ plan: edit plan file, write artifact │
1490
+ // │ ship: auto-generate tests, write artifact │
1491
+ // │ review: Fix-First ASK, INFORMATIONAL gaps │
1492
+ // └────────────────────────────────────────────────┘
1493
+
1494
+ type CoverageAuditMode = 'plan' | 'ship' | 'review';
1495
+
1496
+ function generateTestCoverageAuditInner(mode: CoverageAuditMode): string {
1497
+ const sections: string[] = [];
1498
+
1499
+ // ── Intro (mode-specific) ──
1500
+ if (mode === 'ship') {
1501
+ sections.push(`100% coverage is the goal — every untested path is a path where bugs hide and vibe coding becomes yolo coding. Evaluate what was ACTUALLY coded (from the diff), not what was planned.`);
1502
+ } else if (mode === 'plan') {
1503
+ sections.push(`100% coverage is the goal. Evaluate every codepath in the plan and ensure the plan includes tests for each one. If the plan is missing tests, add them — the plan should be complete enough that implementation includes full test coverage from the start.`);
1504
+ } else {
1505
+ sections.push(`100% coverage is the goal. Evaluate every codepath changed in the diff and identify test gaps. Gaps become INFORMATIONAL findings that follow the Fix-First flow.`);
1506
+ }
1507
+
1508
+ // ── Test framework detection (shared) ──
1509
+ sections.push(`
1510
+ ### Test Framework Detection
1511
+
1512
+ Before analyzing coverage, detect the project's test framework:
1513
+
1514
+ 1. **Read CLAUDE.md** — look for a \`## Testing\` section with test command and framework name. If found, use that as the authoritative source.
1515
+ 2. **If CLAUDE.md has no testing section, auto-detect:**
1516
+
1517
+ \`\`\`bash
1518
+ # Detect project runtime
1519
+ [ -f Gemfile ] && echo "RUNTIME:ruby"
1520
+ [ -f package.json ] && echo "RUNTIME:node"
1521
+ [ -f requirements.txt ] || [ -f pyproject.toml ] && echo "RUNTIME:python"
1522
+ [ -f go.mod ] && echo "RUNTIME:go"
1523
+ [ -f Cargo.toml ] && echo "RUNTIME:rust"
1524
+ # Check for existing test infrastructure
1525
+ ls jest.config.* vitest.config.* playwright.config.* cypress.config.* .rspec pytest.ini phpunit.xml 2>/dev/null
1526
+ ls -d test/ tests/ spec/ __tests__/ cypress/ e2e/ 2>/dev/null
1527
+ \`\`\`
1528
+
1529
+ 3. **If no framework detected:**${mode === 'ship' ? ' falls through to the Test Framework Bootstrap step (Step 2.5) which handles full setup.' : ' still produce the coverage diagram, but skip test generation.'}`);
1530
+
1531
+ // ── Before/after count (ship only) ──
1532
+ if (mode === 'ship') {
1533
+ sections.push(`
1534
+ **0. Before/after test count:**
1535
+
1536
+ \`\`\`bash
1537
+ # Count test files before any generation
1538
+ find . -name '*.test.*' -o -name '*.spec.*' -o -name '*_test.*' -o -name '*_spec.*' | grep -v node_modules | wc -l
1539
+ \`\`\`
1540
+
1541
+ Store this number for the PR body.`);
1542
+ }
1543
+
1544
+ // ── Codepath tracing methodology (shared, with mode-specific source) ──
1545
+ const traceSource = mode === 'plan'
1546
+ ? `**Step 1. Trace every codepath in the plan:**
1547
+
1548
+ Read the plan document. For each new feature, service, endpoint, or component described, trace how data will flow through the code — don't just list planned functions, actually follow the planned execution:`
1549
+ : `**${mode === 'ship' ? '1' : 'Step 1'}. Trace every codepath changed** using \`git diff origin/<base>...HEAD\`:
1550
+
1551
+ Read every changed file. For each one, trace how data flows through the code — don't just list functions, actually follow the execution:`;
1552
+
1553
+ const traceStep1 = mode === 'plan'
1554
+ ? `1. **Read the plan.** For each planned component, understand what it does and how it connects to existing code.`
1555
+ : `1. **Read the diff.** For each changed file, read the full file (not just the diff hunk) to understand context.`;
1556
+
1557
+ sections.push(`
1558
+ ${traceSource}
1559
+
1560
+ ${traceStep1}
1561
+ 2. **Trace data flow.** Starting from each entry point (route handler, exported function, event listener, component render), follow the data through every branch:
1562
+ - Where does input come from? (request params, props, database, API call)
1563
+ - What transforms it? (validation, mapping, computation)
1564
+ - Where does it go? (database write, API response, rendered output, side effect)
1565
+ - What can go wrong at each step? (null/undefined, invalid input, network failure, empty collection)
1566
+ 3. **Diagram the execution.** For each changed file, draw an ASCII diagram showing:
1567
+ - Every function/method that was added or modified
1568
+ - Every conditional branch (if/else, switch, ternary, guard clause, early return)
1569
+ - Every error path (try/catch, rescue, error boundary, fallback)
1570
+ - Every call to another function (trace into it — does IT have untested branches?)
1571
+ - Every edge: what happens with null input? Empty array? Invalid type?
1572
+
1573
+ This is the critical step — you're building a map of every line of code that can execute differently based on input. Every branch in this diagram needs a test.`);
1574
+
1575
+ // ── User flow coverage (shared) ──
1576
+ sections.push(`
1577
+ **${mode === 'ship' ? '2' : 'Step 2'}. Map user flows, interactions, and error states:**
1578
+
1579
+ Code coverage isn't enough — you need to cover how real users interact with the changed code. For each changed feature, think through:
1580
+
1581
+ - **User flows:** What sequence of actions does a user take that touches this code? Map the full journey (e.g., "user clicks 'Pay' → form validates → API call → success/failure screen"). Each step in the journey needs a test.
1582
+ - **Interaction edge cases:** What happens when the user does something unexpected?
1583
+ - Double-click/rapid resubmit
1584
+ - Navigate away mid-operation (back button, close tab, click another link)
1585
+ - Submit with stale data (page sat open for 30 minutes, session expired)
1586
+ - Slow connection (API takes 10 seconds — what does the user see?)
1587
+ - Concurrent actions (two tabs, same form)
1588
+ - **Error states the user can see:** For every error the code handles, what does the user actually experience?
1589
+ - Is there a clear error message or a silent failure?
1590
+ - Can the user recover (retry, go back, fix input) or are they stuck?
1591
+ - What happens with no network? With a 500 from the API? With invalid data from the server?
1592
+ - **Empty/zero/boundary states:** What does the UI show with zero results? With 10,000 results? With a single character input? With maximum-length input?
1593
+
1594
+ Add these to your diagram alongside the code branches. A user flow with no test is just as much a gap as an untested if/else.`);
1595
+
1596
+ // ── Check branches against tests + quality rubric (shared) ──
1597
+ sections.push(`
1598
+ **${mode === 'ship' ? '3' : 'Step 3'}. Check each branch against existing tests:**
1599
+
1600
+ Go through your diagram branch by branch — both code paths AND user flows. For each one, search for a test that exercises it:
1601
+ - Function \`processPayment()\` → look for \`billing.test.ts\`, \`billing.spec.ts\`, \`test/billing_test.rb\`
1602
+ - An if/else → look for tests covering BOTH the true AND false path
1603
+ - An error handler → look for a test that triggers that specific error condition
1604
+ - A call to \`helperFn()\` that has its own branches → those branches need tests too
1605
+ - A user flow → look for an integration or E2E test that walks through the journey
1606
+ - An interaction edge case → look for a test that simulates the unexpected action
1607
+
1608
+ Quality scoring rubric:
1609
+ - ★★★ Tests behavior with edge cases AND error paths
1610
+ - ★★ Tests correct behavior, happy path only
1611
+ - ★ Smoke test / existence check / trivial assertion (e.g., "it renders", "it doesn't throw")`);
1612
+
1613
+ // ── E2E test decision matrix (shared) ──
1614
+ sections.push(`
1615
+ ### E2E Test Decision Matrix
1616
+
1617
+ When checking each branch, also determine whether a unit test or E2E/integration test is the right tool:
1618
+
1619
+ **RECOMMEND E2E (mark as [→E2E] in the diagram):**
1620
+ - Common user flow spanning 3+ components/services (e.g., signup → verify email → first login)
1621
+ - Integration point where mocking hides real failures (e.g., API → queue → worker → DB)
1622
+ - Auth/payment/data-destruction flows — too important to trust unit tests alone
1623
+
1624
+ **RECOMMEND EVAL (mark as [→EVAL] in the diagram):**
1625
+ - Critical LLM call that needs a quality eval (e.g., prompt change → test output still meets quality bar)
1626
+ - Changes to prompt templates, system instructions, or tool definitions
1627
+
1628
+ **STICK WITH UNIT TESTS:**
1629
+ - Pure function with clear inputs/outputs
1630
+ - Internal helper with no side effects
1631
+ - Edge case of a single function (null input, empty array)
1632
+ - Obscure/rare flow that isn't customer-facing`);
1633
+
1634
+ // ── Regression rule (shared) ──
1635
+ sections.push(`
1636
+ ### REGRESSION RULE (mandatory)
1637
+
1638
+ **IRON RULE:** When the coverage audit identifies a REGRESSION — code that previously worked but the diff broke — a regression test is ${mode === 'plan' ? 'added to the plan as a critical requirement' : 'written immediately'}. No AskUserQuestion. No skipping. Regressions are the highest-priority test because they prove something broke.
1639
+
1640
+ A regression is when:
1641
+ - The diff modifies existing behavior (not new code)
1642
+ - The existing test suite (if any) doesn't cover the changed path
1643
+ - The change introduces a new failure mode for existing callers
1644
+
1645
+ When uncertain whether a change is a regression, err on the side of writing the test.${mode !== 'plan' ? '\n\nFormat: commit as `test: regression test for {what broke}`' : ''}`);
1646
+
1647
+ // ── ASCII coverage diagram (shared) ──
1648
+ sections.push(`
1649
+ **${mode === 'ship' ? '4' : 'Step 4'}. Output ASCII coverage diagram:**
1650
+
1651
+ Include BOTH code paths and user flows in the same diagram. Mark E2E-worthy and eval-worthy paths:
1652
+
1653
+ \`\`\`
1654
+ CODE PATH COVERAGE
1655
+ ===========================
1656
+ [+] src/services/billing.ts
1657
+
1658
+ ├── processPayment()
1659
+ │ ├── [★★★ TESTED] Happy path + card declined + timeout — billing.test.ts:42
1660
+ │ ├── [GAP] Network timeout — NO TEST
1661
+ │ └── [GAP] Invalid currency — NO TEST
1662
+
1663
+ └── refundPayment()
1664
+ ├── [★★ TESTED] Full refund — billing.test.ts:89
1665
+ └── [★ TESTED] Partial refund (checks non-throw only) — billing.test.ts:101
1666
+
1667
+ USER FLOW COVERAGE
1668
+ ===========================
1669
+ [+] Payment checkout flow
1670
+
1671
+ ├── [★★★ TESTED] Complete purchase — checkout.e2e.ts:15
1672
+ ├── [GAP] [→E2E] Double-click submit — needs E2E, not just unit
1673
+ ├── [GAP] Navigate away during payment — unit test sufficient
1674
+ └── [★ TESTED] Form validation errors (checks render only) — checkout.test.ts:40
1675
+
1676
+ [+] Error states
1677
+
1678
+ ├── [★★ TESTED] Card declined message — billing.test.ts:58
1679
+ ├── [GAP] Network timeout UX (what does user see?) — NO TEST
1680
+ └── [GAP] Empty cart submission — NO TEST
1681
+
1682
+ [+] LLM integration
1683
+
1684
+ └── [GAP] [→EVAL] Prompt template change — needs eval test
1685
+
1686
+ ─────────────────────────────────
1687
+ COVERAGE: 5/13 paths tested (38%)
1688
+ Code paths: 3/5 (60%)
1689
+ User flows: 2/8 (25%)
1690
+ QUALITY: ★★★: 2 ★★: 2 ★: 1
1691
+ GAPS: 8 paths need tests (2 need E2E, 1 needs eval)
1692
+ ─────────────────────────────────
1693
+ \`\`\`
1694
+
1695
+ **Fast path:** All paths covered → "${mode === 'ship' ? 'Step 3.4' : mode === 'review' ? 'Step 4.75' : 'Test review'}: All new code paths have test coverage ✓" Continue.`);
1696
+
1697
+ // ── Mode-specific action section ──
1698
+ if (mode === 'plan') {
1699
+ sections.push(`
1700
+ **Step 5. Add missing tests to the plan:**
1701
+
1702
+ For each GAP identified in the diagram, add a test requirement to the plan. Be specific:
1703
+ - What test file to create (match existing naming conventions)
1704
+ - What the test should assert (specific inputs → expected outputs/behavior)
1705
+ - Whether it's a unit test, E2E test, or eval (use the decision matrix)
1706
+ - For regressions: flag as **CRITICAL** and explain what broke
1707
+
1708
+ The plan should be complete enough that when implementation begins, every test is written alongside the feature code — not deferred to a follow-up.`);
1709
+
1710
+ // ── Test plan artifact (plan + ship) ──
1711
+ sections.push(`
1712
+ ### Test Plan Artifact
1713
+
1714
+ After producing the coverage diagram, write a test plan artifact to the project directory so \`/qa\` and \`/qa-only\` can consume it as primary test input:
1715
+
1716
+ \`\`\`bash
1717
+ source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) && mkdir -p ~/.gstack/projects/$SLUG
1718
+ USER=$(whoami)
1719
+ DATETIME=$(date +%Y%m%d-%H%M%S)
1720
+ \`\`\`
1721
+
1722
+ Write to \`~/.gstack/projects/{slug}/{user}-{branch}-eng-review-test-plan-{datetime}.md\`:
1723
+
1724
+ \`\`\`markdown
1725
+ # Test Plan
1726
+ Generated by /plan-eng-review on {date}
1727
+ Branch: {branch}
1728
+ Repo: {owner/repo}
1729
+
1730
+ ## Affected Pages/Routes
1731
+ - {URL path} — {what to test and why}
1732
+
1733
+ ## Key Interactions to Verify
1734
+ - {interaction description} on {page}
1735
+
1736
+ ## Edge Cases
1737
+ - {edge case} on {page}
1738
+
1739
+ ## Critical Paths
1740
+ - {end-to-end flow that must work}
1741
+ \`\`\`
1742
+
1743
+ This file is consumed by \`/qa\` and \`/qa-only\` as primary test input. Include only the information that helps a QA tester know **what to test and where** — not implementation details.`);
1744
+ } else if (mode === 'ship') {
1745
+ sections.push(`
1746
+ **5. Generate tests for uncovered paths:**
1747
+
1748
+ If test framework detected (or bootstrapped in Step 2.5):
1749
+ - Prioritize error handlers and edge cases first (happy paths are more likely already tested)
1750
+ - Read 2-3 existing test files to match conventions exactly
1751
+ - Generate unit tests. Mock all external dependencies (DB, API, Redis).
1752
+ - For paths marked [→E2E]: generate integration/E2E tests using the project's E2E framework (Playwright, Cypress, Capybara, etc.)
1753
+ - For paths marked [→EVAL]: generate eval tests using the project's eval framework, or flag for manual eval if none exists
1754
+ - Write tests that exercise the specific uncovered path with real assertions
1755
+ - Run each test. Passes → commit as \`test: coverage for {feature}\`
1756
+ - Fails → fix once. Still fails → revert, note gap in diagram.
1757
+
1758
+ Caps: 30 code paths max, 20 tests generated max (code + user flow combined), 2-min per-test exploration cap.
1759
+
1760
+ If no test framework AND user declined bootstrap → diagram only, no generation. Note: "Test generation skipped — no test framework configured."
1761
+
1762
+ **Diff is test-only changes:** Skip Step 3.4 entirely: "No new application code paths to audit."
1763
+
1764
+ **6. After-count and coverage summary:**
1765
+
1766
+ \`\`\`bash
1767
+ # Count test files after generation
1768
+ find . -name '*.test.*' -o -name '*.spec.*' -o -name '*_test.*' -o -name '*_spec.*' | grep -v node_modules | wc -l
1769
+ \`\`\`
1770
+
1771
+ For PR body: \`Tests: {before} → {after} (+{delta} new)\`
1772
+ Coverage line: \`Test Coverage Audit: N new code paths. M covered (X%). K tests generated, J committed.\``);
1773
+
1774
+ // ── Test plan artifact (ship mode) ──
1775
+ sections.push(`
1776
+ ### Test Plan Artifact
1777
+
1778
+ After producing the coverage diagram, write a test plan artifact so \`/qa\` and \`/qa-only\` can consume it:
1779
+
1780
+ \`\`\`bash
1781
+ source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) && mkdir -p ~/.gstack/projects/$SLUG
1782
+ USER=$(whoami)
1783
+ DATETIME=$(date +%Y%m%d-%H%M%S)
1784
+ \`\`\`
1785
+
1786
+ Write to \`~/.gstack/projects/{slug}/{user}-{branch}-ship-test-plan-{datetime}.md\`:
1787
+
1788
+ \`\`\`markdown
1789
+ # Test Plan
1790
+ Generated by /ship on {date}
1791
+ Branch: {branch}
1792
+ Repo: {owner/repo}
1793
+
1794
+ ## Affected Pages/Routes
1795
+ - {URL path} — {what to test and why}
1796
+
1797
+ ## Key Interactions to Verify
1798
+ - {interaction description} on {page}
1799
+
1800
+ ## Edge Cases
1801
+ - {edge case} on {page}
1802
+
1803
+ ## Critical Paths
1804
+ - {end-to-end flow that must work}
1805
+ \`\`\``);
1806
+ } else {
1807
+ // review mode
1808
+ sections.push(`
1809
+ **Step 5. Generate tests for gaps (Fix-First):**
1810
+
1811
+ If test framework is detected and gaps were identified:
1812
+ - Classify each gap as AUTO-FIX or ASK per the Fix-First Heuristic:
1813
+ - **AUTO-FIX:** Simple unit tests for pure functions, edge cases of existing tested functions
1814
+ - **ASK:** E2E tests, tests requiring new test infrastructure, tests for ambiguous behavior
1815
+ - For AUTO-FIX gaps: generate the test, run it, commit as \`test: coverage for {feature}\`
1816
+ - For ASK gaps: include in the Fix-First batch question with the other review findings
1817
+ - For paths marked [→E2E]: always ASK (E2E tests are higher-effort and need user confirmation)
1818
+ - For paths marked [→EVAL]: always ASK (eval tests need user confirmation on quality criteria)
1819
+
1820
+ If no test framework detected → include gaps as INFORMATIONAL findings only, no generation.
1821
+
1822
+ **Diff is test-only changes:** Skip Step 4.75 entirely: "No new application code paths to audit."`);
1823
+ }
1824
+
1825
+ return sections.join('\n');
1826
+ }
1827
+
1828
+ function generateTestCoverageAuditPlan(_ctx: TemplateContext): string {
1829
+ return generateTestCoverageAuditInner('plan');
1830
+ }
1831
+
1832
+ function generateTestCoverageAuditShip(_ctx: TemplateContext): string {
1833
+ return generateTestCoverageAuditInner('ship');
1834
+ }
1835
+
1836
+ function generateTestCoverageAuditReview(_ctx: TemplateContext): string {
1837
+ return generateTestCoverageAuditInner('review');
1838
+ }
1839
+
1840
+ function generateSpecReviewLoop(_ctx: TemplateContext): string {
1841
+ return `## Spec Review Loop
1842
+
1843
+ Before presenting the document to the user for approval, run an adversarial review.
1844
+
1845
+ **Step 1: Dispatch reviewer subagent**
1846
+
1847
+ Use the Agent tool to dispatch an independent reviewer. The reviewer has fresh context
1848
+ and cannot see the brainstorming conversation — only the document. This ensures genuine
1849
+ adversarial independence.
1850
+
1851
+ Prompt the subagent with:
1852
+ - The file path of the document just written
1853
+ - "Read this document and review it on 5 dimensions. For each dimension, note PASS or
1854
+ list specific issues with suggested fixes. At the end, output a quality score (1-10)
1855
+ across all dimensions."
1856
+
1857
+ **Dimensions:**
1858
+ 1. **Completeness** — Are all requirements addressed? Missing edge cases?
1859
+ 2. **Consistency** — Do parts of the document agree with each other? Contradictions?
1860
+ 3. **Clarity** — Could an engineer implement this without asking questions? Ambiguous language?
1861
+ 4. **Scope** — Does the document creep beyond the original problem? YAGNI violations?
1862
+ 5. **Feasibility** — Can this actually be built with the stated approach? Hidden complexity?
1863
+
1864
+ The subagent should return:
1865
+ - A quality score (1-10)
1866
+ - PASS if no issues, or a numbered list of issues with dimension, description, and fix
1867
+
1868
+ **Step 2: Fix and re-dispatch**
1869
+
1870
+ If the reviewer returns issues:
1871
+ 1. Fix each issue in the document on disk (use Edit tool)
1872
+ 2. Re-dispatch the reviewer subagent with the updated document
1873
+ 3. Maximum 3 iterations total
1874
+
1875
+ **Convergence guard:** If the reviewer returns the same issues on consecutive iterations
1876
+ (the fix didn't resolve them or the reviewer disagrees with the fix), stop the loop
1877
+ and persist those issues as "Reviewer Concerns" in the document rather than looping
1878
+ further.
1879
+
1880
+ If the subagent fails, times out, or is unavailable — skip the review loop entirely.
1881
+ Tell the user: "Spec review unavailable — presenting unreviewed doc." The document is
1882
+ already written to disk; the review is a quality bonus, not a gate.
1883
+
1884
+ **Step 3: Report and persist metrics**
1885
+
1886
+ After the loop completes (PASS, max iterations, or convergence guard):
1887
+
1888
+ 1. Tell the user the result — summary by default:
1889
+ "Your doc survived N rounds of adversarial review. M issues caught and fixed.
1890
+ Quality score: X/10."
1891
+ If they ask "what did the reviewer find?", show the full reviewer output.
1892
+
1893
+ 2. If issues remain after max iterations or convergence, add a "## Reviewer Concerns"
1894
+ section to the document listing each unresolved issue. Downstream skills will see this.
1895
+
1896
+ 3. Append metrics:
1897
+ \`\`\`bash
1898
+ mkdir -p ~/.gstack/analytics
1899
+ echo '{"skill":"${_ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","iterations":ITERATIONS,"issues_found":FOUND,"issues_fixed":FIXED,"remaining":REMAINING,"quality_score":SCORE}' >> ~/.gstack/analytics/spec-review.jsonl 2>/dev/null || true
1900
+ \`\`\`
1901
+ Replace ITERATIONS, FOUND, FIXED, REMAINING, SCORE with actual values from the review.`;
1902
+ }
1903
+
1904
+ function generateBenefitsFrom(ctx: TemplateContext): string {
1905
+ if (!ctx.benefitsFrom || ctx.benefitsFrom.length === 0) return '';
1906
+
1907
+ const skillList = ctx.benefitsFrom.map(s => `\`/${s}\``).join(' or ');
1908
+ const first = ctx.benefitsFrom[0];
1909
+
1910
+ return `## Prerequisite Skill Offer
1911
+
1912
+ When the design doc check above prints "No design doc found," offer the prerequisite
1913
+ skill before proceeding.
1914
+
1915
+ Say to the user via AskUserQuestion:
1916
+
1917
+ > "No design doc found for this branch. ${skillList} produces a structured problem
1918
+ > statement, premise challenge, and explored alternatives — it gives this review much
1919
+ > sharper input to work with. Takes about 10 minutes. The design doc is per-feature,
1920
+ > not per-product — it captures the thinking behind this specific change."
1921
+
1922
+ Options:
1923
+ - A) Run /${first} first (in another window, then come back)
1924
+ - B) Skip — proceed with standard review
1925
+
1926
+ If they skip: "No worries — standard review. If you ever want sharper input, try
1927
+ /${first} first next time." Then proceed normally. Do not re-offer later in the session.`;
1928
+ }
1929
+
1930
+ function generateDesignSketch(_ctx: TemplateContext): string {
1931
+ return `## Visual Sketch (UI ideas only)
1932
+
1933
+ If the chosen approach involves user-facing UI (screens, pages, forms, dashboards,
1934
+ or interactive elements), generate a rough wireframe to help the user visualize it.
1935
+ If the idea is backend-only, infrastructure, or has no UI component — skip this
1936
+ section silently.
1937
+
1938
+ **Step 1: Gather design context**
1939
+
1940
+ 1. Check if \`DESIGN.md\` exists in the repo root. If it does, read it for design
1941
+ system constraints (colors, typography, spacing, component patterns). Use these
1942
+ constraints in the wireframe.
1943
+ 2. Apply core design principles:
1944
+ - **Information hierarchy** — what does the user see first, second, third?
1945
+ - **Interaction states** — loading, empty, error, success, partial
1946
+ - **Edge case paranoia** — what if the name is 47 chars? Zero results? Network fails?
1947
+ - **Subtraction default** — "as little design as possible" (Rams). Every element earns its pixels.
1948
+ - **Design for trust** — every interface element builds or erodes user trust.
1949
+
1950
+ **Step 2: Generate wireframe HTML**
1951
+
1952
+ Generate a single-page HTML file with these constraints:
1953
+ - **Intentionally rough aesthetic** — use system fonts, thin gray borders, no color,
1954
+ hand-drawn-style elements. This is a sketch, not a polished mockup.
1955
+ - Self-contained — no external dependencies, no CDN links, inline CSS only
1956
+ - Show the core interaction flow (1-3 screens/states max)
1957
+ - Include realistic placeholder content (not "Lorem ipsum" — use content that
1958
+ matches the actual use case)
1959
+ - Add HTML comments explaining design decisions
1960
+
1961
+ Write to a temp file:
1962
+ \`\`\`bash
1963
+ SKETCH_FILE="/tmp/gstack-sketch-$(date +%s).html"
1964
+ \`\`\`
1965
+
1966
+ **Step 3: Render and capture**
1967
+
1968
+ \`\`\`bash
1969
+ $B goto "file://$SKETCH_FILE"
1970
+ $B screenshot /tmp/gstack-sketch.png
1971
+ \`\`\`
1972
+
1973
+ If \`$B\` is not available (browse binary not set up), skip the render step. Tell the
1974
+ user: "Visual sketch requires the browse binary. Run the setup script to enable it."
1975
+
1976
+ **Step 4: Present and iterate**
1977
+
1978
+ Show the screenshot to the user. Ask: "Does this feel right? Want to iterate on the layout?"
1979
+
1980
+ If they want changes, regenerate the HTML with their feedback and re-render.
1981
+ If they approve or say "good enough," proceed.
1982
+
1983
+ **Step 5: Include in design doc**
1984
+
1985
+ Reference the wireframe screenshot in the design doc's "Recommended Approach" section.
1986
+ The screenshot file at \`/tmp/gstack-sketch.png\` can be referenced by downstream skills
1987
+ (\`/plan-design-review\`, \`/design-review\`) to see what was originally envisioned.`;
1988
+ }
1989
+
1990
+ function generateAdversarialStep(ctx: TemplateContext): string {
1991
+ // Codex host: strip entirely — Codex should never invoke itself
1992
+ if (ctx.host === 'codex') return '';
1993
+
1994
+ const isShip = ctx.skillName === 'ship';
1995
+ const stepNum = isShip ? '3.8' : '5.7';
1996
+
1997
+ return `## Step ${stepNum}: Adversarial review (auto-scaled)
1998
+
1999
+ Adversarial review thoroughness scales automatically based on diff size. No configuration needed.
2000
+
2001
+ **Detect diff size and tool availability:**
2002
+
2003
+ \`\`\`bash
2004
+ DIFF_INS=$(git diff origin/<base> --stat | tail -1 | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
2005
+ DIFF_DEL=$(git diff origin/<base> --stat | tail -1 | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo "0")
2006
+ DIFF_TOTAL=$((DIFF_INS + DIFF_DEL))
2007
+ which codex 2>/dev/null && echo "CODEX_AVAILABLE" || echo "CODEX_NOT_AVAILABLE"
2008
+ # Respect old opt-out
2009
+ OLD_CFG=$(~/.claude/skills/gstack/bin/gstack-config get codex_reviews 2>/dev/null || true)
2010
+ echo "DIFF_SIZE: $DIFF_TOTAL"
2011
+ echo "OLD_CFG: \${OLD_CFG:-not_set}"
2012
+ \`\`\`
2013
+
2014
+ If \`OLD_CFG\` is \`disabled\`: skip this step silently. Continue to the next step.
2015
+
2016
+ **User override:** If the user explicitly requested a specific tier (e.g., "run all passes", "paranoid review", "full adversarial", "do all 4 passes", "thorough review"), honor that request regardless of diff size. Jump to the matching tier section.
2017
+
2018
+ **Auto-select tier based on diff size:**
2019
+ - **Small (< 50 lines changed):** Skip adversarial review entirely. Print: "Small diff ($DIFF_TOTAL lines) — adversarial review skipped." Continue to the next step.
2020
+ - **Medium (50–199 lines changed):** Run Codex adversarial challenge (or Claude adversarial subagent if Codex unavailable). Jump to the "Medium tier" section.
2021
+ - **Large (200+ lines changed):** Run all remaining passes — Codex structured review + Claude adversarial subagent + Codex adversarial. Jump to the "Large tier" section.
2022
+
2023
+ ---
2024
+
2025
+ ### Medium tier (50–199 lines)
2026
+
2027
+ Claude's structured review already ran. Now add a **cross-model adversarial challenge**.
2028
+
2029
+ **If Codex is available:** run the Codex adversarial challenge. **If Codex is NOT available:** fall back to the Claude adversarial subagent instead.
2030
+
2031
+ **Codex adversarial:**
2032
+
2033
+ \`\`\`bash
2034
+ TMPERR_ADV=$(mktemp /tmp/codex-adv-XXXXXXXX)
2035
+ codex exec "Review the changes on this branch against the base branch. Run git diff origin/<base> to see the diff. Your job is to find ways this code will fail in production. Think like an attacker and a chaos engineer. Find edge cases, race conditions, security holes, resource leaks, failure modes, and silent data corruption paths. Be adversarial. Be thorough. No compliments — just the problems." -s read-only -c 'model_reasoning_effort="xhigh"' --enable web_search_cached 2>"$TMPERR_ADV"
2036
+ \`\`\`
2037
+
2038
+ Use a 5-minute timeout (\`timeout: 300000\`). After the command completes, read stderr:
2039
+ \`\`\`bash
2040
+ cat "$TMPERR_ADV"
2041
+ \`\`\`
2042
+
2043
+ Present the full output verbatim. This is informational — it never blocks shipping.
2044
+
2045
+ **Error handling:** All errors are non-blocking — adversarial review is a quality enhancement, not a prerequisite.
2046
+ - **Auth failure:** If stderr contains "auth", "login", "unauthorized", or "API key": "Codex authentication failed. Run \\\`codex login\\\` to authenticate."
2047
+ - **Timeout:** "Codex timed out after 5 minutes."
2048
+ - **Empty response:** "Codex returned no response. Stderr: <paste relevant error>."
2049
+
2050
+ On any Codex error, fall back to the Claude adversarial subagent automatically.
2051
+
2052
+ **Claude adversarial subagent** (fallback when Codex unavailable or errored):
2053
+
2054
+ Dispatch via the Agent tool. The subagent has fresh context — no checklist bias from the structured review. This genuine independence catches things the primary reviewer is blind to.
2055
+
2056
+ Subagent prompt:
2057
+ "Read the diff for this branch with \`git diff origin/<base>\`. Think like an attacker and a chaos engineer. Your job is to find ways this code will fail in production. Look for: edge cases, race conditions, security holes, resource leaks, failure modes, silent data corruption, logic errors that produce wrong results silently, error handling that swallows failures, and trust boundary violations. Be adversarial. Be thorough. No compliments — just the problems. For each finding, classify as FIXABLE (you know how to fix it) or INVESTIGATE (needs human judgment)."
2058
+
2059
+ Present findings under an \`ADVERSARIAL REVIEW (Claude subagent):\` header. **FIXABLE findings** flow into the same Fix-First pipeline as the structured review. **INVESTIGATE findings** are presented as informational.
2060
+
2061
+ If the subagent fails or times out: "Claude adversarial subagent unavailable. Continuing without adversarial review."
2062
+
2063
+ **Persist the review result:**
2064
+ \`\`\`bash
2065
+ ~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"adversarial-review","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","status":"STATUS","source":"SOURCE","tier":"medium","commit":"'"$(git rev-parse --short HEAD)"'"}'
2066
+ \`\`\`
2067
+ Substitute STATUS: "clean" if no findings, "issues_found" if findings exist. SOURCE: "codex" if Codex ran, "claude" if subagent ran. If both failed, do NOT persist.
2068
+
2069
+ **Cleanup:** Run \`rm -f "$TMPERR_ADV"\` after processing (if Codex was used).
2070
+
2071
+ ---
2072
+
2073
+ ### Large tier (200+ lines)
2074
+
2075
+ Claude's structured review already ran. Now run **all three remaining passes** for maximum coverage:
2076
+
2077
+ **1. Codex structured review (if available):**
2078
+ \`\`\`bash
2079
+ TMPERR=$(mktemp /tmp/codex-review-XXXXXXXX)
2080
+ codex review --base <base> -c 'model_reasoning_effort="xhigh"' --enable web_search_cached 2>"$TMPERR"
2081
+ \`\`\`
2082
+
2083
+ Use a 5-minute timeout. Present output under \`CODEX SAYS (code review):\` header.
2084
+ Check for \`[P1]\` markers: found → \`GATE: FAIL\`, not found → \`GATE: PASS\`.
2085
+
2086
+ If GATE is FAIL, use AskUserQuestion:
2087
+ \`\`\`
2088
+ Codex found N critical issues in the diff.
2089
+
2090
+ A) Investigate and fix now (recommended)
2091
+ B) Continue — review will still complete
2092
+ \`\`\`
2093
+
2094
+ If A: address the findings${isShip ? '. After fixing, re-run tests (Step 3) since code has changed' : ''}. Re-run \`codex review\` to verify.
2095
+
2096
+ Read stderr for errors (same error handling as medium tier).
2097
+
2098
+ After stderr: \`rm -f "$TMPERR"\`
2099
+
2100
+ **2. Claude adversarial subagent:** Dispatch a subagent with the adversarial prompt (same prompt as medium tier). This always runs regardless of Codex availability.
2101
+
2102
+ **3. Codex adversarial challenge (if available):** Run \`codex exec\` with the adversarial prompt (same as medium tier).
2103
+
2104
+ If Codex is not available for steps 1 and 3, note to the user: "Codex CLI not found — large-diff review ran Claude structured + Claude adversarial (2 of 4 passes). Install Codex for full 4-pass coverage: \`npm install -g @openai/codex\`"
2105
+
2106
+ **Persist the review result AFTER all passes complete** (not after each sub-step):
2107
+ \`\`\`bash
2108
+ ~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"adversarial-review","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","status":"STATUS","source":"SOURCE","tier":"large","gate":"GATE","commit":"'"$(git rev-parse --short HEAD)"'"}'
2109
+ \`\`\`
2110
+ Substitute: STATUS = "clean" if no findings across ALL passes, "issues_found" if any pass found issues. SOURCE = "both" if Codex ran, "claude" if only Claude subagent ran. GATE = the Codex structured review gate result ("pass"/"fail"), or "informational" if Codex was unavailable. If all passes failed, do NOT persist.
2111
+
2112
+ ---
2113
+
2114
+ ### Cross-model synthesis (medium and large tiers)
2115
+
2116
+ After all passes complete, synthesize findings across all sources:
2117
+
2118
+ \`\`\`
2119
+ ADVERSARIAL REVIEW SYNTHESIS (auto: TIER, N lines):
2120
+ ════════════════════════════════════════════════════════════
2121
+ High confidence (found by multiple sources): [findings agreed on by >1 pass]
2122
+ Unique to Claude structured review: [from earlier step]
2123
+ Unique to Claude adversarial: [from subagent, if ran]
2124
+ Unique to Codex: [from codex adversarial or code review, if ran]
2125
+ Models used: Claude structured ✓ Claude adversarial ✓/✗ Codex ✓/✗
2126
+ ════════════════════════════════════════════════════════════
2127
+ \`\`\`
2128
+
2129
+ High-confidence findings (agreed on by multiple sources) should be prioritized for fixes.
2130
+
2131
+ ---`;
2132
+ }
2133
+
2134
+ function generateDeployBootstrap(_ctx: TemplateContext): string {
2135
+ return `\`\`\`bash
2136
+ # Check for persisted deploy config in CLAUDE.md
2137
+ DEPLOY_CONFIG=$(grep -A 20 "## Deploy Configuration" CLAUDE.md 2>/dev/null || echo "NO_CONFIG")
2138
+ echo "$DEPLOY_CONFIG"
2139
+
2140
+ # If config exists, parse it
2141
+ if [ "$DEPLOY_CONFIG" != "NO_CONFIG" ]; then
2142
+ PROD_URL=$(echo "$DEPLOY_CONFIG" | grep -i "production.*url" | head -1 | sed 's/.*: *//')
2143
+ PLATFORM=$(echo "$DEPLOY_CONFIG" | grep -i "platform" | head -1 | sed 's/.*: *//')
2144
+ echo "PERSISTED_PLATFORM:$PLATFORM"
2145
+ echo "PERSISTED_URL:$PROD_URL"
2146
+ fi
2147
+
2148
+ # Auto-detect platform from config files
2149
+ [ -f fly.toml ] && echo "PLATFORM:fly"
2150
+ [ -f render.yaml ] && echo "PLATFORM:render"
2151
+ ([ -f vercel.json ] || [ -d .vercel ]) && echo "PLATFORM:vercel"
2152
+ [ -f netlify.toml ] && echo "PLATFORM:netlify"
2153
+ [ -f Procfile ] && echo "PLATFORM:heroku"
2154
+ ([ -f railway.json ] || [ -f railway.toml ]) && echo "PLATFORM:railway"
2155
+
2156
+ # Detect deploy workflows
2157
+ for f in .github/workflows/*.yml .github/workflows/*.yaml; do
2158
+ [ -f "$f" ] && grep -qiE "deploy|release|production|staging|cd" "$f" 2>/dev/null && echo "DEPLOY_WORKFLOW:$f"
2159
+ done
2160
+ \`\`\`
2161
+
2162
+ If \`PERSISTED_PLATFORM\` and \`PERSISTED_URL\` were found in CLAUDE.md, use them directly
2163
+ and skip manual detection. If no persisted config exists, use the auto-detected platform
2164
+ to guide deploy verification. If nothing is detected, ask the user via AskUserQuestion
2165
+ in the decision tree below.
2166
+
2167
+ If you want to persist deploy settings for future runs, suggest the user run \`/setup-deploy\`.`;
2168
+ }
2169
+
2170
+ const RESOLVERS: Record<string, (ctx: TemplateContext) => string> = {
2171
+ COMMAND_REFERENCE: generateCommandReference,
2172
+ SNAPSHOT_FLAGS: generateSnapshotFlags,
2173
+ PREAMBLE: generatePreamble,
2174
+ BROWSE_SETUP: generateBrowseSetup,
2175
+ BASE_BRANCH_DETECT: generateBaseBranchDetect,
2176
+ QA_METHODOLOGY: generateQAMethodology,
2177
+ DESIGN_METHODOLOGY: generateDesignMethodology,
2178
+ DESIGN_REVIEW_LITE: generateDesignReviewLite,
2179
+ REVIEW_DASHBOARD: generateReviewDashboard,
2180
+ PLAN_FILE_REVIEW_REPORT: generatePlanFileReviewReport,
2181
+ TEST_BOOTSTRAP: generateTestBootstrap,
2182
+ TEST_COVERAGE_AUDIT_PLAN: generateTestCoverageAuditPlan,
2183
+ TEST_COVERAGE_AUDIT_SHIP: generateTestCoverageAuditShip,
2184
+ TEST_COVERAGE_AUDIT_REVIEW: generateTestCoverageAuditReview,
2185
+ TEST_FAILURE_TRIAGE: generateTestFailureTriage,
2186
+ SPEC_REVIEW_LOOP: generateSpecReviewLoop,
2187
+ DESIGN_SKETCH: generateDesignSketch,
2188
+ BENEFITS_FROM: generateBenefitsFrom,
2189
+ CODEX_REVIEW_STEP: generateAdversarialStep,
2190
+ ADVERSARIAL_STEP: generateAdversarialStep,
2191
+ DEPLOY_BOOTSTRAP: generateDeployBootstrap,
2192
+ };
2193
+
2194
+ // ─── Codex Helpers ───────────────────────────────────────────
2195
+
2196
+ function codexSkillName(skillDir: string): string {
2197
+ if (skillDir === '.' || skillDir === '') return 'gstack';
2198
+ // Don't double-prefix: gstack-upgrade → gstack-upgrade (not gstack-gstack-upgrade)
2199
+ if (skillDir.startsWith('gstack-')) return skillDir;
2200
+ return `gstack-${skillDir}`;
2201
+ }
2202
+
2203
+ /**
2204
+ * Transform frontmatter for Codex: keep only name + description.
2205
+ * Strips allowed-tools, hooks, version, and all other fields.
2206
+ * Handles multiline block scalar descriptions (YAML | syntax).
2207
+ */
2208
+ function transformFrontmatter(content: string, host: Host): string {
2209
+ if (host === 'claude') return content;
2210
+
2211
+ // Find frontmatter boundaries
2212
+ const fmStart = content.indexOf('---\n');
2213
+ if (fmStart !== 0) return content; // frontmatter must be at the start
2214
+ const fmEnd = content.indexOf('\n---', fmStart + 4);
2215
+ if (fmEnd === -1) return content;
2216
+
2217
+ const frontmatter = content.slice(fmStart + 4, fmEnd);
2218
+ const body = content.slice(fmEnd + 4); // includes the leading \n after ---
2219
+
2220
+ // Parse name
2221
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
2222
+ const name = nameMatch ? nameMatch[1].trim() : '';
2223
+
2224
+ // Parse description — handle both simple and block scalar (|) formats
2225
+ let description = '';
2226
+ const lines = frontmatter.split('\n');
2227
+ let inDescription = false;
2228
+ const descLines: string[] = [];
2229
+ for (const line of lines) {
2230
+ if (line.match(/^description:\s*\|?\s*$/)) {
2231
+ // Block scalar start: "description: |" or "description:"
2232
+ inDescription = true;
2233
+ continue;
2234
+ }
2235
+ if (line.match(/^description:\s*\S/)) {
2236
+ // Simple inline: "description: some text"
2237
+ description = line.replace(/^description:\s*/, '').trim();
2238
+ break;
2239
+ }
2240
+ if (inDescription) {
2241
+ // Block scalar continuation — indented lines (2 spaces) or blank lines
2242
+ if (line === '' || line.match(/^\s/)) {
2243
+ descLines.push(line.replace(/^ /, ''));
2244
+ } else {
2245
+ // End of block scalar — hit a non-indented, non-blank line
2246
+ break;
2247
+ }
2248
+ }
2249
+ }
2250
+ if (descLines.length > 0) {
2251
+ description = descLines.join('\n').trim();
2252
+ }
2253
+
2254
+ // Re-emit Codex frontmatter (name + description only)
2255
+ const indentedDesc = description.split('\n').map(l => ` ${l}`).join('\n');
2256
+ const codexFm = `---\nname: ${name}\ndescription: |\n${indentedDesc}\n---`;
2257
+ return codexFm + body;
2258
+ }
2259
+
2260
+ /**
2261
+ * Extract hook descriptions from frontmatter for inline safety prose.
2262
+ * Returns a description of what the hooks do, or null if no hooks.
2263
+ */
2264
+ function extractHookSafetyProse(tmplContent: string): string | null {
2265
+ if (!tmplContent.match(/^hooks:/m)) return null;
2266
+
2267
+ // Parse the hook matchers to build a human-readable safety description
2268
+ const matchers: string[] = [];
2269
+ const matcherRegex = /matcher:\s*"(\w+)"/g;
2270
+ let m;
2271
+ while ((m = matcherRegex.exec(tmplContent)) !== null) {
2272
+ if (!matchers.includes(m[1])) matchers.push(m[1]);
2273
+ }
2274
+
2275
+ if (matchers.length === 0) return null;
2276
+
2277
+ // Build safety prose based on what tools are hooked
2278
+ const toolDescriptions: Record<string, string> = {
2279
+ Bash: 'check bash commands for destructive operations (rm -rf, DROP TABLE, force-push, git reset --hard, etc.) before execution',
2280
+ Edit: 'verify file edits are within the allowed scope boundary before applying',
2281
+ Write: 'verify file writes are within the allowed scope boundary before applying',
2282
+ };
2283
+
2284
+ const safetyChecks = matchers
2285
+ .map(t => toolDescriptions[t] || `check ${t} operations for safety`)
2286
+ .join(', and ');
2287
+
2288
+ return `> **Safety Advisory:** This skill includes safety checks that ${safetyChecks}. When using this skill, always pause and verify before executing potentially destructive operations. If uncertain about a command's safety, ask the user for confirmation before proceeding.`;
2289
+ }
2290
+
2291
+ // ─── Template Processing ────────────────────────────────────
2292
+
2293
+ const GENERATED_HEADER = `<!-- AUTO-GENERATED from {{SOURCE}} — do not edit directly -->\n<!-- Regenerate: bun run gen:skill-docs -->\n`;
2294
+
2295
+ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: string; content: string } {
2296
+ const tmplContent = fs.readFileSync(tmplPath, 'utf-8');
2297
+ const relTmplPath = path.relative(ROOT, tmplPath);
2298
+ let outputPath = tmplPath.replace(/\.tmpl$/, '');
2299
+
2300
+ // Determine skill directory relative to ROOT
2301
+ const skillDir = path.relative(ROOT, path.dirname(tmplPath));
2302
+
2303
+ // For codex host, route output to .agents/skills/{codexSkillName}/SKILL.md
2304
+ if (host === 'codex') {
2305
+ const codexName = codexSkillName(skillDir === '.' ? '' : skillDir);
2306
+ const outputDir = path.join(ROOT, '.agents', 'skills', codexName);
2307
+ fs.mkdirSync(outputDir, { recursive: true });
2308
+ outputPath = path.join(outputDir, 'SKILL.md');
2309
+ }
2310
+
2311
+ // Extract skill name from frontmatter for TemplateContext
2312
+ const nameMatch = tmplContent.match(/^name:\s*(.+)$/m);
2313
+ const skillName = nameMatch ? nameMatch[1].trim() : path.basename(path.dirname(tmplPath));
2314
+
2315
+ // Extract benefits-from list from frontmatter (inline YAML: benefits-from: [a, b])
2316
+ const benefitsMatch = tmplContent.match(/^benefits-from:\s*\[([^\]]*)\]/m);
2317
+ const benefitsFrom = benefitsMatch
2318
+ ? benefitsMatch[1].split(',').map(s => s.trim()).filter(Boolean)
2319
+ : undefined;
2320
+
2321
+ const ctx: TemplateContext = { skillName, tmplPath, benefitsFrom, host, paths: HOST_PATHS[host] };
2322
+
2323
+ // Replace placeholders
2324
+ let content = tmplContent.replace(/\{\{(\w+)\}\}/g, (match, name) => {
2325
+ const resolver = RESOLVERS[name];
2326
+ if (!resolver) throw new Error(`Unknown placeholder {{${name}}} in ${relTmplPath}`);
2327
+ return resolver(ctx);
2328
+ });
2329
+
2330
+ // Check for any remaining unresolved placeholders
2331
+ const remaining = content.match(/\{\{(\w+)\}\}/g);
2332
+ if (remaining) {
2333
+ throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`);
2334
+ }
2335
+
2336
+ // For codex host: transform frontmatter and replace Claude-specific paths
2337
+ if (host === 'codex') {
2338
+ // Extract hook safety prose BEFORE transforming frontmatter (which strips hooks)
2339
+ const safetyProse = extractHookSafetyProse(tmplContent);
2340
+
2341
+ // Transform frontmatter: keep only name + description
2342
+ content = transformFrontmatter(content, host);
2343
+
2344
+ // Insert safety advisory at the top of the body (after frontmatter)
2345
+ if (safetyProse) {
2346
+ const bodyStart = content.indexOf('\n---') + 4;
2347
+ content = content.slice(0, bodyStart) + '\n' + safetyProse + '\n' + content.slice(bodyStart);
2348
+ }
2349
+
2350
+ // Replace remaining hardcoded Claude paths with host-appropriate paths
2351
+ content = content.replace(/~\/\.claude\/skills\/gstack/g, ctx.paths.skillRoot);
2352
+ content = content.replace(/\.claude\/skills\/gstack/g, ctx.paths.localSkillRoot);
2353
+ content = content.replace(/\.claude\/skills\/review/g, '.agents/skills/gstack/review');
2354
+ content = content.replace(/\.claude\/skills/g, '.agents/skills');
2355
+ }
2356
+
2357
+ // Prepend generated header (after frontmatter)
2358
+ const header = GENERATED_HEADER.replace('{{SOURCE}}', path.basename(tmplPath));
2359
+ const fmEnd = content.indexOf('---', content.indexOf('---') + 3);
2360
+ if (fmEnd !== -1) {
2361
+ const insertAt = content.indexOf('\n', fmEnd) + 1;
2362
+ content = content.slice(0, insertAt) + header + content.slice(insertAt);
2363
+ } else {
2364
+ content = header + content;
2365
+ }
2366
+
2367
+ return { outputPath, content };
2368
+ }
2369
+
2370
+ // ─── Main ───────────────────────────────────────────────────
2371
+
2372
+ function findTemplates(): string[] {
2373
+ const templates: string[] = [];
2374
+ const rootTmpl = path.join(ROOT, 'SKILL.md.tmpl');
2375
+ if (fs.existsSync(rootTmpl)) templates.push(rootTmpl);
2376
+
2377
+ for (const entry of fs.readdirSync(ROOT, { withFileTypes: true })) {
2378
+ if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === 'node_modules') continue;
2379
+ const tmpl = path.join(ROOT, entry.name, 'SKILL.md.tmpl');
2380
+ if (fs.existsSync(tmpl)) templates.push(tmpl);
2381
+ }
2382
+ return templates;
2383
+ }
2384
+
2385
+ let hasChanges = false;
2386
+
2387
+ for (const tmplPath of findTemplates()) {
2388
+ // Skip /codex skill for codex host (self-referential — it's a Claude wrapper around codex exec)
2389
+ if (HOST === 'codex') {
2390
+ const dir = path.basename(path.dirname(tmplPath));
2391
+ if (dir === 'codex') continue;
2392
+ }
2393
+
2394
+ const { outputPath, content } = processTemplate(tmplPath, HOST);
2395
+ const relOutput = path.relative(ROOT, outputPath);
2396
+
2397
+ if (DRY_RUN) {
2398
+ const existing = fs.existsSync(outputPath) ? fs.readFileSync(outputPath, 'utf-8') : '';
2399
+ if (existing !== content) {
2400
+ console.log(`STALE: ${relOutput}`);
2401
+ hasChanges = true;
2402
+ } else {
2403
+ console.log(`FRESH: ${relOutput}`);
2404
+ }
2405
+ } else {
2406
+ fs.writeFileSync(outputPath, content);
2407
+ console.log(`GENERATED: ${relOutput}`);
2408
+ }
2409
+ }
2410
+
2411
+ if (DRY_RUN && hasChanges) {
2412
+ console.error('\nGenerated SKILL.md files are stale. Run: bun run gen:skill-docs');
2413
+ process.exit(1);
2414
+ }