agent-templates 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/ADOPTING.md +57 -57
  2. package/CLAUDE.md +128 -125
  3. package/LICENSE +21 -21
  4. package/README.md +32 -32
  5. package/package.json +32 -32
  6. package/patterns/three-agent-architect-builder-reviewer/README.md +164 -153
  7. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/architect.md +31 -31
  8. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/builder.md +25 -25
  9. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/reviewer.md +33 -33
  10. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/agents/triage.md +31 -31
  11. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/breakdown-prd.md +18 -18
  12. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/nightly-issues.md +14 -14
  13. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/review-ticket.md +14 -14
  14. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-all.md +17 -0
  15. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-milestone.md +15 -15
  16. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/verify-delivery.md +20 -20
  17. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/hooks/guard-main-session-writes.mjs +53 -53
  18. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/milestone-dag.mjs +94 -0
  19. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/publish-tickets.mjs +263 -263
  20. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/settings.json +15 -15
  21. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/nightly-issues.js +141 -139
  22. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/run-milestone.js +225 -223
  23. package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/start-all.js +120 -0
  24. package/patterns/three-agent-architect-builder-reviewer/scaffold/INSTALL.md +65 -65
  25. package/patterns/three-agent-architect-builder-reviewer/scaffold/claude-md-snippet.md +38 -37
  26. package/scripts/adopt.mjs +207 -177
  27. package/scripts/build-site.mjs +366 -264
  28. package/scripts/cli.mjs +52 -52
  29. package/templates/pattern-README.template.md +65 -65
  30. package/templates/ticket.template.md +104 -104
  31. package/templates/tracker/github/ISSUE_TEMPLATE/bug-report.md +28 -28
  32. package/templates/tracker/github/ISSUE_TEMPLATE/decision-record.md +25 -25
  33. package/templates/tracker/github/ISSUE_TEMPLATE/task.md +36 -36
  34. package/templates/tracker/github/PULL_REQUEST_TEMPLATE.md +54 -54
  35. package/templates/tracker/gitlab/issue_templates/bug-report.md +23 -23
  36. package/templates/tracker/gitlab/issue_templates/decision-record.md +20 -20
  37. package/templates/tracker/gitlab/issue_templates/task.md +31 -31
  38. package/templates/tracker/gitlab/merge_request_templates/default.md +55 -55
@@ -1,264 +1,366 @@
1
- #!/usr/bin/env node
2
- // build-site.mjs — generates the catalog's GitHub Pages site from the catalog's own
3
- // data (patterns/*/README.md + package.json). Never hand-edit the output: the page
4
- // must not be able to drift from the pattern metadata.
5
- //
6
- // Usage: node scripts/build-site.mjs [--out <dir>] (default: site/)
7
- // Output: <out>/index.html (self-contained) + <out>/.nojekyll
8
- //
9
- // Style: claymorphism per catalog issue #17cream background, pastel clay palette,
10
- // puffy cards, soft extruded shadows, rounded chunky type, emoji as clay icons.
11
-
12
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
13
- import { join } from 'node:path'
14
- import { fileURLToPath } from 'node:url'
15
-
16
- const ROOT = fileURLToPath(new URL('..', import.meta.url))
17
- const argv = process.argv.slice(2)
18
- const outIx = argv.indexOf('--out')
19
- const OUT = outIx !== -1 && argv[outIx + 1] ? argv[outIx + 1] : join(ROOT, 'site')
20
-
21
- const GITHUB = 'https://github.com/Ruihang2017/agent-templates'
22
- const NPM = 'https://www.npmjs.com/package/agent-templates'
23
- const QUICKSTART = 'npx agent-templates@latest adopt three-agent-architect-builder-reviewer .'
24
-
25
- const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
26
- const strip = (s) => String(s).replace(/\*\*/g, '').replace(/`/g, '').trim()
27
-
28
- const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
29
-
30
- function parsePattern(dir) {
31
- const path = join(ROOT, 'patterns', dir, 'README.md')
32
- if (!existsSync(path)) return null
33
- const md = readFileSync(path, 'utf8')
34
- const pick = (re) => (md.match(re) || [])[1] || ''
35
-
36
- const title = strip(pick(/^# Pattern: (.+)$/m)) || dir
37
- const statusRaw = strip(pick(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)\|/))
38
- const status = (statusRaw.match(/[a-z]+/) || ['proposed'])[0]
39
- const asOf = strip(pick(/\|\s*\*\*As-of date\*\*\s*\|\s*([^|]+)\|/))
40
-
41
- // the one-line topology summary: first plain paragraph after the metadata table
42
- let summary = ''
43
- const lines = md.split('\n')
44
- for (let i = 0; i < lines.length; i++) {
45
- const l = lines[i].trim()
46
- if (!l || l.startsWith('#') || l.startsWith('|') || l.startsWith('<!--')) continue
47
- summary = strip(l)
48
- break
49
- }
50
-
51
- // model/effort rows from §3
52
- const roles = []
53
- const sec3 = md.split(/^## 3\. Model \+ effort[^\n]*$/m)[1]
54
- if (sec3) {
55
- for (const row of sec3.split(/^## /m)[0].split('\n')) {
56
- const m = row.match(/^\|\s*([A-Z][^|]*?)\s*\|\s*([^|]+?)\s*\|\s*`([^`]+)`\s*\|/)
57
- if (m && !/^Role/.test(m[1])) roles.push({ role: strip(m[1]), model: strip(m[2]), effort: m[3] })
58
- }
59
- }
60
-
61
- // first three "Use when" bullets from §1
62
- const useWhen = []
63
- const sec1 = md.split(/\*\*Use when:\*\*/)[1]
64
- if (sec1) {
65
- for (const l of sec1.split(/\*\*Do not use when:\*\*/)[0].split('\n')) {
66
- const m = l.match(/^- (.+)$/)
67
- if (m && useWhen.length < 3) useWhen.push(strip(m[1]))
68
- }
69
- }
70
-
71
- return { dir, title, status, asOf, summary, roles, useWhen }
72
- }
73
-
74
- const patterns = readdirSync(join(ROOT, 'patterns'), { withFileTypes: true })
75
- .filter((d) => d.isDirectory() && existsSync(join(ROOT, 'patterns', d.name, 'scaffold')))
76
- .map((d) => parsePattern(d.name))
77
- .filter(Boolean)
78
-
79
- const STATUS_STYLE = {
80
- proposed: ['var(--blue)', 'var(--blue-d)', '🧪'],
81
- trialed: ['var(--butter)', 'var(--butter-d)', '🌱'],
82
- adopted: ['var(--sage)', 'var(--sage-d)', ''],
83
- deprecated: ['var(--pink)', 'var(--pink-d)', '🗄️'],
84
- }
85
- const ROLE_EMOJI = { Architect: '📐', Builder: '🔨', Reviewer: '🔍' }
86
- const roleEmoji = (r) => ROLE_EMOJI[Object.keys(ROLE_EMOJI).find((k) => r.startsWith(k))] || '🌙'
87
-
88
- const patternCards = patterns
89
- .map((p) => {
90
- const [bg, fg, ico] = STATUS_STYLE[p.status] || STATUS_STYLE.proposed
91
- return `
92
- <article class="card pattern">
93
- <div class="pattern-head">
94
- <h3>${esc(p.title)}</h3>
95
- <span class="pill status" style="background:${bg};color:${fg}">${ico} ${esc(p.status)} · as of ${esc(p.asOf)}</span>
96
- </div>
97
- <p class="summary">${esc(p.summary)}</p>
98
- <div class="roles">
99
- ${p.roles.map((r) => `<span class="pill role">${roleEmoji(r.role)} <b>${esc(r.role)}</b>&nbsp;· ${esc(r.model)} <code>@${esc(r.effort)}</code></span>`).join('\n ')}
100
- </div>
101
- ${p.useWhen.length ? `<ul class="usewhen">${p.useWhen.map((u) => `<li>${esc(u)}</li>`).join('')}</ul>` : ''}
102
- <div class="links">
103
- <a class="btn small sage" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}">📖 Pattern write-up</a>
104
- <a class="btn small lav" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}/scaffold">🧰 Scaffold</a>
105
- </div>
106
- </article>`
107
- })
108
- .join('\n')
109
-
110
- const STEPS = [
111
- ['📦', 'Adopt', `<code>${esc(QUICKSTART)}</code> — scaffold, templates, docs skeleton, CLAUDE.md, in one idempotent command.`],
112
- ['🗺️', 'Break down', '<code>/breakdown-prd</code> the Architect turns your PRD into sub-PRDs and cold-startable tickets, then stops.'],
113
- ['🚦', 'Gate 1 you decide', 'Review the breakdown, then <code>/start-milestone</code>: tickets become tracker issues and the pipeline starts.'],
114
- ['🤖', 'Autonomous middle', 'Plan build fresh-context review (bounce-capped in code) → merge on CLEAR → issue closed → delivery verified.'],
115
- ['🔎', 'Gate 2 smoke test', 'Agents own unit/integration/E2E all along; you test once, when the PRD is done. A nightly sweep fixes issues while you sleep.'],
116
- ]
117
-
118
- const html = `<!doctype html>
119
- <html lang="en">
120
- <head>
121
- <meta charset="utf-8">
122
- <meta name="viewport" content="width=device-width, initial-scale=1">
123
- <title>agent-templates multi-agent patterns, ready to drop in</title>
124
- <meta name="description" content="A catalog of multi-agent development architecture patterns: design write-ups plus drop-in scaffolding, E2E-tested, published on npm.">
125
- <link rel="preconnect" href="https://fonts.googleapis.com">
126
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
127
- <link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@600;700;800&family=Nunito:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
128
- <style>
129
- :root{
130
- --bg:#f7f0e6; --card:#fdf8f2; --ink:#5b4a3f; --muted:#96826f;
131
- --sage:#cfe3d0; --sage-d:#5f9c6c; --peach:#f9d3b5; --peach-d:#d97f45;
132
- --pink:#f7c6d0; --pink-d:#cf6d87; --butter:#f7e0a8; --butter-d:#a97e1c;
133
- --blue:#c5d7ee; --blue-d:#5c82b6; --lav:#d9cdf0; --lav-d:#8266bd;
134
- --clay:0 10px 24px rgba(91,74,63,.13), inset 0 3px 8px rgba(255,255,255,.95), inset 0 -4px 8px rgba(91,74,63,.07);
135
- --clay-sm:0 6px 14px rgba(91,74,63,.12), inset 0 2px 5px rgba(255,255,255,.9), inset 0 -2px 5px rgba(91,74,63,.06);
136
- }
137
- *{box-sizing:border-box}
138
- body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.6 "Nunito",system-ui,sans-serif;
139
- background-image:radial-gradient(600px 300px at 85% -50px, rgba(247,198,208,.35), transparent 70%),
140
- radial-gradient(500px 260px at -60px 30%, rgba(207,227,208,.4), transparent 70%),
141
- radial-gradient(520px 300px at 110% 75%, rgba(217,205,240,.32), transparent 70%);}
142
- h1,h2,h3{font-family:"Baloo 2","Nunito",sans-serif;line-height:1.15;margin:0}
143
- a{color:inherit;text-decoration:none}
144
- .wrap{max-width:1060px;margin:0 auto;padding:28px 20px 60px}
145
- header.nav{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:26px}
146
- .logo{font-family:"Baloo 2",sans-serif;font-weight:800;font-size:1.35rem;background:var(--card);
147
- padding:10px 20px;border-radius:999px;box-shadow:var(--clay-sm);border:1.5px solid #fff}
148
- .nav .spacer{flex:1}
149
- .btn{display:inline-flex;align-items:center;gap:8px;font-weight:700;padding:10px 20px;border-radius:999px;
150
- box-shadow:var(--clay-sm);border:1.5px solid #fff;transition:transform .15s ease, box-shadow .15s ease;cursor:pointer}
151
- .btn:hover{transform:translateY(-2px)}
152
- .btn.small{padding:8px 14px;font-size:.9rem}
153
- .btn.sage{background:var(--sage);color:var(--sage-d)} .btn.peach{background:var(--peach);color:var(--peach-d)}
154
- .btn.blue{background:var(--blue);color:var(--blue-d)} .btn.lav{background:var(--lav);color:var(--lav-d)}
155
- .card{background:var(--card);border-radius:28px;box-shadow:var(--clay);border:1.5px solid #fff;padding:26px 28px}
156
- .hero{display:grid;grid-template-columns:1.5fr 1fr;gap:22px;align-items:stretch;margin-bottom:22px}
157
- .hero .main{background:linear-gradient(145deg,#fbe3cd, #f9d3b5);}
158
- .hero h1{font-size:clamp(1.9rem,4.5vw,2.9rem);color:#7a4b28;margin-bottom:10px}
159
- .hero p.lede{color:#8a5a35;font-size:1.06rem;max-width:34em;margin:0 0 18px}
160
- .cta{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:18px}
161
- .quick{background:rgba(255,255,255,.75);border-radius:18px;box-shadow:var(--clay-sm);border:1.5px solid #fff;
162
- padding:12px 16px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
163
- .quick code{font:600 .85rem/1.4 ui-monospace,Consolas,monospace;color:#7a4b28;word-break:break-all}
164
- .hero .side{display:flex;flex-direction:column;gap:14px;justify-content:center}
165
- .side .fact{display:flex;gap:12px;align-items:center;background:var(--card);border-radius:20px;
166
- box-shadow:var(--clay-sm);border:1.5px solid #fff;padding:12px 16px;font-weight:600}
167
- .side .fact .ico{font-size:1.5rem}
168
- .chips{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px;margin:22px 0}
169
- .chip{border-radius:24px;padding:18px 20px;box-shadow:var(--clay);border:1.5px solid #fff}
170
- .chip .big{font-family:"Baloo 2",sans-serif;font-weight:800;font-size:1.7rem;display:block}
171
- .chip small{font-weight:700;opacity:.75}
172
- .chip.c1{background:var(--sage);color:var(--sage-d)} .chip.c2{background:var(--peach);color:var(--peach-d)}
173
- .chip.c3{background:var(--butter);color:var(--butter-d)} .chip.c4{background:var(--blue);color:var(--blue-d)}
174
- section{margin-top:34px}
175
- section > h2{font-size:1.6rem;margin-bottom:14px}
176
- .pattern{margin-bottom:18px}
177
- .pattern-head{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;align-items:center;margin-bottom:8px}
178
- .pattern h3{font-size:1.3rem}
179
- .pill{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;font-weight:700;
180
- font-size:.85rem;box-shadow:var(--clay-sm);border:1.5px solid #fff}
181
- .summary{color:var(--muted);margin:6px 0 14px;font-weight:600}
182
- .roles{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
183
- .pill.role{background:#f3ece1;color:var(--ink)} .pill.role code{color:var(--lav-d)}
184
- .usewhen{margin:0 0 14px;padding-left:22px;color:var(--muted)} .usewhen li{margin:4px 0}
185
- .links{display:flex;gap:10px;flex-wrap:wrap}
186
- .steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px}
187
- .step{border-radius:24px;padding:20px;box-shadow:var(--clay);border:1.5px solid #fff;background:var(--card)}
188
- .step .n{display:inline-flex;width:44px;height:44px;align-items:center;justify-content:center;font-size:1.4rem;
189
- border-radius:16px;box-shadow:var(--clay-sm);border:1.5px solid #fff;margin-bottom:10px}
190
- .step:nth-child(1) .n{background:var(--sage)} .step:nth-child(2) .n{background:var(--butter)}
191
- .step:nth-child(3) .n{background:var(--peach)} .step:nth-child(4) .n{background:var(--lav)}
192
- .step:nth-child(5) .n{background:var(--pink)}
193
- .step h3{font-size:1.05rem;margin-bottom:6px} .step p{margin:0;font-size:.9rem;color:var(--muted)}
194
- .step code{font-size:.8rem;color:var(--lav-d);word-break:break-all}
195
- footer{margin-top:44px;text-align:center;color:var(--muted);font-weight:600;font-size:.9rem}
196
- footer a{color:var(--lav-d)}
197
- @media (max-width:760px){.hero{grid-template-columns:1fr}}
198
- </style>
199
- </head>
200
- <body>
201
- <div class="wrap">
202
- <header class="nav">
203
- <span class="logo">🏗️ agent-templates</span>
204
- <span class="spacer"></span>
205
- <a class="btn sage" href="${GITHUB}">⭐ GitHub</a>
206
- <a class="btn peach" href="${NPM}">📦 npm <span data-npm-version>v${esc(pkg.version)}</span></a>
207
- </header>
208
-
209
- <div class="hero">
210
- <div class="card main">
211
- <h1>Multi-agent patterns,<br>ready to drop in.</h1>
212
- <p class="lede">Field-proven architectures for AI-agent development each one a design write-up <b>plus</b> working scaffolding. Humans decide at two gates; the agents do the rest.</p>
213
- <div class="cta">
214
- <a class="btn blue" href="${GITHUB}/blob/main/ADOPTING.md">🚀 Adoption guide</a>
215
- <a class="btn lav" href="${GITHUB}/blob/main/CLAUDE.md">📜 Operating manual</a>
216
- </div>
217
- <div class="quick"><code id="qs">${esc(QUICKSTART)}</code><button class="btn small butter" style="background:var(--butter);color:var(--butter-d)" onclick="navigator.clipboard.writeText(document.getElementById('qs').textContent).then(()=>{this.textContent='Copied ✓'})">Copy</button></div>
218
- </div>
219
- <div class="side">
220
- <div class="fact"><span class="ico">🧾</span> Every model/effort claim carries a source label and an expiry date</div>
221
- <div class="fact"><span class="ico">🛡️</span> Role boundaries enforced by hooks, not prose</div>
222
- <div class="fact"><span class="ico">🌙</span> Nightly sweep triages and fixes issues while you sleep</div>
223
- </div>
224
- </div>
225
-
226
- <div class="chips">
227
- <div class="chip c1"><span class="big">${patterns.length}</span><small>pattern${patterns.length === 1 ? '' : 's'} in the catalog</small></div>
228
- <div class="chip c2"><span class="big" data-npm-version>v${esc(pkg.version)}</span><small>on npm · MIT</small></div>
229
- <div class="chip c3"><span class="big">E2E</span><small>gated merges — deterministic, zero-token tests</small></div>
230
- <div class="chip c4"><span class="big">2</span><small>human gates: sign-off &amp; smoke test</small></div>
231
- </div>
232
-
233
- <section>
234
- <h2>🧩 Patterns</h2>
235
- ${patternCards}
236
- </section>
237
-
238
- <section>
239
- <h2>🛤️ From a bare PRD.md to shipped</h2>
240
- <div class="steps">
241
- ${STEPS.map(([ico, t, d], i) => `<div class="step"><span class="n">${ico}</span><h3>${i + 1}. ${t}</h3><p>${d}</p></div>`).join('\n ')}
242
- </div>
243
- </section>
244
-
245
- <footer>
246
- Generated from the pattern catalog by <a href="${GITHUB}/blob/main/scripts/build-site.mjs">scripts/build-site.mjs</a>
247
- · ${new Date().toISOString().slice(0, 10)} · <a href="${GITHUB}/blob/main/LICENSE">MIT</a>
248
- · <a href="${GITHUB}/issues/new/choose">Feedback issues</a>
249
- </footer>
250
- </div>
251
- <script>
252
- fetch('https://registry.npmjs.org/agent-templates').then(r=>r.json()).then(d=>{
253
- var v=d['dist-tags']&&d['dist-tags'].latest;
254
- if(v){document.querySelectorAll('[data-npm-version]').forEach(function(e){e.textContent='v'+v})}
255
- }).catch(function(){})
256
- </script>
257
- </body>
258
- </html>
259
- `
260
-
261
- mkdirSync(OUT, { recursive: true })
262
- writeFileSync(join(OUT, 'index.html'), html)
263
- writeFileSync(join(OUT, '.nojekyll'), '')
264
- console.log(`site: ${join(OUT, 'index.html')} (${patterns.length} pattern(s), pkg v${pkg.version})`)
1
+ #!/usr/bin/env node
2
+ // build-site.mjs — generates the catalog's GitHub Pages site from the catalog's own
3
+ // data (patterns/*/README.md + package.json). Never hand-edit the output: the page
4
+ // must not be able to drift from the pattern metadata.
5
+ //
6
+ // Usage: node scripts/build-site.mjs [--out <dir>] (default: site/)
7
+ // Output: <out>/index.html (self-contained) + <out>/.nojekyll
8
+ //
9
+ // Style: clay-morphism per the approved hi-fi mock on catalog issue #19layered
10
+ // clay shadows (outer drop + inset top highlight + inset bottom ink), Baloo 2 +
11
+ // Nunito, five build-time palettes with mint baked as default, pure-CSS clay icons
12
+ // (no emoji, no icon fonts). The mock is the source of truth for colors/shadows/
13
+ // spacing; the pattern catalog stays the source of truth for all copy.
14
+
15
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
16
+ import { join } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+
19
+ const ROOT = fileURLToPath(new URL('..', import.meta.url))
20
+ const argv = process.argv.slice(2)
21
+ const outIx = argv.indexOf('--out')
22
+ const OUT = outIx !== -1 && argv[outIx + 1] ? argv[outIx + 1] : join(ROOT, 'site')
23
+
24
+ const GITHUB = 'https://github.com/Ruihang2017/agent-templates'
25
+ const NPM = 'https://www.npmjs.com/package/agent-templates'
26
+ const QUICKSTART = 'npx agent-templates@latest adopt three-agent-architect-builder-reviewer .'
27
+
28
+ const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
29
+ const strip = (s) => String(s).replace(/\*\*/g, '').replace(/`/g, '').trim()
30
+
31
+ const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
32
+
33
+ function parsePattern(dir) {
34
+ const path = join(ROOT, 'patterns', dir, 'README.md')
35
+ if (!existsSync(path)) return null
36
+ const md = readFileSync(path, 'utf8')
37
+ const pick = (re) => (md.match(re) || [])[1] || ''
38
+
39
+ const title = strip(pick(/^# Pattern: (.+)$/m)) || dir
40
+ const statusRaw = strip(pick(/\|\s*\*\*Status\*\*\s*\|\s*([^|]+)\|/))
41
+ const status = (statusRaw.match(/[a-z]+/) || ['proposed'])[0]
42
+ const asOf = strip(pick(/\|\s*\*\*As-of date\*\*\s*\|\s*([^|]+)\|/))
43
+
44
+ // the one-line topology summary: first plain paragraph after the metadata table
45
+ let summary = ''
46
+ const lines = md.split('\n')
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const l = lines[i].trim()
49
+ if (!l || l.startsWith('#') || l.startsWith('|') || l.startsWith('<!--')) continue
50
+ summary = strip(l)
51
+ break
52
+ }
53
+
54
+ // model/effort rows from §3
55
+ const roles = []
56
+ const sec3 = md.split(/^## 3\. Model \+ effort[^\n]*$/m)[1]
57
+ if (sec3) {
58
+ for (const row of sec3.split(/^## /m)[0].split('\n')) {
59
+ const m = row.match(/^\|\s*([A-Z][^|]*?)\s*\|\s*([^|]+?)\s*\|\s*`([^`]+)`\s*\|/)
60
+ if (m && !/^Role/.test(m[1])) roles.push({ role: strip(m[1]), model: strip(m[2]), effort: m[3] })
61
+ }
62
+ }
63
+
64
+ return { dir, title, status, asOf, summary, roles }
65
+ }
66
+
67
+ const patterns = readdirSync(join(ROOT, 'patterns'), { withFileTypes: true })
68
+ .filter((d) => d.isDirectory() && existsSync(join(ROOT, 'patterns', d.name, 'scaffold')))
69
+ .map((d) => parsePattern(d.name))
70
+ .filter(Boolean)
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Theme palettes, distilled from the approved mock (issue #19). The mock names
74
+ // them cream / mint / lavender / sakura / sky; mint is the approved default.
75
+ // Swapping the palette is a build-time constant change — no runtime switcher.
76
+ // amb/ins/flt are bare R,G,B triplets consumed via rgba(var(--amb), a).
77
+ // ---------------------------------------------------------------------------
78
+ const THEMES = {
79
+ cream: { page: '#ffeecb', heroA: '#fbd2b0', heroB: '#f5b98f', heroInk: 'rgba(190,110,60,0.18)', heroAmb: 'rgba(199,141,109,0.32)', heroTitle: '#5a3520', heroBody: '#8a5a3d', card: '#fdf4e6', pill: '#fff7e9', ink: '#4a3020', sub: '#7b5a42', mut: '#a3805f', code: '#7b4a2a', amb: '199,141,109', ins: '214,160,120', flt: '150,90,60' },
80
+ mint: { page: '#e0f3e0', heroA: '#c3e6bd', heroB: '#a3d49a', heroInk: 'rgba(40,110,50,0.18)', heroAmb: 'rgba(110,160,120,0.35)', heroTitle: '#2f5a30', heroBody: '#4f7a52', card: '#f0f9ec', pill: '#f8fdf5', ink: '#2f4a28', sub: '#567a50', mut: '#7fa077', code: '#4f6b3a', amb: '110,150,115', ins: '160,195,155', flt: '80,120,85' },
81
+ lavender: { page: '#f0e7fa', heroA: '#ddcbf3', heroB: '#c4abe8', heroInk: 'rgba(90,50,140,0.16)', heroAmb: 'rgba(140,110,180,0.35)', heroTitle: '#46306b', heroBody: '#6b5490', card: '#f6f1fc', pill: '#fbf9fe', ink: '#3f2f5c', sub: '#6b5a86', mut: '#9284ab', code: '#5f4a8a', amb: '130,110,165', ins: '185,165,215', flt: '100,80,135' },
82
+ sakura: { page: '#fde7ee', heroA: '#f9c9d7', heroB: '#f3abc2', heroInk: 'rgba(180,60,100,0.15)', heroAmb: 'rgba(190,120,140,0.35)', heroTitle: '#6b2f45', heroBody: '#96556b', card: '#fcf1f5', pill: '#fef8fa', ink: '#5c2f40', sub: '#86566a', mut: '#ab8494', code: '#8a4a62', amb: '180,120,140', ins: '215,165,185', flt: '150,80,105' },
83
+ sky: { page: '#e3eefa', heroA: '#c8ddf4', heroB: '#a8c7ec', heroInk: 'rgba(50,90,150,0.15)', heroAmb: 'rgba(110,140,180,0.35)', heroTitle: '#2c4a6b', heroBody: '#4f6f96', card: '#eff5fc', pill: '#f7fafe', ink: '#2b3f5c', sub: '#52698a', mut: '#8095ad', code: '#3f5a80', amb: '100,130,170', ins: '155,180,215', flt: '70,100,140' },
84
+ }
85
+ const THEME = THEMES.mint
86
+
87
+ // Status chips: trialed/adopted/proposed use the mock's yellow/green("stable")/
88
+ // pink("experimental") recipes; deprecated (catalog-only status) is a neutral clay.
89
+ const STATUS_CHIP = {
90
+ trialed: { chip: 'background:linear-gradient(180deg,#f9d66e,#f0c14a);color:#6b4a15;box-shadow:inset 0 2px 2px rgba(255,255,255,0.6),inset 0 -3px 4px rgba(180,120,20,0.3),0 5px 10px rgba(180,130,60,0.3)', dot: 'inset -1px -1px 2px rgba(180,120,20,0.4)' },
91
+ proposed: { chip: 'background:linear-gradient(180deg,#f792ab,#ef6f92);color:#fff;text-shadow:0 1px 2px rgba(170,30,80,0.35);box-shadow:inset 0 2px 2px rgba(255,255,255,0.5),inset 0 -3px 4px rgba(170,30,80,0.3),0 5px 10px rgba(199,100,110,0.3)', dot: 'inset -1px -1px 2px rgba(170,30,80,0.35)' },
92
+ adopted: { chip: 'background:linear-gradient(180deg,#b3dcab,#98cb8e);color:#fff;text-shadow:0 1px 2px rgba(30,90,40,0.35);box-shadow:inset 0 2px 2px rgba(255,255,255,0.5),inset 0 -3px 4px rgba(30,100,40,0.28),0 5px 10px rgba(120,150,100,0.3)', dot: 'inset -1px -1px 2px rgba(30,90,40,0.35)' },
93
+ deprecated: { chip: 'background:linear-gradient(180deg,#cfc4b4,#b8aa96);color:#fff;text-shadow:0 1px 2px rgba(110,90,60,0.35);box-shadow:inset 0 2px 2px rgba(255,255,255,0.5),inset 0 -3px 4px rgba(110,90,60,0.3),0 5px 10px rgba(150,130,100,0.3)', dot: 'inset -1px -1px 2px rgba(110,90,60,0.35)' },
94
+ }
95
+
96
+ const ROLE_DOT = {
97
+ Architect: ['#a98fd6', 'rgba(80,40,140,0.3)'],
98
+ Builder: ['#f28ba3', 'rgba(170,30,80,0.3)'],
99
+ Reviewer: ['#8fb4e6', 'rgba(40,80,150,0.3)'],
100
+ }
101
+ const roleDot = (r) => ROLE_DOT[Object.keys(ROLE_DOT).find((k) => r.startsWith(k))] || ['#f2c44e', 'rgba(180,120,20,0.35)']
102
+
103
+ // --- pure-CSS clay icons, markup lifted from the approved mock -------------
104
+ const LOGO_ICON = `<span class="gx" style="width:26px;height:26px;filter:drop-shadow(0 2px 3px rgba(var(--flt),0.3))"><span style="position:absolute;left:1px;top:2px;width:15px;height:20px;border-radius:5px;background:#b7a0e2;transform:rotate(-9deg);box-shadow:inset 0 2px 2px rgba(255,255,255,0.5)"></span><span style="position:absolute;left:9px;top:3px;width:15px;height:20px;border-radius:5px;background:var(--pill);transform:rotate(7deg);box-shadow:inset 0 -2px 3px rgba(var(--ins),0.35);display:flex;flex-direction:column;gap:3px;padding:4px 3px;box-sizing:border-box"><span style="height:2.5px;border-radius:2px;background:#f4a0b5"></span><span style="height:2.5px;border-radius:2px;background:#d9c8f0"></span><span style="height:2.5px;border-radius:2px;background:#d9c8f0;width:70%"></span></span></span>`
105
+ const STAR_ICON = `<span style="width:14px;height:14px;background:#f9d66e;clip-path:polygon(50% 0%,61% 35%,98% 35%,68% 57%,79% 91%,50% 70%,21% 91%,32% 57%,2% 35%,39% 35%);display:inline-block"></span>`
106
+ const NPM_ICON = `<span class="gx" style="width:14px;height:14px"><span style="position:absolute;inset:0;border-radius:4px;background:#fff3df;box-shadow:inset 0 -2px 3px rgba(190,110,60,0.4)"></span><span style="position:absolute;left:0;right:0;top:5.5px;height:3px;background:#e8b48c"></span></span>`
107
+ const PATTERNS_ICON = `<span style="display:inline-grid;grid-template-columns:9px 9px;gap:2.5px;filter:drop-shadow(0 2px 3px rgba(var(--flt),0.3))"><span style="width:9px;height:9px;border-radius:3px;background:#9ed095"></span><span style="width:9px;height:9px;border-radius:3px;background:#f6a5bb"></span><span style="width:9px;height:9px;border-radius:3px;background:#f4cd6d"></span><span style="width:9px;height:9px;border-radius:3px;background:#c3abe9"></span></span>`
108
+ const PIPELINE_ICON = `<span class="gx" style="width:22px;height:18px;border-radius:5px;background:#a9c6ec;overflow:hidden;filter:drop-shadow(0 2px 3px rgba(var(--flt),0.3))"><span style="position:absolute;left:3px;top:3px;width:6px;height:6px;border-radius:50%;background:#f9d66e"></span><span style="position:absolute;left:-5px;bottom:-9px;width:17px;height:17px;border-radius:50%;background:#9ed095"></span><span style="position:absolute;right:-4px;bottom:-8px;width:15px;height:15px;border-radius:50%;background:#8fc98b"></span></span>`
109
+
110
+ // hero side facts: [icon-tile style, glyph markup, copy]
111
+ const FACTS = [
112
+ ['background:#d9c8f0;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.55),inset -3px -4px 6px rgba(90,50,140,0.18)',
113
+ `<span class="gx" style="width:15px;height:18px"><span style="position:absolute;inset:0;border-radius:3px;background:#fffaf2;box-shadow:0 2px 4px rgba(90,50,140,0.25)"></span><span style="position:absolute;left:3px;right:3px;top:4px;height:2px;border-radius:2px;background:#c3abe9"></span><span style="position:absolute;left:3px;right:3px;top:8px;height:2px;border-radius:2px;background:#c3abe9"></span><span style="position:absolute;left:3px;right:6px;top:12px;height:2px;border-radius:2px;background:#c3abe9"></span></span>`,
114
+ 'Every model/effort claim carries a source label and an expiry date'],
115
+ ['background:#b3cdf0;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.55),inset -3px -4px 6px rgba(40,80,150,0.2)',
116
+ `<span style="width:16px;height:18px;background:#fffaf2;border-radius:5px 5px 50% 50% / 5px 5px 75% 75%;box-shadow:inset 0 -3px 4px rgba(40,80,150,0.25),0 2px 4px rgba(40,80,150,0.3);display:inline-block"></span>`,
117
+ 'Role boundaries enforced by hooks, not prose'],
118
+ ['background:#f9d66e;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.6),inset -3px -4px 6px rgba(180,120,20,0.25)',
119
+ `<span class="gx" style="width:17px;height:17px"><span style="position:absolute;inset:0;border-radius:50%;background:#fffaf2;box-shadow:0 2px 4px rgba(160,100,20,0.3)"></span><span style="position:absolute;left:6px;top:-2px;width:14px;height:14px;border-radius:50%;background:#f9d66e"></span></span>`,
120
+ 'Nightly sweep triages and fixes issues while you sleep'],
121
+ ]
122
+
123
+ // pipeline steps: [icon-tile style, glyph markup, title, description]
124
+ const STEPS = [
125
+ ['background:#9ed095;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.55),inset -3px -4px 6px rgba(30,90,40,0.2),0 6px 10px rgba(var(--amb),0.22)',
126
+ `<span class="gx" style="width:20px;height:16px"><span style="position:absolute;inset:0;border-radius:4px;background:#fffaf2;box-shadow:inset 0 -2px 3px rgba(30,90,40,0.2),0 3px 5px rgba(30,90,40,0.3)"></span><span style="position:absolute;left:8.5px;top:0;width:3px;height:16px;background:#f4cd6d"></span></span>`,
127
+ 'Adopt', `<code>${esc(QUICKSTART)}</code> — scaffold, templates, docs skeleton, CLAUDE.md, in one idempotent command.`],
128
+ ['background:#f4cd6d;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.6),inset -3px -4px 6px rgba(180,120,20,0.25),0 6px 10px rgba(var(--amb),0.22)',
129
+ `<span class="gx" style="width:22px;height:16px"><span style="position:absolute;inset:0;border-radius:3px;background:#fffaf2;box-shadow:0 3px 5px rgba(160,100,20,0.3)"></span><span style="position:absolute;left:6.5px;top:0;width:1.5px;height:16px;background:rgba(var(--flt),0.25)"></span><span style="position:absolute;left:14px;top:0;width:1.5px;height:16px;background:rgba(var(--flt),0.25)"></span><span style="position:absolute;left:9px;top:5px;width:4px;height:4px;border-radius:50%;background:#f2789a"></span></span>`,
130
+ 'Break down', '<code>/breakdown-prd</code> — the Architect turns your PRD into sub-PRDs and cold-startable tickets, then stops.'],
131
+ ['background:#f3e6d0;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.7),inset -3px -4px 6px rgba(170,120,70,0.2),0 6px 10px rgba(var(--amb),0.22)',
132
+ `<span style="width:13px;height:28px;border-radius:6.5px;background:#7a6a58;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;box-shadow:inset 0 2px 2px rgba(255,255,255,0.25),0 3px 5px rgba(90,60,30,0.35)"><span style="width:6px;height:6px;border-radius:50%;background:#f28b8b"></span><span style="width:6px;height:6px;border-radius:50%;background:#f9d66e"></span><span style="width:6px;height:6px;border-radius:50%;background:#8fc98b"></span></span>`,
133
+ 'Gate 1 — you decide', 'Review the breakdown, then <code>/start-milestone</code>: tickets become tracker issues and the pipeline starts.'],
134
+ ['background:#c3abe9;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.55),inset -3px -4px 6px rgba(90,50,140,0.22),0 6px 10px rgba(var(--amb),0.22)',
135
+ `<span class="gx" style="width:24px;height:24px"><span style="position:absolute;left:11px;top:0;width:2px;height:4px;background:#fffaf2"></span><span style="position:absolute;left:9.5px;top:-3px;width:5px;height:5px;border-radius:50%;background:#f2789a"></span><span style="position:absolute;left:0;top:5px;width:24px;height:18px;border-radius:7px;background:#fffaf2;box-shadow:inset 0 -3px 4px rgba(90,50,140,0.2),0 3px 5px rgba(70,30,120,0.3)"></span><span style="position:absolute;left:5px;top:11px;width:5px;height:5px;border-radius:50%;background:#8f74c4"></span><span style="position:absolute;left:14px;top:11px;width:5px;height:5px;border-radius:50%;background:#8f74c4"></span></span>`,
136
+ 'Autonomous middle', 'Plan → build → fresh-context review (bounce-capped in code) → merge on <code>CLEAR</code> → issue closed → delivery verified.'],
137
+ ['background:#f6a5bb;box-shadow:inset 2px 3px 4px rgba(255,255,255,0.55),inset -3px -4px 6px rgba(170,30,80,0.2),0 6px 10px rgba(var(--amb),0.22)',
138
+ `<span class="gx" style="width:22px;height:22px"><span style="position:absolute;left:0;top:0;width:13px;height:13px;border-radius:50%;border:4px solid #fffaf2;box-shadow:0 3px 5px rgba(150,60,90,0.3)"></span><span style="position:absolute;left:16px;top:15px;width:4.5px;height:9px;border-radius:2.5px;background:#fffaf2;transform:rotate(-45deg)"></span></span>`,
139
+ 'Gate 2 smoke test', 'Agents own unit/integration/E2E all along; you test once, when the PRD is done. A nightly sweep fixes issues while you sleep.'],
140
+ ]
141
+
142
+ // Use-when bullets are intentionally gone (parse + render): the approved mock's
143
+ // pattern card is title + status chip + summary + role chips + links only.
144
+ const patternCards = patterns
145
+ .map((p) => {
146
+ const c = STATUS_CHIP[p.status] || STATUS_CHIP.proposed
147
+ const summary = esc(p.summary).replace(/→/g, '<span class="arr">→</span>')
148
+ return `
149
+ <article class="panel pattern">
150
+ <div class="pattern-head">
151
+ <h3>${esc(p.title)}</h3>
152
+ <span class="chip" style="${c.chip}"><span class="chip-dot" style="box-shadow:${c.dot}"></span>${esc(p.status)} · as of ${esc(p.asOf)}</span>
153
+ </div>
154
+ <p class="summary">${summary}</p>
155
+ <div class="roles">
156
+ ${p.roles.map((r) => { const [dot, dotInk] = roleDot(r.role); return `<span class="role"><span class="dot" style="background:${dot};box-shadow:inset 1px 1.5px 1.5px rgba(255,255,255,0.6),inset -1px -1.5px 2px ${dotInk}"></span><b>${esc(r.role)}</b><span class="sep">·</span><code>${esc(r.model)} <span class="eff">@${esc(r.effort)}</span></code></span>` }).join('\n ')}
157
+ </div>
158
+ <div class="links">
159
+ <a class="btn btn-green" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}">Pattern write-up</a>
160
+ <a class="btn btn-purple" href="${GITHUB}/tree/main/patterns/${esc(p.dir)}/scaffold">Scaffold</a>
161
+ </div>
162
+ </article>`
163
+ })
164
+ .join('\n')
165
+
166
+ const html = `<!doctype html>
167
+ <html lang="en">
168
+ <head>
169
+ <meta charset="utf-8">
170
+ <meta name="viewport" content="width=device-width, initial-scale=1">
171
+ <title>agent-templates — multi-agent patterns, ready to drop in</title>
172
+ <meta name="description" content="A catalog of multi-agent development architecture patterns: design write-ups plus drop-in scaffolding, E2E-tested, published on npm.">
173
+ <link rel="preconnect" href="https://fonts.googleapis.com">
174
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
175
+ <link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700;800&family=Nunito:wght@400;600;700;800;900&display=swap" rel="stylesheet">
176
+ <style>
177
+ :root{
178
+ --page:${THEME.page}; --hero-a:${THEME.heroA}; --hero-b:${THEME.heroB};
179
+ --hero-ink:${THEME.heroInk}; --hero-amb:${THEME.heroAmb};
180
+ --hero-title:${THEME.heroTitle}; --hero-body:${THEME.heroBody};
181
+ --card:${THEME.card}; --pill:${THEME.pill};
182
+ --ink:${THEME.ink}; --sub:${THEME.sub}; --mut:${THEME.mut}; --code:${THEME.code};
183
+ --amb:${THEME.amb}; --ins:${THEME.ins}; --flt:${THEME.flt};
184
+ --mono:ui-monospace,'Cascadia Code',Consolas,Menlo,monospace;
185
+ }
186
+ *{box-sizing:border-box}
187
+ body{margin:0;background:var(--page);font-family:'Nunito',sans-serif}
188
+ a{color:#e7548c;text-decoration:none}
189
+ a:hover{color:#c13a63}
190
+ .wrap{max-width:880px;margin:0 auto;padding:22px 0 34px}
191
+ .gx{position:relative;display:inline-block}
192
+ .arr{color:#e7548c}
193
+
194
+ .nav{display:flex;align-items:center;margin-bottom:22px}
195
+ .logo{display:flex;align-items:center;gap:10px;padding:8px 18px 8px 13px;border-radius:19px;background:var(--pill);
196
+ box-shadow:inset 0 2px 2px #fff,inset 0 -3px 5px rgba(var(--ins),0.2),0 8px 16px rgba(var(--amb),0.25)}
197
+ .logo b{font-family:'Baloo 2',cursive;font-weight:800;font-size:19px;color:var(--ink)}
198
+ .nav-links{margin-left:auto;display:flex;gap:10px}
199
+
200
+ .btn{display:inline-flex;align-items:center;gap:8px;padding:9px 18px;border-radius:18px;color:#fff;
201
+ font-size:12.5px;font-weight:900;transition:transform .15s ease;cursor:pointer}
202
+ .btn:hover{transform:translateY(-2px);color:#fff}
203
+ .btn:active{transform:translateY(1px)}
204
+ .btn-lg{padding:10px 20px;font-size:13px}
205
+ .btn-green{background:linear-gradient(180deg,#b3dcab,#98cb8e);text-shadow:0 1px 2px rgba(30,90,40,0.35);
206
+ box-shadow:inset 0 2px 3px rgba(255,255,255,0.5),inset 0 -3px 5px rgba(30,100,40,0.25),0 8px 14px rgba(120,150,100,0.35)}
207
+ .btn-orange{background:linear-gradient(180deg,#f6c9a2,#efb185);text-shadow:0 1px 2px rgba(160,80,30,0.35);
208
+ box-shadow:inset 0 2px 3px rgba(255,255,255,0.5),inset 0 -3px 5px rgba(170,90,30,0.25),0 8px 14px rgba(190,130,90,0.35)}
209
+ .btn-blue{background:linear-gradient(180deg,#b3cdf0,#96b8e5);text-shadow:0 1px 2px rgba(40,80,150,0.4);
210
+ box-shadow:inset 0 2px 3px rgba(255,255,255,0.55),inset 0 -3px 5px rgba(40,80,150,0.28),0 8px 14px rgba(var(--amb),0.3)}
211
+ .btn-purple{background:linear-gradient(180deg,#c3abe9,#a78cd8);text-shadow:0 1px 2px rgba(90,50,130,0.4);
212
+ box-shadow:inset 0 2px 3px rgba(255,255,255,0.55),inset 0 -4px 6px rgba(94,58,140,0.28),0 8px 14px rgba(var(--amb),0.3)}
213
+
214
+ .hero{display:grid;grid-template-columns:1fr 300px;gap:16px;align-items:stretch}
215
+ .hero-main{border-radius:30px;padding:30px 34px 26px;background:linear-gradient(170deg,var(--hero-a),var(--hero-b));
216
+ box-shadow:inset 0 3px 4px rgba(255,255,255,0.6),inset 0 -6px 10px var(--hero-ink),0 16px 30px var(--hero-amb)}
217
+ .hero-main h1{margin:0;font-family:'Baloo 2',cursive;font-weight:800;font-size:38px;line-height:1.1;
218
+ color:var(--hero-title);text-shadow:0 2px 0 rgba(255,255,255,0.4)}
219
+ .lede{margin:14px 0 0;max-width:410px;font-size:13.5px;line-height:1.6;font-weight:700;color:var(--hero-body)}
220
+ .cta{display:flex;gap:12px;margin-top:20px}
221
+ .quick{margin-top:20px;border-radius:16px;background:var(--pill);padding:14px 16px 12px;
222
+ box-shadow:inset 0 3px 6px rgba(var(--flt),0.2),inset 0 -2px 2px rgba(255,255,255,0.8)}
223
+ .quick code{display:block;font-family:var(--mono);font-size:12px;font-weight:700;color:var(--code);line-height:1.55;word-break:break-all}
224
+ .copy{display:inline-flex;margin-top:10px;padding:6px 16px;border-radius:13px;border:0;
225
+ background:linear-gradient(180deg,#f9d66e,#f0c14a);cursor:pointer;font-family:'Nunito',sans-serif;
226
+ font-size:12px;font-weight:900;color:#6b4a15;transition:transform .15s ease;
227
+ box-shadow:inset 0 2px 2px rgba(255,255,255,0.6),inset 0 -3px 4px rgba(180,120,20,0.3),0 5px 10px rgba(180,130,60,0.3)}
228
+ .copy:hover{transform:translateY(-1.5px)}
229
+ .copy:active{transform:translateY(1px)}
230
+ .hero-side{display:flex;flex-direction:column;gap:14px;justify-content:center}
231
+ .fact{display:flex;align-items:center;gap:12px;padding:13px 16px;border-radius:20px;background:var(--card);
232
+ box-shadow:inset 0 2px 2px #fff,inset 0 -3px 6px rgba(var(--ins),0.16),0 10px 20px rgba(var(--amb),0.22)}
233
+ .fact-ico{flex:none;width:38px;height:38px;border-radius:12px;display:grid;place-items:center}
234
+ .fact p{margin:0;font-size:12.5px;font-weight:800;color:var(--sub);line-height:1.5}
235
+
236
+ .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:15px;margin-top:18px}
237
+ .stat{border-radius:22px;padding:16px 18px 18px}
238
+ .stat .big{font-family:'Baloo 2',cursive;font-weight:800;font-size:23px}
239
+ .stat p{margin:3px 0 0;font-size:11.5px;font-weight:800;line-height:1.5}
240
+ .stat-green{background:linear-gradient(180deg,#b9dfb1,#9bce92);
241
+ box-shadow:inset 0 3px 4px rgba(255,255,255,0.55),inset 0 -5px 8px rgba(30,90,40,0.2),0 10px 20px rgba(var(--amb),0.25)}
242
+ .stat-green .big{color:#2f6b35} .stat-green p{color:#3f7a45}
243
+ .stat-orange{background:linear-gradient(180deg,#f9cba6,#f1af85);
244
+ box-shadow:inset 0 3px 4px rgba(255,255,255,0.55),inset 0 -5px 8px rgba(170,90,30,0.2),0 10px 20px rgba(var(--amb),0.25)}
245
+ .stat-orange .big{color:#94481c} .stat-orange p{color:#9c5426}
246
+ .stat-yellow{background:linear-gradient(180deg,#fae09c,#f3cb6a);
247
+ box-shadow:inset 0 3px 4px rgba(255,255,255,0.6),inset 0 -5px 8px rgba(180,120,20,0.22),0 10px 20px rgba(var(--amb),0.25)}
248
+ .stat-yellow .big{color:#7e5a12} .stat-yellow p{color:#8a6218}
249
+ .stat-blue{background:linear-gradient(180deg,#b7d2f4,#9abfe9);
250
+ box-shadow:inset 0 3px 4px rgba(255,255,255,0.55),inset 0 -5px 8px rgba(40,80,150,0.22),0 10px 20px rgba(var(--amb),0.25)}
251
+ .stat-blue .big{color:#2c548c} .stat-blue p{color:#315e9c}
252
+
253
+ .sec-head{display:flex;align-items:center;gap:11px;margin:32px 0 14px}
254
+ .sec-head h2{margin:0;font-family:'Baloo 2',cursive;font-weight:800;font-size:25px;color:var(--ink)}
255
+
256
+ .panel{border-radius:24px;background:var(--card);padding:20px 24px 22px;
257
+ box-shadow:inset 0 2px 2px #fff,inset 0 -4px 8px rgba(var(--ins),0.15),0 14px 26px rgba(var(--amb),0.25)}
258
+ .pattern + .pattern{margin-top:16px}
259
+ .pattern-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
260
+ .pattern-head h3{margin:0;font-family:'Baloo 2',cursive;font-weight:700;font-size:20px;color:var(--ink);flex:1}
261
+ .chip{display:inline-flex;align-items:center;gap:7px;padding:7px 15px;border-radius:15px;font-size:11.5px;font-weight:900}
262
+ .chip-dot{width:8px;height:8px;border-radius:50%;background:#fffaf2}
263
+ .summary{margin:12px 0 0;font-size:13px;line-height:1.6;font-weight:700;color:var(--sub);max-width:760px}
264
+ .roles{display:flex;flex-wrap:wrap;gap:10px;margin-top:15px}
265
+ .role{display:inline-flex;align-items:center;gap:8px;padding:8px 15px;border-radius:16px;background:var(--pill);
266
+ box-shadow:inset 0 2px 2px #fff,inset 0 -3px 5px rgba(var(--ins),0.25),0 5px 10px rgba(var(--amb),0.2)}
267
+ .role .dot{width:9px;height:9px;border-radius:50%}
268
+ .role b{font-size:12px;font-weight:900;color:var(--ink)}
269
+ .role .sep{font-size:11px;font-weight:700;color:var(--mut)}
270
+ .role code{font-family:var(--mono);font-size:11px;font-weight:700;color:#8a5fd0}
271
+ .role .eff{color:#e7548c}
272
+ .links{display:flex;gap:12px;margin-top:17px}
273
+
274
+ .steps{display:grid;grid-template-columns:repeat(5,1fr);gap:14px}
275
+ .step{border-radius:20px;background:var(--card);padding:15px 14px 17px;
276
+ box-shadow:inset 0 2px 2px #fff,inset 0 -3px 6px rgba(var(--ins),0.15),0 10px 20px rgba(var(--amb),0.22)}
277
+ .step-ico{width:42px;height:42px;border-radius:13px;display:grid;place-items:center}
278
+ .step h3{margin:11px 0 0;font-size:13px;font-weight:900;color:var(--ink);font-family:'Nunito',sans-serif}
279
+ .step p{margin:6px 0 0;font-size:11.5px;font-weight:700;color:var(--sub);line-height:1.55}
280
+ .step code{font-family:var(--mono);font-size:10px;background:rgba(var(--flt),0.12);border-radius:5px;padding:1px 4px;color:var(--code);word-break:break-all}
281
+
282
+ footer{margin-top:30px;text-align:center;font-size:12.5px;font-weight:700;color:var(--mut)}
283
+ footer code{font-family:var(--mono);font-size:11.5px;color:#8a5fd0}
284
+
285
+ @media (max-width:912px){.wrap{padding-left:16px;padding-right:16px}}
286
+ @media (max-width:860px){.hero{grid-template-columns:1fr}.stats{grid-template-columns:repeat(2,1fr)}.steps{grid-template-columns:repeat(2,1fr)}}
287
+ @media (max-width:540px){.stats,.steps{grid-template-columns:1fr}.pattern-head h3{flex-basis:100%}}
288
+ </style>
289
+ </head>
290
+ <body>
291
+ <div class="wrap">
292
+ <header class="nav">
293
+ <span class="logo">${LOGO_ICON}<b>agent-templates</b></span>
294
+ <span class="nav-links">
295
+ <a class="btn btn-green" href="${GITHUB}">${STAR_ICON} GitHub</a>
296
+ <a class="btn btn-orange" href="${NPM}">${NPM_ICON} npm&nbsp;&nbsp;<span data-npm-version>v${esc(pkg.version)}</span></a>
297
+ </span>
298
+ </header>
299
+
300
+ <div class="hero">
301
+ <div class="hero-main">
302
+ <h1>Multi-agent patterns,<br>ready to drop in.</h1>
303
+ <p class="lede">Field-proven architectures for AI-agent development — each one a design write-up <em>plus</em> working scaffolding. Humans decide at two gates; the agents do the rest.</p>
304
+ <div class="cta">
305
+ <a class="btn btn-lg btn-blue" href="${GITHUB}/blob/main/ADOPTING.md">Adoption guide</a>
306
+ <a class="btn btn-lg btn-purple" href="${GITHUB}/blob/main/CLAUDE.md">Operating manual</a>
307
+ </div>
308
+ <div class="quick">
309
+ <code id="qs">${esc(QUICKSTART)}</code>
310
+ <button class="copy" id="copy-btn" type="button">Copy</button>
311
+ </div>
312
+ </div>
313
+ <div class="hero-side">
314
+ ${FACTS.map(([tile, glyph, copy]) => `<div class="fact"><span class="fact-ico" style="${tile}">${glyph}</span><p>${copy}</p></div>`).join('\n ')}
315
+ </div>
316
+ </div>
317
+
318
+ <div class="stats">
319
+ <div class="stat stat-green"><span class="big">${patterns.length}</span><p>pattern${patterns.length === 1 ? '' : 's'} in the catalog</p></div>
320
+ <div class="stat stat-orange"><span class="big" data-npm-version>v${esc(pkg.version)}</span><p>on npm · MIT</p></div>
321
+ <div class="stat stat-yellow"><span class="big">E2E</span><p>gated merges — deterministic, zero-token tests</p></div>
322
+ <div class="stat stat-blue"><span class="big">2</span><p>human gates: sign-off &amp; smoke test</p></div>
323
+ </div>
324
+
325
+ <section>
326
+ <div class="sec-head">${PATTERNS_ICON}<h2>Patterns</h2></div>
327
+ ${patternCards}
328
+ </section>
329
+
330
+ <section>
331
+ <div class="sec-head">${PIPELINE_ICON}<h2>From a bare PRD.md to shipped</h2></div>
332
+ <div class="steps">
333
+ ${STEPS.map(([tile, glyph, t, d], i) => `<div class="step"><span class="step-ico" style="${tile}">${glyph}</span><h3>${i + 1}. ${t}</h3><p>${d}</p></div>`).join('\n ')}
334
+ </div>
335
+ </section>
336
+
337
+ <footer>
338
+ Generated from the pattern catalog by <a href="${GITHUB}/blob/main/scripts/build-site.mjs"><code>scripts/build-site.mjs</code></a>
339
+ · ${new Date().toISOString().slice(0, 10)} · <a href="${GITHUB}/blob/main/LICENSE">MIT</a>
340
+ · <a href="${GITHUB}/issues/new/choose">Feedback → issues</a>
341
+ </footer>
342
+ </div>
343
+ <script>
344
+ (function(){
345
+ var b=document.getElementById('copy-btn'),q=document.getElementById('qs'),t
346
+ if(!b||!q)return
347
+ b.addEventListener('click',function(){
348
+ try{navigator.clipboard.writeText(q.textContent).catch(function(){})}catch(e){}
349
+ b.textContent='Copied!'
350
+ clearTimeout(t)
351
+ t=setTimeout(function(){b.textContent='Copy'},1400)
352
+ })
353
+ })()
354
+ fetch('https://registry.npmjs.org/agent-templates').then(function(r){return r.json()}).then(function(d){
355
+ var v=d['dist-tags']&&d['dist-tags'].latest
356
+ if(v){document.querySelectorAll('[data-npm-version]').forEach(function(e){e.textContent='v'+v})}
357
+ }).catch(function(){})
358
+ </script>
359
+ </body>
360
+ </html>
361
+ `
362
+
363
+ mkdirSync(OUT, { recursive: true })
364
+ writeFileSync(join(OUT, 'index.html'), html)
365
+ writeFileSync(join(OUT, '.nojekyll'), '')
366
+ console.log(`site: ${join(OUT, 'index.html')} (${patterns.length} pattern(s), pkg v${pkg.version})`)