agentic-team 0.0.0-stage → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -2
- package/bin/build-template.js +208 -0
- package/bin/index.js +1139 -0
- package/package.json +41 -4
- package/template/.claude-plugin/plugin.json +10 -0
- package/template/.orchestra/README.md +401 -0
- package/template/.orchestra/blueprints/README.md +87 -0
- package/template/.orchestra/blueprints/api-only.md +46 -0
- package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
- package/template/.orchestra/blueprints/saas-starter.md +69 -0
- package/template/.orchestra/config.yml +277 -0
- package/template/.orchestra/roles/orchestrator.md +105 -0
- package/template/.orchestra/roles/product-manager.md +438 -0
- package/template/.orchestra/scripts/compact-diff.awk +45 -0
- package/template/.orchestra/stacks/README.md +81 -0
- package/template/.orchestra/stacks/_lib/common.sh +3095 -0
- package/template/.orchestra/stacks/_repo/setup.sh +1109 -0
- package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
- package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
- package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
- package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
- package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
- package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
- package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
- package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
- package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
- package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
- package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
- package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
- package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
- package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
- package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
- package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
- package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
- package/template/.orchestra/trackers/github.sh +467 -0
- package/template/CLAUDE.md +54 -0
- package/template/agents/architect.md +83 -0
- package/template/agents/engineer-high.md +17 -0
- package/template/agents/engineer-low.md +17 -0
- package/template/agents/engineer-max.md +17 -0
- package/template/agents/engineer-medium.md +17 -0
- package/template/agents/engineer-xhigh.md +17 -0
- package/template/agents/lead.md +870 -0
- package/template/agents/milestone-reviewer.md +63 -0
- package/template/agents/orchestrator.md +105 -0
- package/template/agents/product-manager.md +438 -0
- package/template/agents/reviewer.md +301 -0
- package/template/agents/scaffolder.md +180 -0
- package/template/agents/tracker.md +202 -0
- package/template/commands/blueprint.md +23 -0
- package/template/commands/create-role.md +64 -0
- package/template/commands/export.md +45 -0
- package/template/commands/help.md +55 -0
- package/template/commands/hotfix.md +12 -0
- package/template/commands/orchestrator.md +12 -0
- package/template/commands/pm.md +18 -0
- package/template/commands/rewind.md +61 -0
- package/template/commands/setup.md +14 -0
- package/template/commands/start.md +29 -0
- package/template/commands/status.md +11 -0
- package/template/commands/sync.md +73 -0
- package/template/commands/verifier.md +53 -0
- package/template/hooks/audit-context.sh +153 -0
- package/template/hooks/enforce-role.sh +175 -0
- package/template/hooks/protect-system-files.sh +123 -0
- package/template/hooks/review-rounds.sh +172 -0
- package/template/rules/acceptance-check.orchestra.md +11 -0
- package/template/rules/analytics-kpi.orchestra.md +56 -0
- package/template/rules/code-standards.orchestra.md +34 -0
- package/template/rules/comments.orchestra.md +77 -0
- package/template/rules/commit-format.orchestra.md +20 -0
- package/template/rules/dependency-upgrade.orchestra.md +112 -0
- package/template/rules/design-parity.orchestra.md +170 -0
- package/template/rules/observability.orchestra.md +118 -0
- package/template/rules/performance.orchestra.md +186 -0
- package/template/rules/phase-limits.orchestra.md +3 -0
- package/template/rules/pull-request.orchestra.md +213 -0
- package/template/rules/role-boundaries.orchestra.md +117 -0
- package/template/rules/security.orchestra.md +106 -0
- package/template/rules/stuck-detection.orchestra.md +25 -0
- package/template/rules/testing-standards.orchestra.md +214 -0
- package/template/rules/tracker-authority.orchestra.md +190 -0
- package/template/rules/verification-gate.orchestra.md +56 -0
- package/template/skills/accessibility/SKILL.md +31 -0
- package/template/skills/android-testing/SKILL.md +112 -0
- package/template/skills/architecture/SKILL.md +175 -0
- package/template/skills/auth-setup/SKILL.md +105 -0
- package/template/skills/best-practices/SKILL.md +29 -0
- package/template/skills/code-optimizer/SKILL.md +27 -0
- package/template/skills/core-web-vitals/SKILL.md +32 -0
- package/template/skills/crud-api/SKILL.md +32 -0
- package/template/skills/debug/SKILL.md +28 -0
- package/template/skills/deployment/SKILL.md +104 -0
- package/template/skills/error-handling/SKILL.md +154 -0
- package/template/skills/frontend-design/SKILL.md +30 -0
- package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
- package/template/skills/ios-testing/SKILL.md +100 -0
- package/template/skills/mobile-testing/SKILL.md +102 -0
- package/template/skills/observability/SKILL.md +91 -0
- package/template/skills/project-structure/SKILL.md +114 -0
- package/template/skills/push-notifications/SKILL.md +146 -0
- package/template/skills/react-best-practices/SKILL.md +31 -0
- package/template/skills/realtime/SKILL.md +146 -0
- package/template/skills/review/SKILL.md +82 -0
- package/template/skills/testing/SKILL.md +44 -0
- package/template/skills/web-quality-audit/SKILL.md +47 -0
- package/template/skills/web-testing/SKILL.md +120 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Dependency Upgrades
|
|
2
|
+
|
|
3
|
+
Dependabot opens the PR. The test suite decides.
|
|
4
|
+
|
|
5
|
+
## Merging
|
|
6
|
+
|
|
7
|
+
Green CI is the merge signal for a **minor or patch** bump. That is the
|
|
8
|
+
entire reason the seams are covered: a bump that changes behaviour has to
|
|
9
|
+
turn the suite red.
|
|
10
|
+
|
|
11
|
+
- **minor / patch, green** → merge. No changelog reading required.
|
|
12
|
+
- **major, green** → a human reads the changelog first. A major version is
|
|
13
|
+
the maintainer stating the contract changed, and some of what changed —
|
|
14
|
+
default timeouts, retry behaviour, performance, error types — is not
|
|
15
|
+
visible to any test. Green means "nothing we assert broke", not "nothing
|
|
16
|
+
broke".
|
|
17
|
+
- **Red** → **do not** loosen the test to make it pass. The test is doing
|
|
18
|
+
its job. Fix our code to match the new behaviour, or hold the upgrade.
|
|
19
|
+
- Never merge a red dependency PR "because it's just a minor."
|
|
20
|
+
|
|
21
|
+
The split is recorded in `.orchestra/config.yml` under
|
|
22
|
+
`testing.dependencies.automerge`, and it is **automation, not etiquette**:
|
|
23
|
+
|
|
24
|
+
- A dependency PR that changes a test file **fails**. A bump that needed a
|
|
25
|
+
test loosened is a behaviour change wearing a version number.
|
|
26
|
+
- A major is labelled `needs-human` with a comment saying why green is not
|
|
27
|
+
the same as safe.
|
|
28
|
+
- Minor and patch auto-merge with `--auto`, so they still wait for every
|
|
29
|
+
required check.
|
|
30
|
+
|
|
31
|
+
Nothing here is a bot deciding a major is fine. It only removes the queue of
|
|
32
|
+
patches that would otherwise be merged in a batch with the majors hidden in
|
|
33
|
+
it.
|
|
34
|
+
|
|
35
|
+
## When a test fails on a bump
|
|
36
|
+
|
|
37
|
+
1. Read the library's changelog for that version range before touching code.
|
|
38
|
+
2. Fix forward — update our wrapper to the new behavior.
|
|
39
|
+
3. If the change is genuinely breaking and the fix is large, hold the
|
|
40
|
+
upgrade and open a task. Do not half-migrate.
|
|
41
|
+
|
|
42
|
+
## Adding a dependency
|
|
43
|
+
|
|
44
|
+
- Check it is maintained: when did it last publish? A package quiet for a
|
|
45
|
+
year is a liability, not a dependency.
|
|
46
|
+
- Prefer the platform. Do not add a library for something the language or
|
|
47
|
+
framework already does.
|
|
48
|
+
- Anything we wrap gets a test, so the next bump is checked automatically.
|
|
49
|
+
|
|
50
|
+
## Framework-managed versions
|
|
51
|
+
|
|
52
|
+
Some frameworks pin their dependency tree on purpose, and there the registry
|
|
53
|
+
is not the authority — the framework's own table is.
|
|
54
|
+
|
|
55
|
+
Expo is the case that bites. The SDK is built against specific versions of
|
|
56
|
+
react, react-native, typescript and every native module. `expo install`
|
|
57
|
+
resolves against that table; `yarn add <pkg>` resolves against the registry
|
|
58
|
+
and installs something the SDK rejects — often at runtime, on a device, long
|
|
59
|
+
after the install reported success.
|
|
60
|
+
|
|
61
|
+
- **In an Expo app, install with `expo install`.** Never `yarn add` a
|
|
62
|
+
package the SDK manages.
|
|
63
|
+
- `expo install --check` says whether the tree is aligned. Run it after
|
|
64
|
+
touching dependencies; it is the honest answer, not a guess.
|
|
65
|
+
- `expo install --fix` is what moves these versions, and an SDK upgrade is
|
|
66
|
+
the event that triggers it.
|
|
67
|
+
- Dependabot cannot see that table, so SDK-managed packages are in the
|
|
68
|
+
`ignore` list in `.github/dependabot.yml`. Do not remove them to "get
|
|
69
|
+
updates" — a green CI on one of those bumps still breaks the app.
|
|
70
|
+
|
|
71
|
+
The same reasoning applies to any framework that ships a compatibility
|
|
72
|
+
table. Check for one before reaching for `@latest`.
|
|
73
|
+
|
|
74
|
+
## A fresh repo has no upgrades pending
|
|
75
|
+
|
|
76
|
+
Scaffolding is where staleness gets in. A create tool writes its own
|
|
77
|
+
dependency versions into the package.json it generates, and those were
|
|
78
|
+
current on the day its template was published — `create-next-app` ships
|
|
79
|
+
`"@types/node": "^20"`, `create-expo-app` ships jest 29. A workflow written
|
|
80
|
+
from memory pins `actions/checkout@v4` the same way.
|
|
81
|
+
|
|
82
|
+
So a scaffold ends by resolving everything the template left behind against
|
|
83
|
+
the registry, and the check is simple: **if Dependabot opens a PR against a
|
|
84
|
+
repo created the same day, the scaffold is broken.** Fix it in the stack —
|
|
85
|
+
`refresh_scaffold_deps` and `latest_action_ref` in
|
|
86
|
+
`.orchestra/stacks/_lib/common.sh` — not in the project.
|
|
87
|
+
|
|
88
|
+
## A scaffold proves it is clean, and fixes nothing at the end
|
|
89
|
+
|
|
90
|
+
Every deterministic fix happens *during* setup. The last step only verifies:
|
|
91
|
+
|
|
92
|
+
- peers this app should provide and does not → **fails**
|
|
93
|
+
- unmet peers inside a dependency's own manifest → reported, never fatal,
|
|
94
|
+
because it cannot be fixed from here and a gate nobody can satisfy is a
|
|
95
|
+
gate that gets disabled
|
|
96
|
+
- `yarn npm audit --severity critical` → fails
|
|
97
|
+
|
|
98
|
+
It deliberately does not upgrade or downgrade anything. Changing versions
|
|
99
|
+
after the verification gate has run leaves a repo whose gate passed against
|
|
100
|
+
different versions than the ones committed — and it would quietly undo the
|
|
101
|
+
deliberate pins: the age-gate deferral, the Expo SDK table, Robolectric's
|
|
102
|
+
supported API level.
|
|
103
|
+
|
|
104
|
+
When it fails, fix it in `.orchestra/stacks/`. Fixing it in the project means
|
|
105
|
+
the next project meets the same thing.
|
|
106
|
+
|
|
107
|
+
## Versions
|
|
108
|
+
|
|
109
|
+
Install at `@latest`; the lockfile records what landed. Pin to a range only
|
|
110
|
+
where a real compatibility constraint exists, and write the reason next to
|
|
111
|
+
it. Gradle is the exception — versions are explicit in the version catalog,
|
|
112
|
+
because dynamic versions destroy build reproducibility.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Design Parity
|
|
2
|
+
|
|
3
|
+
## What a design source is
|
|
4
|
+
|
|
5
|
+
A phase has a design source when its frontmatter declares one:
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
design: designs/checkout/payment-step.png # file in the repo
|
|
9
|
+
design: https://… # published Claude Design artboard
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
No `design:` field means no parity check. Do not infer one from a phase
|
|
13
|
+
title, and do not invent a path — a check against a guessed design is worse
|
|
14
|
+
than no check.
|
|
15
|
+
|
|
16
|
+
Design files live under `designs/` at the repo root, mirroring the screen
|
|
17
|
+
structure. A URL is fetched at check time.
|
|
18
|
+
|
|
19
|
+
## What the check needs
|
|
20
|
+
|
|
21
|
+
Two images: the design, and the implemented screen. If you cannot produce
|
|
22
|
+
the second one, **the check cannot run** — say so plainly and mark the
|
|
23
|
+
criterion unverified. Do not approve on the strength of reading the code.
|
|
24
|
+
|
|
25
|
+
Producing it needs a render target and a capture tool:
|
|
26
|
+
|
|
27
|
+
| App | Render target | Capture |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| Web | react-cosmos fixture | browser tool screenshot |
|
|
30
|
+
| Expo | the screen in a simulator | simulator screenshot |
|
|
31
|
+
| Native iOS / Android | the screen in a simulator | simulator screenshot |
|
|
32
|
+
|
|
33
|
+
Capture with the session's own tools — a browser tool screenshot for web, the
|
|
34
|
+
simulator tool for mobile. Where the simulator integration is unavailable
|
|
35
|
+
(a machine whose `xcode-select` points elsewhere, a session without the
|
|
36
|
+
panel), fall back to the platform's own command and **say that you did**:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
xcrun simctl io booted screenshot designs/.captures/<phase>.png # iOS
|
|
40
|
+
adb exec-out screencap -p > designs/.captures/<phase>.png # Android
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If no capture is possible at all, report that as the reason the check was
|
|
44
|
+
skipped. That is an honest unverified, not a pass.
|
|
45
|
+
|
|
46
|
+
### Where the files live
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
designs/
|
|
50
|
+
├── <screen>.png the design, committed, mirroring the screen structure
|
|
51
|
+
├── .fetched/ a URL design, fetched once at milestone start
|
|
52
|
+
└── .captures/ what was built, committed so the PR can show it
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A URL is fetched **once** into `designs/.fetched/`, when lead composes the
|
|
56
|
+
phase, not at each comparison. The capture goes to `designs/.captures/` and is
|
|
57
|
+
committed with the change, because `gh` cannot upload an image and a
|
|
58
|
+
screenshot outside the branch cannot appear in a pull request. A design edited mid-milestone would otherwise silently change what
|
|
59
|
+
the phase was measured against, and the PR would show a pair that never
|
|
60
|
+
existed together.
|
|
61
|
+
|
|
62
|
+
Applies when the task has a design source. Who judges what is left over
|
|
63
|
+
depends on whether anyone is watching — the `--auto` flag.
|
|
64
|
+
|
|
65
|
+
## Measured where it can be measured, judged where it cannot
|
|
66
|
+
|
|
67
|
+
The goal is the design, exactly — not something in its spirit. But a literal
|
|
68
|
+
pixel threshold cannot be the gate: fonts hint differently, antialiasing
|
|
69
|
+
differs, and a capture at device scale is not the design's export scale, so
|
|
70
|
+
the difference is never zero and a run would never exit. A gate that can never
|
|
71
|
+
pass is a gate that gets disabled.
|
|
72
|
+
|
|
73
|
+
So the strictness goes where it can actually bite. Most of what people mean by
|
|
74
|
+
"pixel perfect" is a **number**, and numbers are checkable:
|
|
75
|
+
|
|
76
|
+
| What | How it is checked | Verdict if wrong |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Spacing, padding, gaps | Measured off the capture against the design's value | **blocking** |
|
|
79
|
+
| Type size, weight, line height | Read from the design, compared to the token used | **blocking** |
|
|
80
|
+
| Colour | The design's value, matched to a token value | **blocking** |
|
|
81
|
+
| Radius, border width, elevation | Same | **blocking** |
|
|
82
|
+
| Element present, ordered, aligned | Read from both | **blocking** |
|
|
83
|
+
| Every state the design shows — hover, pressed, disabled, empty, error | Rendered and compared | **blocking** |
|
|
84
|
+
| Content: real strings, not lorem | Read from both | **blocking** |
|
|
85
|
+
| Font rendering, sub-pixel antialiasing, capture scale artefacts | — | non-blocking |
|
|
86
|
+
|
|
87
|
+
A colour or a spacing that is *close* is wrong. The app has a token module for
|
|
88
|
+
exactly this reason, and "16 where the design says 12" is not a judgement call
|
|
89
|
+
— it is a defect with a number attached. Report it that way: the value in the
|
|
90
|
+
design, the value in the build, and the token that should have carried it.
|
|
91
|
+
|
|
92
|
+
Only the last row is non-blocking, and it is the only row a machine cannot
|
|
93
|
+
decide. Everything above it, you can.
|
|
94
|
+
|
|
95
|
+
If a value in the design cannot be expressed by the app's token module, that is
|
|
96
|
+
a **blocker to report** — the design and the design system disagree, and
|
|
97
|
+
splitting the difference silently is how a token module stops being true.
|
|
98
|
+
|
|
99
|
+
## The value comes from the source, not from the pixels
|
|
100
|
+
|
|
101
|
+
The design's value in the table above is read from the source when the
|
|
102
|
+
design states it — CSS, a token file, an export with numbers — and sampled
|
|
103
|
+
from the image only when it does not. The capture is still needed for
|
|
104
|
+
what only a render can show: placement, order, states, whether an element is
|
|
105
|
+
there at all.
|
|
106
|
+
|
|
107
|
+
Recovering from pixels a number the design already wrote down means building
|
|
108
|
+
a measuring engine, and where the design renders through a different engine
|
|
109
|
+
than the app — an HTML board against a native screen — that engine never
|
|
110
|
+
converges, because what is left over is the renderers' difference, not the
|
|
111
|
+
build's. One run spent most of a weekly budget on exactly that.
|
|
112
|
+
|
|
113
|
+
## The non-blocking row is not promoted
|
|
114
|
+
|
|
115
|
+
Font rendering, sub-pixel antialiasing and capture scale artefacts stay
|
|
116
|
+
non-blocking. No agent makes them blocking — not lead by a decision, not the
|
|
117
|
+
architect by an RFC, not an engineer or a reviewer by a tolerance. Only the
|
|
118
|
+
user can, by saying so; the PM then writes it into that phase's acceptance
|
|
119
|
+
criteria, where lead and the reviewer read it.
|
|
120
|
+
|
|
121
|
+
"Perfect" and "pixel perfect" from the user mean the table above, exactly.
|
|
122
|
+
They are not a request for this row. If the words seem to ask for more, ask.
|
|
123
|
+
|
|
124
|
+
A check whose job is to tell a renderer difference from a defect is this row
|
|
125
|
+
being worked on, and it does not belong in the gate.
|
|
126
|
+
|
|
127
|
+
## Who fixes what
|
|
128
|
+
|
|
129
|
+
The agent building the screen fixes every blocking discrepancy it can see,
|
|
130
|
+
before it reports done. Always — "build it, then let someone else notice it is
|
|
131
|
+
wrong" is not a workflow, it is how an approximation reaches a reviewer with a
|
|
132
|
+
straight face.
|
|
133
|
+
|
|
134
|
+
What changes is who judges the remainder.
|
|
135
|
+
|
|
136
|
+
## Without `--auto` — the human judges
|
|
137
|
+
|
|
138
|
+
Both images go in the pull request body's table and a person decides. Lead
|
|
139
|
+
does not loop on its own: the builder already fixed what it could see, and the
|
|
140
|
+
call that is left is the one a person is better at.
|
|
141
|
+
|
|
142
|
+
`gh` cannot upload an image. Reference the design by its repo path or URL, and
|
|
143
|
+
commit the capture into the branch under `designs/.captures/` so the pull
|
|
144
|
+
request can show it. If you could not capture, write
|
|
145
|
+
"not captured — {reason}" in the cell rather than leaving it blank.
|
|
146
|
+
|
|
147
|
+
## With `--auto` — nobody is watching, so it loops
|
|
148
|
+
|
|
149
|
+
1. Implement, render, compare.
|
|
150
|
+
2. Blocking discrepancies → fix and repeat.
|
|
151
|
+
3. Stop at `delivery.max_design_iterations`.
|
|
152
|
+
4. On exhaustion, apply `delivery.on_parity_exhausted`:
|
|
153
|
+
- `tech-debt` — open a tech debt task naming the exact discrepancies that
|
|
154
|
+
remain, and move on. See
|
|
155
|
+
`.claude/rules/tracker-authority.orchestra.md`.
|
|
156
|
+
- `fail` — mark the phase failed and stop
|
|
157
|
+
|
|
158
|
+
The pull request still carries both images, and it still gets reviewed. The
|
|
159
|
+
loop exists because there is no human at the gate to catch what the builder
|
|
160
|
+
could not see, not because the work is invisible.
|
|
161
|
+
|
|
162
|
+
Never silently accept a screen that does not match. Never quietly raise the
|
|
163
|
+
iteration cap to make a run succeed.
|
|
164
|
+
|
|
165
|
+
## Render target
|
|
166
|
+
|
|
167
|
+
Prefer the component workbench over a live app screen. A fixture renders
|
|
168
|
+
deterministically; a real screen carries real data, loading states, and
|
|
169
|
+
timing, all of which produce differences that have nothing to do with the
|
|
170
|
+
design.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Observability
|
|
2
|
+
|
|
3
|
+
**Mandatory.** An app without error monitoring is not shippable, and this
|
|
4
|
+
is not a launch-day task — it goes in before the first release, not after
|
|
5
|
+
the first incident.
|
|
6
|
+
|
|
7
|
+
## Error monitoring
|
|
8
|
+
|
|
9
|
+
Every app reports uncaught errors and crashes to a monitoring service
|
|
10
|
+
before it reaches any real user.
|
|
11
|
+
|
|
12
|
+
- **Release tagging is required.** An error you cannot tie to a build is an
|
|
13
|
+
error you cannot fix. Every report carries the app version and commit.
|
|
14
|
+
- **Upload the symbols** — source maps for web and Node, dSYMs for iOS,
|
|
15
|
+
mapping files for Android. A minified stack trace tells you nothing.
|
|
16
|
+
- **Attach context**: route or screen, and a stable anonymous session id.
|
|
17
|
+
- **Never attach secrets or PII.** No tokens, no emails, no request bodies
|
|
18
|
+
that might contain either. Scrub before send, not after.
|
|
19
|
+
|
|
20
|
+
## Swallowed errors are bugs
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
catch (e) { } // defect
|
|
24
|
+
catch (e) { console.log(e) } // defect
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Every catch either **handles** the error meaningfully, or **reports** it and
|
|
28
|
+
rethrows. "Handled" means the user gets a correct outcome — not that the
|
|
29
|
+
crash was hidden from them.
|
|
30
|
+
|
|
31
|
+
The one exception is a genuinely optional path, and it carries a comment
|
|
32
|
+
saying why failure is acceptable.
|
|
33
|
+
|
|
34
|
+
Depth on what "handled" means — typed errors, propagation, what the user
|
|
35
|
+
sees, degrading instead of crashing — in
|
|
36
|
+
`.claude/skills/error-handling/SKILL.md`.
|
|
37
|
+
|
|
38
|
+
## Structured logging
|
|
39
|
+
|
|
40
|
+
Logs are data, not prose. JSON, one event per line.
|
|
41
|
+
|
|
42
|
+
- Levels used honestly: `error` means someone should look, `warn` means it
|
|
43
|
+
might become an error, `info` is a business event, `debug` is off in
|
|
44
|
+
production.
|
|
45
|
+
- Every server request carries a correlation id, and it appears in every
|
|
46
|
+
log line for that request. Without it you cannot follow a failure across
|
|
47
|
+
services. Take it from the caller's `x-request-id` when present, so a trace
|
|
48
|
+
that started upstream stays one trace, and echo it back so a caller can
|
|
49
|
+
quote it in a bug report.
|
|
50
|
+
- **Redaction is configured in the logger**, not trusted to every call site.
|
|
51
|
+
"Never log a token" holds until the one time somebody logs the whole
|
|
52
|
+
request object, and by then it is in the log store.
|
|
53
|
+
- No secrets, no PII, no full request bodies.
|
|
54
|
+
- One line per request, at completion, where status and duration are known.
|
|
55
|
+
Logging on the way in as well doubles the volume and answers nothing.
|
|
56
|
+
- Never log inside a hot loop.
|
|
57
|
+
- Every line carries the release and environment, so a log and an error
|
|
58
|
+
report line up against the same deploy.
|
|
59
|
+
|
|
60
|
+
## Health is two endpoints, not one
|
|
61
|
+
|
|
62
|
+
- **`/health`** — liveness. The process is up. Cheap, no dependencies.
|
|
63
|
+
- **`/ready`** — readiness. Every dependency this service cannot serve
|
|
64
|
+
without is reachable. Answers 503, naming the one that failed.
|
|
65
|
+
|
|
66
|
+
The platform's healthcheck points at **readiness**. On Railway the previous
|
|
67
|
+
deployment keeps serving until the new one answers 200, so a readiness probe
|
|
68
|
+
that returns 200 while the database is unreachable does not merely mislead a
|
|
69
|
+
dashboard — it promotes a broken release.
|
|
70
|
+
|
|
71
|
+
That makes readiness load-bearing infrastructure. Anything missing from the
|
|
72
|
+
checks map is a dependency that can be broken while a release is promoted
|
|
73
|
+
anyway.
|
|
74
|
+
|
|
75
|
+
**A readiness check that cannot fail is worse than none.** There is a test
|
|
76
|
+
that makes it fail; if you add a dependency, add its probe and its failing
|
|
77
|
+
test in the same change.
|
|
78
|
+
|
|
79
|
+
## Releasing
|
|
80
|
+
|
|
81
|
+
There is no percentage canary on Railway. What protects a release is:
|
|
82
|
+
|
|
83
|
+
1. the healthcheck gating promotion,
|
|
84
|
+
2. a smoke test against the real URL afterwards, and
|
|
85
|
+
3. putting the last good commit back when that fails.
|
|
86
|
+
|
|
87
|
+
Rollback is the same path as a normal deploy, deliberately — an untested
|
|
88
|
+
recovery path is not a recovery path. The `last-good-deploy` tag moves only
|
|
89
|
+
after the smoke passes, so it always names a release observed working rather
|
|
90
|
+
than one that merely deployed.
|
|
91
|
+
|
|
92
|
+
Confidence comes from being able to undo, not from being sure.
|
|
93
|
+
|
|
94
|
+
## Feature flags
|
|
95
|
+
|
|
96
|
+
A risky change ships dark and is turned off in seconds without a deploy.
|
|
97
|
+
|
|
98
|
+
The flag exists to protect the service, so a lookup must never be able to
|
|
99
|
+
take it down: bounded by a timeout, failures reported and answered with the
|
|
100
|
+
flag's own fallback rather than thrown into a handler. For a kill switch the
|
|
101
|
+
fallback is **false** — if we cannot ask whether a risky path is allowed, the
|
|
102
|
+
answer is no.
|
|
103
|
+
|
|
104
|
+
Flags live in one typed registry. A string literal at a call site is how they
|
|
105
|
+
rot: the flag is deleted, the call still compiles, and nobody finds out until
|
|
106
|
+
the behaviour quietly changes.
|
|
107
|
+
|
|
108
|
+
## Defaults
|
|
109
|
+
|
|
110
|
+
Sentry for error monitoring, `pino` for structured logging in Node. Both
|
|
111
|
+
are defaults, not mandates — substitute freely. What is **not** optional is
|
|
112
|
+
that the capability exists and is wired before release.
|
|
113
|
+
|
|
114
|
+
Configure the provider in `.orchestra/config.yml` under `observability`.
|
|
115
|
+
|
|
116
|
+
`observability.require_before_release` (true by default) is checked by lead
|
|
117
|
+
at milestone completion. Setting it false defers the requirement — it does
|
|
118
|
+
not remove it.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
Performance is a property of the code as it is written, not a phase that
|
|
4
|
+
happens later. A task whose acceptance criteria say nothing about it still has
|
|
5
|
+
to meet what is below — the same way security and accessibility apply without
|
|
6
|
+
being restated per task.
|
|
7
|
+
|
|
8
|
+
Nothing here names a framework. What is fast on one platform is irrelevant on
|
|
9
|
+
another, and a rule that enumerates today's libraries is wrong within a year.
|
|
10
|
+
**Decide what applies from the code in front of you**, and reach for the
|
|
11
|
+
platform's own skill when you need depth.
|
|
12
|
+
|
|
13
|
+
## Measure before claiming
|
|
14
|
+
|
|
15
|
+
The rule that outranks the rest of this file: **do not assert a performance
|
|
16
|
+
win you have not measured.**
|
|
17
|
+
|
|
18
|
+
An optimisation with no before-and-after is a guess that made the code harder
|
|
19
|
+
to read. State what was measured, what was assumed, and the numbers. When you
|
|
20
|
+
cannot measure, say *unmeasured* — that is the honest word, and
|
|
21
|
+
`.claude/rules/observability.orchestra.md` asks for it everywhere else too.
|
|
22
|
+
|
|
23
|
+
Two failures, and the second is the common one:
|
|
24
|
+
|
|
25
|
+
- optimising something that was never slow
|
|
26
|
+
- *making it slower* while believing otherwise, because the cheap-looking
|
|
27
|
+
change added work on a path that runs more often than the one it saved
|
|
28
|
+
|
|
29
|
+
## Scale is the only question worth asking first
|
|
30
|
+
|
|
31
|
+
Before anything else: **how does this behave when the input grows?**
|
|
32
|
+
|
|
33
|
+
Everything is fast at n=10. The defect that matters is the one that is
|
|
34
|
+
invisible in development and fatal in production — and it is almost always
|
|
35
|
+
one of three shapes:
|
|
36
|
+
|
|
37
|
+
| Shape | Looks like |
|
|
38
|
+
|---|---|
|
|
39
|
+
| Work inside a loop that should have happened once | a query, a request, a file read, a compile, a sort |
|
|
40
|
+
| Complexity that is one order worse than it needs to be | a nested scan where a lookup table would do |
|
|
41
|
+
| Unbounded input | no limit, no pagination, no size cap, no timeout |
|
|
42
|
+
|
|
43
|
+
The first is the same defect whether the loop calls a database, an HTTP API,
|
|
44
|
+
the filesystem, or a native bridge. Naming it "N+1" is a database habit; the
|
|
45
|
+
pattern is general.
|
|
46
|
+
|
|
47
|
+
The third is the one that turns into an incident rather than a slow page.
|
|
48
|
+
Anything that accepts input from outside states its bound.
|
|
49
|
+
|
|
50
|
+
## Do the work once
|
|
51
|
+
|
|
52
|
+
Batch, cache, precompute, reuse. In that order of preference, because each
|
|
53
|
+
step down adds a thing that can be stale.
|
|
54
|
+
|
|
55
|
+
A cache with no stated lifetime and no invalidation path is a correctness bug
|
|
56
|
+
scheduled for later. Say how long it lives and what clears it, next to it.
|
|
57
|
+
|
|
58
|
+
## Do not pay for what you do not use
|
|
59
|
+
|
|
60
|
+
- Load it when it is needed, not at startup — code, data, connections,
|
|
61
|
+
images, permissions.
|
|
62
|
+
- Send the fields the caller asked for.
|
|
63
|
+
- Release what you hold. A subscription, a listener, a timer or a connection
|
|
64
|
+
that outlives its owner is a leak, and leaks read as "gets slower the longer
|
|
65
|
+
it runs" rather than as an error.
|
|
66
|
+
|
|
67
|
+
## Memory is a question of lifetime, not of size
|
|
68
|
+
|
|
69
|
+
Every platform this system builds for has a collector — a tracing GC on the
|
|
70
|
+
JVM, V8 and Hermes, reference counting on Apple platforms. A collector frees
|
|
71
|
+
what is unreachable. So on a managed platform **a leak is never lost memory;
|
|
72
|
+
it is a reference that outlived its purpose** — a listener on a global, a
|
|
73
|
+
closure held in a long-lived map, a cache with no bound, a child holding its
|
|
74
|
+
parent, a timer nobody cleared. The collector cannot fix any of those,
|
|
75
|
+
because by its definition they are not garbage.
|
|
76
|
+
|
|
77
|
+
The tell is the axis the growth follows. Memory proportional to **load** is
|
|
78
|
+
a size problem — bound the input, stream, page. Memory proportional to
|
|
79
|
+
**uptime** is a leak, and it arrives as "gets slower the longer it runs,"
|
|
80
|
+
then as an out-of-memory in the one process that stayed up over the weekend.
|
|
81
|
+
|
|
82
|
+
**Whoever starts a lifetime ends it, in the same place.** A subscription, a
|
|
83
|
+
listener, a timer, a connection, a task: the code that creates it is the
|
|
84
|
+
code that names where it stops — the effect's cleanup, the `deinit`, the
|
|
85
|
+
scope's cancellation. A lifetime with no stated end is a leak with a delay.
|
|
86
|
+
|
|
87
|
+
**Allocation is deferred, not free.** A collector turns an allocation into a
|
|
88
|
+
later pause, and where the budget is a frame or a request, the pause lands
|
|
89
|
+
inside it. An object, closure or array created per frame, per row, or per
|
|
90
|
+
request is the shape to look for — it reads as ordinary code and is paid
|
|
91
|
+
for on the path that runs most often. Hoist it, reuse it, or make it a value
|
|
92
|
+
type.
|
|
93
|
+
|
|
94
|
+
**Test for it where the platform lets you.** A `deinit` that must fire, a
|
|
95
|
+
leak detector in the debug build, a heap that is the same size after ten
|
|
96
|
+
thousand iterations as after ten. A leak found in review is luck; one found
|
|
97
|
+
by a test is a rule.
|
|
98
|
+
|
|
99
|
+
## Data and I/O, wherever the work is served
|
|
100
|
+
|
|
101
|
+
The tier most likely to fail at scale, and the one where the failure arrives
|
|
102
|
+
as an outage rather than as a slow screen.
|
|
103
|
+
|
|
104
|
+
- **One round trip, not one per row.** Fetch the set, join it, or batch it. A
|
|
105
|
+
loop that awaits is the shape to look for — it reads as ordinary code and
|
|
106
|
+
costs a full round trip per iteration.
|
|
107
|
+
- **Index what is filtered, joined and sorted on.** An index nobody measured
|
|
108
|
+
is a write cost with no read benefit, so check the query plan rather than
|
|
109
|
+
adding one by reflex.
|
|
110
|
+
- **Ask for the columns you use.** Selecting everything moves data you throw
|
|
111
|
+
away, through the driver, the network and the serialiser.
|
|
112
|
+
- **Every list endpoint has a hard maximum**, not just a default. A caller
|
|
113
|
+
passing a large enough page size is the same outage as no limit at all.
|
|
114
|
+
- **Every outbound call has a timeout**, and every pool has a ceiling. A
|
|
115
|
+
dependency that hangs should fail your request, not exhaust your process.
|
|
116
|
+
- **Stream what is large.** Buffering a response into memory to send it makes
|
|
117
|
+
the peak proportional to the biggest caller.
|
|
118
|
+
- **Keep slow work off the path that answers.** Hashing, image work, report
|
|
119
|
+
building and anything unbounded belong in a job, not in a request.
|
|
120
|
+
|
|
121
|
+
Where the runtime is single-threaded, a synchronous call on a request path
|
|
122
|
+
stops every other request, not just this one. Blocking work there is a defect
|
|
123
|
+
even when it is fast in isolation.
|
|
124
|
+
|
|
125
|
+
## Rendering and interaction, wherever the UI runs
|
|
126
|
+
|
|
127
|
+
The words differ per platform; the constraints do not.
|
|
128
|
+
|
|
129
|
+
- **Re-render only what changed.** Anything drawn inside a list or under a
|
|
130
|
+
frequently-changing parent is the thing to look at, and allocating a new
|
|
131
|
+
object or closure per frame defeats whatever memoization sits above it.
|
|
132
|
+
- **Identity, not position.** A list keyed by index reuses the wrong element
|
|
133
|
+
with the wrong state when the list reorders. That is a correctness bug that
|
|
134
|
+
merely looks like a performance one.
|
|
135
|
+
- **Long lists are windowed.** Rendering ten thousand rows to show twelve is
|
|
136
|
+
the same waste on every platform.
|
|
137
|
+
- **Never block the thread that draws.** Move parsing, crypto, image work and
|
|
138
|
+
large loops off it.
|
|
139
|
+
|
|
140
|
+
## The shipped size is a budget
|
|
141
|
+
|
|
142
|
+
Whatever the user downloads or installs has a number, and the number only goes
|
|
143
|
+
down. `.claude/rules/testing-standards.orchestra.md` holds the ratchet: it may
|
|
144
|
+
fall and may not rise, and deliberate growth is re-recorded in its own commit
|
|
145
|
+
so it is a decision rather than a drift.
|
|
146
|
+
|
|
147
|
+
## What is enforced
|
|
148
|
+
|
|
149
|
+
Only what can be decided precisely. Complexity, cache lifetime, index choice
|
|
150
|
+
and unbounded input all matter more than the four below and none of them is
|
|
151
|
+
mechanically decidable — a gate that cries wolf is a gate people learn to
|
|
152
|
+
override, so they stay review items above.
|
|
153
|
+
|
|
154
|
+
| Check | Fails on |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `await-in-loop` | a round trip per iteration |
|
|
157
|
+
| `array-index-key` | a list keyed by position |
|
|
158
|
+
| `inline-style-object` | an object allocated in a component kit's render |
|
|
159
|
+
| `unmemoized-kit-component` | a kit component memoizing nothing, where the toolchain is not doing it for you |
|
|
160
|
+
| `effect-without-cleanup` | a React effect that subscribes, listens or schedules and returns no cleanup |
|
|
161
|
+
|
|
162
|
+
These five are concrete because the stacks this system scaffolds are concrete.
|
|
163
|
+
They are the floor, not the definition of performance work.
|
|
164
|
+
|
|
165
|
+
`effect-without-cleanup` sees a lifetime start with no stated end — the
|
|
166
|
+
memory rule above, in the one place a script can see it. An effect that
|
|
167
|
+
genuinely needs none says so in a comment, and the check reads it.
|
|
168
|
+
|
|
169
|
+
`await-in-loop` has an escape hatch, because sequential really is the answer
|
|
170
|
+
sometimes — an ordered write, a rate limit, a cursor that depends on the
|
|
171
|
+
previous page. A comment above the loop saying which one it is clears the
|
|
172
|
+
check. Nothing else does: `Promise.all` over the batch is the fix in every
|
|
173
|
+
other case.
|
|
174
|
+
|
|
175
|
+
**Read the toolchain before applying the third.** React 19's compiler
|
|
176
|
+
memoizes automatically, so on a project with `reactCompiler` enabled,
|
|
177
|
+
hand-written `memo` is redundant noise and the check stays silent. Whether it
|
|
178
|
+
is on is a fact to read from the project, never recalled — the same discipline
|
|
179
|
+
as a library version.
|
|
180
|
+
|
|
181
|
+
## Depth
|
|
182
|
+
|
|
183
|
+
`.claude/skills/code-optimizer/SKILL.md` is for optimising code that already
|
|
184
|
+
exists. `.claude/skills/core-web-vitals/SKILL.md` is web-specific. Neither
|
|
185
|
+
covers writing something performant the first time, which is what this rule is
|
|
186
|
+
for.
|