@golden-frijoles/kit 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/dist/init.mjs ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ // init.mjs — `gf-kit init`: adopt any repo by writing the Roadmap/ skeleton (golden-frijoles-plugin S3.2, D2).
3
+ //
4
+ // node scripts/init.mjs # write the skeleton into projectRoot(); never overwrite a file
5
+ //
6
+ // ── Why ───────────────────────────────────────────────────────────────────────────────────────
7
+ // A stranger who pastes the install prompt into an EXISTING repo gets the plugin, but `groom` needs
8
+ // somewhere to write on day one — `Roadmap/README.md`, `WAYS-OF-WORKING.md`, `LEARNINGS.md`, and the
9
+ // `00-ideas/` funnel. `gf-kit init` is that one step: it writes the same skeleton a project spawned
10
+ // from `template/` already has, into any repo, copied or installed (D2's two roots).
11
+ //
12
+ // ── Where the skeleton's SOURCE lives ─────────────────────────────────────────────────────────
13
+ // INSTALLED: `kitRoot()/skeleton/` — `scripts/build-kit.mjs` copies `template/<each SKELETON path>`
14
+ // into `kit/dist/skeleton/`, reading the very list exported below, so there is one list, not two.
15
+ // COPIED: `kitRoot()/../` — when this file runs as `template/scripts/init.mjs` (this repo's own dev
16
+ // checkout) that is `template/`, the skeleton's real source; when it runs as a spawned project's own
17
+ // `scripts/init.mjs`, that is the project's own root, which already has every file, so every write is
18
+ // a no-op `skipped … (exists)` — never a broken read.
19
+ //
20
+ // ── The one rule ──────────────────────────────────────────────────────────────────────────────
21
+ // It NEVER overwrites a file that already exists (it prints `skipped <path> (exists)`), so it is safe
22
+ // to run on a partially-adopted repo. It writes nothing outside `Roadmap/`. Its own exit code is 0
23
+ // whether it wrote anything or not — "already adopted" is success, not a no-op failure.
24
+ //
25
+ // ── Two guards found by cross-review on #47 ───────────────────────────────────────────────────
26
+ // (1) COPIED mode, source === destination: `skeletonRoot()` in copied mode is `kitRoot()/..`, the
27
+ // SAME directory `projectRoot()` resolves to (this repo's own dev checkout runs exactly this way).
28
+ // So `join(source, rel)` and `join(project, rel)` are the identical path — reproduced by deleting
29
+ // one SKELETON file from a template/ checkout and running `node scripts/init.mjs`: the destination
30
+ // "doesn't exist" (it was deleted), so the copy proceeds, and the SOURCE (the same path) doesn't
31
+ // exist either — `copyFileSync` throws ENOENT instead of a clean skip. A stranger in installed mode
32
+ // never hits this (source and project are genuinely different directories there), but the repo's
33
+ // own dev loop and any future copied-mode caller can. Treated as `skipped`, same as "exists" — the
34
+ // file not being copyable from itself is not a failure this tool should crash on.
35
+ // (2) INSTALLED mode, project root resolves to `$HOME`: `projectRoot()` walks up to the nearest
36
+ // `Roadmap/` or `.git`. A non-git folder under a dotfiles repo tracked at `$HOME` (a common setup)
37
+ // has no closer match, so the walk lands on `$HOME` itself — and writing `Roadmap/` there would
38
+ // scatter the skeleton across the operator's actual home directory. Refused outright.
39
+ //
40
+ // Zero deps — Node 18+.
41
+
42
+ import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
43
+ import { homedir } from 'node:os';
44
+ import { dirname, join, resolve } from 'node:path';
45
+ import { isInstalled, kitRoot, projectRoot } from './lib/project-root.mjs';
46
+
47
+ /**
48
+ * The Roadmap skeleton `gf-kit init` writes, relative both to its source root (template/, or
49
+ * kit/dist/skeleton/ once built) and to the project it writes into. ONE list: `build-kit.mjs` reads
50
+ * this export to populate `kit/dist/skeleton/`, so there is no second copy of the file names to drift.
51
+ */
52
+ export const SKELETON = [
53
+ 'Roadmap/README.md',
54
+ 'Roadmap/WAYS-OF-WORKING.md',
55
+ 'Roadmap/LEARNINGS.md',
56
+ 'Roadmap/00-ideas/README.md',
57
+ 'Roadmap/00-ideas/seeds/.gitkeep',
58
+ 'Roadmap/00-ideas/audits/.gitkeep',
59
+ ];
60
+
61
+ /** Where the skeleton's SOURCE files live: `kitRoot()/skeleton` when installed, `kitRoot()/..` when copied. */
62
+ export function skeletonRoot({ root = kitRoot(), installed = isInstalled({ root }) } = {}) {
63
+ return installed ? join(root, 'skeleton') : join(root, '..');
64
+ }
65
+
66
+ /**
67
+ * Write the skeleton into `project`, never overwriting. Pure given its injected fs functions.
68
+ * Returns `{ wrote, skipped }`, each a list of the SKELETON-relative paths.
69
+ */
70
+ export function initSkeleton({
71
+ project = projectRoot(),
72
+ source = skeletonRoot(),
73
+ files = SKELETON,
74
+ exists = existsSync,
75
+ mkdir = mkdirSync,
76
+ copy = copyFileSync,
77
+ log = () => {},
78
+ } = {}) {
79
+ const wrote = [];
80
+ const skipped = [];
81
+ for (const rel of files) {
82
+ const dest = join(project, rel);
83
+ if (exists(dest)) {
84
+ skipped.push(rel);
85
+ log(`skipped ${rel} (exists)`);
86
+ continue;
87
+ }
88
+ const from = join(source, rel);
89
+ // Copied mode's source and destination roots can be the identical directory (see the header) —
90
+ // a source that is the destination, or that simply isn't there, is a skip, never a crash. The
91
+ // destination not existing (checked above) does NOT imply the source exists: they can be the
92
+ // same absent path.
93
+ if (from === dest || !exists(from)) {
94
+ skipped.push(rel);
95
+ log(`skipped ${rel} (${from === dest ? 'source is the destination' : 'source is absent'})`);
96
+ continue;
97
+ }
98
+ mkdir(dirname(dest), { recursive: true });
99
+ copy(from, dest);
100
+ wrote.push(rel);
101
+ log(`wrote ${rel}`);
102
+ }
103
+ return { wrote, skipped };
104
+ }
105
+
106
+ /**
107
+ * True when `project` resolves to the operator's own home directory — the one place `gf-kit init`
108
+ * must never write, even though `projectRoot()`'s walk-up can legitimately land there (a non-git
109
+ * folder under a dotfiles repo tracked at `$HOME`). Pure given its injected `home`.
110
+ */
111
+ export function isHomeDirectory({ project = projectRoot(), home = homedir() } = {}) {
112
+ return resolve(project) === resolve(home);
113
+ }
114
+
115
+ function main() {
116
+ const project = projectRoot();
117
+ if (isHomeDirectory({ project })) {
118
+ console.error(
119
+ `gf-kit init: refusing to run — the resolved project root is your home directory (${project}).\n` +
120
+ ' This usually means no Roadmap/ or .git was found between the current directory and $HOME,\n' +
121
+ ' and $HOME itself is a git repo (a dotfiles checkout, for example). Run this from inside a\n' +
122
+ ' real project — a directory with its own .git — or pass --root explicitly.'
123
+ );
124
+ return 2;
125
+ }
126
+ const { wrote, skipped } = initSkeleton({ project, log: (line) => console.log(line) });
127
+ console.log(`gf-kit init: ${wrote.length} written, ${skipped.length} already present.`);
128
+ return 0;
129
+ }
130
+
131
+ const isMain = process.argv[1] && process.argv[1].endsWith('init.mjs');
132
+ if (isMain) process.exitCode = main();
@@ -156,6 +156,31 @@ export function compareVersions(a, b) {
156
156
  return left.pre < right.pre ? -1 : 1;
157
157
  }
158
158
 
159
+ /**
160
+ * The ONE install prompt a stranger pastes into their agent (golden-frijoles-plugin S3.3/S3.4, D6/D8/X10).
161
+ *
162
+ * This is a CONSTANT, not this repo's business to author: golden-beans `apps/web/lib/install-prompt.ts`
163
+ * is the SOURCE (`export const INSTALL_PROMPT`), and it names only `github.com` /
164
+ * `raw.githubusercontent.com` URLs and no site URL — so unlike `getSiteUrl()`-backed strings there, it
165
+ * needs no per-environment origin and is safe to transcribe byte-for-byte. This is that transcription,
166
+ * exactly as golden-beans' `CopyPromptCard` renders it on the closing CTA, `/install` and onboarding.
167
+ * `scripts/check-onboarding-parity.mjs` asserts this string appears VERBATIM in the repo README.md and
168
+ * the `golden-frijoles` umbrella SKILL.md — a one-word drift fails it. Never hand-edit this without the
169
+ * source (golden-beans `apps/web/lib/install-prompt.ts`) changing first.
170
+ *
171
+ * Text: the audit's §3.1 prompt (`Roadmap/00-ideas/audits/golden-frijoles-unification-2026-09-23.md`,
172
+ * golden-beans checkout), verbatim, its soft-wrapped blockquote lines joined into the one paragraph it
173
+ * renders as.
174
+ */
175
+ export const INSTALL_PROMPT =
176
+ "Install the golden-frijoles plugin. If you're in Claude Code, run `claude plugin marketplace add " +
177
+ 'golden-frijoles/skills`, then `claude plugin install golden-frijoles@golden-frijoles`. If you\'re in ' +
178
+ 'another agent, run `npx skills add golden-frijoles/skills --skill \'*\'` and select your ' +
179
+ 'agent. Use one installation method. You can read the skill directly at ' +
180
+ 'https://github.com/golden-frijoles/skills/blob/main/plugins/golden-frijoles/skills/golden-frijoles/SKILL.md ' +
181
+ '(raw: https://raw.githubusercontent.com/golden-frijoles/skills/main/plugins/golden-frijoles/skills/golden-frijoles/SKILL.md). ' +
182
+ 'Then use the golden-frijoles skill when working on this project, and start with its setup.';
183
+
159
184
  /**
160
185
  * The value of `name` in a dotenv file, or null.
161
186
  *
@@ -0,0 +1,85 @@
1
+ # 00-ideas — the idea funnel
2
+
3
+ The front of the pipeline: raw ideas → scoped seeds → scaffolded epics. Lifecycle is tracked in
4
+ **frontmatter on each seed**, not in folder names.
5
+
6
+ ```
7
+ 00-ideas/
8
+ ├── README.md ← you are here
9
+ ├── BUILD-ORDER.md ← GENERATED status board (run `node scripts/build-order.mjs`) — do NOT hand-edit
10
+ ├── seeds/ ← every idea/scope seed, flat, one .md each (with frontmatter)
11
+ └── audits/ ← UX/UI (or equivalent) audit findings (reference material, NOT seeds)
12
+ ```
13
+
14
+ ## Seed frontmatter (the lifecycle source)
15
+
16
+ Every file in `seeds/` starts with this block:
17
+
18
+ ```yaml
19
+ ---
20
+ title: "Example feature idea"
21
+ slug: example-feature-idea # kebab; matches the filename
22
+ status: raw # raw | ready | queued | scaffolded | in-progress | shipped | archived
23
+ area: "01" # macro-section number, matching Roadmap/README.md's table
24
+ type: feature # feature | spike | chore | epic
25
+ priority: null # a wave/priority label, or null
26
+ appetite: null # S | M | L — the budget, set at shaping; REQUIRED before `queued`
27
+ underwritten_by: null # wave that pays for this (a Roadmap/bets/<wave>.md), or null
28
+ risk: low # low | high
29
+ epic: null # path to the scaffolded epic, or null until scaffolded
30
+ build_order: null # BUILD-ORDER id, or null
31
+ updated: <date>
32
+ ---
33
+ ```
34
+
35
+ ### status — definitions
36
+
37
+ | status | meaning |
38
+ |---|---|
39
+ | `raw` | unrefined idea, no scope yet |
40
+ | `ready` | Definition-of-Ready scope doc written |
41
+ | `queued` | accepted into `BUILD-ORDER.md` (⬜) |
42
+ | `scaffolded` | epic + sprint docs created (`epic:` set; poster 🚧) |
43
+ | `in-progress` | building (some sprint stories ticked) |
44
+ | `shipped` | epic done (epic ✅ + RETROSPECTIVE; poster ✅) |
45
+ | `archived` | dropped or superseded |
46
+
47
+ The enum should be **enforced, not advisory** — wire `scripts/build-order.mjs` (and any Notion/board
48
+ sync you add) to hard-fail on a present-but-unrecognized `status:` value, rather than falling back
49
+ silently to a derived status. A silent fallback makes drift undetectable exactly where it matters.
50
+
51
+ ### Who owns `status` (seed vs. epic-README frontmatter)
52
+
53
+ One field is authoritative at each stage — they never both drive the board:
54
+
55
+ - **Before an epic exists** (`epic: null`) → the **seed's** `status` (`raw`/`ready`/`queued`) is
56
+ authoritative; you set it by hand or the `groom` skill sets it. This is what the BUILD-ORDER
57
+ **funnel** shows.
58
+ - **Once `epic:` is set** → the **epic README's frontmatter `status:` is the SSOT** (set at epic
59
+ close: `scaffolded` → `in-progress` → `shipped`). The seed is now **funnel-only** — its `status:` is
60
+ no longer read for the board, so it can't drift it. **`BUILD-ORDER.md` is a generated view — never
61
+ hand-edit it; change the README `status:` and run `node scripts/build-order.mjs`.**
62
+
63
+ ### appetite & underwriting — the economics fields
64
+
65
+ `appetite` (S | M | L) is the **budget the idea is worth**, fixed at shaping *before* the solution
66
+ is designed — sessions + an implied token band, never a time estimate (see WAYS-OF-WORKING →
67
+ *Betting & appetite*). `underwritten_by` names the **wave that pays for it** (a
68
+ `Roadmap/bets/<wave>.md` file), set at the betting table. `null` means nobody has paid for it yet —
69
+ fine in the funnel, impossible on the board: `build-order.mjs` **hard-fails** a `queued` seed with
70
+ no `appetite`, and flags a missing `underwritten_by` as drift. Like `status`, `appetite` is an
71
+ enforced enum — a present-but-unrecognized value fails the board, it never falls back silently.
72
+
73
+ ## How seeds flow (no file moves)
74
+
75
+ 1. **Capture** — drop a raw idea as `seeds/<slug>.md` with `status: raw` (the `groom` skill does this
76
+ from a brain-dump).
77
+ 2. **Scope** — `groom` fills out the Definition-of-Ready (appetite included) and flips
78
+ `status: ready`.
79
+ 3. **Queue** — bet on it at a wave boundary (`appetite:` + `underwritten_by:` set, the wave's
80
+ `Roadmap/bets/` file records what it displaced); `status: queued`.
81
+ 4. **Scaffold** — on approval, `groom` runs its own `scaffold-epic.mjs` (ships inside the `groom`
82
+ skill, `golden-frijoles` plugin) to create the epic/sprint docs, then sets the seed's `epic:` +
83
+ `status: scaffolded`. **No file ever moves between folders** — the frontmatter carries the state.
84
+
85
+ Filenames are kebab-case and match `slug`. Audits live in `audits/`, never in `seeds/`.
File without changes
File without changes
@@ -0,0 +1,164 @@
1
+ # Learnings — operating notes for every build
2
+
3
+ **Read this at the start of every session.** It's the distilled, cross-cutting wisdom from past
4
+ epics' retrospectives — the things that would have saved the last agent time. The full story of any
5
+ item lives in its epic's `RETROSPECTIVE.md`; this file keeps only the *transferable* rule.
6
+
7
+ **How this file stays useful (Definition of Done, epic):** at epic close, promote any durable,
8
+ generalizable learning from your `RETROSPECTIVE.md` into the right section below — a one-liner + a
9
+ *why* + the date/source. **Dedupe** (sharpen the existing line, don't append a near-duplicate). If a
10
+ rule here is now wrong, fix or delete it. Keep it short — a long digest is an unread digest.
11
+
12
+ **TEMPLATE NOTE:** the entries below are a curated, generalized subset carried over from the origin
13
+ project (`dobby-foundation`'s own extraction) — the tooling/process gotchas that don't depend on any
14
+ particular stack. As you build this project, your own entries will accumulate here; keep the same
15
+ one-liner + why + date shape.
16
+
17
+ ---
18
+
19
+ ## Multi-agent & async deploy coordination
20
+ *If several agents work in parallel on their own branches, against repos that deploy independently.*
21
+
22
+ - **`main` moves under you.** Before opening a PR — and again if it sits open — **merge latest `main`
23
+ into your branch**. Tell-tale: CI fails on a spec/check for something you never touched → a sibling
24
+ agent landed something on `main` and your preview (if you have one) predates it. **A re-run alone
25
+ won't fix it** — the mismatch is structural; only `git merge origin/main` + push clears it. Confirm
26
+ with `git log HEAD..origin/main`.
27
+ **Corollary — the stale-vs-fresh mismatch can hit your own NEW code too, not just an untouched
28
+ check, when a sibling PR changes a shared file's CONVENTIONS (a lint rule, not a feature).** The
29
+ diagnostic tell: check whether a FAILING assertion is about a rule/convention that changed, not just
30
+ a feature/data mismatch.
31
+ - **Announce cross-cutting or direct-to-`main` changes**, and prefer a PR even for "engine" features.
32
+ Anything touching shared surface — a root layout/middleware file, global styles, `package.json`/deps,
33
+ a new sibling worktree — can break every other open PR.
34
+ - **Don't yank a shared branch out from under another agent.** If the repo's working tree is on
35
+ someone else's branch, do your change in an isolated `git worktree` instead of switching it.
36
+ **Corollary — checking CI status and merging a PR need no local checkout at all.** `gh pr checks <N>`
37
+ and `gh pr merge <N>` operate against the pushed remote branch via the GitHub API; they don't care
38
+ what's checked out locally.
39
+ - **Before building a story, grep whether a sibling PR already fixed the identical root cause.** Two
40
+ epics approved the same day can target the same bug from different scope docs. Check
41
+ `git log --oneline -- <the file the story's root-cause names>` + `gh pr list` during research, not
42
+ assumed.
43
+ - **Risk tier decides who merges**: low-risk → the reviewer/agent may merge on green CI; anything
44
+ touching money / auth / DB / shared infra → the product owner merges. When unsure, treat as high.
45
+ **Corollary — an explicit "merge on green" authorization changes who decides to pause and check in,
46
+ not whether the review layers themselves still run.** "Merge on green" is permission to proceed
47
+ through the established gate without re-asking at each step, not permission to skip the gate.
48
+ **Corollary — a "merge on green" given for one PR does not carry forward to a LATER PR in the same
49
+ session/epic, even a similarly-scoped one**, and a builder's own plan can promise a review step the
50
+ standing authorization never touched. Re-check whether a standing "merge on green" was given for
51
+ *this* PR/story, not just somewhere earlier in the conversation.
52
+ - **When your branch is BEHIND `main`, the two-dot `git diff main..HEAD` lies — read the three-dot.**
53
+ Two-dot compares tips directly, so it folds in the *inverse* of every commit `main` gained since you
54
+ branched (a sibling epic's new files show up as "deletions" in your diff — alarming and wrong).
55
+ Review with **three-dot `git diff main...HEAD`** (merge-base→HEAD = only your changes), and **merge
56
+ `origin/main` into the branch before merging the PR** so the merged tree is what actually ships.
57
+ - **A squash-merged sprint branch is a dead end — start the next sprint on a FRESH branch off `main`.**
58
+ A squash-merged PR's individual commits aren't on `main` (only the one squash commit is), so
59
+ continuing that branch for the next sprint re-introduces a messy duplicate diff and can't
60
+ fast-forward. Branch clean off `origin/main` for each new sprint.
61
+ - **To verify "is the prior sprint serving?", reason off `origin/main` — never the working tree — and
62
+ read PR *state*, not branch commits.** Local app checkouts routinely sit on *other* agents'
63
+ branches, so on-disk files lie about `main`, and a squash-merged sprint's individual commits
64
+ genuinely aren't on `main`. Confirm with `gh pr view <#> --json state,mergeCommit` or `git fetch`
65
+ then `git grep <x> origin/main` — an `ls`/working-tree read is not evidence about `main`.
66
+ - **Concurrent planning commits in a shared worktree collide the git index.** Fix: (1) **path-limited
67
+ commits** — `git add <your files>` + `git commit -- <those paths>`, never `git add -A`; (2) for
68
+ parallel planning, give each session its own worktree, or appoint a single **scribe** for shared
69
+ files (like `BUILD-ORDER.md`).
70
+ - **A subagent/fork that dies mid-task from a shared session rate-limit still returns a `result` — that
71
+ text is its last tool-call narration, not a trustworthy completion claim.** After any subagent/fork
72
+ batch — especially one large enough to plausibly share a rate-limit, or any showing a failed status —
73
+ re-derive actual file state directly (grep the real repo) and run the language's type-checker/build
74
+ before treating the batch as complete.
75
+
76
+ ## Tooling gotchas
77
+ - **A script with a co-located pure-logic test file MUST guard its `main()` call with an `isMain`
78
+ check.** Importing a script that calls `main()` unconditionally at module scope re-executes the
79
+ whole script for real (shell-outs, notifications, git pushes, all of it) the moment a test file
80
+ loads it for its pure helpers: `const isMain = process.argv[1] && …; if (isMain) main()`.
81
+ - **Run the repo binaries directly when `npm`/`npx` chokes.** A sibling worktree that reuses the same
82
+ `package.json` name as the main checkout breaks npm **workspace resolution** at the monorepo root.
83
+ Use the binary path directly (`node /…/node_modules/typescript/bin/tsc --noEmit`,
84
+ `/…/node_modules/.bin/{next,playwright}`). New worktrees should use a unique package name or be
85
+ excluded from the root `workspaces` glob.
86
+ - **A worktree needing its own `npm install` forces worktree-local binaries for everything, including
87
+ test runners.** A fresh `git worktree` resolves most tooling fine via walk-up to the root
88
+ `node_modules`, but if any dependency needs a local install (e.g. a CSS framework's PostCSS plugin
89
+ resolution), that install adds a worktree-local copy of your test framework too — switch to the
90
+ **worktree-local** binary path, or you'll hit "two different versions" / "No tests found" errors.
91
+ - **`gh pr merge --delete-branch` fails when a worktree holds `main`.** The merge still succeeds on
92
+ GitHub; only the local branch-delete errors. Verify with `gh pr view <n> --json state`.
93
+ - **A server-side `process.env.X ?? \`https://${req.headers.get('host')}\`` fallback is a real
94
+ production landmine, distinct from client-bundle build-time-inlining bugs.** The trap is the
95
+ Host-header fallback when the env var is unset: a bare container run without an explicit runtime env
96
+ var can get a literal `0.0.0.0:PORT` or similar garbage as the `Host` header, and the fallback
97
+ happily builds a broken URL from it — dangerous on any redirect-URL-building code path (OAuth
98
+ callbacks, payment-provider return URLs). Fix: one shared `resolveOrigin()`-style helper that
99
+ rejects obviously-wrong hosts and **throws instead of silently building a broken URL** — a loud
100
+ failure beats a dead redirect.
101
+ - **A unit-tested pure helper can't live in the same file as code that imports a framework/runtime-only
102
+ module** (e.g. a Next.js `next/cache` import, or an auth SDK's server-only entrypoint). A generic
103
+ test runner that can't load that module throws an opaque, unrelated-looking error the moment it
104
+ imports the file at all — even if the pure function itself never touches the framework-only code.
105
+ Keep the pure logic in its own zero-import file; let the framework-touching wrapper import *it*.
106
+ - **Swapping a framework-generated artifact for a hand-rolled route breaks specs on exact format.**
107
+ Converting a typed/generated file (robots.txt, sitemap, OG image, metadata) to a hand-rolled
108
+ equivalent can silently change output details (header casing, field order) that an existing spec
109
+ asserted on. When you replace anything a framework generates, diff the *exact bytes* the old one
110
+ emitted and grep the suite for any spec asserting that surface.
111
+ - **CI sometimes just doesn't schedule a workflow for a PR.** Seen occasionally on `opened`; close/
112
+ reopen doesn't always fix it — an empty-commit push (a real `synchronize` event) does. Don't merge
113
+ on an absent gate: re-trigger, and lean on the local gate + a green preview as the real signal.
114
+ - **`node --test <dir>` (bare directory) can silently fail to discover tests depending on your Node
115
+ version** — it may try to load the directory as a module instead of globbing it. Use an explicit
116
+ glob: `node --test 'scripts/lib/*.test.mjs'`.
117
+ - **A "resolve the PR from the current branch" tool must read PR `state`** — a list/view call can
118
+ return MERGED/CLOSED PRs too, especially for a reused branch name whose PR already merged. Treat
119
+ `state !== 'OPEN'` as "no open PR for this branch" and pair it with a stale-HEAD guard
120
+ (`git rev-parse HEAD` vs the PR's `headRefOid` → warn + require an explicit override) so the first
121
+ run always reviews the current diff.
122
+ - **A hosted CLI-authenticated integration (Vercel-style env-var management, similar platforms) can
123
+ silently store or report EMPTY values** through a convenience CLI command even when the underlying
124
+ API call "succeeds." Verify by value **length** where you can't read the value directly (a scoped
125
+ read token may be needed), not just by exit code.
126
+ - **A "sensitive"/write-only secret is confirmable by presence/type but not by value** — you can check
127
+ it exists and which environment it targets, but not its actual content, from a CLI or API. Read the
128
+ provider's dashboard, or have the app surface the cause on use (missing key → a specific, classifiable
129
+ error) instead of guessing.
130
+ - **Driving a young foreign CLI: run `<cli> --help` first, pin the version, and design for degrade —
131
+ never build against a documented flag from memory.** A less-mature CLI can have surprising interface
132
+ shapes (no JSON output mode, arguments only via argv not stdin, or vice versa) that don't match a
133
+ more mainstream CLI's conventions. Smoke-test by running it against something real and reading the
134
+ actual output before scripting around it.
135
+ **A young foreign CLI can silently break its own contract on a MINOR version bump** — a print mode
136
+ that used to always emit something can start exiting 0 with empty output on a real failure. Treat
137
+ **empty output as failure** (not success), and make any version-pin check **fail loud** so a
138
+ contract break gets caught, not silently absorbed.
139
+ **A CLI authed by an interactive/OAuth login is NOT free to run in CI** — confirm a portable
140
+ non-interactive credential path AND its cost before automating it in a runner; some CLIs have no
141
+ headless auth at all, which may mean an advisory/local-only tool stays local-only rather than
142
+ becoming a CI job.
143
+ - **`process.exit()` truncates piped stdout — flush synchronously, or you ship a tool that works to a
144
+ file but crashes in a pipe.** A script that does `console.log(json); process.exit(0)` can produce
145
+ valid output when redirected to a file (sync writes) but truncated output down a pipe, because the
146
+ async stdout write hasn't drained when exit fires. Use a synchronous write before `process.exit`, or
147
+ exit in the write callback. Test a tool the way it's actually invoked (pipe, not just file redirect).
148
+ - **Git background auto-maintenance can race a burst of rapid commits and leave stale `*.lock`
149
+ files**, producing intermittent "cannot lock ref" errors. Clear locks recursively
150
+ (`find .git -name '*.lock'`) and run a rapid-commit batch with `git -c gc.auto=0 commit …` so
151
+ auto-maintenance can't re-trigger mid-sequence.
152
+ - **A delta-only reporting tool must special-case a missing/wiped baseline as a bounded no-op, never as
153
+ "everything happened."** Diffing current state against an empty/`null` previous snapshot makes every
154
+ historical item look "new" — guard for a missing baseline with ONE bounded summary (counts only)
155
+ instead of enumerating full history, and keep a message-length safety net regardless of the guard.
156
+ - **A script with both scheduled state-tracking delivery and on-demand artifact generation must keep
157
+ the artifact mode stateless.** Reusing a stateful window/log rail for an on-demand report mode risks
158
+ silently advancing state a scheduled run depends on — keep on-demand modes explicitly
159
+ non-state-mutating and lock that with a test.
160
+
161
+ ## Working efficiently
162
+ - **Running a whole multi-sprint epic in one session is the main context-cost driver.** The durable
163
+ state (the plan file, sprint docs, team memory) makes re-entry cheap by design — compact at each
164
+ sprint/PR boundary, and for big epics consider a fresh session per sprint.
@@ -0,0 +1,67 @@
1
+ # <TEMPLATE FILL-IN: Project Name> — Product Roadmap & Feature Poster
2
+
3
+ > **Mission:** <TEMPLATE FILL-IN: one or two sentences — what this product does and for whom.>
4
+
5
+ This folder is the **product source of truth**. It speaks in plain product language for product,
6
+ design, and business — **no engineering or tech specs here** (those live in `tasks/` and team memory).
7
+
8
+ ---
9
+
10
+ ## How this roadmap is organized
11
+
12
+ ```
13
+ Roadmap/
14
+ ├── README.md ← you are here · the product poster (all features)
15
+ ├── WAYS-OF-WORKING.md ← how we plan, build, ship (scrum cadence) + tooling
16
+ ├── LEARNINGS.md ← the cross-cutting retro digest, read at every session start
17
+ ├── SESSION-KICKOFFS.md ← thin-pointer prompt cheat sheet for starting a session
18
+ ├── 00-ideas/ ← the idea funnel (seeds, audits, the generated BUILD-ORDER.md)
19
+ └── <Macro-section>/ ← a product domain (a journey, not a component)
20
+ ├── README.md ← what this area is, for whom, current features
21
+ └── <Epic>/ ← a meaningful body of work
22
+ ├── README.md ← the epic's product overview
23
+ ├── sprint-N.md ← the sprint's user stories (As a… I want… so that…)
24
+ └── RETROSPECTIVE.md ← what we learned
25
+ ```
26
+
27
+ **Levels:** `Roadmap → Macro-section → Epic → Sprint → User Story`. Each user story is a small,
28
+ independently shippable slice of value.
29
+
30
+ ---
31
+
32
+ ## The macro-sections (product domains)
33
+
34
+ <!-- TEMPLATE FILL-IN: list your project's product domains here, one per macro-section folder
35
+ (e.g. `01-onboarding-and-auth`, `02-core-workflow`, ...). Number them so BUILD-ORDER.md and
36
+ epic paths sort predictably. One macro-section should be reserved for platform/infra work
37
+ that isn't user-facing product — see the convention below. -->
38
+
39
+ | # | Macro-section | Covers |
40
+ |---|---|---|
41
+ | 01 | <TEMPLATE FILL-IN> | <TEMPLATE FILL-IN> |
42
+ | 09 | Platform & Infra | Engineering/observability work that isn't a user-facing product domain — deploy pipeline, dev tooling, cross-cutting process (this convention — reserving `09` for platform/infra — is a deliberate carry-over from the origin project; keep the number stable so tooling that reads it doesn't need per-project config). |
43
+
44
+ ---
45
+
46
+ ## Feature map
47
+
48
+ <!-- TEMPLATE FILL-IN: one `### <NN> · <Macro-section name>` heading per row above, each listing
49
+ shipped/in-progress features with a status marker. Convention: ✅ means enforced in code, not
50
+ merely intended — partial/aspirational is 🚧. Don't let this page lag a shipped epic; updating
51
+ it is part of the epic Definition of Done (see WAYS-OF-WORKING.md). -->
52
+
53
+ ### 01 · <TEMPLATE FILL-IN: macro-section name>
54
+ - 🚧 <TEMPLATE FILL-IN: first feature/epic, one line>
55
+
56
+ ---
57
+
58
+ ## Recent highlights
59
+
60
+ <!-- TEMPLATE FILL-IN: newest-first log of shipped epics, one bullet each, dated. Added at every
61
+ epic close (WAYS-OF-WORKING.md's epic Definition of Done). Example shape:
62
+ - **2026-01-01** — `<epic-slug>` shipped: <one-line what changed and why it matters>.
63
+ -->
64
+
65
+ ## License
66
+
67
+ <TEMPLATE FILL-IN: if this repo is private/internal, say so; otherwise state the license.>
@@ -0,0 +1,233 @@
1
+ <!-- GENERATED FILE — do not edit by hand.
2
+ Source: Roadmap/WAYS-OF-WORKING.template.md + Roadmap/fill-ins.yml
3
+ Regenerate: node scripts/render-ways-of-working.mjs (CI checks it with --check)
4
+ Shared process text belongs in the .template.md; this project's own belongs in fill-ins.yml. -->
5
+ # Ways of Working
6
+
7
+ How the product owner and Claude (builder) ship product together. Small slices, plan first, ship
8
+ the moment each slice works — and each slice is a piece of the final product, never a test of it.
9
+
10
+ ## Roles and the unit of work
11
+
12
+ The **product owner** sets direction, approves plans, tests each shipped slice and makes the consequential
13
+ calls (architecture forks, infra, money). **Claude** researches, proposes the plan as user stories,
14
+ builds, verifies, ships and documents.
15
+
16
+ Everything is sliced into **user stories** — the smallest independently testable, shippable value: *As a
17
+ \<role\>, I want \<capability\>, so that \<outcome\>*, plus **acceptance checks the product owner can
18
+ run**. Stories roll up into sprints, sprints into an epic, epics into a macro-section. Before building,
19
+ check whether existing features plus communication already deliver the outcome — surface that lighter
20
+ path first; `groom` gates on it (Stage 2.5).
21
+
22
+ ## The cadence
23
+
24
+ ```
25
+ Plan → branch + scaffold docs → build story → verify → QA/smoke → PR → review → merge (= deploy) → close
26
+ ```
27
+
28
+ 1. **Plan.** Non-trivial work goes through plan mode as user stories, approved before code, naming its
29
+ QA/smoke stage. Reference end-states are inspiration, never signed-off scope.
30
+ 2. **Branch + scaffold.** One branch per epic (`feat/<slug>`, or `fix/…`, `chore/…`) off the latest
31
+ `main`, in each repo you touch. Scaffold the epic `README.md` + `sprint-N.md` *before* any code, so
32
+ the product owner sees scope as it grows, and keep them current (✅ ticks, commit refs).
33
+ 3. **Build one story at a time.** Reuse before rebuild. Commit per story, **path-limited**.
34
+ 4. **Verify + QA.** The deterministic gate — typecheck, lint, build, the suite — is green **before**
35
+ merge, run by the building agent, not only by CI. **TEMPLATE FILL-IN — your deploy rail:** whether a branch gets a preview and how the gate reaches it, what merging to `main` builds, and — if there is no per-branch preview — who does the post-merge browser half.
36
+ 5. **PR → review → merge.** Declare a risk tier, run the review the policy asks for, resolve or answer
37
+ every finding, merge on green. **Merging to `main` is the production deploy.** Delete the branch.
38
+ 6. **Close.** Sprint close: the sprint-wrap summary. Epic close: the Definition of Done below.
39
+
40
+ ## Epic-mode builds — the default for a scaffolded epic
41
+
42
+ A whole epic in one orchestrated session is the normal unit of work; sprint docs are integration and
43
+ rollback boundaries *inside* it (a per-sprint session stays valid for a one-sprint epic, or when a sprint's
44
+ outcome genuinely changes the next one's scope). Six things make it work, and the first is the leverage:
45
+
46
+ 1. **Lock the architecture before any builder starts** — numbered decisions `D1…Dn` in the epic README
47
+ plus a per-sprint **"Build contract (locked by the architect before the builder started)"**, each
48
+ verified **against live code and live data**. Builders *cite* them; a paraphrased contract drifts
49
+ permissive. The lock must **disprove scope** (an acceptance criterion describing a guard, table or flag
50
+ the live system lacks is fiction — saying so is a success), **query the live data** (row counts decide
51
+ what is safe: a schema fork that is free while a table is empty is only free then), **name every
52
+ deviation** in the README, and **say where each contract lives, once** — import the rule, never
53
+ restate it.
54
+ 2. **Stack the branches** — `feat/<slug>` → `-s2` → `-s3`, one PR per sprint (a single PR only when the
55
+ sprints don't split along a review boundary), merged in order; sprints share hot files, so stack or
56
+ pay. **Never delete a base branch while a stacked PR is open** — GitHub closes that PR for good.
57
+ 3. **Route models by risk, invert for review** — the contract-defining sprint to the stronger model, the
58
+ mechanical ones to the faster, the riskiest PR's fresh reviewer to the strongest; state the routing in
59
+ the epic README. Findings route back to the original builder, whose context makes fixes cheap.
60
+ 4. **Merges are pre-authorized on green** in a named run: that removes the round-trip, not the gate or the
61
+ review layers, and never extends to a new category of production mutation (TLS/IAM/secrets, money or
62
+ entitlement writes, a new external dependency) — name those in one focused question.
63
+ 5. **Generate the kickoff, don't compose it** — `node skills/groom/emit-epic-kickoff.mjs --epic <slug>`
64
+ (the `groom` skill) reads the epic README and every sprint file and prints the orchestrator prompt.
65
+ Hand-composing it is how the architecture lock gets summarised away and the review policy silently
66
+ reverts to whatever the composing agent remembered.
67
+ 6. **Derive state, journal intent** — at session start run `node scripts/session-resume.mjs` (it ships in
68
+ the template's `scripts/`; a project spawned from it has both scripts): it
69
+ re-derives branches, worktrees, open PRs and migration drift live across the project's repos and leads
70
+ with what is surprising. Journal each locked decision with `node scripts/session-note.mjs --kind
71
+ decision "<text>"` — one intent line, nothing derived. A killed worker's agent is resumed with a
72
+ one-paragraph state recap (its actual `git status`/`diff`), not re-spawned cold.
73
+
74
+ *Done* means **shipped**, not merged: a merged PR that has not deployed, a migration written but not
75
+ applied, a flag that exists only in code are none of them done. With a migration: apply it **before**
76
+ merging, verify live, merge, then confirm the deploy succeeded.
77
+
78
+ ## Betting & appetite
79
+
80
+ Appetite is denominated in **sessions** — with agents the binding constraints are product-owner attention
81
+ and session context. Fixed appetite, variable scope.
82
+
83
+ | Appetite | Buys | Circuit breaker |
84
+ |---|---|---|
85
+ | **S** | one builder session; fixed scope (a bug, a chore, a clear story) | escalate-don't-guess — no hard breaker |
86
+ | **M** | one wave: an architect session + builder fan-out + review rounds | appetite exhausted → stop, back to shaping |
87
+ | **L** | a multi-wave epic | per-wave: each wave is re-bet at the boundary |
88
+
89
+ Four rules: **an exhausted bet returns to shaping**, never extends in flight; **nothing reaches
90
+ `status: queued` without an `appetite:` and an `underwritten_by:` wave**; **bets are placed at wave
91
+ boundaries** into `Roadmap/bets/<wave>.md`, three lines each, recording what they displaced; and **uphill
92
+ work stays on the strongest model**. Not every ask earns the betting table — `groom` sorts shaped bets
93
+ from fixed scope (appetite S, straight to a builder) and reactive/ops work. Why it works this way:
94
+ [`references/shapeup/`](https://github.com/golden-frijoles/skills/blob/main/template/references/shapeup/README.md).
95
+
96
+ ## Review & merge
97
+
98
+ The deterministic gate is the gate. Everything else is judgment, and it is reads that answer **different
99
+ questions** — not more reviewers of the same kind.
100
+
101
+ ```
102
+ CI (deterministic gate) — does it build, typecheck, pass the suite? BLOCKS merge
103
+ → fresh pr-reviewer subagent — context independence: did not hold the diff
104
+ → one external cross-family pass — family independence: different blind spots
105
+ → + a lean security lens — when the diff touches a security path
106
+ → the builder merges on green
107
+ ```
108
+
109
+ **Which PRs**: `scripts/review-config.json` → `reviewScope` — `every-pr` (all non-trivial PRs;
110
+ `--skip-trivial` drops docs-only and tiny diffs) or `security-paths-only`. The **security lens** is
111
+ triggered by a `securityPaths` glob or a `risk: high` body in either scope — paths, not judgement, so a
112
+ builder can add it but never skip it.
113
+
114
+ **Who reviews** is printed by `node scripts/review-route.mjs --builder <who> <PR#>`, never picked by hand:
115
+ the highest-preference family that did **not** build the diff takes the general pass, the next takes the
116
+ security lens (`codex → agy → vibe → claude`; a capped family falls through with `--exclude`). One family
117
+ left runs both prompts and says so in the PR body; none left means the layer is **DARK**, said out loud.
118
+
119
+ **A silent reviewer is a FAILED run, not a clean one**: the run asserts the reply carries real review
120
+ structure, posts `pending` before the CLI is even checked, pins the reviewed sha, and otherwise prints the
121
+ full reply, exits non-zero and fails the PR's `cross-review/<lens>` status. Both readers share one prompt
122
+ (`scripts/cross-review.prompt.md`): one pass, a `file:line` citation or the finding is not posted, at most
123
+ 3 nits, skip what CI enforces, Blocking/Should-fix only on a re-review. `/security-review` is available
124
+ locally as a pre-push self-check, never a gate. Why this shape:
125
+ [`references/review-stack.md`](https://github.com/golden-frijoles/skills/blob/main/template/references/review-stack.md).
126
+
127
+ **Every finding is fixed or answered on the PR; neither pass authorizes anything.** **HIGH** = money
128
+ (payments, checkout, fulfillment), auth and authorization boundaries, tenancy, DB migrations, shared infra;
129
+ **LOW** = the rest; unsure means HIGH. The **builder merges their own PR at every
130
+ risk tier** once CI is green and findings are resolved — the tier selects the review
131
+ scope, not the merge authority. Roll back with `git revert` on `main`.
132
+
133
+ **A deterministic floor runs underneath, free and without an LLM:** GitHub secret scanning with push protection, and CodeQL default setup on any repo with app code. The lens finds logic flaws; the scanners find known patterns and leaked credentials.
134
+
135
+ ## Escalate, don't guess — the ONE trigger list
136
+
137
+ Stop and hand back to the planning tier — rather than inventing an answer — on any of:
138
+
139
+ > **money (payments, checkout, fulfillment) · auth and authorization boundaries · tenancy · DB migrations ·
140
+ > shared infra · plan ambiguity · a decision the plan doesn't cover · 2+ failed attempts at the same
141
+ > problem.**
142
+
143
+ Default to escalate when unsure. This is a **model-routing** trigger, not a merge gate. Everywhere else
144
+ that needs this list references it here; there is no second copy.
145
+
146
+ ## Permissions — what an agent may do unattended
147
+
148
+ `.claude/settings.json` carries a committed `permissions` block: **allow** = verb classes only (never a
149
+ literal past command, and never one that destroys uncommitted work — an allowed command skips the
150
+ auto-mode classifier); **deny** = the irreversible-by-rule (CLI deploys, migration replays, force pushes and
151
+ protected-branch deletes, `rm -r`, whole-tree staging, edits to generated files, and the few repo scripts
152
+ that write production secrets from inside an allowed `node scripts/*`); **ask** = production secrets, env
153
+ writes and a service deploy. Deny and ask rules are carried in **three spellings** — bare,
154
+ assignment-prefixed and `env`-prefixed — because a leading assignment escapes a bare rule, and an escaped
155
+ *ask* is not a stricter outcome but a silent downgrade to the classifier.
156
+ Every deny/ask rule cites what it enforces in `.claude/permissions-ledger.json`, and
157
+ `node scripts/permissions-smoke.mjs` fails on an uncited rule, a stale ledger entry, a literal allow or a
158
+ missing baseline guardrail.
159
+
160
+ **Three actions get one focused question before you take them** — about irreversibility, not review: a
161
+ destructive or hard-to-reverse change to live data; real money or a third party's metered resource;
162
+ production secrets/IAM/DNS/TLS. The `ask` rules make that prompt automatic for the commands that do it.
163
+
164
+ **Auto mode is a USER setting** (`~/.claude/settings.json`): the same line in
165
+ a project file is ignored *and* masks the user default, so the smoke fails on it. A deny rule matches the
166
+ command an agent normally writes — it is not a sandbox: a leading assignment with an expansion
167
+ (`PATH=/x:$PATH vercel deploy`) was observed escaping a bare rule, so the critical rules carry an
168
+ expansion-safe form and the auto-mode classifier is the second floor.
169
+
170
+ ## Definitions of Done
171
+
172
+ **A story can start** when its "as a / I want / so that" is clear, its acceptance check is testable, and
173
+ it ships on its own.
174
+
175
+ **A story is done** when acceptance criteria are confirmed working; typecheck, lint and build are clean;
176
+ the real behaviour is **smoke-tested** end to end (never "build passes, therefore done" — an untestable
177
+ gap is stated in the PR, not glossed); **every new spec has been observed failing once** via a deliberate
178
+ mutation, so it is not a tautology; and the sprint doc is ticked.
179
+
180
+ **An epic is done** when `node scripts/epic-dod.mjs --check <macro/slug>` passes — it derives the
181
+ mechanical half (sprints merged, README `status: shipped`, sprint statuses ticked with refs, a real
182
+ retrospective, no leftover branch) — **and** the judgment items below are true:
183
+
184
+ - [ ] The **product poster** reflects what is now live (✅ = enforced in code).
185
+ - [ ] **`RETROSPECTIVE.md`** says what actually happened, and its durable learnings are promoted into
186
+ `Roadmap/LEARNINGS.md` — sharpen the existing line, don't append a near-duplicate.
187
+ - [ ] **Each sprint has a smoke walkthrough** a person can follow blind, with real URLs; money/auth steps
188
+ are flagged by name as owed to the product owner.
189
+ - [ ] **Team memory** (and its index, if your tooling keeps one) records the epic.
190
+
191
+ ## Automated QA
192
+
193
+ The harness grows by **one spec per browser-/API-testable story** — coverage accretes with the work. Two
194
+ layers: an **`api` project** that is the deterministic gate (no browser binaries, runs on every PR, green
195
+ before merge), and an opt-in **`browser` project** for rendered UI an API call can't see (nightly or on
196
+ demand, never the gate). A browser spec replaces a browser smoke previously owed to the product owner.
197
+
198
+ ## Documentation map
199
+
200
+ **`Roadmap/`** is the product source of truth in plain language (macro-section → epic → sprint → story,
201
+ plus the poster); **`LEARNINGS.md`** is the cross-epic digest, read at session start and fed at every
202
+ close; **`Roadmap/00-ideas/`** is the funnel — `seeds/` (lifecycle in frontmatter, never folder-shuffled),
203
+ `audits/`, and the **generated** `BUILD-ORDER.md` (`node scripts/build-order.mjs`, never hand-edited).
204
+ **Status SSOT = each epic README's frontmatter `status:`**; the board and any external projection are
205
+ derived views. **`Roadmap/bets/`** holds one file per wave; **`tasks/`** is the engineering delivery log.
206
+
207
+ ## Conventions
208
+
209
+ - **Gitflow.** Branch off `main`, commit per story, PR → merge. Never commit feature work straight to
210
+ `main`; never force-push a shared branch; merge latest `main` in before opening the PR.
211
+ - **Path-limited commits.** `git add <specific files>` then `git commit -- <those paths>` — never
212
+ `git add -A`. Several agents share a checkout, so whole-tree staging commits a sibling's in-flight
213
+ work. The deny list enforces it; parallel planners take their own `git worktree` or appoint one scribe
214
+ for shared files like `BUILD-ORDER.md`.
215
+ - **Docs track code — verified, not generalized.** A canonical rule must reflect what the code actually
216
+ does, checked against it — and a scoped learning is not globalized into a site-wide rule. On the poster
217
+ ✅ means enforced in code, 🚧 partial or aspirational. Run a light drift audit periodically.
218
+ - **Parallel agents and async deploys.** `main` moves under you: merge latest `main` into a long-running
219
+ branch; when repos deploy at different speeds, merge the data-producing repo first and make the consumer
220
+ degrade gracefully.
221
+ - **Worker death is a normal case.** Each builder on its own worktree; a killed worker's uncommitted tree
222
+ is evidence, not garbage; **verify by re-deriving repo state, never by trusting a completion report** —
223
+ a rate-limited subagent still returns a plausible-sounding result. Compact at sprint/PR boundaries; for a
224
+ big epic consider a fresh session per sprint.
225
+ - Commit messages end with the `Co-Authored-By: Claude` trailer.
226
+ - **Language.** Docs, comments and PR descriptions are in **English**. **TEMPLATE FILL-IN:** if your user-facing copy has its own policy (a default plus a bilingual allow-list), state it here and in `AGENTS.md`.
227
+
228
+ ## Tooling
229
+
230
+ **TEMPLATE FILL-IN:** list this project's authenticated CLI access and what each is used for — at minimum **git/gh** (branches, PRs, merges) and **node/npm** (typecheck, lint, build, dev server), plus your deploy, database and container CLIs.
231
+
232
+ Actions that touch live production, real money, or paid infrastructure are surfaced to the product owner
233
+ before running.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@golden-frijoles/kit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The scripts the Golden Frijoles skills run, packaged so they work in any repo: planning, build-order, reporting and verification rails. Zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {