agentic-team 0.0.0-stage → 6.2.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 +189 -2
- package/bin/build-template.js +208 -0
- package/bin/index.js +1334 -0
- package/bin/platforms.js +510 -0
- package/package.json +41 -4
- package/template/.claude-plugin/plugin.json +10 -0
- package/template/.orchestra/README.md +391 -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 +293 -0
- package/template/.orchestra/roles/orchestrator.md +105 -0
- package/template/.orchestra/roles/product-manager.md +439 -0
- package/template/.orchestra/scripts/compact-diff.awk +45 -0
- package/template/.orchestra/scripts/context.mjs +373 -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 +1113 -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 +925 -0
- package/template/agents/milestone-reviewer.md +65 -0
- package/template/agents/orchestrator.md +105 -0
- package/template/agents/product-manager.md +439 -0
- package/template/agents/re-reviewer.md +25 -0
- package/template/agents/reviewer.md +303 -0
- package/template/agents/scaffolder.md +206 -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 +56 -0
- package/template/commands/hotfix.md +14 -0
- package/template/commands/orchestrator.md +12 -0
- package/template/commands/pm.md +18 -0
- package/template/commands/rewind.md +63 -0
- package/template/commands/setup.md +15 -0
- package/template/commands/start.md +29 -0
- package/template/commands/status.md +11 -0
- package/template/commands/sync.md +76 -0
- package/template/commands/verifier.md +53 -0
- package/template/hooks/audit-context.sh +166 -0
- package/template/hooks/enforce-role.sh +193 -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 +35 -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 +181 -0
- package/template/rules/observability.orchestra.md +121 -0
- package/template/rules/performance.orchestra.md +190 -0
- package/template/rules/phase-limits.orchestra.md +3 -0
- package/template/rules/pull-request.orchestra.md +218 -0
- package/template/rules/role-boundaries.orchestra.md +132 -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 +219 -0
- package/template/rules/tracker-authority.orchestra.md +198 -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 +45 -0
- package/template/skills/web-quality-audit/SKILL.md +47 -0
- package/template/skills/web-testing/SKILL.md +120 -0
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
<!-- platforms: backend -->
|
|
39
|
+
## Structured logging
|
|
40
|
+
|
|
41
|
+
Logs are data, not prose. JSON, one event per line.
|
|
42
|
+
|
|
43
|
+
- Levels used honestly: `error` means someone should look, `warn` means it
|
|
44
|
+
might become an error, `info` is a business event, `debug` is off in
|
|
45
|
+
production.
|
|
46
|
+
- Every server request carries a correlation id, and it appears in every
|
|
47
|
+
log line for that request. Without it you cannot follow a failure across
|
|
48
|
+
services. Take it from the caller's `x-request-id` when present, so a trace
|
|
49
|
+
that started upstream stays one trace, and echo it back so a caller can
|
|
50
|
+
quote it in a bug report.
|
|
51
|
+
- **Redaction is configured in the logger**, not trusted to every call site.
|
|
52
|
+
"Never log a token" holds until the one time somebody logs the whole
|
|
53
|
+
request object, and by then it is in the log store.
|
|
54
|
+
- No secrets, no PII, no full request bodies.
|
|
55
|
+
- One line per request, at completion, where status and duration are known.
|
|
56
|
+
Logging on the way in as well doubles the volume and answers nothing.
|
|
57
|
+
- Never log inside a hot loop.
|
|
58
|
+
- Every line carries the release and environment, so a log and an error
|
|
59
|
+
report line up against the same deploy.
|
|
60
|
+
|
|
61
|
+
<!-- platforms: backend -->
|
|
62
|
+
## Health is two endpoints, not one
|
|
63
|
+
|
|
64
|
+
- **`/health`** — liveness. The process is up. Cheap, no dependencies.
|
|
65
|
+
- **`/ready`** — readiness. Every dependency this service cannot serve
|
|
66
|
+
without is reachable. Answers 503, naming the one that failed.
|
|
67
|
+
|
|
68
|
+
The platform's healthcheck points at **readiness**. On Railway the previous
|
|
69
|
+
deployment keeps serving until the new one answers 200, so a readiness probe
|
|
70
|
+
that returns 200 while the database is unreachable does not merely mislead a
|
|
71
|
+
dashboard — it promotes a broken release.
|
|
72
|
+
|
|
73
|
+
That makes readiness load-bearing infrastructure. Anything missing from the
|
|
74
|
+
checks map is a dependency that can be broken while a release is promoted
|
|
75
|
+
anyway.
|
|
76
|
+
|
|
77
|
+
**A readiness check that cannot fail is worse than none.** There is a test
|
|
78
|
+
that makes it fail; if you add a dependency, add its probe and its failing
|
|
79
|
+
test in the same change.
|
|
80
|
+
|
|
81
|
+
<!-- platforms: backend -->
|
|
82
|
+
## Releasing
|
|
83
|
+
|
|
84
|
+
There is no percentage canary on Railway. What protects a release is:
|
|
85
|
+
|
|
86
|
+
1. the healthcheck gating promotion,
|
|
87
|
+
2. a smoke test against the real URL afterwards, and
|
|
88
|
+
3. putting the last good commit back when that fails.
|
|
89
|
+
|
|
90
|
+
Rollback is the same path as a normal deploy, deliberately — an untested
|
|
91
|
+
recovery path is not a recovery path. The `last-good-deploy` tag moves only
|
|
92
|
+
after the smoke passes, so it always names a release observed working rather
|
|
93
|
+
than one that merely deployed.
|
|
94
|
+
|
|
95
|
+
Confidence comes from being able to undo, not from being sure.
|
|
96
|
+
|
|
97
|
+
## Feature flags
|
|
98
|
+
|
|
99
|
+
A risky change ships dark and is turned off in seconds without a deploy.
|
|
100
|
+
|
|
101
|
+
The flag exists to protect the service, so a lookup must never be able to
|
|
102
|
+
take it down: bounded by a timeout, failures reported and answered with the
|
|
103
|
+
flag's own fallback rather than thrown into a handler. For a kill switch the
|
|
104
|
+
fallback is **false** — if we cannot ask whether a risky path is allowed, the
|
|
105
|
+
answer is no.
|
|
106
|
+
|
|
107
|
+
Flags live in one typed registry. A string literal at a call site is how they
|
|
108
|
+
rot: the flag is deleted, the call still compiles, and nobody finds out until
|
|
109
|
+
the behaviour quietly changes.
|
|
110
|
+
|
|
111
|
+
## Defaults
|
|
112
|
+
|
|
113
|
+
Sentry for error monitoring, `pino` for structured logging in Node. Both
|
|
114
|
+
are defaults, not mandates — substitute freely. What is **not** optional is
|
|
115
|
+
that the capability exists and is wired before release.
|
|
116
|
+
|
|
117
|
+
Configure the provider in `.orchestra/config.yml` under `observability`.
|
|
118
|
+
|
|
119
|
+
`observability.require_before_release` (true by default) is checked by lead
|
|
120
|
+
at milestone completion. Setting it false defers the requirement — it does
|
|
121
|
+
not remove it.
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
<!-- platforms: web, expo, ios, android -->
|
|
126
|
+
## Rendering and interaction, wherever the UI runs
|
|
127
|
+
|
|
128
|
+
The words differ per platform; the constraints do not.
|
|
129
|
+
|
|
130
|
+
- **Re-render only what changed.** Anything drawn inside a list or under a
|
|
131
|
+
frequently-changing parent is the thing to look at, and allocating a new
|
|
132
|
+
object or closure per frame defeats whatever memoization sits above it.
|
|
133
|
+
- **Identity, not position.** A list keyed by index reuses the wrong element
|
|
134
|
+
with the wrong state when the list reorders. That is a correctness bug that
|
|
135
|
+
merely looks like a performance one.
|
|
136
|
+
- **Long lists are windowed.** Rendering ten thousand rows to show twelve is
|
|
137
|
+
the same waste on every platform.
|
|
138
|
+
- **Never block the thread that draws.** Move parsing, crypto, image work and
|
|
139
|
+
large loops off it.
|
|
140
|
+
|
|
141
|
+
<!-- platforms: web -->
|
|
142
|
+
## The shipped size is a budget
|
|
143
|
+
|
|
144
|
+
Whatever the user downloads or installs has a number, and the number only goes
|
|
145
|
+
down. `.claude/rules/testing-standards.orchestra.md` holds the ratchet: it may
|
|
146
|
+
fall and may not rise, and deliberate growth is re-recorded in its own commit
|
|
147
|
+
so it is a decision rather than a drift.
|
|
148
|
+
|
|
149
|
+
<!-- platforms: web, expo, backend -->
|
|
150
|
+
## What is enforced
|
|
151
|
+
|
|
152
|
+
Only what can be decided precisely. Complexity, cache lifetime, index choice
|
|
153
|
+
and unbounded input all matter more than the four below and none of them is
|
|
154
|
+
mechanically decidable — a gate that cries wolf is a gate people learn to
|
|
155
|
+
override, so they stay review items above.
|
|
156
|
+
|
|
157
|
+
| Check | Fails on |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `await-in-loop` | a round trip per iteration |
|
|
160
|
+
| `array-index-key` | a list keyed by position |
|
|
161
|
+
| `inline-style-object` | an object allocated in a component kit's render |
|
|
162
|
+
| `unmemoized-kit-component` | a kit component memoizing nothing, where the toolchain is not doing it for you |
|
|
163
|
+
| `effect-without-cleanup` | a React effect that subscribes, listens or schedules and returns no cleanup |
|
|
164
|
+
|
|
165
|
+
These five are concrete because the stacks this system scaffolds are concrete.
|
|
166
|
+
They are the floor, not the definition of performance work.
|
|
167
|
+
|
|
168
|
+
`effect-without-cleanup` sees a lifetime start with no stated end — the
|
|
169
|
+
memory rule above, in the one place a script can see it. An effect that
|
|
170
|
+
genuinely needs none says so in a comment, and the check reads it.
|
|
171
|
+
|
|
172
|
+
`await-in-loop` has an escape hatch, because sequential really is the answer
|
|
173
|
+
sometimes — an ordered write, a rate limit, a cursor that depends on the
|
|
174
|
+
previous page. A comment above the loop saying which one it is clears the
|
|
175
|
+
check. Nothing else does: `Promise.all` over the batch is the fix in every
|
|
176
|
+
other case.
|
|
177
|
+
|
|
178
|
+
**Read the toolchain before applying the third.** React 19's compiler
|
|
179
|
+
memoizes automatically, so on a project with `reactCompiler` enabled,
|
|
180
|
+
hand-written `memo` is redundant noise and the check stays silent. Whether it
|
|
181
|
+
is on is a fact to read from the project, never recalled — the same discipline
|
|
182
|
+
as a library version.
|
|
183
|
+
|
|
184
|
+
## Depth
|
|
185
|
+
|
|
186
|
+
`.claude/skills/code-optimizer/SKILL.md` is for optimising code that already
|
|
187
|
+
exists. `.claude/skills/core-web-vitals/SKILL.md` is web-specific, and installed
|
|
188
|
+
only for web. Neither
|
|
189
|
+
covers writing something performant the first time, which is what this rule is
|
|
190
|
+
for.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- ".github/**"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pull Requests
|
|
7
|
+
|
|
8
|
+
**One task, one PR.** Always — there is no mode in which work reaches the
|
|
9
|
+
user any other way.
|
|
10
|
+
|
|
11
|
+
One per milestone is the failure this rule exists to prevent, and it happened
|
|
12
|
+
anyway: the diff spans every phase, no reviewer can hold it, and the design
|
|
13
|
+
table has one row while the milestone has ten screens — so the screenshots get
|
|
14
|
+
dropped and nobody notices. A PR is one task, one commit, one screen.
|
|
15
|
+
|
|
16
|
+
## Body
|
|
17
|
+
|
|
18
|
+
Use the repo's PR template. Keep it short — the reader wants to know what
|
|
19
|
+
changed and whether it is safe, not how you got there.
|
|
20
|
+
|
|
21
|
+
- **What** — one or two sentences
|
|
22
|
+
- **Why** — link the milestone or task
|
|
23
|
+
- **Design** — required for any UI change: expected and implemented, side by
|
|
24
|
+
side, in the template's table. Any deliberate difference is stated with
|
|
25
|
+
its reason. "Close enough" is not a reason.
|
|
26
|
+
|
|
27
|
+
One PR is one task is one screen, so the table has one row and an empty
|
|
28
|
+
cell has no excuse. The implemented image is the task's `design_capture`,
|
|
29
|
+
committed under `designs/.captures/` **in this branch** — `gh` cannot
|
|
30
|
+
upload an image, so a screenshot outside the branch cannot appear here. If
|
|
31
|
+
it could not be captured, the cell carries the reason. An empty cell reads
|
|
32
|
+
as a pass nobody observed.
|
|
33
|
+
- **Verification** — the checklist, actually checked
|
|
34
|
+
|
|
35
|
+
Do not paste the diff into the description. Do not narrate your process.
|
|
36
|
+
Do not paste the acceptance criteria. Read the body back as the colleague
|
|
37
|
+
who will review it: a table of files, a `path:line`, a list of what was
|
|
38
|
+
checked — those came from a tool, and the reader wanted a person.
|
|
39
|
+
|
|
40
|
+
## Mechanism
|
|
41
|
+
|
|
42
|
+
The `gh` CLI. Check it before relying on it:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gh auth status # not authenticated → stop and tell the user, do not work around it
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Open with a body file, never an inline string — the body contains newlines,
|
|
49
|
+
a table, and backticks:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gh pr create --base <delivery.base_branch> --title "#<task-id> - <subject>" --body-file .git/PR_BODY.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Naming
|
|
56
|
+
|
|
57
|
+
| | Shape | Example |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Branch | `<git-user>/<task-id>-<slug>` | `sulhadin/123-add-login` |
|
|
60
|
+
| PR title | `#<task-id> - <subject>` | `#123 - Add login` |
|
|
61
|
+
|
|
62
|
+
The id goes in both so the board finds the work without anyone pasting a link,
|
|
63
|
+
and it is the tracker's own id — a GitHub issue number or a ClickUp task id. With `tracker.provider: none` there is no id, so the branch drops
|
|
64
|
+
that segment and the title is the subject alone.
|
|
65
|
+
|
|
66
|
+
**The title is not conventional-commit form**, and that is deliberate. A task
|
|
67
|
+
PR accumulates commits — the implementation, then whatever review asked for —
|
|
68
|
+
and each of those *is* conventional. The squash collapses them, and its
|
|
69
|
+
subject is the one place the board's id has to appear.
|
|
70
|
+
|
|
71
|
+
That squashed subject still passes the scaffolded `commit-msg` hook, which
|
|
72
|
+
accepts `#<task-id> - <subject>` alongside the conventional form for exactly
|
|
73
|
+
this case. Without that, the delivery flow would produce the one commit the
|
|
74
|
+
repo's own gate rejects.
|
|
75
|
+
|
|
76
|
+
Record the returned URL. Lead reports it and stores it in the phase's note (`context.mjs phase … "pr: <url>"`), so a
|
|
77
|
+
later session can find the PR without searching.
|
|
78
|
+
|
|
79
|
+
Reading the change is `gh pr diff <n>`, and a diff read straight into
|
|
80
|
+
context spends four lines per file on the `diff --git`/`index`/`---`/`+++`
|
|
81
|
+
header. Pipe it — or any `git diff` / `git show` — through
|
|
82
|
+
`.orchestra/scripts/compact-diff.awk`; it replaces those four with one and
|
|
83
|
+
touches nothing else, so every hunk line comes through as git wrote it.
|
|
84
|
+
|
|
85
|
+
Reading review feedback is also `gh`, and it is two different calls — a
|
|
86
|
+
review body and inline line comments are separate objects, and reading only
|
|
87
|
+
one loses half the feedback:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
gh pr view <n> --json reviews,comments
|
|
91
|
+
gh api repos/{owner}/{repo}/pulls/<n>/comments
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Never poll in a loop. Fetch when the user says the review is ready.
|
|
95
|
+
|
|
96
|
+
## Review cycle
|
|
97
|
+
|
|
98
|
+
The review happens **on the pull request**, not in a file beside it. Findings
|
|
99
|
+
land inline, on the line they are about, and the engineer answers them on the
|
|
100
|
+
thread. Nothing about the conversation lives only on somebody's disk.
|
|
101
|
+
|
|
102
|
+
The loop is: reviewer comments → a fresh engineer with the phase's identity
|
|
103
|
+
fixes, replies and resolves → re-review. Max rounds is `thresholds.stuck_retry_limit`,
|
|
104
|
+
counted by `.claude/hooks/review-rounds.sh`. On exhaustion the user decides
|
|
105
|
+
whether another round is worth it — say what is still open rather than going
|
|
106
|
+
round again.
|
|
107
|
+
|
|
108
|
+
**An engineer answers, not lead.** It reads the code, the diff and the thread
|
|
109
|
+
before it replies. A thread answered from a summary is a thread answered
|
|
110
|
+
wrong. It is a new engineer each round rather than the one that wrote the
|
|
111
|
+
code, because a resumed agent re-reads its whole history on every call.
|
|
112
|
+
|
|
113
|
+
### Posting the review — one call, every finding
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
cat > .git/REVIEW.json <<'JSON'
|
|
117
|
+
{
|
|
118
|
+
"event": "COMMENT",
|
|
119
|
+
"body": "changes-requested — two or three sentences",
|
|
120
|
+
"comments": [
|
|
121
|
+
{ "path": "src/auth/session.ts", "line": 42, "side": "RIGHT",
|
|
122
|
+
"body": "[blocking] The session id is compared with ===, which is not constant time.\n\nUse `crypto.timingSafeEqual` over the two buffers." }
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
JSON
|
|
126
|
+
gh api repos/{owner}/{repo}/pulls/<n>/reviews --input .git/REVIEW.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
One review, not one call per finding — each separate call is a separate
|
|
130
|
+
notification.
|
|
131
|
+
|
|
132
|
+
**`COMMENT`, never `APPROVE` or `REQUEST_CHANGES`.** The same account authored
|
|
133
|
+
the pull request and GitHub refuses to let an author approve their own.
|
|
134
|
+
|
|
135
|
+
So the verdict travels as a **label**, which is the part GitHub will let the
|
|
136
|
+
author set and the part everything else can read — the board, a filter, a
|
|
137
|
+
human scanning the list, and the next agent.
|
|
138
|
+
|
|
139
|
+
| Label | Means |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `review:changes-requested` | unresolved findings; the engineer is up |
|
|
142
|
+
| `review:approved` | nothing blocking; the task can move on |
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# once per repo, idempotent — the scaffold does this
|
|
146
|
+
gh label create review:changes-requested -c D93F0B -d "Reviewer found blocking issues" -f
|
|
147
|
+
gh label create review:approved -c 0E8A16 -d "Reviewer found nothing blocking" -f
|
|
148
|
+
|
|
149
|
+
# one state at a time, so the two can never both be on
|
|
150
|
+
gh pr edit <n> --add-label review:changes-requested --remove-label review:approved
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The label is the handshake between reviewer and engineer, and it is what lead
|
|
154
|
+
reads to decide the next move. The review body still carries the summary, but
|
|
155
|
+
a body has to be parsed and a label does not — and a run that dies between
|
|
156
|
+
rounds can pick up exactly where it left off by asking the pull request what
|
|
157
|
+
state it is in.
|
|
158
|
+
|
|
159
|
+
Every inline comment names the problem in a sentence and says what to do
|
|
160
|
+
instead. A comment that only objects makes the next agent guess.
|
|
161
|
+
|
|
162
|
+
### Reading the threads
|
|
163
|
+
|
|
164
|
+
REST returns comments; only GraphQL carries the thread id and whether it is
|
|
165
|
+
resolved, and both are needed to answer one.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
gh api graphql -f query='
|
|
169
|
+
query($o:String!,$r:String!,$n:Int!) {
|
|
170
|
+
repository(owner:$o, name:$r) { pullRequest(number:$n) {
|
|
171
|
+
reviewThreads(first:50) { nodes {
|
|
172
|
+
id isResolved path line
|
|
173
|
+
comments(first:20) { nodes { body author { login } } }
|
|
174
|
+
} } } } }' -f o={owner} -f r={repo} -F n=<n>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Answering and resolving
|
|
178
|
+
|
|
179
|
+
Reply on the thread, then resolve it. A few words on what was done and the
|
|
180
|
+
commit id — the diff says the rest.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
gh api graphql -f query='
|
|
184
|
+
mutation($t:ID!, $b:String!) {
|
|
185
|
+
addPullRequestReviewThreadReply(input:{pullRequestReviewThreadId:$t, body:$b}) {
|
|
186
|
+
comment { id } } }' -f t=<thread-id> -f b="Fixed with timingSafeEqual — a1b2c3d"
|
|
187
|
+
|
|
188
|
+
gh api graphql -f query='
|
|
189
|
+
mutation($t:ID!) {
|
|
190
|
+
resolveReviewThread(input:{threadId:$t}) { thread { isResolved } } }' -f t=<thread-id>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Resolve only what you actually addressed. A thread resolved without a fix is
|
|
194
|
+
worse than one left open, because the next reader believes it.
|
|
195
|
+
|
|
196
|
+
If you did something other than what was asked, say why in the reply. If you
|
|
197
|
+
disagree, say so and leave the thread open — resolving a disagreement is not
|
|
198
|
+
answering it.
|
|
199
|
+
|
|
200
|
+
Never poll in a loop. A human review is fetched when the user says it is
|
|
201
|
+
ready.
|
|
202
|
+
|
|
203
|
+
## Who merges
|
|
204
|
+
|
|
205
|
+
Decided by the `--auto` flag on `/orchestra:start`, and by nothing else.
|
|
206
|
+
|
|
207
|
+
| | Who merges | What follows |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| normal | **the human**, squashing | lead waits; a task whose `depends_on` is unmerged does not start |
|
|
210
|
+
| `--auto` | lead, squash-merging | commit link posted on the tracker task, status moved, branch deleted |
|
|
211
|
+
|
|
212
|
+
Outside `--auto`, **never merge your own PR unless explicitly told to.**
|
|
213
|
+
|
|
214
|
+
**Never stack a branch on an unmerged branch** to avoid waiting. The human
|
|
215
|
+
squashes, so the parent's commits reappear in the child's diff until it is
|
|
216
|
+
rebased, and an agent doing that on every task will get it wrong. Waiting is
|
|
217
|
+
the honest cost of reviewing per task — say what is open and what is blocked
|
|
218
|
+
rather than stalling silently.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- ".orchestra/**"
|
|
4
|
+
- ".claude/**"
|
|
5
|
+
- "CLAUDE.md"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Role Boundary Enforcement
|
|
9
|
+
|
|
10
|
+
Before writing ANY file, check your role's ownership scope.
|
|
11
|
+
|
|
12
|
+
## Ownership Map
|
|
13
|
+
|
|
14
|
+
| Role | Can Write | Everything Else |
|
|
15
|
+
|------|-----------|----------------|
|
|
16
|
+
| Orchestrator | `.orchestra/roles/`, `.orchestra/config.yml`, `.orchestra/README.md`, `.orchestra/blueprints/`, `.orchestra/stacks/`, `.orchestra/trackers/`, `.orchestra/scripts/`, `.claude/agents/`, `.claude/hooks/`, `.claude/rules/*.orchestra.md`, `.claude/skills/*/SKILL.md`, `.claude/commands/orchestra/`, `CLAUDE.md`, `docs/` | Refuse |
|
|
17
|
+
| PM | `.orchestra/milestones/*` (prd, milestone, grooming, phases) | Refuse |
|
|
18
|
+
| Lead | `.orchestra/milestones/*/{context,decisions,journal}.md`, through `.orchestra/scripts/context.mjs` | Refuse |
|
|
19
|
+
| Scaffolder | `.orchestra/config.yml` (verification, tracker, observability, `delivery.base_branch`, `project.platforms` keys only), scaffolded app files, repo config | Refuse |
|
|
20
|
+
| Tracker | Tracker id frontmatter in `.orchestra/milestones/*` | Refuse |
|
|
21
|
+
| Sub-agents | Only what phase `## Scope` defines | Refuse |
|
|
22
|
+
| Reviewer | Review verdict in phase file only | Refuse |
|
|
23
|
+
|
|
24
|
+
Some entries on the Orchestrator's list are **not** blocked by the hook below,
|
|
25
|
+
each for its own reason. `.orchestra/blueprints/` is where `/orchestra
|
|
26
|
+
blueprint` writes, `.orchestra/config.yml` is merged rather than overwritten,
|
|
27
|
+
and `CLAUDE.md` keeps everything outside the orchestra section for the user —
|
|
28
|
+
the installer preserves all three on upgrade, so an edit there is not lost
|
|
29
|
+
work. `docs/` is not installed into a project at all; it is this repo's own
|
|
30
|
+
documentation, so in an installed copy there is nothing under that name to
|
|
31
|
+
protect and blocking it would only block the user's own docs.
|
|
32
|
+
|
|
33
|
+
They stay on the ownership map because a role should still not reach into
|
|
34
|
+
them. They are simply not things a hook can decide.
|
|
35
|
+
|
|
36
|
+
## Enforced, not asked for
|
|
37
|
+
|
|
38
|
+
Three `PreToolUse` hooks. The first blocks Write and Edit against installed
|
|
39
|
+
system files. The second holds a session inside the role it declared. The
|
|
40
|
+
third caps review rounds per phase.
|
|
41
|
+
|
|
42
|
+
### The system-file hook
|
|
43
|
+
|
|
44
|
+
Two questions decide it, and neither is "which role is running": a slash
|
|
45
|
+
command is a markdown prompt and cannot export anything a hook could read on
|
|
46
|
+
its own, so a guard built only on the role would never fire.
|
|
47
|
+
|
|
48
|
+
**Which repo is this?** In the agentic-team repo these files are source, and
|
|
49
|
+
the hook allows everything. Elsewhere they are build output.
|
|
50
|
+
|
|
51
|
+
**Did Orchestra put this file here?** `.orchestra/.installed.json` records
|
|
52
|
+
every file the installer wrote, and the hook blocks exactly that list.
|
|
53
|
+
|
|
54
|
+
The manifest exists because the answer stopped being guessable. Rules carry a
|
|
55
|
+
`.orchestra.md` suffix, so a system rule can be told from one you wrote.
|
|
56
|
+
Skills lost that marker when they became directories, and a list of globs
|
|
57
|
+
cannot tell a shipped skill from yours — so it blocked both, including the
|
|
58
|
+
extension point `docs/skills.md` tells you to use, and including a stack's
|
|
59
|
+
own `apps/<name>/.claude/` pack. The upgrade had the same blindness from the
|
|
60
|
+
other side: a skill this project stops shipping looked like one you wrote, and
|
|
61
|
+
survived forever. The manifest answers both, and retires what a new version no
|
|
62
|
+
longer ships.
|
|
63
|
+
|
|
64
|
+
**`.orchestra/config.yml` is never blocked.** It is merged on upgrade rather
|
|
65
|
+
than overwritten, so an edit survives — and `/orchestra:setup` writes its
|
|
66
|
+
verification keys, which `.orchestra/README.md` has always documented as an
|
|
67
|
+
exception the hook did not implement.
|
|
68
|
+
|
|
69
|
+
Neither are `.orchestra/milestones/`, `blueprints/`, roles you create, or
|
|
70
|
+
skills and rules you write: none of them is in the manifest, because the
|
|
71
|
+
installer preserves them.
|
|
72
|
+
|
|
73
|
+
When it blocks: make the change in the agentic-team repo and reinstall, or —
|
|
74
|
+
if it is genuinely project-specific — put it in the app's own `CLAUDE.md`, a
|
|
75
|
+
project skill of your own, or `config.yml`, none of which is overwritten.
|
|
76
|
+
|
|
77
|
+
### The role hook
|
|
78
|
+
|
|
79
|
+
The ownership map above was prose, and a PM opened a pull request. So the
|
|
80
|
+
role is now something a hook can read: each role command has the agent run
|
|
81
|
+
one Bash line — `true # orchestra-role=product-manager` — and the hook, which
|
|
82
|
+
sees every Bash call together with the session id Claude Code passes it,
|
|
83
|
+
records the pair under `.orchestra/.sessions/`. Every later call in that
|
|
84
|
+
session is judged against it. Two terminals are two sessions; the PM's marker
|
|
85
|
+
never touches lead.
|
|
86
|
+
|
|
87
|
+
What it refuses for the PM: a Write or Edit outside `.orchestra/milestones/`;
|
|
88
|
+
`gh pr` in any form; a new branch; merge, rebase, cherry-pick, a hard reset,
|
|
89
|
+
a force push; `git commit -a`; `git add` of anything but milestone files;
|
|
90
|
+
any `Agent` launch other than `milestone-reviewer` and `tracker`; any
|
|
91
|
+
`Workflow`; and a ClickUp task write carrying a status other than the
|
|
92
|
+
backlog one. The last three came from one afternoon: the word "ultracode" in
|
|
93
|
+
a message told the platform to use the Workflow tool, and a PM whose role
|
|
94
|
+
said "cannot review code" ran a multi-agent code review, recommended itself
|
|
95
|
+
for it, and opened the task straight into `in progress`.
|
|
96
|
+
|
|
97
|
+
Lead is held to one rule: phase work starts inside a milestone agent. An
|
|
98
|
+
`engineer-*`, `reviewer` or `re-reviewer` launched from the lead session
|
|
99
|
+
itself is refused; one launched by a milestone agent passes, because a call
|
|
100
|
+
from a sub-agent carries `agent_id` and one from the session does not —
|
|
101
|
+
verified against a live payload. A hotfix engineer whose prompt starts with
|
|
102
|
+
`Hotfix:` passes. One run kept two milestones in the lead session and grew
|
|
103
|
+
to 738 calls and a 925k-token context. Sub-agents share the session id, so
|
|
104
|
+
the role marker reaches them; lead's own sub-agents still write code, which
|
|
105
|
+
is why nothing else is restricted. The Orchestrator is recorded and not
|
|
106
|
+
restricted.
|
|
107
|
+
|
|
108
|
+
Two things are structural rather than hook-judged, because they do not
|
|
109
|
+
depend on the role. `Workflow` is denied in the project's `settings.json`;
|
|
110
|
+
no role runs one. The reviewer, the re-reviewer and the milestone reviewer carry
|
|
111
|
+
`disallowedTools: [Write, Edit, NotebookEdit]` in their frontmatter; they
|
|
112
|
+
review, and Claude Code removes the tools rather than asking them not to.
|
|
113
|
+
|
|
114
|
+
No marker means no role, and nothing is refused. That is discussion mode.
|
|
115
|
+
|
|
116
|
+
When it blocks, the message says why. Say so to the user and stop; the
|
|
117
|
+
answer is never to find a command the pattern does not match.
|
|
118
|
+
|
|
119
|
+
### The review-round hook
|
|
120
|
+
|
|
121
|
+
`.claude/hooks/review-rounds.sh` counts reviewer launches per phase, from the
|
|
122
|
+
`Phase:` line lead puts in every reviewer prompt, in
|
|
123
|
+
`.orchestra/.sessions/review-rounds.json`. Past `thresholds.stuck_retry_limit`
|
|
124
|
+
it refuses the launch until the prompt quotes the user's approval. The cap
|
|
125
|
+
was prose, and a run read "loop until clean" as overriding it.
|
|
126
|
+
|
|
127
|
+
## Rules
|
|
128
|
+
|
|
129
|
+
- Check what file you'd write to, not what the user said
|
|
130
|
+
- If user insists → still refuse: "Outside my scope as {role}. Use /orchestra:{command}."
|
|
131
|
+
- Discussion mode (no role) → no restrictions
|
|
132
|
+
- Reading → always allowed
|