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,213 @@
|
|
|
1
|
+
# Pull Requests
|
|
2
|
+
|
|
3
|
+
**One task, one PR.** Always — there is no mode in which work reaches the
|
|
4
|
+
user any other way.
|
|
5
|
+
|
|
6
|
+
One per milestone is the failure this rule exists to prevent, and it happened
|
|
7
|
+
anyway: the diff spans every phase, no reviewer can hold it, and the design
|
|
8
|
+
table has one row while the milestone has ten screens — so the screenshots get
|
|
9
|
+
dropped and nobody notices. A PR is one task, one commit, one screen.
|
|
10
|
+
|
|
11
|
+
## Body
|
|
12
|
+
|
|
13
|
+
Use the repo's PR template. Keep it short — the reader wants to know what
|
|
14
|
+
changed and whether it is safe, not how you got there.
|
|
15
|
+
|
|
16
|
+
- **What** — one or two sentences
|
|
17
|
+
- **Why** — link the milestone or task
|
|
18
|
+
- **Design** — required for any UI change: expected and implemented, side by
|
|
19
|
+
side, in the template's table. Any deliberate difference is stated with
|
|
20
|
+
its reason. "Close enough" is not a reason.
|
|
21
|
+
|
|
22
|
+
One PR is one task is one screen, so the table has one row and an empty
|
|
23
|
+
cell has no excuse. The implemented image is the task's `design_capture`,
|
|
24
|
+
committed under `designs/.captures/` **in this branch** — `gh` cannot
|
|
25
|
+
upload an image, so a screenshot outside the branch cannot appear here. If
|
|
26
|
+
it could not be captured, the cell carries the reason. An empty cell reads
|
|
27
|
+
as a pass nobody observed.
|
|
28
|
+
- **Verification** — the checklist, actually checked
|
|
29
|
+
|
|
30
|
+
Do not paste the diff into the description. Do not narrate your process.
|
|
31
|
+
Do not paste the acceptance criteria. Read the body back as the colleague
|
|
32
|
+
who will review it: a table of files, a `path:line`, a list of what was
|
|
33
|
+
checked — those came from a tool, and the reader wanted a person.
|
|
34
|
+
|
|
35
|
+
## Mechanism
|
|
36
|
+
|
|
37
|
+
The `gh` CLI. Check it before relying on it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gh auth status # not authenticated → stop and tell the user, do not work around it
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Open with a body file, never an inline string — the body contains newlines,
|
|
44
|
+
a table, and backticks:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
gh pr create --title "#<task-id> - <subject>" --body-file .git/PR_BODY.md
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Naming
|
|
51
|
+
|
|
52
|
+
| | Shape | Example |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Branch | `<git-user>/<task-id>-<slug>` | `sulhadin/123-add-login` |
|
|
55
|
+
| PR title | `#<task-id> - <subject>` | `#123 - Add login` |
|
|
56
|
+
|
|
57
|
+
The id goes in both so the board finds the work without anyone pasting a link,
|
|
58
|
+
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
|
|
59
|
+
that segment and the title is the subject alone.
|
|
60
|
+
|
|
61
|
+
**The title is not conventional-commit form**, and that is deliberate. A task
|
|
62
|
+
PR accumulates commits — the implementation, then whatever review asked for —
|
|
63
|
+
and each of those *is* conventional. The squash collapses them, and its
|
|
64
|
+
subject is the one place the board's id has to appear.
|
|
65
|
+
|
|
66
|
+
That squashed subject still passes the scaffolded `commit-msg` hook, which
|
|
67
|
+
accepts `#<task-id> - <subject>` alongside the conventional form for exactly
|
|
68
|
+
this case. Without that, the delivery flow would produce the one commit the
|
|
69
|
+
repo's own gate rejects.
|
|
70
|
+
|
|
71
|
+
Record the returned URL. Lead reports it and stores it in `context.md`, so a
|
|
72
|
+
later session can find the PR without searching.
|
|
73
|
+
|
|
74
|
+
Reading the change is `gh pr diff <n>`, and a diff read straight into
|
|
75
|
+
context spends four lines per file on the `diff --git`/`index`/`---`/`+++`
|
|
76
|
+
header. Pipe it — or any `git diff` / `git show` — through
|
|
77
|
+
`.orchestra/scripts/compact-diff.awk`; it replaces those four with one and
|
|
78
|
+
touches nothing else, so every hunk line comes through as git wrote it.
|
|
79
|
+
|
|
80
|
+
Reading review feedback is also `gh`, and it is two different calls — a
|
|
81
|
+
review body and inline line comments are separate objects, and reading only
|
|
82
|
+
one loses half the feedback:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gh pr view <n> --json reviews,comments
|
|
86
|
+
gh api repos/{owner}/{repo}/pulls/<n>/comments
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Never poll in a loop. Fetch when the user says the review is ready.
|
|
90
|
+
|
|
91
|
+
## Review cycle
|
|
92
|
+
|
|
93
|
+
The review happens **on the pull request**, not in a file beside it. Findings
|
|
94
|
+
land inline, on the line they are about, and the engineer answers them on the
|
|
95
|
+
thread. Nothing about the conversation lives only on somebody's disk.
|
|
96
|
+
|
|
97
|
+
The loop is: reviewer comments → a fresh engineer with the phase's identity
|
|
98
|
+
fixes, replies and resolves → re-review. Max rounds is `thresholds.stuck_retry_limit`,
|
|
99
|
+
counted by `.claude/hooks/review-rounds.sh`. On exhaustion the user decides
|
|
100
|
+
whether another round is worth it — say what is still open rather than going
|
|
101
|
+
round again.
|
|
102
|
+
|
|
103
|
+
**An engineer answers, not lead.** It reads the code, the diff and the thread
|
|
104
|
+
before it replies. A thread answered from a summary is a thread answered
|
|
105
|
+
wrong. It is a new engineer each round rather than the one that wrote the
|
|
106
|
+
code, because a resumed agent re-reads its whole history on every call.
|
|
107
|
+
|
|
108
|
+
### Posting the review — one call, every finding
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cat > .git/REVIEW.json <<'JSON'
|
|
112
|
+
{
|
|
113
|
+
"event": "COMMENT",
|
|
114
|
+
"body": "changes-requested — two or three sentences",
|
|
115
|
+
"comments": [
|
|
116
|
+
{ "path": "src/auth/session.ts", "line": 42, "side": "RIGHT",
|
|
117
|
+
"body": "[blocking] The session id is compared with ===, which is not constant time.\n\nUse `crypto.timingSafeEqual` over the two buffers." }
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
JSON
|
|
121
|
+
gh api repos/{owner}/{repo}/pulls/<n>/reviews --input .git/REVIEW.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
One review, not one call per finding — each separate call is a separate
|
|
125
|
+
notification.
|
|
126
|
+
|
|
127
|
+
**`COMMENT`, never `APPROVE` or `REQUEST_CHANGES`.** The same account authored
|
|
128
|
+
the pull request and GitHub refuses to let an author approve their own.
|
|
129
|
+
|
|
130
|
+
So the verdict travels as a **label**, which is the part GitHub will let the
|
|
131
|
+
author set and the part everything else can read — the board, a filter, a
|
|
132
|
+
human scanning the list, and the next agent.
|
|
133
|
+
|
|
134
|
+
| Label | Means |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `review:changes-requested` | unresolved findings; the engineer is up |
|
|
137
|
+
| `review:approved` | nothing blocking; the task can move on |
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# once per repo, idempotent — the scaffold does this
|
|
141
|
+
gh label create review:changes-requested -c D93F0B -d "Reviewer found blocking issues" -f
|
|
142
|
+
gh label create review:approved -c 0E8A16 -d "Reviewer found nothing blocking" -f
|
|
143
|
+
|
|
144
|
+
# one state at a time, so the two can never both be on
|
|
145
|
+
gh pr edit <n> --add-label review:changes-requested --remove-label review:approved
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The label is the handshake between reviewer and engineer, and it is what lead
|
|
149
|
+
reads to decide the next move. The review body still carries the summary, but
|
|
150
|
+
a body has to be parsed and a label does not — and a run that dies between
|
|
151
|
+
rounds can pick up exactly where it left off by asking the pull request what
|
|
152
|
+
state it is in.
|
|
153
|
+
|
|
154
|
+
Every inline comment names the problem in a sentence and says what to do
|
|
155
|
+
instead. A comment that only objects makes the next agent guess.
|
|
156
|
+
|
|
157
|
+
### Reading the threads
|
|
158
|
+
|
|
159
|
+
REST returns comments; only GraphQL carries the thread id and whether it is
|
|
160
|
+
resolved, and both are needed to answer one.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
gh api graphql -f query='
|
|
164
|
+
query($o:String!,$r:String!,$n:Int!) {
|
|
165
|
+
repository(owner:$o, name:$r) { pullRequest(number:$n) {
|
|
166
|
+
reviewThreads(first:50) { nodes {
|
|
167
|
+
id isResolved path line
|
|
168
|
+
comments(first:20) { nodes { body author { login } } }
|
|
169
|
+
} } } } }' -f o={owner} -f r={repo} -F n=<n>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Answering and resolving
|
|
173
|
+
|
|
174
|
+
Reply on the thread, then resolve it. A few words on what was done and the
|
|
175
|
+
commit id — the diff says the rest.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
gh api graphql -f query='
|
|
179
|
+
mutation($t:ID!, $b:String!) {
|
|
180
|
+
addPullRequestReviewThreadReply(input:{pullRequestReviewThreadId:$t, body:$b}) {
|
|
181
|
+
comment { id } } }' -f t=<thread-id> -f b="Fixed with timingSafeEqual — a1b2c3d"
|
|
182
|
+
|
|
183
|
+
gh api graphql -f query='
|
|
184
|
+
mutation($t:ID!) {
|
|
185
|
+
resolveReviewThread(input:{threadId:$t}) { thread { isResolved } } }' -f t=<thread-id>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Resolve only what you actually addressed. A thread resolved without a fix is
|
|
189
|
+
worse than one left open, because the next reader believes it.
|
|
190
|
+
|
|
191
|
+
If you did something other than what was asked, say why in the reply. If you
|
|
192
|
+
disagree, say so and leave the thread open — resolving a disagreement is not
|
|
193
|
+
answering it.
|
|
194
|
+
|
|
195
|
+
Never poll in a loop. A human review is fetched when the user says it is
|
|
196
|
+
ready.
|
|
197
|
+
|
|
198
|
+
## Who merges
|
|
199
|
+
|
|
200
|
+
Decided by the `--auto` flag on `/orchestra:start`, and by nothing else.
|
|
201
|
+
|
|
202
|
+
| | Who merges | What follows |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| normal | **the human**, squashing | lead waits; a task whose `depends_on` is unmerged does not start |
|
|
205
|
+
| `--auto` | lead, squash-merging | commit link posted on the tracker task, status moved, branch deleted |
|
|
206
|
+
|
|
207
|
+
Outside `--auto`, **never merge your own PR unless explicitly told to.**
|
|
208
|
+
|
|
209
|
+
**Never stack a branch on an unmerged branch** to avoid waiting. The human
|
|
210
|
+
squashes, so the parent's commits reappear in the child's diff until it is
|
|
211
|
+
rebased, and an agent doing that on every task will get it wrong. Waiting is
|
|
212
|
+
the honest cost of reviewing per task — say what is open and what is blocked
|
|
213
|
+
rather than stalling silently.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Role Boundary Enforcement
|
|
2
|
+
|
|
3
|
+
Before writing ANY file, check your role's ownership scope.
|
|
4
|
+
|
|
5
|
+
## Ownership Map
|
|
6
|
+
|
|
7
|
+
| Role | Can Write | Everything Else |
|
|
8
|
+
|------|-----------|----------------|
|
|
9
|
+
| 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 |
|
|
10
|
+
| PM | `.orchestra/milestones/*` (prd, milestone, grooming, phases) | Refuse |
|
|
11
|
+
| Lead | `.orchestra/milestones/*/context.md` | Refuse |
|
|
12
|
+
| Scaffolder | `.orchestra/config.yml` (verification, tracker, observability keys only), scaffolded app files, repo config | Refuse |
|
|
13
|
+
| Tracker | Tracker id frontmatter in `.orchestra/milestones/*` | Refuse |
|
|
14
|
+
| Sub-agents | Only what phase `## Scope` defines | Refuse |
|
|
15
|
+
| Reviewer | Review verdict in phase file only | Refuse |
|
|
16
|
+
|
|
17
|
+
Some entries on the Orchestrator's list are **not** blocked by the hook below,
|
|
18
|
+
each for its own reason. `.orchestra/blueprints/` is where `/orchestra
|
|
19
|
+
blueprint` writes, `.orchestra/config.yml` is merged rather than overwritten,
|
|
20
|
+
and `CLAUDE.md` keeps everything outside the orchestra section for the user —
|
|
21
|
+
the installer preserves all three on upgrade, so an edit there is not lost
|
|
22
|
+
work. `docs/` is not installed into a project at all; it is this repo's own
|
|
23
|
+
documentation, so in an installed copy there is nothing under that name to
|
|
24
|
+
protect and blocking it would only block the user's own docs.
|
|
25
|
+
|
|
26
|
+
They stay on the ownership map because a role should still not reach into
|
|
27
|
+
them. They are simply not things a hook can decide.
|
|
28
|
+
|
|
29
|
+
## Enforced, not asked for
|
|
30
|
+
|
|
31
|
+
Three `PreToolUse` hooks. The first blocks Write and Edit against installed
|
|
32
|
+
system files. The second holds a session inside the role it declared. The
|
|
33
|
+
third caps review rounds per phase.
|
|
34
|
+
|
|
35
|
+
### The system-file hook
|
|
36
|
+
|
|
37
|
+
Two questions decide it, and neither is "which role is running": a slash
|
|
38
|
+
command is a markdown prompt and cannot export anything a hook could read on
|
|
39
|
+
its own, so a guard built only on the role would never fire.
|
|
40
|
+
|
|
41
|
+
**Which repo is this?** In the orchestrator repo these files are source, and
|
|
42
|
+
the hook allows everything. Elsewhere they are build output.
|
|
43
|
+
|
|
44
|
+
**Did Orchestra put this file here?** `.orchestra/.installed.json` records
|
|
45
|
+
every file the installer wrote, and the hook blocks exactly that list.
|
|
46
|
+
|
|
47
|
+
The manifest exists because the answer stopped being guessable. Rules carry a
|
|
48
|
+
`.orchestra.md` suffix, so a system rule can be told from one you wrote.
|
|
49
|
+
Skills lost that marker when they became directories, and a list of globs
|
|
50
|
+
cannot tell a shipped skill from yours — so it blocked both, including the
|
|
51
|
+
extension point `docs/skills.md` tells you to use, and including a stack's
|
|
52
|
+
own `apps/<name>/.claude/` pack. The upgrade had the same blindness from the
|
|
53
|
+
other side: a skill this project stops shipping looked like one you wrote, and
|
|
54
|
+
survived forever. The manifest answers both, and retires what a new version no
|
|
55
|
+
longer ships.
|
|
56
|
+
|
|
57
|
+
**`.orchestra/config.yml` is never blocked.** It is merged on upgrade rather
|
|
58
|
+
than overwritten, so an edit survives — and `/orchestra:setup` writes its
|
|
59
|
+
verification keys, which `.orchestra/README.md` has always documented as an
|
|
60
|
+
exception the hook did not implement.
|
|
61
|
+
|
|
62
|
+
Neither are `.orchestra/milestones/`, `blueprints/`, roles you create, or
|
|
63
|
+
skills and rules you write: none of them is in the manifest, because the
|
|
64
|
+
installer preserves them.
|
|
65
|
+
|
|
66
|
+
When it blocks: make the change in the orchestrator repo and reinstall, or —
|
|
67
|
+
if it is genuinely project-specific — put it in the app's own `CLAUDE.md`, a
|
|
68
|
+
project skill of your own, or `config.yml`, none of which is overwritten.
|
|
69
|
+
|
|
70
|
+
### The role hook
|
|
71
|
+
|
|
72
|
+
The ownership map above was prose, and a PM opened a pull request. So the
|
|
73
|
+
role is now something a hook can read: each role command has the agent run
|
|
74
|
+
one Bash line — `true # orchestra-role=product-manager` — and the hook, which
|
|
75
|
+
sees every Bash call together with the session id Claude Code passes it,
|
|
76
|
+
records the pair under `.orchestra/.sessions/`. Every later call in that
|
|
77
|
+
session is judged against it. Two terminals are two sessions; the PM's marker
|
|
78
|
+
never touches lead.
|
|
79
|
+
|
|
80
|
+
What it refuses for the PM: a Write or Edit outside `.orchestra/milestones/`;
|
|
81
|
+
`gh pr` in any form; a new branch; merge, rebase, cherry-pick, a hard reset,
|
|
82
|
+
a force push; `git commit -a`; `git add` of anything but milestone files;
|
|
83
|
+
any `Agent` launch other than `milestone-reviewer` and `tracker`; any
|
|
84
|
+
`Workflow`; and a ClickUp task write carrying a status other than the
|
|
85
|
+
backlog one. The last three came from one afternoon: the word "ultracode" in
|
|
86
|
+
a message told the platform to use the Workflow tool, and a PM whose role
|
|
87
|
+
said "cannot review code" ran a multi-agent code review, recommended itself
|
|
88
|
+
for it, and opened the task straight into `in progress`. Lead and the
|
|
89
|
+
Orchestrator are recorded and, for now, not restricted — lead's sub-agents
|
|
90
|
+
write code in the same session, and a restriction there would block the
|
|
91
|
+
work it exists to do.
|
|
92
|
+
|
|
93
|
+
Two things are structural rather than hook-judged, because they do not
|
|
94
|
+
depend on the role. `Workflow` is denied in the project's `settings.json`;
|
|
95
|
+
no role runs one. The reviewer and the milestone reviewer carry
|
|
96
|
+
`disallowedTools: [Write, Edit, NotebookEdit]` in their frontmatter; they
|
|
97
|
+
review, and Claude Code removes the tools rather than asking them not to.
|
|
98
|
+
|
|
99
|
+
No marker means no role, and nothing is refused. That is discussion mode.
|
|
100
|
+
|
|
101
|
+
When it blocks, the message says why. Say so to the user and stop; the
|
|
102
|
+
answer is never to find a command the pattern does not match.
|
|
103
|
+
|
|
104
|
+
### The review-round hook
|
|
105
|
+
|
|
106
|
+
`.claude/hooks/review-rounds.sh` counts reviewer launches per phase, from the
|
|
107
|
+
`Phase:` line lead puts in every reviewer prompt, in
|
|
108
|
+
`.orchestra/.sessions/review-rounds.json`. Past `thresholds.stuck_retry_limit`
|
|
109
|
+
it refuses the launch until the prompt quotes the user's approval. The cap
|
|
110
|
+
was prose, and a run read "loop until clean" as overriding it.
|
|
111
|
+
|
|
112
|
+
## Rules
|
|
113
|
+
|
|
114
|
+
- Check what file you'd write to, not what the user said
|
|
115
|
+
- If user insists → still refuse: "Outside my scope as {role}. Use /orchestra:{command}."
|
|
116
|
+
- Discussion mode (no role) → no restrictions
|
|
117
|
+
- Reading → always allowed
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Not a review checklist item — a property of the code as it is written.
|
|
4
|
+
|
|
5
|
+
## OWASP is the reference
|
|
6
|
+
|
|
7
|
+
When a security question has an OWASP answer, that answer is the one — the
|
|
8
|
+
cheat sheets for how to do a specific thing, the ASVS for what a review
|
|
9
|
+
should ask.
|
|
10
|
+
|
|
11
|
+
It is named here rather than left implicit because "industry standard
|
|
12
|
+
practice" resolves to whatever the model remembers, and what it remembers is
|
|
13
|
+
usually a few years stale. Password storage is the clearest case: the
|
|
14
|
+
recommended algorithm and its parameters have changed more than once, and a
|
|
15
|
+
work factor written from memory is weaker than the one somebody thinks they
|
|
16
|
+
configured.
|
|
17
|
+
|
|
18
|
+
**So it is read at implementation time, not recalled**, the same as a library
|
|
19
|
+
version — see `.claude/rules/dependency-upgrade.orchestra.md`. Quote the
|
|
20
|
+
setting you took and where it came from, in a comment beside the code.
|
|
21
|
+
|
|
22
|
+
Where the guidance offers several acceptable options, choosing among them is
|
|
23
|
+
an engineering decision that gets written down with its reason. The scaffolded
|
|
24
|
+
backend hashes with the 16 MiB scrypt parameters rather than OWASP's primary
|
|
25
|
+
128 MiB set, because a login route allocating 128 MiB per attempt is the
|
|
26
|
+
cheapest way to exhaust a small container — the comment says exactly that, and
|
|
27
|
+
the numbers behind it were measured.
|
|
28
|
+
|
|
29
|
+
Deviating without a reason is not an option this rule offers.
|
|
30
|
+
|
|
31
|
+
## Secrets
|
|
32
|
+
|
|
33
|
+
- Never in source. Never in a commit. Never in a log line, an error
|
|
34
|
+
message, or an analytics property.
|
|
35
|
+
- Read from the environment. Fail loudly at boot when a required one is
|
|
36
|
+
missing — a service that starts without its credentials and fails on the
|
|
37
|
+
first request is harder to diagnose than one that refuses to start.
|
|
38
|
+
- `.env` is gitignored in every project, always. `.env.example` is committed,
|
|
39
|
+
and it is **generated from the validating schema**, never written beside it
|
|
40
|
+
— a hand-kept example is wrong the first time somebody adds a variable and
|
|
41
|
+
forgets, and a new engineer trusts it anyway.
|
|
42
|
+
- If a secret is ever committed, rotating it is the fix. Removing the commit
|
|
43
|
+
is not — assume it was scraped the moment it was pushed.
|
|
44
|
+
|
|
45
|
+
**Enforced, not asked for.** gitleaks runs in its own workflow over the full
|
|
46
|
+
history — a secret committed and later deleted is still leaked, so scanning
|
|
47
|
+
the tip would miss every one of those — and in the pre-commit gate over
|
|
48
|
+
staged changes, where it is still cheap to fix.
|
|
49
|
+
|
|
50
|
+
## The rules a machine can check
|
|
51
|
+
|
|
52
|
+
`scripts/policy-check.mjs` runs in the gate. These are the checks it makes
|
|
53
|
+
that belong to this rule — it decides others too;
|
|
54
|
+
`.claude/rules/performance.orchestra.md` and
|
|
55
|
+
`.claude/rules/comments.orchestra.md` each list their own:
|
|
56
|
+
|
|
57
|
+
| Check | What fails |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `swallowed-error` | a catch that neither handles nor reports |
|
|
60
|
+
| `query-by-testid` | `getByTestId` / `onNodeWithTag` in a test |
|
|
61
|
+
| `mocked-database` | `vi.mock` / `jest.mock` aimed at a db module |
|
|
62
|
+
| `interpolated-sql` | a template literal passed to query/execute/raw |
|
|
63
|
+
|
|
64
|
+
Only checks that can be decided precisely live there. **Authorization on a
|
|
65
|
+
route and PII in a log line cannot be**, so they stay review items below
|
|
66
|
+
rather than becoming a check that cries wolf — a gate people learn to
|
|
67
|
+
override is worse than no gate.
|
|
68
|
+
|
|
69
|
+
## Input
|
|
70
|
+
|
|
71
|
+
- Validate at the boundary, before anything else touches it.
|
|
72
|
+
- Parameterised queries only. String-built SQL is a defect regardless of
|
|
73
|
+
where the input came from.
|
|
74
|
+
- Escape on output according to the sink — HTML, SQL, and a shell command
|
|
75
|
+
need different treatment, and "it was already validated" is not one of
|
|
76
|
+
them.
|
|
77
|
+
- Size limits on uploads and request bodies.
|
|
78
|
+
|
|
79
|
+
## Authorisation
|
|
80
|
+
|
|
81
|
+
- Checked on the route, not in a helper a future route can forget to call.
|
|
82
|
+
- Deny by default. A new endpoint is closed until it is deliberately opened.
|
|
83
|
+
- Never trust a client-supplied id to imply ownership — verify it.
|
|
84
|
+
|
|
85
|
+
## Dependencies
|
|
86
|
+
|
|
87
|
+
- Anything added is checked for maintenance, per
|
|
88
|
+
`.claude/rules/dependency-upgrade.orchestra.md`.
|
|
89
|
+
- Audit runs in CI. A known critical vulnerability blocks the merge.
|
|
90
|
+
- Prefer the platform over a package. The safest dependency is the one you
|
|
91
|
+
did not add.
|
|
92
|
+
|
|
93
|
+
## Client-side
|
|
94
|
+
|
|
95
|
+
- No secret ever reaches a client bundle. Anything in the browser or the app
|
|
96
|
+
binary is public.
|
|
97
|
+
- Content Security Policy on web.
|
|
98
|
+
- Certificate pinning where the threat model calls for it, not by reflex.
|
|
99
|
+
|
|
100
|
+
## Data
|
|
101
|
+
|
|
102
|
+
- Collect the minimum that answers the question.
|
|
103
|
+
- PII never lands in logs, errors, or analytics — see
|
|
104
|
+
`.claude/rules/observability.orchestra.md`.
|
|
105
|
+
- Encrypt in transit, always. At rest wherever the data would matter if the
|
|
106
|
+
disk were taken.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Stuck Detection & Recovery
|
|
2
|
+
|
|
3
|
+
You are **stuck** when any of these happen:
|
|
4
|
+
- **Same error N times** (N = config.yml `thresholds.stuck_retry_limit`, default 3)
|
|
5
|
+
- **Circular fix** — fixing issue A creates issue B, fixing B recreates A
|
|
6
|
+
- **Verification loop** — verification gate fails max retries on the same check
|
|
7
|
+
- **No progress** — reading files and running commands but not making meaningful code changes
|
|
8
|
+
|
|
9
|
+
## Recovery Protocol
|
|
10
|
+
|
|
11
|
+
1. **STOP immediately.** Do not attempt another fix.
|
|
12
|
+
2. **Log** in context.md: phase name, symptom, attempts, root cause hypothesis.
|
|
13
|
+
3. **Try a different approach** (ONE attempt):
|
|
14
|
+
- Same code fix failed? → entirely different implementation strategy
|
|
15
|
+
- Dependency broken? → alternative library
|
|
16
|
+
- Tests fail due to environment? → isolate the test
|
|
17
|
+
4. **If different approach also fails** → escalate to user with options:
|
|
18
|
+
- Try specific alternative
|
|
19
|
+
- Skip this phase and continue
|
|
20
|
+
- Stop execution
|
|
21
|
+
|
|
22
|
+
## Auto-Recovery (--auto mode)
|
|
23
|
+
|
|
24
|
+
Try the different approach (step 3) automatically.
|
|
25
|
+
If that also fails → set phase to `failed`, log everything, move to next phase.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Testing Standards
|
|
2
|
+
|
|
3
|
+
**Code without tests is not done.** Write them as part of implementation,
|
|
4
|
+
never as a follow-up phase.
|
|
5
|
+
|
|
6
|
+
## The rule that decides what to test
|
|
7
|
+
|
|
8
|
+
> If one of our functions calls into a third-party library, that function
|
|
9
|
+
> has a test. When a dependency is bumped and its behaviour changes, the
|
|
10
|
+
> test must fail.
|
|
11
|
+
|
|
12
|
+
A green suite is the merge signal for dependency upgrades. That only works
|
|
13
|
+
if the suite covers the seams where our code meets someone else's.
|
|
14
|
+
|
|
15
|
+
Snapshot tests do not satisfy this. A snapshot passes while a handler
|
|
16
|
+
silently stops firing — assert behaviour, not markup.
|
|
17
|
+
|
|
18
|
+
## Backend — the shape
|
|
19
|
+
|
|
20
|
+
The test pyramid is wrong for a backend. It comes from an era when starting
|
|
21
|
+
a database took minutes. A backend is mostly plumbing — parse, validate,
|
|
22
|
+
query, transform, respond — and the bugs live in the seams, not in pure
|
|
23
|
+
functions.
|
|
24
|
+
|
|
25
|
+
| Layer | How much | What |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Unit | few | Pure logic only: date math, pricing, permissions, state machines. No I/O. |
|
|
28
|
+
| **Integration** | **most of the suite** | Route level, through the real app, against a real database. |
|
|
29
|
+
| External services | as needed | Faked at the HTTP boundary. |
|
|
30
|
+
| E2E | a handful | Smoke: it boots, health is honest, signup and login work. |
|
|
31
|
+
|
|
32
|
+
A route-level test covers routing, middleware, validation, auth, the
|
|
33
|
+
handler, the query and serialisation in one assertion path. That is where
|
|
34
|
+
the value is.
|
|
35
|
+
|
|
36
|
+
### Never mock the database
|
|
37
|
+
|
|
38
|
+
Mock it and you test your own mock. A fake repository returns exactly what
|
|
39
|
+
you told it to; it cannot tell you the `WHERE` clause is wrong, the
|
|
40
|
+
constraint is missing, or the transaction does not roll back.
|
|
41
|
+
|
|
42
|
+
The database driver is a third-party library, so the rule above already
|
|
43
|
+
forbids this.
|
|
44
|
+
|
|
45
|
+
- Real database, in a container, **same major version as production**.
|
|
46
|
+
- Never SQLite standing in for Postgres — different dialect, different
|
|
47
|
+
types, different constraint behaviour. You would be testing a database
|
|
48
|
+
you do not ship.
|
|
49
|
+
- Run migrations against it. Migration testing comes free.
|
|
50
|
+
- Isolation: each test in a transaction that is rolled back, or truncate
|
|
51
|
+
between tests. Never rely on ordering.
|
|
52
|
+
- Each test creates the data it needs. A shared seed fixture is what makes
|
|
53
|
+
a suite unmaintainable somewhere around test three hundred.
|
|
54
|
+
|
|
55
|
+
### Fake external services at the network boundary
|
|
56
|
+
|
|
57
|
+
Intercept the HTTP call — MSW or a stub server. Do **not** mock your own
|
|
58
|
+
`sendEmail()`.
|
|
59
|
+
|
|
60
|
+
Mocking your own wrapper means the wrapper is never tested, and the wrapper
|
|
61
|
+
is exactly where a provider's API change bites you.
|
|
62
|
+
|
|
63
|
+
### Test data
|
|
64
|
+
|
|
65
|
+
Synthetic, built by factories, created per test. Never a copy of production
|
|
66
|
+
data — it carries PII and it drifts.
|
|
67
|
+
|
|
68
|
+
## Coverage is a diagnostic, not a target
|
|
69
|
+
|
|
70
|
+
A single global percentage stops measuring test quality the moment it
|
|
71
|
+
becomes a gate. It measures test quantity instead, and it treats a design
|
|
72
|
+
token file as equal to the module wrapping your database driver.
|
|
73
|
+
|
|
74
|
+
Four gates replace it:
|
|
75
|
+
|
|
76
|
+
| Gate | Value |
|
|
77
|
+
|---|---|
|
|
78
|
+
| Global coverage | **ratchet** — may never fall below what the branch already had |
|
|
79
|
+
| Branch coverage of the lines this change touched | `testing.coverage.diff_branch_threshold` |
|
|
80
|
+
| Modules that wrap a third-party library | **100%** |
|
|
81
|
+
| Type-only files, tokens, generated code, entry points | excluded, not counted |
|
|
82
|
+
|
|
83
|
+
The diff gate is the one that changes behaviour. It asks about the code
|
|
84
|
+
being written now rather than the historical average.
|
|
85
|
+
|
|
86
|
+
The seam gate is live from the first commit, because the location is a
|
|
87
|
+
convention: anything wrapping a third party lives in
|
|
88
|
+
`src/integrations/<provider>/`, so the glob is known before the first wrapper
|
|
89
|
+
exists. It asks that the wrapper be **exercised**, not that a test file sit
|
|
90
|
+
beside it — a route test driving it through a faked HTTP boundary satisfies
|
|
91
|
+
it. Depth in `.claude/skills/project-structure/SKILL.md`.
|
|
92
|
+
|
|
93
|
+
Do not chase the global number with unit tests. If the integration tests
|
|
94
|
+
are real, coverage arrives on its own. If tests are being written to move a
|
|
95
|
+
percentage, the gate has started doing harm — say so rather than complying.
|
|
96
|
+
|
|
97
|
+
## Mutation testing
|
|
98
|
+
|
|
99
|
+
Coverage says a line ran. Mutation says the line was **checked**.
|
|
100
|
+
|
|
101
|
+
Stryker rewrites the code one small change at a time — `>` becomes `>=`,
|
|
102
|
+
a return value is replaced — and runs the suite. If the tests still pass,
|
|
103
|
+
nothing was actually asserting that behaviour.
|
|
104
|
+
|
|
105
|
+
Runs on every pull request, over the files that pull request changed
|
|
106
|
+
(`--mutate` with the changed paths, plus `--incremental`). A full run over
|
|
107
|
+
everything is a scheduled job, not a gate — it takes hours.
|
|
108
|
+
|
|
109
|
+
A suite at 95% line coverage with a 40% mutation score is decorative.
|
|
110
|
+
|
|
111
|
+
Stryker reads the TypeScript compiler's JS API, which TypeScript 7 removed.
|
|
112
|
+
On an app using TS 7 the scaffolder skips the install and says so — do not
|
|
113
|
+
work around it by pinning TypeScript backwards. The gate turns on by itself
|
|
114
|
+
once Stryker supports the new compiler.
|
|
115
|
+
|
|
116
|
+
## Web — jsdom is not a browser
|
|
117
|
+
|
|
118
|
+
It has no layout engine. `getBoundingClientRect` returns zeros, nothing
|
|
119
|
+
scrolls, CSS is never applied, focus order is approximated. A component can
|
|
120
|
+
be invisible, unclickable or overlapping another and every jsdom test still
|
|
121
|
+
passes.
|
|
122
|
+
|
|
123
|
+
Component tests therefore run in **real Chromium** via Vitest browser mode.
|
|
124
|
+
Do not swap in jsdom because a test is awkward — the awkwardness is usually
|
|
125
|
+
the bug.
|
|
126
|
+
|
|
127
|
+
Query by role, never by test id: Testing Library resolves roles from the
|
|
128
|
+
accessible tree, so a div-with-onClick fails the test and accessibility
|
|
129
|
+
becomes something the suite enforces rather than something review notices.
|
|
130
|
+
|
|
131
|
+
**axe runs as an assertion**, at two levels, because they see different
|
|
132
|
+
failures. In a component test it checks the mounted subtree; in
|
|
133
|
+
`e2e/a11y.spec.ts` it checks the built page, which is the only place contrast
|
|
134
|
+
against the real background, landmark structure and heading order exist at
|
|
135
|
+
all. Both are wired by the stack — accessibility is not a review checklist
|
|
136
|
+
item here.
|
|
137
|
+
|
|
138
|
+
Depth in `.claude/skills/web-testing/SKILL.md`.
|
|
139
|
+
|
|
140
|
+
## Mobile is a different shape
|
|
141
|
+
|
|
142
|
+
Jest does not run React Native — `jest-expo` runs components in Node against
|
|
143
|
+
mocked native modules. The whole JS suite can be green while the app fails
|
|
144
|
+
to build, crashes on launch, or works on one platform and not the other.
|
|
145
|
+
|
|
146
|
+
So the device layer is not optional there, and two commands belong in the
|
|
147
|
+
gate because nothing else can see what they see:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
expo-doctor
|
|
151
|
+
expo install --check
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Depth in `.claude/skills/mobile-testing/SKILL.md`. Native iOS and Android
|
|
155
|
+
have their own shapes again — `.claude/skills/ios-testing/SKILL.md` and
|
|
156
|
+
`.claude/skills/android-testing/SKILL.md`.
|
|
157
|
+
|
|
158
|
+
## Native has two more shapes
|
|
159
|
+
|
|
160
|
+
**iOS:** a green unit suite does not prove the app launches. A missing
|
|
161
|
+
Info.plist key, an entitlement the profile lacks, a `@main` that throws
|
|
162
|
+
before the first view — no XCTest sees any of them. The launch test is the
|
|
163
|
+
first test worth writing, not the last.
|
|
164
|
+
|
|
165
|
+
**Android:** a passing debug build proves little about the release build. R8
|
|
166
|
+
shrinks and obfuscates, a keep rule nobody wrote removes a class loaded by
|
|
167
|
+
name, and the release APK crashes on launch with every test green. So
|
|
168
|
+
`assembleRelease` is in the gate.
|
|
169
|
+
|
|
170
|
+
Both query the accessibility tree rather than an identifier — XCUITest
|
|
171
|
+
through accessible names, Compose through the semantics tree — for the same
|
|
172
|
+
reason web and React Native query by role: it makes the suite enforce
|
|
173
|
+
accessibility instead of review noticing it.
|
|
174
|
+
|
|
175
|
+
Depth in `.claude/skills/ios-testing/SKILL.md` and
|
|
176
|
+
`.claude/skills/android-testing/SKILL.md`.
|
|
177
|
+
|
|
178
|
+
## The bundle has a budget, and it is a ratchet
|
|
179
|
+
|
|
180
|
+
Same shape as the coverage ratchet: the committed baseline is whatever the
|
|
181
|
+
build actually produced. It may fall and it may not rise.
|
|
182
|
+
|
|
183
|
+
Nobody has to defend "why 180kB", and the build still fails the day a stray
|
|
184
|
+
import pulls in a date library. Measured gzipped, because that is what a
|
|
185
|
+
browser downloads.
|
|
186
|
+
|
|
187
|
+
Deliberate growth is re-recorded on purpose, in its own commit, so it is a
|
|
188
|
+
decision somebody made rather than a number that drifted.
|
|
189
|
+
|
|
190
|
+
## A build is part of the suite
|
|
191
|
+
|
|
192
|
+
`tsc --noEmit` accepts an app that fails to bundle, and neither tests nor
|
|
193
|
+
lint ever load the bundler. The gate runs `build` for that reason, and a
|
|
194
|
+
failure is fixed in the code — never by loosening the config.
|
|
195
|
+
|
|
196
|
+
## Dependency upgrades
|
|
197
|
+
|
|
198
|
+
- **minor and patch** green → merge, no changelog needed. That is what the
|
|
199
|
+
seam rule buys.
|
|
200
|
+
- **major** → a human reads it. A major version is the maintainer stating
|
|
201
|
+
the contract changed, and some of what changed — default timeouts, retry
|
|
202
|
+
behaviour, performance — is not visible to any test.
|
|
203
|
+
|
|
204
|
+
Never loosen a test to make a dependency PR green. See
|
|
205
|
+
`.claude/rules/dependency-upgrade.orchestra.md`.
|
|
206
|
+
|
|
207
|
+
## Always
|
|
208
|
+
|
|
209
|
+
- Cover happy path, edge cases, and failure paths — a funnel that only
|
|
210
|
+
records success cannot tell you why people left.
|
|
211
|
+
- Match the project's existing test style and naming.
|
|
212
|
+
- Run the tests, then break the code and confirm they fail. A test that has
|
|
213
|
+
never failed has not been tested.
|
|
214
|
+
- One assertion per concept. The test name states the scenario.
|