agent-templates 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -2
- package/package.json +1 -1
- package/patterns/three-agent-architect-builder-reviewer/README.md +5 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-all.md +2 -2
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-milestone.md +1 -1
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/deliver-ticket.mjs +219 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/settings.json +7 -0
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/nightly-issues.js +3 -1
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/run-milestone.js +26 -9
- package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/start-all.js +9 -2
- package/patterns/three-agent-architect-builder-reviewer/scaffold/INSTALL.md +2 -2
- package/scripts/adopt.mjs +20 -0
- package/scripts/build-site.mjs +229 -127
package/CLAUDE.md
CHANGED
|
@@ -48,12 +48,13 @@ patterns/three-agent-architect-builder-reviewer/
|
|
|
48
48
|
├── INSTALL.md # how to drop the scaffold into a target repo
|
|
49
49
|
├── claude-md-snippet.md # block to append to the target repo's CLAUDE.md
|
|
50
50
|
└── .claude/
|
|
51
|
-
├── settings.json # PreToolUse write guard
|
|
51
|
+
├── settings.json # PreToolUse write guard + deterministic-script allowlist
|
|
52
52
|
├── hooks/
|
|
53
53
|
│ └── guard-main-session-writes.mjs
|
|
54
54
|
├── scripts/
|
|
55
55
|
│ ├── publish-tickets.mjs
|
|
56
|
-
│
|
|
56
|
+
│ ├── milestone-dag.mjs
|
|
57
|
+
│ └── deliver-ticket.mjs
|
|
57
58
|
├── workflows/
|
|
58
59
|
│ ├── run-milestone.js
|
|
59
60
|
│ ├── start-all.js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Catalog of multi-agent development architecture patterns - design write-ups plus drop-in scaffolding (agents, slash commands, workflows, templates).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Horace Hou",
|
|
@@ -67,6 +67,8 @@ Figures are transcribed from the team-finalized specification dated 2026-07-17 (
|
|
|
67
67
|
| **Nightly grinding** — the sweep re-attempts the same unfixable issue night after night, burning tokens | Design risk of the nightly loop (not a measured incident) | Unsolved/failed issues get `nightly:escalated` and are excluded from later sweeps until a human clears the label; `maxIssues` caps each night's spend | 2026-07-17 |
|
|
68
68
|
| **Autonomous misjudgment of "invalid"** — the sweep wrongly declares a real issue invalid and it gets ignored | Design risk | Invalid verdicts only **label** (`triage:invalid`) and comment with evidence — never auto-close; the morning human is the final judge; triage is instructed to prefer `needs-human` when uncertain | 2026-07-17 |
|
|
69
69
|
| **CRLF workflow scripts rejected on Windows** — the Workflow tool's permission layer refuses scripts containing `\r` ("control characters that would be hidden in the approval dialog"), so `/start-milestone` and `/nightly-issues` cannot launch their workflows when git autocrlf has rewritten the installed `.js` files | `[internal]` — **first field observation**: Ruihang2017/Groundwork, Windows 11, Claude Code, 2026-07-18 (catalog issue #21; workaround used: inline `script` param with LF) | Fixed 2026-07-18: catalog ships `.gitattributes` (`* text=auto eol=lf`), `adopt.mjs` normalizes all text installs to LF regardless of source state, and E2E guards assert no `\r` in runtime files (scaffold + installs + catalog self-host) | 2026-07-18 |
|
|
70
|
+
| **CRLF regression on target checkouts** — issue #21's install-time LF normalization does not survive the target repo's own git: a later `checkout`/clone with `core.autocrlf=true` re-CRLFs the installed workflow scripts, and `Workflow({name: …})` (including `start-all`'s name-resolved `run-milestone` child) is rejected again for control characters | `[internal]` — recurrence: Ruihang2017/Groundwork, Windows 11, Claude Code, 2026-07-18 (catalog issue #23; workaround used: inline `script` param with LF) | Fixed 2026-07-18: `adopt.mjs` installs/appends a `.gitattributes` in the **target repo** pinning `.claude/workflows/*.js` and `.claude/scripts/*.mjs` to `text eol=lf` (marker-checked, append-once); E2E asserts fresh install, append-to-existing, and idempotent re-run | 2026-07-18 |
|
|
71
|
+
| **Workflow `args` arrive JSON-stringified** — some harness paths deliver the Workflow tool's `args` as a JSON **string**, so `Object.assign(defaults, args)` merges nothing and launch validation throws in milliseconds (`args.tickets must be a non-empty array`, `args.modules must be a non-empty array`) | `[internal]` — Ruihang2017/Groundwork, Windows 11, Claude Code, 2026-07-18 (catalog issue #23; killed the first `run-milestone` launch in 10ms and would crash `/start-all` identically) | Fixed 2026-07-18: all three workflow scripts (`run-milestone.js`, `start-all.js`, `nightly-issues.js`) parse first — `typeof args === 'string' ? JSON.parse(args) : (args \|\| {})`; E2E runs runner + start-all scenarios with stringified args end-to-end | 2026-07-18 |
|
|
70
72
|
| **Harness-specific timeouts/failures** for these exact model+effort combinations | **None recorded yet** (as of 2026-07-17) | When observed: record here with harness name, conditions, and date | — |
|
|
71
73
|
|
|
72
74
|
## 5. Upstream / downstream integration
|
|
@@ -150,6 +152,9 @@ Install steps and usage modes: [scaffold/INSTALL.md](scaffold/INSTALL.md). Front
|
|
|
150
152
|
| 2026-07-17 | Gate 1 made concrete: `/start-milestone` = the human start signal → tickets auto-published as tracker issues (`publish-tickets.mjs`, adapted from fx-eye-tracking `create-issues.mjs`; `[<id>]` title prefix as dedupe key; smoke-tested dry-run/no-CLI/usage paths) → deterministic milestone runner (`run-milestone.js` Workflow script: stage order, reviewer freshness, bounce cap, merge policy in code). Added `templates/ticket.template.md` (adapted from fx ticket discipline). Recorded the Bash guard boundary as a known pitfall. Clarified `ultracode`: a session setting (`xhigh` + automatic workflow orchestration), not a model effort level — role efforts unchanged. | Maintainer direction (Horace Hou), 2026-07-17. fx-eye-tracking conventions read the same day (read-only). Workflow tool, `.claude/workflows/`, and `ultracode` semantics verified against live docs (workflows, claude-directory, model-config — record in `scaffold/INSTALL.md`). | Horace Hou (direction) / Claude Fable 5 (write-up) |
|
|
151
153
|
| 2026-07-18 | **`/start-all` shipped** (catalog issue #20; maintainer-decided semantics: failed module blocks dependents, independent branches continue in `autonomous`, any non-CLEAR stops everything in `supervised`, no cost cap, resume via closed-issue filtering): deterministic DAG script (`milestone-dag.mjs`, ticket `blocked_by` as ground truth, cycles/dangling refs are hard errors) + `start-all` workflow composing `run-milestone` per module + `/start-all` command. 19 new E2E checks (DAG fixtures incl. cycle; module-policy scenarios via stubbed child workflows). Per-ticket stages unchanged. | Maintainer answers on issue #20, 2026-07-18. | Horace Hou (decisions) / Claude Fable 5 (build) |
|
|
152
154
|
| 2026-07-18 | **First field bug fixed** (catalog issue #21, reported from Ruihang2017/Groundwork through the upstream feedback channel): CRLF-rewritten workflow scripts are rejected by the Workflow tool on Windows. Fix: repo-wide `.gitattributes` (`eol=lf`), working tree renormalized (67 files), `adopt.mjs` installs all text as LF, E2E `\r` guards added. Recorded as the pattern's first true `[internal]` pitfall observation. | Field report (Horace Hou via Groundwork), 2026-07-18. | Horace Hou (report) / Claude Fable 5 (fix) |
|
|
155
|
+
| 2026-07-19 | **Delivery made deterministic** (catalog issue #26, field report: harness safety classifiers blocked the agent-run deliver stage 3/3 after journaled CLEARs): new `.claude/scripts/deliver-ticket.mjs` — clean-tree guard, `--no-ff` merge (idempotent on re-runs, conflicts abort clean), push, tracker close with post-close verification (`--issue` or `[<id>]` title-prefix lookup), deterministic DoD, `DELIVER-SUMMARY-JSON` contract; run-milestone's deliver agent reduced to executing that one command and relaying its summary; `settings.json` gained explicit allow rules for the three deterministic scripts. E2E: new suite-deliver (12 scenarios on real temp git repos + fake CLIs). The tracker close only happens after merge AND push actually landed — a closed issue is what resume filtering treats as delivered, so closing on a failed delivery would silently drop the ticket. One DoD item relocated: the post-merge test run executes deterministically when `testCmd` is supplied (workflow arg, forwarded as `--test-cmd`; `/start-all` passes it through) and otherwise stays with `/verify-delivery` — builder and reviewer both already ran tests on the branch. Per-ticket stages before deliver, and the model/effort table, unchanged. | Field report (Horace Hou via Groundwork), 2026-07-18; fix direction proposed in the issue itself. | Horace Hou (report) / Claude Fable 5 (fix) |
|
|
156
|
+
| 2026-07-19 | **Field bug #3 fixed** (catalog issue #27, reported from Ruihang2017/Groundwork2): `/start-all` step 3 never stated that `modules[].tickets` elements must be `{id, path, issue}` objects joined from two sources (DAG plan order + per-module `PUBLISH-SUMMARY-JSON`) — the DAG JSON alone emits bare id strings, which run-milestone's validator rejects. Command doc now names the exact shape and the join; `/start-milestone` already documented it. (The issue's other defect — stringified Workflow `args` — was already fixed under #23 / v0.2.1.) | Field report (Horace Hou via Groundwork2), 2026-07-18. | Horace Hou (report) / Claude Fable 5 (fix) |
|
|
157
|
+
| 2026-07-18 | **Field bug #2 fixed** (catalog issue #23, same reporter channel): (a) #21's install-time LF fix did not survive target-side git checkouts — `adopt.mjs` now installs/appends a target-repo `.gitattributes` pinning `.claude/workflows/*.js` + `.claude/scripts/*.mjs` to `eol=lf` (marker-checked, append-once); (b) Workflow `args` can arrive JSON-stringified — all three workflow scripts gained the `typeof args === 'string' ? JSON.parse(args) : (args \|\| {})` shim. Both recorded as §4 pitfalls; E2E extended (stringified-args runner + start-all scenarios; `.gitattributes` install/append/idempotency). Per-ticket stages and model/effort table unchanged. | Field report (Horace Hou via Groundwork), 2026-07-18. | Horace Hou (report) / Claude Fable 5 (fix) |
|
|
153
158
|
| 2026-07-17 | **Catalog site**: claymorphic GitHub Pages page generated from pattern data (`scripts/build-site.mjs` — parses pattern README metadata/§1/§3 + package.json; live npm-version fetch with build-time fallback; deployed via `gh-pages` branch). Style spec + generated-not-handwritten rationale in catalog issue #17. | Maintainer direction (Horace Hou) with reference designs, 2026-07-17. | Horace Hou (direction) / Claude Fable 5 (build) |
|
|
154
159
|
| 2026-07-17 | **Released**: catalog published to public npm as `agent-templates` under MIT (maintainer decisions: MIT + personal ownership + publish, 2026-07-17 — catalog issue #15). README carries the scaffold-output attribution carve-out. Adoption command is now `npx agent-templates@latest adopt …`. | Maintainer decision (Horace Hou), 2026-07-17. | Horace Hou (decision) / Claude Fable 5 (release) |
|
|
155
160
|
| 2026-07-17 | **Registry decision — public npm over GitHub Packages** (repo is public; goal is public sharing): GH Packages requires a token even to install public packages (`[official]` GitHub docs, verified live 2026-07-17) and is scope-only; npm allows anonymous installs and the bare name `agent-templates` was unclaimed (registry 404, same day). Publish itself is gated on maintainer decisions — license (repo has none), npm account/name — tracked in catalog issue #13; `package.json` carries the publish-prep (`files`, `repository`) with `private: true` until then. | Maintainer direction (Horace Hou) + live verification, 2026-07-17. | Horace Hou (direction) / Claude Fable 5 (write-up) |
|
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-all.md
CHANGED
|
@@ -8,8 +8,8 @@ Arguments: `$ARGUMENTS` — optional mode override (else the repo's declared Ope
|
|
|
8
8
|
Typing this command is the human Gate 1 sign-off **for the whole PRD** — every module, in dependency order. Prefer `/start-milestone` per module when you still want module-boundary checkpoints. Execute in order:
|
|
9
9
|
|
|
10
10
|
1. **Compute the plan.** Run `node .claude/scripts/milestone-dag.mjs` and show its output (module order, per-module dependencies). STOP on any error — a dangling `blocked_by` or a cycle is a spec defect for the Architect/human, not something to patch here.
|
|
11
|
-
2. **Publish all tickets.** For each module in DAG order: `node .claude/scripts/publish-tickets.mjs docs/prd/<module>` (dry-run, then `--create`; STOP on `error` entries). Then, per module, **filter out tickets whose issues are already closed** (closed = delivered by an earlier run — this makes `/start-all` re-runs resume: fully-closed modules pass an empty ticket list and are marked already-complete).
|
|
12
|
-
3. **Launch the driver.** Call the **Workflow** tool with `name: "start-all"` and `args: { modules: [{name, dependsOn, tickets}] in DAG order, mode, defaultBranch, platform }`. This command's instruction is your authorization to use the Workflow tool. Failure policy is enforced in the workflow: failed modules block their dependents; independent branches continue in `autonomous`; anything short of a CLEAR stops everything in `supervised`.
|
|
11
|
+
2. **Publish all tickets.** For each module in DAG order: `node .claude/scripts/publish-tickets.mjs docs/prd/<module>` (dry-run, then `--create`; STOP on `error` entries). Keep each module's `PUBLISH-SUMMARY-JSON` line — step 3 needs it. Then, per module, **filter out tickets whose issues are already closed** (closed = delivered by an earlier run — this makes `/start-all` re-runs resume: fully-closed modules pass an empty ticket list and are marked already-complete).
|
|
12
|
+
3. **Launch the driver.** Call the **Workflow** tool with `name: "start-all"` and `args: { modules: [{name, dependsOn, tickets}] in DAG order, mode, defaultBranch, platform }`. Each element of `tickets` MUST be an object `{id, path, issue}` — the exact shape run-milestone validates (issue #27; bare id strings fail). Build it by joining the two step outputs per module: ticket ids and their order come from the DAG plan (step 1), `path` and `issue` come from that module's `PUBLISH-SUMMARY-JSON` (step 2), matched by `id`. Add `testCmd` (the repo's test command, if its CLAUDE.md declares one) so the deterministic deliver script re-runs tests on the merged default branch as part of DoD. This command's instruction is your authorization to use the Workflow tool. Failure policy is enforced in the workflow: failed modules block their dependents; independent branches continue in `autonomous`; anything short of a CLEAR stops everything in `supervised`.
|
|
13
13
|
4. **Relay the final report verbatim** — per module: `completed` / `already-complete` / `paused-for-merge` (supervised: tell the human to merge, then re-run this command) / `failed` (with the failing ticket + stage) / `skipped-dependency` / `not-started`. Escalations inside a module carry through from run-milestone unchanged.
|
|
14
14
|
|
|
15
15
|
While the workflow runs you are an observer. Do not do stage work in parallel, do not "help" a slow stage, do not edit files.
|
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/commands/start-milestone.md
CHANGED
|
@@ -9,7 +9,7 @@ Typing this command **is** the human Gate 1 sign-off: the sub-PRD and tickets ar
|
|
|
9
9
|
|
|
10
10
|
1. **Verify Gate 1 inputs.** `MODULE/README.md` (the sub-PRD) exists and `MODULE/tickets/*.md` is non-empty; every ticket has the required frontmatter (see `templates/ticket.template.md`). Anything missing → STOP and list exactly what is missing. Do not fix it yourself — that is Architect-stage work.
|
|
11
11
|
2. **Publish tickets as tracker issues.** Run `node .claude/scripts/publish-tickets.mjs MODULE` (dry-run) and show the mapping. If the summary contains `error` entries → STOP and report them. Otherwise re-run with `--create` (idempotent — the `[<id>]` title prefix dedupes, so re-running is safe), and again STOP on any `error` entries.
|
|
12
|
-
3. **Launch the pipeline.** Parse the final `PUBLISH-SUMMARY-JSON` line into `tickets: [{id, path, issue}]`. **Filter out tickets whose issue is already closed** (check via `gh`/`glab` — closed means delivered by an earlier run; this filter is what makes re-runs after supervised pauses or crashes safe). Determine the Operating mode, then call the **Workflow** tool with `name: "run-milestone"` and `args: { tickets, mode, defaultBranch, platform }
|
|
12
|
+
3. **Launch the pipeline.** Parse the final `PUBLISH-SUMMARY-JSON` line into `tickets: [{id, path, issue}]`. **Filter out tickets whose issue is already closed** (check via `gh`/`glab` — closed means delivered by an earlier run; this filter is what makes re-runs after supervised pauses or crashes safe). Determine the Operating mode, then call the **Workflow** tool with `name: "run-milestone"` and `args: { tickets, mode, defaultBranch, platform }` — plus `testCmd` (the repo's test command, if its CLAUDE.md declares one) so the deterministic deliver script re-runs tests on the merged default branch as part of DoD. This command's instruction is your authorization to use the Workflow tool.
|
|
13
13
|
4. **Relay the final report verbatim** — per ticket: `delivered` · `awaiting-human-merge` · `escalated` (stage `review`, `reviewer-failed`, or `bounce-fix-build`) · `failed` · `delivery-incomplete`, plus `notStarted` when the run stopped early. Escalated and failed items go to the human; do not fix them inline (the write guard will hold you to that). In `supervised` mode the run stops after each CLEAR verdict — tell the human to merge, then re-run this command to continue.
|
|
14
14
|
|
|
15
15
|
While the workflow runs you are an observer. Do not do stage work in parallel, do not "help" a slow stage, do not edit files.
|
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/scripts/deliver-ticket.mjs
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// deliver-ticket.mjs — the ONLY sanctioned delivery path for the three-agent
|
|
3
|
+
// pattern (catalog issue #26). Delivery used to be a generic LLM agent prompted
|
|
4
|
+
// to merge + close + verify; harness safety classifiers repeatedly blocked that
|
|
5
|
+
// agent even after a journaled CLEAR (3/3 in one field session), stranding
|
|
6
|
+
// CLEAR-verdict tickets. Like issue creation (publish-tickets.mjs), delivery is
|
|
7
|
+
// now deterministic: the merge policy stays mechanically checkable and the only
|
|
8
|
+
// permission surface is this one command.
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// node .claude/scripts/deliver-ticket.mjs --id <ticket-id> --branch <branch>
|
|
12
|
+
// [--default-branch main] [--issue <n>] [--platform gh|glab]
|
|
13
|
+
// [--test-cmd "<command>"]
|
|
14
|
+
//
|
|
15
|
+
// Does, in order (every step lands in the summary):
|
|
16
|
+
// 1. refuses to run on a dirty working tree
|
|
17
|
+
// 2. checks out <default-branch>; merges <branch> with --no-ff (an
|
|
18
|
+
// already-merged branch is recognized and succeeds idempotently);
|
|
19
|
+
// a conflict aborts the merge and leaves the tree clean
|
|
20
|
+
// 3. pushes <default-branch> to origin (required only when origin exists)
|
|
21
|
+
// 4. closes the tracker issue (--issue <n>, else looked up by the "[<id>]"
|
|
22
|
+
// title prefix) and VERIFIES it is actually closed afterwards
|
|
23
|
+
// 5. deterministic DoD: plan file exists (docs/plans/<id>.md), merged,
|
|
24
|
+
// pushed, issue closed, plus --test-cmd green when supplied (post-merge
|
|
25
|
+
// test runs otherwise stay with /verify-delivery)
|
|
26
|
+
//
|
|
27
|
+
// Last line of stdout is machine-readable for run-milestone:
|
|
28
|
+
// DELIVER-SUMMARY-JSON: {"id","branch","merged","issueClosed","dodPassed","checks":{...},"notes"}
|
|
29
|
+
// Exit codes: 0 = definitive summary printed (flags may still be false);
|
|
30
|
+
// 1 = bad invocation or unexpected internal error.
|
|
31
|
+
|
|
32
|
+
import { execFileSync, spawnSync } from 'node:child_process'
|
|
33
|
+
import { existsSync } from 'node:fs'
|
|
34
|
+
import { join } from 'node:path'
|
|
35
|
+
|
|
36
|
+
const argv = process.argv.slice(2)
|
|
37
|
+
const opt = (name) => {
|
|
38
|
+
const i = argv.indexOf('--' + name)
|
|
39
|
+
if (i === -1) return ''
|
|
40
|
+
const v = argv[i + 1]
|
|
41
|
+
return v && !v.startsWith('--') ? v : ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ID = opt('id')
|
|
45
|
+
const BRANCH = opt('branch')
|
|
46
|
+
const DEFAULT_BRANCH = opt('default-branch') || 'main'
|
|
47
|
+
const ISSUE_ARG = opt('issue')
|
|
48
|
+
const PLATFORM = opt('platform') || 'gh'
|
|
49
|
+
const TEST_CMD = opt('test-cmd')
|
|
50
|
+
|
|
51
|
+
if (!ID || !BRANCH) {
|
|
52
|
+
console.error('usage: node deliver-ticket.mjs --id <ticket-id> --branch <branch> [--default-branch main] [--issue <n>] [--platform gh|glab] [--test-cmd "<command>"]')
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
55
|
+
if (!/^[A-Za-z0-9._-]+$/.test(ID)) {
|
|
56
|
+
console.error(`invalid --id (allowed: letters, digits, . _ -): ${ID}`)
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
if (!/^[A-Za-z0-9/._-]+$/.test(BRANCH) || !/^[A-Za-z0-9/._-]+$/.test(DEFAULT_BRANCH)) {
|
|
60
|
+
console.error('invalid --branch / --default-branch (allowed: letters, digits, / . _ -)')
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
if (BRANCH === DEFAULT_BRANCH) {
|
|
64
|
+
console.error(`--branch must differ from --default-branch (got ${BRANCH} for both) — nothing to deliver`)
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
if (PLATFORM !== 'gh' && PLATFORM !== 'glab') {
|
|
68
|
+
console.error(`unknown platform: ${PLATFORM} (expected gh or glab)`)
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const run = (bin, args, opts = {}) => execFileSync(bin, args, { encoding: 'utf8', ...opts })
|
|
73
|
+
const git = (args, opts = {}) => run('git', args, opts)
|
|
74
|
+
const errText = (e) => String((e && (e.stderr || e.stdout || e.message)) || e).trim()
|
|
75
|
+
const firstLine = (s) => String(s).trim().split('\n')[0]
|
|
76
|
+
const lastLine = (s) => String(s).trim().split('\n').filter(Boolean).pop() || ''
|
|
77
|
+
const tryGit = (args) => {
|
|
78
|
+
try { return { ok: true, out: git(args, { stdio: ['ignore', 'pipe', 'pipe'] }) } } catch (e) { return { ok: false, out: errText(e) } }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// GH_BIN / GLAB_BIN env overrides (same mechanism as publish-tickets.mjs) for
|
|
82
|
+
// non-PATH binaries and test doubles, e.g. GH_BIN="node tools/fake-gh.mjs".
|
|
83
|
+
const cli = (args, opts = {}) => {
|
|
84
|
+
const raw = PLATFORM === 'gh' ? process.env.GH_BIN || 'gh' : process.env.GLAB_BIN || 'glab'
|
|
85
|
+
const parts = raw.split(' ')
|
|
86
|
+
return run(parts[0], [...parts.slice(1), ...args], opts)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const checks = { planExists: false, alreadyMerged: false, merged: false, pushRequired: false, pushed: false, issueClosed: false, testsPassed: null }
|
|
90
|
+
const notes = []
|
|
91
|
+
const note = (line) => { notes.push(line); console.log(' (note) ' + line) }
|
|
92
|
+
|
|
93
|
+
const finish = (code) => {
|
|
94
|
+
const dodPassed =
|
|
95
|
+
checks.planExists &&
|
|
96
|
+
checks.merged &&
|
|
97
|
+
checks.issueClosed &&
|
|
98
|
+
(!checks.pushRequired || checks.pushed) &&
|
|
99
|
+
(TEST_CMD ? checks.testsPassed === true : true)
|
|
100
|
+
const summary = { id: ID, branch: BRANCH, merged: checks.merged, issueClosed: checks.issueClosed, dodPassed, checks, notes: notes.join('; ') }
|
|
101
|
+
console.log('DELIVER-SUMMARY-JSON: ' + JSON.stringify(summary))
|
|
102
|
+
process.exit(code)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// 0. operate from the repo root regardless of cwd (plan path + command shape)
|
|
107
|
+
process.chdir(git(['rev-parse', '--show-toplevel']).trim())
|
|
108
|
+
|
|
109
|
+
// 1. clean tree — merging over uncommitted work is never sanctioned
|
|
110
|
+
if (git(['status', '--porcelain']).trim()) {
|
|
111
|
+
note('working tree not clean — refusing to merge')
|
|
112
|
+
finish(0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2. refs must exist
|
|
116
|
+
for (const ref of [BRANCH, DEFAULT_BRANCH]) {
|
|
117
|
+
if (!tryGit(['rev-parse', '--verify', '--quiet', ref]).ok) {
|
|
118
|
+
note(`ref not found: ${ref}`)
|
|
119
|
+
finish(0)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 3. merge --no-ff into the default branch (idempotent on re-runs)
|
|
124
|
+
git(['checkout', DEFAULT_BRANCH], { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
125
|
+
if (tryGit(['merge-base', '--is-ancestor', BRANCH, 'HEAD']).ok) {
|
|
126
|
+
checks.alreadyMerged = true
|
|
127
|
+
checks.merged = true
|
|
128
|
+
console.log(`= merged ${BRANCH} is already contained in ${DEFAULT_BRANCH}`)
|
|
129
|
+
} else {
|
|
130
|
+
const m = tryGit(['merge', '--no-ff', '--no-edit', '-m', `merge: [${ID}] ${BRANCH} -> ${DEFAULT_BRANCH} (pipeline CLEAR)`, BRANCH])
|
|
131
|
+
if (m.ok) {
|
|
132
|
+
checks.merged = true
|
|
133
|
+
console.log(`+ merged ${BRANCH} -> ${DEFAULT_BRANCH} (--no-ff)`)
|
|
134
|
+
} else {
|
|
135
|
+
tryGit(['merge', '--abort'])
|
|
136
|
+
note(`merge failed (aborted, tree left clean): ${firstLine(m.out)}`)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 4. push (only meaningful when an origin remote exists)
|
|
141
|
+
checks.pushRequired = tryGit(['remote', 'get-url', 'origin']).ok
|
|
142
|
+
if (checks.merged && checks.pushRequired) {
|
|
143
|
+
const p = tryGit(['push', 'origin', DEFAULT_BRANCH])
|
|
144
|
+
if (p.ok) {
|
|
145
|
+
checks.pushed = true
|
|
146
|
+
console.log(`+ pushed ${DEFAULT_BRANCH} -> origin`)
|
|
147
|
+
} else {
|
|
148
|
+
note(`push failed: ${lastLine(p.out)}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 5. close the tracker issue and verify the transition (never assume auto-close).
|
|
153
|
+
// ONLY after the work actually landed: a closed issue is what resume filtering
|
|
154
|
+
// treats as "delivered by an earlier run", so closing on a failed merge/push
|
|
155
|
+
// would silently drop the ticket from every future run.
|
|
156
|
+
const landed = checks.merged && (!checks.pushRequired || checks.pushed)
|
|
157
|
+
let issueNum = ISSUE_ARG ? Number(ISSUE_ARG) : null
|
|
158
|
+
if (issueNum !== null && (!Number.isInteger(issueNum) || issueNum < 1)) {
|
|
159
|
+
note(`invalid --issue value: ${ISSUE_ARG}`)
|
|
160
|
+
issueNum = null
|
|
161
|
+
}
|
|
162
|
+
if (!landed) {
|
|
163
|
+
note('skipping tracker close — merge/push did not complete, ticket is NOT delivered')
|
|
164
|
+
} else {
|
|
165
|
+
if (!issueNum) {
|
|
166
|
+
// fall back to the "[<id>]" title-prefix convention (publish-tickets.mjs key)
|
|
167
|
+
try {
|
|
168
|
+
if (PLATFORM === 'gh') {
|
|
169
|
+
const list = JSON.parse(cli(['issue', 'list', '--state', 'all', '--limit', '1000', '--json', 'number,title']))
|
|
170
|
+
const hit = list.find((i) => String(i.title).startsWith(`[${ID}]`))
|
|
171
|
+
if (hit) issueNum = hit.number
|
|
172
|
+
} else {
|
|
173
|
+
const text = cli(['issue', 'list', '--all'])
|
|
174
|
+
const line = text.split('\n').find((l) => l.includes(`[${ID}]`))
|
|
175
|
+
const m = line && line.match(/#(\d+)\b/)
|
|
176
|
+
if (m) issueNum = Number(m[1])
|
|
177
|
+
}
|
|
178
|
+
} catch (e) {
|
|
179
|
+
note(`issue lookup failed: ${firstLine(errText(e))}`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!issueNum) {
|
|
183
|
+
note(`no tracker issue found for [${ID}]`)
|
|
184
|
+
} else {
|
|
185
|
+
try {
|
|
186
|
+
cli(['issue', 'close', String(issueNum), ...(PLATFORM === 'gh' ? ['--comment', `Delivered: ${BRANCH} merged to ${DEFAULT_BRANCH} (run-milestone, CLEAR verdict).`] : [])])
|
|
187
|
+
} catch (e) {
|
|
188
|
+
note(`issue close command failed: ${firstLine(errText(e))}`) // verification below still decides
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
if (PLATFORM === 'gh') {
|
|
192
|
+
const view = JSON.parse(cli(['issue', 'view', String(issueNum), '--json', 'state']))
|
|
193
|
+
checks.issueClosed = String(view.state).toUpperCase() === 'CLOSED'
|
|
194
|
+
} else {
|
|
195
|
+
// text scrape (older glab has no JSON): only trust a "closed" in the header lines
|
|
196
|
+
checks.issueClosed = cli(['issue', 'view', String(issueNum)]).split('\n').slice(0, 5).some((l) => /\bclosed\b/i.test(l))
|
|
197
|
+
}
|
|
198
|
+
console.log((checks.issueClosed ? '+ closed ' : ' (note) NOT closed: ') + `issue #${issueNum}`)
|
|
199
|
+
if (!checks.issueClosed) notes.push(`issue #${issueNum} still open after close attempt`)
|
|
200
|
+
} catch (e) {
|
|
201
|
+
note(`issue state verification failed: ${firstLine(errText(e))}`)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 6. deterministic DoD inputs
|
|
207
|
+
checks.planExists = existsSync(join('docs', 'plans', `${ID}.md`))
|
|
208
|
+
if (!checks.planExists) note(`plan file missing: docs/plans/${ID}.md`)
|
|
209
|
+
if (TEST_CMD) {
|
|
210
|
+
const t = spawnSync(TEST_CMD, { shell: true, encoding: 'utf8' })
|
|
211
|
+
checks.testsPassed = t.status === 0
|
|
212
|
+
if (!checks.testsPassed) note(`--test-cmd failed (exit ${t.status}): ${String(t.stdout || t.stderr || '').trim().split('\n').slice(-3).join(' | ')}`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
finish(0)
|
|
216
|
+
} catch (e) {
|
|
217
|
+
note(`unexpected error: ${firstLine(errText(e))}`)
|
|
218
|
+
finish(1)
|
|
219
|
+
}
|
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/nightly-issues.js
CHANGED
|
@@ -22,7 +22,9 @@ export const meta = {
|
|
|
22
22
|
// end — comments, labels, and the "Nightly report <date>" issue the human reads
|
|
23
23
|
// over morning email (tracker notifications).
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// args may arrive as a JSON string depending on the harness (catalog issue #23)
|
|
26
|
+
const parsedArgs = typeof args === 'string' ? JSON.parse(args) : (args || {})
|
|
27
|
+
const cfg = Object.assign({ maxIssues: 5, defaultBranch: 'main', platform: 'gh' }, parsedArgs)
|
|
26
28
|
if (!Array.isArray(cfg.issues)) throw new Error('args.issues must be an array (may be empty)')
|
|
27
29
|
if (typeof cfg.reportDate !== 'string' || !cfg.reportDate) throw new Error('args.reportDate is required (the caller stamps the date)')
|
|
28
30
|
if (!Number.isInteger(cfg.maxIssues) || cfg.maxIssues < 1) throw new Error('args.maxIssues must be an integer >= 1')
|
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/run-milestone.js
CHANGED
|
@@ -13,7 +13,9 @@ export const meta = {
|
|
|
13
13
|
// defaultBranch: 'main', // optional, default 'main'
|
|
14
14
|
// maxBounces: 2, // optional, default 2
|
|
15
15
|
// continueOnFailure: false, // optional; default fail-fast (tickets may depend on earlier ones)
|
|
16
|
-
// platform: 'gh' | 'glab'
|
|
16
|
+
// platform: 'gh' | 'glab', // tracker CLI for the deliver step, default 'gh'
|
|
17
|
+
// testCmd: 'npm test' // optional; forwarded to deliver-ticket.mjs --test-cmd so the
|
|
18
|
+
// // deterministic DoD re-runs tests on the merged default branch
|
|
17
19
|
// }
|
|
18
20
|
//
|
|
19
21
|
// Guarantees encoded below (each one exists because prose alone failed before):
|
|
@@ -23,9 +25,11 @@ export const meta = {
|
|
|
23
25
|
// - supervised mode STOPS the run after each CLEAR (later tickets may depend on the merge);
|
|
24
26
|
// re-running /start-milestone continues — already-closed issues are filtered out upstream
|
|
25
27
|
|
|
28
|
+
// args may arrive as a JSON string depending on the harness (catalog issue #23)
|
|
29
|
+
const parsedArgs = typeof args === 'string' ? JSON.parse(args) : (args || {})
|
|
26
30
|
const cfg = Object.assign(
|
|
27
31
|
{ maxBounces: 2, continueOnFailure: false, defaultBranch: 'main', platform: 'gh' },
|
|
28
|
-
|
|
32
|
+
parsedArgs
|
|
29
33
|
)
|
|
30
34
|
if (!Array.isArray(cfg.tickets) || cfg.tickets.length === 0) {
|
|
31
35
|
throw new Error('args.tickets must be a non-empty array of {id, path, issue}')
|
|
@@ -34,10 +38,17 @@ for (const t of cfg.tickets) {
|
|
|
34
38
|
if (!t || typeof t.id !== 'string' || !t.id || typeof t.path !== 'string' || !t.path) {
|
|
35
39
|
throw new Error('every ticket needs a non-empty string id and path; got: ' + JSON.stringify(t))
|
|
36
40
|
}
|
|
41
|
+
// the id is composed into branch names and the deliver command — keep it boring
|
|
42
|
+
if (!/^[A-Za-z0-9._-]+$/.test(t.id)) {
|
|
43
|
+
throw new Error('ticket id must match [A-Za-z0-9._-]+; got: ' + t.id)
|
|
44
|
+
}
|
|
37
45
|
}
|
|
38
46
|
if (cfg.mode !== 'supervised' && cfg.mode !== 'autonomous') {
|
|
39
47
|
throw new Error("args.mode must be 'supervised' or 'autonomous'")
|
|
40
48
|
}
|
|
49
|
+
if (cfg.testCmd !== undefined && (typeof cfg.testCmd !== 'string' || !cfg.testCmd || cfg.testCmd.includes('"'))) {
|
|
50
|
+
throw new Error('args.testCmd must be a non-empty string without double quotes when provided')
|
|
51
|
+
}
|
|
41
52
|
if (!Number.isInteger(cfg.maxBounces) || cfg.maxBounces < 0) {
|
|
42
53
|
throw new Error('args.maxBounces must be an integer >= 0')
|
|
43
54
|
}
|
|
@@ -192,15 +203,21 @@ for (const t of cfg.tickets) {
|
|
|
192
203
|
continue
|
|
193
204
|
}
|
|
194
205
|
|
|
195
|
-
log('[' + t.id + '] deliver: merge + close issue + DoD')
|
|
206
|
+
log('[' + t.id + '] deliver: merge + close issue + DoD (deterministic script)')
|
|
207
|
+
// Delivery is a deterministic script, not agent judgment (catalog issue #26):
|
|
208
|
+
// harness safety classifiers blocked agent-run merges even after a journaled
|
|
209
|
+
// CLEAR. The agent below only executes the sanctioned command and relays its
|
|
210
|
+
// machine-readable summary — same discipline as publish-tickets.mjs for issues.
|
|
211
|
+
const deliverCmd = 'node .claude/scripts/deliver-ticket.mjs --id ' + t.id + ' --branch ' + branch +
|
|
212
|
+
' --default-branch ' + cfg.defaultBranch + ' --platform ' + cfg.platform + (t.issue ? ' --issue ' + t.issue : '') +
|
|
213
|
+
(cfg.testCmd ? ' --test-cmd "' + cfg.testCmd + '"' : '')
|
|
196
214
|
const delivery = await agent(
|
|
197
215
|
'Delivery step (autonomous mode — pre-authorized by the human Gate 1 start signal). ' +
|
|
198
|
-
'
|
|
199
|
-
|
|
200
|
-
'
|
|
201
|
-
'
|
|
202
|
-
'
|
|
203
|
-
'Never report an item you did not check. Return merged, issueClosed, dodPassed, notes.',
|
|
216
|
+
'Delivery is DETERMINISTIC: from the repo root, run EXACTLY this command and let it do all git and tracker work: ' +
|
|
217
|
+
deliverCmd + ' — the script is the only sanctioned delivery path; do not merge, push, close issues, or retry pieces yourself. ' +
|
|
218
|
+
'Parse the DELIVER-SUMMARY-JSON line it prints last and return merged, issueClosed, dodPassed EXACTLY as reported there, ' +
|
|
219
|
+
'with notes = its notes field plus anything unusual you observed. ' +
|
|
220
|
+
'If the command cannot run or prints no DELIVER-SUMMARY-JSON, return all three as false with the output tail in notes.',
|
|
204
221
|
{ label: 'deliver:' + t.id, phase: P, schema: DELIVERY }
|
|
205
222
|
)
|
|
206
223
|
// Delivered requires ALL THREE flags — a hallucinated dodPassed alone must not count.
|
package/patterns/three-agent-architect-builder-reviewer/scaffold/.claude/workflows/start-all.js
CHANGED
|
@@ -13,7 +13,8 @@ export const meta = {
|
|
|
13
13
|
// // so an [] tickets list means "module already complete" (resume semantics)
|
|
14
14
|
// mode: 'supervised' | 'autonomous',
|
|
15
15
|
// defaultBranch: 'main', // optional
|
|
16
|
-
// platform: 'gh' | 'glab'
|
|
16
|
+
// platform: 'gh' | 'glab', // optional, default 'gh'
|
|
17
|
+
// testCmd: 'npm test' // optional; passed through to run-milestone (deliver DoD test run)
|
|
17
18
|
// }
|
|
18
19
|
//
|
|
19
20
|
// Failure policy (maintainer decisions, catalog issue #20, 2026-07-18):
|
|
@@ -23,7 +24,9 @@ export const meta = {
|
|
|
23
24
|
// issues are filtered upstream, so completed modules arrive as already-complete);
|
|
24
25
|
// - no cost ceiling.
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// args may arrive as a JSON string depending on the harness (catalog issue #23)
|
|
28
|
+
const parsedArgs = typeof args === 'string' ? JSON.parse(args) : (args || {})
|
|
29
|
+
const cfg = Object.assign({ defaultBranch: 'main', platform: 'gh' }, parsedArgs)
|
|
27
30
|
if (!Array.isArray(cfg.modules) || cfg.modules.length === 0) {
|
|
28
31
|
throw new Error('args.modules must be a non-empty array of {name, dependsOn, tickets}')
|
|
29
32
|
}
|
|
@@ -36,6 +39,9 @@ if (cfg.mode !== 'supervised' && cfg.mode !== 'autonomous') {
|
|
|
36
39
|
throw new Error("args.mode must be 'supervised' or 'autonomous'")
|
|
37
40
|
}
|
|
38
41
|
if (cfg.platform !== 'gh' && cfg.platform !== 'glab') throw new Error("args.platform must be 'gh' or 'glab'")
|
|
42
|
+
if (cfg.testCmd !== undefined && (typeof cfg.testCmd !== 'string' || !cfg.testCmd || cfg.testCmd.includes('"'))) {
|
|
43
|
+
throw new Error('args.testCmd must be a non-empty string without double quotes when provided')
|
|
44
|
+
}
|
|
39
45
|
|
|
40
46
|
const state = {} // name -> 'completed' | 'failed' | 'skipped' | 'paused'
|
|
41
47
|
const results = []
|
|
@@ -73,6 +79,7 @@ for (const m of cfg.modules) {
|
|
|
73
79
|
mode: cfg.mode,
|
|
74
80
|
defaultBranch: cfg.defaultBranch,
|
|
75
81
|
platform: cfg.platform,
|
|
82
|
+
...(cfg.testCmd ? { testCmd: cfg.testCmd } : {}),
|
|
76
83
|
})
|
|
77
84
|
} catch (e) {
|
|
78
85
|
err = e && e.message ? e.message : String(e)
|
|
@@ -8,10 +8,10 @@ From a checkout of the catalog: `node scripts/adopt.mjs three-agent-architect-bu
|
|
|
8
8
|
|
|
9
9
|
## Steps
|
|
10
10
|
|
|
11
|
-
1. Copy `.claude/` from this scaffold into the target repo root. If the target already has `.claude/settings.json`, merge the `hooks.PreToolUse` entry instead of overwriting.
|
|
11
|
+
1. Copy `.claude/` from this scaffold into the target repo root. If the target already has `.claude/settings.json`, merge the `hooks.PreToolUse` entry and the `permissions.allow` list instead of overwriting.
|
|
12
12
|
2. Install the catalog's **universal templates** (shared by all patterns — source of truth is the catalog root, not this scaffold): `templates/ticket.template.md` → the target repo's `templates/`; the platform half of `templates/tracker/` → `.github/` (ISSUE_TEMPLATE/ + PULL_REQUEST_TEMPLATE.md) or `.gitlab/` (issue_templates/ + merge_request_templates/). Then fill the PR/MR template's **Constraint check** section with the target repo's CLAUDE.md non-negotiables. Hand-written issues and pipeline PRs now share one format that triage can convert and reviewers can verify.
|
|
13
13
|
3. The write-guard hook needs Node.js ≥ 18 on PATH. It denies main-session Edit/Write with a dispatch instruction; subagent writes pass. Override switch for human-approved out-of-pipeline edits: create `.claude/allow-main-writes`, delete it afterwards — and add that path to `.gitignore`.
|
|
14
|
-
4. The tracker steps (`publish-tickets.mjs`,
|
|
14
|
+
4. The tracker steps (`publish-tickets.mjs`, `deliver-ticket.mjs`, `/verify-delivery`, the nightly sweep) need the platform CLI installed and authenticated: `gh` (GitHub) or `glab` (GitLab). The publish script autodetects the platform from the origin remote; override with `--platform gh|glab` (test doubles / non-PATH binaries: `GH_BIN` / `GLAB_BIN` env overrides). Delivery (merge `--no-ff` + push + verified issue close + deterministic DoD) is `deliver-ticket.mjs` — never an agent improvising git/tracker writes; `settings.json` carries explicit allow rules for the three deterministic scripts so unattended runs do not stall waiting for interactive approval (catalog issue #26).
|
|
15
15
|
5. Append the content of `claude-md-snippet.md` to the target repo's `CLAUDE.md`, and set the **Operating mode** line (`supervised` for a fresh adoption; `autonomous` is the target).
|
|
16
16
|
6. Ensure the docs layout the pipeline assumes exists: `docs/PRD.md`, `docs/prd/<module>/README.md` (the sub-PRD — `/start-milestone` hard-requires it), `docs/prd/<module>/tickets/` (author tickets from `templates/ticket.template.md`), `docs/adr/`, and an empty `docs/plans/`.
|
|
17
17
|
7. Check the pattern entry's expiry (README metadata table). If expired, re-verify the model/effort table against current official docs before adopting — do not copy an expired recommendation into a new project.
|
package/scripts/adopt.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// docs/PRD.md copied from a root PRD.md if present and docs/PRD.md is absent
|
|
12
12
|
// docs/prd/ docs/adr/ docs/plans/ the docs skeleton the pipeline assumes
|
|
13
13
|
// CLAUDE.md created from the snippet, or snippet appended once (marker-checked)
|
|
14
|
+
// .gitattributes eol=lf rules for scaffold runtime files, appended once (marker-checked)
|
|
14
15
|
//
|
|
15
16
|
// Idempotent: re-running skips everything that exists (--force overwrites files, never
|
|
16
17
|
// re-appends the snippet). Exit 0 = installed/verified; exit 1 = bad invocation.
|
|
@@ -174,6 +175,25 @@ if (!existsSync(claudeMd)) {
|
|
|
174
175
|
skipped++
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
// 7. .gitattributes: pin scaffold runtime files to LF. Install-time normalization
|
|
179
|
+
// (above) is not enough on Windows — a later `git checkout` with autocrlf re-CRLFs
|
|
180
|
+
// them and the Workflow tool rejects the script content (catalog issue #23).
|
|
181
|
+
const GA_MARKER = '# agent-templates: Workflow tool rejects CRLF scripts (keep LF)'
|
|
182
|
+
const GA_RULES = `${GA_MARKER}\n.claude/workflows/*.js text eol=lf\n.claude/scripts/*.mjs text eol=lf\n`
|
|
183
|
+
const gaPath = join(target, '.gitattributes')
|
|
184
|
+
if (!existsSync(gaPath)) {
|
|
185
|
+
writeFileSync(gaPath, GA_RULES)
|
|
186
|
+
console.log('+ install .gitattributes (eol=lf for scaffold runtime files)')
|
|
187
|
+
installed++
|
|
188
|
+
} else if (!readFileSync(gaPath, 'utf8').includes(GA_MARKER)) {
|
|
189
|
+
writeFileSync(gaPath, readFileSync(gaPath, 'utf8').trimEnd() + '\n\n' + GA_RULES)
|
|
190
|
+
console.log('+ append .gitattributes (eol=lf rules for scaffold runtime files)')
|
|
191
|
+
installed++
|
|
192
|
+
} else {
|
|
193
|
+
console.log('= exists .gitattributes (eol=lf rules already present)')
|
|
194
|
+
skipped++
|
|
195
|
+
}
|
|
196
|
+
|
|
177
197
|
console.log(`\nadopt: ${installed} installed, ${skipped} already present. Pattern: ${pattern}, platform: ${PLATFORM}.`)
|
|
178
198
|
console.log(`
|
|
179
199
|
NEXT STEPS (details: ${join(CATALOG, 'ADOPTING.md')})
|
package/scripts/build-site.mjs
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
// Usage: node scripts/build-site.mjs [--out <dir>] (default: site/)
|
|
7
7
|
// Output: <out>/index.html (self-contained) + <out>/.nojekyll
|
|
8
8
|
//
|
|
9
|
-
// Style:
|
|
10
|
-
//
|
|
9
|
+
// Style: clay-morphism per the approved hi-fi mock on catalog issue #19 — layered
|
|
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.
|
|
11
14
|
|
|
12
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
13
16
|
import { join } from 'node:path'
|
|
@@ -58,17 +61,7 @@ function parsePattern(dir) {
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
|
|
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 }
|
|
64
|
+
return { dir, title, status, asOf, summary, roles }
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
const patterns = readdirSync(join(ROOT, 'patterns'), { withFileTypes: true })
|
|
@@ -76,45 +69,100 @@ const patterns = readdirSync(join(ROOT, 'patterns'), { withFileTypes: true })
|
|
|
76
69
|
.map((d) => parsePattern(d.name))
|
|
77
70
|
.filter(Boolean)
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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)'],
|
|
84
100
|
}
|
|
85
|
-
const
|
|
86
|
-
const roleEmoji = (r) => ROLE_EMOJI[Object.keys(ROLE_EMOJI).find((k) => r.startsWith(k))] || '🌙'
|
|
101
|
+
const roleDot = (r) => ROLE_DOT[Object.keys(ROLE_DOT).find((k) => r.startsWith(k))] || ['#f2c44e', 'rgba(180,120,20,0.35)']
|
|
87
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.
|
|
88
144
|
const patternCards = patterns
|
|
89
145
|
.map((p) => {
|
|
90
|
-
const
|
|
146
|
+
const c = STATUS_CHIP[p.status] || STATUS_CHIP.proposed
|
|
147
|
+
const summary = esc(p.summary).replace(/→/g, '<span class="arr">→</span>')
|
|
91
148
|
return `
|
|
92
|
-
<article class="
|
|
149
|
+
<article class="panel pattern">
|
|
93
150
|
<div class="pattern-head">
|
|
94
151
|
<h3>${esc(p.title)}</h3>
|
|
95
|
-
<span class="
|
|
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>
|
|
96
153
|
</div>
|
|
97
|
-
<p class="summary">${
|
|
154
|
+
<p class="summary">${summary}</p>
|
|
98
155
|
<div class="roles">
|
|
99
|
-
${p.roles.map((r) => `<span class="
|
|
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 ')}
|
|
100
157
|
</div>
|
|
101
|
-
${p.useWhen.length ? `<ul class="usewhen">${p.useWhen.map((u) => `<li>${esc(u)}</li>`).join('')}</ul>` : ''}
|
|
102
158
|
<div class="links">
|
|
103
|
-
<a class="btn
|
|
104
|
-
<a class="btn
|
|
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>
|
|
105
161
|
</div>
|
|
106
162
|
</article>`
|
|
107
163
|
})
|
|
108
164
|
.join('\n')
|
|
109
165
|
|
|
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
166
|
const html = `<!doctype html>
|
|
119
167
|
<html lang="en">
|
|
120
168
|
<head>
|
|
@@ -124,133 +172,187 @@ const html = `<!doctype html>
|
|
|
124
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.">
|
|
125
173
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
126
174
|
<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:
|
|
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">
|
|
128
176
|
<style>
|
|
129
177
|
:root{
|
|
130
|
-
--
|
|
131
|
-
--
|
|
132
|
-
--
|
|
133
|
-
--
|
|
134
|
-
--
|
|
135
|
-
--
|
|
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;
|
|
136
185
|
}
|
|
137
186
|
*{box-sizing:border-box}
|
|
138
|
-
body{margin:0;background:var(--
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.logo{
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
.btn
|
|
152
|
-
|
|
153
|
-
.btn
|
|
154
|
-
.btn
|
|
155
|
-
.
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
.
|
|
171
|
-
.
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
.
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
.
|
|
185
|
-
.
|
|
186
|
-
|
|
187
|
-
.
|
|
188
|
-
.
|
|
189
|
-
|
|
190
|
-
.
|
|
191
|
-
.
|
|
192
|
-
|
|
193
|
-
.
|
|
194
|
-
.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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%}}
|
|
198
288
|
</style>
|
|
199
289
|
</head>
|
|
200
290
|
<body>
|
|
201
291
|
<div class="wrap">
|
|
202
292
|
<header class="nav">
|
|
203
|
-
<span class="logo"
|
|
204
|
-
<span class="
|
|
205
|
-
|
|
206
|
-
|
|
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 <span data-npm-version>v${esc(pkg.version)}</span></a>
|
|
297
|
+
</span>
|
|
207
298
|
</header>
|
|
208
299
|
|
|
209
300
|
<div class="hero">
|
|
210
|
-
<div class="
|
|
301
|
+
<div class="hero-main">
|
|
211
302
|
<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 <
|
|
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>
|
|
213
304
|
<div class="cta">
|
|
214
|
-
<a class="btn blue" href="${GITHUB}/blob/main/ADOPTING.md"
|
|
215
|
-
<a class="btn
|
|
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>
|
|
216
311
|
</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
312
|
</div>
|
|
219
|
-
<div class="side">
|
|
220
|
-
|
|
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>
|
|
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 ')}
|
|
223
315
|
</div>
|
|
224
316
|
</div>
|
|
225
317
|
|
|
226
|
-
<div class="
|
|
227
|
-
<div class="
|
|
228
|
-
<div class="
|
|
229
|
-
<div class="
|
|
230
|
-
<div class="
|
|
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 & smoke test</p></div>
|
|
231
323
|
</div>
|
|
232
324
|
|
|
233
325
|
<section>
|
|
234
|
-
<h2
|
|
326
|
+
<div class="sec-head">${PATTERNS_ICON}<h2>Patterns</h2></div>
|
|
235
327
|
${patternCards}
|
|
236
328
|
</section>
|
|
237
329
|
|
|
238
330
|
<section>
|
|
239
|
-
<h2
|
|
331
|
+
<div class="sec-head">${PIPELINE_ICON}<h2>From a bare PRD.md to shipped</h2></div>
|
|
240
332
|
<div class="steps">
|
|
241
|
-
${STEPS.map(([
|
|
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 ')}
|
|
242
334
|
</div>
|
|
243
335
|
</section>
|
|
244
336
|
|
|
245
337
|
<footer>
|
|
246
|
-
Generated from the pattern catalog by <a href="${GITHUB}/blob/main/scripts/build-site.mjs">scripts/build-site.mjs</a>
|
|
338
|
+
Generated from the pattern catalog by <a href="${GITHUB}/blob/main/scripts/build-site.mjs"><code>scripts/build-site.mjs</code></a>
|
|
247
339
|
· ${new Date().toISOString().slice(0, 10)} · <a href="${GITHUB}/blob/main/LICENSE">MIT</a>
|
|
248
340
|
· <a href="${GITHUB}/issues/new/choose">Feedback → issues</a>
|
|
249
341
|
</footer>
|
|
250
342
|
</div>
|
|
251
343
|
<script>
|
|
252
|
-
|
|
253
|
-
var
|
|
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
|
|
254
356
|
if(v){document.querySelectorAll('[data-npm-version]').forEach(function(e){e.textContent='v'+v})}
|
|
255
357
|
}).catch(function(){})
|
|
256
358
|
</script>
|