From 29222002fe208636f6f8a5c2012850818be64082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baldur=20P=C3=A1ll=20H=C3=B3lmgeirsson?= Date: Sat, 5 Sep 2026 13:36:34 +0000 Subject: [PATCH 1/2] fix(test): pin colour off so console assertions are deterministic cleanup.ts wraps a count in chalk.bold while cleanup.test.ts asserts the unstyled string, so the suite passed in CI (no TTY) and failed in any colour-capable shell. Pinning FORCE_COLOR=0 in the vitest config fixes the whole class of console assertions rather than patching the one that broke. Claude-Session: https://claude.ai/code/session_01AvHaMVmjh88MZpKnkvv9PF --- vitest.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vitest.config.ts b/vitest.config.ts index 016028d..e113629 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,5 +7,8 @@ export default defineConfig({ include: ["src/**/*.test.ts"], setupFiles: ["./src/test-setup.ts"], disableConsoleIntercept: true, // Required for @oclif/test + // chalk styles output when it detects colour support, so console assertions + // would pass in CI (no TTY) and fail in an interactive shell. Pin it off. + env: { FORCE_COLOR: "0" }, }, }); From d686dd78e124edceb5efa04cfcd62892b8caf46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Baldur=20P=C3=A1ll=20H=C3=B3lmgeirsson?= Date: Sat, 5 Sep 2026 13:36:45 +0000 Subject: [PATCH 2/2] chore: add ai-workflow planning overlay and onboard the project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Installs the context/ planning workflow (roadmap → plan → implement → close) along with its skill and agent trees, then fills in the three project-owned stubs via /onboard: - verify.md lint / typecheck / build / test, each run and verified to exit 0 - executors.md in-host coder; the bundled reviewer subagent for Gate 2 - stack.md layout, runtime, and the conventions specific to this repo AGENTS.md is pruned to what only it can say — its layout, conventions and command documentation now live in context/, with the tool-owned block left byte-identical. Two changes make context/standards/ project-owned, so the updater no longer manages that tree: - PHP standards removed, along with every reference to them; this repo has no PHP and the conditional table should not offer it. - templates/biome.json renamed to biome-example.json. Biome 2 loads any file named biome.json anywhere in the tree as a nested config, before files.includes is consulted, which broke pnpm check, lint and format together. No exclusion in the root config can prevent it — only the filename can. Claude-Session: https://claude.ai/code/session_01AvHaMVmjh88MZpKnkvv9PF --- .agents/skills/feature-close/SKILL.md | 102 ++++ .agents/skills/feature-implement/SKILL.md | 142 +++++ .agents/skills/feature-plan/SKILL.md | 138 +++++ .agents/skills/feature-status/SKILL.md | 86 +++ .agents/skills/onboard/SKILL.md | 190 ++++++ .agents/skills/orchestrate/SKILL.md | 92 +++ .agents/skills/roadmap/SKILL.md | 97 ++++ .claude/agents/planner.agent.md | 60 ++ .claude/agents/reviewer.agent.md | 77 +++ .claude/skills/feature-close/SKILL.md | 103 ++++ .claude/skills/feature-implement/SKILL.md | 143 +++++ .claude/skills/feature-plan/SKILL.md | 139 +++++ .claude/skills/feature-status/SKILL.md | 87 +++ .claude/skills/onboard/SKILL.md | 191 +++++++ .claude/skills/orchestrate/SKILL.md | 93 +++ .claude/skills/roadmap/SKILL.md | 98 ++++ AGENTS.md | 154 ++--- CLAUDE.md | 6 + context/.state/manifest.json | 110 ++++ context/README.md | 81 +++ context/archive/.gitkeep | 0 context/drafts/.gitkeep | 0 context/executors.md | 64 +++ context/findings.md | 40 ++ context/history.md | 13 + context/plan-template.md | 69 +++ context/plan-template.notes.md | 55 ++ context/plans/.gitkeep | 0 context/roadmap.md | 29 + context/roles/coder.md | 76 +++ context/stack.md | 102 ++++ context/standards/.source | 8 + context/standards/README.md | 100 ++++ context/standards/architecture/api-design.md | 212 +++++++ .../architecture/dependency-boundaries.md | 104 ++++ .../standards/architecture/feature-driven.md | 102 ++++ .../architecture/folder-structure.md | 108 ++++ context/standards/architecture/monorepos.md | 107 ++++ context/standards/architecture/refactoring.md | 97 ++++ context/standards/architecture/shared-code.md | 81 +++ context/standards/docs/SPEC.md | 540 ++++++++++++++++++ .../standards/examples/bad/data-fetching.tsx | 52 ++ .../standards/examples/bad/derived-state.tsx | 60 ++ .../examples/bad/feature-component.tsx | 80 +++ .../standards/examples/bad/prop-drilling.tsx | 70 +++ .../standards/examples/good/data-fetching.tsx | 53 ++ .../standards/examples/good/derived-state.tsx | 52 ++ .../examples/good/feature-component.tsx | 141 +++++ .../standards/examples/good/invoice.schema.ts | 20 + .../standards/examples/good/prop-drilling.tsx | 83 +++ .../standards/philosophy/ai-agent-behavior.md | 85 +++ .../standards/philosophy/core-principles.md | 49 ++ .../philosophy/incremental-abstraction.md | 56 ++ .../standards/philosophy/maintainability.md | 66 +++ context/standards/philosophy/readability.md | 87 +++ context/standards/react/accessibility.md | 130 +++++ context/standards/react/anti-patterns.md | 187 ++++++ context/standards/react/component-design.md | 139 +++++ context/standards/react/error-boundaries.md | 195 +++++++ context/standards/react/forms.md | 147 +++++ context/standards/react/hooks.md | 88 +++ context/standards/react/memoization.md | 87 +++ context/standards/react/state-management.md | 123 ++++ context/standards/react/testing.md | 118 ++++ context/standards/react/use-effect.md | 110 ++++ context/standards/security/api-security.md | 136 +++++ context/standards/security/auth.md | 112 ++++ context/standards/security/secrets.md | 105 ++++ context/standards/security/validation.md | 89 +++ context/standards/templates/.editorconfig | 16 + context/standards/templates/.gitignore | 50 ++ context/standards/templates/.nvmrc | 1 + context/standards/templates/AGENTS.md | 46 ++ context/standards/templates/CLAUDE.md | 3 + .../standards/templates/biome-example.json | 53 ++ context/standards/templates/eslint.config.js | 61 ++ context/standards/templates/project-readme.md | 76 +++ .../templates/pull-request-template.md | 46 ++ context/standards/templates/tsconfig.json | 35 ++ context/standards/tooling/biome.md | 92 +++ context/standards/tooling/ci.md | 128 +++++ context/standards/tooling/dates.md | 194 +++++++ context/standards/tooling/dependencies.md | 90 +++ context/standards/tooling/nx.md | 113 ++++ context/standards/tooling/observability.md | 183 ++++++ context/standards/tooling/prisma.md | 152 +++++ context/standards/tooling/shadcn.md | 115 ++++ context/standards/tooling/tailwind.md | 135 +++++ context/standards/tooling/tanstack-query.md | 214 +++++++ context/standards/tooling/tanstack-router.md | 365 ++++++++++++ context/standards/tooling/tanstack.md | 31 + context/standards/tooling/vite.md | 135 +++++ context/standards/typescript/anti-patterns.md | 107 ++++ .../standards/typescript/error-handling.md | 216 +++++++ context/standards/typescript/naming.md | 78 +++ context/standards/typescript/rules.md | 159 ++++++ .../standards/typescript/tsconfig/base.json | 32 ++ .../standards/typescript/tsconfig/nextjs.json | 12 + .../standards/typescript/tsconfig/react.json | 9 + context/standards/typescript/validation.md | 104 ++++ context/verify.md | 91 +++ context/workflow.md | 143 +++++ 102 files changed, 9986 insertions(+), 115 deletions(-) create mode 100644 .agents/skills/feature-close/SKILL.md create mode 100644 .agents/skills/feature-implement/SKILL.md create mode 100644 .agents/skills/feature-plan/SKILL.md create mode 100644 .agents/skills/feature-status/SKILL.md create mode 100644 .agents/skills/onboard/SKILL.md create mode 100644 .agents/skills/orchestrate/SKILL.md create mode 100644 .agents/skills/roadmap/SKILL.md create mode 100644 .claude/agents/planner.agent.md create mode 100644 .claude/agents/reviewer.agent.md create mode 100644 .claude/skills/feature-close/SKILL.md create mode 100644 .claude/skills/feature-implement/SKILL.md create mode 100644 .claude/skills/feature-plan/SKILL.md create mode 100644 .claude/skills/feature-status/SKILL.md create mode 100644 .claude/skills/onboard/SKILL.md create mode 100644 .claude/skills/orchestrate/SKILL.md create mode 100644 .claude/skills/roadmap/SKILL.md create mode 100644 CLAUDE.md create mode 100644 context/.state/manifest.json create mode 100644 context/README.md create mode 100644 context/archive/.gitkeep create mode 100644 context/drafts/.gitkeep create mode 100644 context/executors.md create mode 100644 context/findings.md create mode 100644 context/history.md create mode 100644 context/plan-template.md create mode 100644 context/plan-template.notes.md create mode 100644 context/plans/.gitkeep create mode 100644 context/roadmap.md create mode 100644 context/roles/coder.md create mode 100644 context/stack.md create mode 100644 context/standards/.source create mode 100644 context/standards/README.md create mode 100644 context/standards/architecture/api-design.md create mode 100644 context/standards/architecture/dependency-boundaries.md create mode 100644 context/standards/architecture/feature-driven.md create mode 100644 context/standards/architecture/folder-structure.md create mode 100644 context/standards/architecture/monorepos.md create mode 100644 context/standards/architecture/refactoring.md create mode 100644 context/standards/architecture/shared-code.md create mode 100644 context/standards/docs/SPEC.md create mode 100644 context/standards/examples/bad/data-fetching.tsx create mode 100644 context/standards/examples/bad/derived-state.tsx create mode 100644 context/standards/examples/bad/feature-component.tsx create mode 100644 context/standards/examples/bad/prop-drilling.tsx create mode 100644 context/standards/examples/good/data-fetching.tsx create mode 100644 context/standards/examples/good/derived-state.tsx create mode 100644 context/standards/examples/good/feature-component.tsx create mode 100644 context/standards/examples/good/invoice.schema.ts create mode 100644 context/standards/examples/good/prop-drilling.tsx create mode 100644 context/standards/philosophy/ai-agent-behavior.md create mode 100644 context/standards/philosophy/core-principles.md create mode 100644 context/standards/philosophy/incremental-abstraction.md create mode 100644 context/standards/philosophy/maintainability.md create mode 100644 context/standards/philosophy/readability.md create mode 100644 context/standards/react/accessibility.md create mode 100644 context/standards/react/anti-patterns.md create mode 100644 context/standards/react/component-design.md create mode 100644 context/standards/react/error-boundaries.md create mode 100644 context/standards/react/forms.md create mode 100644 context/standards/react/hooks.md create mode 100644 context/standards/react/memoization.md create mode 100644 context/standards/react/state-management.md create mode 100644 context/standards/react/testing.md create mode 100644 context/standards/react/use-effect.md create mode 100644 context/standards/security/api-security.md create mode 100644 context/standards/security/auth.md create mode 100644 context/standards/security/secrets.md create mode 100644 context/standards/security/validation.md create mode 100644 context/standards/templates/.editorconfig create mode 100644 context/standards/templates/.gitignore create mode 100644 context/standards/templates/.nvmrc create mode 100644 context/standards/templates/AGENTS.md create mode 100644 context/standards/templates/CLAUDE.md create mode 100644 context/standards/templates/biome-example.json create mode 100644 context/standards/templates/eslint.config.js create mode 100644 context/standards/templates/project-readme.md create mode 100644 context/standards/templates/pull-request-template.md create mode 100644 context/standards/templates/tsconfig.json create mode 100644 context/standards/tooling/biome.md create mode 100644 context/standards/tooling/ci.md create mode 100644 context/standards/tooling/dates.md create mode 100644 context/standards/tooling/dependencies.md create mode 100644 context/standards/tooling/nx.md create mode 100644 context/standards/tooling/observability.md create mode 100644 context/standards/tooling/prisma.md create mode 100644 context/standards/tooling/shadcn.md create mode 100644 context/standards/tooling/tailwind.md create mode 100644 context/standards/tooling/tanstack-query.md create mode 100644 context/standards/tooling/tanstack-router.md create mode 100644 context/standards/tooling/tanstack.md create mode 100644 context/standards/tooling/vite.md create mode 100644 context/standards/typescript/anti-patterns.md create mode 100644 context/standards/typescript/error-handling.md create mode 100644 context/standards/typescript/naming.md create mode 100644 context/standards/typescript/rules.md create mode 100644 context/standards/typescript/tsconfig/base.json create mode 100644 context/standards/typescript/tsconfig/nextjs.json create mode 100644 context/standards/typescript/tsconfig/react.json create mode 100644 context/standards/typescript/validation.md create mode 100644 context/verify.md create mode 100644 context/workflow.md diff --git a/.agents/skills/feature-close/SKILL.md b/.agents/skills/feature-close/SKILL.md new file mode 100644 index 0000000..d046e56 --- /dev/null +++ b/.agents/skills/feature-close/SKILL.md @@ -0,0 +1,102 @@ +--- +name: feature-close +description: "Retire a finished or abandoned feature — write its context/history.md row, git mv its document to context/archive/, and sweep every reference to its old path for review. Explicit invocation only — run this when the user types /feature-close. Do NOT match on 'we're done with X', 'close this out', or general wrap-up requests." +--- + +# /feature-close + +Owns the **Tier 2 → retired** transition. Nothing else in this workflow archives a plan — +`/feature-implement` detects that a feature is finished and *names* this command; it never does the work. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## Usage + +``` +/feature-close # retire the active feature as shipped +/feature-close "" # retire a named feature as shipped +/feature-close "" --dropped "" # retire one that will not be built +``` + +**Resolving the target:** with no argument, the entry marked `active`. With a name, resolve it against +`context/roadmap.md` — **any entry holding a plan is a valid target**, not just an active one. An abandoned +plan is a droppable state. + +## Mode 1 — `shipped` + +### Refuse first + +Check both before touching anything, and refuse if either fails: + +1. **Every phase in the ledger is `done`.** If not, list the ones that are not, and stop. Do not offer to + mark them. +2. **No open `P0` or `P1` in `context/findings.md` is tied to this feature.** If there is, quote it and + stop. + +A refusal here is the workflow working, not a problem to route around. If the user overrides after being +told, say plainly what is being overridden, then proceed. + +### Then, as one reviewed change + +1. **Move the entry into history.** Remove it from `context/roadmap.md` entirely, and append one row to + `context/history.md`: date, name, outcome `shipped`, a one-line why, and a link into `context/archive/`. + One line. That file **indexes** depth, it does not duplicate it — the reasoning stays in the archived + plan. +2. **`git mv` the plan** from `context/plans/` to `context/archive/`. Use `git mv`, not `mv` — the file's + history is the record of how the feature was actually built. +3. **Rewrite the document's header to point at the history row.** In the moved document, replace + whatever the header claimed before with: + + ```markdown + Retired — its outcome and date are in [`../history.md`](../history.md). + ``` + + Add a note if its §-references are cited from source comments. **Do not stamp the outcome and date + into the document.** `history.md` owns them, and a copy in the header is a second place to maintain. + Replacing the old header is what stops a stale status surviving the move. +4. **Sweep every reference to its old path**, and **show the sweep for review before committing.** + +### The sweep + +Plan documents get cited by path from root-level entry points, from other `context/` files, from skills, +and from inside `context/standards/`. An unattended `git mv` breaks all of them silently. + +```bash +grep -rn "\|" --include='*.md' . | grep -v node_modules +``` + +- **Rewrite links, minding depth.** `context/plans/` and `context/archive/` are the same distance from the + root, so a `../../` link *inside* the moved document still resolves — but a link *to* it from elsewhere + changes. Verify, don't assume. +- **Leave §-number citations alone.** Source comments cite plan sections without a path + (`// SMART-CROP-PLAN.md §7.3`). Those survive the move untouched and must not be "helpfully" rewritten + into paths that will rot. +- **Show the full list of edits before committing.** That review is why this is an explicit command rather + than a side-effect. + +### Finally + +Move every **closed** finding tied to this feature out of `context/findings.md` and into the archived plan's +own log. `findings.md` must not grow for the life of the project. + +## Mode 2 — `--dropped` + +For an entry that will not be built. **There is no ledger check in this mode** — unfinished phases are +expected. + +1. Append a `context/history.md` row with outcome `dropped` (or `superseded by `) and **the reason + the user gave**, verbatim in substance, not softened. That row is what stops the idea being re-proposed, + so a vague reason makes it worthless. +2. Remove the entry from `context/roadmap.md`. +3. **If the entry never had a document, stop here.** If it had one — a draft in `context/drafts/` or a plan + in `context/plans/` — `git mv` it to `context/archive/`, repoint its header at the `history.md` row (no + stamped outcome, same rule as Mode 1), and sweep. + +## Rules + +- **Never delete a plan document.** Archiving keeps the reasoning; deleting throws away the record of a + decision someone will otherwise re-litigate. +- **Never leave `roadmap.md` and `history.md` inconsistent.** An entry is in exactly one of them. +- **Never commit the sweep unreviewed.** +- **Never mark a phase `done` to get past the refusal.** If phases are unfinished, the feature is + unfinished. diff --git a/.agents/skills/feature-implement/SKILL.md b/.agents/skills/feature-implement/SKILL.md new file mode 100644 index 0000000..9f4aaec --- /dev/null +++ b/.agents/skills/feature-implement/SKILL.md @@ -0,0 +1,142 @@ +--- +name: feature-implement +description: "Activate a planned feature in context/roadmap.md and run the next phase of its plan through implementation, verification and review, updating that plan's status ledger. Explicit invocation only — run this when the user types /feature-implement. Do NOT match on 'implement X', 'build this', 'let's code it', or any general request to write code." +--- + +# /feature-implement + +Owns the transition from *has a plan* to *being worked*, **and** the phases within it. One invocation runs +**one phase**: pick it, do it, gate it, close out its ledger row. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model, the one-active-feature rule +and the gate contract. This skill cites those rather than restating them. + +## Usage + +``` +/feature-implement # resolve or choose a feature, then run the next phase +/feature-implement "" # a named feature +``` + +## 1. Resolve the feature + +Read `context/roadmap.md`. + +- **One entry is `active`** → that is the feature. Skip to step 3. +- **None active** → rank the entries whose **Doc** points into `context/plans/` and **ask which to + activate**, using your runtime's question mechanism if it has one. Rank by: open questions resolved → + dependencies shipped (a `history.md` row under it) → smaller first. +- **No entry has a plan** → say so and name `/feature-plan`. Do not plan one here. +- **A name was given** → resolve it against `roadmap.md`. It must have a plan; if it does not, name + `/feature-plan`. + +**Never execute anything out of `context/drafts/`.** A draft has no ledger and is not a plan, however +detailed it looks. + +## 2. The approval checkpoint — before setting the marker + +This is the step that used to be structural: `/feature-plan` stopped and you typed a second command. It is +explicit here now, or it is lost. + +1. **Surface the plan's §8 Open questions and require an acknowledgement.** Do not proceed on silence. +2. **Re-check that the files the plan cites still exist.** A plan drafted a month ago against a + since-changed tree is a state that can now exist and could not before. Name anything that has moved. +3. **Check the one-active-feature rule** in [`context/workflow.md`](../../../context/workflow.md). If + another entry holds the slot, stop and name it. +4. Set the marker to `active`. One token, one place — do not move the entry, add a section, or write a + summary line anywhere. + +## 3. Pick the phase + +Read the plan's status ledger. Take the **lowest-numbered phase that is not `done` and whose `Depends on` +entries are all `done`.** + +**State which phase you picked, and why, in one line, before doing anything else.** + +If it is already `in progress`, **read its Note and resume from there — do not restart it.** + +## 4. Check `context/findings.md` + +An open `P0` or `P1` tied to this phase **is** the work. Fix it before starting anything new. + +## 5. Stop on disagreement + +If the ledger's claim contradicts the repo — a phase marked `done` whose **Files:** do not exist, or work +plainly in the tree under a phase marked `not started` — **say so and stop.** Never silently re-do or skip +a phase on a stale ledger. + +## 6. Do the work + +Read the phase's §6.2 sub-section: its scope, its **Files:**, and what `done` means for it. + +Delegate to a coder per [`context/executors.md`](../../../context/executors.md) if one is configured; +otherwise implement in-host. The coder's system prompt is +[`context/roles/coder.md`](../../../context/roles/coder.md). + +The brief **cites paths, it does not paste files.** Point at `context/standards/README.md` and say to load +per its conditional table; point at `context/stack.md` and the phase's own section. Anything that can read +this repository can open them, and a brief that inlines them is a brief that goes stale. + +Describe **what** needs to happen, never **how** to code it. Scope each delegated task to specific files. + +## 7. Gate 1 — verification + +Per the gate contract in [`context/workflow.md`](../../../context/workflow.md): read +[`context/verify.md`](../../../context/verify.md) and run its sections in order — Lint → Typecheck → Build → +Test. + +**Never carry a copy of these commands here and never invent one.** A missing section is skipped and said +so, never faked. Exit 0 is the verdict regardless of summary text. If `verify.md` does not exist or has no +filled-in section, stop and say so. Docs-only changes run Lint plus a read of the diff. + +A failure is the verdict — go to step 9 with the failing output verbatim as the feedback. + +## 8. Gate 2 — review + +Dispatch per [`context/executors.md`](../../../context/executors.md). With no independent reviewer +configured, review the diff yourself against the plan's review expectations and the standards — weaker, and +**say which one you ran.** + +Require concrete evidence — file paths, command output — for every verdict, and a `P0`–`P3` severity on +every blocking finding. + +- `PASS` or `PASS WITH NOTES` → the phase's work is done; go to step 10. +- `FAIL` → **write it to [`context/findings.md`](../../../context/findings.md) first, then** go to step 9. + +**Write the finding before the loopback, not after it.** A verdict that lives only in this session's +transcript evaporates when the conversation ends — including a `P0` the cap never got to. + +## 9. Loopback + +Cap: **two loops per gate, per phase.** + +Under the cap: re-brief with the prior implementation and the validator's feedback **verbatim — do not +summarise or paraphrase it** — plus the instruction to address only the failing items, refactor nothing +that passes, and expand no scope. Then re-run the same gate. + +At the cap: **write a finding** (`P1` for a Gate 1 cap-out — a phase whose verification cannot pass is +blocked by definition), then escalate to the user with the current state and the last feedback. +**Escalating is not a substitute for recording.** + +## 10. Close out the ledger row + +In the same commit as the work: + +- **All of the phase's scope landed and both gates passed** → `done`. +- **Some landed** → stays `in progress`, Note rewritten to name exactly what remains. +- **A gate hit its cap, or something external blocks it** → `blocked`, with the blocker in the Note. + +**Never mark `done` on a coder's self-report** — the gate output is the evidence. **Refuse `done` while an +open `P0` or `P1` is tied to this phase**; leave it `in progress` and name the finding. + +## 11. Report + +- What changed, and which files. +- Gate 1 output, and Gate 2's verdict. +- Loopback counts, if any. +- Findings written or closed, by id. +- The phase's new ledger status, and which phase is next. + +**When every phase is `done`, say so and name `/feature-close`.** Do not move files, stamp headers or sweep +references — that is a tier boundary, and crossing it is an explicit command the user runs, not a +side-effect of the last phase finishing. diff --git a/.agents/skills/feature-plan/SKILL.md b/.agents/skills/feature-plan/SKILL.md new file mode 100644 index 0000000..a720534 --- /dev/null +++ b/.agents/skills/feature-plan/SKILL.md @@ -0,0 +1,138 @@ +--- +name: feature-plan +description: "Promote one item from the Tier-1 backlog in context/roadmap.md into a Tier-2 plan document under context/plans/, then stop without implementing. Explicit invocation only — run this when the user types /feature-plan. Do NOT match on 'plan out X', 'how should we build X', or any general planning or design request." +--- + +# /feature-plan + +Turns one roadmap entry into a plan document, then **stops**. It never implements anything and never marks +a phase `done` — every phase in a new plan is `not started`. + +**Planning is not activation.** Several features may hold plans at once; there is no "a feature is already +active" refusal here. That is what makes planning ahead possible. Activation is `--activate` or +`/feature-implement`, and both are subject to the one-active-feature rule in +[`context/workflow.md`](../../../context/workflow.md). + +## Usage + +``` +/feature-plan # rank the pending entries and ask which to plan +/feature-plan "" # plan a named entry +/feature-plan "" --activate # plan it, and mark it active +``` + +**Nothing needs to be looked up first.** This command resolves its own starting point. + +## Steps + +### 1. Pick the entry + +Read `context/roadmap.md`. + +**With a name:** take that entry. If none matches, say which names exist and stop. + +**With no argument:** rank the `pending` entries and **ask which to plan**, using your runtime's question +mechanism if it has one, or a plain numbered question if it does not. Writing a plan is a commitment and +takes real work to produce; silently taking the top entry makes that decision on the user's behalf, badly, +whenever the backlog order is stale. + +**Ranking**, in priority order: + +1. **Has a draft** — the entry's **Doc** names a document in `context/drafts/`. Half-researched is better + and cheaper. This dominates: an entry with real notes beats a one-line entry almost regardless. +2. **Unblocked by what just shipped** — it builds on something with a `context/history.md` row, so the + ground under it is settled rather than hypothetical. +3. **Smaller first** — `small`, then `medium`, then `large`. A plan that can be executed beats one that + gets admired. +4. **Backlog order** — ties break by position in the file. + +Offer the top four, **best first**, each with a one-line reason drawn from the ranking — say *why* it is +ranked there, not just what it is. Leave room for the user to name something else. + +Special cases, where asking is noise rather than help: + +- **Exactly one `pending` entry** — state it and proceed. A one-option question is not a choice. +- **No `pending` entries** — say the backlog is empty and name `/roadmap "some idea"`. Do not invent one. + +**State which entry you picked and why, in one line, before doing anything else.** + +### 2. Already planned? Start a conversation, not a refusal + +If the entry's **Doc** already points into `context/plans/`, say so, show the plan, and ask whether to +iterate on it. + +- **Every phase `not started`** → iterate freely. +- **Any phase `in progress`, `blocked` or `done`** → **warn first, and get an answer before writing.** + Rewriting a plan under work that already happened is the "ledger disagrees with the repo" hazard arriving + by a new route. Name which phases have moved. + +### 3. Decide the document + +The plan ends up at `context/plans/-PLAN.md`, where `` is the entry's kebab-case name +upper-cased. + +- **If the entry's **Doc** names a draft in `context/drafts/`**, `git mv` it to that path and build the + plan on top of its content. Use `git mv`, not `mv`. Do not create a second file and do not leave the + draft behind — a draft and a plan for the same feature is two documents disagreeing about one thing. +- **Otherwise** copy `context/plan-template.md` to that path. Copy it verbatim; it is a bare skeleton with + nothing to strip. [`context/plan-template.notes.md`](../../../context/plan-template.notes.md) says what + goes in each section. + +**The draft is the most valuable input you have** — material the user gathered deliberately, often from +somewhere you cannot reach. Carry its specifics forward; do not summarise them away, and do not silently +drop a fact because you could not verify it. Mark it as an open question instead. + +### 4. Research and draft + +Delegate the research and the draft to a planner subagent **if your runtime provides one**; otherwise do it +inline. Either way the brief is the same, and the output contract is the template's section list, not a +planner's own default shape: + +- The roadmap entry verbatim, and the full content of its draft if there was one. +- **The full section list from `context/plan-template.md`, stated as required output**, in order, with the + ledger's exact column set. A general-purpose planner will otherwise emit implementation-steps-and- + acceptance-criteria — a per-phase artifact, not a plan — and you will throw it away. +- Pointers to `context/stack.md`, `context/standards/README.md` (load per its conditional table) and + `context/verify.md`. Cite the paths; do not paste the files in. Anything reading this repo can open them. +- **Cite file paths and command output for every claim about the current codebase.** Anything unverified is + an open question, not an assertion. +- Phases are **commit-sized units with checkable outcomes**, each with a real `Depends on` value and a + **Files:** line naming every path it touches. That line is what makes reconciliation a check rather than + a judgement call. + +### 5. Write the document + +Fill in the template's shape. Then: + +- Date it and point its header at the roadmap entry. +- **No `**Status:**` header.** Feature status lives in `roadmap.md`, phase status in the ledger. A document + that claims its own status is a copy that goes stale. +- Fill in **§8 Open questions** honestly. An honest gap is worth more than an invented decision. +- Every phase is `not started`. + +### 6. Update the roadmap entry + +Repoint the entry's **Doc** field at the new `plans/` path. If you `git mv`d a draft, that same edit is +what fixes the now-dead `drafts/` link, so do it together. + +**Leave the marker alone unless `--activate` was given.** `pending` with a `plans/` document is the correct +state for a planned-but-not-started feature. + +**With `--activate`:** check the one-active-feature rule in +[`context/workflow.md`](../../../context/workflow.md) first. If another entry holds the slot, **write the +plan, skip the activation, and name the feature that holds it.** The plan is valuable and harmless on its +own; discarding it over a marker would undo the point of the split. + +### 7. Report and stop + +State the document path, the phase count, and the open questions. Then say plainly that **what you produced +is a reviewable skeleton plus open questions, not a finished plan of record** — the value is the structure +and the research. Name the next step: the user reviews and edits the plan, and `/feature-implement` runs it +once they are satisfied. + +## Rules + +- **Never implement anything.** Not "just the first phase", not "a quick scaffold". +- **Never mark a phase `done`**, and never mark a phase anything other than `not started`. +- **Never write outside `context/`.** No source files, no config. +- Do not fold the draft's content into `context/roadmap.md`. Tier 1 stays high-level. diff --git a/.agents/skills/feature-status/SKILL.md b/.agents/skills/feature-status/SKILL.md new file mode 100644 index 0000000..d419921 --- /dev/null +++ b/.agents/skills/feature-status/SKILL.md @@ -0,0 +1,86 @@ +--- +name: feature-status +description: "Read-only report of where the active feature stands — its plan's phase ledger, open findings, and git state — ending with exactly one next action. Explicit invocation only — run this when the user types /feature-status. Do NOT match on 'what's the status', 'where are we', or general progress questions." +--- + +# /feature-status + +The read-only "where do things stand" view. **It writes nothing, commits nothing, and invokes no other +agent.** + +**It is never a prerequisite.** Every other command resolves its own starting point — nobody has to run +this first. It exists for when *you* want to know. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## 1. Read, in this order + +1. `context/roadmap.md` — which entry is `active`, and what each entry's **Doc** points at. +2. That plan document's **status ledger**, and each phase's **Files:** line. +3. `context/findings.md` — open findings, and which phase each is tied to. +4. Git state — `git status --short` and the last few commits. + +**Nothing is cached and nothing is parsed by a script.** Read the ledger every time. That is the property +that makes hand-editing a row change this command's answer immediately, with no regeneration step. + +## 2. Reconcile before trusting the ledger + +Report and **stop** on any of these: + +- A phase marked `done` whose **Files:** or commits do not exist. +- A phase marked `not started` whose work is plainly already in the tree. +- An entry marked `active` pointing at a document that does not exist. +- An entry marked `active` for a feature that already has a `context/history.md` row. +- **A document in `context/plans/` that no roadmap entry points at.** + +Do not resolve a discrepancy yourself, and do not pick a next action off a ledger you have just shown to be +stale. That is the exact failure this workflow exists to prevent. + +**Two things that are not discrepancies:** + +- Every phase `done` while the entry still reads `active` — that is the normal state before + `/feature-close`. Next action 5 handles it. +- A `done` row with its changes still in the working tree — a phase finished but not committed. Name it in + the report; do not stop on it. + +## 3. Report + +Keep it short. The user is asking a question, not reading a document. + +``` +Feature: (or: none active) +Plan: +Phases: done · in progress · blocked · not started +Findings: open () (or: none open) + +Next: +``` + +Under the header, list only the phases that are **not** `done`, one line each with their Note. Do not +re-print the whole ledger. + +## 4. Name exactly one next action + +In priority order — take the **first** that applies and name only it: + +1. An open `P0` or `P1` → fix it. Quote the finding's id and its closing condition. +2. A phase **`in progress`** → resume it, quoting its Note. Do not restart it. +3. A phase **`blocked`** with every other phase `done` → report the blocker; the next action is the user's. +4. A phase `done` with a next **unblocked** phase → `/feature-implement`, naming the phase it will pick. +5. **Every** phase in the active plan `done` → `/feature-close`. +6. **No active feature, but at least one entry has a plan** → `/feature-implement`, which ranks the planned + entries and asks. +7. **No plans, at least one `pending` entry** → `/feature-plan`. **Do not pick a candidate yourself** — + that command ranks the backlog and asks, and naming one here would either duplicate its ranking or + contradict it. +8. **Nothing at all** → `/roadmap "some idea"`. + +"Exactly one" is the point. A list of three things to consider is what this command exists to replace. + +## Rules + +- **Read-only. No exceptions.** Not the ledger, not the roadmap, not a finding, not a "quick fix while I'm + here". If you spot something that needs changing, name it as the next action and let the user decide. +- **Never invoke another agent.** +- **Never mark anything.** Reporting that a phase looks finished is not marking it `done`; only + `/feature-implement` does that, on gate evidence. diff --git a/.agents/skills/onboard/SKILL.md b/.agents/skills/onboard/SKILL.md new file mode 100644 index 0000000..1d29b78 --- /dev/null +++ b/.agents/skills/onboard/SKILL.md @@ -0,0 +1,190 @@ +--- +name: onboard +description: "Fill in this project's own workflow stubs — context/verify.md, context/executors.md and context/stack.md — by adopting what the repository already documented, asking wherever a claim's destination is unclear, and running each candidate verification command so only the ones that pass are written down. Explicit invocation only — run this when the user types /onboard. Do NOT match on 'set up the project', 'get started', or general setup requests." +--- + +# /onboard + +Fills the project-owned stubs the installer deliberately left empty, and folds whatever the repository +already documented into them. **Re-runnable** — run it again after the stack changes, and it re-proposes +against what is there now. + +**Asking is not guessing.** The installer could have detected a test command and written it in; that is +exactly how a file ends up naming a command that has never run. This command asks, and where it can, it +*checks*. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## What it writes + +| File | Gets | +|---|---| +| [`context/verify.md`](../../../context/verify.md) | the real Lint / Typecheck / Build / Test commands — **only ones that exited 0** | +| [`context/executors.md`](../../../context/executors.md) | how this project dispatches a coder and a reviewer | +| [`context/stack.md`](../../../context/stack.md) | runtime, layout, conventions | +| [`AGENTS.md`](../../../AGENTS.md) | pruned, on confirmation, of what moved into those three. The region between the `ai-workflow` markers is never touched | + +Show every proposed edit before writing it, and **do not commit.** The user reviews and commits. + +## Step 1 — Adopt what the repository already says + +The installer appends its block to `AGENTS.md` and leaves the rest of that file alone, so a repository that +documented itself before the overlay arrived now states some of the same things twice, in two places, with +no rule about which wins. Resolving that is this step, and it runs first because what turns up here is the +raw material for every step below. + +**Read, in this order:** everything in `AGENTS.md` *outside* the `ai-workflow` markers, then `CLAUDE.md` +apart from its import line. If the repository carries other agent-instruction files — a nested `AGENTS.md` +under a subdirectory, a directory of per-host instruction files — **list them and stop there.** Say they +exist and that this command left them alone. A nested file usually scopes to its own subtree, and moving it +up is a decision rather than a cleanup. + +Break what you read into claims — a paragraph, a table row, a bullet — and propose a destination for each: + +| A claim about | Goes to | +|---|---| +| what the project is, its layout, its conventions | `context/stack.md` | +| a lint, typecheck, build or test command | a **candidate** for `context/verify.md` — Step 5 still has to run it | +| how a coder or a reviewer is dispatched | `context/executors.md` | +| a rule the bundled standards already state | nowhere — the standards own it. Ask before dropping | +| planning or review process this workflow now owns | nowhere — superseded. Ask before dropping | +| anything else — ownership, contacts, external links | stays in `AGENTS.md`, outside the block | + +**Show the whole table before moving anything**, one row per claim, and let the user correct any +destination. Two kinds of row are never decided alone: + +- **Unsure** — a claim you cannot place, or that fits two files equally well. Ask. Do not quietly pick the + likelier one: a misfiled convention is a rule nobody reads again. +- **Contradicts** — the existing prose and the installed workflow give different answers to the same + question. Where project-local skills live, what the review process is, which file is authoritative. + **Quote both and ask which stands.** Never resolve one silently in either direction — the older text is + often right about this project and wrong only about the overlay. + +An adopted claim is an *input* to the steps below, not a substitute for them. A command lifted out of the +old file is a candidate like any other and still has to run. + +**Nothing is deleted here.** Pruning is Step 7, after the destination files exist. + +On a re-run, a claim its destination file already states is already adopted. Say so in a line and move on. + +## Step 2 — Coder dispatch + +Ask whether implementation runs **in-host** or is **offloaded** to an external coder CLI. + +- **In-host** is the default, and a valid configuration rather than a gap. Leave the Coder section of + `context/executors.md` saying so. +- **Offloaded** — the user names the invocation. Write it into `context/executors.md` verbatim, including + any directory or permission scoping it needs on this machine. Its system prompt is + [`context/roles/coder.md`](../../../context/roles/coder.md). + +If an external coder is named, **test one assumption before writing it down**: that it can read this +repository unaided. Briefs cite paths rather than pasting file contents, so everything downstream depends +on that being true, and it is true of some executors and not others. + +1. Pick a fact only available by opening a file here — a heading partway down `context/workflow.md` does. +2. Send a one-line brief that cites the path and asks for that fact. Nothing else. +3. If the fact comes back, record in `context/executors.md` that repository reads were verified, and when. +4. If it comes back empty, guessed, or refused, record that **this executor needs content inline** — the + one case where a brief carries file contents instead of paths. + +Never write down an invocation you have not run. This is the same rule as Step 5, for the same reason. + +## Step 3 — Reviewer dispatch + +Ask how Gate 2 should get a review: + +- **The host reviews the diff itself** against the plan's review checklist and the standards. That is the + default. It is weaker than an independent reviewer, and any command that runs the gate must say which one + it ran. +- **An external reviewer** — the user names the invocation. Write it into `context/executors.md` verbatim, + including any scoping it needs on this machine. + +A host that offers review usually offers more than one shape of it — a review subcommand, a review skill it +can be asked to run, or both — and they do not review alike. **Find out what this host actually provides +rather than assuming**, show the user what you found, and let them choose. Nothing shipped here names a +winner: it differs per host and changes underneath you. What ships is the contract, not the command. + +Whatever is chosen, that contract stands: a review happens, every blocking finding carries a `P0`–`P3` +severity, and a `FAIL` writes a finding before the loopback. + +## Step 4 — Standards source + +`context/standards/` ships with a bundled default. Ask whether that is right for this project. + +- **Keep it** — nothing to do. It stays tool-owned and updates with the tool. +- **Swap it** — the user gives a git URL, and the swap is + `npx @baldurpan/create-ai-workflow standards add `. Tell them that command rather than cloning + it yourself: it validates that the tree has a usable conditional-loading table, and whatever lands + becomes project-owned from that point. + +Say plainly what the default is and that a wrong set is not inert — agents load from that README's +conditional table unprompted, on every task. If Step 1 turned up house rules that the bundled set already +covers, this is the moment that matters: keeping both means the project has two answers. + +## Step 5 — Verification commands + +**This is the most valuable step in this command.** Do it properly. + +1. **Propose candidates.** Take the command claims Step 1 adopted, then read `package.json` scripts, or the + stack's equivalent — `Makefile`, `composer.json`, `pyproject.toml`, `Cargo.toml`, the CI workflow. The + CI config is the best source available: it lists commands that demonstrably run in a clean checkout. A + command the old file named and CI does not is worth asking about — one of the two is stale. +2. **Show the candidates and ask** which belong in Lint, Typecheck, Build and Test, and whether anything is + missing. Ask about prerequisites too — a package manager version, an install step, a service that must + be up. +3. **Run each one.** Actually run it, from the repo root. +4. **Write only the commands that exited 0.** For each one that failed, show the output and ask: fix it, + replace it, or leave that section empty. **Never write a command that has not passed** — an inherited + one least of all, since it is the likeliest to have rotted. An empty section is skipped by Gate 1 and + says so; a wrong command fails a gate on every task until someone notices. +5. Put anything that needs Docker, a cloud account or a deploy target under **Not run by Gate 1**, so + nobody promotes it into a gate section by mistake. + +Explain what you are doing: this turns `verify.md` from someone's guess into something verified at install +time, which is the one moment it is cheap to catch. + +## Step 6 — Stack + +Start from what Step 1 routed here, show it back as a draft, and ask only for the gaps: + +- What does this project do, in a paragraph — and anything about its history that explains its shape. +- Runtime, package manager, database, storage, hosting. +- The directories that matter, one line each. +- **The conventions that would not be guessed** — what breaks in this runtime, what is deliberately kept + separate, where local secrets live, what must never be run against production. This section is the one + that earns its keep; the rest is discoverable. Inherited prose is usually strongest here and weakest at + describing layout, which drifts. + +Point out that anything else added under `context/` should be indexed in `stack.md`, not in +`context/README.md`, which is tool-owned and replaced on update. + +## Step 7 — Prune the sources + +Only now, with the three stubs written, remove from `AGENTS.md` and `CLAUDE.md` what has landed elsewhere. +Duplication left standing is the failure this step exists to prevent: two statements of one fact drift, and +the stale copy is indistinguishable from the live one. + +- **Show the removal as a diff and ask before applying it.** Whole sections at a time, not scattered lines. +- **Remove only what you can point at.** For each deletion name the file and section that now holds it. A + claim you could not place stays exactly where it is — an unpruned file is a smaller problem than a lost + rule. +- **Never touch the region between the `ai-workflow` markers.** It is tool-owned and replaced on update, so + an edit there is an edit lost, and nothing migrates into it. +- What should be left is what only `AGENTS.md` can say: the repository's own front matter, and the block's + pointer into `context/`. + +Keeping the original prose in place is a valid answer. If the user chooses it, write one line in +`context/stack.md` saying which file is authoritative, so the next reader is not left to guess. + +## Rules + +- **Copy before cut.** Nothing leaves `AGENTS.md` until the file that replaces it is written and shown. +- **Never write a credential.** Write `$SENTRY_DSN`-style placeholders and name where the real value lives + — this command collects shell commands, which is the most likely place a token appears inline. Inherited + prose gets the same read before it moves. See the standing rule in + [`context/workflow.md`](../../../context/workflow.md). +- **Never write a command you have not run.** +- **Never delete a claim you could not place.** +- **Never touch a tool-owned file.** `README.md`, `workflow.md`, `plan-template*.md` and `roles/` are + replaced on the next update; an edit there is an edit lost. +- **Do not commit.** diff --git a/.agents/skills/orchestrate/SKILL.md b/.agents/skills/orchestrate/SKILL.md new file mode 100644 index 0000000..7d43917 --- /dev/null +++ b/.agents/skills/orchestrate/SKILL.md @@ -0,0 +1,92 @@ +--- +name: orchestrate +description: "Run one ad-hoc, commit-sized change through the same verification and review gates the feature loop uses, without a roadmap entry or a phase ledger. Explicit invocation only — run this when the user types /orchestrate. Do NOT match on 'build X', 'implement X', 'orchestrate the work', or any request that belongs to a planned feature." +--- + +# /orchestrate + +A gated one-shot pass over a scope you name. No roadmap entry, no ledger, **no tier boundary crossed.** + +It exists because the valuable part of the loop is the **gate machinery** — Gate 1 reading `verify.md`, +Gate 2's reviewer, failures landing in `findings.md` before the loopback — and that is worth having for +unplanned work too, arguably most of all, since that is where fixes get cowboyed. Without it, the only +route to a verified, reviewed change is to file a roadmap entry, and people will route around the workflow +for small things. + +Read [`context/workflow.md`](../../../context/workflow.md) for the gate contract and the feature/task rule. + +## Usage + +``` +/orchestrate "" +``` + +## 0. Sweep first + +Move every **closed** finding tied to `ad-hoc` out of `context/findings.md`. Those belong to no feature, so +nothing else would ever retire them and the file would grow forever. Say what you swept. + +## 1. Refuse, before anything else + +Two guards, or this becomes the way to skip planning: + +1. **Refuse anything that is not commit-sized.** A commit-sized unit has one checkable outcome. A category + of activity ("add tests", "improve error handling", "refactor the API layer") is not one. Say what the + scope would need to be split into, and name `/roadmap`. +2. **Refuse anything an existing roadmap entry already covers.** Read `context/roadmap.md` and check. If + one covers it, say which, and name `/feature-plan` and `/feature-implement`. + +Apply the standing test from [`context/workflow.md`](../../../context/workflow.md): *if you would want a +`history.md` row for it, it is a feature.* Ask that question out loud and answer it before proceeding. + +A refusal here is the workflow working. + +## 2. Do the work + +Read `context/stack.md` and load `context/standards/README.md` per its conditional table. + +Delegate to a coder per [`context/executors.md`](../../../context/executors.md) if one is configured; +otherwise implement in-host. The coder's system prompt is +[`context/roles/coder.md`](../../../context/roles/coder.md). The brief **cites paths, it does not paste +files.** Describe what needs to happen, never how to code it. + +## 3. Gate 1 — verification + +Read [`context/verify.md`](../../../context/verify.md) and run its sections in order: Lint → Typecheck → +Build → Test. **Never carry a copy of these commands here and never invent one.** A missing section is +skipped and said so, never faked. Exit 0 is the verdict regardless of summary text. If `verify.md` does not +exist or has no filled-in section, stop and say so. Docs-only changes run Lint plus a read of the diff. + +## 4. Gate 2 — review + +Dispatch per [`context/executors.md`](../../../context/executors.md). With no independent reviewer +configured, review the diff yourself against the standards — weaker, and **say which one you ran.** + +Require concrete evidence — file paths, command output — for every verdict, and a `P0`–`P3` severity on +every blocking finding. + +- `PASS` or `PASS WITH NOTES` → done. +- `FAIL` → **write it to [`context/findings.md`](../../../context/findings.md) first, then** loop back. + +Findings raised here are recorded with **`Tied to: ad-hoc`**. + +## 5. Loopback + +Cap: **two loops per gate.** Re-brief with the prior implementation and the validator's feedback +**verbatim**, plus the instruction to address only the failing items, refactor nothing that passes, and +expand no scope. + +At the cap: write a finding (`P1` for a Gate 1 cap-out), then escalate with the current state and the last +feedback. **Escalating is not a substitute for recording.** + +## 6. Report + +What changed, the Gate 1 output, the Gate 2 verdict, any loopbacks, and any findings written, closed or +swept — by id. + +## Rules + +- **No ledger row is touched.** This command has no phase and does not belong to a feature. +- **No roadmap entry is created, activated or retired.** If the work turns out to be a feature, stop and + say so; the user runs `/roadmap`. +- **Never skip Gate 1 to save time.** The gates are the entire reason this command exists. diff --git a/.agents/skills/roadmap/SKILL.md b/.agents/skills/roadmap/SKILL.md new file mode 100644 index 0000000..1753687 --- /dev/null +++ b/.agents/skills/roadmap/SKILL.md @@ -0,0 +1,97 @@ +--- +name: roadmap +description: "Print the Tier-1 feature backlog in context/roadmap.md, or append one new pending entry to it. Explicit invocation only — run this when the user types /roadmap. Do NOT match on general planning talk, 'what should we build next', or any request to design, plan, or implement a feature." +--- + +# /roadmap + +Maintains **Tier 1** — the backlog in `context/roadmap.md`. It never promotes anything, never writes a +plan, never marks anything `active`, and never removes an entry. + +It *may* write a **draft** in `context/drafts/` — raw reference material the user supplied, which +`/feature-plan` later turns into a plan. Keep that line straight: capturing what someone told you is not +designing. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## Usage + +``` +/roadmap # print the backlog — read-only +/roadmap "some idea" # append one pending entry +``` + +## No arguments — print the backlog + +1. Read `context/roadmap.md`. +2. Print every entry: name, marker, size, the one-line why, and what its **Doc** field points at. +3. Say which entries have a plan (**Doc** into `plans/`) and which do not. Those are different facts from + the `pending`/`active` marker, and reporting them as one is the mistake the two fields exist to prevent. +4. If nothing is `active`, name `/feature-plan` as the way to get there. **Do not pick a candidate** — that + command ranks the backlog and asks. + +**Do not** read a plan's ledger or report phase status. That is `/feature-status`. This command answers +"what is on the list", not "what is next". + +## With an argument — append an entry + +1. Read `context/roadmap.md` for the existing entries and the format already in the file. +2. **Check for an entry that already covers the idea.** If one exists, say so, show it, and stop — do not + add a near-duplicate. Also check `context/history.md`: an idea previously `dropped` has a recorded + reason, and re-proposing it needs that reason addressed, not ignored. +3. Derive a **kebab-case name**. It becomes the entry's identity and is what `/feature-plan`, + `/feature-implement` and `/feature-close` are given later, so make it specific and stable. +4. Append to the end of the **Features** list, matching the file's format. It is always `pending` — the + marker in the heading is the entry's status, and this command never sets any other value: + +```markdown +### — `pending` + + + +- **Size:** +- **Doc:** none yet +``` + +5. **If the user supplied reference material, capture it** — see below. Otherwise leave `**Doc:** none yet`. +6. Show the appended entry, and the draft if you wrote one, then stop. + +### Capturing reference material + +**Capture beats summarise.** When the user gives you more than the idea — a screenshot of documentation, a +pasted setup guide, a URL, an error dump, a long explanation of how they want it to work — that material +does not go in the entry and must not be thrown away. Write it to `context/drafts/.md` and point the +entry's **Doc** field at it: + +```markdown +- **Doc:** [`drafts/.md`](drafts/.md) — +``` + +`drafts/` is the right directory and `plans/` is not. A draft is notes: no ledger, no template sections, +nothing executable. `/feature-plan` moves it into `plans/` when it writes the plan, which is also why you +never write directly into `plans/` from here. + +What to write in it: + +- **The specifics that are expensive to re-derive** — exact package names, version or compatibility + requirements, config keys, the shape of an API call, the wording of an error. +- **Where it came from, and when.** A screenshot of vendor docs on a date is worth more than the same facts + with no provenance, because docs move. +- **What it means for *this* repo.** Check the relevant config or source and say what already holds and + what would have to change. This is the part a screenshot cannot tell you, and the part that rots slowest. +- **Never transcribe a credential.** See the standing rule in + [`context/workflow.md`](../../../context/workflow.md): a DSN, token or key gets described and pointed at + the secret store, never copied into a tracked file. + +Keep the entry itself one or two lines regardless. + +## Rules + +- **One or two lines of why. No more.** If you find yourself writing a third paragraph, that is a signal the + idea is ready for `/feature-plan`, not that the entry should be longer. +- **Never guess at a design.** The entry records that a thing is wanted, not how it would work. +- **Never mark anything `active`.** Only `/feature-plan --activate` and `/feature-implement` do that. +- **Never remove an entry.** Entries leave only via `/feature-close`, which records why. Deleting one loses + the reason it was dropped, which is the whole point of `history.md`. +- If the idea is really several ideas, say so and offer to add them as separate entries rather than writing + one vague entry covering all of them. diff --git a/.claude/agents/planner.agent.md b/.claude/agents/planner.agent.md new file mode 100644 index 0000000..45474ee --- /dev/null +++ b/.claude/agents/planner.agent.md @@ -0,0 +1,60 @@ +--- +name: planner +description: Researches the codebase and the relevant documentation and drafts a plan. Use when a plan is needed before implementing a feature or fixing a complex issue. Does not write code. +model: inherit +tools: Read, Grep, Glob, Bash, WebFetch, WebSearch +--- + +# Planner + +You research and draft. You do **not** write code, and you do not edit files. + +## Research + +You do your own research — a subagent cannot dispatch further subagents. Use `Read`, `Grep` and `Glob` for +the codebase, a documentation MCP server if one is configured, and `WebFetch` / `WebSearch` for anything it +does not cover. `Bash` is for read-only inspection (`git log`, `ls`, `--version`) — never for writing. + +Do not assume a library's current API from memory. Verify it, and cite what you confirmed. + +## The output contract comes from the brief + +**Your brief names the required output shape. It overrides everything below.** When it names a template's +section list, that section list *is* your output — in that order, with the ledger's exact column set. A +plan document and a per-phase work item are different artifacts, and producing the wrong one wastes the +run. + +The default shape below applies only when the brief names none. + +## Workflow + +1. **Load the standards.** Read `context/standards/README.md` and load the files its conditional-loading + table points at for this task. Your checklist items must reference specific rules from those files, not + generic principles. +2. **Read the project.** `context/stack.md` for what this repo is; `context/verify.md` for how it proves + itself. Search the codebase and find the patterns that already exist. +3. **Verify externals.** Check documentation for every library and API involved. Cite what you confirmed. +4. **Consider.** Edge cases, error states, and implicit requirements the request did not mention. +5. **Draft.** Say *what* needs to happen, not *how* to code it. + +## Default output shape + +**Summary** — one paragraph on the change and its intent. + +**Steps** — ordered. Each one: what needs to happen, the explicit file paths it creates or modifies, the +testable acceptance criteria, and the review checklist items (naming the specific standards that apply). + +**Edge cases** — the cross-cutting ones not tied to a single step. + +**Open questions** — anything you could not resolve. Mark them clearly. + +## Rules + +- **Cite a file path or command output for every claim about the current codebase.** An assertion about + what a file contains needs the file read, not recalled. +- **Anything you could not verify is an open question, not an assertion.** An honest gap is worth more than + an invented decision. +- Acceptance criteria must be **testable** — say what passes and what fails, not "should work well". +- Review checklist items must name **specific standards**, not "follow best practices". +- **Name no verification command.** `context/verify.md` is the only file in this project that does. Point + at it. diff --git a/.claude/agents/reviewer.agent.md b/.claude/agents/reviewer.agent.md new file mode 100644 index 0000000..bd16d58 --- /dev/null +++ b/.claude/agents/reviewer.agent.md @@ -0,0 +1,77 @@ +--- +name: reviewer +description: Reviews a completed implementation against the plan's review checklist and the project's engineering standards, returning a verdict with severities and cited evidence. Use as the review gate after verification passes. +model: inherit +tools: Read, Grep, Glob, Bash +--- + +# Reviewer + +You review a completed implementation. You are an **evidence-gatherer first and a judge second**. + +You are called after verification has passed. You receive the implementation output, the plan's review +checklist, and the verification result — for context, not for re-running. + +Your verdict gates completion: + +- **PASS**, or **PASS WITH NOTES** the caller accepts → the work is complete. +- **FAIL** → the caller writes a finding and loops back. Cap: two loops. + +## Do these in order + +### 1. Gather evidence, before forming any opinion + +- For every file in the brief's file list: if its content is inlined in the prompt, treat that as the + source of truth. Otherwise `Read` it. Do this **before** writing a summary, before forming a verdict. +- For every "missing X" claim you are considering: run `grep -n "" `. The empty output is + your evidence. Cite the search you ran. +- For every standards rule you cite: open the file and quote the rule verbatim. Load them per the + conditional table in `context/standards/README.md`. +- Read the diff: `git diff` for uncommitted work, `git diff ...HEAD` against a base branch. + +**You may not cite a line, quote code, or reference a file you have not opened.** If you find yourself +writing "line 42 says X", verify that line 42 says X. If you cannot, write +`NOT VERIFIED: I do not have access to :42`. + +### 2. Walk the checklist + +For every item in the plan's review checklist, write a verdict, using only the evidence from step 1. + +### 3. Form blocking findings + +A finding is **blocking** only if all three hold: + +1. It corresponds to a specific review checklist item, or violates a standard the plan referenced. +2. You have a verbatim quote from the actual file as evidence — line number and content. +3. The fix is specific and actionable in one targeted edit. + +If you cannot satisfy all three, it is a **non-blocking note**, not a blocking finding. + +### 4. Write the verdict + +``` +Verdict: PASS | PASS WITH NOTES | FAIL (severity: P0 | P1 | P2 | P3) + +Evidence gathered +- : +- + +Checklist +- + +Blocking findings (each with a severity, the file, and the specific fix) +Non-blocking notes +``` + +Every blocking finding carries a `P0`–`P3` severity, and the verdict carries the highest of them. +`P0` breaks production or data. `P1` blocks a phase or a gate. `P2` is a real defect that does not block. +`P3` is a note worth not losing. + +## Rules + +- **Never invent evidence.** These rules exist because past reviews have. +- **Do not re-run verification.** It already ran; you are reading the code. +- **Do not fix anything.** You review; the coder fixes. +- **Name no verification command.** `context/verify.md` is the only file in this project that does. +- Scope is the change, not the repository. You are judging what landed, not everything that was already + there. diff --git a/.claude/skills/feature-close/SKILL.md b/.claude/skills/feature-close/SKILL.md new file mode 100644 index 0000000..f9fc7d0 --- /dev/null +++ b/.claude/skills/feature-close/SKILL.md @@ -0,0 +1,103 @@ +--- +name: feature-close +description: "Retire a finished or abandoned feature — write its context/history.md row, git mv its document to context/archive/, and sweep every reference to its old path for review. Explicit invocation only — run this when the user types /feature-close. Do NOT match on 'we're done with X', 'close this out', or general wrap-up requests." +disable-model-invocation: true +--- + +# /feature-close + +Owns the **Tier 2 → retired** transition. Nothing else in this workflow archives a plan — +`/feature-implement` detects that a feature is finished and *names* this command; it never does the work. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## Usage + +``` +/feature-close # retire the active feature as shipped +/feature-close "" # retire a named feature as shipped +/feature-close "" --dropped "" # retire one that will not be built +``` + +**Resolving the target:** with no argument, the entry marked `active`. With a name, resolve it against +`context/roadmap.md` — **any entry holding a plan is a valid target**, not just an active one. An abandoned +plan is a droppable state. + +## Mode 1 — `shipped` + +### Refuse first + +Check both before touching anything, and refuse if either fails: + +1. **Every phase in the ledger is `done`.** If not, list the ones that are not, and stop. Do not offer to + mark them. +2. **No open `P0` or `P1` in `context/findings.md` is tied to this feature.** If there is, quote it and + stop. + +A refusal here is the workflow working, not a problem to route around. If the user overrides after being +told, say plainly what is being overridden, then proceed. + +### Then, as one reviewed change + +1. **Move the entry into history.** Remove it from `context/roadmap.md` entirely, and append one row to + `context/history.md`: date, name, outcome `shipped`, a one-line why, and a link into `context/archive/`. + One line. That file **indexes** depth, it does not duplicate it — the reasoning stays in the archived + plan. +2. **`git mv` the plan** from `context/plans/` to `context/archive/`. Use `git mv`, not `mv` — the file's + history is the record of how the feature was actually built. +3. **Rewrite the document's header to point at the history row.** In the moved document, replace + whatever the header claimed before with: + + ```markdown + Retired — its outcome and date are in [`../history.md`](../history.md). + ``` + + Add a note if its §-references are cited from source comments. **Do not stamp the outcome and date + into the document.** `history.md` owns them, and a copy in the header is a second place to maintain. + Replacing the old header is what stops a stale status surviving the move. +4. **Sweep every reference to its old path**, and **show the sweep for review before committing.** + +### The sweep + +Plan documents get cited by path from root-level entry points, from other `context/` files, from skills, +and from inside `context/standards/`. An unattended `git mv` breaks all of them silently. + +```bash +grep -rn "\|" --include='*.md' . | grep -v node_modules +``` + +- **Rewrite links, minding depth.** `context/plans/` and `context/archive/` are the same distance from the + root, so a `../../` link *inside* the moved document still resolves — but a link *to* it from elsewhere + changes. Verify, don't assume. +- **Leave §-number citations alone.** Source comments cite plan sections without a path + (`// SMART-CROP-PLAN.md §7.3`). Those survive the move untouched and must not be "helpfully" rewritten + into paths that will rot. +- **Show the full list of edits before committing.** That review is why this is an explicit command rather + than a side-effect. + +### Finally + +Move every **closed** finding tied to this feature out of `context/findings.md` and into the archived plan's +own log. `findings.md` must not grow for the life of the project. + +## Mode 2 — `--dropped` + +For an entry that will not be built. **There is no ledger check in this mode** — unfinished phases are +expected. + +1. Append a `context/history.md` row with outcome `dropped` (or `superseded by `) and **the reason + the user gave**, verbatim in substance, not softened. That row is what stops the idea being re-proposed, + so a vague reason makes it worthless. +2. Remove the entry from `context/roadmap.md`. +3. **If the entry never had a document, stop here.** If it had one — a draft in `context/drafts/` or a plan + in `context/plans/` — `git mv` it to `context/archive/`, repoint its header at the `history.md` row (no + stamped outcome, same rule as Mode 1), and sweep. + +## Rules + +- **Never delete a plan document.** Archiving keeps the reasoning; deleting throws away the record of a + decision someone will otherwise re-litigate. +- **Never leave `roadmap.md` and `history.md` inconsistent.** An entry is in exactly one of them. +- **Never commit the sweep unreviewed.** +- **Never mark a phase `done` to get past the refusal.** If phases are unfinished, the feature is + unfinished. diff --git a/.claude/skills/feature-implement/SKILL.md b/.claude/skills/feature-implement/SKILL.md new file mode 100644 index 0000000..99ed27f --- /dev/null +++ b/.claude/skills/feature-implement/SKILL.md @@ -0,0 +1,143 @@ +--- +name: feature-implement +description: "Activate a planned feature in context/roadmap.md and run the next phase of its plan through implementation, verification and review, updating that plan's status ledger. Explicit invocation only — run this when the user types /feature-implement. Do NOT match on 'implement X', 'build this', 'let's code it', or any general request to write code." +disable-model-invocation: true +--- + +# /feature-implement + +Owns the transition from *has a plan* to *being worked*, **and** the phases within it. One invocation runs +**one phase**: pick it, do it, gate it, close out its ledger row. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model, the one-active-feature rule +and the gate contract. This skill cites those rather than restating them. + +## Usage + +``` +/feature-implement # resolve or choose a feature, then run the next phase +/feature-implement "" # a named feature +``` + +## 1. Resolve the feature + +Read `context/roadmap.md`. + +- **One entry is `active`** → that is the feature. Skip to step 3. +- **None active** → rank the entries whose **Doc** points into `context/plans/` and **ask which to + activate**, using your runtime's question mechanism if it has one. Rank by: open questions resolved → + dependencies shipped (a `history.md` row under it) → smaller first. +- **No entry has a plan** → say so and name `/feature-plan`. Do not plan one here. +- **A name was given** → resolve it against `roadmap.md`. It must have a plan; if it does not, name + `/feature-plan`. + +**Never execute anything out of `context/drafts/`.** A draft has no ledger and is not a plan, however +detailed it looks. + +## 2. The approval checkpoint — before setting the marker + +This is the step that used to be structural: `/feature-plan` stopped and you typed a second command. It is +explicit here now, or it is lost. + +1. **Surface the plan's §8 Open questions and require an acknowledgement.** Do not proceed on silence. +2. **Re-check that the files the plan cites still exist.** A plan drafted a month ago against a + since-changed tree is a state that can now exist and could not before. Name anything that has moved. +3. **Check the one-active-feature rule** in [`context/workflow.md`](../../../context/workflow.md). If + another entry holds the slot, stop and name it. +4. Set the marker to `active`. One token, one place — do not move the entry, add a section, or write a + summary line anywhere. + +## 3. Pick the phase + +Read the plan's status ledger. Take the **lowest-numbered phase that is not `done` and whose `Depends on` +entries are all `done`.** + +**State which phase you picked, and why, in one line, before doing anything else.** + +If it is already `in progress`, **read its Note and resume from there — do not restart it.** + +## 4. Check `context/findings.md` + +An open `P0` or `P1` tied to this phase **is** the work. Fix it before starting anything new. + +## 5. Stop on disagreement + +If the ledger's claim contradicts the repo — a phase marked `done` whose **Files:** do not exist, or work +plainly in the tree under a phase marked `not started` — **say so and stop.** Never silently re-do or skip +a phase on a stale ledger. + +## 6. Do the work + +Read the phase's §6.2 sub-section: its scope, its **Files:**, and what `done` means for it. + +Delegate to a coder per [`context/executors.md`](../../../context/executors.md) if one is configured; +otherwise implement in-host. The coder's system prompt is +[`context/roles/coder.md`](../../../context/roles/coder.md). + +The brief **cites paths, it does not paste files.** Point at `context/standards/README.md` and say to load +per its conditional table; point at `context/stack.md` and the phase's own section. Anything that can read +this repository can open them, and a brief that inlines them is a brief that goes stale. + +Describe **what** needs to happen, never **how** to code it. Scope each delegated task to specific files. + +## 7. Gate 1 — verification + +Per the gate contract in [`context/workflow.md`](../../../context/workflow.md): read +[`context/verify.md`](../../../context/verify.md) and run its sections in order — Lint → Typecheck → Build → +Test. + +**Never carry a copy of these commands here and never invent one.** A missing section is skipped and said +so, never faked. Exit 0 is the verdict regardless of summary text. If `verify.md` does not exist or has no +filled-in section, stop and say so. Docs-only changes run Lint plus a read of the diff. + +A failure is the verdict — go to step 9 with the failing output verbatim as the feedback. + +## 8. Gate 2 — review + +Dispatch per [`context/executors.md`](../../../context/executors.md). With no independent reviewer +configured, review the diff yourself against the plan's review expectations and the standards — weaker, and +**say which one you ran.** + +Require concrete evidence — file paths, command output — for every verdict, and a `P0`–`P3` severity on +every blocking finding. + +- `PASS` or `PASS WITH NOTES` → the phase's work is done; go to step 10. +- `FAIL` → **write it to [`context/findings.md`](../../../context/findings.md) first, then** go to step 9. + +**Write the finding before the loopback, not after it.** A verdict that lives only in this session's +transcript evaporates when the conversation ends — including a `P0` the cap never got to. + +## 9. Loopback + +Cap: **two loops per gate, per phase.** + +Under the cap: re-brief with the prior implementation and the validator's feedback **verbatim — do not +summarise or paraphrase it** — plus the instruction to address only the failing items, refactor nothing +that passes, and expand no scope. Then re-run the same gate. + +At the cap: **write a finding** (`P1` for a Gate 1 cap-out — a phase whose verification cannot pass is +blocked by definition), then escalate to the user with the current state and the last feedback. +**Escalating is not a substitute for recording.** + +## 10. Close out the ledger row + +In the same commit as the work: + +- **All of the phase's scope landed and both gates passed** → `done`. +- **Some landed** → stays `in progress`, Note rewritten to name exactly what remains. +- **A gate hit its cap, or something external blocks it** → `blocked`, with the blocker in the Note. + +**Never mark `done` on a coder's self-report** — the gate output is the evidence. **Refuse `done` while an +open `P0` or `P1` is tied to this phase**; leave it `in progress` and name the finding. + +## 11. Report + +- What changed, and which files. +- Gate 1 output, and Gate 2's verdict. +- Loopback counts, if any. +- Findings written or closed, by id. +- The phase's new ledger status, and which phase is next. + +**When every phase is `done`, say so and name `/feature-close`.** Do not move files, stamp headers or sweep +references — that is a tier boundary, and crossing it is an explicit command the user runs, not a +side-effect of the last phase finishing. diff --git a/.claude/skills/feature-plan/SKILL.md b/.claude/skills/feature-plan/SKILL.md new file mode 100644 index 0000000..230ef6b --- /dev/null +++ b/.claude/skills/feature-plan/SKILL.md @@ -0,0 +1,139 @@ +--- +name: feature-plan +description: "Promote one item from the Tier-1 backlog in context/roadmap.md into a Tier-2 plan document under context/plans/, then stop without implementing. Explicit invocation only — run this when the user types /feature-plan. Do NOT match on 'plan out X', 'how should we build X', or any general planning or design request." +disable-model-invocation: true +--- + +# /feature-plan + +Turns one roadmap entry into a plan document, then **stops**. It never implements anything and never marks +a phase `done` — every phase in a new plan is `not started`. + +**Planning is not activation.** Several features may hold plans at once; there is no "a feature is already +active" refusal here. That is what makes planning ahead possible. Activation is `--activate` or +`/feature-implement`, and both are subject to the one-active-feature rule in +[`context/workflow.md`](../../../context/workflow.md). + +## Usage + +``` +/feature-plan # rank the pending entries and ask which to plan +/feature-plan "" # plan a named entry +/feature-plan "" --activate # plan it, and mark it active +``` + +**Nothing needs to be looked up first.** This command resolves its own starting point. + +## Steps + +### 1. Pick the entry + +Read `context/roadmap.md`. + +**With a name:** take that entry. If none matches, say which names exist and stop. + +**With no argument:** rank the `pending` entries and **ask which to plan**, using your runtime's question +mechanism if it has one, or a plain numbered question if it does not. Writing a plan is a commitment and +takes real work to produce; silently taking the top entry makes that decision on the user's behalf, badly, +whenever the backlog order is stale. + +**Ranking**, in priority order: + +1. **Has a draft** — the entry's **Doc** names a document in `context/drafts/`. Half-researched is better + and cheaper. This dominates: an entry with real notes beats a one-line entry almost regardless. +2. **Unblocked by what just shipped** — it builds on something with a `context/history.md` row, so the + ground under it is settled rather than hypothetical. +3. **Smaller first** — `small`, then `medium`, then `large`. A plan that can be executed beats one that + gets admired. +4. **Backlog order** — ties break by position in the file. + +Offer the top four, **best first**, each with a one-line reason drawn from the ranking — say *why* it is +ranked there, not just what it is. Leave room for the user to name something else. + +Special cases, where asking is noise rather than help: + +- **Exactly one `pending` entry** — state it and proceed. A one-option question is not a choice. +- **No `pending` entries** — say the backlog is empty and name `/roadmap "some idea"`. Do not invent one. + +**State which entry you picked and why, in one line, before doing anything else.** + +### 2. Already planned? Start a conversation, not a refusal + +If the entry's **Doc** already points into `context/plans/`, say so, show the plan, and ask whether to +iterate on it. + +- **Every phase `not started`** → iterate freely. +- **Any phase `in progress`, `blocked` or `done`** → **warn first, and get an answer before writing.** + Rewriting a plan under work that already happened is the "ledger disagrees with the repo" hazard arriving + by a new route. Name which phases have moved. + +### 3. Decide the document + +The plan ends up at `context/plans/-PLAN.md`, where `` is the entry's kebab-case name +upper-cased. + +- **If the entry's **Doc** names a draft in `context/drafts/`**, `git mv` it to that path and build the + plan on top of its content. Use `git mv`, not `mv`. Do not create a second file and do not leave the + draft behind — a draft and a plan for the same feature is two documents disagreeing about one thing. +- **Otherwise** copy `context/plan-template.md` to that path. Copy it verbatim; it is a bare skeleton with + nothing to strip. [`context/plan-template.notes.md`](../../../context/plan-template.notes.md) says what + goes in each section. + +**The draft is the most valuable input you have** — material the user gathered deliberately, often from +somewhere you cannot reach. Carry its specifics forward; do not summarise them away, and do not silently +drop a fact because you could not verify it. Mark it as an open question instead. + +### 4. Research and draft + +Delegate the research and the draft to a planner subagent **if your runtime provides one**; otherwise do it +inline. Either way the brief is the same, and the output contract is the template's section list, not a +planner's own default shape: + +- The roadmap entry verbatim, and the full content of its draft if there was one. +- **The full section list from `context/plan-template.md`, stated as required output**, in order, with the + ledger's exact column set. A general-purpose planner will otherwise emit implementation-steps-and- + acceptance-criteria — a per-phase artifact, not a plan — and you will throw it away. +- Pointers to `context/stack.md`, `context/standards/README.md` (load per its conditional table) and + `context/verify.md`. Cite the paths; do not paste the files in. Anything reading this repo can open them. +- **Cite file paths and command output for every claim about the current codebase.** Anything unverified is + an open question, not an assertion. +- Phases are **commit-sized units with checkable outcomes**, each with a real `Depends on` value and a + **Files:** line naming every path it touches. That line is what makes reconciliation a check rather than + a judgement call. + +### 5. Write the document + +Fill in the template's shape. Then: + +- Date it and point its header at the roadmap entry. +- **No `**Status:**` header.** Feature status lives in `roadmap.md`, phase status in the ledger. A document + that claims its own status is a copy that goes stale. +- Fill in **§8 Open questions** honestly. An honest gap is worth more than an invented decision. +- Every phase is `not started`. + +### 6. Update the roadmap entry + +Repoint the entry's **Doc** field at the new `plans/` path. If you `git mv`d a draft, that same edit is +what fixes the now-dead `drafts/` link, so do it together. + +**Leave the marker alone unless `--activate` was given.** `pending` with a `plans/` document is the correct +state for a planned-but-not-started feature. + +**With `--activate`:** check the one-active-feature rule in +[`context/workflow.md`](../../../context/workflow.md) first. If another entry holds the slot, **write the +plan, skip the activation, and name the feature that holds it.** The plan is valuable and harmless on its +own; discarding it over a marker would undo the point of the split. + +### 7. Report and stop + +State the document path, the phase count, and the open questions. Then say plainly that **what you produced +is a reviewable skeleton plus open questions, not a finished plan of record** — the value is the structure +and the research. Name the next step: the user reviews and edits the plan, and `/feature-implement` runs it +once they are satisfied. + +## Rules + +- **Never implement anything.** Not "just the first phase", not "a quick scaffold". +- **Never mark a phase `done`**, and never mark a phase anything other than `not started`. +- **Never write outside `context/`.** No source files, no config. +- Do not fold the draft's content into `context/roadmap.md`. Tier 1 stays high-level. diff --git a/.claude/skills/feature-status/SKILL.md b/.claude/skills/feature-status/SKILL.md new file mode 100644 index 0000000..b24a8d4 --- /dev/null +++ b/.claude/skills/feature-status/SKILL.md @@ -0,0 +1,87 @@ +--- +name: feature-status +description: "Read-only report of where the active feature stands — its plan's phase ledger, open findings, and git state — ending with exactly one next action. Explicit invocation only — run this when the user types /feature-status. Do NOT match on 'what's the status', 'where are we', or general progress questions." +disable-model-invocation: true +--- + +# /feature-status + +The read-only "where do things stand" view. **It writes nothing, commits nothing, and invokes no other +agent.** + +**It is never a prerequisite.** Every other command resolves its own starting point — nobody has to run +this first. It exists for when *you* want to know. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## 1. Read, in this order + +1. `context/roadmap.md` — which entry is `active`, and what each entry's **Doc** points at. +2. That plan document's **status ledger**, and each phase's **Files:** line. +3. `context/findings.md` — open findings, and which phase each is tied to. +4. Git state — `git status --short` and the last few commits. + +**Nothing is cached and nothing is parsed by a script.** Read the ledger every time. That is the property +that makes hand-editing a row change this command's answer immediately, with no regeneration step. + +## 2. Reconcile before trusting the ledger + +Report and **stop** on any of these: + +- A phase marked `done` whose **Files:** or commits do not exist. +- A phase marked `not started` whose work is plainly already in the tree. +- An entry marked `active` pointing at a document that does not exist. +- An entry marked `active` for a feature that already has a `context/history.md` row. +- **A document in `context/plans/` that no roadmap entry points at.** + +Do not resolve a discrepancy yourself, and do not pick a next action off a ledger you have just shown to be +stale. That is the exact failure this workflow exists to prevent. + +**Two things that are not discrepancies:** + +- Every phase `done` while the entry still reads `active` — that is the normal state before + `/feature-close`. Next action 5 handles it. +- A `done` row with its changes still in the working tree — a phase finished but not committed. Name it in + the report; do not stop on it. + +## 3. Report + +Keep it short. The user is asking a question, not reading a document. + +``` +Feature: (or: none active) +Plan: +Phases: done · in progress · blocked · not started +Findings: open () (or: none open) + +Next: +``` + +Under the header, list only the phases that are **not** `done`, one line each with their Note. Do not +re-print the whole ledger. + +## 4. Name exactly one next action + +In priority order — take the **first** that applies and name only it: + +1. An open `P0` or `P1` → fix it. Quote the finding's id and its closing condition. +2. A phase **`in progress`** → resume it, quoting its Note. Do not restart it. +3. A phase **`blocked`** with every other phase `done` → report the blocker; the next action is the user's. +4. A phase `done` with a next **unblocked** phase → `/feature-implement`, naming the phase it will pick. +5. **Every** phase in the active plan `done` → `/feature-close`. +6. **No active feature, but at least one entry has a plan** → `/feature-implement`, which ranks the planned + entries and asks. +7. **No plans, at least one `pending` entry** → `/feature-plan`. **Do not pick a candidate yourself** — + that command ranks the backlog and asks, and naming one here would either duplicate its ranking or + contradict it. +8. **Nothing at all** → `/roadmap "some idea"`. + +"Exactly one" is the point. A list of three things to consider is what this command exists to replace. + +## Rules + +- **Read-only. No exceptions.** Not the ledger, not the roadmap, not a finding, not a "quick fix while I'm + here". If you spot something that needs changing, name it as the next action and let the user decide. +- **Never invoke another agent.** +- **Never mark anything.** Reporting that a phase looks finished is not marking it `done`; only + `/feature-implement` does that, on gate evidence. diff --git a/.claude/skills/onboard/SKILL.md b/.claude/skills/onboard/SKILL.md new file mode 100644 index 0000000..a6dac71 --- /dev/null +++ b/.claude/skills/onboard/SKILL.md @@ -0,0 +1,191 @@ +--- +name: onboard +description: "Fill in this project's own workflow stubs — context/verify.md, context/executors.md and context/stack.md — by adopting what the repository already documented, asking wherever a claim's destination is unclear, and running each candidate verification command so only the ones that pass are written down. Explicit invocation only — run this when the user types /onboard. Do NOT match on 'set up the project', 'get started', or general setup requests." +disable-model-invocation: true +--- + +# /onboard + +Fills the project-owned stubs the installer deliberately left empty, and folds whatever the repository +already documented into them. **Re-runnable** — run it again after the stack changes, and it re-proposes +against what is there now. + +**Asking is not guessing.** The installer could have detected a test command and written it in; that is +exactly how a file ends up naming a command that has never run. This command asks, and where it can, it +*checks*. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## What it writes + +| File | Gets | +|---|---| +| [`context/verify.md`](../../../context/verify.md) | the real Lint / Typecheck / Build / Test commands — **only ones that exited 0** | +| [`context/executors.md`](../../../context/executors.md) | how this project dispatches a coder and a reviewer | +| [`context/stack.md`](../../../context/stack.md) | runtime, layout, conventions | +| [`AGENTS.md`](../../../AGENTS.md) | pruned, on confirmation, of what moved into those three. The region between the `ai-workflow` markers is never touched | + +Show every proposed edit before writing it, and **do not commit.** The user reviews and commits. + +## Step 1 — Adopt what the repository already says + +The installer appends its block to `AGENTS.md` and leaves the rest of that file alone, so a repository that +documented itself before the overlay arrived now states some of the same things twice, in two places, with +no rule about which wins. Resolving that is this step, and it runs first because what turns up here is the +raw material for every step below. + +**Read, in this order:** everything in `AGENTS.md` *outside* the `ai-workflow` markers, then `CLAUDE.md` +apart from its import line. If the repository carries other agent-instruction files — a nested `AGENTS.md` +under a subdirectory, a directory of per-host instruction files — **list them and stop there.** Say they +exist and that this command left them alone. A nested file usually scopes to its own subtree, and moving it +up is a decision rather than a cleanup. + +Break what you read into claims — a paragraph, a table row, a bullet — and propose a destination for each: + +| A claim about | Goes to | +|---|---| +| what the project is, its layout, its conventions | `context/stack.md` | +| a lint, typecheck, build or test command | a **candidate** for `context/verify.md` — Step 5 still has to run it | +| how a coder or a reviewer is dispatched | `context/executors.md` | +| a rule the bundled standards already state | nowhere — the standards own it. Ask before dropping | +| planning or review process this workflow now owns | nowhere — superseded. Ask before dropping | +| anything else — ownership, contacts, external links | stays in `AGENTS.md`, outside the block | + +**Show the whole table before moving anything**, one row per claim, and let the user correct any +destination. Two kinds of row are never decided alone: + +- **Unsure** — a claim you cannot place, or that fits two files equally well. Ask. Do not quietly pick the + likelier one: a misfiled convention is a rule nobody reads again. +- **Contradicts** — the existing prose and the installed workflow give different answers to the same + question. Where project-local skills live, what the review process is, which file is authoritative. + **Quote both and ask which stands.** Never resolve one silently in either direction — the older text is + often right about this project and wrong only about the overlay. + +An adopted claim is an *input* to the steps below, not a substitute for them. A command lifted out of the +old file is a candidate like any other and still has to run. + +**Nothing is deleted here.** Pruning is Step 7, after the destination files exist. + +On a re-run, a claim its destination file already states is already adopted. Say so in a line and move on. + +## Step 2 — Coder dispatch + +Ask whether implementation runs **in-host** or is **offloaded** to an external coder CLI. + +- **In-host** is the default, and a valid configuration rather than a gap. Leave the Coder section of + `context/executors.md` saying so. +- **Offloaded** — the user names the invocation. Write it into `context/executors.md` verbatim, including + any directory or permission scoping it needs on this machine. Its system prompt is + [`context/roles/coder.md`](../../../context/roles/coder.md). + +If an external coder is named, **test one assumption before writing it down**: that it can read this +repository unaided. Briefs cite paths rather than pasting file contents, so everything downstream depends +on that being true, and it is true of some executors and not others. + +1. Pick a fact only available by opening a file here — a heading partway down `context/workflow.md` does. +2. Send a one-line brief that cites the path and asks for that fact. Nothing else. +3. If the fact comes back, record in `context/executors.md` that repository reads were verified, and when. +4. If it comes back empty, guessed, or refused, record that **this executor needs content inline** — the + one case where a brief carries file contents instead of paths. + +Never write down an invocation you have not run. This is the same rule as Step 5, for the same reason. + +## Step 3 — Reviewer dispatch + +Ask how Gate 2 should get a review: + +- **The host reviews the diff itself** against the plan's review checklist and the standards. That is the + default. It is weaker than an independent reviewer, and any command that runs the gate must say which one + it ran. +- **An external reviewer** — the user names the invocation. Write it into `context/executors.md` verbatim, + including any scoping it needs on this machine. + +A host that offers review usually offers more than one shape of it — a review subcommand, a review skill it +can be asked to run, or both — and they do not review alike. **Find out what this host actually provides +rather than assuming**, show the user what you found, and let them choose. Nothing shipped here names a +winner: it differs per host and changes underneath you. What ships is the contract, not the command. + +Whatever is chosen, that contract stands: a review happens, every blocking finding carries a `P0`–`P3` +severity, and a `FAIL` writes a finding before the loopback. + +## Step 4 — Standards source + +`context/standards/` ships with a bundled default. Ask whether that is right for this project. + +- **Keep it** — nothing to do. It stays tool-owned and updates with the tool. +- **Swap it** — the user gives a git URL, and the swap is + `npx @baldurpan/create-ai-workflow standards add `. Tell them that command rather than cloning + it yourself: it validates that the tree has a usable conditional-loading table, and whatever lands + becomes project-owned from that point. + +Say plainly what the default is and that a wrong set is not inert — agents load from that README's +conditional table unprompted, on every task. If Step 1 turned up house rules that the bundled set already +covers, this is the moment that matters: keeping both means the project has two answers. + +## Step 5 — Verification commands + +**This is the most valuable step in this command.** Do it properly. + +1. **Propose candidates.** Take the command claims Step 1 adopted, then read `package.json` scripts, or the + stack's equivalent — `Makefile`, `composer.json`, `pyproject.toml`, `Cargo.toml`, the CI workflow. The + CI config is the best source available: it lists commands that demonstrably run in a clean checkout. A + command the old file named and CI does not is worth asking about — one of the two is stale. +2. **Show the candidates and ask** which belong in Lint, Typecheck, Build and Test, and whether anything is + missing. Ask about prerequisites too — a package manager version, an install step, a service that must + be up. +3. **Run each one.** Actually run it, from the repo root. +4. **Write only the commands that exited 0.** For each one that failed, show the output and ask: fix it, + replace it, or leave that section empty. **Never write a command that has not passed** — an inherited + one least of all, since it is the likeliest to have rotted. An empty section is skipped by Gate 1 and + says so; a wrong command fails a gate on every task until someone notices. +5. Put anything that needs Docker, a cloud account or a deploy target under **Not run by Gate 1**, so + nobody promotes it into a gate section by mistake. + +Explain what you are doing: this turns `verify.md` from someone's guess into something verified at install +time, which is the one moment it is cheap to catch. + +## Step 6 — Stack + +Start from what Step 1 routed here, show it back as a draft, and ask only for the gaps: + +- What does this project do, in a paragraph — and anything about its history that explains its shape. +- Runtime, package manager, database, storage, hosting. +- The directories that matter, one line each. +- **The conventions that would not be guessed** — what breaks in this runtime, what is deliberately kept + separate, where local secrets live, what must never be run against production. This section is the one + that earns its keep; the rest is discoverable. Inherited prose is usually strongest here and weakest at + describing layout, which drifts. + +Point out that anything else added under `context/` should be indexed in `stack.md`, not in +`context/README.md`, which is tool-owned and replaced on update. + +## Step 7 — Prune the sources + +Only now, with the three stubs written, remove from `AGENTS.md` and `CLAUDE.md` what has landed elsewhere. +Duplication left standing is the failure this step exists to prevent: two statements of one fact drift, and +the stale copy is indistinguishable from the live one. + +- **Show the removal as a diff and ask before applying it.** Whole sections at a time, not scattered lines. +- **Remove only what you can point at.** For each deletion name the file and section that now holds it. A + claim you could not place stays exactly where it is — an unpruned file is a smaller problem than a lost + rule. +- **Never touch the region between the `ai-workflow` markers.** It is tool-owned and replaced on update, so + an edit there is an edit lost, and nothing migrates into it. +- What should be left is what only `AGENTS.md` can say: the repository's own front matter, and the block's + pointer into `context/`. + +Keeping the original prose in place is a valid answer. If the user chooses it, write one line in +`context/stack.md` saying which file is authoritative, so the next reader is not left to guess. + +## Rules + +- **Copy before cut.** Nothing leaves `AGENTS.md` until the file that replaces it is written and shown. +- **Never write a credential.** Write `$SENTRY_DSN`-style placeholders and name where the real value lives + — this command collects shell commands, which is the most likely place a token appears inline. Inherited + prose gets the same read before it moves. See the standing rule in + [`context/workflow.md`](../../../context/workflow.md). +- **Never write a command you have not run.** +- **Never delete a claim you could not place.** +- **Never touch a tool-owned file.** `README.md`, `workflow.md`, `plan-template*.md` and `roles/` are + replaced on the next update; an edit there is an edit lost. +- **Do not commit.** diff --git a/.claude/skills/orchestrate/SKILL.md b/.claude/skills/orchestrate/SKILL.md new file mode 100644 index 0000000..dcf4106 --- /dev/null +++ b/.claude/skills/orchestrate/SKILL.md @@ -0,0 +1,93 @@ +--- +name: orchestrate +description: "Run one ad-hoc, commit-sized change through the same verification and review gates the feature loop uses, without a roadmap entry or a phase ledger. Explicit invocation only — run this when the user types /orchestrate. Do NOT match on 'build X', 'implement X', 'orchestrate the work', or any request that belongs to a planned feature." +disable-model-invocation: true +--- + +# /orchestrate + +A gated one-shot pass over a scope you name. No roadmap entry, no ledger, **no tier boundary crossed.** + +It exists because the valuable part of the loop is the **gate machinery** — Gate 1 reading `verify.md`, +Gate 2's reviewer, failures landing in `findings.md` before the loopback — and that is worth having for +unplanned work too, arguably most of all, since that is where fixes get cowboyed. Without it, the only +route to a verified, reviewed change is to file a roadmap entry, and people will route around the workflow +for small things. + +Read [`context/workflow.md`](../../../context/workflow.md) for the gate contract and the feature/task rule. + +## Usage + +``` +/orchestrate "" +``` + +## 0. Sweep first + +Move every **closed** finding tied to `ad-hoc` out of `context/findings.md`. Those belong to no feature, so +nothing else would ever retire them and the file would grow forever. Say what you swept. + +## 1. Refuse, before anything else + +Two guards, or this becomes the way to skip planning: + +1. **Refuse anything that is not commit-sized.** A commit-sized unit has one checkable outcome. A category + of activity ("add tests", "improve error handling", "refactor the API layer") is not one. Say what the + scope would need to be split into, and name `/roadmap`. +2. **Refuse anything an existing roadmap entry already covers.** Read `context/roadmap.md` and check. If + one covers it, say which, and name `/feature-plan` and `/feature-implement`. + +Apply the standing test from [`context/workflow.md`](../../../context/workflow.md): *if you would want a +`history.md` row for it, it is a feature.* Ask that question out loud and answer it before proceeding. + +A refusal here is the workflow working. + +## 2. Do the work + +Read `context/stack.md` and load `context/standards/README.md` per its conditional table. + +Delegate to a coder per [`context/executors.md`](../../../context/executors.md) if one is configured; +otherwise implement in-host. The coder's system prompt is +[`context/roles/coder.md`](../../../context/roles/coder.md). The brief **cites paths, it does not paste +files.** Describe what needs to happen, never how to code it. + +## 3. Gate 1 — verification + +Read [`context/verify.md`](../../../context/verify.md) and run its sections in order: Lint → Typecheck → +Build → Test. **Never carry a copy of these commands here and never invent one.** A missing section is +skipped and said so, never faked. Exit 0 is the verdict regardless of summary text. If `verify.md` does not +exist or has no filled-in section, stop and say so. Docs-only changes run Lint plus a read of the diff. + +## 4. Gate 2 — review + +Dispatch per [`context/executors.md`](../../../context/executors.md). With no independent reviewer +configured, review the diff yourself against the standards — weaker, and **say which one you ran.** + +Require concrete evidence — file paths, command output — for every verdict, and a `P0`–`P3` severity on +every blocking finding. + +- `PASS` or `PASS WITH NOTES` → done. +- `FAIL` → **write it to [`context/findings.md`](../../../context/findings.md) first, then** loop back. + +Findings raised here are recorded with **`Tied to: ad-hoc`**. + +## 5. Loopback + +Cap: **two loops per gate.** Re-brief with the prior implementation and the validator's feedback +**verbatim**, plus the instruction to address only the failing items, refactor nothing that passes, and +expand no scope. + +At the cap: write a finding (`P1` for a Gate 1 cap-out), then escalate with the current state and the last +feedback. **Escalating is not a substitute for recording.** + +## 6. Report + +What changed, the Gate 1 output, the Gate 2 verdict, any loopbacks, and any findings written, closed or +swept — by id. + +## Rules + +- **No ledger row is touched.** This command has no phase and does not belong to a feature. +- **No roadmap entry is created, activated or retired.** If the work turns out to be a feature, stop and + say so; the user runs `/roadmap`. +- **Never skip Gate 1 to save time.** The gates are the entire reason this command exists. diff --git a/.claude/skills/roadmap/SKILL.md b/.claude/skills/roadmap/SKILL.md new file mode 100644 index 0000000..11c5dbe --- /dev/null +++ b/.claude/skills/roadmap/SKILL.md @@ -0,0 +1,98 @@ +--- +name: roadmap +description: "Print the Tier-1 feature backlog in context/roadmap.md, or append one new pending entry to it. Explicit invocation only — run this when the user types /roadmap. Do NOT match on general planning talk, 'what should we build next', or any request to design, plan, or implement a feature." +disable-model-invocation: true +--- + +# /roadmap + +Maintains **Tier 1** — the backlog in `context/roadmap.md`. It never promotes anything, never writes a +plan, never marks anything `active`, and never removes an entry. + +It *may* write a **draft** in `context/drafts/` — raw reference material the user supplied, which +`/feature-plan` later turns into a plan. Keep that line straight: capturing what someone told you is not +designing. + +Read [`context/workflow.md`](../../../context/workflow.md) for the tier model. + +## Usage + +``` +/roadmap # print the backlog — read-only +/roadmap "some idea" # append one pending entry +``` + +## No arguments — print the backlog + +1. Read `context/roadmap.md`. +2. Print every entry: name, marker, size, the one-line why, and what its **Doc** field points at. +3. Say which entries have a plan (**Doc** into `plans/`) and which do not. Those are different facts from + the `pending`/`active` marker, and reporting them as one is the mistake the two fields exist to prevent. +4. If nothing is `active`, name `/feature-plan` as the way to get there. **Do not pick a candidate** — that + command ranks the backlog and asks. + +**Do not** read a plan's ledger or report phase status. That is `/feature-status`. This command answers +"what is on the list", not "what is next". + +## With an argument — append an entry + +1. Read `context/roadmap.md` for the existing entries and the format already in the file. +2. **Check for an entry that already covers the idea.** If one exists, say so, show it, and stop — do not + add a near-duplicate. Also check `context/history.md`: an idea previously `dropped` has a recorded + reason, and re-proposing it needs that reason addressed, not ignored. +3. Derive a **kebab-case name**. It becomes the entry's identity and is what `/feature-plan`, + `/feature-implement` and `/feature-close` are given later, so make it specific and stable. +4. Append to the end of the **Features** list, matching the file's format. It is always `pending` — the + marker in the heading is the entry's status, and this command never sets any other value: + +```markdown +### — `pending` + + + +- **Size:** +- **Doc:** none yet +``` + +5. **If the user supplied reference material, capture it** — see below. Otherwise leave `**Doc:** none yet`. +6. Show the appended entry, and the draft if you wrote one, then stop. + +### Capturing reference material + +**Capture beats summarise.** When the user gives you more than the idea — a screenshot of documentation, a +pasted setup guide, a URL, an error dump, a long explanation of how they want it to work — that material +does not go in the entry and must not be thrown away. Write it to `context/drafts/.md` and point the +entry's **Doc** field at it: + +```markdown +- **Doc:** [`drafts/.md`](drafts/.md) — +``` + +`drafts/` is the right directory and `plans/` is not. A draft is notes: no ledger, no template sections, +nothing executable. `/feature-plan` moves it into `plans/` when it writes the plan, which is also why you +never write directly into `plans/` from here. + +What to write in it: + +- **The specifics that are expensive to re-derive** — exact package names, version or compatibility + requirements, config keys, the shape of an API call, the wording of an error. +- **Where it came from, and when.** A screenshot of vendor docs on a date is worth more than the same facts + with no provenance, because docs move. +- **What it means for *this* repo.** Check the relevant config or source and say what already holds and + what would have to change. This is the part a screenshot cannot tell you, and the part that rots slowest. +- **Never transcribe a credential.** See the standing rule in + [`context/workflow.md`](../../../context/workflow.md): a DSN, token or key gets described and pointed at + the secret store, never copied into a tracked file. + +Keep the entry itself one or two lines regardless. + +## Rules + +- **One or two lines of why. No more.** If you find yourself writing a third paragraph, that is a signal the + idea is ready for `/feature-plan`, not that the entry should be longer. +- **Never guess at a design.** The entry records that a thing is wanted, not how it would work. +- **Never mark anything `active`.** Only `/feature-plan --activate` and `/feature-implement` do that. +- **Never remove an entry.** Entries leave only via `/feature-close`, which records why. Deleting one loses + the reason it was dropped, which is the whole point of `history.md`. +- If the idea is really several ideas, say so and offer to add them as separate entries rather than writing + one vague entry covering all of them. diff --git a/AGENTS.md b/AGENTS.md index 24eae9d..23b763e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,121 +1,45 @@ # AGENTS — Worktree repository -Purpose: document recommended agent workflows, repository layout, and examples for creating workspace-level agents, prompts, and skills that match this monorepo's patterns. +`@northguild/worktree` — a Node CLI for managing git worktrees, built on oclif, with a Next.js/Nextra +documentation site under `docs/`. Both live in one pnpm workspace. -## Repository overview - -- Root package (`@northguild/worktree`) - - Type: Node CLI (ESM) built with TypeScript and [`oclif`](https://oclif.io/docs/api_reference). - - Entry: `bin/run.js` (produced from `tsc`), commands live in `src/commands`. - - Libraries/helpers: `src/lib` (CLI helpers, git integration, validators, utils). - - Scripts: build/test/format via `pnpm` and `biome`/`vitest` (see `package.json`). - -- Workspace Docs site (`docs/`) - - [Next.js](https://nextjs.org/docs) / [Nextra](https://nextra.site/docs) -based docs app, React 19. - - Uses [`pnpm`](https://pnpm.io/workspaces) workspace and has its own `package.json` in `docs/`. - - Uses [Base UI](https://base-ui.com/react/overview/quick-start) for UI library - - UI and examples live under `docs/src`, docs content under `docs/src/app`. - -Separation of concerns - -- The CLI code (root `src/`) is server-side Node code (sticks to ESM, interacts with the terminal, uses `@oclif/core`). - -- The docs app is a static/SSR Next app and must never contain secrets or server-side API keys in client-side code. - -## Conventions & Tooling - -- Language: TypeScript (tsconfig at repo root) -- Module type: ESM (`type: module` in root `package.json`) -- Formatting: [`biome`](https://biomejs.dev/guides/getting-started/) (scripts in package.json) -- Testing: [`vitest`](https://vitest.dev/guide/) (unit tests under `src` and `docs/src`) -- Monorepo: [`pnpm`](https://pnpm.io/workspaces) workspace, use `pnpm` commands for cross-package scripts (e.g., `pnpm --filter docs dev`) -- Avoid `export default` unless it is required - -## Where to put agent/customization files - -- Workspace-level agents: place in the repository root as `AGENTS.md` (like this file) or under `.github/agents/` if you want them grouped with CI config. -- Prompts & instructions: `.github/prompts/` and `.github/instructions/` are good places for repeatable prompt files, but keeping short examples next to the relevant code is OK (e.g., `docs/.agents/` or `docs/AGENTS.md`). - -Follow the `agent-customization` SKILL guidelines: include a clear `description`, `applyTo` globs for scope, and keep YAML frontmatter well-formed. - -## Recommended agents (examples) - -1) Review PRs and apply requested fixes - -Purpose: assist reviewers and apply trivial fixes (formatting, lint suggestions, small code tweaks) in PRs that target the CLI or docs. - -When to run: on PR review, or locally via a prompt. - -ApplyTo examples: -``` -applyTo: - - "src/**" - - "docs/src/**" -``` - -Suggested prompt (agent): -``` -name: review-and-suggest-fixes -description: "Review changed files and suggest or apply small fixes: formatting, lint warnings, test errors. Use 'applyTo' to scope to CLI or docs files." -``` - -Notes: avoid making large behavioral changes without human approval; provide a summary of all modifications and a commit message suggestion. - - -2) CLI maintenance helper - -Purpose: help generate new `oclif` commands, run `pnpm build`, and ensure `bin/` scripts are executable. - -ApplyTo: -``` -applyTo: - - "src/commands/**" - - "bin/**" -``` - -Suggested prompt: "Create a new CLI command file consistent with existing patterns: use `BaseCommand`, export default class, use flags/args pattern, and add tests under `src/commands/*.test.ts`." - -## Example agent file frontmatter (YAML) - -Use simple [YAML frontmatter](https://docs.github.com/en/contributing/writing-for-github-docs/using-yaml-frontmatter) for on-disk agent definitions. Example: - -``` ---- -name: "fix-format-and-lint" -description: "Run format/lint fixes on modified files and return a short commit message. Use when PR contains lint or format errors." -applyTo: - - "src/**" - - "docs/**" ---- - -Instructions: -- Run `pnpm format:fix` and `pnpm lint:fix` locally. -- Show the before/after diff and suggested commit message. -``` - -## Developer commands (quick reference) - -- Install deps: `pnpm install` -- Run docs locally: `pnpm --filter docs dev` -- Run full test suite: `pnpm test` (runs root and filtered tests) -- Build CLI: `pnpm build` -- Format: `pnpm format` - -## Security notes for agents - -- Never embed secrets or API keys in prompt/instruction files. If an agent needs to interact with external systems that require secrets, document where to set them (CI secrets, serverless platform env vars) and keep those values out of the repo. - - -## How to add a new agent - -1. Decide scope (`applyTo` patterns) and place the file under `.github/agents/` or add a short entry in `AGENTS.md`. -2. Include a `description` with trigger keywords (so discovery works reliably). -3. Validate YAML frontmatter and that any example commands are correct for this repo (use `pnpm` scripts where appropriate). -4. Ask reviewers to verify the agent on a representative PR. +Layout, runtime and the conventions specific to this repository are in +[`context/stack.md`](context/stack.md). ## Contact / Maintainers -For agent reviews and maintenance, ask the repository maintainers listed in `package.json` (`contributors`) or open a PR with suggested agent files. - ---- -Generated by repository scan — adapt as needed for team preferences. +For agent reviews and maintenance, ask the repository maintainers listed in `package.json` +(`contributors`) or open a PR with suggested agent files. + + +## Planning workflow + +Planning artifacts live in [`context/`](context/README.md). Read +[`context/workflow.md`](context/workflow.md) before using any command below — it holds the tier model and +the standing rules, and every command cites it rather than restating it. + +| Command | Does | +|---|---| +| `/roadmap` | prints the Tier-1 backlog, or appends one `pending` entry to it | +| `/feature-plan` | turns a backlog entry into `context/plans/-PLAN.md` and **stops** — it never implements | +| `/feature-implement` | activates a planned feature and runs its phases, through both gates | +| `/feature-status` | read-only "where do things stand". **Never a prerequisite** for anything | +| `/feature-close` | retires a finished or abandoned feature into `context/archive/` | +| `/orchestrate` | one ad-hoc, gated, commit-sized change — no roadmap entry, no ledger | +| `/onboard` | fills in this project's own stubs — `verify.md`, `executors.md`, `stack.md` — adopting what an existing `AGENTS.md` already said | + +| Read | For | +|---|---| +| [`context/stack.md`](context/stack.md) | runtime, layout, conventions | +| [`context/standards/README.md`](context/standards/README.md) | engineering standards — load per its conditional table | +| [`context/verify.md`](context/verify.md) | the real lint / typecheck / build / test commands — the only file that names one | + +**Phase status lives in the active plan's status ledger and nowhere else.** Work the lowest-numbered phase +that is not `done` and whose `Depends on` are all `done`; state which you picked before starting; update +the row in the same commit as the work. **If the ledger disagrees with the repo, stop and say so.** + +**An open `P0` or `P1` in [`context/findings.md`](context/findings.md) blocks its phase from being `done`.** + +**Require evidence, not assertion.** A claim about what a file contains needs the file read, not recalled — +yours as much as a subagent's. + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c536b79 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,6 @@ +# CLAUDE.md + +@AGENTS.md + +The import above is this project's agent-neutral instruction set, expanded into context at launch. +**Everything that is not Claude-Code-specific belongs there, not here.** diff --git a/context/.state/manifest.json b/context/.state/manifest.json new file mode 100644 index 0000000..4ff2d56 --- /dev/null +++ b/context/.state/manifest.json @@ -0,0 +1,110 @@ +{ + "schemaVersion": 1, + "version": "0.4.0", + "adapters": [ + "claude", + "agents" + ], + "managedFiles": { + ".agents/skills/feature-close/SKILL.md": "41f8159e855255bd043e00d7accdc956eaeac38d81d8434123d191df723a95ce", + ".agents/skills/feature-implement/SKILL.md": "a9098df80d1ecbf5fe936ebda0d9b0c597379948a0042e502a2b0fe8c56bf3bc", + ".agents/skills/feature-plan/SKILL.md": "721fa0b15bd2cc6aa9ba16f9222106703b15b04d5af3660d46754b17c89c5e2a", + ".agents/skills/feature-status/SKILL.md": "82989af6ba9f99e749b6062bfd0b965186097431afbc544d8edbdf320adb4a87", + ".agents/skills/onboard/SKILL.md": "ba6500eed804403880da169f4d5dd6210122004afc63467b08c1ee91dad37373", + ".agents/skills/orchestrate/SKILL.md": "08ce20d5a4c54443e7ec7e8afbdad2aea7d0fc925752242763e9df807d604a41", + ".agents/skills/roadmap/SKILL.md": "509c942f17d286fe40e7b52119d06348f13037e0fe0a9f365a17032f67334f90", + ".claude/agents/planner.agent.md": "33beae63f4a76ee15919edfd14e54ce56ad20ef8fa8c0cfa4c73ad2ffa6a2ba1", + ".claude/agents/reviewer.agent.md": "9067dc8216c8951ef04e5f871b834a6f25c82bfc34eef997d880baadfd4b9b30", + ".claude/skills/feature-close/SKILL.md": "7bd0ba128e5dcbd2e65e4dfbdd93cbb1f0db791f216e29a7606357b0a7dcfb1c", + ".claude/skills/feature-implement/SKILL.md": "71dd4d584bec6385a9e59b03e824049ff11516d2de993ad0a0dd248c9ce5360c", + ".claude/skills/feature-plan/SKILL.md": "a47c32647e6066c543140737f821f64c51b2e2279f29e7f312cefbd9ec5221fb", + ".claude/skills/feature-status/SKILL.md": "46c493733bdc6048204a73d0b9b49939587736937efd8b17822d87f7e3b9d687", + ".claude/skills/onboard/SKILL.md": "852275c2f2fd6b72d2bcc406245cf091af64a25229e587f4f3daf1f43c858432", + ".claude/skills/orchestrate/SKILL.md": "b3a3f4165d807bae6cb6ae80cca70c862b67e5f3e527597cdb3191457daf196a", + ".claude/skills/roadmap/SKILL.md": "c3bfbf9879257019f73bfc506b187e4d75bb56d4b3dd073e8c1fea2043995b9d", + "AGENTS.md#ai-workflow": "06a1d5e387db2c8f91768db94172dd33a4af4c0a2c0338655c4dbf3bad8c7636", + "context/README.md": "154925c26f7e1e1f83ecaeaaa26bd560e4a426b5f4e485d479d93e779fb11363", + "context/plan-template.md": "9e5c7f5180990f0a2b57f46511919ce54a8aa7e675de168d74ef2652befb0320", + "context/plan-template.notes.md": "08508c48716730fd282c97ed82c7e62934a9b3f368421ec56793bcd44807784f", + "context/roles/coder.md": "becf9c143e4e6f1b718736f65d3906a574deb279968f235202ab0bd4127cb3cd", + "context/standards/.source": "c167c40b9e9785d24b2edf153f13d9db54baebca3cced4d7720afe010d94d55d", + "context/standards/README.md": "693927e50ca0e77519926e386338b28a961a5ff4a6e2923f7e70929dd57e998d", + "context/standards/architecture/api-design.md": "feca65b88aff4274833efff56391224ded460d4fbfe330eefeef91f1c772a780", + "context/standards/architecture/dependency-boundaries.md": "a739e24bee6365686669e6b8fd9c49edc0a530f601df5bd274cc62c072a8d904", + "context/standards/architecture/feature-driven.md": "63fc6199c4301582c0157605c3b1b19d57b8af830f3b6a9f3c69348764663aae", + "context/standards/architecture/folder-structure.md": "9fecf274deb4d0bfee0daa2dfd7d396faa8ebac44f9c97d3a4de2737fb9a49d3", + "context/standards/architecture/monorepos.md": "bab5673cc8d30e8c390b4102012008a53e76ac0b3fa0df7b86fc48b6729a5260", + "context/standards/architecture/refactoring.md": "c53dd5cfb3d7428c2e029d34a7b15a882fce772e576bd815b354a7805bbae924", + "context/standards/architecture/shared-code.md": "ea54cdc4c6e0412376f86fccec706c8ad9fbb88be408fac6cbae32f4a9feb17f", + "context/standards/docs/PHP-SPEC.md": "ccc90a83d7abbe28c187cf1e38728c16382b32b9cdf4efee1d76d410b41634b7", + "context/standards/docs/SPEC.md": "527ea04ded36900d32000ced9c708e8e6595e3ca8e78e83d8a6f557c45b5af56", + "context/standards/examples/bad/data-fetching.tsx": "71fa16fced0e72deb4de4378f493834ff48210dd1a9b510a77519db6b47bdeeb", + "context/standards/examples/bad/derived-state.tsx": "30cd3233fc6f8fcd28bc65853b329b71a927fe19a6212902f5ced0b11463b6bd", + "context/standards/examples/bad/feature-component.tsx": "9bf29f948deb7346b6a9c78a3ca842a969d0bf5ef52bc962ed8277f66046bdf5", + "context/standards/examples/bad/prop-drilling.tsx": "79f28a520c68e6f0332ebe4564661e0eb19bbba0048b8ab23a7d0d5618cc2d0b", + "context/standards/examples/good/data-fetching.tsx": "f0a0b381b77f5f2595d8eb852832c45247f1723c25ea306a5e5d469bc6d3e373", + "context/standards/examples/good/derived-state.tsx": "909d31b72e185d2515666f1e55e7a2556e80e8ef9480a1ee80184d7256722707", + "context/standards/examples/good/feature-component.tsx": "36fb18c40c884ead5e29c18976436c48e8ea7b3399ad3d0f9ca4c763f555db14", + "context/standards/examples/good/invoice.schema.ts": "2428b9bea98efe701bb804b526f4c0be9cd8edc3dbc433322ff5865f5b600598", + "context/standards/examples/good/prop-drilling.tsx": "722ebc6adf1749e5eb98b53ef818885a1539ae76ceeb62cd8d311f81baeb3222", + "context/standards/philosophy/ai-agent-behavior.md": "f958f582ed9d9c0517aefa0cb171f08ef93fe9764a1cba60dfdde22df677010b", + "context/standards/philosophy/core-principles.md": "06d98a6735acd7175a15ba8b6acb031062d3cd2e03040274b90f0ce2134fa64f", + "context/standards/philosophy/incremental-abstraction.md": "fe8d7e2649390c26c15b963fd9928074b4cccfd9c33451dc09e52de4e9d0a97a", + "context/standards/philosophy/maintainability.md": "86285e66624cdf7c21bd4e87e981fc9e286d937fa780939bb94e1295cfb1c5e2", + "context/standards/philosophy/readability.md": "7baca19254a9d457bbc32a6c331af71417d95420737f988ed2eacd549ade5ea1", + "context/standards/php/README.md": "b180abc62005e9a8ba440cd55602ecbd77bd5b3b68d9bb75417a965dd6ece430", + "context/standards/php/anti-patterns.md": "734d5c5c64bfadc29fde706fcde5acdd07f4c1fef87e740983ca2d4b1319594e", + "context/standards/php/architecture.md": "3112acd56156f26a80bee0b8b33f221a977e0b6e8eca710026580f02cf23a524", + "context/standards/php/frameworks.md": "e5447150a9ecee92a0eb799a08d0378530d578ccd54ff12598d1b114e97102fc", + "context/standards/php/rules.md": "346d41cf0174e4ea510bb813cd04a0e2ac1c3c7a117f9d60966f3aa297129725", + "context/standards/php/security.md": "1b603c721e7a943852302631a6ab5322b9eea14625174b463cf4e03595550bf6", + "context/standards/php/testing.md": "97df1fccc313c17cddd16b82e5f42f2a2b00658793f722c4dd90b12706b3c9f2", + "context/standards/php/tooling.md": "bf2db2c7b4fb8418d7df7858bfeabe917502545b2a5b355e9509c1afbd109232", + "context/standards/react/accessibility.md": "906791467df80e6dc2833961c81178ac061cc7d5caa9a2390431ff956082662c", + "context/standards/react/anti-patterns.md": "f3f9dbad35506732d6ed4dc01db36ca0d51214368d46072c7fb1bbca94e980a4", + "context/standards/react/component-design.md": "4c32c2a511c1200bfa6da01694455e68c1d00f1cf0ebb17ac17470203c1199be", + "context/standards/react/error-boundaries.md": "1b87938961a681bc5ac818a050778930c55204f12d57636ceaa1bcb074611671", + "context/standards/react/forms.md": "702a9050ce95e05e05ffa017eec3981de5c7ca84886a792a627ec31d1f5a9e50", + "context/standards/react/hooks.md": "037057fe079ade95915dd1e9719f3d2b748483f052ac94478944902faabdcdb9", + "context/standards/react/memoization.md": "cee82cb80230ed80aac3e9cbf115ecdc469c0f9366150b2b45e05c8c2e2d9595", + "context/standards/react/state-management.md": "753a0b5b96635469fb7b80144c4ac87f9380bd298df4bc4b7ebc7dec62d0c129", + "context/standards/react/testing.md": "286ea0fe481ccb1976045770693cb775ac26e36b41df3c9f9be2f009d8e8771b", + "context/standards/react/use-effect.md": "922bd0156149a9b939561805ead3d4f92025cc9a2ad6be2ef5fa2a182a2c06b4", + "context/standards/security/api-security.md": "49a8e2f51032d66c0f6bf6c4dc3774b9b904e82400bbb1ac982cf70dd6c3eea9", + "context/standards/security/auth.md": "1fe0b5a32515234eabca743545a8484d68dd45f0555397c3b341d176de2d421c", + "context/standards/security/secrets.md": "d86188052b2c492976609ec13faeb9770f31944b6cc44ef08f7d8a2736840dd5", + "context/standards/security/validation.md": "67017dec5962ec07b6b8c0ff431d35191392c97928e07734a8308fd6f8d09048", + "context/standards/templates/.editorconfig": "c00104e890c599d71dfecf939be1a1420897c2acc42385758ed58d457817d4d9", + "context/standards/templates/.gitignore": "8960def4458546712f82ee252f193ead8eeec44330257527e6245e13afaa6721", + "context/standards/templates/.nvmrc": "f14b4987904bcb5814e4459a057ed4d20f58a633152288a761214dcd28780b56", + "context/standards/templates/AGENTS.md": "b6a794f7cb00c27ba35256c0250e7ed182c8ab70139b6f114d3cbc8b8c2dec46", + "context/standards/templates/CLAUDE.md": "6cad5a915a4eca889930756f8c6cf033f9c756f0bd96d1bc528e2fe80c09f1be", + "context/standards/templates/biome.json": "f70832429597c1653a792641f0af311660333f6b7408752afb78578897e91096", + "context/standards/templates/eslint.config.js": "39881e3ab71f259b730be3392d0d85c58b163a4ac801590ce3030bfd1e0b0fd1", + "context/standards/templates/project-readme.md": "63d6978b3a694a0587a0bb295783f2b85d9394bd47b1bce90ce51aaffa1a32f1", + "context/standards/templates/pull-request-template.md": "9609db4174253d718c640a3a237a5f8be79948908a2f89938fae76c4da12ed28", + "context/standards/templates/tsconfig.json": "ba737dee4c097d665df9aa87c4cdf1a53d25d7aaaeb81855b2b2991038c6965f", + "context/standards/tooling/biome.md": "9efc3eaa34bd89f34c1dcb2b501c103f6ddc48f3650886040f56eb7b18133baa", + "context/standards/tooling/ci.md": "9f1f06fe631624a0b23c82db180e0bc92656058905f6bb441b24069e3bf27fbc", + "context/standards/tooling/dates.md": "aa58a8e3d72418ccd85ab18506d8e2d3f5fa5b458d59ee5552e329ac3c81031b", + "context/standards/tooling/dependencies.md": "43ee419eef3f3d1200b4ac0bb3eeaa939c6f5a95d18c6ea3743caaa2a032be43", + "context/standards/tooling/nx.md": "2b465c603057310749499effc943c4cbdf2d651660d6697707db4bf50ebf2181", + "context/standards/tooling/observability.md": "2c61950f3ae2f54332d7cca0ab7dc9d5a895a2f0cee5b6014948f12e33ee6ed9", + "context/standards/tooling/prisma.md": "d9ee1aebadb9fa965658f213861532a06ad6c1c786564884e3f495167254281e", + "context/standards/tooling/shadcn.md": "b93b919a1e8f213ac3f922e8ac89d3021f44e8be8793d94ae71fcb88dfe0f047", + "context/standards/tooling/tailwind.md": "b0dcd7499546d6958e1aefe2c74d9e406988c03983265993e216343b87acce5b", + "context/standards/tooling/tanstack-query.md": "1430896fbd2e966fc9d08741700b32d97d0db51c4916fc16d0260211445b8476", + "context/standards/tooling/tanstack-router.md": "acc0801e4c75293c0b1a9cad8fabb34bdad7a163578e0585ae1d47c926d29681", + "context/standards/tooling/tanstack.md": "e3517c3adc5e3b04cc7393c8510a4b9ffa79926279f4f4ba71fac77ff69775b7", + "context/standards/tooling/vite.md": "1d61d725f6999b24caa68894b8d76172b5583b90d76986abac10df3693609117", + "context/standards/typescript/anti-patterns.md": "24b72a5fd91f4b2858ae2cbfd267603059a5edd6e2ed16de322587cdfc4b5733", + "context/standards/typescript/error-handling.md": "af5b347965376c855a390b73581d98033304604600ecfd33d8152e6a2b1fcbd1", + "context/standards/typescript/naming.md": "c1afe5e179ba932548bdfc80a5b8e94b996003d178e77b77569e01fc796c4c95", + "context/standards/typescript/rules.md": "4fa17c5d86a87719754c4ae3104a6c6a71a55c25bbe3ce37d2e8aa3d52cb7b4f", + "context/standards/typescript/tsconfig/base.json": "5ed7251002b4c8dca0f7b58a083bd3a67d7ba8783e7efaa77062a1e919b2a24d", + "context/standards/typescript/tsconfig/nextjs.json": "c99b090c161cc32609d00b9d0f759a2c27d39520436035aa46b9f2a0e88865c8", + "context/standards/typescript/tsconfig/react.json": "ae1e5d86ceb00439dd572cc4fb59dc0cc3c437abc7c8fef3dfa260d94a2aaeda", + "context/standards/typescript/validation.md": "e93433db3fde939380a0d069dc4315d8034d0c95d051d0e59dc84c684e325a89", + "context/workflow.md": "ed58742816e57bb2757518cd2cdb01dab7315bd34f95723c840b44297aa9d26d" + } +} diff --git a/context/README.md b/context/README.md new file mode 100644 index 0000000..f3c781d --- /dev/null +++ b/context/README.md @@ -0,0 +1,81 @@ +# context/ + +Everything an agent needs to work on this project, in one agent-neutral place. Nothing here is specific to +any one coding agent — `AGENTS.md` at the repo root is the entry point that points here. + +## The one rule that outranks the others + +**No document states its own status.** There is no `**Status:**` header anywhere under `context/`. Every +status has exactly one home: + +| To know | Read | +|---|---| +| whether a feature is being worked | the `pending` / `active` marker in its [`roadmap.md`](roadmap.md) heading | +| whether a feature has a plan | whether its **Doc** field points into [`plans/`](plans/) | +| where a phase stands | that plan's own status ledger | +| what a retired feature's outcome was | its [`history.md`](history.md) row | + +A header that claims a status is a copy nobody remembers to update. The two facts "is it being worked" and +"does it have a plan" are orthogonal, which is why they live in two different places and neither can go +stale against the other. + +Nothing here is cached, parsed by a script, or generated. There is no build step in the planning loop and no +generated "current state" file — hand-editing a ledger row changes every command's answer immediately. + +## What the tool installs + +| File | Holds | Owner | +|---|---|---| +| `README.md` | this file | tool | +| [`workflow.md`](workflow.md) | the tier model, the commands, the standing invariants | tool | +| [`plan-template.md`](plan-template.md) | the bare skeleton every Tier-2 plan is copied from | tool | +| [`plan-template.notes.md`](plan-template.notes.md) | what goes in each section of that skeleton | tool | +| [`roles/coder.md`](roles/coder.md) | the coder system prompt — names no commands | tool | +| [`standards/`](standards/README.md) | engineering standards, loaded per that README's conditional table | tool* | +| [`stack.md`](stack.md) | this project's runtime, layout, conventions, and an index of your own files | project | +| [`verify.md`](verify.md) | this project's real lint / typecheck / build / test commands | project | +| [`executors.md`](executors.md) | how this project dispatches a coder and a reviewer | project | +| [`roadmap.md`](roadmap.md) | Tier 1 — the backlog. `pending` and `active` entries only | project | +| [`history.md`](history.md) | index of retired features, newest last | project | +| [`findings.md`](findings.md) | open findings that gate a phase from being marked `done` | project | +| [`drafts/`](drafts/) | notes and source material for ideas not yet planned — no ledger | project | +| [`plans/`](plans/) | Tier 2 — documents with an executable phase ledger | project | +| [`archive/`](archive/) | retired plans, moved here by `/feature-close` | project | + +`*` `standards/` is tool-owned only while it is the bundled default and unmodified. Swap it with +`standards add `, or edit it, and it becomes yours — it drops out of the manifest and updates +stop reaching it. + +A document moves once per tier transition, and **which directory it sits in tells you what it is**: + +``` +drafts/ ──/feature-plan──▶ plans/ ──/feature-close──▶ archive/ +notes a phase ledger retired +``` + +`plans/` means "has an executable ledger" and nothing more — it does not imply the feature is being worked. +If a document is ever misfiled, the roadmap's link to it breaks loudly rather than lying quietly. + +## Ownership + +This directory mixes tool-supplied content with project state. The boundary is enforced by a data structure, +not by a rule someone has to remember: `context/.state/manifest.json` lists every tool-owned file with its +hash, `update` walks that manifest, and a project-owned file is not in it — so no code path reaches it. + +| Tool-owned — replaced on `update` | Project-owned — unreachable by the updater | +|---|---| +| `README.md`, `workflow.md`, `plan-template*.md`, `roles/` | `stack.md`, `verify.md`, `executors.md` | +| the skill and agent trees, the `AGENTS.md` block | `roadmap.md`, `history.md`, `findings.md` | +| `standards/` while it is ours and unmodified | `drafts/`, `plans/`, `archive/`, `CLAUDE.md` | + +`update` prints both columns when it runs. A visible boundary beats a documented one. + +**Anything else you add under `context/` is yours forever**, by the same property — `context/decisions.md`, +`context/glossary.md`, `context/ops-notes.md` all survive by default, with no feature required to protect +them. Index your own additions in [`stack.md`](stack.md), not here: this file is replaced on every update, +so a line you add to it is a line you lose. + +## What does not belong here + +Planning artifacts live in `context/`, wherever else your docs live. Product specs, API references and +anything else written for humans or library consumers stay where this project already keeps them. diff --git a/context/archive/.gitkeep b/context/archive/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/context/drafts/.gitkeep b/context/drafts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/context/executors.md b/context/executors.md new file mode 100644 index 0000000..c5bbbd6 --- /dev/null +++ b/context/executors.md @@ -0,0 +1,64 @@ +# Executors + +How this project dispatches a **coder** and a **reviewer**. Hand-written prose, read fresh at dispatch +time — the exact parallel to [`verify.md`](verify.md), and for the same reason: a skill that hardcodes an +invocation bakes one machine's setup into a tool that ships everywhere. + +Configured by `/onboard` on 2026-09-05. + +## Coder + +**Not configured — implement in-host.** + +This is a deliberate choice, not a gap. The host implements directly; no external coder CLI is dispatched, +so the repository-read test in `/onboard` Step 2 does not apply and no invocation is recorded here. + +`codex` and `cursor-agent` are both present on the maintainer's machine and were considered. If either is +adopted later, write its exact invocation here along with any directory or permission scoping, test that it +can read this repository unaided before trusting it, and record the result of that test — the standing +rules below say why the answer changes how briefs are written. Its system prompt is +[`roles/coder.md`](roles/coder.md). + +## Reviewer + +**The bundled `reviewer` subagent, dispatched with the Agent tool.** + +``` +Agent(subagent_type: "reviewer", …) +``` + +Its definition is [`.claude/agents/reviewer.agent.md`](../.claude/agents/reviewer.agent.md) — tool-owned, +replaced on update. It runs with `Read, Grep, Glob, Bash` only, gathers evidence before forming a verdict, +and emits `PASS | PASS WITH NOTES | FAIL` with a `P0`–`P3` severity on every blocking finding. That is the +Gate 2 contract's shape exactly, so nothing has to be mapped or translated at the gate. + +Give it the implementation output, the plan's review checklist, and the verification result. It does not +re-run verification and it does not fix anything. + +Two alternatives were considered and rejected on 2026-09-05: + +- **`/code-review`** reports findings by category (`correctness`, `simplification`) rather than `P0`–`P3`, + which would put an undefined translation step inside the gate. Its `ultra` level runs in the cloud, is + user-triggered and billed, and **cannot be launched by the host** — so it can never serve as the Gate 2 + executor. It remains useful when a human wants a deeper look; it is not the gate. +- **The host reviewing its own diff** is the fallback if the subagent is ever unavailable. It is weaker + than an independent reviewer, and any gate run that falls back to it must say so. + +## The contract, whatever is configured + +A review happens, it returns a verdict with a `P0`–`P3` severity on every blocking finding, and a `FAIL` +writes a finding to [`findings.md`](findings.md) **before** the loopback. + +## Standing rules for any external executor + +- **Exit code alone proves nothing.** A CLI can exit 0 after hitting a usage limit mid-run, having + completed most but not provably all of a brief. Grep the captured output for exhaustion and error + markers before trusting a summary, and on a hit check `git status` and each acceptance criterion + individually. +- **Take the model from the CLI's own config**, not from a flag written here. A hardcoded model flag is one + more place to update when models turn over, and a rejected model can still exit 0 having written nothing. +- **No blanket permission-bypass flag.** Scope permissions in the CLI's own config instead. A standing + bypass-everything instruction in a committed file is persistent privilege escalation. +- **Assume the executor can read this repository** unless you have tested otherwise. Briefs cite paths; + they do not paste file contents. If an executor genuinely has no filesystem access, say so here — that is + the one case where a brief has to carry content inline. diff --git a/context/findings.md b/context/findings.md new file mode 100644 index 0000000..bacb273 --- /dev/null +++ b/context/findings.md @@ -0,0 +1,40 @@ +# Findings + +Defects that outlive the session that found them. A reviewer `FAIL`, a verification gate that hits its +loopback cap, or a defect found by hand all land here — **before** the loopback, not after it, so a finding +survives even when the cap is hit and the task is escalated. + +## Contract + +**Severity.** `P0` breaks production or data. `P1` blocks a phase or a gate. `P2` is a real defect that +does not block. `P3` is a note worth not losing. + +**Tied to.** Either a phase — ` Phase ` — or `ad-hoc` for a finding raised by +`/orchestrate` outside any feature. + +**Gating.** An open `P0` or `P1` tied to a phase blocks that phase from being marked `done`, and blocks +`/feature-close` on the feature that owns it. + +**Closing.** A finding closes when **the gate that raised it re-passes**, citing that run. There is no +"fixed but unverified" state — that implies an owner this workflow does not have. + +**Bound.** Closed findings leave this file: a feature's at `/feature-close`, folded into the retiring +plan's own log; an `ad-hoc` one at the start of the next `/orchestrate`. This file must not grow for the +life of the project. + +**Shape.** Entries go under **Open** below, newest last, and look like this: + +> ### F-001 — P1 — one line naming the defect +> +> **Tied to:** some-feature Phase 2 · **Raised:** YYYY-MM-DD (by the gate that raised it, or "hand") +> +> What is wrong, where, and why it matters. Cite the file and the evidence. +> +> **Closes when:** the condition that closes it, naming the gate run that would prove it. + +--- + +## Open + +## Closed + diff --git a/context/history.md b/context/history.md new file mode 100644 index 0000000..dae968e --- /dev/null +++ b/context/history.md @@ -0,0 +1,13 @@ +# History + +Retired features, newest last. Append-only — written by `/feature-close`, one row per feature, never edited +afterwards. + +This file **indexes** depth rather than duplicating it: the reasoning stays in the archived plan document, +so a row never grows and this file gains one line per feature. + +`dropped` is the load-bearing case. An idea killed before it ever got a plan document leaves no trace +except git history, and this row is what stops it being re-proposed. + +| Date | Feature | Outcome | Why | Document | +|---|---|---|---|---| diff --git a/context/plan-template.md b/context/plan-template.md new file mode 100644 index 0000000..a854869 --- /dev/null +++ b/context/plan-template.md @@ -0,0 +1,69 @@ +# Plan + +Written . The `` entry in +[`../roadmap.md`](../roadmap.md) is where this feature's status lives. + +**Phase status lives in §6.1 of this document, and nowhere else.** + +--- + +## 1. Why + + + +## 2. Constraints + + + +## 3. Decisions + +**D1.** Rejected: , because . + +## 4. Design + + + +## 5. Risks + + + +## 6. Phases + +### 6.1 Status ledger + +| # | Phase | Status | Depends on | Note | +|---|---|---|---|---| +| 1 | | not started | — | | +| 2 | | not started | 1 | | + +Status is one of `not started`, `in progress`, `blocked`, `done`. `done` only when committed and verified, +and whoever finishes a phase updates the row in the same commit. + +**Exactly one table in this document has these columns.** Do not add a second phase table — a +differently-shaped one nearby is a decoy that gets read by mistake. + +### 6.2 The phases + +#### Phase 1 — + +**Files:** + +**Scope:** + +**Done when:** + +#### Phase 2 — + +**Files:** + +**Scope:** + +**Done when:** + +## 7. Verification + + + +## 8. Open questions + +- diff --git a/context/plan-template.notes.md b/context/plan-template.notes.md new file mode 100644 index 0000000..85e53fd --- /dev/null +++ b/context/plan-template.notes.md @@ -0,0 +1,55 @@ +# Filling in a Tier-2 plan + +[`plan-template.md`](plan-template.md) is the skeleton. `/feature-plan` copies it **verbatim** into +`context/plans/-PLAN.md` and then fills it in — there is no fenced block to extract and no italic +guidance to strip, because all of that is here instead. + +Sections may be added. The ones in the template may not be dropped or reordered, and the ledger's column +set is fixed — `check` compares every plan's ledger against the template's. + +## What goes in each section + +**§1 Why** — the problem, with evidence. Measurements, failing cases, user-visible symptoms; not "it would +be nice if". A reader who disagrees with this section should not read further, and a reader who agrees +should not need convincing again later. + +**§2 Constraints** — what the solution may not do. Runtime limits, compatibility promises, things already +decided elsewhere that this must respect. Link to [`stack.md`](stack.md) rather than restating it. + +**§3 Decisions** — numbered `D1`, `D2`, … so later sections and source comments can cite them by number. +Each one: the decision, the alternative rejected, and why. This is the section that stops a question being +re-litigated in three months. + +**§4 Design** — how it works. Sub-sections as the shape of the work demands. Cite file paths for anything +that already exists. + +**§5 Risks** — what could go wrong, how it would show up, and what the response is. "Unknown" is an +acceptable response; silence is not. + +**§6.1 Status ledger** — one row per phase, `not started` in a newly written plan. Every row needs a real +`Depends on` value (`—` for none): the phase-selection rule *is* "lowest-numbered phase that is not `done` +and whose `Depends on` are all `done`", so a missing column silently degrades it. + +**§6.2 The phases** — one sub-section per ledger row. **The `Files:` line is required, not advisory.** It +is what turns `/feature-status`'s reconciliation from a judgement call into a check: without it, "does the +repo match the ledger" has no answer. A phase is a **commit-sized unit of work with a checkable outcome**, +not a category of activity — "grammar plus container support for the new token" is a phase; "testing" is +not. + +**§7 Verification** — how to prove the feature works, beyond [`verify.md`](verify.md) passing. Commands to +run by hand, files to eyeball, numbers to compare against §1's measurements. Anything that belongs to the +project's standing verification stack goes in `verify.md` instead, not here. + +**§8 Open questions** — what the plan could not settle. Do not paper over them. + +## Standing rules + +- **The document never states its own status.** No `**Status:**` header, ever. Feature status lives in + [`roadmap.md`](roadmap.md), phase status in §6.1, retired outcomes in [`history.md`](history.md) — one + place each. `check` fails a plan that grows one. +- **`/feature-plan` produces a reviewable skeleton plus open questions**, not a finished plan of record. + §1, §3 and §6.1 are the sections research can usefully draft. §4 and §5 usually arrive as open questions. +- **Cite by §-number, not by line number.** Source comments cite plan sections; line numbers rot on the + first edit, and a §-number survives the move into `archive/`. +- **Never mark a phase `done` in a plan that has not been executed.** Every phase in a new plan is + `not started`. diff --git a/context/plans/.gitkeep b/context/plans/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/context/roadmap.md b/context/roadmap.md new file mode 100644 index 0000000..c558f17 --- /dev/null +++ b/context/roadmap.md @@ -0,0 +1,29 @@ +# Roadmap + +Tier 1 — the backlog. High-level by design: depth belongs in the plan document, not here. + +Only `pending` and `active` entries live in this file. Finished and abandoned entries leave entirely, via +`/feature-close`, and are indexed in [`history.md`](history.md). + +**An entry's status is the marker in its heading, and nowhere else.** At most one entry is `active` at a +time. There is no "active" section and no summary line — a second place to say the same thing is a second +place for it to go stale. To know what is being worked, scan for the marker. + +Each entry's **Doc** field points at its document, and the path says what stage it is at: `drafts/` is +notes, `plans/` is a real plan with a phase ledger. `pending` with a `plans/` document means planned but +not being worked — that is a normal state, not a discrepancy. + +Add an entry with `/roadmap "some idea"`. Turn one into a plan with `/feature-plan`. Entries look like +this, appended under **Features** below: + +> ### some-feature-name — `pending` +> +> One or two lines: the problem, or what becomes possible. Not a design. +> +> - **Size:** small — what drives the size, a few words +> - **Doc:** none yet + +--- + +## Features + diff --git a/context/roles/coder.md b/context/roles/coder.md new file mode 100644 index 0000000..edb4cb7 --- /dev/null +++ b/context/roles/coder.md @@ -0,0 +1,76 @@ +# Coder + +You implement one task, exactly as briefed. You do not choose what to build and you do not decide when it +is finished — two gates do that. + +Consult current documentation for any language, framework or library you touch. Your training data is +older than the ecosystem you are working in, even for technology you know well. Verify, then cite what you +verified. + +## What you receive + +- A **plan** — the scope, the files, the acceptance criteria, and the review checklist the reviewer will + use. +- Paths to the project's standards. **Read them yourself**; they are files in this repository, not + something that has to be pasted into your brief. +- On a loopback: your prior implementation and the validator's verbatim feedback. + +## What happens to your output + +Two gates, in order: + +1. **Verification** — the project's real lint, typecheck, build and test commands, listed in + `context/verify.md`. That file is the only place those commands are written down. Read it if you want to + run them yourself before declaring the task done; never assume what they are. +2. **Review** — your implementation read against the plan's review checklist and the project's standards. + +Before you declare a task done, walk through both: *will verification pass?* — fix anything you can already +see will fail. *What will the reviewer flag?* — fix the obvious ones. + +## Loopback rules + +When you are re-invoked with validator feedback: + +- **Address only the failing items.** No exceptions. +- **Do not refactor passing code**, even if you would structure it differently now. +- **Do not expand scope.** No features, comments or improvements the validator did not ask for. +- Reply with a focused summary that maps each fix to the specific feedback item it resolves. + +This is what makes the loop converge instead of spin. + +## Output contract + +Every task output includes: + +- **Summary** — one to three sentences on what you did. +- **Files** — every file created or modified, with a brief per-file description. +- **Acceptance criteria coverage** — for each criterion in the plan, the implementation detail covering it. +- **Notes for testing** — fixtures, edge cases and setup details that are not obvious from the diff. +- **Notes for the reviewer** — deviations from the plan with justification, and any decision worth + flagging. + +This structure is what lets the gates do their jobs without guessing what you intended. + +## Coding principles + +1. **Structure** — a consistent, predictable layout. Group code by feature; keep shared utilities minimal; + make entry points obvious. Before scaffolding several files, identify the shared structure first and use + the framework's own composition patterns for it. Duplication that requires the same fix in several + places is a smell, not a pattern. +2. **Architecture** — prefer flat, explicit code over abstraction and deep hierarchy. Avoid clever + patterns, metaprogramming and unnecessary indirection. Minimise coupling. +3. **Functions and modules** — keep control flow linear. Small-to-medium functions, shallow nesting, state + passed explicitly rather than reached for. +4. **Naming and comments** — descriptive but simple names. Comment to record invariants, assumptions and + external requirements; not to narrate the code. +5. **Errors and logging** — make errors explicit and informative. Log at boundaries, with structure. +6. **Regenerability** — write so that any single file can be rewritten from scratch without breaking the + system. Prefer declarative configuration. +7. **Platform** — use platform conventions directly and simply, without wrapping them. +8. **Modifications** — when extending or refactoring, follow the patterns already in the file. +9. **Quality** — favour deterministic, testable behaviour. Keep tests focused on observable behaviour. + +## Diagnose before fixing + +When something fails, identify the root cause and say what it is before applying a fix. A fix applied to a +symptom you have not explained is a guess, and it will be reviewed as one. diff --git a/context/stack.md b/context/stack.md new file mode 100644 index 0000000..5b57d9f --- /dev/null +++ b/context/stack.md @@ -0,0 +1,102 @@ +# Stack + +`@northguild/worktree` is a Node CLI for managing git worktrees, published to npm and built on +[oclif](https://oclif.io/docs/api_reference). The repository is a two-package pnpm workspace: the CLI at +the root, and a Next.js/Nextra documentation site under `docs/` that deploys to GitHub Pages and talks to +a small Cloudflare Worker proxying Gemini for its chat feature. + +One piece of history explains a lot of the current shape: the project moved from the `@burglekitt` org to +`@northguild` in 35fc08f (2026-08-26). Package names, the biome plugin and the docs' shared library all +changed scope in that commit, so anything installed or generated before it is stale in a way that produces +confusing errors rather than obvious ones — see the prerequisites in [`verify.md`](verify.md). + +| Concern | Target | +|---|---| +| Runtime | Node 24 (CI pins it; `@types/node` is still on 18) | +| Package manager | pnpm 10.32.1, workspace with one member (`docs`) | +| Database | none | +| Storage | none | +| Hosting | CLI → npm; docs → GitHub Pages; chat proxy → Cloudflare Workers | + +## Layout + +``` +bin/ oclif entry point — bin/run.js, produced by tsc +src/commands/ one file per CLI command; each default-exports a BaseCommand subclass +src/lib/ CLI helpers — git integration, validators, env, types, constants, cli +src/integrations/ GitHub and Jira integrations +scripts/ version-sync scripts, run by `pnpm sync-version` +skills/ shipped inside the npm package (package.json `files`); generated — see below +docs/ Next.js 16 + Nextra 4 docs app, React 19, Base UI; own package.json +docs/src/app/ docs content +docs/src/ UI, components, chat client, site metadata +docs/worker/ Cloudflare Worker proxying Gemini for the docs chat +context/ planning-workflow artifacts (this directory) +.github/agents/ documentation-only agent manifests — nothing executes them +``` + +## Conventions + +- **ESM throughout** (`"type": "module"`). Relative imports carry a `.js` extension even in TypeScript + source — `import { BaseCommand } from "../lib/base-command.js"`. This is a runtime requirement, not a + style choice; dropping the extension breaks the built CLI. +- **Avoid `export default`.** The one exception is `src/commands/*.ts`: oclif requires each command file to + default-export its command class. `src/lib/` and `src/integrations/` use named exports throughout. +- **New commands follow the existing pattern** — extend `BaseCommand` from `src/lib/base-command.js`, use + the oclif flags/args pattern, and add a colocated `src/commands/.test.ts`. +- **Tests are colocated** as `*.test.ts` next to the code they cover, in both workspaces. vitest. +- **Biome owns formatting and linting**, configured in `biome.json` with the `@northguild/gmt-biome` grit + plugin. Never add a file named `biome.json` or `biome.jsonc` anywhere else in the tree, including as an + example or template — Biome 2 loads every one it finds as a nested config, and no exclusion in the root + config can stop it. [`verify.md`](verify.md) has the detail; the standards bundle's starter template is + renamed to `biome-example.json` here for exactly this reason. +- **The docs app is static/SSR and must never hold a secret.** `GEMINI_API_KEY` lives in Cloudflare Worker + secrets (`pnpm --filter docs worker:setup-secret`, i.e. `wrangler secret put`). For local development, + `worker:setup-dev-vars` reads it from `docs/.env.local` and writes `docs/worker/.dev.vars`; both are + gitignored. `ci.yml` runs a secret scan that fails the build if a key value is committed. +- **Three generated files must be committed in sync with `package.json`'s version:** + `docs/src/lib/site-meta.ts`, `skills/core/SKILL.md`, `skills/_artifacts/skill_tree.yaml`. Run + `pnpm sync-version` after a version bump; CI hard-fails on drift via `git diff --exit-code`. Use the + root `pnpm docs:dev`, not `pnpm --filter docs dev` — the former syncs the version first. +- **One lockfile, at the root.** There is no `.npmrc` — the one that pinned + `shared-workspace-lockfile=true` was removed in 260eb2f, and pnpm's default keeps the behaviour. CI + still fails the build if `docs/pnpm-lock.yaml` ever appears, so do not add one. +- **Never deploy from a workstation.** Worker and docs deploys belong to `worker-deploy.yml` and + `docs-deploy.yml`. `wrangler.toml` pins local dev to port 8787 and asks you to free the port rather than + let it drift. +- **Console output is styled with chalk, so it is TTY-dependent.** Any test asserting on printed text has + to control colour explicitly — see the `FORCE_COLOR=0` note in [`verify.md`](verify.md). + +## Agent customization lives in `.claude/` and `.agents/` + +Project skills are in `.claude/skills/` and `.agents/skills/`; agents in `.claude/agents/`. Both trees are +tool-owned and replaced on update. + +`.github/agents/` is **documentation only and nothing runs it.** Its own README says so, the +`scripts/run-agents.js` runner it proposes was never written, and no workflow references the directory. +`.github/prompts/` and `.github/instructions/`, recommended by the pre-overlay `AGENTS.md`, do not exist. +The directory is left in place; treat it as a note to contributors, not as a mechanism. + +## `context/standards/` is project-owned + +It no longer tracks the bundled default. Two deliberate changes were made on 2026-09-05: + +- **PHP removed** — `standards/php/` and `docs/PHP-SPEC.md` deleted, and every reference to them dropped + from `standards/README.md` and `standards/philosophy/ai-agent-behavior.md`. This repo has no PHP and the + conditional table should not offer it. +- **`templates/biome.json` renamed to `biome-example.json`** — see [`verify.md`](verify.md). + +Editing any file under `standards/` flips the whole tree to project-owned in +`@baldurpan/create-ai-workflow`'s manifest, which is what makes both changes stick: `update` now skips +every `context/standards/*` path rather than restoring it. The trade is that upstream standards fixes no +longer arrive automatically — refresh by re-vendoring with `standards add ` when you want them, +and expect to redo these two changes afterwards. Everything outside `standards/` is unaffected and still +updates normally. + +## Also in `context/` + +Nothing beyond what the tool installs. Index anything you add here — not in `context/README.md`, which is +tool-owned and replaced on every update. + +Verification commands are in [`verify.md`](verify.md), not here. Executor dispatch is in +[`executors.md`](executors.md). diff --git a/context/standards/.source b/context/standards/.source new file mode 100644 index 0000000..b8b91fc --- /dev/null +++ b/context/standards/.source @@ -0,0 +1,8 @@ +# Origin of this standards tree, recorded so `update` can report when upstream has moved. +# Managed by @baldurpan/create-ai-workflow. Replace this whole directory with: +# npx @baldurpan/create-ai-workflow standards add +# Vendored verbatim, with one mechanical rename: npm cannot publish a file called `.gitignore`, so it +# ships as `templates/_dot_gitignore` and is written back with its leading dot at install time. +origin=https://github.com/baldurpan/ai-engineering-standards +ref=dd8b7e206ff46d7555aabb2522c792d9272510b9 +vendored=2026-05-19T22:07:27+00:00 diff --git a/context/standards/README.md b/context/standards/README.md new file mode 100644 index 0000000..f891619 --- /dev/null +++ b/context/standards/README.md @@ -0,0 +1,100 @@ +# AI Engineering Standards + +Personal AI-native engineering standards for TypeScript and React projects. Optimized for AI agent consumption while remaining readable for humans. + +**Source:** `https://github.com/baldurpan/ai-engineering-standards` + +**Stack:** TypeScript on Node.js, React on the frontend. All standards in `philosophy/`, `typescript/`, `react/`, `architecture/`, `tooling/`, and `security/` apply to this stack. + +--- + +## For Humans — How to Use This Repo + +In your project, create an `AGENTS.md` that references this repo: + +```md +# AGENTS.md + +This project follows [AI Engineering Standards](https://github.com/baldurpan/ai-engineering-standards). +Load that repo's `README.md` and follow its conditional-loading guidance. + +## Project / Stack / Commands +[your project-specific content] +``` + +Copy [`templates/AGENTS.md`](templates/AGENTS.md) as a starting point. Optionally add a `CLAUDE.md` that points at your `AGENTS.md` ([`templates/CLAUDE.md`](templates/CLAUDE.md)). + +Everything below this line is for the AI agent. + +--- + +## For AI Agents — Standards Index + +You are working inside a project that references this standards repository. Load only the sections relevant to the current task. All paths below are relative to the repository root (`https://github.com/baldurpan/ai-engineering-standards`). + +### Conditional Loading + +| If the task involves… | Load… | +|---|---| +| Any task (always) | [`philosophy/ai-agent-behavior.md`](philosophy/ai-agent-behavior.md), [`philosophy/core-principles.md`](philosophy/core-principles.md) | +| TypeScript code | [`typescript/rules.md`](typescript/rules.md), [`typescript/anti-patterns.md`](typescript/anti-patterns.md) | +| Validation / schemas | [`typescript/validation.md`](typescript/validation.md), [`security/validation.md`](security/validation.md) | +| Naming | [`typescript/naming.md`](typescript/naming.md) | +| Errors | [`typescript/error-handling.md`](typescript/error-handling.md) | +| React components or hooks | [`react/component-design.md`](react/component-design.md), [`react/hooks.md`](react/hooks.md), [`react/use-effect.md`](react/use-effect.md) | +| State management | [`react/state-management.md`](react/state-management.md) | +| Forms | [`react/forms.md`](react/forms.md) | +| Accessibility | [`react/accessibility.md`](react/accessibility.md) | +| Performance / memoization | [`react/memoization.md`](react/memoization.md) | +| Error boundaries / Suspense / loading states | [`react/error-boundaries.md`](react/error-boundaries.md) | +| Testing | [`react/testing.md`](react/testing.md) | +| Refactoring | [`architecture/refactoring.md`](architecture/refactoring.md), [`philosophy/incremental-abstraction.md`](philosophy/incremental-abstraction.md) | +| Folder structure / new module | [`architecture/feature-driven.md`](architecture/feature-driven.md), [`architecture/folder-structure.md`](architecture/folder-structure.md), [`architecture/dependency-boundaries.md`](architecture/dependency-boundaries.md) | +| Shared code decisions | [`architecture/shared-code.md`](architecture/shared-code.md) | +| Monorepo work | [`architecture/monorepos.md`](architecture/monorepos.md) | +| API client / contract design | [`architecture/api-design.md`](architecture/api-design.md) | +| Date / time / timezone handling | [`tooling/dates.md`](tooling/dates.md) | +| Tooling — TanStack (overview) | [`tooling/tanstack.md`](tooling/tanstack.md) | +| Tooling — TanStack Router (deep) | [`tooling/tanstack-router.md`](tooling/tanstack-router.md) | +| Tooling — TanStack Query (deep) | [`tooling/tanstack-query.md`](tooling/tanstack-query.md) | +| Tooling — Vite | [`tooling/vite.md`](tooling/vite.md) | +| Tooling — Biome | [`tooling/biome.md`](tooling/biome.md) | +| Tooling — Tailwind | [`tooling/tailwind.md`](tooling/tailwind.md) | +| Tooling — shadcn/ui | [`tooling/shadcn.md`](tooling/shadcn.md) | +| Tooling — Prisma | [`tooling/prisma.md`](tooling/prisma.md) | +| Tooling — Nx | [`tooling/nx.md`](tooling/nx.md) | +| Adding a dependency | [`tooling/dependencies.md`](tooling/dependencies.md) | +| CI / PRs / merge strategy | [`tooling/ci.md`](tooling/ci.md) | +| Logging, metrics, error monitoring | [`tooling/observability.md`](tooling/observability.md) | +| Security — auth | [`security/auth.md`](security/auth.md) | +| Security — secrets | [`security/secrets.md`](security/secrets.md) | +| Security — API design | [`security/api-security.md`](security/api-security.md) | +| Concrete examples | [`examples/good/`](examples/good/), [`examples/bad/`](examples/bad/) | + +### Repository Map + +| Directory | Purpose | +|---|---| +| [`philosophy/`](philosophy/) | Core principles and AI agent behavior rules | +| [`typescript/`](typescript/) | TypeScript rules, anti-patterns, validation, naming, error handling | +| [`typescript/tsconfig/`](typescript/tsconfig/) | Reference tsconfig presets (base, React, Next.js) | +| [`react/`](react/) | Component design, hooks, state, forms, a11y, testing | +| [`architecture/`](architecture/) | Feature-driven structure, monorepos, refactoring, dependency rules | +| [`tooling/`](tooling/) | TanStack, Nx, Vite, Biome, Tailwind, shadcn, Prisma | +| [`security/`](security/) | Validation, secrets, auth, API security | +| [`examples/`](examples/) | Concrete good and bad code examples | +| [`templates/`](templates/) | Drop-in starter files — `CLAUDE.md`, `AGENTS.md`, `tsconfig.json`, `biome-example.json`, `eslint.config.js`, `.gitignore`, `.editorconfig`, `.nvmrc`, project README, PR template | + +### Core Philosophy + +> Prefer explicit, composable, feature-oriented architecture over generalized abstractions and centralized complexity. + +See [`philosophy/core-principles.md`](philosophy/core-principles.md) for the full architectural values. + +### Documentation Style + +All docs are directive and concise. Expect: +- `## DO` / `## DO NOT` / `## PRIORITY` sections +- Side-by-side good and bad code examples +- Hierarchical headings, no prose essays +- Cross-references via relative links diff --git a/context/standards/architecture/api-design.md b/context/standards/architecture/api-design.md new file mode 100644 index 0000000..928b8d4 --- /dev/null +++ b/context/standards/architecture/api-design.md @@ -0,0 +1,212 @@ +# API Design and Client Patterns + +## Core Rules + +- API types are generated or inferred — never duplicated by hand +- Validate at the boundary on both sides (server validates incoming, client validates responses) +- Use a consistent error response shape across all endpoints +- Versioning is explicit when contracts change in breaking ways +- Mock servers in development — do not block frontend work on backend availability + +## Choosing an API Layer + +| Approach | Use when | +|---|---| +| **tRPC** | Same team owns FE and BE, both in TypeScript — end-to-end type safety, no codegen step | +| **OpenAPI + generated client** | Polyglot backend, public API, or external consumers | +| **GraphQL** | Many client types with different field needs, or complex relational queries | +| **Hand-written REST + Zod** | Small project, no FE/BE coordination needed, simple endpoints | + +Pick one per service. Mixing approaches creates two type pipelines to maintain. + +## tRPC Pattern + +```ts +// server/router.ts +import { initTRPC } from "@trpc/server"; +import { z } from "zod"; + +const t = initTRPC.create(); + +export const appRouter = t.router({ + invoice: t.router({ + list: t.procedure + .input(z.object({ status: z.enum(["all", "open", "paid"]).default("all") })) + .query(({ input }) => db.invoice.findMany({ where: { status: input.status } })), + + create: t.procedure + .input(createInvoiceSchema) + .mutation(({ input, ctx }) => + db.invoice.create({ data: { ...input, userId: ctx.userId } }), + ), + }), +}); + +export type AppRouter = typeof appRouter; +``` + +```ts +// client — types flow from the server router definition +const invoices = await trpc.invoice.list.query({ status: "open" }); +// invoices is fully typed; no manual DTO +``` + +## OpenAPI Pattern + +For polyglot or public APIs: + +```bash +# Generate a typed client from an OpenAPI spec +pnpm dlx openapi-typescript ./openapi.yaml -o ./src/lib/api-types.ts +``` + +Use a small fetch wrapper that validates with the generated types: + +```ts +import type { paths } from "~/lib/api-types"; +import createClient from "openapi-fetch"; + +export const api = createClient({ baseUrl: env.VITE_API_BASE_URL }); + +const { data, error } = await api.GET("/invoices", { params: { query: { status: "open" } } }); +``` + +## Hand-Written Fetch + Zod + +For small projects without a generated client: + +```ts +import { z } from "zod"; + +const invoiceSchema = z.object({ id: z.string(), title: z.string(), amount: z.number() }); +const invoicesResponseSchema = z.object({ items: invoiceSchema.array(), nextCursor: z.string().nullable() }); + +export async function fetchInvoices(cursor?: string) { + const url = new URL("/api/invoices", env.VITE_API_BASE_URL); + if (cursor) url.searchParams.set("cursor", cursor); + + const response = await fetch(url); + if (!response.ok) { + throw new ApiError(response.status, await response.text()); + } + return invoicesResponseSchema.parse(await response.json()); +} +``` + +## Standard Error Response Shape + +Use one error shape across all endpoints: + +```ts +// shared/schemas/api-error.schema.ts +export const apiErrorSchema = z.object({ + error: z.object({ + code: z.string(), // machine-readable: "NOT_FOUND", "VALIDATION_FAILED" + message: z.string(), // human-readable, safe to show + details: z.record(z.unknown()).optional(), // field errors, etc. + requestId: z.string().optional(), + }), +}); +``` + +Server example: + +```ts +return res.status(422).json({ + error: { + code: "VALIDATION_FAILED", + message: "Some fields are invalid", + details: result.error.flatten().fieldErrors, + requestId: req.id, + }, +}); +``` + +## Pagination + +Prefer **cursor-based pagination** for performance and stability with mutable datasets: + +```ts +// Request: GET /invoices?cursor=eyJpZCI6...&limit=20 +// Response: +{ + items: Invoice[], + nextCursor: string | null, // null when at end +} +``` + +Use **offset/page pagination** only for small, stable datasets where the user expects page numbers (admin tables, search results). + +## Versioning + +When making a breaking change: + +- **Additive change** (new optional field, new endpoint) — no version bump needed +- **Breaking change** (removed field, changed type, removed endpoint) — bump the API version + +Common strategies: +- URL prefix: `/v1/invoices`, `/v2/invoices` +- Header: `Accept: application/vnd.acme.v2+json` + +Keep the old version working for one deprecation cycle (typically 1–2 releases). Document the migration path. + +## Mock Servers + +Frontend should never be blocked on backend availability. + +Options: +- **MSW** (Mock Service Worker) — intercepts fetch at the browser/Node level; same handlers in tests and dev +- **Prism** — runs an OpenAPI spec as a mock server +- **tRPC** — type-safe mocks via `@trpc/react-query`'s `createTRPCMsw` + +```ts +// MSW handler +import { http, HttpResponse } from "msw"; + +export const handlers = [ + http.get("/api/invoices", () => + HttpResponse.json({ + items: [{ id: "1", title: "Q4 Report", amount: 500 }], + nextCursor: null, + }), + ), +]; +``` + +## Idempotency + +For mutations that might be retried (network errors, optimistic UI), accept an `Idempotency-Key` header so the same request can be safely sent twice: + +```ts +// Client +await api.POST("/invoices", { + body: invoice, + headers: { "Idempotency-Key": crypto.randomUUID() }, +}); +``` + +The server deduplicates on this key for some window (e.g., 24 hours). + +## DO NOT + +- Hand-write FE types that mirror BE types — generate or infer instead +- Return different error shapes from different endpoints +- Mix snake_case and camelCase in the same API surface +- Expose internal IDs / database errors / stack traces in error responses +- Skip mocking in development — it slows everyone down + +## PRIORITY + +``` +Generated types > Hand-written DTOs +Cursor pagination > Offset pagination +Mock-first frontend > Blocked-on-backend frontend +``` + +## See Also + +- [`typescript/validation.md`](../typescript/validation.md) — schema-driven validation +- [`security/api-security.md`](../security/api-security.md) — status codes, rate limiting, CORS +- [`security/validation.md`](../security/validation.md) — input validation at boundaries +- [`tooling/tanstack-query.md`](../tooling/tanstack-query.md) — TanStack Query patterns +- [`tooling/tanstack-router.md`](../tooling/tanstack-router.md) — route loaders, typed search params diff --git a/context/standards/architecture/dependency-boundaries.md b/context/standards/architecture/dependency-boundaries.md new file mode 100644 index 0000000..7b84495 --- /dev/null +++ b/context/standards/architecture/dependency-boundaries.md @@ -0,0 +1,104 @@ +# Dependency Boundaries + +## Core Rule + +Features must not depend on each other. Features depend on `shared/`. `shared/` depends on nothing internal. + +## Allowed Dependencies + +``` +apps/web → packages/ui, packages/schemas +apps/api → packages/schemas +features/X → shared/ +features/X → lib/ +shared/ → lib/ +lib/ → external packages only +packages/* → external packages only (no internal app code) +``` + +## Forbidden Dependencies + +``` +features/invoices → features/users ✗ cross-feature import +packages/ui → apps/web ✗ package depends on app +shared/ → features/ ✗ shared depends on feature +``` + +## In Practice + +If feature A needs something from feature B: + +**Option 1: Move it to `shared/`** +- Move the shared concept to `shared/` +- Both features import from `shared/` + +**Option 2: Pass it as a prop or callback** +- Feature A receives the data it needs via props +- Feature B provides the data at the composition layer (page, layout) + +**Option 3: Use a shared store or context** +- If it is runtime state (current user, theme), put it in context or Zustand +- Both features read from the same source + +## Detecting Violations + +In a monorepo, use Nx's enforcement: + +```json +// nx.json +{ + "targetDefaults": { + "lint": { + "configurations": { + "enforce-boundaries": true + } + } + } +} +``` + +In a single app, use ESLint's `import/no-restricted-paths`: + +```js +// eslint.config.js +{ + rules: { + "import/no-restricted-paths": ["error", { + zones: [ + { + target: "./src/features", + from: "./src/features", + except: ["./src/features/invoices"], // each feature only allows itself + }, + ], + }], + } +} +``` + +## Circular Dependencies + +Circular dependencies are always forbidden. They indicate a design problem. + +Common causes: +- `shared/` importing from a feature +- Two features that should be combined into one +- Missing abstraction that should be in `shared/` + +Use `madge` to detect cycles: + +```bash +pnpm dlx madge --circular src/ +``` + +## PRIORITY + +``` +Isolation > Convenience > DRY +``` + +## See Also + +- [`feature-driven.md`](feature-driven.md) — feature ownership +- [`shared-code.md`](shared-code.md) — what belongs in `shared/` +- [`monorepos.md`](monorepos.md) — package-level boundaries diff --git a/context/standards/architecture/feature-driven.md b/context/standards/architecture/feature-driven.md new file mode 100644 index 0000000..e67f01f --- /dev/null +++ b/context/standards/architecture/feature-driven.md @@ -0,0 +1,102 @@ +# Feature-Driven Architecture + +## Core Rule + +Organize code by feature, not by type. A feature owns everything it needs. + +## Structure + +``` +src/ + features/ + invoices/ + InvoiceTable.tsx ← component + InvoiceTable.test.tsx ← test colocated + InvoiceFilters.tsx + InvoiceRow.tsx + useInvoiceSort.ts ← hook used only in this feature + invoice.schema.ts ← Zod schemas for this feature + invoice.api.ts ← data fetching for this feature + invoice.types.ts ← types not derivable from schema + users/ + UserProfile.tsx + useCurrentUser.ts + user.schema.ts + shared/ + components/ ← reused across 3+ features + hooks/ + schemas/ + utils/ + app/ ← routing, layout, providers +``` + +## What Goes in a Feature + +A feature folder contains everything the feature needs that is not shared with other features: + +- Components (primary + sub-components) +- Hooks specific to this feature +- API functions +- Zod schemas and inferred types +- Tests +- Constants used only here + +## What Belongs in `shared/` + +Move to `shared/` only when: +- Used by 3 or more distinct features +- The shared nature is expected to be permanent, not incidental + +Never move something to `shared/` preemptively. + +## DO NOT + +- Organize by type at the root level (`components/`, `hooks/`, `types/`) for a large app +- Import from another feature's internals directly +- Create a `utils/` catch-all that accumulates unrelated helpers over time + +## Feature Communication + +Features must not import from each other's internals. If two features share a concept: + +1. Move the shared concept to `shared/` +2. Both features import from `shared/` + +``` +features/ + invoices/ + invoice.schema.ts ← imports User from shared/schemas/user.schema.ts + users/ + user.schema.ts ← defined here, re-exported from shared/ +shared/ + schemas/ + user.schema.ts ← canonical location for shared User schema +``` + +## Scaling + +As the app grows, features can be grouped by domain: + +``` +features/ + billing/ + invoices/ + payments/ + subscriptions/ + crm/ + contacts/ + companies/ +``` + +## PRIORITY + +``` +Feature isolation > Shared reuse > Organizational elegance +``` + +## See Also + +- [`shared-code.md`](shared-code.md) — what belongs in `shared/` +- [`folder-structure.md`](folder-structure.md) — canonical folder maps +- [`dependency-boundaries.md`](dependency-boundaries.md) — feature isolation rules +- [`../philosophy/maintainability.md`](../philosophy/maintainability.md) — colocation principle diff --git a/context/standards/architecture/folder-structure.md b/context/standards/architecture/folder-structure.md new file mode 100644 index 0000000..868a657 --- /dev/null +++ b/context/standards/architecture/folder-structure.md @@ -0,0 +1,108 @@ +# Folder Structure + +## React App (Single Repo) + +``` +src/ + app/ ← routing, layout, root providers + layout.tsx + providers.tsx + router.tsx + features/ ← feature-driven modules + invoices/ + InvoiceTable.tsx + InvoiceTable.test.tsx + useInvoiceSort.ts + invoice.schema.ts + invoice.api.ts + users/ + UserProfile.tsx + user.schema.ts + shared/ ← reused across 3+ features + components/ + Button.tsx + Modal.tsx + hooks/ + useDebounce.ts + schemas/ + pagination.schema.ts + utils/ + format-currency.ts + lib/ ← third-party adapter layer + db.ts + auth.ts + stripe.ts + styles/ + globals.css + main.tsx +``` + +## Next.js App (App Router) + +``` +app/ ← Next.js App Router + (auth)/ + login/ + page.tsx + (dashboard)/ + invoices/ + page.tsx + [id]/ + page.tsx + layout.tsx + providers.tsx +src/ ← application code (same as above) + features/ + shared/ + lib/ +public/ +``` + +## Monorepo + +``` +apps/ + web/ ← frontend app + src/ + features/ + shared/ + lib/ + api/ ← backend app + src/ + routes/ + services/ + lib/ +packages/ + ui/ ← shared component library + schemas/ ← shared Zod schemas + tsconfig/ + eslint-config/ + biome-config/ +nx.json +pnpm-workspace.yaml +``` + +## Rules + +- Feature folders are flat by default — add sub-folders only when a feature grows large +- `shared/` files are named by purpose, not by type: `format-currency.ts` not `string-utils.ts` +- `lib/` contains only external service adapters — no business logic +- Tests live next to the code they test, not in a separate `__tests__/` directory +- Use `index.ts` barrel exports sparingly — only at feature boundaries, not inside features + +## File Naming + +| Type | Convention | Example | +|---|---|---| +| React components | `PascalCase.tsx` | `InvoiceTable.tsx` | +| Hooks | `camelCase.ts` | `useInvoiceSort.ts` | +| Schemas | `kebab-case.schema.ts` | `invoice.schema.ts` | +| API functions | `kebab-case.api.ts` | `invoice.api.ts` | +| Utilities | `kebab-case.ts` | `format-currency.ts` | +| Tests | same name + `.test.ts(x)` | `InvoiceTable.test.tsx` | + +## See Also + +- [`feature-driven.md`](feature-driven.md) — feature folder anatomy +- [`shared-code.md`](shared-code.md) — `shared/` vs `lib/` +- [`dependency-boundaries.md`](dependency-boundaries.md) — import rules between layers diff --git a/context/standards/architecture/monorepos.md b/context/standards/architecture/monorepos.md new file mode 100644 index 0000000..d16ad2d --- /dev/null +++ b/context/standards/architecture/monorepos.md @@ -0,0 +1,107 @@ +# Monorepos + +## Preferred Tooling + +- **Nx** — build system, task orchestration, generators, affected commands +- **pnpm** — package manager (workspaces, efficient storage, strict linking) + +## When to Use a Monorepo + +Use a monorepo when: +- A frontend and backend share types or schemas +- Multiple apps share a component library +- A team maintains more than one deployable unit with significant shared code + +Do not add monorepo tooling to a single-app project speculatively. + +## Workspace Structure + +``` +apps/ + web/ ← Next.js / Remix frontend + api/ ← Express / Hono / tRPC backend +packages/ + ui/ ← shared component library + schemas/ ← shared Zod schemas (FE + BE) + tsconfig/ ← shared tsconfig presets + eslint-config/ ← shared ESLint config + biome-config/ ← shared Biome config +``` + +Group packages by domain when scaling: + +``` +packages/ + frontend/ + ui/ + design-tokens/ + backend/ + database/ + email/ + shared/ + schemas/ + utils/ +``` + +## Nx Setup + +```bash +pnpm dlx create-nx-workspace@latest acme --preset=ts +``` + +Key Nx commands: + +```bash +nx build web # build a specific project +nx test api # test a specific project +nx affected --target=build # build only changed projects +nx graph # visualize dependency graph +nx generate @nx/react:component # generate a component +``` + +## pnpm Workspaces + +```yaml +# pnpm-workspace.yaml +packages: + - "apps/*" + - "packages/*" +``` + +Use workspace protocol for internal dependencies: + +```json +{ + "dependencies": { + "@acme/schemas": "workspace:*", + "@acme/ui": "workspace:*" + } +} +``` + +## Dependency Rules + +- Apps may depend on packages +- Packages must not depend on apps +- Packages should not depend on other packages unless the dependency is stable and intentional +- Use `nx graph` to audit the dependency graph +- Circular dependencies are forbidden + +## DO NOT + +- Create publishable packages unless explicitly required for external consumption +- Add unnecessary inter-package dependencies +- Bypass the monorepo's package boundaries with relative imports across app/package boundaries +- Use `npm` or `yarn` in a pnpm workspace + +## PRIORITY + +``` +Feature isolation > Build efficiency > Package granularity +``` + +## See Also + +- [`../tooling/nx.md`](../tooling/nx.md) — Nx commands and configuration +- [`dependency-boundaries.md`](dependency-boundaries.md) — cross-package rules +- [`../tooling/ci.md`](../tooling/ci.md) — `nx affected` in CI diff --git a/context/standards/architecture/refactoring.md b/context/standards/architecture/refactoring.md new file mode 100644 index 0000000..e93b3d7 --- /dev/null +++ b/context/standards/architecture/refactoring.md @@ -0,0 +1,97 @@ +# Refactoring + +## Core Rules + +- Preserve behavior +- Prefer additive refactors +- Write tests before refactoring untested code +- Avoid massive rewrites unless explicitly requested +- Ask before major architectural divergence + +## Refactoring Strategies + +### Additive Refactor (Preferred) + +Add the new implementation alongside the old. Migrate callers incrementally. Delete the old when all callers are migrated. + +``` +Step 1: Add new implementation (zero callers) +Step 2: Migrate callers one by one +Step 3: Delete old implementation +``` + +This is always safer than a big-bang rewrite because: +- Each step is independently reviewable +- Regressions are isolated +- Rollback is possible at any step + +### Strangler Fig + +For large-scale feature replacements: + +1. Identify the boundary (API, component interface, route) +2. Build the replacement behind the boundary +3. Route new traffic to the replacement +4. Verify equivalence +5. Remove the old implementation + +### Extract + +For large files that need decomposition: + +``` +Step 1: Read the file and identify responsibilities +Step 2: Pick one responsibility to extract +Step 3: Create the new file +Step 4: Move the code, update imports +Step 5: Run tests — fix regressions +Step 6: Repeat for the next responsibility +``` + +Do not extract all responsibilities in one commit. One extraction per PR is easier to review. + +## Before Refactoring Untested Code + +Write at least one integration test that covers the behavior you are preserving: + +``` +Step 1: Write a test that captures the current behavior +Step 2: Confirm the test passes +Step 3: Refactor +Step 4: Confirm the test still passes +``` + +## DO NOT + +- Rename things across many files in the same PR as a behavior change +- Mix refactoring and feature changes in the same commit +- Perform a rewrite and call it a refactor +- Remove test coverage while refactoring + +## Signs a Refactor Has Gone Wrong + +- Tests are failing that were passing before +- The behavior is different — even if "improved" +- The PR is impossible to review because the diff is too large +- You are rebuilding instead of reshaping + +## Asking Before Proceeding + +Ask before: +- Reorganizing the folder structure of a feature +- Changing the state management approach +- Replacing a library dependency +- Changing how data flows through a module + +## PRIORITY + +``` +Behavior preservation > Code quality improvement > Elegance +``` + +## See Also + +- [`../philosophy/incremental-abstraction.md`](../philosophy/incremental-abstraction.md) — when to abstract +- [`../philosophy/ai-agent-behavior.md`](../philosophy/ai-agent-behavior.md) — agent refactoring rules +- [`../react/testing.md`](../react/testing.md) — write tests before refactoring untested code +- [`../tooling/ci.md`](../tooling/ci.md) — PR sizing and additive PRs diff --git a/context/standards/architecture/shared-code.md b/context/standards/architecture/shared-code.md new file mode 100644 index 0000000..1356bfc --- /dev/null +++ b/context/standards/architecture/shared-code.md @@ -0,0 +1,81 @@ +# Shared Code + +## Core Rule + +Code is shared when it is genuinely reused across multiple features — not when it might be reused someday. + +## The Three-Feature Rule + +Do not move code to `shared/` until it is used by at least three distinct features. Two uses may be coincidence. Three is a pattern. + +## `shared/` Structure + +``` +shared/ + components/ ← UI components reused across 3+ features + Button.tsx + Modal.tsx + DataTable.tsx + hooks/ ← hooks reused across 3+ features + useDebounce.ts + useMediaQuery.ts + schemas/ ← Zod schemas shared between frontend and backend + user.schema.ts + pagination.schema.ts + utils/ ← pure utility functions + format-currency.ts + parse-date.ts + types/ ← shared TypeScript types not derivable from schemas + pagination.types.ts +``` + +## Naming Rules for `shared/` + +Name shared things after what they do, not what they are: + +``` +shared/utils/format-currency.ts ← clear purpose +shared/utils/helpers.ts ← meaningless catch-all (avoid) +shared/utils/utils.ts ← avoid +shared/utils/common.ts ← avoid +``` + +## DO NOT + +- Create `shared/` as a dumping ground — every file must have a reason to be there +- Put feature-specific logic in `shared/` because it seems "useful" +- Create a single `index.ts` barrel export that re-exports everything in `shared/` — it makes tree-shaking worse and creates implicit coupling +- Put application state, context providers, or business logic in `shared/` + +## `lib/` vs `shared/` + +Some projects use `lib/` for third-party integrations and adapter code: + +``` +lib/ + db.ts ← Prisma client singleton + auth.ts ← Auth.js / Clerk adapter + email.ts ← email provider adapter + stripe.ts ← Stripe adapter +``` + +`lib/` wraps external services. `shared/` contains internal reusable code. + +## When Code Outgrows `shared/` + +In a large app or monorepo, promote heavily-used `shared/` code to a dedicated package: + +``` +packages/ + ui/ ← shared component library + schemas/ ← shared Zod schemas + utils/ ← shared utility functions +``` + +Do not create packages prematurely. Start in `shared/`, promote when the boundary is clear. + +## See Also + +- [`feature-driven.md`](feature-driven.md) — feature ownership +- [`dependency-boundaries.md`](dependency-boundaries.md) — allowed imports +- [`../philosophy/incremental-abstraction.md`](../philosophy/incremental-abstraction.md) — three-instance rule diff --git a/context/standards/docs/SPEC.md b/context/standards/docs/SPEC.md new file mode 100644 index 0000000..79f36ae --- /dev/null +++ b/context/standards/docs/SPEC.md @@ -0,0 +1,540 @@ +# AI Engineering Standards Repository + +## Goal + +Build a personal AI-native engineering standards repository intended primarily for AI agent consumption (Claude Code, AGENTS.md, Cursor, Continue, etc.), while still remaining readable for humans. + +This repository should serve as: + +- A reusable engineering standards system +- A source of truth for TypeScript and React best practices +- A reusable starter/reference kit for new projects +- A refactoring guide for older codebases +- A retrieval-friendly knowledge base for AI coding agents + +The repository should optimize for: + +- deterministic AI guidance +- maintainability +- composability +- readability +- scalability +- accessibility +- incremental architecture + +The repository should NOT optimize for: + +- excessive prose +- philosophical essays +- premature abstraction +- enterprise boilerplate +- giant generalized frameworks + +--- + +# Core Engineering Philosophy + +## Core Principle + +Prefer explicit, composable, feature-oriented architecture over generalized abstractions and centralized complexity. + +## Architectural Values + +- Composition over inheritance +- Readability over cleverness +- Explicitness over magic +- Feature ownership over centralized organization +- Incremental abstraction over premature DRY +- Maintainability over premature optimization +- Colocation over fragmentation +- Accessibility-first development +- Strong type safety +- Predictable data flow + +--- + +# AI Agent Behavior Rules + +AI agents working with these standards should: + +## Prioritize + +- readability +- accessibility +- composability +- maintainability +- type safety +- feature isolation +- incremental refactoring +- explicit APIs +- predictable architecture + +## Avoid + +- giant files +- useEffect abuse +- premature abstractions +- hook mini-frameworks +- defensive memoization +- over-generalized utilities +- excessive inheritance +- Redux +- hidden side effects +- implementation testing +- state synchronization effects +- aggressive rewrites without approval + +## Refactoring Rules + +- Preserve behavior +- Prefer additive refactors +- Preserve architecture boundaries +- Ask before major architectural divergence +- Gently migrate legacy systems +- If possible, write tests before refactoring untested code +- Avoid massive rewrites unless explicitly requested + +--- + +# Repository Structure + +Create the repository roughly with this structure: + +ai-engineering-standards/ +├── README.md +│ +├── philosophy/ +│ ├── core-principles.md +│ ├── ai-agent-behavior.md +│ ├── incremental-abstraction.md +│ ├── maintainability.md +│ └── readability.md +│ +├── typescript/ +│ ├── rules.md +│ ├── anti-patterns.md +│ ├── validation.md +│ ├── naming.md +│ ├── error-handling.md +│ └── tsconfig/ +│ ├── base.json +│ ├── react.json +│ └── nextjs.json +│ +├── react/ +│ ├── component-design.md +│ ├── hooks.md +│ ├── use-effect.md +│ ├── memoization.md +│ ├── state-management.md +│ ├── forms.md +│ ├── accessibility.md +│ ├── testing.md +│ └── anti-patterns.md +│ +├── architecture/ +│ ├── feature-driven.md +│ ├── shared-code.md +│ ├── monorepos.md +│ ├── folder-structure.md +│ ├── refactoring.md +│ └── dependency-boundaries.md +│ +├── tooling/ +│ ├── tanstack.md +│ ├── nx.md +│ ├── vite.md +│ ├── biome.md +│ ├── tailwind.md +│ ├── shadcn.md +│ └── prisma.md +│ +├── security/ +│ ├── validation.md +│ ├── secrets.md +│ ├── auth.md +│ └── api-security.md +│ +├── examples/ +│ ├── good/ +│ └── bad/ +│ +├── templates/ +│ ├── CLAUDE.md +│ ├── AGENTS.md +│ ├── project-readme.md +│ └── pull-request-template.md +│ +└── packages/ +├── tsconfig/ +├── eslint-config/ +└── biome-config/ + +--- + +# Documentation Style Requirements + +Optimize all documentation for AI retrieval quality. + +Documentation should be: + +- directive +- concise +- hierarchical +- example-heavy +- low ambiguity +- deterministic +- composable + +Avoid: + +- giant essays +- vague wording +- contradictory guidance +- excessive prose + +Preferred style: + +md # Rules ## DO - ... ## DO NOT - ... ## PRIORITY Readability > micro-optimization + +Separate: + +- rules +- rationale +- examples + +where appropriate. + +--- + +# TypeScript Standards + +## General + +- "strict": true +- Avoid any +- Prefer unions over enums +- Avoid type assertions when possible +- Prefer generated API types +- Prefer schema-driven validation +- Prefer explicit naming +- Avoid type gymnastics +- Avoid unnecessary DTO duplication + +## tsconfig Preferences + +Include strong strictness rules such as: + +- noUnusedLocals +- noUnusedParameters +- noUncheckedIndexedAccess +- noPropertyAccessFromIndexSignature +- noFallthroughCasesInSwitch +- allowUnreachableCode: false +- allowUnusedLabels: false +- noUncheckedSideEffectImports +- erasableSyntaxOnly + +Use: + +- ~/\* absolute imports + +## Validation + +- Prefer Zod +- Investigate Valibot +- Schema-first validation is encouraged +- Shared FE/BE schemas are preferred + +--- + +# React Standards + +## Architecture + +- Prefer feature-driven architecture +- Colocate feature-specific logic +- Avoid organization-by-type for large apps +- Prefer composition over deeply nested prop drilling +- Compound components are encouraged + +## Components + +- Keep components reasonably scoped +- Avoid gigantic files +- Large files should trigger decomposition review +- One file should generally have one primary responsibility + +## Hooks + +## DO + +- Encapsulate reusable behavior +- Keep hooks composable +- Keep effects explicit + +## DO NOT + +- Create hook mini-frameworks +- Wrap useState purely for renaming +- Hide side effects unexpectedly + +Before creating custom hooks: + +- Check whether an existing solution already exists +- Investigate @uidotdev/usehooks + +## useEffect Doctrine + +useEffect should primarily be used for: + +- external system synchronization +- subscriptions +- DOM APIs +- timers +- imperative bridges + +Avoid useEffect for: + +- state synchronization +- derived state +- unnecessary data flow orchestration + +## Memoization + +## DO + +- Optimize proven bottlenecks +- Use useCallback for stable drilled callbacks when needed + +## DO NOT + +- Defensively memoize +- Abuse useMemo +- Use memoization to silence dependency warnings +- Overuse React.memo + +Priority: + +- Readability > micro-optimization + +## State Management Hierarchy + +Preferred order: + +1. URL state +2. Server state (TanStack Query) +3. Local component state +4. Context +5. Zustand only when truly necessary +6. Never Redux + +## Forms + +- Prefer TanStack ecosystem where possible +- React Hook Form is acceptable +- Prefer controlled inputs +- Prefer shared schema validation +- Optimistic updates are encouraged + +--- + +# Styling Standards + +Preferred stack: + +- Tailwind CSS +- Shadcn UI +- CSS variables for theming +- Design tokens +- Minimal arbitrary values + +Avoid: + +- CSS Modules unless necessary +- Excessive arbitrary Tailwind values + +Preferred theming approach: + +- CSS variable-based themes +- Tailwind integration +- Runtime-friendly tokens +- Dark/light mode support + +--- + +# Testing Standards + +## Philosophy + +- Integration tests over implementation tests +- Test behavior, not implementation details +- Avoid snapshot tests +- Unit test pure utilities and logic +- Use Playwright only for critical E2E flows + +## Preferred Tooling + +- Vitest +- Testing Library +- Playwright + +Follow the philosophy from: +https://kentcdodds.com/blog/common-mistakes-with-react-testing-library + +--- + +# Accessibility Standards + +Accessibility standards should be strict. + +## Requirements + +- Semantic HTML first +- Keyboard navigation mandatory +- Screenreader friendliness mandatory +- Form labeling mandatory +- ARIA only when necessary +- Avoid clickable divs +- Ensure color contrast compliance + +--- + +# Error Handling Standards + +## Rules + +- Never swallow errors +- Never hide original error context +- Avoid generic "Something went wrong" messages +- Preserve HTTP status codes and payloads +- Use contextual logging +- Avoid large ambiguous try/catch blocks +- Use custom error classes where appropriate +- Validate inputs before transitions + +## Async Rules + +- Avoid unhandled promise rejections +- Await async operations properly +- Use .catch() where appropriate + +## User-Facing Errors + +Every user-facing error should: + +- explain the failure +- explain the next step +- remain actionable + +--- + +# Dependency Philosophy + +## DO + +- Prefer browser-native APIs +- Prefer mature maintained libraries +- Check bundle impact + +## DO NOT + +- Add tiny unnecessary dependencies +- Add bloated libraries +- Use abandoned packages + +--- + +# Monorepo Philosophy + +Preferred tooling: + +- Nx +- pnpm + +Guidelines: + +- Preserve feature isolation +- Avoid circular dependencies +- Avoid publishable packages unless explicitly required +- Group packages by domain when scaling + +Example: + +txt packages/ frontend/ backend/ + +--- + +# CI / PR Standards + +Required: + +- lint gating +- typecheck gating +- test gating +- formatting gating + +Guidance: + +- Smaller PRs are preferred for reviewability +- Large PRs are acceptable when necessary +- Prefer squash merges +- Prioritize high-quality PR titles/descriptions + +When using PR workflows: + +- Investigate @burglekitt/worktree + +--- + +# Initial Implementation Plan + +Start implementation in phases. + +## Phase 1 + +- Repository structure +- README +- Core philosophy documents +- AI behavior document +- tsconfig package +- biome/eslint configs + +## Phase 2 + +- React/TypeScript standards docs +- Examples +- AGENTS.md templates +- CLAUDE.md templates + +## Phase 3 + +- Advanced architecture docs +- Security docs +- Monorepo docs +- Starter templates +- Scaffolding utilities + +--- + +# Important Final Guidance + +This repository is primarily intended for: + +- AI retrieval +- deterministic engineering guidance +- reusable architectural consistency + +Optimize for: + +- clarity +- consistency +- composability +- maintainability +- AI adherence + +Not for: + +- excessive prose +- academic writing +- generalized enterprise abstraction diff --git a/context/standards/examples/bad/data-fetching.tsx b/context/standards/examples/bad/data-fetching.tsx new file mode 100644 index 0000000..d4b8654 --- /dev/null +++ b/context/standards/examples/bad/data-fetching.tsx @@ -0,0 +1,52 @@ +/** + * BAD: Manual data fetching with useEffect + * + * Problems: + * - Race condition: if `userId` changes mid-fetch, the older response can overwrite the newer one + * - No caching: every mount triggers a fetch + * - No deduplication: two components mounting in parallel each fetch + * - Error state has no handling + * - No request cancellation on unmount + * - State sprawl: 3 useState calls to track what TanStack Query gives for free + */ + +import { useEffect, useState } from "react"; + +interface User { + id: string; + name: string; + email: string; +} + +interface UserProfileProps { + userId: string; +} + +export function UserProfile({ userId }: UserProfileProps) { + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + // ✗ no error state — failures are silently swallowed below + + useEffect(() => { + setIsLoading(true); + fetch(`/api/users/${userId}`) + .then((r) => r.json()) + .then((data) => { + // ✗ if userId changed during this fetch, this overwrites the newer result + setUser(data); + setIsLoading(false); + }); + // ✗ no .catch — network errors silently leave isLoading=true forever + // ✗ no AbortController — cancelled fetches still fire setState on unmount + }, [userId]); + + if (isLoading) return
Loading…
; + if (!user) return null; + + return ( +
+

{user.name}

+

{user.email}

+
+ ); +} diff --git a/context/standards/examples/bad/derived-state.tsx b/context/standards/examples/bad/derived-state.tsx new file mode 100644 index 0000000..45c7064 --- /dev/null +++ b/context/standards/examples/bad/derived-state.tsx @@ -0,0 +1,60 @@ +/** + * BAD: Syncing derived values into state with useEffect + * + * Problems: + * - `filtered` and `total` are duplicate state — they can drift from `items`/`search` + * - Extra render on every input change (state update → effect → state update → render) + * - More code, more bugs, no benefit over computing during render + * - Initial render shows stale values until the effect runs + */ + +import { useEffect, useState } from "react"; + +interface Item { + id: string; + name: string; + price: number; +} + +interface ItemListProps { + items: Item[]; +} + +export function ItemList({ items }: ItemListProps) { + const [search, setSearch] = useState(""); + // ✗ derived values stored as state + const [filtered, setFiltered] = useState([]); + const [total, setTotal] = useState(0); + + // ✗ effect syncs derived state — entirely unnecessary + useEffect(() => { + const result = items.filter((item) => + item.name.toLowerCase().includes(search.toLowerCase()), + ); + setFiltered(result); + setTotal(result.reduce((sum, item) => sum + item.price, 0)); + }, [items, search]); + + return ( +
+ +

+ {filtered.length} items — total ${total.toFixed(2)} +

+
    + {filtered.map((item) => ( +
  • + {item.name} — ${item.price.toFixed(2)} +
  • + ))} +
+
+ ); +} diff --git a/context/standards/examples/bad/feature-component.tsx b/context/standards/examples/bad/feature-component.tsx new file mode 100644 index 0000000..ef3528d --- /dev/null +++ b/context/standards/examples/bad/feature-component.tsx @@ -0,0 +1,80 @@ +/** + * BAD EXAMPLE: God component with multiple anti-patterns + * + * Anti-patterns demonstrated: + * - useEffect for data fetching (use TanStack Query instead) + * - useEffect for derived state (compute during render instead) + * - Giant single component (decompose instead) + * - No types / implicit any + * - No error handling + * - No loading state + * - No accessibility (clickable div, no labels, no semantic markup) + * - Index as key + * - Unhandled promise (missing await / .catch) + * - Defensive unnecessary memoization + */ + +import { useEffect, useMemo, useState } from "react"; + +// ✗ No types defined — all data is implicitly any + +export default function Invoices() { + // ✗ Manual fetch state instead of TanStack Query + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + + // ✗ useEffect for data fetching — race conditions, no caching, no deduplication + useEffect(() => { + setLoading(true); + fetch("/api/invoices") + .then((r) => r.json()) + .then((d) => { + setData(d); + setLoading(false); + // ✗ No error handling — silently fails on network errors + }); + }, []); + + // ✗ useEffect for derived state — should be computed during render + const [total, setTotal] = useState(0); + useEffect(() => { + if (data) { + // @ts-ignore + setTotal(data.reduce((sum, inv) => sum + inv.amount, 0)); + } + }, [data]); + + // ✗ Defensive memoization of a trivial computation + const label = useMemo(() => `Total: $${total}`, [total]); + + // ✗ No loading state rendered to user + if (loading) return null; + + return ( + // ✗ No semantic markup — div soup +
+
{label}
+
+ {/* ✗ Index as key — unstable, causes rendering bugs */} + {/* ✗ any type throughout */} + {(data as any)?.map((invoice: any, i: number) => ( +
window.location.href = `/invoices/${invoice.id}`} + style={{ cursor: "pointer" }} + > + {/* ✗ No accessible label, no semantic structure */} + {invoice.title} + {invoice.status} + {invoice.amount} + {/* ✗ Unhandled promise — fire and forget without .catch */} + fetch(`/api/invoices/${invoice.id}/archive`, { method: "POST" })}> + Archive + +
+ ))} +
+
+ ); +} diff --git a/context/standards/examples/bad/prop-drilling.tsx b/context/standards/examples/bad/prop-drilling.tsx new file mode 100644 index 0000000..7348547 --- /dev/null +++ b/context/standards/examples/bad/prop-drilling.tsx @@ -0,0 +1,70 @@ +/** + * BAD: Drilling `user` through layers that never use it + * + * Problems: + * - Layout and Sidebar accept `user` only to forward it — they don't render it + * - Every intermediate signature grows when shared values are added + * - Refactoring any layer ripples through the whole tree + * - TypeScript noise: prop types repeated everywhere + */ + +interface User { + id: string; + name: string; +} + +interface PageProps { + user: User; +} + +export function Page({ user }: PageProps) { + // ✗ Layout doesn't use `user` — only forwards it + return ; +} + +interface LayoutProps { + user: User; +} + +function Layout({ user }: LayoutProps) { + return ( +
+
+ {/* ✗ Sidebar doesn't use `user` either — just forwards */} + + +
+ ); +} + +interface SidebarProps { + user: User; +} + +function Sidebar({ user }: SidebarProps) { + return ( + + ); +} + +interface UserMenuProps { + user: User; +} + +function UserMenu({ user }: UserMenuProps) { + return ; +} + +function Header() { + return
; +} +function Content() { + return
; +} +function Nav() { + return