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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -2
  3. package/bin/build-template.js +208 -0
  4. package/bin/index.js +1139 -0
  5. package/package.json +41 -4
  6. package/template/.claude-plugin/plugin.json +10 -0
  7. package/template/.orchestra/README.md +401 -0
  8. package/template/.orchestra/blueprints/README.md +87 -0
  9. package/template/.orchestra/blueprints/api-only.md +46 -0
  10. package/template/.orchestra/blueprints/component-crud-resource.md +25 -0
  11. package/template/.orchestra/blueprints/saas-starter.md +69 -0
  12. package/template/.orchestra/config.yml +277 -0
  13. package/template/.orchestra/roles/orchestrator.md +105 -0
  14. package/template/.orchestra/roles/product-manager.md +438 -0
  15. package/template/.orchestra/scripts/compact-diff.awk +45 -0
  16. package/template/.orchestra/stacks/README.md +81 -0
  17. package/template/.orchestra/stacks/_lib/common.sh +3095 -0
  18. package/template/.orchestra/stacks/_repo/setup.sh +1109 -0
  19. package/template/.orchestra/stacks/backend-hono/claude/commands/endpoint.md +13 -0
  20. package/template/.orchestra/stacks/backend-hono/claude/skills/api-endpoint/SKILL.md +111 -0
  21. package/template/.orchestra/stacks/backend-hono/claude/skills/testing/SKILL.md +75 -0
  22. package/template/.orchestra/stacks/backend-hono/setup.sh +5945 -0
  23. package/template/.orchestra/stacks/backend-hono/stack.md +195 -0
  24. package/template/.orchestra/stacks/mobile-android-native/claude/commands/component.md +13 -0
  25. package/template/.orchestra/stacks/mobile-android-native/claude/skills/design-component/SKILL.md +58 -0
  26. package/template/.orchestra/stacks/mobile-android-native/setup.sh +607 -0
  27. package/template/.orchestra/stacks/mobile-android-native/stack.md +138 -0
  28. package/template/.orchestra/stacks/mobile-expo/claude/commands/component.md +12 -0
  29. package/template/.orchestra/stacks/mobile-expo/claude/skills/design-component/SKILL.md +92 -0
  30. package/template/.orchestra/stacks/mobile-expo/setup.sh +470 -0
  31. package/template/.orchestra/stacks/mobile-expo/stack.md +141 -0
  32. package/template/.orchestra/stacks/mobile-ios-native/claude/commands/component.md +12 -0
  33. package/template/.orchestra/stacks/mobile-ios-native/claude/skills/design-component/SKILL.md +58 -0
  34. package/template/.orchestra/stacks/mobile-ios-native/setup.sh +468 -0
  35. package/template/.orchestra/stacks/mobile-ios-native/stack.md +130 -0
  36. package/template/.orchestra/stacks/web-nextjs/claude/commands/component.md +12 -0
  37. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-component/SKILL.md +100 -0
  38. package/template/.orchestra/stacks/web-nextjs/claude/skills/design-tokens/SKILL.md +61 -0
  39. package/template/.orchestra/stacks/web-nextjs/setup.sh +128 -0
  40. package/template/.orchestra/stacks/web-nextjs/stack.md +170 -0
  41. package/template/.orchestra/stacks/web-vite-react/claude/commands/component.md +12 -0
  42. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-component/SKILL.md +100 -0
  43. package/template/.orchestra/stacks/web-vite-react/claude/skills/design-tokens/SKILL.md +61 -0
  44. package/template/.orchestra/stacks/web-vite-react/setup.sh +185 -0
  45. package/template/.orchestra/stacks/web-vite-react/stack.md +177 -0
  46. package/template/.orchestra/trackers/github.sh +467 -0
  47. package/template/CLAUDE.md +54 -0
  48. package/template/agents/architect.md +83 -0
  49. package/template/agents/engineer-high.md +17 -0
  50. package/template/agents/engineer-low.md +17 -0
  51. package/template/agents/engineer-max.md +17 -0
  52. package/template/agents/engineer-medium.md +17 -0
  53. package/template/agents/engineer-xhigh.md +17 -0
  54. package/template/agents/lead.md +870 -0
  55. package/template/agents/milestone-reviewer.md +63 -0
  56. package/template/agents/orchestrator.md +105 -0
  57. package/template/agents/product-manager.md +438 -0
  58. package/template/agents/reviewer.md +301 -0
  59. package/template/agents/scaffolder.md +180 -0
  60. package/template/agents/tracker.md +202 -0
  61. package/template/commands/blueprint.md +23 -0
  62. package/template/commands/create-role.md +64 -0
  63. package/template/commands/export.md +45 -0
  64. package/template/commands/help.md +55 -0
  65. package/template/commands/hotfix.md +12 -0
  66. package/template/commands/orchestrator.md +12 -0
  67. package/template/commands/pm.md +18 -0
  68. package/template/commands/rewind.md +61 -0
  69. package/template/commands/setup.md +14 -0
  70. package/template/commands/start.md +29 -0
  71. package/template/commands/status.md +11 -0
  72. package/template/commands/sync.md +73 -0
  73. package/template/commands/verifier.md +53 -0
  74. package/template/hooks/audit-context.sh +153 -0
  75. package/template/hooks/enforce-role.sh +175 -0
  76. package/template/hooks/protect-system-files.sh +123 -0
  77. package/template/hooks/review-rounds.sh +172 -0
  78. package/template/rules/acceptance-check.orchestra.md +11 -0
  79. package/template/rules/analytics-kpi.orchestra.md +56 -0
  80. package/template/rules/code-standards.orchestra.md +34 -0
  81. package/template/rules/comments.orchestra.md +77 -0
  82. package/template/rules/commit-format.orchestra.md +20 -0
  83. package/template/rules/dependency-upgrade.orchestra.md +112 -0
  84. package/template/rules/design-parity.orchestra.md +170 -0
  85. package/template/rules/observability.orchestra.md +118 -0
  86. package/template/rules/performance.orchestra.md +186 -0
  87. package/template/rules/phase-limits.orchestra.md +3 -0
  88. package/template/rules/pull-request.orchestra.md +213 -0
  89. package/template/rules/role-boundaries.orchestra.md +117 -0
  90. package/template/rules/security.orchestra.md +106 -0
  91. package/template/rules/stuck-detection.orchestra.md +25 -0
  92. package/template/rules/testing-standards.orchestra.md +214 -0
  93. package/template/rules/tracker-authority.orchestra.md +190 -0
  94. package/template/rules/verification-gate.orchestra.md +56 -0
  95. package/template/skills/accessibility/SKILL.md +31 -0
  96. package/template/skills/android-testing/SKILL.md +112 -0
  97. package/template/skills/architecture/SKILL.md +175 -0
  98. package/template/skills/auth-setup/SKILL.md +105 -0
  99. package/template/skills/best-practices/SKILL.md +29 -0
  100. package/template/skills/code-optimizer/SKILL.md +27 -0
  101. package/template/skills/core-web-vitals/SKILL.md +32 -0
  102. package/template/skills/crud-api/SKILL.md +32 -0
  103. package/template/skills/debug/SKILL.md +28 -0
  104. package/template/skills/deployment/SKILL.md +104 -0
  105. package/template/skills/error-handling/SKILL.md +154 -0
  106. package/template/skills/frontend-design/SKILL.md +30 -0
  107. package/template/skills/fullstack-infrastructure/SKILL.md +108 -0
  108. package/template/skills/ios-testing/SKILL.md +100 -0
  109. package/template/skills/mobile-testing/SKILL.md +102 -0
  110. package/template/skills/observability/SKILL.md +91 -0
  111. package/template/skills/project-structure/SKILL.md +114 -0
  112. package/template/skills/push-notifications/SKILL.md +146 -0
  113. package/template/skills/react-best-practices/SKILL.md +31 -0
  114. package/template/skills/realtime/SKILL.md +146 -0
  115. package/template/skills/review/SKILL.md +82 -0
  116. package/template/skills/testing/SKILL.md +44 -0
  117. package/template/skills/web-quality-audit/SKILL.md +47 -0
  118. 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.
@@ -0,0 +1,3 @@
1
+ # Phase Limits
2
+
3
+ **Scope guard:** Working on something NOT in phase acceptance criteria → STOP. Note in context.md, don't implement.