@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 +132 -0
- package/dist/lib/golden-onboarding.mjs +25 -0
- package/dist/skeleton/Roadmap/00-ideas/README.md +85 -0
- package/dist/skeleton/Roadmap/00-ideas/audits/.gitkeep +0 -0
- package/dist/skeleton/Roadmap/00-ideas/seeds/.gitkeep +0 -0
- package/dist/skeleton/Roadmap/LEARNINGS.md +164 -0
- package/dist/skeleton/Roadmap/README.md +67 -0
- package/dist/skeleton/Roadmap/WAYS-OF-WORKING.md +233 -0
- package/package.json +1 -1
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.
|
|
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": {
|