@mmerterden/multi-agent-pipeline 12.7.0 → 12.9.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 (247) hide show
  1. package/CHANGELOG.md +207 -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/features/review-multi-repo.md +65 -0
  13. package/pipeline/multi-agent-refs/features/url-enrichment.md +93 -0
  14. package/pipeline/multi-agent-refs/phases/operations.md +28 -0
  15. package/pipeline/multi-agent-refs/phases/phase-0-init.md +3 -83
  16. package/pipeline/multi-agent-refs/phases/phase-2-planning.md +1 -1
  17. package/pipeline/multi-agent-refs/phases/phase-3-dev.md +1 -2
  18. package/pipeline/multi-agent-refs/phases/phase-4-review.md +50 -62
  19. package/pipeline/schemas/prefs.schema.json +6 -0
  20. package/pipeline/scripts/_smoke-root.sh +61 -0
  21. package/pipeline/scripts/agent-guard.py +57 -3
  22. package/pipeline/scripts/audit-log.sh +25 -0
  23. package/pipeline/scripts/smoke-cross-cli-behavior.sh +21 -7
  24. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-1-analysis.json +0 -25
  25. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-2-plan.json +0 -30
  26. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-review.json +0 -20
  27. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/expected/phase-4-triage.json +0 -15
  28. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/metadata.json +0 -14
  29. package/pipeline/eval/golden-tasks/01-ios-bugfix-darkmode/task.json +0 -12
  30. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-1-analysis.json +0 -29
  31. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-2-plan.json +0 -43
  32. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-review.json +0 -35
  33. package/pipeline/eval/golden-tasks/02-android-feature-compose/expected/phase-4-triage.json +0 -35
  34. package/pipeline/eval/golden-tasks/02-android-feature-compose/metadata.json +0 -14
  35. package/pipeline/eval/golden-tasks/02-android-feature-compose/task.json +0 -12
  36. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-1-analysis.json +0 -29
  37. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-2-plan.json +0 -42
  38. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-review.json +0 -20
  39. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/expected/phase-4-triage.json +0 -15
  40. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/metadata.json +0 -14
  41. package/pipeline/eval/golden-tasks/03-backend-python-ratelimit/task.json +0 -12
  42. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-1-analysis.json +0 -29
  43. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-2-plan.json +0 -40
  44. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-review.json +0 -20
  45. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/expected/phase-4-triage.json +0 -15
  46. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/metadata.json +0 -14
  47. package/pipeline/eval/golden-tasks/04-frontend-next-hydration/task.json +0 -12
  48. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-1-analysis.json +0 -29
  49. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-2-plan.json +0 -42
  50. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-review.json +0 -28
  51. package/pipeline/eval/golden-tasks/05-ios-security-keychain/expected/phase-4-triage.json +0 -27
  52. package/pipeline/eval/golden-tasks/05-ios-security-keychain/metadata.json +0 -14
  53. package/pipeline/eval/golden-tasks/05-ios-security-keychain/task.json +0 -12
  54. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-1-analysis.json +0 -29
  55. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-2-plan.json +0 -41
  56. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-review.json +0 -12
  57. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/expected/phase-4-triage.json +0 -6
  58. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/metadata.json +0 -14
  59. package/pipeline/eval/golden-tasks/06-android-refactor-usecase/task.json +0 -12
  60. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-1-analysis.json +0 -29
  61. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-2-plan.json +0 -42
  62. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-review.json +0 -28
  63. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/expected/phase-4-triage.json +0 -27
  64. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/metadata.json +0 -14
  65. package/pipeline/eval/golden-tasks/07-backend-node-idempotency/task.json +0 -12
  66. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-1-analysis.json +0 -25
  67. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-2-plan.json +0 -31
  68. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-review.json +0 -12
  69. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/expected/phase-4-triage.json +0 -18
  70. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/metadata.json +0 -14
  71. package/pipeline/eval/golden-tasks/08-ios-auth-consensus-unverified/task.json +0 -12
  72. package/pipeline/eval/golden-tasks/README.md +0 -65
  73. package/pipeline/eval/intent-cases.json +0 -40
  74. package/pipeline/eval/run-metrics-fixture.json +0 -93
  75. package/pipeline/eval/triage/01-empty-findings/expected.json +0 -6
  76. package/pipeline/eval/triage/01-empty-findings/input.json +0 -5
  77. package/pipeline/eval/triage/01-empty-findings/notes.md +0 -7
  78. package/pipeline/eval/triage/02-real-blocker/expected.json +0 -15
  79. package/pipeline/eval/triage/02-real-blocker/input.json +0 -14
  80. package/pipeline/eval/triage/02-real-blocker/notes.md +0 -7
  81. package/pipeline/eval/triage/03-out-of-scope-defer/expected.json +0 -18
  82. package/pipeline/eval/triage/03-out-of-scope-defer/input.json +0 -14
  83. package/pipeline/eval/triage/03-out-of-scope-defer/notes.md +0 -10
  84. package/pipeline/eval/triage/04-false-positive-reject/expected.json +0 -18
  85. package/pipeline/eval/triage/04-false-positive-reject/input.json +0 -14
  86. package/pipeline/eval/triage/04-false-positive-reject/notes.md +0 -10
  87. package/pipeline/eval/triage/05-mixed-classification/expected.json +0 -43
  88. package/pipeline/eval/triage/05-mixed-classification/input.json +0 -38
  89. package/pipeline/eval/triage/05-mixed-classification/notes.md +0 -17
  90. package/pipeline/eval/triage/06-severity-mismatch/expected.json +0 -15
  91. package/pipeline/eval/triage/06-severity-mismatch/input.json +0 -14
  92. package/pipeline/eval/triage/06-severity-mismatch/notes.md +0 -9
  93. package/pipeline/eval/triage/07-duplicate-reviewers/expected.json +0 -27
  94. package/pipeline/eval/triage/07-duplicate-reviewers/input.json +0 -22
  95. package/pipeline/eval/triage/07-duplicate-reviewers/notes.md +0 -9
  96. package/pipeline/eval/triage/08-style-misclassified/expected.json +0 -18
  97. package/pipeline/eval/triage/08-style-misclassified/input.json +0 -14
  98. package/pipeline/eval/triage/08-style-misclassified/notes.md +0 -9
  99. package/pipeline/eval/triage/09-cascading-finding/expected.json +0 -23
  100. package/pipeline/eval/triage/09-cascading-finding/input.json +0 -22
  101. package/pipeline/eval/triage/09-cascading-finding/notes.md +0 -9
  102. package/pipeline/eval/triage/10-deferred-crossref/expected.json +0 -18
  103. package/pipeline/eval/triage/10-deferred-crossref/input.json +0 -14
  104. package/pipeline/eval/triage/10-deferred-crossref/notes.md +0 -9
  105. package/pipeline/eval/triage/11-vercel-token-leak-blocker/expected.json +0 -27
  106. package/pipeline/eval/triage/11-vercel-token-leak-blocker/input.json +0 -22
  107. package/pipeline/eval/triage/11-vercel-token-leak-blocker/notes.md +0 -14
  108. package/pipeline/eval/triage/README.md +0 -54
  109. package/pipeline/scripts/benchmark-phase-0.sh +0 -128
  110. package/pipeline/scripts/check-md-links.mjs +0 -88
  111. package/pipeline/scripts/eval-golden-tasks-live.mjs +0 -302
  112. package/pipeline/scripts/eval-golden-tasks.mjs +0 -224
  113. package/pipeline/scripts/eval-intent.mjs +0 -107
  114. package/pipeline/scripts/eval-mine-corpus.mjs +0 -211
  115. package/pipeline/scripts/eval-triage.mjs +0 -171
  116. package/pipeline/scripts/fixtures/diff-risk-android.diff +0 -40
  117. package/pipeline/scripts/fixtures/diff-risk-ios.diff +0 -48
  118. package/pipeline/scripts/fixtures/diff-risk-test-removal.diff +0 -40
  119. package/pipeline/scripts/fixtures/install-layout.tsv +0 -19
  120. package/pipeline/scripts/fixtures/pack-expected-count.txt +0 -1
  121. package/pipeline/scripts/fixtures/test-gap-node.diff +0 -30
  122. package/pipeline/scripts/fixtures/test-gap-python.diff +0 -32
  123. package/pipeline/scripts/lint-mcp-refs.mjs +0 -218
  124. package/pipeline/scripts/lint-skills.mjs +0 -154
  125. package/pipeline/scripts/run-smokes.mjs +0 -130
  126. package/pipeline/scripts/scorecard.mjs +0 -258
  127. package/pipeline/scripts/smoke-add-detail.sh +0 -137
  128. package/pipeline/scripts/smoke-agent-guard.sh +0 -74
  129. package/pipeline/scripts/smoke-agent-log-cost.sh +0 -262
  130. package/pipeline/scripts/smoke-agent-model-routing.sh +0 -87
  131. package/pipeline/scripts/smoke-ask-choice.sh +0 -42
  132. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +0 -36
  133. package/pipeline/scripts/smoke-bitbucket-contract.sh +0 -255
  134. package/pipeline/scripts/smoke-changelog-version.sh +0 -47
  135. package/pipeline/scripts/smoke-channels-approval-gate.sh +0 -60
  136. package/pipeline/scripts/smoke-channels-flow.sh +0 -130
  137. package/pipeline/scripts/smoke-ci-workflows.sh +0 -88
  138. package/pipeline/scripts/smoke-clarify.sh +0 -148
  139. package/pipeline/scripts/smoke-command-inventory.sh +0 -81
  140. package/pipeline/scripts/smoke-commands-skills-parity.sh +0 -87
  141. package/pipeline/scripts/smoke-community-gates.sh +0 -75
  142. package/pipeline/scripts/smoke-compliance-skills.sh +0 -119
  143. package/pipeline/scripts/smoke-config-hygiene.sh +0 -58
  144. package/pipeline/scripts/smoke-context-budget.sh +0 -72
  145. package/pipeline/scripts/smoke-cost-budget.sh +0 -70
  146. package/pipeline/scripts/smoke-cost-summary.sh +0 -139
  147. package/pipeline/scripts/smoke-cross-phase-cohesion.sh +0 -128
  148. package/pipeline/scripts/smoke-description-tr.sh +0 -82
  149. package/pipeline/scripts/smoke-dev-critic.sh +0 -144
  150. package/pipeline/scripts/smoke-diff-explain.sh +0 -147
  151. package/pipeline/scripts/smoke-diff-risk.sh +0 -190
  152. package/pipeline/scripts/smoke-dynamic-skill-loading.sh +0 -160
  153. package/pipeline/scripts/smoke-eval-live.sh +0 -136
  154. package/pipeline/scripts/smoke-evidence-gate.sh +0 -93
  155. package/pipeline/scripts/smoke-extract-conventions.sh +0 -163
  156. package/pipeline/scripts/smoke-fetchers-offline.sh +0 -448
  157. package/pipeline/scripts/smoke-figma-dispatch.sh +0 -112
  158. package/pipeline/scripts/smoke-gate-hooks.sh +0 -74
  159. package/pipeline/scripts/smoke-gc-tmp.sh +0 -130
  160. package/pipeline/scripts/smoke-gc-worktrees.sh +0 -125
  161. package/pipeline/scripts/smoke-generate-issue.sh +0 -120
  162. package/pipeline/scripts/smoke-handoff-contract.sh +0 -92
  163. package/pipeline/scripts/smoke-identity-isolation.sh +0 -70
  164. package/pipeline/scripts/smoke-install-layout.sh +0 -248
  165. package/pipeline/scripts/smoke-intent-guard.sh +0 -86
  166. package/pipeline/scripts/smoke-issue-comment-template.sh +0 -86
  167. package/pipeline/scripts/smoke-issue-jira-triad.sh +0 -120
  168. package/pipeline/scripts/smoke-keychain.sh +0 -158
  169. package/pipeline/scripts/smoke-language-axis.sh +0 -109
  170. package/pipeline/scripts/smoke-learning-curve.sh +0 -61
  171. package/pipeline/scripts/smoke-learnings-ledger.sh +0 -86
  172. package/pipeline/scripts/smoke-lib-scripts.sh +0 -448
  173. package/pipeline/scripts/smoke-mcp-gate.sh +0 -68
  174. package/pipeline/scripts/smoke-md-links.sh +0 -8
  175. package/pipeline/scripts/smoke-md2confluence.sh +0 -126
  176. package/pipeline/scripts/smoke-metrics-cache-ratio.sh +0 -72
  177. package/pipeline/scripts/smoke-migrate-state.sh +0 -102
  178. package/pipeline/scripts/smoke-mode-dispatch-drift.sh +0 -161
  179. package/pipeline/scripts/smoke-model-fallback.sh +0 -89
  180. package/pipeline/scripts/smoke-multi-repo-integration.sh +0 -116
  181. package/pipeline/scripts/smoke-multi-repo-worktree.sh +0 -61
  182. package/pipeline/scripts/smoke-no-mcp-in-dev-phases.sh +0 -194
  183. package/pipeline/scripts/smoke-no-token-prompt.sh +0 -85
  184. package/pipeline/scripts/smoke-own-punctuation.sh +0 -103
  185. package/pipeline/scripts/smoke-pack-contents.sh +0 -140
  186. package/pipeline/scripts/smoke-pat-audit.sh +0 -128
  187. package/pipeline/scripts/smoke-per-repo-memory.sh +0 -156
  188. package/pipeline/scripts/smoke-phase-0-multi-repo.sh +0 -170
  189. package/pipeline/scripts/smoke-phase-6-multi.sh +0 -79
  190. package/pipeline/scripts/smoke-phase-banner.sh +0 -101
  191. package/pipeline/scripts/smoke-phase-tracker.sh +0 -324
  192. package/pipeline/scripts/smoke-phase0-bridge-contract.sh +0 -241
  193. package/pipeline/scripts/smoke-phase4-gates.sh +0 -45
  194. package/pipeline/scripts/smoke-phase4-triage.sh +0 -229
  195. package/pipeline/scripts/smoke-plan-approval-gate.sh +0 -71
  196. package/pipeline/scripts/smoke-plan-safety.sh +0 -139
  197. package/pipeline/scripts/smoke-plan-todos.sh +0 -196
  198. package/pipeline/scripts/smoke-pr-review-actions.sh +0 -152
  199. package/pipeline/scripts/smoke-pre-commit.sh +0 -170
  200. package/pipeline/scripts/smoke-pref-migration.sh +0 -226
  201. package/pipeline/scripts/smoke-prefs-language.sh +0 -134
  202. package/pipeline/scripts/smoke-progress-contract.sh +0 -127
  203. package/pipeline/scripts/smoke-prune-logs.sh +0 -137
  204. package/pipeline/scripts/smoke-purge.sh +0 -138
  205. package/pipeline/scripts/smoke-push-retry.sh +0 -75
  206. package/pipeline/scripts/smoke-repo-map.sh +0 -300
  207. package/pipeline/scripts/smoke-review-readiness.sh +0 -92
  208. package/pipeline/scripts/smoke-review-watch.sh +0 -146
  209. package/pipeline/scripts/smoke-routines.sh +0 -84
  210. package/pipeline/scripts/smoke-run-aggregator.sh +0 -216
  211. package/pipeline/scripts/smoke-run-metrics.sh +0 -50
  212. package/pipeline/scripts/smoke-search.sh +0 -187
  213. package/pipeline/scripts/smoke-shadow-git.sh +0 -224
  214. package/pipeline/scripts/smoke-skill-authoring.sh +0 -137
  215. package/pipeline/scripts/smoke-skill-language.sh +0 -83
  216. package/pipeline/scripts/smoke-skill-manifest.sh +0 -138
  217. package/pipeline/scripts/smoke-skill-scan.sh +0 -198
  218. package/pipeline/scripts/smoke-source-parity.sh +0 -85
  219. package/pipeline/scripts/smoke-subagent-validators.sh +0 -108
  220. package/pipeline/scripts/smoke-sync-parity.sh +0 -92
  221. package/pipeline/scripts/smoke-tasklist-ordering.sh +0 -112
  222. package/pipeline/scripts/smoke-telemetry.sh +0 -147
  223. package/pipeline/scripts/smoke-test-gap.sh +0 -183
  224. package/pipeline/scripts/smoke-token-budget.sh +0 -67
  225. package/pipeline/scripts/smoke-token-preflight.sh +0 -82
  226. package/pipeline/scripts/smoke-tracker-contract.sh +0 -191
  227. package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +0 -73
  228. package/pipeline/scripts/smoke-triage-memory.sh +0 -174
  229. package/pipeline/scripts/smoke-update-check.sh +0 -135
  230. package/pipeline/scripts/smoke-url-enrichment.sh +0 -70
  231. package/pipeline/scripts/smoke-validate-analysis-doc.sh +0 -161
  232. package/pipeline/scripts/smoke-validator-contradiction.sh +0 -67
  233. package/pipeline/scripts/smoke-validator-gates.sh +0 -164
  234. package/pipeline/scripts/smoke-vercel-deploy-redact.sh +0 -129
  235. package/pipeline/scripts/smoke-verify-by-test.sh +0 -148
  236. package/pipeline/scripts/smoke-wiki-integration.sh +0 -122
  237. package/pipeline/scripts/smoke-work-summary.sh +0 -163
  238. package/pipeline/scripts/smoke-workflow-audit.sh +0 -101
  239. package/pipeline/scripts/smoke-worktree-path-convention.sh +0 -86
  240. package/pipeline/scripts/smoke-wrapper-preservation.sh +0 -68
  241. package/pipeline/scripts/smoke-write-state.sh +0 -159
  242. package/pipeline/scripts/sync-parity-check.sh +0 -135
  243. package/pipeline/scripts/test-gap-rules/android.json +0 -25
  244. package/pipeline/scripts/test-gap-rules/ios.json +0 -34
  245. package/pipeline/scripts/test-gap-rules/node.json +0 -29
  246. package/pipeline/scripts/test-gap-rules/python.json +0 -25
  247. package/pipeline/scripts/validate-schemas.mjs +0 -88
@@ -25,7 +25,50 @@ ATTRIBUTION = re.compile(
25
25
  r"|claude code <",
26
26
  re.IGNORECASE,
27
27
  )
28
- FORCE = re.compile(r"(^|\s)(--force(\b|$)|--force-with-lease|-\S*f\S*)(\s|$)")
28
+ # Force flags, and ONLY force flags.
29
+ #
30
+ # The previous pattern ended in `-\S*f\S*`, which matches any flag containing an
31
+ # `f` anywhere - so it fired on `--ff-only`, `--follow-tags`, and `--file=...`.
32
+ # Two real false positives followed:
33
+ #
34
+ # git pull --ff-only && git push origin main -> BLOCKED
35
+ # git push --follow-tags origin main -> BLOCKED
36
+ #
37
+ # Neither rewrites history. `--follow-tags` is the normal way to push a release
38
+ # tag with its commit, so the guard blocked a routine release step. Being
39
+ # fail-closed is right for a data-loss guard, but only against the thing it
40
+ # guards; blocking safe commands trains people to work around the hook, which
41
+ # costs more safety than it buys.
42
+ #
43
+ # The short-flag arm is now `-[A-Za-z]*f[A-Za-z]*`: a SINGLE dash followed only by
44
+ # letters, so it still catches `-f`, `-fq`, `-qf`, and never a `--long-flag`
45
+ # (whose second character is `-`, not a letter).
46
+ FORCE = re.compile(
47
+ r"(^|\s)("
48
+ r"--force(-with-lease|-if-includes)?(=\S+)?" # --force, --force-with-lease[=ref], --force-if-includes
49
+ r"|-[A-Za-z]*f[A-Za-z]*" # -f and short clusters containing f
50
+ r")(\s|$)"
51
+ )
52
+
53
+
54
+ # Shell separators that end one command and start the next. Used to attribute a
55
+ # flag to the command it actually belongs to.
56
+ SEPARATORS = re.compile(r"&&|\|\||;|\||\n")
57
+
58
+
59
+ def push_segments(cmd: str) -> list:
60
+ """The sub-commands that invoke `git push`.
61
+
62
+ A flag must be attributed to its own command. Scanning the whole string made
63
+ `git commit -f ... && git push origin main` read as a force-push, because the
64
+ `-f` belonged to the commit. Splitting on shell separators first keeps each
65
+ command's flags with that command.
66
+
67
+ Deliberately coarse: a separator inside a quoted string splits too, which can
68
+ only ever produce MORE segments to inspect, never fewer - the safe direction
69
+ for a guard.
70
+ """
71
+ return [seg for seg in SEPARATORS.split(cmd) if re.search(r"git\s+push", seg.lower())]
29
72
 
30
73
 
31
74
  def decide(cmd: str) -> str:
@@ -35,8 +78,19 @@ def decide(cmd: str) -> str:
35
78
  if re.search(r"git\s+commit", low) and ATTRIBUTION.search(cmd):
36
79
  return "BLOCK_ATTRIB"
37
80
 
38
- # Rule 2: force-push to a protected branch.
39
- if re.search(r"git\s+push", low) and FORCE.search(cmd):
81
+ # Rule 2: force-push to a protected branch. Evaluate each push segment on its
82
+ # own so a neighbouring command's flags cannot implicate it.
83
+ for segment in push_segments(cmd):
84
+ verdict = _decide_push(segment)
85
+ if verdict != "OK":
86
+ return verdict
87
+
88
+ return "OK"
89
+
90
+
91
+ def _decide_push(cmd: str) -> str:
92
+ """Verdict for a single `git push ...` command."""
93
+ if FORCE.search(cmd):
40
94
  try:
41
95
  toks = shlex.split(cmd)
42
96
  except Exception:
@@ -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
- ]
@@ -1,15 +0,0 @@
1
- {
2
- "accepted": [
3
- {
4
- "severity": "blocking",
5
- "file": "app/middleware/rate_limit.py",
6
- "line": 38,
7
- "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",
8
- "fix": "Guard the read-modify-write with an asyncio.Lock keyed per IP, or use an atomic counter structure",
9
- "reviewer": "opus"
10
- }
11
- ],
12
- "deferred": [],
13
- "rejected": [],
14
- "approved": false
15
- }
@@ -1,14 +0,0 @@
1
- {
2
- "focus": "Backend (Python/FastAPI) feature path - verifies stack detection on a non-mobile repo and that Phase 4 catches a concurrency blocker (unsynchronized shared counter) in newly added middleware.",
3
- "exercises": [
4
- "Phase 1 stack detection (backend / Python / FastAPI)",
5
- "Phase 2 TDD ordering (failing rate-limit test before middleware code)",
6
- "Phase 4 Reviewer-1 (opus) catches a race condition on the shared window counter; Reviewer-2 (sonnet) approves the wiring",
7
- "Triage keeps the concurrency blocker accepted, no deferrals, not approved"
8
- ],
9
- "regressionsCaught": [
10
- "If stack detection misroutes a FastAPI repo to 'frontend' or 'unknown', the stack-match check fails",
11
- "If planning emits files outside Phase 1 touchedAreas, the subset check fails",
12
- "If triage stops requiring a reviewer attribution on accepted findings, this fixture fails validate-triage"
13
- ]
14
- }
@@ -1,12 +0,0 @@
1
- {
2
- "title": "Add per-IP rate limiting to the auth login endpoint",
3
- "description": "Brute-force attempts on POST /auth/login are unthrottled. Add per-IP rate limiting (sliding window) as FastAPI middleware and wire it into the auth router. Return 429 with Retry-After when the limit is exceeded.",
4
- "inputType": "free-text",
5
- "expectedStack": "backend",
6
- "expectedLanguage": "Python",
7
- "expectedFramework": "FastAPI",
8
- "expectedBlockers": 1,
9
- "expectedDeferrals": 0,
10
- "baseBranch": "main",
11
- "taskBranch": "feature/auth-rate-limit"
12
- }
@@ -1,29 +0,0 @@
1
- {
2
- "stack": {
3
- "primary": "frontend",
4
- "language": "TypeScript 5",
5
- "framework": "Next.js"
6
- },
7
- "touchedAreas": [
8
- {
9
- "path": "components/CartBadge.tsx",
10
- "why": "Reads localStorage during render, diverging from the server-rendered 0"
11
- },
12
- {
13
- "path": "hooks/useCart.ts",
14
- "why": "Exposes the count; needs an SSR-safe initial value with hydration on mount"
15
- },
16
- {
17
- "path": "components/CartBadge.test.tsx",
18
- "why": "Adds a render test asserting no hydration warning and the count appears after mount"
19
- }
20
- ],
21
- "risks": [
22
- {
23
- "risk": "Other components may also read the same localStorage key during render and reintroduce the mismatch elsewhere",
24
- "severity": "low",
25
- "mitigation": "Centralize the SSR-safe read in useCart so callers share one hydration-safe source"
26
- }
27
- ],
28
- "summary": "The cart badge reads localStorage during render while the server renders 0, triggering a hydration mismatch and a count flash. Move the read behind an SSR-safe initial value that hydrates on mount, centralized in useCart. Three files; low regression risk if the read stays centralized."
29
- }