@mmerterden/multi-agent-pipeline 12.7.0 → 12.8.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 (244) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/install/_common.mjs +48 -0
  3. package/install/_dev-only-files.mjs +125 -5
  4. package/install/claude.mjs +14 -8
  5. package/install/copilot.mjs +5 -8
  6. package/package.json +17 -2
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -1
  8. package/pipeline/lib/credential-store.sh +20 -0
  9. package/pipeline/multi-agent-refs/_account-picker.md +1 -1
  10. package/pipeline/multi-agent-refs/_dev-context.md +1 -1
  11. package/pipeline/multi-agent-refs/_repo-picker.md +1 -1
  12. package/pipeline/multi-agent-refs/phases/operations.md +28 -0
  13. package/pipeline/multi-agent-refs/phases/phase-0-init.md +1 -0
  14. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
  15. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
  16. package/pipeline/multi-agent-refs/phases/phase-4-review.md +49 -4
  17. package/pipeline/schemas/prefs.schema.json +6 -0
  18. package/pipeline/scripts/_smoke-root.sh +61 -0
  19. package/pipeline/scripts/audit-log.sh +25 -0
  20. package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
  21. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
  22. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
  23. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
  24. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
  25. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
  26. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
  27. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
  28. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
  29. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
  30. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
  31. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
  32. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
  33. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
  34. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
  35. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
  36. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
  37. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
  38. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
  39. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
  40. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
  41. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
  42. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
  43. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
  44. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
  45. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
  46. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
  47. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
  48. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
  49. package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
  50. package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
  51. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
  52. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
  53. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
  54. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
  55. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
  56. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
  57. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
  58. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
  59. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
  60. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
  61. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
  62. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
  63. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
  64. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
  65. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
  66. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
  67. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
  68. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
  69. package/pipeline/eval/golden-tasks/README.md +0 -65
  70. package/pipeline/eval/intent-cases.json +0 -40
  71. package/pipeline/eval/run-metrics-fixture.json +0 -93
  72. package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
  73. package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
  74. package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
  75. package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
  76. package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
  77. package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
  78. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
  79. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
  80. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
  81. package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
  82. package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
  83. package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
  84. package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
  85. package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
  86. package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
  87. package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
  88. package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
  89. package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
  90. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
  91. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
  92. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
  93. package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
  94. package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
  95. package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
  96. package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
  97. package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
  98. package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
  99. package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
  100. package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
  101. package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
  102. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
  103. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
  104. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
  105. package/pipeline/eval/triage/README.md +0 -54
  106. package/pipeline/scripts/benchmark-phase-0.sh +0 -128
  107. package/pipeline/scripts/check-md-links.mjs +0 -88
  108. package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
  109. package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
  110. package/pipeline/scripts/eval-intent.mjs +0 -107
  111. package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
  112. package/pipeline/scripts/eval-triage.mjs +0 -171
  113. package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
  114. package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
  115. package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
  116. package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
  117. package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
  118. package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
  119. package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
  120. package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
  121. package/pipeline/scripts/lint-skills.mjs +0 -154
  122. package/pipeline/scripts/run-smokes.mjs +0 -130
  123. package/pipeline/scripts/scorecard.mjs +0 -258
  124. package/pipeline/scripts/smoke-add-detail.sh +0 -137
  125. package/pipeline/scripts/smoke-agent-guard.sh +0 -74
  126. package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
  127. package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
  128. package/pipeline/scripts/smoke-ask-choice.sh +0 -42
  129. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
  130. package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
  131. package/pipeline/scripts/smoke-changelog-version.sh +0 -47
  132. package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
  133. package/pipeline/scripts/smoke-channels-flow.sh +0 -130
  134. package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
  135. package/pipeline/scripts/smoke-clarify.sh +0 -148
  136. package/pipeline/scripts/smoke-command-inventory.sh +0 -81
  137. package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
  138. package/pipeline/scripts/smoke-community-gates.sh +0 -75
  139. package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
  140. package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
  141. package/pipeline/scripts/smoke-context-budget.sh +0 -72
  142. package/pipeline/scripts/smoke-cost-budget.sh +0 -70
  143. package/pipeline/scripts/smoke-cost-summary.sh +0 -139
  144. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
  145. package/pipeline/scripts/smoke-description-tr.sh +0 -82
  146. package/pipeline/scripts/smoke-dev-critic.sh +0 -144
  147. package/pipeline/scripts/smoke-diff-explain.sh +0 -147
  148. package/pipeline/scripts/smoke-diff-risk.sh +0 -190
  149. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
  150. package/pipeline/scripts/smoke-eval-live.sh +0 -136
  151. package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
  152. package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
  153. package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
  154. package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
  155. package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
  156. package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
  157. package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
  158. package/pipeline/scripts/smoke-generate-issue.sh +0 -120
  159. package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
  160. package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
  161. package/pipeline/scripts/smoke-install-layout.sh +0 -248
  162. package/pipeline/scripts/smoke-intent-guard.sh +0 -86
  163. package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
  164. package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
  165. package/pipeline/scripts/smoke-keychain.sh +0 -158
  166. package/pipeline/scripts/smoke-language-axis.sh +0 -109
  167. package/pipeline/scripts/smoke-learning-curve.sh +0 -61
  168. package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
  169. package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
  170. package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
  171. package/pipeline/scripts/smoke-md-links.sh +0 -8
  172. package/pipeline/scripts/smoke-md2confluence.sh +0 -126
  173. package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
  174. package/pipeline/scripts/smoke-migrate-state.sh +0 -102
  175. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
  176. package/pipeline/scripts/smoke-model-fallback.sh +0 -89
  177. package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
  178. package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
  179. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
  180. package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
  181. package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
  182. package/pipeline/scripts/smoke-pack-contents.sh +0 -140
  183. package/pipeline/scripts/smoke-pat-audit.sh +0 -128
  184. package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
  185. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
  186. package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
  187. package/pipeline/scripts/smoke-phase-banner.sh +0 -101
  188. package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
  189. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
  190. package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
  191. package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
  192. package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
  193. package/pipeline/scripts/smoke-plan-safety.sh +0 -139
  194. package/pipeline/scripts/smoke-plan-todos.sh +0 -196
  195. package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
  196. package/pipeline/scripts/smoke-pre-commit.sh +0 -170
  197. package/pipeline/scripts/smoke-pref-migration.sh +0 -226
  198. package/pipeline/scripts/smoke-prefs-language.sh +0 -134
  199. package/pipeline/scripts/smoke-progress-contract.sh +0 -127
  200. package/pipeline/scripts/smoke-prune-logs.sh +0 -137
  201. package/pipeline/scripts/smoke-purge.sh +0 -138
  202. package/pipeline/scripts/smoke-push-retry.sh +0 -75
  203. package/pipeline/scripts/smoke-repo-map.sh +0 -300
  204. package/pipeline/scripts/smoke-review-readiness.sh +0 -92
  205. package/pipeline/scripts/smoke-review-watch.sh +0 -146
  206. package/pipeline/scripts/smoke-routines.sh +0 -84
  207. package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
  208. package/pipeline/scripts/smoke-run-metrics.sh +0 -50
  209. package/pipeline/scripts/smoke-search.sh +0 -187
  210. package/pipeline/scripts/smoke-shadow-git.sh +0 -224
  211. package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
  212. package/pipeline/scripts/smoke-skill-language.sh +0 -83
  213. package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
  214. package/pipeline/scripts/smoke-skill-scan.sh +0 -198
  215. package/pipeline/scripts/smoke-source-parity.sh +0 -85
  216. package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
  217. package/pipeline/scripts/smoke-sync-parity.sh +0 -92
  218. package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
  219. package/pipeline/scripts/smoke-telemetry.sh +0 -147
  220. package/pipeline/scripts/smoke-test-gap.sh +0 -183
  221. package/pipeline/scripts/smoke-token-budget.sh +0 -67
  222. package/pipeline/scripts/smoke-token-preflight.sh +0 -82
  223. package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
  224. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
  225. package/pipeline/scripts/smoke-triage-memory.sh +0 -174
  226. package/pipeline/scripts/smoke-update-check.sh +0 -135
  227. package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
  228. package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
  229. package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
  230. package/pipeline/scripts/smoke-validator-gates.sh +0 -164
  231. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
  232. package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
  233. package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
  234. package/pipeline/scripts/smoke-work-summary.sh +0 -163
  235. package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
  236. package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
  237. package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
  238. package/pipeline/scripts/smoke-write-state.sh +0 -159
  239. package/pipeline/scripts/sync-parity-check.sh +0 -135
  240. package/pipeline/scripts/test-gap-rules/android.json +0 -25
  241. package/pipeline/scripts/test-gap-rules/ios.json +0 -34
  242. package/pipeline/scripts/test-gap-rules/node.json +0 -29
  243. package/pipeline/scripts/test-gap-rules/python.json +0 -25
  244. package/pipeline/scripts/validate-schemas.mjs +0 -88
@@ -112,11 +112,16 @@ Cost ledger: `phase-4.repo_map_emitted bytes=N budget=B cache_hit=true|false` -
112
112
 
113
113
  Before dispatching reviewers, run the deterministic diff risk scorer and inject the top-N files into each reviewer's prompt as a priority hint. **Advisory only - never gates the pipeline.** Heuristic, zero LLM, runs in well under a second.
114
114
 
115
+ Score the diff **once, in full** (no `--top`): Steps 1.76/1.77 need every scored file (a shrinking test file ranked 20th; whether ANY file is high-stakes). The top-N hint is derived from that report, not a second git walk.
116
+
115
117
  ```bash
116
- RISK_JSON=$(node pipeline/scripts/diff-risk-score.mjs \
118
+ RISK_FULL=$(node pipeline/scripts/diff-risk-score.mjs \
117
119
  --base "$BASE_BRANCH" --head HEAD \
118
- --task-id "$TASK_ID" --top 5 2>/dev/null)
119
- echo "$RISK_JSON" | node pipeline/scripts/validate-diff-risk.mjs - >/dev/null 2>&1 || RISK_JSON=""
120
+ --task-id "$TASK_ID" 2>/dev/null)
121
+ echo "$RISK_FULL" | node pipeline/scripts/validate-diff-risk.mjs - >/dev/null 2>&1 || RISK_FULL=""
122
+
123
+ # Priority hint for the reviewer prompts: top 5 of the full report.
124
+ RISK_JSON=$([ -n "$RISK_FULL" ] && jq -c '.files |= (sort_by(-.score) | .[:5])' <<< "$RISK_FULL" || echo "")
120
125
  ```
121
126
 
122
127
  **Signals & weights** (see `pipeline/schemas/diff-risk.schema.json`):
@@ -151,6 +156,35 @@ pipeline/scripts/log-metric.sh "$TASK_ID" 4 review.diff_risk \
151
156
 
152
157
  **Opt-out**: `prefs.global.diffRiskAdvisory = false` skips this step entirely (no script invocation, no priority block injection). Default `true` because the cost is bounded and the signal-to-noise has been measured against the golden-task fixture set.
153
158
 
159
+ #### Step 1.76 - Test-integrity gate (produces BLOCKING findings)
160
+
161
+ Step 1.75 uses `test_lines_removed` as an advisory hint only - too weak for what it detects: a suite made green by deleting tests instead of fixing code. This turns the signal into blocking findings triage must adjudicate. Pure function of the full report, no git, no LLM.
162
+
163
+ ```bash
164
+ TEST_INTEGRITY_JSON=$(printf '%s' "$RISK_FULL" | node pipeline/scripts/test-integrity-gate.mjs 2>/dev/null || echo "")
165
+ TI_COUNT=$(jq -r '.count // 0' <<< "${TEST_INTEGRITY_JSON:-{\}}" 2>/dev/null || echo 0)
166
+ [ "$TI_COUNT" -gt 0 ] && pipeline/scripts/log-metric.sh "$TASK_ID" 4 review.test_integrity findings="$TI_COUNT"
167
+ ```
168
+
169
+ `findings[]` are reviewer-shaped (`test_integrity`, `blocking`), so they merge into the reviewer findings at Step 3.0 and need no triage-prompt or `validate-triage.mjs` change. Triage keeps each blocking unless the removal is justified per the immutable-test rule (spec changed AND commit body names the test) → `deferred[]`.
170
+
171
+ The gate never blocks the phase; it *emits* blocking findings. Empty or unreadable input yields zero findings and the phase continues. Feed it the FULL report - `--top` hides a shrinking test file below the cut. **No opt-out**: a run that can switch off its own anti-reward-hacking control cannot be trusted to report a pass.
172
+
173
+ #### Step 1.77 - Reviewer scope (cost gate)
174
+
175
+ On a trivial diff every reviewer agrees and the extra models plus triage are paid for nothing. Reviewer count comes from the same report, no LLM.
176
+
177
+ ```bash
178
+ SCOPE_JSON=$(printf '%s' "$RISK_FULL" | node pipeline/scripts/review-scope.mjs 2>/dev/null \
179
+ || echo '{"scope":"full","reason":"no risk report - failing safe"}')
180
+ REVIEW_SCOPE=$(jq -r '.scope // "full"' <<< "$SCOPE_JSON")
181
+ pipeline/scripts/log-metric.sh "$TASK_ID" 4 review.scope scope="$REVIEW_SCOPE"
182
+ ```
183
+
184
+ `single` (Reviewer 1 only) requires **all** of: churn <= 20 lines, `totals.max_score` < 3.0, and no `security_path` / `migration` / `public_api` / `no_test_change` / `test_lines_removed` on any file. Anything else → `full`.
185
+
186
+ Fails safe in one direction only: empty report, parse failure or validator rejection all resolve to `full`, because skipping a reviewer trades coverage for cost. `consensus.reviewerCount` records what actually ran, so a single-reviewer run never reads as cross-model agreement. Opt-out: `prefs.global.reviewScopeGate = false` forces `full`.
187
+
154
188
  #### Step 1.8 - Figma visual-fidelity context (when task carries a Figma reference)
155
189
 
156
190
  When `state.evidence.figma[]` is non-empty, the reviewer subagents MUST receive the captured screenshot URLs / paths and the canonical-component name (from `state.evidence.figma[i].screenshotUrl` and `state.evidence.figma[i].codeConnectSnippets[0].componentName` when present) so they can compare visual fidelity. Pass them inline in each reviewer prompt under a `## Figma evidence` block, one row per frame.
@@ -182,6 +216,8 @@ Phase 4 sends the same diff to every reviewer and then to triage, so the diff is
182
216
 
183
217
  Launch Agent instances **in parallel** using the shared `code-reviewer` subagent definition (`~/.claude/agents/code-reviewer.md`). The reviewer set is determined by the host CLI - GPT-5.4 is only available on Copilot CLI, so Claude Code skips that reviewer and runs a 2-model parallel review; Copilot CLI runs all three.
184
218
 
219
+ **Scope from Step 1.77.** `$REVIEW_SCOPE == "single"` → dispatch **Reviewer 1 only**, and skip Step 2.5 + 3.6 (both no-ops with one reviewer). `"full"` (default + fail-safe) → the whole set below. Either way record the count in `consensus.reviewerCount`.
220
+
185
221
  | Reviewer | subagent_type | Model | Focus | Skills Referenced | Where it runs |
186
222
  | ---------- | ----------------- | ------------------- | --------------------------------- | --------------------------------------------- | -------------------- |
187
223
  | Reviewer 1 | `code-reviewer` | `claude-fable-5` (Claude Code) / `claude-opus-4-8` (Copilot CLI) | Deep security + architecture | `api-security-best-practices`, `architecture` | Both CLIs |
@@ -259,9 +295,18 @@ Exit 0 = valid. Exit 2 = contradiction (approved=true with blocking findings) -
259
295
 
260
296
  Opt-in empirical layer: when `prefs.global.verifyByTest.enabled` is `true`, accepted blocking findings additionally go through Step 3.7 (verify-by-test), which tries to reproduce each one with a minimal failing test before the Phase 3 rework loop fires. Full wiring: `$HOME/.claude/multi-agent-refs/features/verify-by-test.md`.
261
297
 
298
+ ##### 3.0 Merge the deterministic findings in
299
+
300
+ Append the Step 1.76 test-integrity findings to the reviewer findings before counting, so they are adjudicated like any reviewer finding rather than resolved by triage never seeing them:
301
+
302
+ ```bash
303
+ MERGED=$(jq -s '.[0] + (.[1].findings // [])' \
304
+ <(printf '%s' "$REVIEWER_FINDINGS_JSON") <(printf '%s' "${TEST_INTEGRITY_JSON:-{\}}"))
305
+ ```
306
+
262
307
  ##### 3.1 Short-circuit: no findings
263
308
 
264
- If merged findings `length === 0`, **skip triage**: write empty result `{"accepted": [], "deferred": [], "rejected": [], "approved": true}`, log, proceed to Phase 5.
309
+ If **merged** findings `length === 0`, **skip triage**: write empty result `{"accepted": [], "deferred": [], "rejected": [], "approved": true}`, log, proceed to Phase 5. Note this is the merged count from 3.0: a run with zero reviewer findings but a non-empty test-integrity set must NOT short-circuit.
265
310
 
266
311
  ##### 3.2 Launch triage agent
267
312
 
@@ -960,6 +960,12 @@
960
960
  "default": true,
961
961
  "description": "v8.3+ - Phase 4 Step 1.75 advisory diff risk scoring. When enabled, `pipeline/scripts/diff-risk-score.mjs` runs before reviewer dispatch and the top-N risk-ranked files are injected into each reviewer's prompt as a priority hint (security paths, public API surfaces, untested source changes, schema migrations). Heuristic, deterministic, no LLM cost. Default ON - the run is sub-second and never gates the pipeline. Flip to false to skip the script and the prompt injection entirely."
962
962
  },
963
+ "reviewScopeGate": {
964
+ "type": "boolean",
965
+ "default": true,
966
+ "description": "v12.8+ - Phase 4 Step 1.77 reviewer-scope gate. Decides the reviewer count from the deterministic diff-risk report via `pipeline/scripts/review-scope.mjs`: a diff under 20 lines of churn with max_score < 3.0 and no security_path / migration / public_api / no_test_change / test_lines_removed signal runs ONE reviewer instead of the full CLI-aware set (2 on Claude Code, 3 on Copilot CLI). No LLM. Fails safe in one direction only - any error, empty report or validator rejection resolves to the full set, because skipping a reviewer trades coverage for cost. Set false to force the full set on every diff.",
967
+ "$comment": "Shipped inert for a release: the script existed, was unit- and smoke-tested, and no phase doc referenced it, so every diff paid for the full reviewer set. Wired in Step 1.77; smoke-gate-wiring.sh keeps it reachable."
968
+ },
963
969
  "priorArtEnrichment": {
964
970
  "type": "object",
965
971
  "additionalProperties": false,
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # _smoke-root.sh - resolve the pipeline tree for a smoke that must run in BOTH
4
+ # the repo checkout and an end-user install.
5
+ #
6
+ # Why this exists: the repo and an install have different shapes.
7
+ #
8
+ # repo checkout install (~/.claude or ~/.copilot)
9
+ # ------------------ --------------------------------
10
+ # pipeline/scripts/ scripts/
11
+ # pipeline/schemas/ schemas/
12
+ # pipeline/multi-agent-refs/ multi-agent-refs/
13
+ # pipeline/commands/ commands/
14
+ # package.json (absent)
15
+ #
16
+ # A smoke that hardcodes `REPO_ROOT="$(dirname "$0")/../.."` and then reads
17
+ # `$REPO_ROOT/pipeline/...` resolves to `$HOME/pipeline/...` once installed, so
18
+ # every path check fails. Whoever ran it sees a red report and concludes the
19
+ # install is broken. Source this file instead and read the MA_* variables.
20
+ #
21
+ # Usage (from any script in pipeline/scripts/):
22
+ # . "$(dirname "${BASH_SOURCE[0]}")/_smoke-root.sh"
23
+ # grep -q needle "$MA_REFS/phases/phase-4-review.md"
24
+ #
25
+ # Exports:
26
+ # MA_LAYOUT "repo" | "install"
27
+ # MA_ROOT repo root, or the install root (~/.claude)
28
+ # MA_SCRIPTS directory holding this script
29
+ # MA_SCHEMAS JSON schemas
30
+ # MA_REFS multi-agent-refs
31
+ # MA_COMMANDS slash-command tree
32
+ # MA_SKILLS skills tree
33
+ #
34
+ # Every exported directory is verified to exist; a missing one is left empty so
35
+ # the caller can skip that check rather than report a false failure.
36
+
37
+ MA_SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38
+
39
+ # The repo is the only layout with pipeline/ beside package.json. Checking both
40
+ # rules out an install that happens to sit next to an unrelated pipeline/ dir.
41
+ _ma_candidate="$(cd "$MA_SCRIPTS/../.." && pwd)"
42
+ if [ -f "$_ma_candidate/package.json" ] && [ -d "$_ma_candidate/pipeline" ]; then
43
+ MA_LAYOUT="repo"
44
+ MA_ROOT="$_ma_candidate"
45
+ _ma_base="$_ma_candidate/pipeline"
46
+ else
47
+ MA_LAYOUT="install"
48
+ MA_ROOT="$(cd "$MA_SCRIPTS/.." && pwd)"
49
+ _ma_base="$MA_ROOT"
50
+ fi
51
+ unset _ma_candidate
52
+
53
+ _ma_dir() { [ -d "$1" ] && echo "$1" || echo ""; }
54
+
55
+ MA_SCHEMAS="$(_ma_dir "$_ma_base/schemas")"
56
+ MA_REFS="$(_ma_dir "$_ma_base/multi-agent-refs")"
57
+ MA_COMMANDS="$(_ma_dir "$_ma_base/commands")"
58
+ MA_SKILLS="$(_ma_dir "$_ma_base/skills")"
59
+ unset _ma_base
60
+
61
+ export MA_LAYOUT MA_ROOT MA_SCRIPTS MA_SCHEMAS MA_REFS MA_COMMANDS MA_SKILLS
@@ -80,3 +80,28 @@ LINE="{\"ts\":\"${TS}\",\"event\":\"$(json_escape "$EVENT")\",\"service\":\"$(js
80
80
  LINE="${LINE}}"
81
81
 
82
82
  echo "$LINE" >> "$AUDIT_FILE" 2>/dev/null || true
83
+
84
+ # Opportunistic rotation. audit-log-rotate.sh documents a launchd/cron install,
85
+ # but nothing ever told a user to set that up, so an unrotated trail would grow
86
+ # without bound now that credential-store.sh writes an event on every PAT lookup.
87
+ # Self-triggering removes the operator step: check a cheap size threshold and hand
88
+ # off to the real rotator, which is atomic against concurrent appenders (it moves
89
+ # the live inode first). Silent and non-blocking - telemetry never fails a run.
90
+ ROTATE_AT_BYTES="${AUDIT_ROTATE_AT_BYTES:-1048576}"
91
+ _audit_size() {
92
+ case "$(uname -s)" in
93
+ Darwin|*BSD*) stat -f %z "$1" 2>/dev/null || echo 0 ;;
94
+ *) stat -c %s "$1" 2>/dev/null || echo 0 ;;
95
+ esac
96
+ }
97
+ if [ "${AUDIT_SELF_ROTATE:-1}" = "1" ] && [ -f "$AUDIT_FILE" ]; then
98
+ SIZE="$(_audit_size "$AUDIT_FILE")"
99
+ if [ "${SIZE:-0}" -ge "$ROTATE_AT_BYTES" ]; then
100
+ ROTATOR="$(dirname "${BASH_SOURCE[0]:-$0}")/audit-log-rotate.sh"
101
+ # The rotator derives AUDIT_FILE from AUDIT_DIR, so pass the DIRECTORY.
102
+ # Handing it AUDIT_FILE would leave it rotating the default path while a
103
+ # caller-overridden trail kept growing.
104
+ [ -f "$ROTATOR" ] &&
105
+ AUDIT_DIR="$(dirname "$AUDIT_FILE")" bash "$ROTATOR" >/dev/null 2>&1 || true
106
+ fi
107
+ fi
@@ -190,22 +190,36 @@ fi
190
190
  # (the orchestrator dispatches per the doc), so lock the CONTRACT here against
191
191
  # drift across the phase doc, the schema, and the consensus block.
192
192
  echo "→ reviewer-count contract (Claude=2, Copilot=3)"
193
- REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
194
- P4="$REPO_ROOT/pipeline/multi-agent-refs/phases/phase-4-review.md"
195
- REVSCHEMA="$REPO_ROOT/pipeline/schemas/reviewer-output.schema.json"
196
- TRSCHEMA="$REPO_ROOT/pipeline/schemas/triage-output.schema.json"
197
- if grep -qiE "Claude Code (dispatches|=|:) ?2|2-model" "$P4" && grep -qiE "Copilot CLI (dispatches|=|:) ?3|3-model" "$P4"; then
193
+ # `/multi-agent:update` runs this smoke on the user's machine, where the tree is
194
+ # ~/.claude/{schemas,multi-agent-refs} and NOT <root>/pipeline/*. Resolving the
195
+ # root by hand here reported three phantom failures in an install, which reads as
196
+ # a broken installation. _smoke-root.sh handles both layouts.
197
+ # shellcheck source=pipeline/scripts/_smoke-root.sh
198
+ . "$(dirname "${BASH_SOURCE[0]}")/_smoke-root.sh"
199
+ P4="${MA_REFS:+$MA_REFS/phases/phase-4-review.md}"
200
+ REVSCHEMA="${MA_SCHEMAS:+$MA_SCHEMAS/reviewer-output.schema.json}"
201
+ TRSCHEMA="${MA_SCHEMAS:+$MA_SCHEMAS/triage-output.schema.json}"
202
+
203
+ if [ -z "$P4" ] || [ ! -f "$P4" ]; then
204
+ echo " ↷ SKIP: phase-4-review.md not present in this $MA_LAYOUT layout"
205
+ elif grep -qiE "Claude Code (dispatches|=|:) ?2|2-model" "$P4" && grep -qiE "Copilot CLI (dispatches|=|:) ?3|3-model" "$P4"; then
198
206
  pass "phase-4-review declares Claude=2 / Copilot=3 reviewers"
199
207
  else
200
208
  fail "phase-4-review does not declare the CLI-aware reviewer count"
201
209
  fi
202
- if grep -qi "Copilot CLI" "$REVSCHEMA" && grep -qi "Claude" "$REVSCHEMA"; then
210
+
211
+ if [ -z "$REVSCHEMA" ] || [ ! -f "$REVSCHEMA" ]; then
212
+ echo " ↷ SKIP: reviewer-output.schema.json not present in this $MA_LAYOUT layout"
213
+ elif grep -qi "Copilot CLI" "$REVSCHEMA" && grep -qi "Claude" "$REVSCHEMA"; then
203
214
  pass "reviewer-output schema notes the CLI-aware reviewer set"
204
215
  else
205
216
  fail "reviewer-output schema missing the CLI-aware note"
206
217
  fi
218
+
207
219
  # consensus.reviewerCount must accommodate both 2 and 3 (min 1, no max < 3)
208
- if grep -q '"reviewerCount"' "$TRSCHEMA" && grep -qi "Claude Code = 2, Copilot CLI = 3" "$TRSCHEMA"; then
220
+ if [ -z "$TRSCHEMA" ] || [ ! -f "$TRSCHEMA" ]; then
221
+ echo " ↷ SKIP: triage-output.schema.json not present in this $MA_LAYOUT layout"
222
+ elif grep -q '"reviewerCount"' "$TRSCHEMA" && grep -qi "Claude Code = 2, Copilot CLI = 3" "$TRSCHEMA"; then
209
223
  pass "triage consensus.reviewerCount documents 2 (Claude) and 3 (Copilot)"
210
224
  else
211
225
  fail "triage consensus.reviewerCount does not document the 2/3 split"
@@ -1,25 +0,0 @@
1
- {
2
- "stack": {
3
- "primary": "ios",
4
- "language": "Swift 6",
5
- "framework": "SwiftUI"
6
- },
7
- "touchedAreas": [
8
- {
9
- "path": "Sources/Auth/LoginView.swift",
10
- "why": "Hardcoded Color.white yerine DesignSystem.Color.Primary.onBackground kullanacak"
11
- },
12
- {
13
- "path": "Sources/Auth/LoginViewTests.swift",
14
- "why": "Dark mode snapshot + ViewInspector contrast assertion eklenecek"
15
- }
16
- ],
17
- "risks": [
18
- {
19
- "risk": "LoginView başka ekran tarafından preview alanı olarak kullanılıyor olabilir - token değişimi preview snapshot'larını kırabilir",
20
- "severity": "low",
21
- "mitigation": "Preview varyantlarını snapshot'lar ile koruyarak incremental değiştir"
22
- }
23
- ],
24
- "summary": "LoginView'de primary button rengi hardcoded Color.white. Token sistemi ile değiştirilip light+dark mode için ayrık snapshot testleri eklenecek. Değişim iki dosya ile sınırlı, regresyon riski düşük."
25
- }
@@ -1,30 +0,0 @@
1
- {
2
- "approach": "TDD-first. Önce dark-mode contrast için snapshot + ViewInspector testi yazılır (RED), sonra LoginView'de hardcoded renk token çağrısıyla değiştirilir (GREEN). Refactor: aynı dosyada ikinci hardcoded renk varsa tek geçişte temizlenir.",
3
- "tasks": [
4
- {
5
- "id": "T1",
6
- "title": "Add failing dark mode snapshot + contrast ViewInspector test",
7
- "type": "test",
8
- "files": ["Sources/Auth/LoginViewTests.swift"],
9
- "dependsOn": [],
10
- "acceptanceCriteria": [
11
- "Snapshot test for dark mode renders primary button",
12
- "ViewInspector asserts foreground color != Color.white when env.colorScheme == .dark"
13
- ]
14
- },
15
- {
16
- "id": "T2",
17
- "title": "Replace hardcoded Color.white with DesignSystem token",
18
- "type": "code",
19
- "files": ["Sources/Auth/LoginView.swift"],
20
- "dependsOn": ["T1"],
21
- "acceptanceCriteria": [
22
- "No Color(red:..., green:..., blue:...) or Color.white remains in LoginView",
23
- "Primary button uses DesignSystem.Color.Primary.onBackground",
24
- "All snapshot + inspector tests pass"
25
- ]
26
- }
27
- ],
28
- "approved": true,
29
- "userFeedback": null
30
- }
@@ -1,20 +0,0 @@
1
- [
2
- {
3
- "reviewer": "opus",
4
- "findings": [
5
- {
6
- "severity": "blocking",
7
- "file": "Sources/Auth/LoginView.swift",
8
- "line": 42,
9
- "issue": "Hardcoded .font(.system(size: 14)) kaldı - aynı fix sırasında typography token'ına da bağlanmalı",
10
- "fix": "Replace with .typographyStyle(.body1) - same pattern as the color token migration"
11
- }
12
- ],
13
- "approved": false
14
- },
15
- {
16
- "reviewer": "sonnet",
17
- "findings": [],
18
- "approved": true
19
- }
20
- ]
@@ -1,15 +0,0 @@
1
- {
2
- "accepted": [
3
- {
4
- "severity": "blocking",
5
- "file": "Sources/Auth/LoginView.swift",
6
- "line": 42,
7
- "issue": "Hardcoded .font(.system(size: 14)) kaldı - aynı fix sırasında typography token'ına da bağlanmalı",
8
- "fix": "Replace with .typographyStyle(.body1) - same pattern as the color token migration",
9
- "reviewer": "opus"
10
- }
11
- ],
12
- "deferred": [],
13
- "rejected": [],
14
- "approved": false
15
- }
@@ -1,14 +0,0 @@
1
- {
2
- "focus": "SwiftUI bugfix path - verifies that Phase 4 picks up an in-scope but adjacent blocker (token drift) even when the primary issue is resolved.",
3
- "exercises": [
4
- "Phase 1 stack detection (iOS / Swift 6 / SwiftUI)",
5
- "Phase 2 TDD ordering (test before code)",
6
- "Phase 4 Reviewer-1 catches adjacent token drift; Reviewer-2 approves base fix",
7
- "Triage keeps the blocker accepted, no deferrals, not approved"
8
- ],
9
- "regressionsCaught": [
10
- "If stack detection drops SwiftUI from the framework field, this fixture fails the analysis schema check",
11
- "If planning schema drops the 'dependsOn' field, T2 loses its order guarantee",
12
- "If Phase 4 no longer tolerates Reviewer-1 blocker + Reviewer-2 approval (approved divergence), triage fixture fails"
13
- ]
14
- }
@@ -1,12 +0,0 @@
1
- {
2
- "title": "LoginView dark mode contrast fix",
3
- "description": "Dark mode'da primary button text'i zemin rengiyle düşük kontrast veriyor. Tokens switch'i çalışmıyor çünkü LoginView hardcoded Color.white kullanıyor. Design tokens'a geri bağla.",
4
- "inputType": "free-text",
5
- "expectedStack": "ios",
6
- "expectedLanguage": "Swift",
7
- "expectedFramework": "SwiftUI",
8
- "expectedBlockers": 1,
9
- "expectedDeferrals": 0,
10
- "baseBranch": "develop",
11
- "taskBranch": "bugfix/loginview-dark-mode-contrast"
12
- }
@@ -1,29 +0,0 @@
1
- {
2
- "stack": {
3
- "primary": "android",
4
- "language": "Kotlin 2.1",
5
- "framework": "Jetpack Compose"
6
- },
7
- "touchedAreas": [
8
- {
9
- "path": "feature/orders/src/main/java/com/example/orders/OrdersScreen.kt",
10
- "why": "PullToRefreshBox UI eklenecek, ViewModel.refresh() tetiklenecek"
11
- },
12
- {
13
- "path": "feature/orders/src/main/java/com/example/orders/OrdersViewModel.kt",
14
- "why": "isRefreshing state flow eklenecek - PullToRefreshBox'ın state binding'i için"
15
- },
16
- {
17
- "path": "feature/orders/src/test/java/com/example/orders/OrdersScreenTest.kt",
18
- "why": "Pull-to-refresh compose testi eklenecek"
19
- }
20
- ],
21
- "risks": [
22
- {
23
- "risk": "Material3 PullToRefreshBox henüz beta API - 1.3.x'de signature değişti",
24
- "severity": "medium",
25
- "mitigation": "Existing libs.versions.toml'de material3 sürümünü teyit et, gerekirse bump et"
26
- }
27
- ],
28
- "summary": "OrdersScreen Compose ekranına Material3 pull-to-refresh eklenir. ViewModel'de zaten refresh() var; UI binding + isRefreshing state + Compose testi yeterli. Material3 API surface'i beta olduğu için version check gerekir, risk orta."
29
- }
@@ -1,43 +0,0 @@
1
- {
2
- "approach": "ViewModel state genişletilir (isRefreshing StateFlow), UI PullToRefreshBox'a bağlanır, Compose test yazılır. TDD: önce Compose testi refresh davranışını doğrulamalı (RED), sonra ViewModel + UI bağlanır.",
3
- "tasks": [
4
- {
5
- "id": "T1",
6
- "title": "Add failing Compose test for pull-to-refresh trigger",
7
- "type": "test",
8
- "files": ["feature/orders/src/test/java/com/example/orders/OrdersScreenTest.kt"],
9
- "dependsOn": [],
10
- "acceptanceCriteria": [
11
- "Test performTouchInput { swipeDown() } on OrdersScreen",
12
- "Asserts ViewModel.refresh() is called",
13
- "Test initially fails because PullToRefreshBox is not wired"
14
- ]
15
- },
16
- {
17
- "id": "T2",
18
- "title": "Add isRefreshing StateFlow to OrdersViewModel",
19
- "type": "code",
20
- "files": ["feature/orders/src/main/java/com/example/orders/OrdersViewModel.kt"],
21
- "dependsOn": ["T1"],
22
- "acceptanceCriteria": [
23
- "isRefreshing: StateFlow<Boolean> exposed",
24
- "refresh() sets isRefreshing = true, then false on completion"
25
- ]
26
- },
27
- {
28
- "id": "T3",
29
- "title": "Wire PullToRefreshBox in OrdersScreen",
30
- "type": "code",
31
- "files": ["feature/orders/src/main/java/com/example/orders/OrdersScreen.kt"],
32
- "dependsOn": ["T2"],
33
- "acceptanceCriteria": [
34
- "PullToRefreshBox wraps the list",
35
- "isRefreshing bound from ViewModel state",
36
- "onRefresh calls viewModel::refresh",
37
- "Compose test from T1 passes"
38
- ]
39
- }
40
- ],
41
- "approved": true,
42
- "userFeedback": null
43
- }
@@ -1,35 +0,0 @@
1
- [
2
- {
3
- "reviewer": "opus",
4
- "findings": [
5
- {
6
- "severity": "blocking",
7
- "file": "feature/orders/src/main/java/com/example/orders/OrdersViewModel.kt",
8
- "line": 28,
9
- "issue": "refresh() içinde isRefreshing = false finally bloğunda değil - exception fırlatıldığında spinner sonsuza dek takılır",
10
- "fix": "Wrap the body in try/finally and set _isRefreshing.value = false in finally"
11
- },
12
- {
13
- "severity": "important",
14
- "file": "feature/orders/src/main/java/com/example/orders/OrdersScreen.kt",
15
- "line": 0,
16
- "issue": "Yeni modifier chain lazılarla LazyColumn semantics test matcher'larını kırabilir - Compose stability endişesi var",
17
- "fix": "Measure recomposition count with Compose Compiler Reports before merging"
18
- }
19
- ],
20
- "approved": false
21
- },
22
- {
23
- "reviewer": "sonnet",
24
- "findings": [
25
- {
26
- "severity": "important",
27
- "file": "feature/orders/src/main/java/com/example/orders/OrdersScreen.kt",
28
- "line": 54,
29
- "issue": "PullToRefreshState hoisted yerine composable içinde tutuluyor - configuration change'de state kaybı",
30
- "fix": "Use rememberSaveable or hoist the state up to ViewModel"
31
- }
32
- ],
33
- "approved": false
34
- }
35
- ]
@@ -1,35 +0,0 @@
1
- {
2
- "accepted": [
3
- {
4
- "severity": "blocking",
5
- "file": "feature/orders/src/main/java/com/example/orders/OrdersViewModel.kt",
6
- "line": 28,
7
- "issue": "refresh() içinde isRefreshing = false finally bloğunda değil - exception fırlatıldığında spinner sonsuza dek takılır",
8
- "fix": "Wrap the body in try/finally and set _isRefreshing.value = false in finally",
9
- "reviewer": "opus"
10
- },
11
- {
12
- "severity": "important",
13
- "file": "feature/orders/src/main/java/com/example/orders/OrdersScreen.kt",
14
- "line": 54,
15
- "issue": "PullToRefreshState hoisted yerine composable içinde tutuluyor - configuration change'de state kaybı",
16
- "fix": "Use rememberSaveable or hoist the state up to ViewModel",
17
- "reviewer": "sonnet"
18
- }
19
- ],
20
- "deferred": [
21
- {
22
- "finding": {
23
- "severity": "important",
24
- "file": "feature/orders/src/main/java/com/example/orders/OrdersScreen.kt",
25
- "line": 0,
26
- "issue": "Yeni modifier chain lazılarla LazyColumn semantics test matcher'larını kırabilir - Compose stability endişesi var",
27
- "fix": "Measure recomposition count with Compose Compiler Reports before merging",
28
- "reviewer": "opus"
29
- },
30
- "reason": "Out of scope for this task - recomposition audit is a performance sweep, not a correctness gate for adding pull-to-refresh"
31
- }
32
- ],
33
- "rejected": [],
34
- "approved": false
35
- }
@@ -1,14 +0,0 @@
1
- {
2
- "focus": "Android/Compose feature path with mixed-verdict triage - one finding deferred out-of-scope, two accepted (one blocker + one important cross-reviewer match).",
3
- "exercises": [
4
- "Phase 1 stack detection (android / Kotlin 2.1 / Jetpack Compose)",
5
- "Phase 2 3-task decomposition with clear dependency chain (T1 test → T2 VM → T3 UI)",
6
- "Phase 4 Reviewer-1 + Reviewer-2 surface overlapping concerns at different lines",
7
- "Triage deferred bucket works (important but out-of-scope finding parked, not actioned)"
8
- ],
9
- "regressionsCaught": [
10
- "If triage schema removes the 'deferred' bucket, this fixture fails",
11
- "If reviewer enum drops a required reviewer, accepted[] loses its reviewer attribution and schema fails",
12
- "If planning schema drops 'dependsOn', T1→T2→T3 ordering becomes unverifiable"
13
- ]
14
- }
@@ -1,12 +0,0 @@
1
- {
2
- "title": "Add pull-to-refresh on OrdersScreen",
3
- "description": "Compose OrdersScreen'de swipe-to-refresh yok. Material3 PullToRefreshBox eklenmeli, ViewModel refresh() zaten var - UI tarafı bağlanmadı.",
4
- "inputType": "free-text",
5
- "expectedStack": "android",
6
- "expectedLanguage": "Kotlin",
7
- "expectedFramework": "Jetpack Compose",
8
- "expectedBlockers": 1,
9
- "expectedDeferrals": 1,
10
- "baseBranch": "develop",
11
- "taskBranch": "feature/orders-pull-to-refresh"
12
- }
@@ -1,29 +0,0 @@
1
- {
2
- "stack": {
3
- "primary": "backend",
4
- "language": "Python 3.13",
5
- "framework": "FastAPI"
6
- },
7
- "touchedAreas": [
8
- {
9
- "path": "app/middleware/rate_limit.py",
10
- "why": "New sliding-window per-IP limiter implemented as ASGI middleware"
11
- },
12
- {
13
- "path": "app/api/auth.py",
14
- "why": "Login router registers the limiter and returns 429 + Retry-After on breach"
15
- },
16
- {
17
- "path": "tests/test_rate_limit.py",
18
- "why": "Covers allowed-under-limit, blocked-over-limit, and window reset"
19
- }
20
- ],
21
- "risks": [
22
- {
23
- "risk": "An in-memory counter does not share state across Gunicorn/Uvicorn workers, so the limit is per-process not per-cluster",
24
- "severity": "medium",
25
- "mitigation": "Document the single-process assumption now; back it with Redis when the service scales horizontally"
26
- }
27
- ],
28
- "summary": "Brute-force on POST /auth/login is unthrottled. Add a sliding-window per-IP limiter as middleware, wire it into the auth router, and return 429 with Retry-After. Scope is three files; the main caveat is multi-worker state sharing, deferred to a Redis-backed follow-up."
29
- }
@@ -1,42 +0,0 @@
1
- {
2
- "approach": "TDD-first. Write a failing test that fires N+1 requests from one IP and asserts the last one returns 429 with Retry-After (RED). Implement the sliding-window limiter middleware (GREEN). Wire it into the auth router and assert allowed traffic still passes (refactor + integration).",
3
- "tasks": [
4
- {
5
- "id": "T1",
6
- "title": "Add failing rate-limit tests (under limit, over limit, window reset)",
7
- "type": "test",
8
- "files": ["tests/test_rate_limit.py"],
9
- "dependsOn": [],
10
- "acceptanceCriteria": [
11
- "N requests under the limit all return 200",
12
- "The N+1th request within the window returns 429 with a Retry-After header",
13
- "After the window elapses, requests are allowed again"
14
- ]
15
- },
16
- {
17
- "id": "T2",
18
- "title": "Implement sliding-window per-IP limiter middleware",
19
- "type": "code",
20
- "files": ["app/middleware/rate_limit.py"],
21
- "dependsOn": ["T1"],
22
- "acceptanceCriteria": [
23
- "Tracks request timestamps per client IP in a sliding window",
24
- "Emits 429 with Retry-After once the threshold is exceeded",
25
- "Window bookkeeping is safe under concurrent requests"
26
- ]
27
- },
28
- {
29
- "id": "T3",
30
- "title": "Register the limiter on the auth login route",
31
- "type": "code",
32
- "files": ["app/api/auth.py"],
33
- "dependsOn": ["T2"],
34
- "acceptanceCriteria": [
35
- "POST /auth/login passes through the limiter",
36
- "Normal login traffic under the limit is unaffected"
37
- ]
38
- }
39
- ],
40
- "approved": true,
41
- "userFeedback": null
42
- }
@@ -1,20 +0,0 @@
1
- [
2
- {
3
- "reviewer": "opus",
4
- "findings": [
5
- {
6
- "severity": "blocking",
7
- "file": "app/middleware/rate_limit.py",
8
- "line": 38,
9
- "issue": "The per-IP window dict is read-modified-written without a lock; under concurrent requests two coroutines can both pass the check and under-count, letting bursts through",
10
- "fix": "Guard the read-modify-write with an asyncio.Lock keyed per IP, or use an atomic counter structure"
11
- }
12
- ],
13
- "approved": false
14
- },
15
- {
16
- "reviewer": "sonnet",
17
- "findings": [],
18
- "approved": true
19
- }
20
- ]