forgedock 1.0.22 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -211
- package/bin/batch-runner.mjs +339 -0
- package/bin/cinema.mjs +320 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +168 -0
- package/bin/engine/projector.mjs +32 -0
- package/bin/engine/reconcile.mjs +11 -0
- package/bin/engine/runlog.mjs +79 -0
- package/bin/engine/state.mjs +32 -0
- package/bin/engine-cli.mjs +222 -0
- package/bin/engine.mjs +138 -0
- package/bin/forgedock.mjs +896 -1540
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +841 -0
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +136 -20
- package/bin/settings-hook.mjs +346 -0
- package/bin/tui.mjs +6 -3
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +84 -12
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +63 -4
- package/commands/design-bench.md +6 -1
- package/commands/design-render-critique-loop.md +2 -1
- package/commands/design.md +1 -0
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +122 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +24 -3
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2027
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +326 -42
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +612 -56
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +19 -5
- package/commands/work-on/build/validate.md +62 -6
- package/commands/work-on/build.md +187 -30
- package/commands/work-on/close.md +132 -5
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +48 -4
- package/commands/work-on-monolithic.md +8 -2
- package/commands/work-on.md +247 -7
- package/docs/BRAND.md +16 -5
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +44 -42
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +58 -12
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +10 -2
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
- package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
- package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
- package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +12 -4
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/verify-env-vars.sh +18 -2
- package/scripts/verify-host-headers.sh +4 -4
- package/scripts/verify-route-registration.sh +2 -2
- package/scripts/worktree-lifecycle.sh +202 -0
- package/templates/devdocs/agent/using-forgedock.md +14 -0
package/commands/quality-gate.md
CHANGED
|
@@ -13,7 +13,10 @@ You are a quality gate that runs AFTER implementation but BEFORE commit. Your jo
|
|
|
13
13
|
|
|
14
14
|
You do NOT post to GitHub. You do NOT create issues. You return findings directly to the builder agent that spawned you.
|
|
15
15
|
|
|
16
|
-
**Agent model policy**:
|
|
16
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
17
|
+
**NEVER use plan mode (EnterPlanMode).**
|
|
18
|
+
|
|
19
|
+
<!-- FORGE:SPEC_LOADED — quality-gate.md loaded and active. Agent is bound by this spec. -->
|
|
17
20
|
|
|
18
21
|
---
|
|
19
22
|
|
|
@@ -39,16 +42,17 @@ This quality gate uses **domain detection** to adapt to your project's actual te
|
|
|
39
42
|
| FRONTEND | `.tsx`/`.ts` files in client component paths |
|
|
40
43
|
| PROXY | Client components using `fetch`/`useSWR`/`apiFetch` |
|
|
41
44
|
| SHELL | `.sh` files or scripts using `curl`/`wget` |
|
|
42
|
-
| STRING_SETS | Python files in anti-detection or browser automation paths |
|
|
45
|
+
| STRING_SETS | *(scraping pack — opt-in)* Python files in anti-detection or browser automation paths. Enable via `forge.yaml → quality_gate.optional_domains: [STRING_SETS]` |
|
|
43
46
|
| CONCURRENCY | Python files using `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel` |
|
|
44
47
|
| STATE | Python files with new module-level mutable variable assignments |
|
|
45
|
-
| CAPACITY | Worker/infra Python files with new size/limit/threshold constants |
|
|
48
|
+
| CAPACITY | *(scraping pack — opt-in)* Worker/infra Python files with new size/limit/threshold constants. Enable via `forge.yaml → quality_gate.optional_domains: [CAPACITY]` |
|
|
46
49
|
| WORKFLOW | `.yml` files in `.github/workflows/` |
|
|
47
50
|
| IMPORT_RESOLUTION | Python files with new `from app.*` imports added outside `try:` blocks |
|
|
48
51
|
| INFRA | Dockerfiles or entrypoints with runtime UID changes |
|
|
49
52
|
| ROUTER_BUG | Router Python files where gate conditions are narrowed |
|
|
50
53
|
| FORGE_GRAPH | `commands/*.md` specs or `scripts/*` files change (ForgeDock self-consistency) |
|
|
51
54
|
| CONFIG_SCHEMA | Config files for external tools (`traefik/`, `infra/nginx/`, `k8s/`, `terraform/`, `*.conf`, `*.toml` in infra paths, or `docker-compose*.yml` with service definition changes) |
|
|
55
|
+
| BILLING | Files under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths |
|
|
52
56
|
|
|
53
57
|
**Stack-agnostic coverage**: The example domains above reflect common patterns caught in production; they are not requirements. A Go or Ruby project benefits from SECURITY checks. A Node.js project benefits from FRONTEND, PROXY, and DEPLOY checks. Any project benefits from DATABASE and WORKFLOW checks when those file types are present. The stack-specific examples (Python routers, SOPS secrets chain, FastAPI layouts, appleboy SSH deploys) are illustrative — the domain detection system applies the applicable subset to any codebase.
|
|
54
58
|
|
|
@@ -88,14 +92,15 @@ Before running checks, classify the changed files into domains. This avoids runn
|
|
|
88
92
|
| `*.py` in `routers/` or `route.ts` handlers | AUTH |
|
|
89
93
|
| `*.py`, `*.ts`, `*.tsx`, `*.sh` (any code file) | SECURITY (always) |
|
|
90
94
|
| Files containing `os.getenv` or `process.env` | DEPLOY |
|
|
91
|
-
| `*.sql
|
|
95
|
+
| `*.sql`, files in `migrations/`, or `*migrations/*.py` (Alembic) | DATABASE |
|
|
96
|
+
| Files under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths | BILLING |
|
|
92
97
|
| `*.tsx`, `*.ts` in `web/src/` (excluding `route.ts`) | FRONTEND |
|
|
93
98
|
| `*.tsx`, `*.ts` client components with fetch/useSWR | PROXY |
|
|
94
99
|
| `*.sh` files or scripts with `curl`/`wget` | SHELL |
|
|
95
|
-
| `*.py` in `anti_detection/`, `consumers/`, `browser/`, or `shared/detection/` | STRING_SETS |
|
|
100
|
+
| `*.py` in `anti_detection/`, `consumers/`, `browser/`, or `shared/detection/` | STRING_SETS *(scraping pack — opt-in, see 2J)* |
|
|
96
101
|
| `*.py` containing `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel` | CONCURRENCY |
|
|
97
102
|
| `*.py` files with new module-level `dict`, `set`, `Counter`, `Lock`, or `defaultdict` assignments | STATE |
|
|
98
|
-
| `*.py` in `services/worker/`, `infra/`, or `browser/` with new assignments to variables containing `MB`, `SIZE`, `MAX`, `LIMIT`, `THRESHOLD`, or `TIMEOUT` | CAPACITY |
|
|
103
|
+
| `*.py` in `services/worker/`, `infra/`, or `browser/` with new assignments to variables containing `MB`, `SIZE`, `MAX`, `LIMIT`, `THRESHOLD`, or `TIMEOUT` | CAPACITY *(scraping pack — opt-in, see 2K)* |
|
|
99
104
|
| `*.yml` in `.github/workflows/` | WORKFLOW |
|
|
100
105
|
| `*.py` with new `from app.` import statements added outside `try:` blocks | IMPORT_RESOLUTION |
|
|
101
106
|
| `Dockerfile*` or `entrypoint*.sh` with added/changed `USER`, `su-exec`, `gosu`, or `setuid` | INFRA |
|
|
@@ -116,7 +121,10 @@ DOMAINS=""
|
|
|
116
121
|
IFS=' ' read -ra CHANGED_FILES_ARR <<< "{CHANGED_FILES}"
|
|
117
122
|
for f in "${CHANGED_FILES_ARR[@]}"; do
|
|
118
123
|
case "$f" in
|
|
119
|
-
*.sql|*migrations
|
|
124
|
+
*.sql|*migrations/*|*migrations/*.py) DOMAINS="$DOMAINS DATABASE" ;;
|
|
125
|
+
esac
|
|
126
|
+
case "$f" in
|
|
127
|
+
*billing/*|*credits/*|*subscription*|*ledger*|*payment*) DOMAINS="$DOMAINS BILLING" ;;
|
|
120
128
|
esac
|
|
121
129
|
case "$f" in
|
|
122
130
|
*router*|*route.ts) DOMAINS="$DOMAINS AUTH" ;;
|
|
@@ -131,7 +139,12 @@ for f in "${CHANGED_FILES_ARR[@]}"; do
|
|
|
131
139
|
esac
|
|
132
140
|
case "$f" in
|
|
133
141
|
*anti_detection/*|*consumers/*|*browser/*|*shared/detection/*)
|
|
134
|
-
|
|
142
|
+
# STRING_SETS is a scraping-pack domain — opt-in via forge.yaml → quality_gate.optional_domains
|
|
143
|
+
# <!-- Updated: forge#1349 — not enabled by default; scraping-product-specific -->
|
|
144
|
+
if echo "$f" | grep -qE '\.py$'; then
|
|
145
|
+
_OPT_DOMAINS=$(grep -A5 'optional_domains:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null | grep -oE 'STRING_SETS' | head -1 || true)
|
|
146
|
+
[ -n "$_OPT_DOMAINS" ] && DOMAINS="$DOMAINS STRING_SETS"
|
|
147
|
+
fi
|
|
135
148
|
;;
|
|
136
149
|
esac
|
|
137
150
|
case "$f" in
|
|
@@ -149,10 +162,15 @@ grep -lE "asyncio\.shield|asyncio\.wait_for|Task\.cancel" {CHANGED_FILES} 2>/dev
|
|
|
149
162
|
grep -lE "^\w+ = (\{\}|\[\]|set\(\)|Lock\(\)|defaultdict|Counter)" {CHANGED_FILES} 2>/dev/null | grep -qE '\.py$' && DOMAINS="$DOMAINS STATE"
|
|
150
163
|
|
|
151
164
|
# Check for capacity constants in worker/infra/browser Python files
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
# CAPACITY is a scraping-pack domain — opt-in via forge.yaml → quality_gate.optional_domains
|
|
166
|
+
# <!-- Updated: forge#1349 — not enabled by default; scraping-product-specific -->
|
|
167
|
+
_OPT_CAPACITY=$(grep -A5 'optional_domains:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null | grep -oE 'CAPACITY' | head -1 || true)
|
|
168
|
+
if [ -n "$_OPT_CAPACITY" ]; then
|
|
169
|
+
while IFS= read -r f; do
|
|
170
|
+
[ -z "$f" ] && continue
|
|
171
|
+
git diff HEAD -- "$f" 2>/dev/null | grep -qE '^\+[A-Za-z_]\w*(MB|SIZE|MAX|LIMIT|THRESHOLD|TIMEOUT)\w*\s*=' && DOMAINS="$DOMAINS CAPACITY" && break
|
|
172
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$' | grep -E 'services/worker/|infra/|browser/')
|
|
173
|
+
fi
|
|
156
174
|
|
|
157
175
|
# Check for env var usage in changed files
|
|
158
176
|
grep -lE "os\.getenv|process\.env" {CHANGED_FILES} 2>/dev/null && DOMAINS="$DOMAINS DEPLOY"
|
|
@@ -226,6 +244,9 @@ Run ONLY the checks whose domain was identified in Step 1.5. Skip checks for dom
|
|
|
226
244
|
- **2N (Runtime UID × filesystem write)**: Run if `INFRA` in DOMAINS
|
|
227
245
|
- **2O (Residual pattern check)**: Run if `ROUTER_BUG` in DOMAINS
|
|
228
246
|
- **2P (External tool config schema)**: Run if `CONFIG_SCHEMA` in DOMAINS
|
|
247
|
+
- **2Q (Billing)**: Run if `BILLING` in DOMAINS
|
|
248
|
+
- **2R (Registry checks)**: ALWAYS run — executes all promoted checks from `scripts/check-registry/manifest.json` <!-- Added: forge#1331 -->
|
|
249
|
+
- **2S (Test failure classification)**: Run if any Step 2 check invoked a test suite and it failed — classifies the failure as PRE_BROKEN, FLAKY, or REAL using `scripts/flaky-quarantine.sh` <!-- Added: forge#1336 -->
|
|
229
250
|
|
|
230
251
|
### 2A: Security (ALL files)
|
|
231
252
|
|
|
@@ -266,14 +287,26 @@ done
|
|
|
266
287
|
### 2B: Auth conventions (Python routers, TypeScript route handlers)
|
|
267
288
|
|
|
268
289
|
For new/modified endpoints:
|
|
269
|
-
1. Which auth dependency is used
|
|
270
|
-
2. Does the
|
|
271
|
-
|
|
290
|
+
1. Which auth dependency is used? Read `forge.yaml → review.domains.auth` for the configured dependency name(s). If absent, skip the naming check and fall back to generic resource-ownership verification only.
|
|
291
|
+
2. Does the endpoint check resource ownership (`user_id` or equivalent) before returning data?
|
|
292
|
+
|
|
293
|
+
Route-path-to-dependency convention (e.g. "dashboard routes must use X, public routes must use Y") is project-specific. Configure it in `forge.yaml → review.domains.auth` if your project uses one. Do not enforce a hard-coded convention universally. <!-- Updated: forge#1349 — removed hardcoded SessionUser/CurrentUser rule -->
|
|
272
294
|
|
|
273
295
|
```bash
|
|
296
|
+
# Read configured auth dependency names from forge.yaml (if present)
|
|
297
|
+
AUTH_DEPS=""
|
|
298
|
+
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
299
|
+
AUTH_DEPS=$(grep -A5 'domains:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
300
|
+
| grep -A3 'auth:' | grep -oE '[A-Za-z_]+User|[A-Za-z_]+Auth|get_[a-z_]+' | sort -u | tr '\n' '|' | sed 's/|$//')
|
|
301
|
+
fi
|
|
302
|
+
|
|
274
303
|
while IFS= read -r f; do
|
|
275
304
|
[ -z "$f" ] && continue
|
|
276
|
-
|
|
305
|
+
if [ -n "$AUTH_DEPS" ]; then
|
|
306
|
+
grep -nE "($AUTH_DEPS)|Depends\(get_" "$f" 2>/dev/null
|
|
307
|
+
else
|
|
308
|
+
grep -nE "Depends\(get_" "$f" 2>/dev/null
|
|
309
|
+
fi
|
|
277
310
|
grep -nE "@router\.(get|post|put|delete)" "$f" 2>/dev/null
|
|
278
311
|
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E 'router|route')
|
|
279
312
|
```
|
|
@@ -286,20 +319,26 @@ if [ -n "$NEW_ENVS" ]; then
|
|
|
286
319
|
for var in $NEW_ENVS; do
|
|
287
320
|
# Check if it exists in .env.example
|
|
288
321
|
grep -q "$var" .env.example 2>/dev/null || echo "DEPLOY: $var not in .env.example"
|
|
289
|
-
# Check
|
|
290
|
-
|
|
322
|
+
# Check decrypt-secrets.sh ENV_MAPPING only when the SOPS mapping script exists.
|
|
323
|
+
# Emitting a finding for a file that doesn't exist in the repo produces false BLOCKINGs
|
|
324
|
+
# on non-SOPS repos. Use verify-sops-chain.sh for the full SOPS chain check instead.
|
|
325
|
+
# <!-- Updated: forge#1349 — gate on file presence -->
|
|
326
|
+
if [ -f "{WORKTREE_PATH}/scripts/decrypt-secrets.sh" ]; then
|
|
327
|
+
grep -q "$var" "{WORKTREE_PATH}/scripts/decrypt-secrets.sh" 2>/dev/null || echo "DEPLOY: $var not in decrypt-secrets.sh ENV_MAPPING"
|
|
328
|
+
fi
|
|
291
329
|
done
|
|
292
330
|
fi
|
|
293
331
|
```
|
|
294
332
|
|
|
295
|
-
### 2D: Database quality (SQL migration files)
|
|
333
|
+
### 2D: Database quality (SQL migration files and Alembic Python migrations)
|
|
296
334
|
|
|
297
|
-
For each `.sql` file:
|
|
335
|
+
For each `.sql` file or Alembic migration `.py` file:
|
|
298
336
|
1. **NOT NULL without DEFAULT**: `ALTER TABLE ... ADD COLUMN ... NOT NULL` without `DEFAULT` locks table and fails on existing rows
|
|
299
337
|
2. **DROP without IF EXISTS**: Fails on fresh databases
|
|
300
338
|
3. **Missing indexes**: New columns used in WHERE/JOIN without indexes
|
|
301
339
|
4. **Unbounded queries**: SELECT without LIMIT or time-bound WHERE clause
|
|
302
340
|
5. **Migration number collision**: Check against origin branch
|
|
341
|
+
6. **IF NOT EXISTS guard**: `CREATE TABLE`/`CREATE INDEX` without `IF NOT EXISTS` guard fails on re-run
|
|
303
342
|
|
|
304
343
|
```bash
|
|
305
344
|
while IFS= read -r f; do
|
|
@@ -307,30 +346,39 @@ while IFS= read -r f; do
|
|
|
307
346
|
grep -nE "ADD COLUMN.*NOT NULL" "$f" | grep -v "DEFAULT" && echo "DB: NOT NULL without DEFAULT in $f"
|
|
308
347
|
grep -nE "DROP (TABLE|COLUMN|INDEX)" "$f" | grep -v "IF EXISTS" && echo "DB: DROP without IF EXISTS in $f"
|
|
309
348
|
grep -nE "SELECT.*FROM" "$f" | grep -v "LIMIT" | grep -v "WHERE.*created_at" && echo "DB: possibly unbounded query in $f"
|
|
310
|
-
|
|
349
|
+
# IF NOT EXISTS guard check — CREATE TABLE/INDEX without guard fails on replay or repeated apply
|
|
350
|
+
grep -nE "^\s*(CREATE TABLE|CREATE INDEX|CREATE UNIQUE INDEX)\b" "$f" | grep -iv "IF NOT EXISTS" && \
|
|
351
|
+
echo "DB-GUARD | MEDIUM | $f | CREATE TABLE/INDEX without IF NOT EXISTS guard — statement will fail on re-run or fresh database replay. Add IF NOT EXISTS."
|
|
352
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.sql$')
|
|
353
|
+
|
|
354
|
+
# Also run structural checks on Alembic Python migration files
|
|
355
|
+
while IFS= read -r f; do
|
|
356
|
+
[ -z "$f" ] && continue
|
|
357
|
+
# Check for op.create_table / op.create_index without if_not_exists or checkfirst guard
|
|
358
|
+
grep -nE "\bop\.create_table\b|\bop\.create_index\b" "$f" | grep -iv "if_not_exists\|checkfirst" && \
|
|
359
|
+
echo "DB-GUARD | MEDIUM | $f | op.create_table/op.create_index without if_not_exists/checkfirst guard — will fail on replay. Add if_not_exists=True or checkfirst=True."
|
|
360
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E 'migrations/.*\.py$')
|
|
311
361
|
|
|
312
362
|
# Migration prefix collision scan (Deploy Gate — HIGH)
|
|
313
|
-
# Runs whenever any SQL file changed. Scans the
|
|
314
|
-
#
|
|
315
|
-
#
|
|
363
|
+
# Runs whenever any SQL or Alembic Python migration file changed. Scans the configured
|
|
364
|
+
# migrations directory for duplicate numeric prefixes (both .sql and .py files).
|
|
365
|
+
# Duplicate prefixes can cause ordering conflicts during deploy.
|
|
366
|
+
# <!-- Updated: forge#1349 — removed stale validate-migration-order.sh reference (script does not exist) -->
|
|
367
|
+
# <!-- Updated: forge#1329 — extended collision scan to cover .py Alembic migration files -->
|
|
316
368
|
MIGRATIONS_DIR="{WORKTREE_PATH}/infra/migrations"
|
|
369
|
+
# Support configured migration directory from forge.yaml (if set)
|
|
370
|
+
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
371
|
+
_CONFIGURED_MIGRATIONS=$(grep -E '^\s*migrations_dir:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
372
|
+
| head -1 | sed 's/.*migrations_dir:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
373
|
+
[ -n "$_CONFIGURED_MIGRATIONS" ] && MIGRATIONS_DIR="{WORKTREE_PATH}/${_CONFIGURED_MIGRATIONS}"
|
|
374
|
+
fi
|
|
317
375
|
if [ -d "$MIGRATIONS_DIR" ]; then
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
GRANDFATHERED=""
|
|
321
|
-
if [ -f "$GRANDFATHER_SCRIPT" ]; then
|
|
322
|
-
GRANDFATHERED=$(grep -oE '[0-9]{4}' "$GRANDFATHER_SCRIPT" | sort -u | tr '\n' ' ')
|
|
323
|
-
fi
|
|
324
|
-
# Find duplicate numeric prefixes across all migration files
|
|
325
|
-
DUPLICATE_PREFIXES=$(ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null \
|
|
376
|
+
# Find duplicate numeric prefixes across all migration files (.sql and .py)
|
|
377
|
+
DUPLICATE_PREFIXES=$( { ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null; ls "$MIGRATIONS_DIR"/*.py 2>/dev/null; } \
|
|
326
378
|
| sed 's|.*/\([0-9]*\)_.*|\1|' \
|
|
327
379
|
| sort | uniq -d)
|
|
328
380
|
for prefix in $DUPLICATE_PREFIXES; do
|
|
329
|
-
|
|
330
|
-
echo "DB: migration prefix $prefix is duplicated but grandfathered (INFO)"
|
|
331
|
-
else
|
|
332
|
-
echo "DB-COLLISION | HIGH | infra/migrations/ | Duplicate migration prefix $prefix is NOT grandfathered — will hard-fail deploy-production.yml validate-migration-order.sh gate. Classify as CRITICAL BLOCKER."
|
|
333
|
-
fi
|
|
381
|
+
echo "DB-COLLISION | HIGH | $MIGRATIONS_DIR | Duplicate migration prefix $prefix — ordering conflict will occur during deploy. Classify as CRITICAL BLOCKER."
|
|
334
382
|
done
|
|
335
383
|
fi
|
|
336
384
|
```
|
|
@@ -393,10 +441,29 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.tsx?$' | grep -v 'route
|
|
|
393
441
|
|
|
394
442
|
### 2F: Frontend proxy wiring (client-side fetch calls)
|
|
395
443
|
|
|
444
|
+
The direct-backend path prefix to flag is read from `forge.yaml → review.layout.direct_api_prefix`.
|
|
445
|
+
If absent, defaults to `/api/v1/` (backward compatible). This prevents false findings on projects
|
|
446
|
+
that use a different API path scheme. <!-- Updated: forge#1349 — gate on forge.yaml, not hardcoded prefix -->
|
|
447
|
+
|
|
396
448
|
```bash
|
|
449
|
+
# Read configured direct API prefix from forge.yaml, fall back to /api/v1/
|
|
450
|
+
DIRECT_API_PREFIX="/api/v1/"
|
|
451
|
+
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
452
|
+
_CONFIGURED_PREFIX=$(grep -E '^\s*direct_api_prefix:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
453
|
+
| head -1 | sed 's/.*direct_api_prefix:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
454
|
+
# Also check under review.layout section
|
|
455
|
+
if [ -z "$_CONFIGURED_PREFIX" ]; then
|
|
456
|
+
_CONFIGURED_PREFIX=$(grep -A 10 'layout:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
457
|
+
| grep -E '^\s*direct_api_prefix:' \
|
|
458
|
+
| head -1 | sed 's/.*direct_api_prefix:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
459
|
+
fi
|
|
460
|
+
[ -n "$_CONFIGURED_PREFIX" ] && DIRECT_API_PREFIX="$_CONFIGURED_PREFIX"
|
|
461
|
+
fi
|
|
462
|
+
ESCAPED_PREFIX=$(printf '%s' "$DIRECT_API_PREFIX" | sed 's|/|\\/|g')
|
|
463
|
+
|
|
397
464
|
while IFS= read -r f; do
|
|
398
465
|
[ -z "$f" ] && continue
|
|
399
|
-
grep -nE
|
|
466
|
+
grep -nE "(fetch|useSWR|apiFetch)\\s*[(<]\\s*[\`\"']${ESCAPED_PREFIX}" "$f" 2>/dev/null && echo "PROXY: direct ${DIRECT_API_PREFIX} call in $f — must use /api/ proxy (configure review.layout.direct_api_prefix in forge.yaml to change the flagged prefix)"
|
|
400
467
|
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.(tsx?|jsx?)$' | grep -v 'route\.ts$')
|
|
401
468
|
```
|
|
402
469
|
|
|
@@ -578,9 +645,11 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
|
578
645
|
|
|
579
646
|
---
|
|
580
647
|
|
|
581
|
-
### 2J: String literal consistency (anti-detection/scraping code)
|
|
648
|
+
### 2J: String literal consistency (anti-detection/scraping code) *(Scraping Pack — opt-in)*
|
|
582
649
|
|
|
583
|
-
**
|
|
650
|
+
> **This domain is opt-in and disabled by default.** It is specific to scraping/anti-detection products and produces false findings on general repos. Enable it by adding `STRING_SETS` to `forge.yaml → quality_gate.optional_domains`. <!-- Added: forge#1349 -->
|
|
651
|
+
|
|
652
|
+
**Triggered when**: changed files include Python files in `anti_detection/`, `consumers/`, `browser/`, or `shared/detection/` directories AND `STRING_SETS` is in `forge.yaml → quality_gate.optional_domains`.
|
|
584
653
|
|
|
585
654
|
**Why this matters**: Detection keyword sets (e.g., `CHALLENGE_KEYWORDS`, `COMMON_COOKIES`) across related modules share a domain truth — if Akamai adds a new cookie marker, it must appear in ALL related sets. A typo or omission in one set silently breaks detection for that service path. This check catches both inter-file inconsistencies and intra-file comment/code drift.
|
|
586
655
|
|
|
@@ -670,9 +739,11 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$' | grep -E 'anti_de
|
|
|
670
739
|
|
|
671
740
|
---
|
|
672
741
|
|
|
673
|
-
### 2K: Capacity constant validation (worker/infra code)
|
|
742
|
+
### 2K: Capacity constant validation (worker/infra code) *(Scraping Pack — opt-in)*
|
|
743
|
+
|
|
744
|
+
> **This domain is opt-in and disabled by default.** It is specific to scraping/browser-pool products and produces false findings on general repos. Enable it by adding `CAPACITY` to `forge.yaml → quality_gate.optional_domains`. <!-- Added: forge#1349 -->
|
|
674
745
|
|
|
675
|
-
**Triggered when**: changed Python files in `services/worker/`, `infra/`, or `browser/` introduce new assignments to variables whose names contain `MB`, `SIZE`, `MAX`, `LIMIT`, `THRESHOLD`, or `TIMEOUT`.
|
|
746
|
+
**Triggered when**: changed Python files in `services/worker/`, `infra/`, or `browser/` introduce new assignments to variables whose names contain `MB`, `SIZE`, `MAX`, `LIMIT`, `THRESHOLD`, or `TIMEOUT` AND `CAPACITY` is in `forge.yaml → quality_gate.optional_domains`.
|
|
676
747
|
|
|
677
748
|
**Why this matters**: Hardcoded capacity constants (e.g., `_BROWSER_ESTIMATED_MB = 200`) are design-time guesses that drift from production reality. When used in guard conditions (e.g., "5 × 200 MB = 1000 MB peak"), a wrong constant directly limits system throughput and may never be caught until a slot fails to spawn. A constant is correctly-typed and correctly-used but factually wrong — logic checks pass, the bug ships.
|
|
678
749
|
|
|
@@ -956,6 +1027,219 @@ fi
|
|
|
956
1027
|
|
|
957
1028
|
**Separation of concerns**: ForgeDock reasons about architecture, logic, and security; CI runs deterministic tool validators with zero token cost. The quality gate enforces that the CI layer is complete — it does not replace it.
|
|
958
1029
|
|
|
1030
|
+
### 2Q: Billing domain checks (billing/credits/subscription/ledger/payment code)
|
|
1031
|
+
|
|
1032
|
+
**Triggered when**: BILLING domain is set (changed files are under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths).
|
|
1033
|
+
|
|
1034
|
+
**Why this matters**: Billing code has three reliably grep-detectable defect classes that account for ~27% of review findings in the AlterLab health window: (1) `org_id` not propagated to debit/refund ledger write paths (silent data ownership loss — charges appear with wrong tenant); (2) `asyncio.sleep` / blocking sleep called inside an advisory-lock or open-transaction scope (holds DB connection/lock for the full sleep duration, causing connection exhaustion and deadlocks under load); (3) stdlib `logging.*()` calls passed structlog-style keyword arguments (`event=`, named context keys) that are not valid stdlib kwargs — crashes at runtime with `TypeError: unexpected keyword argument`. <!-- Added: forge#1329 --> <!-- allowlist: AlterLab -->
|
|
1035
|
+
|
|
1036
|
+
**2Q-1: org_id propagation in ledger write functions**
|
|
1037
|
+
|
|
1038
|
+
```bash
|
|
1039
|
+
# Grep billing Python files for ledger write function definitions missing an org_id parameter.
|
|
1040
|
+
# Heuristic: functions named debit/refund/charge/credit/apply_credit/record_usage whose
|
|
1041
|
+
# signature (def line) does not contain org_id are flagged as MEDIUM — reviewer verifies
|
|
1042
|
+
# with full context whether org_id is pulled from a passed object instead.
|
|
1043
|
+
while IFS= read -r f; do
|
|
1044
|
+
[ -z "$f" ] && continue
|
|
1045
|
+
grep -nE "^\s*def (debit|refund|charge|credit|apply_credit|record_usage|create_invoice|issue_credit)\b" "$f" 2>/dev/null | while IFS=: read -r lineno rest; do
|
|
1046
|
+
# Read the full function signature (up to 3 lines to cover multi-line sigs)
|
|
1047
|
+
sig=$(sed -n "${lineno},$((lineno+2))p" "$f" 2>/dev/null)
|
|
1048
|
+
echo "$sig" | grep -qE '\borg_id\b' || \
|
|
1049
|
+
echo "BILLING-1 | MEDIUM | $f:$lineno | Ledger write function '$(echo "$rest" | grep -oE 'def \w+' | head -1)' signature does not include org_id parameter — verify org_id is propagated to all debit/refund paths (AlterLab#25348 pattern)" <!-- allowlist: AlterLab -->
|
|
1050
|
+
done
|
|
1051
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
**2Q-2: Blocking sleep inside advisory-lock or open-transaction scope**
|
|
1055
|
+
|
|
1056
|
+
```bash
|
|
1057
|
+
# Grep billing Python files for asyncio.sleep / time.sleep called inside an advisory-lock
|
|
1058
|
+
# or open-transaction context (within 20 lines after lock acquisition or BEGIN/session open).
|
|
1059
|
+
# Holding a lock or DB connection across a sleep starves the connection pool and causes
|
|
1060
|
+
# deadlocks under concurrent load (AlterLab#25725 pattern). <!-- allowlist: AlterLab -->
|
|
1061
|
+
while IFS= read -r f; do
|
|
1062
|
+
[ -z "$f" ] && continue
|
|
1063
|
+
grep -nE "\basyncio\.sleep\b|\btime\.sleep\b" "$f" 2>/dev/null | while IFS=: read -r lineno rest; do
|
|
1064
|
+
start=$((lineno > 20 ? lineno - 20 : 1))
|
|
1065
|
+
window=$(sed -n "${start},$((lineno-1))p" "$f" 2>/dev/null)
|
|
1066
|
+
echo "$window" | grep -qE 'advisory_lock|BEGIN\b|async with.*session|async with.*conn|async with.*transaction|with.*engine\b' && \
|
|
1067
|
+
echo "BILLING-2 | HIGH | $f:$lineno | asyncio.sleep/time.sleep inside advisory-lock or open-transaction scope — holds DB connection/lock for full sleep duration, causing connection exhaustion and deadlocks under load (AlterLab#25725 pattern). Move sleep outside lock/transaction scope." <!-- allowlist: AlterLab -->
|
|
1068
|
+
done
|
|
1069
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
1070
|
+
```
|
|
1071
|
+
|
|
1072
|
+
**2Q-3: stdlib logging calls with structlog-style kwargs**
|
|
1073
|
+
|
|
1074
|
+
```bash
|
|
1075
|
+
# Grep changed Python files for logging.*()/logger.*() calls that pass keyword arguments
|
|
1076
|
+
# that are NOT valid stdlib logging kwargs (exc_info, stack_info, stacklevel, extra).
|
|
1077
|
+
# Structlog-style kwargs (event=, extra_=, named context keys) are NOT accepted by stdlib
|
|
1078
|
+
# Logger methods and crash at runtime with TypeError: unexpected keyword argument.
|
|
1079
|
+
# (AlterLab#25715, #25837, #25819 pattern) <!-- allowlist: AlterLab -->
|
|
1080
|
+
while IFS= read -r f; do
|
|
1081
|
+
[ -z "$f" ] && continue
|
|
1082
|
+
# Match logging.info/warning/error/debug/critical and logger.info/etc. calls with
|
|
1083
|
+
# non-stdlib kwargs. Valid stdlib kwargs: exc_info, stack_info, stacklevel, extra.
|
|
1084
|
+
# Flag any named kwarg that is NOT one of those four.
|
|
1085
|
+
grep -nE "\b(logging|logger)\.(info|warning|error|debug|critical|exception)\s*\(.*\b\w+\s*=" "$f" 2>/dev/null | \
|
|
1086
|
+
grep -vE "\b(exc_info|stack_info|stacklevel|extra)\s*=" | \
|
|
1087
|
+
grep -v "^\s*#" && \
|
|
1088
|
+
echo "BILLING-3 | HIGH | $f | stdlib logging call with non-stdlib keyword argument detected — stdlib Logger.info/warning/error/etc. only accept exc_info, stack_info, stacklevel, extra as kwargs. Structlog-style kwargs (event=, named context keys) cause TypeError at runtime (AlterLab#25715 pattern). Use structlog.get_logger() instead of logging if structlog kwargs are needed." <!-- allowlist: AlterLab -->
|
|
1089
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
**Flag as findings**:
|
|
1093
|
+
- Ledger write function missing `org_id` parameter: **MEDIUM** — heuristic; reviewer verifies whether org_id is obtained from a passed object. Silent data ownership loss if omitted.
|
|
1094
|
+
- `asyncio.sleep`/`time.sleep` inside advisory-lock or transaction scope: **HIGH** — guaranteed connection/lock starvation under concurrent load.
|
|
1095
|
+
- stdlib `logging.*()` with non-stdlib kwargs: **HIGH** — guaranteed `TypeError` crash at runtime when the log call is reached.
|
|
1096
|
+
|
|
1097
|
+
**Note**: 2Q-3 (stdlib logging check) fires on ALL changed Python files regardless of path — stdlib logger misuse is not specific to billing paths and is a universal crash class. 2Q-1 and 2Q-2 fire only on billing-domain files.
|
|
1098
|
+
|
|
1099
|
+
### 2R: Registry checks (ALWAYS — promoted patterns from recurring review findings)
|
|
1100
|
+
|
|
1101
|
+
<!-- Added: forge#1331 -->
|
|
1102
|
+
|
|
1103
|
+
**Purpose**: Run all checks that have been promoted from recurring review findings into the registry. These are deterministic scripts that catch in milliseconds what previously required a full review cycle to detect. Each check was generated once by an LLM after the same defect class appeared 3+ times — thereafter it runs forever at zero LLM cost.
|
|
1104
|
+
|
|
1105
|
+
**Registry location**: `scripts/check-registry/manifest.json` (relative to repo root)
|
|
1106
|
+
|
|
1107
|
+
```bash
|
|
1108
|
+
REGISTRY_FILE="{WORKTREE_PATH}/scripts/check-registry/manifest.json"
|
|
1109
|
+
REGISTRY_FINDINGS=""
|
|
1110
|
+
|
|
1111
|
+
if [ ! -f "$REGISTRY_FILE" ]; then
|
|
1112
|
+
echo "2R: No check registry found at $REGISTRY_FILE — skipping registry checks."
|
|
1113
|
+
else
|
|
1114
|
+
# Parse manifest and run each registered check
|
|
1115
|
+
CHECK_COUNT=$(jq '.checks | length' "$REGISTRY_FILE" 2>/dev/null || echo 0)
|
|
1116
|
+
|
|
1117
|
+
if [ "$CHECK_COUNT" -eq 0 ]; then
|
|
1118
|
+
echo "2R: Registry is empty — no promoted checks to run."
|
|
1119
|
+
else
|
|
1120
|
+
echo "2R: Running $CHECK_COUNT registered check(s)..."
|
|
1121
|
+
CHECK_INDEX=0
|
|
1122
|
+
while [ "$CHECK_INDEX" -lt "$CHECK_COUNT" ]; do
|
|
1123
|
+
SCRIPT=$(jq -r ".checks[$CHECK_INDEX].script" "$REGISTRY_FILE" 2>/dev/null)
|
|
1124
|
+
SLUG=$(jq -r ".checks[$CHECK_INDEX].slug" "$REGISTRY_FILE" 2>/dev/null)
|
|
1125
|
+
SEVERITY=$(jq -r ".checks[$CHECK_INDEX].severity" "$REGISTRY_FILE" 2>/dev/null)
|
|
1126
|
+
|
|
1127
|
+
if [ -z "$SCRIPT" ] || [ "$SCRIPT" = "null" ]; then
|
|
1128
|
+
CHECK_INDEX=$((CHECK_INDEX + 1))
|
|
1129
|
+
continue
|
|
1130
|
+
fi
|
|
1131
|
+
|
|
1132
|
+
SCRIPT_PATH="{WORKTREE_PATH}/$SCRIPT"
|
|
1133
|
+
|
|
1134
|
+
if [ ! -x "$SCRIPT_PATH" ]; then
|
|
1135
|
+
echo "2R: WARN — registry check $SLUG script not found or not executable at $SCRIPT_PATH"
|
|
1136
|
+
CHECK_INDEX=$((CHECK_INDEX + 1))
|
|
1137
|
+
continue
|
|
1138
|
+
fi
|
|
1139
|
+
|
|
1140
|
+
# Run the check; pass changed files list and worktree path
|
|
1141
|
+
CHECK_OUTPUT=$("$SCRIPT_PATH" "{CHANGED_FILES}" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1142
|
+
CHECK_EXIT=$?
|
|
1143
|
+
|
|
1144
|
+
if [ "$CHECK_EXIT" -eq 1 ]; then
|
|
1145
|
+
# Check fired — append to findings
|
|
1146
|
+
while IFS= read -r line; do
|
|
1147
|
+
[ -z "$line" ] && continue
|
|
1148
|
+
REGISTRY_FINDINGS="${REGISTRY_FINDINGS}
|
|
1149
|
+
${line}"
|
|
1150
|
+
done <<< "$CHECK_OUTPUT"
|
|
1151
|
+
echo "2R: FAIL — $SLUG matched ($SEVERITY)"
|
|
1152
|
+
elif [ "$CHECK_EXIT" -eq 0 ] || [ "$CHECK_EXIT" -eq 2 ]; then
|
|
1153
|
+
echo "2R: PASS — $SLUG"
|
|
1154
|
+
else
|
|
1155
|
+
echo "2R: WARN — $SLUG exited with unexpected code $CHECK_EXIT"
|
|
1156
|
+
fi
|
|
1157
|
+
|
|
1158
|
+
CHECK_INDEX=$((CHECK_INDEX + 1))
|
|
1159
|
+
done
|
|
1160
|
+
fi
|
|
1161
|
+
fi
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
If any registry check produces findings, include them in the Step 3 findings list under the check's slug (e.g. `REGISTRY-migration-number-collision | HIGH | ...`). HIGH-severity registry findings are blocking; MEDIUM are advisory.
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
## Step 2S: Test failure classification (run when tests fail in Step 2)
|
|
1169
|
+
|
|
1170
|
+
<!-- Added: forge#1336 -->
|
|
1171
|
+
|
|
1172
|
+
**Triggered when**: any domain check in Step 2 invokes a test suite command and that command exits non-zero.
|
|
1173
|
+
|
|
1174
|
+
**Why this matters**: Builders and reviewers waste cycles on test failures that pre-exist the PR or are intermittently caused by fixture-state races. A test that fails on both the PR branch and the base branch is *pre-broken* and must not block an unrelated PR. A test that fails then passes on retry is *flaky* and should be quarantined, not "fixed" by the builder.
|
|
1175
|
+
|
|
1176
|
+
### Classification protocol
|
|
1177
|
+
|
|
1178
|
+
For each failing test ID (or test command) surfaced in Step 2:
|
|
1179
|
+
|
|
1180
|
+
```bash
|
|
1181
|
+
# PR_BASE is the target branch (e.g. "staging", "main", or "milestone/slug")
|
|
1182
|
+
# FAILING_TEST is the individual test identifier or re-runnable command
|
|
1183
|
+
# GH_REPO is owner/repo from forge.yaml (used to file the quarantine issue)
|
|
1184
|
+
# PR_ISSUE_NUMBER is the issue this quality gate was invoked from (optional)
|
|
1185
|
+
|
|
1186
|
+
CLASSIFIER="${FORGEDOCK_SCRIPTS:-scripts}/flaky-quarantine.sh"
|
|
1187
|
+
[ -f "$CLASSIFIER" ] || CLASSIFIER="{WORKTREE_PATH}/scripts/flaky-quarantine.sh"
|
|
1188
|
+
|
|
1189
|
+
if [ -f "$CLASSIFIER" ]; then
|
|
1190
|
+
RESULT=$(bash "$CLASSIFIER" \
|
|
1191
|
+
--test "{FAILING_TEST}" \
|
|
1192
|
+
--base "{PR_BASE}" \
|
|
1193
|
+
--worktree "{WORKTREE_PATH}" \
|
|
1194
|
+
--repo "{GH_REPO}" \
|
|
1195
|
+
--issue "{PR_ISSUE_NUMBER}" \
|
|
1196
|
+
--retries 3 \
|
|
1197
|
+
2>&1)
|
|
1198
|
+
echo "$RESULT"
|
|
1199
|
+
CLASSIFICATION=$(echo "$RESULT" | grep '^CLASSIFICATION:' | awk '{print $2}')
|
|
1200
|
+
else
|
|
1201
|
+
# Script not present — fall back to treating all test failures as real.
|
|
1202
|
+
CLASSIFICATION="REAL"
|
|
1203
|
+
echo "NOTE: scripts/flaky-quarantine.sh not found — treating test failure as REAL (no classification)"
|
|
1204
|
+
fi
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
### Classification outcomes
|
|
1208
|
+
|
|
1209
|
+
| Classification | Meaning | Gate behaviour |
|
|
1210
|
+
|---|---|---|
|
|
1211
|
+
| `PRE_BROKEN` | Fails on base branch — broken before this PR | **Does not block the gate.** Log as advisory. Quarantine manifest entry written; issue filed once. |
|
|
1212
|
+
| `FLAKY` | Intermittent — fails then passes on retry | **Does not block the gate.** Log as advisory. Quarantine manifest entry written; issue filed once. |
|
|
1213
|
+
| `REAL` | Deterministic on PR, passes on base | **Blocks the gate** — treated as a regression caused by this PR. |
|
|
1214
|
+
|
|
1215
|
+
### Gate behaviour
|
|
1216
|
+
|
|
1217
|
+
```
|
|
1218
|
+
if CLASSIFICATION == "PRE_BROKEN" or CLASSIFICATION == "FLAKY":
|
|
1219
|
+
# Test does not block the gate — record in findings as advisory only.
|
|
1220
|
+
# Emit a LOW finding so the builder and reviewer see it in the report.
|
|
1221
|
+
emit: "TEST-QUARANTINE | LOW | {FAILING_TEST} | classified {CLASSIFICATION} — quarantine entry recorded; does not block this PR"
|
|
1222
|
+
# Do NOT re-run the test or count it as a gate failure.
|
|
1223
|
+
|
|
1224
|
+
elif CLASSIFICATION == "REAL":
|
|
1225
|
+
# Test failure is caused by this PR — block the gate as before.
|
|
1226
|
+
emit: "TEST-REAL | HIGH | {FAILING_TEST} | deterministic failure on PR branch; passes on base — this PR introduced the regression"
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
### Quarantine manifest
|
|
1230
|
+
|
|
1231
|
+
The manifest lives at `.forgedock/quarantine.jsonl` in the project root (or at the path set in `FORGEDOCK_QUARANTINE_MANIFEST`). Each line is a JSON object:
|
|
1232
|
+
|
|
1233
|
+
```json
|
|
1234
|
+
{"test":"<id>","classification":"PRE_BROKEN|FLAKY","base":"<branch>","pr_branch":"<branch>","issue":"<num>","repo":"<owner/repo>","first_seen":"<ISO-8601>","runs_pr":3,"failures_pr":3,"runs_base":1,"failures_base":1}
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
The manifest is append-only. Duplicate entries (same test already quarantined) are skipped. A corresponding GitHub issue is filed automatically when the script has `--repo` and `gh` is authenticated.
|
|
1238
|
+
|
|
1239
|
+
**Reviewers**: when the manifest exists, read it during Phase 2 of `review-pr.md` to avoid re-filing known-quarantined tests as new findings.
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
959
1243
|
## Step 3: Compile findings
|
|
960
1244
|
|
|
961
1245
|
Format findings as a structured list:
|
package/commands/replay.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Read-only playback of a completed pipeline run — shows FORGE annotations phase by phase with timestamps
|
|
3
3
|
argument-hint: <issue-number>
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# /replay — Pipeline Run Playback
|
|
@@ -9,7 +10,7 @@ argument-hint: <issue-number>
|
|
|
9
10
|
|
|
10
11
|
Replay a completed pipeline run. Fetches all FORGE annotations posted during the pipeline and displays them chronologically — phase headings, timestamps, and key structured fields. Read-only: nothing is written, no labels changed.
|
|
11
12
|
|
|
12
|
-
**Agent model policy**:
|
|
13
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
13
14
|
|
|
14
15
|
---
|
|
15
16
|
|