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,172 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PreToolUse guard: a phase gets at most `thresholds.stuck_retry_limit`
|
|
3
|
+
# review rounds, then a person decides.
|
|
4
|
+
#
|
|
5
|
+
# The cap was prose in lead.md, and a run went nine rounds on one phase
|
|
6
|
+
# because the user's "loop until no high or medium remains" read as
|
|
7
|
+
# overriding it. Each round found a new hole in the previous round's fix, so
|
|
8
|
+
# nothing inside the loop could end it. Counting is the one part a machine
|
|
9
|
+
# can do, so it is done here.
|
|
10
|
+
#
|
|
11
|
+
# A reviewer launch names its phase on a line
|
|
12
|
+
# `Phase: <milestone directory>/<phase file name>`, and that file must exist
|
|
13
|
+
# under .orchestra/milestones/ — a second spelling of the same phase would
|
|
14
|
+
# start a fresh count. Lead must name it; any other session may leave it
|
|
15
|
+
# out, uncounted, so a one-off review in discussion mode is never refused.
|
|
16
|
+
#
|
|
17
|
+
# The count lives in .orchestra/.sessions/review-rounds.json, keyed by phase
|
|
18
|
+
# rather than by session, so a restarted lead does not start from zero, and
|
|
19
|
+
# the installer keeps it across upgrades. Past the cap the launch is refused
|
|
20
|
+
# until the prompt quotes the user's approval on a line
|
|
21
|
+
# `Extra round approved by the user: …` — each launch past it carries its own
|
|
22
|
+
# line, so one approval buys one round. Nothing
|
|
23
|
+
# can check that the quote is real; it is honest by convention, like the
|
|
24
|
+
# escape-hatch comments the policy check reads.
|
|
25
|
+
#
|
|
26
|
+
# Reads the tool call on stdin. Exit 0 allows, exit 2 blocks.
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
input="$(cat)"
|
|
30
|
+
|
|
31
|
+
verdict="$(printf '%s' "$input" | CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}" node -e '
|
|
32
|
+
const fs = require("node:fs");
|
|
33
|
+
const path = require("node:path");
|
|
34
|
+
const { execFileSync } = require("node:child_process");
|
|
35
|
+
|
|
36
|
+
let s = "";
|
|
37
|
+
process.stdin.on("data", (d) => (s += d)).on("end", () => {
|
|
38
|
+
let call;
|
|
39
|
+
try {
|
|
40
|
+
call = JSON.parse(s);
|
|
41
|
+
} catch {
|
|
42
|
+
// Optional by design: an unparseable call is not ours to judge.
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (call.tool_name !== "Agent") return;
|
|
46
|
+
const input = call.tool_input || {};
|
|
47
|
+
if (input.subagent_type !== "reviewer" && input.subagent_type !== "re-reviewer") return;
|
|
48
|
+
const prompt = String(input.prompt || "");
|
|
49
|
+
|
|
50
|
+
// The installer writes into the directory it ran in, which can be an app
|
|
51
|
+
// inside a larger repository, so the project directory comes first.
|
|
52
|
+
let root = process.env.CLAUDE_PROJECT_DIR;
|
|
53
|
+
if (!fs.existsSync(path.join(root, ".orchestra"))) {
|
|
54
|
+
try {
|
|
55
|
+
root = execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], {
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
58
|
+
}).trim();
|
|
59
|
+
} catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const milestones = path.join(root, ".orchestra", "milestones");
|
|
64
|
+
// Not an Orchestra project here: nothing to count against.
|
|
65
|
+
if (!root || !fs.existsSync(milestones)) return;
|
|
66
|
+
const dir = path.join(root, ".orchestra", ".sessions");
|
|
67
|
+
|
|
68
|
+
const phase = /^[ \t]*Phase:[ \t]*(\S[^\n]*?)[ \t]*$/m.exec(prompt);
|
|
69
|
+
if (!phase) {
|
|
70
|
+
const session = String(call.session_id || "").replace(/[^A-Za-z0-9._-]/g, "");
|
|
71
|
+
let role = "";
|
|
72
|
+
try {
|
|
73
|
+
role = session ? fs.readFileSync(path.join(dir, session), "utf8").trim() : "";
|
|
74
|
+
} catch {
|
|
75
|
+
// No marker: not a lead session, so an uncounted review is allowed.
|
|
76
|
+
}
|
|
77
|
+
if (role === "lead") {
|
|
78
|
+
process.stdout.write("A reviewer launch from lead names its phase on a line `Phase: <milestone directory>/<phase file name>`, so its review rounds can be counted.\n");
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const key = phase[1];
|
|
83
|
+
// Exactly two segments, matched by listing rather than by existsSync: a
|
|
84
|
+
// trailing slash, a third segment or a change of case on a
|
|
85
|
+
// case-insensitive disk would each name the same file under a new key.
|
|
86
|
+
const parts = key.split("/");
|
|
87
|
+
const listed = (d, name) => {
|
|
88
|
+
try {
|
|
89
|
+
return fs.readdirSync(d).includes(name);
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
if (parts.length !== 2 || !listed(milestones, parts[0]) || !listed(path.join(milestones, parts[0], "phases"), `${parts[1]}.md`)) {
|
|
95
|
+
process.stdout.write(`Phase: ${key} names no file .orchestra/milestones/<milestone>/phases/<phase>.md. Spell it as the directory and the file name, e.g. Phase: M2-design-system/phase-3 — another spelling would start a fresh count.\n`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let cap = 3;
|
|
100
|
+
try {
|
|
101
|
+
const cfg = fs.readFileSync(path.join(root, ".orchestra", "config.yml"), "utf8");
|
|
102
|
+
const m = /^\s*stuck_retry_limit:\s*(\d+)/m.exec(cfg);
|
|
103
|
+
if (m) cap = Number(m[1]);
|
|
104
|
+
} catch {
|
|
105
|
+
// No config: the documented default is the only cap we know.
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Parallel phases launch reviewers at once. Without the lock two reads
|
|
109
|
+
// see the same count and one increment is lost; without the rename a
|
|
110
|
+
// reader can meet a half-written file and start every phase from zero.
|
|
111
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
112
|
+
const lock = path.join(dir, "review-rounds.lock");
|
|
113
|
+
const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
114
|
+
const deadline = Date.now() + 5000;
|
|
115
|
+
for (;;) {
|
|
116
|
+
try {
|
|
117
|
+
fs.mkdirSync(lock);
|
|
118
|
+
break;
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (e.code !== "EEXIST") throw e;
|
|
121
|
+
try {
|
|
122
|
+
// A launch holds the lock for milliseconds, so one older than this
|
|
123
|
+
// was left by a crash; shorter than the wait, so it clears in time.
|
|
124
|
+
if (Date.now() - fs.statSync(lock).mtimeMs > 3000) fs.rmdirSync(lock);
|
|
125
|
+
} catch {
|
|
126
|
+
// Another launch released or cleared it first; try again.
|
|
127
|
+
}
|
|
128
|
+
if (Date.now() > deadline) {
|
|
129
|
+
process.stdout.write("The review-round count is locked by another launch. Launch the reviewer again.\n");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
sleep(25);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const file = path.join(dir, "review-rounds.json");
|
|
137
|
+
let counts = {};
|
|
138
|
+
try {
|
|
139
|
+
counts = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
140
|
+
} catch {
|
|
141
|
+
// First reviewer launch in this project, or a file someone deleted:
|
|
142
|
+
// both mean no rounds recorded yet.
|
|
143
|
+
}
|
|
144
|
+
const done = counts[key] || 0;
|
|
145
|
+
const approved = /^[ \t]*Extra round approved by the user:[ \t]*\S/m.test(prompt);
|
|
146
|
+
if (done >= cap && !approved) {
|
|
147
|
+
process.stdout.write(
|
|
148
|
+
`Review round ${done + 1} for ${key} is past thresholds.stuck_retry_limit (${cap}). ` +
|
|
149
|
+
"Stop and give the user the open Blocking and Important findings. Under --auto, set the task blocked with a comment naming them. " +
|
|
150
|
+
"If the user approves another round, quote them on a line `Extra round approved by the user: …`.\n",
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
counts[key] = done + 1;
|
|
155
|
+
const tmp = `${file}.${process.pid}`;
|
|
156
|
+
fs.writeFileSync(tmp, `${JSON.stringify(counts, null, 2)}\n`);
|
|
157
|
+
fs.renameSync(tmp, file);
|
|
158
|
+
} finally {
|
|
159
|
+
try {
|
|
160
|
+
fs.rmdirSync(lock);
|
|
161
|
+
} catch {
|
|
162
|
+
// Already cleared as stale by a waiter; there is nothing left to release.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
')"
|
|
167
|
+
|
|
168
|
+
if [ -n "$verdict" ]; then
|
|
169
|
+
printf '%s\n' "$verdict" >&2
|
|
170
|
+
exit 2
|
|
171
|
+
fi
|
|
172
|
+
exit 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Acceptance Check
|
|
2
|
+
|
|
3
|
+
After implementation, check whether you built **the right thing** — not just whether it works.
|
|
4
|
+
|
|
5
|
+
1. Re-read the phase file's acceptance criteria
|
|
6
|
+
2. For EACH criterion: "Does my implementation satisfy this?"
|
|
7
|
+
3. If YES → proceed
|
|
8
|
+
4. If NO → fix it
|
|
9
|
+
5. If UNCERTAIN → flag: "Unverified: {criterion} — {reason}"
|
|
10
|
+
|
|
11
|
+
This catches "code works but doesn't do what was asked."
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Analytics & KPIs
|
|
2
|
+
|
|
3
|
+
**Mandatory.** A milestone that cannot state how its success is measured is
|
|
4
|
+
not ready to build.
|
|
5
|
+
|
|
6
|
+
## KPIs come before code
|
|
7
|
+
|
|
8
|
+
Every milestone declares, in `milestone.md`, before implementation starts:
|
|
9
|
+
|
|
10
|
+
- **The metric** — one primary number this work is meant to move
|
|
11
|
+
- **The baseline** — what it is today, or explicitly "unknown, this
|
|
12
|
+
establishes it"
|
|
13
|
+
- **The target** — the number that would make this worth having built
|
|
14
|
+
- **How it is read** — the query, dashboard, or event that produces it
|
|
15
|
+
|
|
16
|
+
If you cannot fill these in, the milestone is a guess. Say so to the user
|
|
17
|
+
rather than building it and finding out later.
|
|
18
|
+
|
|
19
|
+
A metric that cannot go down is not a KPI. "Number of features shipped"
|
|
20
|
+
measures activity, not outcome.
|
|
21
|
+
|
|
22
|
+
## Events
|
|
23
|
+
|
|
24
|
+
Instrument the user's action, at the point it happens. An event fired from
|
|
25
|
+
a wrapper three layers away eventually fires for the wrong reason.
|
|
26
|
+
|
|
27
|
+
- **Naming** is consistent and boring: `object_action` — `checkout_started`,
|
|
28
|
+
`signup_completed`. Pick the convention once, never mix styles.
|
|
29
|
+
- **Typed and centralised.** Event names and payload shapes live in one
|
|
30
|
+
schema module per app, and instrumentation imports from it. A string
|
|
31
|
+
literal at a call site is how analytics quietly rots.
|
|
32
|
+
- **No PII in properties.** No email, no name, no free-text the user typed.
|
|
33
|
+
Identify with a stable anonymous id.
|
|
34
|
+
- **Respect consent.** Where consent is required, no event leaves the device
|
|
35
|
+
until it is given. This is not a preference.
|
|
36
|
+
|
|
37
|
+
## Every screen and every failure
|
|
38
|
+
|
|
39
|
+
- Each screen or route reports a view event.
|
|
40
|
+
- Each critical flow reports start, success, **and failure** — with a reason
|
|
41
|
+
code. A funnel that only records success cannot tell you why people left,
|
|
42
|
+
which is the only thing you actually wanted to know.
|
|
43
|
+
|
|
44
|
+
## Verifying
|
|
45
|
+
|
|
46
|
+
Instrumentation is code, so it is tested like code: assert the event fires
|
|
47
|
+
once, with the right name and shape. An analytics call is exactly the kind
|
|
48
|
+
of third-party seam the testing rule exists to cover.
|
|
49
|
+
|
|
50
|
+
Before a milestone closes, confirm the events actually arrived. Untested
|
|
51
|
+
instrumentation is usually broken instrumentation.
|
|
52
|
+
|
|
53
|
+
## Defaults
|
|
54
|
+
|
|
55
|
+
PostHog. A default, not a mandate — substitute freely, but the capability
|
|
56
|
+
is required. Configure under `observability` in `.orchestra/config.yml`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Code Standards
|
|
2
|
+
|
|
3
|
+
**SOLID** — Single responsibility, open/closed, Liskov substitution, interface segregation, dependency inversion. Open/closed and dependency inversion earn their place once a second real consumer needs the seam — see DRY below. Built before that consumer exists, they are over-engineering wearing a principle's name.
|
|
4
|
+
|
|
5
|
+
**KISS** — Keep it simple. Don't over-engineer.
|
|
6
|
+
|
|
7
|
+
**YAGNI** — Don't build what you don't need right now.
|
|
8
|
+
|
|
9
|
+
**DRY** — Don't repeat yourself. Extract after second duplication, not before. The same rule covers an interface, a seam, or an injection point built for open/closed or dependency inversion: add it when a second real caller needs it, never for a hypothetical one.
|
|
10
|
+
|
|
11
|
+
**Before writing code, stop at the first rung that holds:**
|
|
12
|
+
1. Does this need to exist? → no: skip it (YAGNI)
|
|
13
|
+
2. Already in this codebase? → reuse it
|
|
14
|
+
3. Stdlib does it? → use it
|
|
15
|
+
4. Native platform feature? → use it
|
|
16
|
+
5. Installed dependency? → use it directly
|
|
17
|
+
6. One line? → one line
|
|
18
|
+
7. Only then: the minimum that works
|
|
19
|
+
|
|
20
|
+
This governs volume, never correctness. Validation, error handling, security
|
|
21
|
+
and accessibility are never on this ladder — they apply at every rung,
|
|
22
|
+
covered by their own rules (`security.orchestra.md`,
|
|
23
|
+
`observability.orchestra.md`, and the `accessibility` skill where the
|
|
24
|
+
platform has one), regardless of how
|
|
25
|
+
small the change looks. Rung 5 does not skip the wrapper
|
|
26
|
+
`testing-standards.orchestra.md` requires for anything calling a third-party
|
|
27
|
+
library (`src/integrations/<provider>/`) — that wrapper exists for the seam
|
|
28
|
+
test, not for swapability, and the ladder governs what sits on top of it,
|
|
29
|
+
never whether it exists.
|
|
30
|
+
|
|
31
|
+
**Zero-tolerance rules:**
|
|
32
|
+
- No workarounds — if the right solution is hard, do it right. Flag the effort in your report.
|
|
33
|
+
- No unused code — delete dead imports, functions, files. Don't comment out.
|
|
34
|
+
- No breaking changes without migration — update every consumer.
|
|
35
|
+
- Current library versions only — verify before using, don't assume from memory.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Comments
|
|
2
|
+
|
|
3
|
+
Default to no comment.
|
|
4
|
+
|
|
5
|
+
A comment explains *why*, never *what*. The "what" is the job of naming: if
|
|
6
|
+
a comment is needed to say what the code does, rename or extract instead.
|
|
7
|
+
The test — would a reviewer ask *why* here? Then comment. Would they only
|
|
8
|
+
ask *what*? Then rename.
|
|
9
|
+
|
|
10
|
+
Write one only for:
|
|
11
|
+
|
|
12
|
+
- a workaround or constraint imposed from outside — bundler, driver, API
|
|
13
|
+
quirk — linking the upstream issue when one exists
|
|
14
|
+
- a non-obvious invariant the reader cannot derive locally
|
|
15
|
+
- a deliberate trade-off, including "do not simplify this to X" where X
|
|
16
|
+
looks tempting and is wrong
|
|
17
|
+
|
|
18
|
+
When a comment is warranted, write the single most accurate sentence you
|
|
19
|
+
can, and stop. It has to close the question, not open new ones: name the
|
|
20
|
+
consequence or the rejected alternative, not the restated rule. If a reader
|
|
21
|
+
would still ask *why* after reading it, the sentence is not finished.
|
|
22
|
+
|
|
23
|
+
Never: restating the next line, section banners, commented-out code, a
|
|
24
|
+
comment duplicating a test name. Put it above the line it guards, not
|
|
25
|
+
trailing a long line.
|
|
26
|
+
|
|
27
|
+
## Some comments are read by the gate
|
|
28
|
+
|
|
29
|
+
Two checks in `scripts/policy-check.mjs` clear on a comment — `await-in-loop`
|
|
30
|
+
on one above the loop, `swallowed-error` on one inside the catch. Those are
|
|
31
|
+
not prose for a reviewer; they are the escape hatch, and deleting one as
|
|
32
|
+
noise turns the gate red.
|
|
33
|
+
|
|
34
|
+
They are also exactly the *why* this rule asks for: which of the sequential
|
|
35
|
+
cases this loop is, why failure on this path is acceptable. `// ok` clears
|
|
36
|
+
the check and tells the next reader nothing — that is a review finding, not
|
|
37
|
+
a pass.
|
|
38
|
+
|
|
39
|
+
`.claude/rules/security.orchestra.md` asks for the same shape beside a
|
|
40
|
+
security setting: the value taken and where it came from.
|
|
41
|
+
|
|
42
|
+
## A TODO is a tech debt that skipped the tracker
|
|
43
|
+
|
|
44
|
+
`.claude/rules/tracker-authority.orchestra.md` gives a workaround a home: a
|
|
45
|
+
`tech_debt` task naming what should have been done instead. A TODO is that
|
|
46
|
+
task written where nobody will find it.
|
|
47
|
+
|
|
48
|
+
So a TODO carries the task — `TODO(#123)`, `TODO(CU-86abc)`, or a link — and
|
|
49
|
+
the task carries the reason. With `tracker.provider: none` there is no task,
|
|
50
|
+
so there is no TODO: do the work, or report it in the phase result as left
|
|
51
|
+
undone.
|
|
52
|
+
|
|
53
|
+
## Doc comments
|
|
54
|
+
|
|
55
|
+
JSDoc, KDoc, Swift `///` — one rule for all three. Not mandatory. Add one
|
|
56
|
+
only when it says something the signature cannot.
|
|
57
|
+
|
|
58
|
+
Never restate the signature: no `@param value - the value`, no `@returns
|
|
59
|
+
the result`, no "this function does X" above a function named X.
|
|
60
|
+
`@deprecated` always earns its place — name the replacement.
|
|
61
|
+
|
|
62
|
+
## While editing
|
|
63
|
+
|
|
64
|
+
Delete a restating comment on a line you are already changing. Leave the
|
|
65
|
+
rest — `.claude/rules/phase-limits.orchestra.md` applies, and a comment
|
|
66
|
+
sweep is not the phase.
|
|
67
|
+
|
|
68
|
+
## What is enforced
|
|
69
|
+
|
|
70
|
+
Only what can be decided precisely. A restating comment cannot be — a
|
|
71
|
+
machine cannot tell "increment the counter" from a reason — so it stays a
|
|
72
|
+
review item above.
|
|
73
|
+
|
|
74
|
+
| Check | Fails on |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `commented-out-code` | a `//` line that parses as a statement |
|
|
77
|
+
| `todo-without-task` | `TODO`, `FIXME`, `HACK`, `XXX` with no task id or link |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Commit Format
|
|
2
|
+
|
|
3
|
+
All commits MUST use conventional commit format.
|
|
4
|
+
|
|
5
|
+
**Format:** `<type>(<scope>): <description>`
|
|
6
|
+
|
|
7
|
+
**Types:** feat, fix, refactor, perf, test, chore, docs, style, ci, build, revert
|
|
8
|
+
|
|
9
|
+
**Rules:**
|
|
10
|
+
- Each phase produces exactly ONE commit
|
|
11
|
+
- Subject line max 72 characters
|
|
12
|
+
- Body explains WHY, not WHAT
|
|
13
|
+
- Breaking changes add `!` after type: `feat!(scope): description`
|
|
14
|
+
- No `Co-Authored-By` trailers — never add co-author lines
|
|
15
|
+
|
|
16
|
+
**The one exception is a squashed task pull request.** Its subject is
|
|
17
|
+
`#<task-id> - <subject>`, because that is where the tracker's link lives and a
|
|
18
|
+
task PR collapses several conventional commits into one. The `commit-msg` hook
|
|
19
|
+
accepts that shape and nothing else outside the convention — see
|
|
20
|
+
`.claude/rules/pull-request.orchestra.md`.
|
|
@@ -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,181 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "designs/**"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Design Parity
|
|
7
|
+
|
|
8
|
+
## What a design source is
|
|
9
|
+
|
|
10
|
+
A phase has a design source when its frontmatter declares one:
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
design: designs/checkout/payment-step.png # file in the repo
|
|
14
|
+
design: https://… # published Claude Design artboard
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No `design:` field means no parity check. Do not infer one from a phase
|
|
18
|
+
title, and do not invent a path — a check against a guessed design is worse
|
|
19
|
+
than no check.
|
|
20
|
+
|
|
21
|
+
Design files live under `designs/` at the repo root, mirroring the screen
|
|
22
|
+
structure. A URL is fetched at check time.
|
|
23
|
+
|
|
24
|
+
## What the check needs
|
|
25
|
+
|
|
26
|
+
Two images: the design, and the implemented screen. If you cannot produce
|
|
27
|
+
the second one, **the check cannot run** — say so plainly and mark the
|
|
28
|
+
criterion unverified. Do not approve on the strength of reading the code.
|
|
29
|
+
|
|
30
|
+
Producing it needs a render target and a capture tool:
|
|
31
|
+
|
|
32
|
+
| App | Render target | Capture |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| Web | react-cosmos fixture | browser tool screenshot |
|
|
35
|
+
| Expo | the screen in a simulator | simulator screenshot |
|
|
36
|
+
| Native iOS / Android | the screen in a simulator | simulator screenshot |
|
|
37
|
+
|
|
38
|
+
Capture with the session's own tools — a browser tool screenshot for web, the
|
|
39
|
+
simulator tool for mobile. Where the simulator integration is unavailable
|
|
40
|
+
(a machine whose `xcode-select` points elsewhere, a session without the
|
|
41
|
+
panel), fall back to the platform's own command and **say that you did**:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
xcrun simctl io booted screenshot designs/.captures/<phase>.png # iOS
|
|
45
|
+
adb exec-out screencap -p > designs/.captures/<phase>.png # Android
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If no capture is possible at all, report that as the reason the check was
|
|
49
|
+
skipped. That is an honest unverified, not a pass.
|
|
50
|
+
|
|
51
|
+
### Where the files live
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
designs/
|
|
55
|
+
├── <screen>.png the design, committed, mirroring the screen structure
|
|
56
|
+
├── .fetched/ a URL design, fetched once at milestone start
|
|
57
|
+
├── .captures/ what was built, committed so the PR can show it
|
|
58
|
+
└── .reports/ machine output of a comparison, gitignored
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Only the images a pull request shows are committed. A comparison tool's
|
|
62
|
+
output — JSON reports, logs, diffs — goes to `designs/.reports/`, which is
|
|
63
|
+
never committed: it is regenerated from the images, and one project committed
|
|
64
|
+
~100k lines of it, which agents then read back as context.
|
|
65
|
+
|
|
66
|
+
A URL is fetched **once** into `designs/.fetched/`, when lead composes the
|
|
67
|
+
phase, not at each comparison. The capture goes to `designs/.captures/` and is
|
|
68
|
+
committed with the change, because `gh` cannot upload an image and a
|
|
69
|
+
screenshot outside the branch cannot appear in a pull request. A design edited mid-milestone would otherwise silently change what
|
|
70
|
+
the phase was measured against, and the PR would show a pair that never
|
|
71
|
+
existed together.
|
|
72
|
+
|
|
73
|
+
Applies when the task has a design source. Who judges what is left over
|
|
74
|
+
depends on whether anyone is watching — the `--auto` flag.
|
|
75
|
+
|
|
76
|
+
## Measured where it can be measured, judged where it cannot
|
|
77
|
+
|
|
78
|
+
The goal is the design, exactly — not something in its spirit. But a literal
|
|
79
|
+
pixel threshold cannot be the gate: fonts hint differently, antialiasing
|
|
80
|
+
differs, and a capture at device scale is not the design's export scale, so
|
|
81
|
+
the difference is never zero and a run would never exit. A gate that can never
|
|
82
|
+
pass is a gate that gets disabled.
|
|
83
|
+
|
|
84
|
+
So the strictness goes where it can actually bite. Most of what people mean by
|
|
85
|
+
"pixel perfect" is a **number**, and numbers are checkable:
|
|
86
|
+
|
|
87
|
+
| What | How it is checked | Verdict if wrong |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| Spacing, padding, gaps | Measured off the capture against the design's value | **blocking** |
|
|
90
|
+
| Type size, weight, line height | Read from the design, compared to the token used | **blocking** |
|
|
91
|
+
| Colour | The design's value, matched to a token value | **blocking** |
|
|
92
|
+
| Radius, border width, elevation | Same | **blocking** |
|
|
93
|
+
| Element present, ordered, aligned | Read from both | **blocking** |
|
|
94
|
+
| Every state the design shows — hover, pressed, disabled, empty, error | Rendered and compared | **blocking** |
|
|
95
|
+
| Content: real strings, not lorem | Read from both | **blocking** |
|
|
96
|
+
| Font rendering, sub-pixel antialiasing, capture scale artefacts | — | non-blocking |
|
|
97
|
+
|
|
98
|
+
A colour or a spacing that is *close* is wrong. The app has a token module for
|
|
99
|
+
exactly this reason, and "16 where the design says 12" is not a judgement call
|
|
100
|
+
— it is a defect with a number attached. Report it that way: the value in the
|
|
101
|
+
design, the value in the build, and the token that should have carried it.
|
|
102
|
+
|
|
103
|
+
Only the last row is non-blocking, and it is the only row a machine cannot
|
|
104
|
+
decide. Everything above it, you can.
|
|
105
|
+
|
|
106
|
+
If a value in the design cannot be expressed by the app's token module, that is
|
|
107
|
+
a **blocker to report** — the design and the design system disagree, and
|
|
108
|
+
splitting the difference silently is how a token module stops being true.
|
|
109
|
+
|
|
110
|
+
## The value comes from the source, not from the pixels
|
|
111
|
+
|
|
112
|
+
The design's value in the table above is read from the source when the
|
|
113
|
+
design states it — CSS, a token file, an export with numbers — and sampled
|
|
114
|
+
from the image only when it does not. The capture is still needed for
|
|
115
|
+
what only a render can show: placement, order, states, whether an element is
|
|
116
|
+
there at all.
|
|
117
|
+
|
|
118
|
+
Recovering from pixels a number the design already wrote down means building
|
|
119
|
+
a measuring engine, and where the design renders through a different engine
|
|
120
|
+
than the app — an HTML board against a native screen — that engine never
|
|
121
|
+
converges, because what is left over is the renderers' difference, not the
|
|
122
|
+
build's. One run spent most of a weekly budget on exactly that.
|
|
123
|
+
|
|
124
|
+
## The non-blocking row is not promoted
|
|
125
|
+
|
|
126
|
+
Font rendering, sub-pixel antialiasing and capture scale artefacts stay
|
|
127
|
+
non-blocking. No agent makes them blocking — not lead by a decision, not the
|
|
128
|
+
architect by an RFC, not an engineer or a reviewer by a tolerance. Only the
|
|
129
|
+
user can, by saying so; the PM then writes it into that phase's acceptance
|
|
130
|
+
criteria, where lead and the reviewer read it.
|
|
131
|
+
|
|
132
|
+
"Perfect" and "pixel perfect" from the user mean the table above, exactly.
|
|
133
|
+
They are not a request for this row. If the words seem to ask for more, ask.
|
|
134
|
+
|
|
135
|
+
A check whose job is to tell a renderer difference from a defect is this row
|
|
136
|
+
being worked on, and it does not belong in the gate.
|
|
137
|
+
|
|
138
|
+
## Who fixes what
|
|
139
|
+
|
|
140
|
+
The agent building the screen fixes every blocking discrepancy it can see,
|
|
141
|
+
before it reports done. Always — "build it, then let someone else notice it is
|
|
142
|
+
wrong" is not a workflow, it is how an approximation reaches a reviewer with a
|
|
143
|
+
straight face.
|
|
144
|
+
|
|
145
|
+
What changes is who judges the remainder.
|
|
146
|
+
|
|
147
|
+
## Without `--auto` — the human judges
|
|
148
|
+
|
|
149
|
+
Both images go in the pull request body's table and a person decides. Lead
|
|
150
|
+
does not loop on its own: the builder already fixed what it could see, and the
|
|
151
|
+
call that is left is the one a person is better at.
|
|
152
|
+
|
|
153
|
+
`gh` cannot upload an image. Reference the design by its repo path or URL, and
|
|
154
|
+
commit the capture into the branch under `designs/.captures/` so the pull
|
|
155
|
+
request can show it. If you could not capture, write
|
|
156
|
+
"not captured — {reason}" in the cell rather than leaving it blank.
|
|
157
|
+
|
|
158
|
+
## With `--auto` — nobody is watching, so it loops
|
|
159
|
+
|
|
160
|
+
1. Implement, render, compare.
|
|
161
|
+
2. Blocking discrepancies → fix and repeat.
|
|
162
|
+
3. Stop at `delivery.max_design_iterations`.
|
|
163
|
+
4. On exhaustion, apply `delivery.on_parity_exhausted`:
|
|
164
|
+
- `tech-debt` — open a tech debt task naming the exact discrepancies that
|
|
165
|
+
remain, and move on. See
|
|
166
|
+
`.claude/rules/tracker-authority.orchestra.md`.
|
|
167
|
+
- `fail` — mark the phase failed and stop
|
|
168
|
+
|
|
169
|
+
The pull request still carries both images, and it still gets reviewed. The
|
|
170
|
+
loop exists because there is no human at the gate to catch what the builder
|
|
171
|
+
could not see, not because the work is invisible.
|
|
172
|
+
|
|
173
|
+
Never silently accept a screen that does not match. Never quietly raise the
|
|
174
|
+
iteration cap to make a run succeed.
|
|
175
|
+
|
|
176
|
+
## Render target
|
|
177
|
+
|
|
178
|
+
Prefer the component workbench over a live app screen. A fixture renders
|
|
179
|
+
deterministically; a real screen carries real data, loading states, and
|
|
180
|
+
timing, all of which produce differences that have nothing to do with the
|
|
181
|
+
design.
|