@nurix/etna 0.19.55 → 0.19.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/harness-index.json +1 -1
- package/data/harnesses/apollo/CLAUDE.md +29 -30
- package/data/harnesses/apollo/agents/bookkeeper.md +1 -1
- package/data/harnesses/apollo/agents/change-review.md +1 -1
- package/data/harnesses/apollo/agents/lld-mode.md +1 -1
- package/data/harnesses/apollo/agents/maintenance.md +1 -1
- package/data/harnesses/apollo/agents/performance-review.md +1 -1
- package/data/harnesses/apollo/agents/retro.md +1 -1
- package/data/harnesses/apollo/harness.json +2 -2
- package/data/harnesses/apollo/hooks/approved-packages.md +42 -0
- package/data/harnesses/apollo/hooks/compliance.mjs +53 -6
- package/data/harnesses/apollo/rules/changelog.md +7 -1
- package/data/harnesses/apollo/rules/cloudflare.md +167 -0
- package/data/harnesses/apollo/rules/database.md +3 -127
- package/data/harnesses/apollo/rules/frontend.md +2 -4
- package/data/harnesses/apollo/rules/general.md +20 -42
- package/data/harnesses/apollo/rules/nomenclature.md +2 -6
- package/data/harnesses/apollo/rules/pnpm.md +2 -2
- package/data/harnesses/apollo/rules/testing.md +44 -3
- package/data/harnesses/apollo/skills/documentation/references/instruction-style.md +1 -0
- package/data/harnesses/apollo/skills/documentation/templates/plan.md +4 -4
- package/data/harnesses/apollo/skills/schema-design/SKILL.md +141 -0
- package/data/harnesses/apollo/skills/test/SKILL.md +104 -0
- package/data/harnesses/apollo/skills/testing/SKILL.md +100 -29
- package/data/harnesses/code/CLAUDE.md +29 -30
- package/data/harnesses/code/agents/bookkeeper.md +1 -1
- package/data/harnesses/code/agents/change-review.md +1 -1
- package/data/harnesses/code/agents/lld-mode.md +1 -1
- package/data/harnesses/code/agents/maintenance.md +1 -1
- package/data/harnesses/code/agents/performance-review.md +1 -1
- package/data/harnesses/code/agents/retro.md +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/hooks/approved-packages.md +42 -0
- package/data/harnesses/code/hooks/compliance.mjs +53 -6
- package/data/harnesses/code/rules/changelog.md +7 -1
- package/data/harnesses/code/rules/cloudflare.md +167 -0
- package/data/harnesses/code/rules/database.md +3 -127
- package/data/harnesses/code/rules/frontend.md +2 -4
- package/data/harnesses/code/rules/general.md +20 -42
- package/data/harnesses/code/rules/nomenclature.md +2 -6
- package/data/harnesses/code/rules/pnpm.md +2 -2
- package/data/harnesses/code/rules/testing.md +44 -3
- package/data/harnesses/code/skills/documentation/references/instruction-style.md +1 -0
- package/data/harnesses/code/skills/documentation/templates/plan.md +4 -4
- package/data/harnesses/code/skills/schema-design/SKILL.md +141 -0
- package/data/harnesses/code/skills/test/SKILL.md +104 -0
- package/data/harnesses/code/skills/testing/SKILL.md +100 -29
- package/data/harnesses/delivery/CLAUDE.md +13 -1
- package/data/harnesses/delivery/agents/lld-mode.md +1 -1
- package/data/harnesses/delivery/agents/maintenance.md +1 -1
- package/data/harnesses/delivery/agents/retro.md +1 -1
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/rules/changelog.md +7 -1
- package/data/harnesses/delivery/rules/nomenclature.md +2 -6
- package/data/harnesses/delivery/skills/documentation/references/instruction-style.md +1 -0
- package/data/harnesses/delivery/skills/documentation/templates/plan.md +4 -4
- package/data/harnesses/discover/CLAUDE.md +14 -1
- package/data/harnesses/discover/harness.json +1 -1
- package/data/harnesses/discussion/CLAUDE.md +4 -2
- package/data/harnesses/discussion/harness.json +1 -1
- package/data/harnesses/etna/CLAUDE.md +14 -2
- package/data/harnesses/etna/agents/change-review.md +1 -1
- package/data/harnesses/etna/agents/lld-mode.md +1 -1
- package/data/harnesses/etna/agents/maintenance.md +1 -1
- package/data/harnesses/etna/agents/performance-review.md +1 -1
- package/data/harnesses/etna/agents/retro.md +1 -1
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/rules/changelog.md +7 -1
- package/data/harnesses/etna/rules/general.md +20 -42
- package/data/harnesses/etna/rules/nomenclature.md +2 -6
- package/data/harnesses/etna/rules/testing.md +44 -3
- package/data/harnesses/etna/skills/documentation/references/instruction-style.md +1 -0
- package/data/harnesses/etna/skills/documentation/templates/plan.md +4 -4
- package/data/harnesses/hr/CLAUDE.md +8 -3
- package/data/harnesses/hr/harness.json +1 -1
- package/data/harnesses/kb-agi/CLAUDE.md +8 -2
- package/data/harnesses/kb-agi/harness.json +1 -1
- package/package.json +2 -2
- package/data/harnesses/apollo/rules/express.md +0 -33
- package/data/harnesses/apollo/rules/verification.md +0 -67
- package/data/harnesses/apollo/skills/test-plan/SKILL.md +0 -85
- package/data/harnesses/code/rules/express.md +0 -33
- package/data/harnesses/code/rules/verification.md +0 -67
- package/data/harnesses/code/skills/test-plan/SKILL.md +0 -85
package/data/harness-index.json
CHANGED
|
@@ -8,13 +8,13 @@ How AI coding agents — any vendor, any runtime — work on this project. Rules
|
|
|
8
8
|
|
|
9
9
|
**Baseline first**: if `docs/structure.md` lacks a `harness-version` frontmatter marker, or it is behind the `version` in `.claude/harness.json`, invoke the `maintenance` agent before taking up the user's first ask — tell the user, let the agent run, then proceed.
|
|
10
10
|
|
|
11
|
-
Invoke the `maintenance` agent
|
|
11
|
+
Invoke the `maintenance` agent when a mechanical condition holds: `docs/structure.md` is missing (the repo isn't reconciled — baseline first), the `harness-version` marker is behind the installed version, a doc contradicts code being touched, or source paths are unowned — not on prose that merely looks stale. Structural reconciliation is never done by hand — the agent auto-selects its mode (baseline when the marker is missing or behind, coherence audit otherwise) and hands back; if it returns a follow-up (typically the module proposal), re-invoke it to finish. Single-glob or single-line doc corrections are done inline, per § Documentation. Each component's internals live in the component, not here.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
## Getting Started
|
|
16
16
|
|
|
17
|
-
-
|
|
17
|
+
- Consult **[`docs/structure.md`](docs/structure.md)** when adding or moving a doc — it is the authority on placement
|
|
18
18
|
- Read **[`docs/memory/`](docs/memory/)** for agent memory files (one per agent)
|
|
19
19
|
- Prefer `docs/` over `README.md` — docs/ is canonical for agents; check the README only when docs/ lacks a setup detail
|
|
20
20
|
- Check **[`package.json`](package.json)** for available scripts. Detect the package manager from the lockfile — see `.claude/rules/general.md`; don't hardcode one.
|
|
@@ -33,9 +33,11 @@ Three peer modes, then a verification gate and a Completion step — enter where
|
|
|
33
33
|
|
|
34
34
|
> Brainstorm entry needs a **basis** — a reference document or a one-shot brain dump; drip-sized asks (a bug fix, one button) are too small and get redirected. A **pillar** is a recorded strategic principle from a past brainstorm: in-progress sessions live under `docs/research/`, harvested ones under `docs/history/brainstorms/`, harvested doctrine under `docs/architecture/`. The LLD is the feature doc itself — `docs/features/{feature}.md`, design intent first, extended in place at Completion.
|
|
35
35
|
|
|
36
|
+
> **The lightweight lane:** a change touching one module, no cross-module contract surface, and under about a day's work skips the modes above entirely — the ad-hoc regime of `.claude/rules/testing.md` applies: make the change, pay its test immediately if it alters promised behavior, cover it with a changelog row.
|
|
37
|
+
|
|
36
38
|
**On Plan Completion:**
|
|
37
39
|
|
|
38
|
-
1. **The verification gate passes first** — user-triggered: when implementation completes, report it and offer the gate; never run it unbidden. It requires the plan's Test Obligations ledger to exist (proof the plan gates ran) and discharges every row, driving the real surface before pinning tests — `.claude/rules/
|
|
40
|
+
1. **The verification gate passes first** — user-triggered: when implementation completes, report it and offer the gate; never run it unbidden. It requires the plan's Test Obligations ledger to exist (proof the plan gates ran) and discharges every row, driving the real surface before pinning tests — `.claude/rules/testing.md`. A plan with outstanding obligations stays open, whatever the code does.
|
|
39
41
|
2. Freeze the plan — `Status: Complete`, fill the Completed date. The file is now frozen.
|
|
40
42
|
3. Flip the feature LLD to `status: active`; extend it in place with the how-it-works sections; confirm its `owns:` globs against the actual code.
|
|
41
43
|
4. Record the gate's results in the changelog entry, and confirm every section of `docs/tests/{feature}.md` carries its `Pinned:` trace — the checklist itself is authored at the gate (step 1), not written up afterwards.
|
|
@@ -67,20 +69,15 @@ Boundaries are **diagnosed, not documented** — the `boundary` skill derives th
|
|
|
67
69
|
|
|
68
70
|
### Test obligations — specify before implementing
|
|
69
71
|
|
|
70
|
-
**After `boundary` passes and its amendments are folded, invoke the `
|
|
72
|
+
**After `boundary` passes and its amendments are folded, invoke the `testing` skill (plan-gate mode)** — same plan gate, ordered second. It derives the tests the plan owes from its Proposed Changes and the owning LLD, and writes the **Test Obligations** ledger into the plan (one row per promised behavior: layer · target file · oracle · fixtures). It writes no test files — the ledger is what the verification gate discharges, and because it lives in the plan file it survives dispatch to an executor that sees only the plan. On a boundary **BLOCK** it does not run — re-plan first. Layer rules and the e2e altitude gate live in the skill.
|
|
71
73
|
|
|
72
74
|
### Verification — prove before completing
|
|
73
75
|
|
|
74
|
-
**Deferring tests is what keeps the build loop fast, and the ledger is what makes deferring safe.**
|
|
75
|
-
|
|
76
|
-
- **Only a plan may defer.** A bug fix in shipped behavior and any ad-hoc change ship their test immediately — deferral needs a ledger and a gate, and non-plan work has neither.
|
|
77
|
-
- **The checklist is the gate's first artifact, not its last.** Opening the gate derives or updates `docs/tests/{feature}.md` from the ledger and the LLD's promises (the `documentation` skill's `templates/behavioral-checklist.md`). It is the script rung 1 drives and the source rung 2's tests are drawn from — written after the driving it would be a report, and the tests would have no stated behavior to trace to.
|
|
78
|
-
- **Drive the real surface before pinning tests.** Browser automation for web UI, the real CLI, HTTP against the running worker; the suite then pins the checklist assertions that must not regress, and each section records which test now guards it.
|
|
79
|
-
- **The gate's verdict is never delegated** — a sub-agent's summary is not verifiable ground truth (the Fan-out guardrail), and browser work is main-session-only where Runtime Notes say so.
|
|
76
|
+
**Deferring tests is what keeps the build loop fast, and the ledger is what makes deferring safe.** Obligations accumulate in the plan and are discharged in one pass at the **verification gate** — mandatory before `Status: Complete`. The gate and the regimes are `.claude/rules/testing.md`; the procedure (checklist → drive → pin → discharge) is the `testing` skill (gate mode). The gate's verdict is never delegated (§ Delegation), and browser work is main-session-only where Runtime Notes say so.
|
|
80
77
|
|
|
81
78
|
### Documentation — close the loop
|
|
82
79
|
|
|
83
|
-
- **
|
|
80
|
+
- **Invoke the `documentation` skill when creating a doc, or when an edit changes a doc's structure or claims** — apply its templates and `references/style-guide.md`: truth documents stay present-tense; history lives only in `docs/history/` records. Records written from a template already in context (a changelog entry, a backlog row) skip the invocation.
|
|
84
81
|
- **[override]** Keep `docs/` updated when code changes affect documented behavior. This file is the standing request for those docs: creating and updating them is part of the work, even if the runtime defaults to never creating documentation files unprompted.
|
|
85
82
|
- After plan completion **[override]**: write the `docs/history/changelog/` entry as a **draft** immediately, while the context is fresh — no confirmation needed — and append its `docs/history/CHANGELOG.md` row; the commit fills what the draft left open and freezes it. Non-plan work needs no separate prompt: every non-exempt commit carries its own entry (the `changelog` rule).
|
|
86
83
|
- When moving or renaming source files: update the `owns:` globs of the feature doc that owns them so the generated ownership index stays accurate. A new doc that owns code declares its paths in its own `owns:` frontmatter — there is no separate map to update.
|
|
@@ -106,20 +103,13 @@ When recording a learning, ask: _is this true of this project, or true of this u
|
|
|
106
103
|
|
|
107
104
|
## Changelog
|
|
108
105
|
|
|
109
|
-
The working session is the last holder of the refinement context that produced a change — asks, iterations, rejections,
|
|
110
|
-
|
|
111
|
-
- **Every non-mechanical commit is covered by an entry** at `docs/history/changelog/YYYY-MM-DD-{three-words}.md`, staged inside the commit it describes — whatever tool or session performs the commit — plus a row in `docs/history/CHANGELOG.md`. Format, required fields, and the closed exemption list are the `documentation` skill's `templates/changelog-entry.md`; the failure fields (rejected attempts, overridden skill output) and verbatim taste quotes are required, not optional — the entry's value concentrates there.
|
|
112
|
-
- **An entry may exist before its commit.** A plan reaching `Status: Complete` writes its entry as a **draft** immediately, while the context is fresh; the commit fills what the draft left open. **Immutability begins at commit, not creation** — a draft is mutable, a committed entry never is, and a correction is a new entry that links back. Doc-health passes skip the genre.
|
|
113
|
-
- **A commit is the smallest unit an entry may describe**; above that the mapping is judgment. Work exceeding a draft's scope gets a *second* entry rather than stretching the first.
|
|
114
|
-
- **Distillation is the `retro` agent** — run on demand (before a brainstorm or LLD, before a new project, after a refinement arc), it converts accumulated entries into a typed punch list (doc content, doc structure, skill evolution, pins, harness rules), appends actionable items to the backlog, and proposes only — application runs through the normal documentation loop.
|
|
115
|
-
|
|
116
|
-
**One-shot migrations are a different genre.** Work that runs **once** against a live system — a data backfill, a schema cutover, a dependency or harness upgrade rolled across repos — is a **migration runbook** at `docs/history/migrations/YYYY-MM-DD-{slug}.md` (template: the `documentation` skill's `templates/migration-runbook.md`), authored **before** execution and outcome-stamped after. A changelog entry says what changed in the repo; a runbook says what was executed against the world and how to verify or undo it. A repeatable procedure is neither — that is `docs/operations/`. When a runbook defers targets, it records **why** and the **trigger to re-evaluate them**, so the next run re-checks rather than inheriting the skip.
|
|
106
|
+
The working session is the last holder of the refinement context that produced a change — asks, iterations, rejections, discoveries; the changelog captures it, and the `retro` agent distills it on demand into doc, skill, and pin feedback. When an entry is owed, its lifecycle, its granularity, and the migration-runbook genre are the `changelog` rule; format and required fields are the `documentation` skill's `templates/changelog-entry.md`. A plan reaching `Status: Complete` writes its entry as a **draft** immediately, while the context is fresh; **immutability begins at commit, not creation** — a committed entry is never edited, a correction is a new entry that links back.
|
|
117
107
|
|
|
118
108
|
---
|
|
119
109
|
|
|
120
110
|
## Out-of-Scope Discovery
|
|
121
111
|
|
|
122
|
-
When a plan's implementation surfaces work that is important but outside the plan's scope and is not being done now (a pre-existing bug being fixed on the spot is the bug-fix regime — the `
|
|
112
|
+
When a plan's implementation surfaces work that is important but outside the plan's scope and is not being done now (a pre-existing bug being fixed on the spot is the bug-fix regime — the `testing` rule — not a backlog item):
|
|
123
113
|
|
|
124
114
|
- Append it to `docs/history/backlog.md` under the appropriate priority (P0-P3)
|
|
125
115
|
- Format: `- **[P1]** Description — discovered by [plan-name or agent-name]`
|
|
@@ -127,22 +117,31 @@ When a plan's implementation surfaces work that is important but outside the pla
|
|
|
127
117
|
|
|
128
118
|
---
|
|
129
119
|
|
|
120
|
+
## Delegation
|
|
121
|
+
|
|
122
|
+
The single policy for sub-agent dispatch — when to fan out, at what tier, and how far to trust the result. Component-internal dispatch mechanics (the foreman loop, the brainstorm twin, batch classification) stay in their components and defer here for anything this section covers. No dispatch mechanism ⇒ inline.
|
|
123
|
+
|
|
124
|
+
- **Delegate to keep bulk out of the main context.** Wide file reads, sweeps, broad research, audits, fixture prep — work whose raw tool output the main session doesn't need — defaults to a sub-agent: output a sub-agent reads dies with it, while output read inline is re-read and re-billed on every later turn. The hand-off is self-contained (the sub-agent sees none of the conversation), so it must still cost less than the work; run independent delegations in parallel.
|
|
125
|
+
- **Small or judgment work stays inline.** A task finishable in a handful of tool calls is faster done than briefed. **Never delegate judgment by reflex** — a second opinion is for a high-stakes call or an explicit request.
|
|
126
|
+
- **A delegated result is a report, not verifiable ground truth** — verify load-bearing results directly.
|
|
127
|
+
- **Tiers**: mechanical fan-out → the cheapest capable tier; judgment-heavy delegation → the session's standard tier; the strongest tier **only** on explicit user request. The session itself defaults to the standard tier, entering the strongest only by explicit user switch. Model names live in Runtime Notes.
|
|
128
|
+
- **An explicit user directive about tier, cost, or delegation overrides this section.**
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
130
132
|
## Guardrails & Defaults
|
|
131
133
|
|
|
132
134
|
Defaults — ❌ prohibitions hold unless the user explicitly asks otherwise; ✅ and ⚠️ items are standing policy:
|
|
133
135
|
|
|
134
|
-
- ✅ **Verification — batched at a gate, not continuous.**
|
|
135
|
-
|
|
136
|
-
- **Drive before pinning**: prove the change in the real product first — browser automation for web UI, the real CLI, HTTP against the running worker — then pin as suite tests only the behaviors that must not regress.
|
|
137
|
-
- **Discipline**: run the affected part of the suite at the gate and report results honestly. Never weaken, skip, or delete a test to mask an unexplained failure — when a plan or LLD intentionally changes behavior, updating the affected tests to the new contract is part of the change.
|
|
138
|
-
- **Throwaway checks are not tests**: temporary verification scripts go to scratch space and are deleted, never committed into the test tree.
|
|
139
|
-
- ❌ **Dependencies** – Never upgrade frameworks, libraries, or dependencies.
|
|
136
|
+
- ✅ **Verification — batched at a gate, not continuous.** Only a plan defers tests; bug fixes and ad-hoc changes that alter promised behavior pay theirs immediately. The gate, regimes, frameworks, and placement are `.claude/rules/testing.md`; the procedure is the `testing` skill (gate mode) — the repo's existing runner and layout always win.
|
|
137
|
+
- ❌ **Dependencies** – Never upgrade pinned packages without asking; security patches and test-tooling installs at `latest` (`testing.md`) are exempt.
|
|
140
138
|
- ❌ **Compatibility** – Never maintain backward compatibility unless specified.
|
|
141
139
|
- ❌ **Branches & Worktrees [override]** – Never self-create git branches or worktrees; commit straight to the checked-out branch. This replaces any built-in "branch first on the default branch" default. Exception: when the user asks for a pull request, create the branch the PR requires before committing its changes.
|
|
142
|
-
- ❌ **Protected Files** –
|
|
140
|
+
- ❌ **Protected Files** – Once per session, if `docs/operations/protected-files.md` exists, read it and never modify any path it lists; never modify `AGENTS.md` (harness-regenerated) or `protected-files.md` itself.
|
|
143
141
|
- ✅ **Browser Automation** – If the runtime provides browser automation, use it freely; no need to ask first. If it doesn't — or the executing agent can't hold the browser connection (see Runtime Notes) — return a browser-verification checklist (URLs, actions, expected outcomes) for the orchestrating session or the user to run.
|
|
144
|
-
- ✅ **
|
|
145
|
-
-
|
|
142
|
+
- ✅ **Bookkeeping** — hooks silently append doc-debt rows to `docs/operations/bookkeeping.md`; the commit hook blocks while unresolved rows remain — dispatch the `bookkeeper` agent to drain the queue.
|
|
143
|
+
- ✅ **Delegation** — when to fan out, tiers, and trust are § Delegation.
|
|
144
|
+
- ⚠️ **Plans** – Plans are created directly in `docs/history/plans/` (see Workflows). Once `Status: Complete` or `Status: Abandoned`, the file is frozen — never edited. Abandoning a plan whose code was committed first converts its undischarged Test Obligations into backlog items (the `testing` rule). If scope changes after completion, write a new dated plan that links back to the old one.
|
|
146
145
|
- ⚠️ **Tool Issues** – If any CLI tool, shell alias, or MCP tool behaves unexpectedly (syntax errors, silent failures, wrong defaults), report the exact tool name, command, and error to the user so they can fix or remove the underlying library.
|
|
147
146
|
|
|
148
147
|
---
|
|
@@ -158,5 +157,5 @@ Defaults — ❌ prohibitions hold unless the user explicitly asks otherwise;
|
|
|
158
157
|
|
|
159
158
|
Everything above is runtime-neutral. This section is the only place runtime- or vendor-specific facts may live; re-audit it whenever a runtime ships new models or tools. Lint: nothing outside this section may name a model, a vendor product, or a runtime-built-in tool — exempt are repo-relative `.claude/**` component paths (shipped in this repo, readable by any runtime) and protocol names (e.g. MCP).
|
|
160
159
|
|
|
161
|
-
- **Claude Code**: sub-agent dispatch is the Agent tool; sub-agents can run skills and spawn their own sub-agents (probe-verified; re-verify on runtime updates), but cannot reliably hold the browser connection (the "can't hold the browser connection" condition the Browser Automation guardrail refers to) — browser work is Chrome CDP ("Claude in Chrome"), main session only. The user-level memory store is `~/.claude/` (auto-memory under `~/.claude/projects/...`). The ownership index and skill/agent descriptions are injected at session start; skills are invoked as slash commands (e.g. `/brainstorm start`). Dispatch tiers: mechanical fan-out runs on the cheapest capable tier;
|
|
160
|
+
- **Claude Code**: sub-agent dispatch is the Agent tool; sub-agents can run skills and spawn their own sub-agents (probe-verified; re-verify on runtime updates), but cannot reliably hold the browser connection (the "can't hold the browser connection" condition the Browser Automation guardrail refers to) — browser work is Chrome CDP ("Claude in Chrome"), main session only. The user-level memory store is `~/.claude/` (auto-memory under `~/.claude/projects/...`). The ownership index and skill/agent descriptions are injected at session start; skills are invoked as slash commands (e.g. `/brainstorm start`). Dispatch tiers (§ Delegation): the standard tier is Opus-class and is the session default; mechanical fan-out runs on the cheapest capable tier (Sonnet-class); the strongest tier (Fable-class) only on explicit user switch or request — pick by capability at dispatch, since model names go stale.
|
|
162
161
|
- **Runtimes without native component support** (AGENTS.md readers, etc.): read `.claude/agents/<name>.md` and each skill's `.claude/skills/<name>/SKILL.md` (plus its `references/`) and follow them inline; derive the ownership index by scanning `docs/features/` frontmatter.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: bookkeeper
|
|
3
3
|
description: Drains docs/operations/bookkeeping.md — performs mechanical fixes (owns: globs, header comments, doc-lint findings, index rows), expands note: rows into doc edits, marks rows resolved. Dispatched by the main session at turn end or commit time; never self-triggers. Triggers on "drain the bookkeeping queue", "dispatch the bookkeeper", "/bookkeeper".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash, Skill
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Bookkeeper Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: change-review
|
|
3
3
|
description: Velocity + change-risk analysis. First classifies recent commits into a pipe-delimited velocity/severity CSV by dispatching parallel low-cost subagents (module, feature, impact, severity, churn), then derives a commit heatmap, risk zones (high churn in contract-heavy code), stabilization signals, and team/bus-factor distribution. Self-contained — produces its own CSV, no separate git-history step. Use when planning where to invest in review focus or rules.
|
|
4
4
|
tools: Bash, Read, Write, Glob, Grep, Task
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Change-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: lld-mode
|
|
3
3
|
description: LLD researcher for Feature and Product workflows. Use to explore the codebase and gather the context needed for writing a feature LLD (low-level design — the feature doc in docs/features/). Returns research findings — does not write files.
|
|
4
4
|
tools: Read, Glob, Grep, WebSearch, WebFetch
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
skills:
|
|
7
7
|
- documentation
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maintenance
|
|
3
3
|
description: The single docs steward. Default behavior is the docs-vs-code coherence audit — detects and fixes docs drifting from code (stale docs, broken ownership, unowned source paths, architecture/backlog drift). An additive **baseline mode** layers structural reconcile on top: when the repo's `harness-version` marker is missing or behind, it converges `docs/` to the installed harness shape (scaffold the standard tree, fold dissolved-shape artifacts forward, stamp the version, propose modules) before running the audit. Mode is auto-selected from the marker. Run weekly, after major changes, or to baseline/upgrade a repo. Cross-cutting contract analysis is the `boundary` skill (`check` for plans, `scan` for diffs) — it derives contracts dynamically from source; there are no persisted boundary docs to audit. Triggers on "run maintenance", "docs health", "baseline this repo", "reconcile the harness", "upgrade the harness", "is this repo on the current harness", "/maintenance".
|
|
4
4
|
tools: Read, Glob, Grep, Edit, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Maintenance Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: performance-review
|
|
3
3
|
description: Combined dependency + code health audit. Part A audits the dependency supply chain — security CVEs, outdated packages, license compliance, abandoned packages, unwanted transitive dependencies, install size. Part B audits code-level health — bundle size, tree-shaking issues, performance anti-patterns, code-quality flags. Run weekly or before releases. Merges the former `dep-health` and `review` agents so the package tree and the code that uses it are audited together.
|
|
4
4
|
tools: Read, Glob, Grep, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Performance-Review Agent
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: retro
|
|
3
3
|
description: Session-distillation agent for the changelog. Reads the per-unit-of-work entries under `docs/history/changelog/` accumulated since its last run and distills the refinement delta — what user-driven iteration added that the one-shot build lacked — into a typed punch list of doc-content gaps, doc-structure moves (missing doc / mothership split / merge), skill-evolution findings backed by survived-vs-overridden counts, unpinned discoveries needing an enforced artifact (test, token, guardrail, schema), and cross-project harness rules. Proposes only — actionable items land in the backlog, never applied directly. Run on demand: before a brainstorm or LLD session, before starting a new project, or after a refinement arc. Triggers on "run retro", "distill the changelog", "refinement retro", "/retro".
|
|
4
4
|
tools: Read, Glob, Grep, Bash, Write, Edit
|
|
5
|
-
model:
|
|
5
|
+
model: opus
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Retro — changelog distillation
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "apollo",
|
|
3
3
|
"name": "apollo",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6",
|
|
5
5
|
"files": [
|
|
6
6
|
".prettierignore",
|
|
7
7
|
".prettierrc.json",
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
"docs/memory/main.md",
|
|
16
16
|
".mcp.json"
|
|
17
17
|
],
|
|
18
|
-
"digest": "
|
|
18
|
+
"digest": "8e61d4b9ddcf"
|
|
19
19
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Approved Packages
|
|
2
|
+
|
|
3
|
+
Read by the PreToolUse install gate (`compliance.mjs`) — an off-table install is denied. Not model-loaded; the policy line lives in `.claude/rules/general.md`. The table changes only by explicit user decision.
|
|
4
|
+
|
|
5
|
+
| Package | Version | Use | Don't use |
|
|
6
|
+
| --- | --- | --- | --- |
|
|
7
|
+
| `react` | ^19.2.3 | UI components | — |
|
|
8
|
+
| `vite` | ^8.0.0 | Build tool, dev server | `webpack`, CRA |
|
|
9
|
+
| `tailwindcss` | ^4.1.18 | Styling | `styled-components` |
|
|
10
|
+
| `@vitejs/plugin-react-swc` | ^4.2.2 | JSX compilation | `@vitejs/plugin-react` |
|
|
11
|
+
| `react-router-dom` | ^7.11.0 | Routing | — |
|
|
12
|
+
| `@reduxjs/toolkit` | ^2.11.2 | State management | `zustand`, `jotai` |
|
|
13
|
+
| `react-redux` | ^9.2.0 | Redux React bindings | — |
|
|
14
|
+
| `axios` | ^1.13.2 | HTTP requests in browser/Node code | `fetch` in browser code — Workers and edge runtimes use native `fetch`, which is correct there |
|
|
15
|
+
| `@microsoft/fetch-event-source` | ^2.0.1 | SSE over POST — streaming agent output | hand-rolled `getReader()` loops, `EventSource` |
|
|
16
|
+
| `date-fns` | ^4.1.0 | Date formatting | `moment`, `dayjs` |
|
|
17
|
+
| `@dnd-kit/core` | ^6.3.1 | Drag and drop | `react-beautiful-dnd` |
|
|
18
|
+
| `@xyflow/react` | ^12.11.0 | Node-based flow charts | `reactflow`, `react-flow-renderer` |
|
|
19
|
+
| `motion` | ^12.23.26 | Animations | `react-spring` |
|
|
20
|
+
| `sonner` | ^2.0.7 | Toast notifications | `react-toastify` |
|
|
21
|
+
| `react-window` | ^2.2.3 | List virtualization | `react-virtualized` |
|
|
22
|
+
| `nanoid` | ^6.0.0 | ID generation | `uuid` — exempt: UUIDv5/v7 database ids per `database.md` |
|
|
23
|
+
| `express` | ^5.2.1 | HTTP server | `fastify`, `koa` |
|
|
24
|
+
| `drizzle-orm` | ^0.45.2 | Database ORM | `prisma`, `sequelize`, `typeorm` |
|
|
25
|
+
| `drizzle-kit` | ^0.31.10 | Schema tooling | — |
|
|
26
|
+
| `zod` | ^4.2.1 | Schema validation | `yup`, `joi` |
|
|
27
|
+
| `jose` | ^6.1.3 | JWT handling | `jsonwebtoken` |
|
|
28
|
+
| `microdiff` | ^1.5.0 | JSON object diffing | `deep-diff`, `just-diff` |
|
|
29
|
+
| `react18-json-view` | ^0.2.9 | JSON tree view | `react-json-view` |
|
|
30
|
+
| `@tiptap/react` | ^3.11.1 | Rich text editor | `slate`, `quill`, `draft-js` |
|
|
31
|
+
| `shiki` | ^4.3.1 | Syntax highlighting | `prism`, `highlight.js` |
|
|
32
|
+
| `lucide-react` | ^1.26.0 | Icons | `react-icons`, `heroicons` |
|
|
33
|
+
| `@pdfme/generator` | ^6.1.12 | PDF generation | `jspdf`, `pdfkit` |
|
|
34
|
+
| `cmdk` | ^1.1.1 | Command palette | `kbar`, `ninja-keys` |
|
|
35
|
+
| `@monaco-editor/react` | ^4.7.0 | Code editor | `codemirror`, `ace` |
|
|
36
|
+
| `localforage` | ^1.10.0 | Client-side storage | `idb`, `dexie` |
|
|
37
|
+
| `react-markdown` | ^10.1.0 | Markdown rendering | `marked`, `markdown-it` |
|
|
38
|
+
| `remark-gfm` | ^4.0.1 | GFM (tables, tasklists) | `remark-github`, custom |
|
|
39
|
+
| `fast-deep-equal` | ^3.1.3 | Deep equality checks | `lodash.isEqual` |
|
|
40
|
+
| `@ngneat/falso` | latest | Fake data generation | `@faker-js/faker`, `chance` |
|
|
41
|
+
| `pino` | ^10.1.0 | Logging | `winston` |
|
|
42
|
+
|
|
@@ -107,14 +107,14 @@ function fileGate(projectDir, rel, toolName) {
|
|
|
107
107
|
if (!plan.hasLedger) {
|
|
108
108
|
return decision(
|
|
109
109
|
"deny",
|
|
110
|
-
`BLOCKED — plan gate. \`${plan.file}\` has no \`## Test Obligations\` section. Run the \`
|
|
110
|
+
`BLOCKED — plan gate. \`${plan.file}\` has no \`## Test Obligations\` section. Run the \`testing\` skill (plan-gate mode); its ledger is what the verification gate discharges.`
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// Writing a test file is never an interruption. Deferring tests to the verification gate stays
|
|
117
|
-
// doctrine (`
|
|
117
|
+
// doctrine (`testing.md` § During the loop), but it needs no gate here: what the ledger
|
|
118
118
|
// actually guarantees is enforced at both ends already — a plan may not take implementation
|
|
119
119
|
// edits until its `## Test Obligations` section exists (above), and it may not reach
|
|
120
120
|
// `Status: Complete` until the gate discharges every row (the Stop gate). Policing the *moment*
|
|
@@ -169,11 +169,16 @@ function commitGate(projectDir) {
|
|
|
169
169
|
return null;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
// Package names approved
|
|
172
|
+
// Package names approved in .claude/hooks/approved-packages.md (plus testing.md frameworks and general.md).
|
|
173
173
|
function approvedPackages(projectDir) {
|
|
174
174
|
const names = new Set();
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
const sources = [
|
|
176
|
+
path.join(projectDir, ".claude", "hooks", "approved-packages.md"),
|
|
177
|
+
path.join(projectDir, ".claude", "rules", "general.md"),
|
|
178
|
+
path.join(projectDir, ".claude", "rules", "testing.md"),
|
|
179
|
+
];
|
|
180
|
+
for (const src of sources) {
|
|
181
|
+
const text = readSafe(src);
|
|
177
182
|
for (const m of text.matchAll(/`(@?[a-z0-9][\w./-]*)`/g)) names.add(m[1]);
|
|
178
183
|
}
|
|
179
184
|
return names;
|
|
@@ -191,7 +196,7 @@ function packageGate(projectDir, command) {
|
|
|
191
196
|
if (!approved.has(pkg)) {
|
|
192
197
|
return decision(
|
|
193
198
|
"deny",
|
|
194
|
-
`BLOCKED — package table. \`${pkg}\` is not in the approved table (.claude/
|
|
199
|
+
`BLOCKED — package table. \`${pkg}\` is not in the approved table (.claude/hooks/approved-packages.md; test packages: .claude/rules/testing.md). The table changes only by explicit user decision.`
|
|
195
200
|
);
|
|
196
201
|
}
|
|
197
202
|
}
|
|
@@ -320,6 +325,43 @@ function planHasChangelogEntry(projectDir, planRel) {
|
|
|
320
325
|
return names.some((name) => name.endsWith(".md") && readSafe(path.join(dir, name)).includes(slug));
|
|
321
326
|
}
|
|
322
327
|
|
|
328
|
+
// Approximate the context the next turn will re-read, from the transcript's last recorded usage.
|
|
329
|
+
// Best-effort: an absent or unparseable transcript reports nothing.
|
|
330
|
+
function sessionContextTokens(transcriptPath) {
|
|
331
|
+
const text = readSafe(transcriptPath || "");
|
|
332
|
+
if (!text) return 0;
|
|
333
|
+
const lines = text.slice(-400000).split("\n");
|
|
334
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
335
|
+
if (!lines[i].includes('"usage"')) continue;
|
|
336
|
+
try {
|
|
337
|
+
const u = JSON.parse(lines[i]).message.usage;
|
|
338
|
+
return (
|
|
339
|
+
(u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0)
|
|
340
|
+
);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
// the slice cut this line in half — keep scanning upward
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Context re-reads in full on every turn, so its size IS the standing per-turn floor — the one
|
|
349
|
+
// number that argues for ending a session rather than carrying it into unrelated work. The agent
|
|
350
|
+
// cannot act on this (it can neither end a session nor start one), so it is addressed to the user
|
|
351
|
+
// as a systemMessage and never blocks. Kept out of the model's context deliberately: a running
|
|
352
|
+
// token count in-context produces context anxiety, not shorter sessions.
|
|
353
|
+
const CONTEXT_ADVISORY_TOKENS = 250000;
|
|
354
|
+
|
|
355
|
+
function sessionSizeNote(payload) {
|
|
356
|
+
const tokens = sessionContextTokens(payload.transcript_path);
|
|
357
|
+
if (tokens < CONTEXT_ADVISORY_TOKENS) return null;
|
|
358
|
+
return (
|
|
359
|
+
`Session context is roughly ${Math.round(tokens / 1000)}K tokens, and every turn re-reads all of it — ` +
|
|
360
|
+
"that is the per-turn floor before anything new is produced. A fresh session starts near zero; " +
|
|
361
|
+
"worth ending this one if the next task is unrelated."
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
323
365
|
function stopGate(projectDir, payload) {
|
|
324
366
|
if (payload.stop_hook_active) return null; // the runtime's infinite-loop guard — never re-block
|
|
325
367
|
|
|
@@ -376,6 +418,11 @@ function main() {
|
|
|
376
418
|
}
|
|
377
419
|
} else if (event === "Stop") {
|
|
378
420
|
out = stopGate(projectDir, payload);
|
|
421
|
+
// Advisory rides alongside a block rather than replacing it — both are true at once.
|
|
422
|
+
if (!payload.stop_hook_active) {
|
|
423
|
+
const note = sessionSizeNote(payload);
|
|
424
|
+
if (note) out = { ...(out || {}), systemMessage: note };
|
|
425
|
+
}
|
|
379
426
|
}
|
|
380
427
|
|
|
381
428
|
if (out) process.stdout.write(JSON.stringify(out));
|
|
@@ -8,7 +8,9 @@ Every unit of work leaves one record — `docs/history/changelog/YYYY-MM-DD-{thr
|
|
|
8
8
|
| --- | --- |
|
|
9
9
|
| A plan reaches `Status: Complete` | A draft entry for that plan, linked to it — written now, while the session still holds the context |
|
|
10
10
|
| The user asks for a changelog | The entry for the work in hand. No commit required |
|
|
11
|
-
| A commit is made | Every non-exempt commit is covered by an entry — the `commit` skill writes or completes it |
|
|
11
|
+
| A commit is made | Every non-exempt commit is covered by an entry — the `commit` skill writes or completes it (no `commit` skill in the runtime ⇒ write the entry from the `documentation` skill's `templates/changelog-entry.md` and stage it with the commit) |
|
|
12
|
+
|
|
13
|
+
An entry is owed only by work that changes promised behavior or takes a design decision; mechanical and cosmetic commits — a typo, a copy tweak, a config value, a log line — take only their `docs/history/CHANGELOG.md` row, no entry file. The failure fields (`Ask:`, `Skills:`, `Pin:`) are required only when non-none.
|
|
12
14
|
|
|
13
15
|
## Lifecycle
|
|
14
16
|
|
|
@@ -17,3 +19,7 @@ Draft (created before its commit exists) → complete (missing fields filled fro
|
|
|
17
19
|
## Granularity
|
|
18
20
|
|
|
19
21
|
A commit is the smallest unit an entry may describe; above that the mapping is judgment — four commits may be four entries or one, whichever reads better as history. An entry spanning several commits rides in the **last** of them, naming the predecessors in `Commits:`. When work exceeds what a draft describes, write a second entry rather than stretching the draft — the first stays an honest account of the plan.
|
|
22
|
+
|
|
23
|
+
## Migrations are a different genre
|
|
24
|
+
|
|
25
|
+
**One-shot migrations are a different genre.** Work that runs **once** against a live system — a data backfill, a schema cutover, a dependency or harness upgrade rolled across repos — is a **migration runbook** at `docs/history/migrations/YYYY-MM-DD-{slug}.md` (template: the `documentation` skill's `templates/migration-runbook.md`), authored **before** execution and outcome-stamped after. A changelog entry says what changed in the repo; a runbook says what was executed against the world and how to verify or undo it. A repeatable procedure is neither — that is `docs/operations/`. When a runbook defers targets, it records **why** and the **trigger to re-evaluate them**, so the next run re-checks rather than inheriting the skip.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Cloudflare Worker
|
|
2
|
+
|
|
3
|
+
Conventions for Cloudflare Workers — `wrangler.jsonc` setup, vars and secrets, and code shape (dispatch, errors, modules, WebSockets). Schema design doctrine is the `schema-design` skill.
|
|
4
|
+
|
|
5
|
+
## 1. Project & wrangler.jsonc Setup
|
|
6
|
+
|
|
7
|
+
### 1.1 Wrangler version
|
|
8
|
+
|
|
9
|
+
- Use Wrangler **v4** — v3's bundled config schema is missing newer keys (e.g. `secrets_store_secrets`), so editors flag valid config as invalid.
|
|
10
|
+
- **No `package.json`** (dep-free worker): use `npx wrangler@4` in every command and doc — the `@4` is the pin. Don't create a `package.json` just to record it.
|
|
11
|
+
- **`package.json` exists**: pin wrangler to the exact version tested against (not `^4.0.0`), reinstall, and verify with `wrangler deploy --dry-run` before pushing.
|
|
12
|
+
|
|
13
|
+
### 1.2 Config format
|
|
14
|
+
|
|
15
|
+
- Use `wrangler.jsonc` (not `wrangler.toml`).
|
|
16
|
+
- Include `"$schema": "node_modules/wrangler/config-schema.json"` so the editor validates against the exact installed wrangler version.
|
|
17
|
+
|
|
18
|
+
### 1.3 Comments in jsonc
|
|
19
|
+
|
|
20
|
+
None by default. Keep a comment only when it captures a non-obvious **WHY** — a hidden constraint, workaround, or account-specific quirk. Never narrate what a key already says.
|
|
21
|
+
|
|
22
|
+
### 1.4 workers.dev URLs
|
|
23
|
+
|
|
24
|
+
- Set `"workers_dev": false` once a custom domain is wired up, so the custom domain is the only entry point.
|
|
25
|
+
- Pair with a `routes` entry using `custom_domain: true` so Cloudflare manages the DNS record and cert.
|
|
26
|
+
|
|
27
|
+
### 1.5 Observability
|
|
28
|
+
|
|
29
|
+
Default block for every Worker:
|
|
30
|
+
|
|
31
|
+
```jsonc
|
|
32
|
+
"observability": {
|
|
33
|
+
"logs": {
|
|
34
|
+
"enabled": false,
|
|
35
|
+
"invocation_logs": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`enabled: false` avoids per-log billing; `invocation_logs: true` keeps the cheap per-request audit trail. **Bootstrap override:** a new Worker (especially cron-only) runs with `enabled: true` until a few real runs look clean — with logs off, `console.log` is visible only live via `npx wrangler tail` and nothing persists after a failed run. Flip back once stable.
|
|
41
|
+
|
|
42
|
+
### 1.6 Project structure & dependencies
|
|
43
|
+
|
|
44
|
+
- Main entry file: `index.ts`. Reusable helpers: `helper.ts`.
|
|
45
|
+
- TypeScript under `strict: true` — wrangler bundles it natively, no separate build step; a minimal `tsconfig.json` ships even in dep-free Workers.
|
|
46
|
+
- Don't run `npm run build` or `npm run deploy` in examples.
|
|
47
|
+
- **`package.json` / `package-lock.json` are opt-in, not default.** Create them only when code under `src/` imports an npm package at runtime — a Worker on built-in Web/Workers APIs alone needs no manifest; run wrangler, linters, and one-offs via `npx <tool>`. Add both files the moment the first real dep lands. `node_modules/` stays in `.gitignore` either way.
|
|
48
|
+
|
|
49
|
+
### 1.7 Environment & configuration
|
|
50
|
+
|
|
51
|
+
- Use the deployed Cloudflare URL in docs/tests, not `localhost`.
|
|
52
|
+
- Prefer SQLite-based Durable Objects over in-memory-only designs; add classes under `migrations.new_sqlite_classes`.
|
|
53
|
+
|
|
54
|
+
### 1.8 Cloudflare Secrets Store
|
|
55
|
+
|
|
56
|
+
Use when the user mentions it. Use the same `binding` name as the `secret_name`.
|
|
57
|
+
|
|
58
|
+
```jsonc
|
|
59
|
+
"secrets_store_secrets": [
|
|
60
|
+
{ "binding": "CLICKUP_KEY", "store_id": "792206780c0c46e8ba8ade3f21b44eac", "secret_name": "CLICKUP_KEY" }
|
|
61
|
+
]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Access in code: `await env.CLICKUP_KEY.get()`.
|
|
65
|
+
|
|
66
|
+
### 1.9 Naming
|
|
67
|
+
|
|
68
|
+
- Repo/dir: `cf-<name>` (e.g. `cf-extract-design`).
|
|
69
|
+
- Worker name: `<name>` — repo name minus the `cf-` prefix.
|
|
70
|
+
- R2 bucket, D1 database, and KV namespace names: equal to the worker name. One name, one resource set.
|
|
71
|
+
|
|
72
|
+
### 1.10 Vars & secrets
|
|
73
|
+
|
|
74
|
+
**Non-secret vars live in the `vars` block in `wrangler.jsonc`** — checked in, one source of truth. Vars are configuration, not runtime state; do not move them to the dashboard. A single-tenant application Worker (one deployment — e.g. Apollo) declares them at the top level; a multi-tenant Worker declares per-tenant vars under each `env.<tenant>` block (§11).
|
|
75
|
+
|
|
76
|
+
**Secrets never touch git.** Local dev: a gitignored `.dev.vars` (`KEY=VALUE` dotenv, auto-loaded by `wrangler dev`) with a committed `.dev.vars.example` listing the keys and placeholder values. Production: `wrangler secret put <NAME>`, a gitignored `.secrets` file pushed with `wrangler secret bulk .secrets` (or shipped with a deploy via `wrangler deploy --secrets-file .secrets`), or the Secrets Store (§1.8). Never log or echo a secret value.
|
|
77
|
+
|
|
78
|
+
**No `keep_vars`.** Leave it unset (default `false`) — each deploy reconciles vars to exactly what the config declares. `keep_vars` only guards plaintext vars set out-of-band in the dashboard; it never touches secrets — `wrangler secret put` secrets survive every deploy regardless. Only the multi-tenant pattern sets `keep_vars: true` (§11).
|
|
79
|
+
|
|
80
|
+
**Never name a deployed-value file `.env` or `.env.<anything>`** — `wrangler dev` auto-loads a root `.env` as local dev vars, so a prod-valued `.env` silently becomes the local environment on a fresh clone or in CI. Committed `*.example` files carry keys with placeholder values only.
|
|
81
|
+
|
|
82
|
+
`.gitignore`: `.dev.vars`, `.secrets`, `.secrets.*`, `.env`, `.env.*`
|
|
83
|
+
|
|
84
|
+
## 2. Code & Helpers
|
|
85
|
+
|
|
86
|
+
- Use `generateUuidV7()` in `helper.ts` for IDs (time-sortable UUIDv7).
|
|
87
|
+
- Only `await` what's needed (e.g. `request.json()`, `do.fetch(...)`).
|
|
88
|
+
|
|
89
|
+
## 3. fetch() Structure
|
|
90
|
+
|
|
91
|
+
`validateRequest()` first, then `switch (path)` for dispatch. Single `try/catch` wrapping the entire handler. No if-chains, no regex in fetch.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
export default {
|
|
95
|
+
async fetch(request, env, ctx) {
|
|
96
|
+
try {
|
|
97
|
+
const route = validateRequest(request);
|
|
98
|
+
switch (route.path) {
|
|
99
|
+
case "monitors":
|
|
100
|
+
return await handleMonitors({ ...route, env, ctx });
|
|
101
|
+
default:
|
|
102
|
+
throw { code: 404, message: "Route not found" };
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("Unhandled error in fetch()", error);
|
|
106
|
+
return createErrorResponse(
|
|
107
|
+
error.message || "Internal server error",
|
|
108
|
+
error.code || 500,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
No nested `try/catch` except around WebSocket `send`/`close` (see §9).
|
|
116
|
+
|
|
117
|
+
## 4. validateRequest()
|
|
118
|
+
|
|
119
|
+
Lives in `helper.ts`. Parses the URL in one place and returns `{ path, method, query, resourceId, subPath }`; throws `{ code: 400 }` when a path requires a resource ID and none is present.
|
|
120
|
+
|
|
121
|
+
## 5. Validation
|
|
122
|
+
|
|
123
|
+
Fail fast with thrown `{ code, message }` (`400` bad input, `401` missing/invalid auth). Validate required fields explicitly — no optional chaining for required fields.
|
|
124
|
+
|
|
125
|
+
## 6. Errors & Responses
|
|
126
|
+
|
|
127
|
+
- Throw structured errors: `{ code, message }`. Always `console.error()` with context (path, user, etc.). No empty catch blocks.
|
|
128
|
+
- Response envelope `{ status: 1|0, message, data }` — the shared cross-service contract.
|
|
129
|
+
- `createResponse(data, message, code)` in `helper.ts` bakes CORS in, returns a null body for 204/304, and uses proper HTTP codes; `createErrorResponse(message, code)` delegates to it.
|
|
130
|
+
- CORS is passive: a `CORS_HEADERS` const in `helper.ts`, spread into `createResponse()` — no explicit OPTIONS handler, no wrapper functions. Origin-restricted CORS builds the headers from an allowlist (plus `localhost` in dev), still inside `createResponse()`.
|
|
131
|
+
|
|
132
|
+
## 7. Handler Signatures
|
|
133
|
+
|
|
134
|
+
Single destructured object — never positional args. Compose by spreading route with env/ctx: `handleGetStats({ request, env, resourceId })`, not `handleGetStats(request, id, env)`.
|
|
135
|
+
|
|
136
|
+
## 8. Module Structure
|
|
137
|
+
|
|
138
|
+
Feature-based folders with `handle.ts` + `helper.ts` pairs. `index.ts` only does dispatch.
|
|
139
|
+
|
|
140
|
+
| Path | Role |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `src/index.ts` | Entry point — dispatch only |
|
|
143
|
+
| `src/helper.ts` | Shared CORS, responses, validation, utilities |
|
|
144
|
+
| `src/database.ts` | Shared D1 queries |
|
|
145
|
+
| `src/<feature>/handle.ts` | Route handlers |
|
|
146
|
+
| `src/<feature>/helper.ts` | Feature-specific helpers & DB ops |
|
|
147
|
+
|
|
148
|
+
## 9. WebSockets
|
|
149
|
+
|
|
150
|
+
Standard `WebSocketPair` handshake (`server.accept()`, return 101 with `webSocket: client`). Check `readyState === WebSocket.OPEN` before sending. The only acceptable nested `try/catch`: wrap `send`; in its catch, `close(1011)` inside a bare inner try, then evict the session.
|
|
151
|
+
|
|
152
|
+
## 10. D1 Schema
|
|
153
|
+
|
|
154
|
+
Follows the shared database naming and column-type vocabulary; what earns a table and the rest of the design doctrine is the `schema-design` skill.
|
|
155
|
+
|
|
156
|
+
## 11. Multi-tenant Workers (native environments)
|
|
157
|
+
|
|
158
|
+
Rare — only when **one Worker codebase ships as several independent tenants** (separate Workers, domains, and data stores from a single `src/`, e.g. `~/dev/auth` → `auth-nuplay`, `auth-nustack`). Use **native wrangler environments**, never one Worker that switches tenant at runtime. For a single-tenant application Worker, §1.10 applies and this section does not.
|
|
159
|
+
|
|
160
|
+
- **One config, `env.<tenant>` blocks.** The top-level block is **local dev** (`wrangler dev`): a `*-dev` name and placeholder D1/KV ids, simulated locally. Each `env.<tenant>` block is **one production tenant**; deploy it with `wrangler deploy --env <tenant>`.
|
|
161
|
+
- **Named environments never inherit** `vars` / `d1_databases` / `kv_namespaces` / `routes` / `secrets_store_secrets` / `assets` from the top level — **each `env.<tenant>` declares them in full**. Dropping a binding from an env block disables that capability for that one tenant.
|
|
162
|
+
- **Per-tenant non-secret vars live in `env.<tenant>.vars`** — still checked in; one file holds every tenant's config side by side.
|
|
163
|
+
- **Local-dev vars** come from a gitignored `.vars.local`, injected as `--var` by the dev script (the top-level block carries no in-config `vars`).
|
|
164
|
+
- **Per-tenant secrets, never in git** — `wrangler secret put <NAME> --env <tenant>`, or a gitignored per-tenant `.secrets` (e.g. `worker/tenants/<tenant>/.secrets`) pushed with `wrangler secret bulk --env <tenant>`, plus a committed `.secrets.example`. Secrets Store bindings are declared inside each env block.
|
|
165
|
+
- **`keep_vars: true`** (top level) keeps a per-tenant deploy **additive**: tenants inject their non-secret vars via `--var` (or the dashboard), and without it a deploy would wipe them. On secrets it changes nothing (§1.10).
|
|
166
|
+
|
|
167
|
+
`.gitignore`: `.vars.local`, `.secrets`, `.secrets.*`, `.env`, `.env.*`
|