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