forgedock 1.0.5 → 1.0.7
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 +7 -1
- package/bin/forgedock.mjs +496 -364
- package/bin/labels.json +152 -0
- package/commands/audit-agents.md +4 -2
- package/commands/autopilot.md +19 -11
- package/commands/cleanup.md +27 -12
- package/commands/deploy-info.md +36 -17
- package/commands/incident-response.md +15 -10
- package/commands/issue.md +8 -6
- package/commands/milestone.md +97 -48
- package/commands/orchestrate.md +109 -65
- package/commands/pipeline-health.md +8 -1
- package/commands/qa-sweep.md +14 -3
- package/commands/review-pr-agents.md +1 -1
- package/commands/review-pr-staging.md +129 -23
- package/commands/review-pr.md +237 -90
- package/commands/rollback.md +26 -7
- package/commands/security-audit.md +27 -25
- package/commands/sync-ecosystem.md +2 -2
- package/commands/work-on/build/architect.md +154 -2
- package/commands/work-on/build/context.md +131 -3
- package/commands/work-on/build/validate.md +32 -13
- package/commands/work-on/decompose.md +2 -0
- package/commands/work-on-monolithic.md +9 -5
- package/commands/work-on.md +67 -23
- package/docs/CONFIG.md +117 -0
- package/package.json +2 -1
- package/scripts/verify-route-registration.sh +36 -15
- package/scripts/verify-sops-chain.sh +18 -0
- package/templates/devdocs/README.md +96 -0
- package/templates/devdocs/agent/using-forgedock.md +245 -0
- package/templates/devdocs/agent/using-github.md +325 -0
- package/templates/devdocs/project/architecture.md +122 -0
- package/templates/devdocs/project/conventions.md +158 -0
- package/templates/devdocs/project/custom-instructions.md +73 -0
- package/templates/devdocs/project/glossary.md +84 -0
- package/templates/devdocs/project/stack.md +80 -0
- package/templates/devdocs/templates/commit-convention.md +147 -0
- package/templates/devdocs/templates/issue.md +141 -0
- package/templates/devdocs/templates/pull-request.md +115 -0
- package/templates/devdocs/templates/review-finding.md +147 -0
- package/bin/github-app-manifest.json +0 -24
package/commands/review-pr.md
CHANGED
|
@@ -156,27 +156,78 @@ Record: services touched, domains detected, PR scope (1-2 sentences), change cat
|
|
|
156
156
|
## Phase 2: Automated Checks (Run ALL in Parallel)
|
|
157
157
|
|
|
158
158
|
### 2A: Python Linting (if Python changed)
|
|
159
|
+
|
|
160
|
+
Read `forge.yaml → verification.commands.python` for project-specific tool commands:
|
|
161
|
+
|
|
159
162
|
```bash
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
# Read toolchain commands from forge.yaml
|
|
164
|
+
PYTHON_FORMAT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'format:' | head -1 | sed "s/.*format: *['\"]//;s/['\"].*//")
|
|
165
|
+
PYTHON_LINT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'lint:' | head -1 | sed "s/.*lint: *['\"]//;s/['\"].*//")
|
|
166
|
+
PYTHON_TYPECHECK=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'typecheck:' | head -1 | sed "s/.*typecheck: *['\"]//;s/['\"].*//")
|
|
167
|
+
|
|
168
|
+
# Run format check
|
|
169
|
+
if [ -n "$PYTHON_FORMAT" ]; then
|
|
170
|
+
eval "$PYTHON_FORMAT" 2>&1 | head -30
|
|
171
|
+
else
|
|
172
|
+
echo "SKIPPED — python.format not configured in verification.commands"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# Run lint
|
|
176
|
+
if [ -n "$PYTHON_LINT" ]; then
|
|
177
|
+
eval "$PYTHON_LINT" 2>&1 | head -30
|
|
178
|
+
else
|
|
179
|
+
echo "SKIPPED — python.lint not configured in verification.commands"
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
# Run typecheck
|
|
183
|
+
if [ -n "$PYTHON_TYPECHECK" ]; then
|
|
184
|
+
eval "$PYTHON_TYPECHECK" 2>&1 | head -50
|
|
185
|
+
else
|
|
186
|
+
echo "SKIPPED — python.typecheck not configured in verification.commands"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Always run compile check on changed Python files (fast, language-universal)
|
|
190
|
+
for f in $(gh pr diff $ARGUMENTS --name-only | grep '\.py$'); do
|
|
191
|
+
python3 -m py_compile "$f" 2>&1
|
|
192
|
+
done
|
|
165
193
|
```
|
|
166
|
-
**IMPORTANT**: Always `cd` into service dir and `poetry run black`. Never `pipx run black`.
|
|
167
194
|
|
|
168
|
-
### 2B: TypeScript/JS (if
|
|
195
|
+
### 2B: TypeScript/JS (if TypeScript/JS changed)
|
|
196
|
+
|
|
197
|
+
Read `forge.yaml → verification.commands.typescript` for project-specific tool commands:
|
|
198
|
+
|
|
169
199
|
```bash
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
TS_FORMAT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'format:' | head -1 | sed "s/.*format: *['\"]//;s/['\"].*//")
|
|
201
|
+
TS_LINT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'lint:' | head -1 | sed "s/.*lint: *['\"]//;s/['\"].*//")
|
|
202
|
+
TS_TYPECHECK=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'typecheck:' | head -1 | sed "s/.*typecheck: *['\"]//;s/['\"].*//")
|
|
203
|
+
|
|
204
|
+
# Run format check
|
|
205
|
+
if [ -n "$TS_FORMAT" ]; then
|
|
206
|
+
eval "$TS_FORMAT" 2>&1 | head -30
|
|
207
|
+
else
|
|
208
|
+
echo "SKIPPED — typescript.format not configured in verification.commands"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# Run lint
|
|
212
|
+
if [ -n "$TS_LINT" ]; then
|
|
213
|
+
eval "$TS_LINT" 2>&1 | head -30
|
|
214
|
+
else
|
|
215
|
+
echo "SKIPPED — typescript.lint not configured in verification.commands"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Run typecheck
|
|
219
|
+
if [ -n "$TS_TYPECHECK" ]; then
|
|
220
|
+
eval "$TS_TYPECHECK" 2>&1 | head -50
|
|
221
|
+
TS_TYPECHECK_EXIT=$?
|
|
222
|
+
else
|
|
223
|
+
echo "SKIPPED — typescript.typecheck not configured in verification.commands"
|
|
224
|
+
TS_TYPECHECK_EXIT=0
|
|
225
|
+
fi
|
|
173
226
|
```
|
|
174
227
|
|
|
175
228
|
### 2C: Static Type Checking (if Python changed)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
cd services/api && poetry run ruff check app/ --select=E,W,F,B,S --ignore=E501 2>&1 | head -30
|
|
179
|
-
```
|
|
229
|
+
|
|
230
|
+
Covered by `PYTHON_TYPECHECK` command in 2A above. If `verification.commands.python.typecheck` is unset, this step is explicitly skipped with a log line — it does not silently pass.
|
|
180
231
|
|
|
181
232
|
### 2D: Environment Variable Audit
|
|
182
233
|
```bash
|
|
@@ -206,8 +257,27 @@ git diff origin/main...HEAD -- "**/package.json" | grep -E "^\+" | grep -v "^\+\
|
|
|
206
257
|
```
|
|
207
258
|
|
|
208
259
|
### 2H: Tests
|
|
260
|
+
|
|
261
|
+
Read `forge.yaml → verification.commands.{lang}.test` for the project's test command:
|
|
262
|
+
|
|
209
263
|
```bash
|
|
210
|
-
|
|
264
|
+
# Check each configured language for a test command
|
|
265
|
+
for lang in python typescript go rust; do
|
|
266
|
+
TEST_CMD=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk "/^ ${lang}:/{f=1;next} f && /^ [^ \t]/{exit} f" | grep 'test:' | head -1 | sed "s/.*test: *['\"]//;s/['\"].*//")
|
|
267
|
+
if [ -n "$TEST_CMD" ]; then
|
|
268
|
+
echo "=== Running tests (${lang}): ${TEST_CMD} ==="
|
|
269
|
+
eval "$TEST_CMD" 2>&1 | tail -30
|
|
270
|
+
TEST_EXIT=$?
|
|
271
|
+
[ "$TEST_EXIT" -ne 0 ] && echo "BLOCKING: ${lang} tests failed (exit $TEST_EXIT)"
|
|
272
|
+
fi
|
|
273
|
+
done
|
|
274
|
+
|
|
275
|
+
# If no test commands were configured, log explicitly
|
|
276
|
+
PYTHON_TEST=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'test:' | head -1 | sed "s/.*test: *['\"]//;s/['\"].*//")
|
|
277
|
+
TS_TEST=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'test:' | head -1 | sed "s/.*test: *['\"]//;s/['\"].*//")
|
|
278
|
+
if [ -z "$PYTHON_TEST" ] && [ -z "$TS_TEST" ]; then
|
|
279
|
+
echo "SKIPPED — no test commands configured in verification.commands"
|
|
280
|
+
fi
|
|
211
281
|
```
|
|
212
282
|
**BLOCKING if tests fail.**
|
|
213
283
|
|
|
@@ -217,36 +287,67 @@ cd services/api && poetry run pytest tests/ -x -q --tb=short 2>&1 | tail -30
|
|
|
217
287
|
CHANGED_FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
218
288
|
HAS_TS=$(echo "$CHANGED_FILES" | grep -E '\.(tsx?|jsx?)$' | head -1)
|
|
219
289
|
HAS_PY=$(echo "$CHANGED_FILES" | grep -E '\.py$' | head -1)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
290
|
+
# Use POSIX-portable if/else (avoid bash-only [[ ]])
|
|
291
|
+
IS_STAGING_TO_MAIN="false"
|
|
292
|
+
if [ "$HEAD" = "staging" ] && [ "$BASE" = "main" ]; then IS_STAGING_TO_MAIN="true"; fi
|
|
293
|
+
IS_MILESTONE_TO_STAGING="false"
|
|
294
|
+
case "$HEAD" in milestone/*) if [ "$BASE" = "staging" ]; then IS_MILESTONE_TO_STAGING="true"; fi ;; esac
|
|
295
|
+
REQUIRES_FULL_BUILD="false"
|
|
296
|
+
if [ "$IS_STAGING_TO_MAIN" = "true" ] || [ "$IS_MILESTONE_TO_STAGING" = "true" ]; then REQUIRES_FULL_BUILD="true"; fi
|
|
223
297
|
```
|
|
224
298
|
|
|
225
299
|
**TypeScript files changed:**
|
|
300
|
+
|
|
301
|
+
Read `forge.yaml → verification.commands.typescript.typecheck` and `.build`:
|
|
302
|
+
|
|
226
303
|
```bash
|
|
227
304
|
gh pr checkout $ARGUMENTS --detach 2>/dev/null
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
305
|
+
|
|
306
|
+
TS_TYPECHECK=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'typecheck:' | head -1 | sed "s/.*typecheck: *['\"]//;s/['\"].*//")
|
|
307
|
+
TS_BUILD=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'build:' | head -1 | sed "s/.*build: *['\"]//;s/['\"].*//")
|
|
308
|
+
|
|
309
|
+
if [ -n "$TS_TYPECHECK" ]; then
|
|
310
|
+
eval "$TS_TYPECHECK" 2>&1
|
|
311
|
+
TSC_EXIT=$?
|
|
312
|
+
else
|
|
313
|
+
echo "SKIPPED — typescript.typecheck not configured in verification.commands"
|
|
314
|
+
TSC_EXIT=0
|
|
315
|
+
fi
|
|
316
|
+
|
|
317
|
+
if [ -n "$TS_BUILD" ] && { [ "$REQUIRES_FULL_BUILD" = "true" ] || [ "$TSC_EXIT" -eq 0 ]; }; then
|
|
318
|
+
eval "$TS_BUILD" 2>&1 | tail -30
|
|
232
319
|
BUILD_EXIT=$?
|
|
320
|
+
elif [ -z "$TS_BUILD" ]; then
|
|
321
|
+
echo "SKIPPED — typescript.build not configured in verification.commands"
|
|
233
322
|
fi
|
|
234
|
-
|
|
323
|
+
|
|
324
|
+
git checkout - 2>/dev/null
|
|
235
325
|
```
|
|
236
326
|
|
|
237
327
|
If `TSC_EXIT != 0`: **CONFIRMED blocking** — type errors.
|
|
238
|
-
If `BUILD_EXIT != 0`: **CONFIRMED blocking** —
|
|
328
|
+
If `BUILD_EXIT != 0`: **CONFIRMED blocking** — build/prerender failure.
|
|
239
329
|
|
|
240
|
-
**CRITICAL**:
|
|
330
|
+
**CRITICAL**: typecheck alone is NOT sufficient for staging→main or milestone→staging — configure `typescript.build` in `verification.commands` to catch SSG/prerender failures that typecheck misses.
|
|
241
331
|
|
|
242
332
|
**Python files changed:**
|
|
333
|
+
|
|
334
|
+
Read `forge.yaml → verification.commands.python.format` and `.build`:
|
|
335
|
+
|
|
243
336
|
```bash
|
|
244
337
|
gh pr checkout $ARGUMENTS --detach 2>/dev/null
|
|
338
|
+
|
|
339
|
+
# Compile-check all changed Python files (language-universal — no config needed)
|
|
245
340
|
for f in $(echo "$CHANGED_FILES" | grep '\.py$'); do python3 -m py_compile "$f" 2>&1; done
|
|
341
|
+
|
|
246
342
|
if [ "$REQUIRES_FULL_BUILD" = "true" ]; then
|
|
247
|
-
|
|
248
|
-
|
|
343
|
+
PYTHON_FORMAT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'format:' | head -1 | sed "s/.*format: *['\"]//;s/['\"].*//")
|
|
344
|
+
if [ -n "$PYTHON_FORMAT" ]; then
|
|
345
|
+
eval "$PYTHON_FORMAT" 2>&1
|
|
346
|
+
else
|
|
347
|
+
echo "SKIPPED — python.format not configured in verification.commands (full-build format check skipped)"
|
|
348
|
+
fi
|
|
249
349
|
fi
|
|
350
|
+
|
|
250
351
|
git checkout - 2>/dev/null
|
|
251
352
|
```
|
|
252
353
|
|
|
@@ -258,9 +359,9 @@ Check whether the PR's actual changes match what the builder committed to in its
|
|
|
258
359
|
|
|
259
360
|
```bash
|
|
260
361
|
# Find the contract comment on the linked issue
|
|
261
|
-
ISSUE_NUM=$(gh pr view $ARGUMENTS --json body --jq '.body
|
|
362
|
+
ISSUE_NUM=$(gh pr view $ARGUMENTS --json body --jq '.body | gsub("(?s).*?(?:Closes #|#)(?<n>[0-9]+).*"; "\(.n)") // empty' 2>/dev/null | head -1)
|
|
262
363
|
if [ -n "$ISSUE_NUM" ]; then
|
|
263
|
-
CONTRACT_FILES=$(gh api repos/${REPO}/issues/${ISSUE_NUM}/comments --jq '[.[] | select(.body | contains("FORGE:CONTRACT"))] | .[0].body' 2>/dev/null | grep -
|
|
364
|
+
CONTRACT_FILES=$(gh api repos/${REPO}/issues/${ISSUE_NUM}/comments --jq '[.[] | select(.body | contains("FORGE:CONTRACT"))] | .[0].body' 2>/dev/null | grep -E '`[^`]+\.(py|tsx?|sql|sh|yml|yaml|json)`' | grep -oE '`[^`]+\.(py|tsx?|sql|sh|yml|yaml|json)`' | tr -d '`' | sort -u)
|
|
264
365
|
PR_FILES=$(gh pr diff $ARGUMENTS --name-only | sort -u)
|
|
265
366
|
|
|
266
367
|
# Files in PR but NOT in contract
|
|
@@ -289,17 +390,28 @@ For each changed file, identify its **activation path** — how does execution r
|
|
|
289
390
|
|
|
290
391
|
### Step 2.5A: Identify File Types and Their Registration Points
|
|
291
392
|
|
|
292
|
-
Map each changed file to its activation requirements
|
|
393
|
+
Map each changed file to its activation requirements.
|
|
394
|
+
|
|
395
|
+
**Layout path resolution** — Before applying the table below, read your project's layout from `forge.yaml → review.layout` and substitute the values into the pattern column. Defaults (used when the key is absent) match the AlterLab monorepo layout:
|
|
396
|
+
|
|
397
|
+
| `forge.yaml` key | Default | Used in table as |
|
|
398
|
+
|-----------------|---------|-----------------|
|
|
399
|
+
| `review.layout.pages` | `web/src/app` | `{PAGES_ROOT}` |
|
|
400
|
+
| `review.layout.api_routers` | `services/api/app/routers` | `{API_ROUTERS}` |
|
|
401
|
+
| `review.layout.api_main` | `services/api/app/main.py` | `{API_MAIN}` |
|
|
402
|
+
| `review.layout.api_middleware` | `services/api/app/middleware` | `{API_MIDDLEWARE}` |
|
|
403
|
+
| `review.layout.migrations` | `infra/migrations` | `{MIGRATIONS}` |
|
|
404
|
+
| `review.layout.worker` | `services/worker` | `{WORKER}` |
|
|
293
405
|
|
|
294
406
|
| Changed File Pattern | Assumption | Verification Target |
|
|
295
407
|
|---------------------|------------|---------------------|
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
408
|
+
| `{PAGES_ROOT}/api/**/*.ts` (Route Handler) | Requests reach this handler | Check `web/next.config.js` rewrites don't shadow it; check `infra/nginx/nginx.conf` routes path to Next.js |
|
|
409
|
+
| `{API_ROUTERS}/*.py` (API Router) | Router is registered in app | Check `{API_MAIN}` includes this router |
|
|
410
|
+
| `{API_MIDDLEWARE}/*.py` | Middleware is in the stack | Check `{API_MAIN}` middleware registration order |
|
|
411
|
+
| `{MIGRATIONS}/*.sql` | Migration runs on current schema | Check previous migration's end state matches assumptions |
|
|
300
412
|
| `shared/**/*.py` | Imported by consumer services | Verify import paths exist in api/worker; check Docker volume mounts |
|
|
301
|
-
| `
|
|
302
|
-
| Any file using `os.getenv("NEW_VAR")` | Env var is set at runtime | Check `docker-compose.yml`, `.env.example`, `
|
|
413
|
+
| `{WORKER}/**/*.py` (Consumer) | Queue consumer is registered | Check consumer registration in worker startup |
|
|
414
|
+
| Any file using `os.getenv("NEW_VAR")` | Env var is set at runtime | Check `docker-compose.yml`, `.env.example`, `{API_MAIN}` env validation module |
|
|
303
415
|
| `scripts/decrypt-secrets.sh` (ENV_MAPPING) | Secret reaches running container | Trace full chain: SOPS key → ENV_MAPPING → deploy workflow SCP target → merge script path → `docker-compose.prod.yml` env_file. See Step 2.5B SOPS deploy chain check. |
|
|
304
416
|
| `.secrets/prod.enc.yaml` | SOPS key maps to ENV_MAPPING | Verify key path in YAML matches the tuple in `decrypt-secrets.sh` ENV_MAPPING |
|
|
305
417
|
| `.github/workflows/deploy-production.yml` | Deploy paths are consistent | Verify SCP target + merge script `PROJECT` var resolve to same dir as `docker-compose.prod.yml` env_file |
|
|
@@ -317,48 +429,66 @@ Map each changed file to its activation requirements:
|
|
|
317
429
|
|
|
318
430
|
For each changed file, execute the relevant checks using the standalone verification scripts in `$FORGE_HOME/scripts/`. These scripts can also be run independently outside the review context (e.g., from `/quality-gate` or `/work-on` builder steps).
|
|
319
431
|
|
|
432
|
+
**Platform note**: The verify-*.sh scripts require bash and standard POSIX tools. On Windows without bash (Git Bash / WSL / MSYS2), these checks are skipped with an explicit message — the review continues without them.
|
|
433
|
+
|
|
320
434
|
```bash
|
|
321
435
|
CHANGED_FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
322
436
|
REPO_ROOT="." # Assumes cwd is the repo root
|
|
323
437
|
|
|
324
|
-
#
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# --- Script-based checks (reusable, testable, deterministic) ---
|
|
331
|
-
# Each script exits 0 (pass), 1 (blocking findings), or 2 (warnings only).
|
|
332
|
-
# Output is structured: "BLOCKING: ...", "WARNING: ...", "OK: ..." per line.
|
|
333
|
-
|
|
334
|
-
# 1. Route/router/middleware/shared-module/component registration
|
|
335
|
-
echo "=== Running: verify-route-registration.sh ==="
|
|
336
|
-
$FORGE_HOME/scripts/verify-route-registration.sh "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
337
|
-
|
|
338
|
-
# 2. Environment variable wiring (checks .env.example, docker-compose, env_validation, SOPS mapping)
|
|
339
|
-
echo "=== Running: verify-env-vars.sh ==="
|
|
340
|
-
$FORGE_HOME/scripts/verify-env-vars.sh "$DIFF_TMP" "$REPO_ROOT" || true
|
|
341
|
-
|
|
342
|
-
# 3. Host headers in shell scripts + client-side proxy bypass check
|
|
343
|
-
# Read project-specific internal service patterns from forge.yaml (if present)
|
|
344
|
-
FORGE_INTERNAL_PATTERNS=""
|
|
345
|
-
if [ -f "$REPO_ROOT/forge.yaml" ]; then
|
|
346
|
-
FORGE_INTERNAL_PATTERNS=$(grep -A 999 'internal_service_patterns:' "$REPO_ROOT/forge.yaml" \
|
|
347
|
-
| grep -E '^\s*-\s+' \
|
|
348
|
-
| sed 's/^\s*-\s*//' \
|
|
349
|
-
| tr -d '"'"'" \
|
|
350
|
-
| paste -sd '|' -)
|
|
438
|
+
# --- Platform / bash capability guard ---
|
|
439
|
+
# The verify-*.sh scripts require bash. Detect availability before invoking.
|
|
440
|
+
# On Windows without Git Bash/WSL, skip gracefully rather than crash.
|
|
441
|
+
BASH_AVAILABLE=false
|
|
442
|
+
if command -v bash >/dev/null 2>&1 && bash -c 'echo ok' >/dev/null 2>&1; then
|
|
443
|
+
BASH_AVAILABLE=true
|
|
351
444
|
fi
|
|
352
|
-
export FORGE_INTERNAL_PATTERNS
|
|
353
|
-
echo "=== Running: verify-host-headers.sh ==="
|
|
354
|
-
$FORGE_HOME/scripts/verify-host-headers.sh "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
355
445
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
446
|
+
if [ "$BASH_AVAILABLE" = "true" ]; then
|
|
447
|
+
# Write changed files and diff to temp files for script consumption.
|
|
448
|
+
# Use PID-based names instead of mktemp for cross-platform compatibility.
|
|
449
|
+
CHANGED_FILES_TMP="/tmp/forge-review-changed-$$.tmp"
|
|
450
|
+
DIFF_TMP="/tmp/forge-review-diff-$$.tmp"
|
|
451
|
+
echo "$CHANGED_FILES" > "$CHANGED_FILES_TMP"
|
|
452
|
+
gh pr diff $ARGUMENTS > "$DIFF_TMP"
|
|
453
|
+
|
|
454
|
+
# --- Script-based checks (reusable, testable, deterministic) ---
|
|
455
|
+
# Each script exits 0 (pass), 1 (blocking findings), or 2 (warnings only).
|
|
456
|
+
# Output is structured: "BLOCKING: ...", "WARNING: ...", "OK: ..." per line.
|
|
457
|
+
|
|
458
|
+
# 1. Route/router/middleware/shared-module/component registration
|
|
459
|
+
echo "=== Running: verify-route-registration.sh ==="
|
|
460
|
+
bash "$FORGE_HOME/scripts/verify-route-registration.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
461
|
+
|
|
462
|
+
# 2. Environment variable wiring (checks .env.example, docker-compose, env_validation, SOPS mapping)
|
|
463
|
+
echo "=== Running: verify-env-vars.sh ==="
|
|
464
|
+
bash "$FORGE_HOME/scripts/verify-env-vars.sh" "$DIFF_TMP" "$REPO_ROOT" || true
|
|
465
|
+
|
|
466
|
+
# 3. Host headers in shell scripts + client-side proxy bypass check
|
|
467
|
+
# Read project-specific internal service patterns from forge.yaml (if present)
|
|
468
|
+
FORGE_INTERNAL_PATTERNS=""
|
|
469
|
+
if [ -f "$REPO_ROOT/forge.yaml" ]; then
|
|
470
|
+
FORGE_INTERNAL_PATTERNS=$(grep -A 999 'internal_service_patterns:' "$REPO_ROOT/forge.yaml" \
|
|
471
|
+
| grep -E '^\s*-\s+' \
|
|
472
|
+
| sed 's/^\s*-\s*//' \
|
|
473
|
+
| tr -d '"'"'" \
|
|
474
|
+
| awk 'NR>1{printf "|"}{printf $0}END{print ""}')
|
|
475
|
+
fi
|
|
476
|
+
export FORGE_INTERNAL_PATTERNS
|
|
477
|
+
echo "=== Running: verify-host-headers.sh ==="
|
|
478
|
+
bash "$FORGE_HOME/scripts/verify-host-headers.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
479
|
+
|
|
480
|
+
# 4. SOPS deploy chain (ENV_MAPPING consistency, deploy path drift, hotfix sync)
|
|
481
|
+
echo "=== Running: verify-sops-chain.sh ==="
|
|
482
|
+
bash "$FORGE_HOME/scripts/verify-sops-chain.sh" "$DIFF_TMP" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
359
483
|
|
|
360
|
-
# Cleanup temp files
|
|
361
|
-
rm -f "$CHANGED_FILES_TMP" "$DIFF_TMP"
|
|
484
|
+
# Cleanup temp files
|
|
485
|
+
rm -f "$CHANGED_FILES_TMP" "$DIFF_TMP"
|
|
486
|
+
else
|
|
487
|
+
echo "=== Phase 2.5B: verify-*.sh skipped — bash not available on this platform ==="
|
|
488
|
+
echo " The verify-*.sh scripts require bash (POSIX shell)."
|
|
489
|
+
echo " Install Git Bash (Windows) or WSL to enable these checks."
|
|
490
|
+
echo " The review continues — integration assumptions should be verified manually."
|
|
491
|
+
fi
|
|
362
492
|
|
|
363
493
|
# --- Inline checks (not yet extracted to scripts) ---
|
|
364
494
|
|
|
@@ -396,7 +526,9 @@ done
|
|
|
396
526
|
# diff. Pre-existing drift is the most dangerous kind — it lurks until
|
|
397
527
|
# staging→main and then blocks the deploy.
|
|
398
528
|
WORKFLOW_FILES=$(echo "$CHANGED_FILES" | grep -E "^\.github/workflows/.*\.yml$" || true)
|
|
399
|
-
|
|
529
|
+
# Use POSIX-portable conditional (avoid bash-only [[ ]])
|
|
530
|
+
IS_STAGING_PR="false"
|
|
531
|
+
if [ "$HEAD" = "staging" ] && [ "$BASE" = "main" ]; then IS_STAGING_PR="true"; fi
|
|
400
532
|
|
|
401
533
|
if [ -n "$WORKFLOW_FILES" ] || [ "$IS_STAGING_PR" = "true" ]; then
|
|
402
534
|
echo "=== Sibling Workflow Drift Check (MANDATORY for staging→main) ==="
|
|
@@ -415,9 +547,14 @@ if [ -n "$WORKFLOW_FILES" ] || [ "$IS_STAGING_PR" = "true" ]; then
|
|
|
415
547
|
|
|
416
548
|
echo "--- Comparing '$JOB' job between ci.yml and deploy-production.yml ---"
|
|
417
549
|
|
|
418
|
-
# Extract env vars from ALL steps in the job (not just pytest)
|
|
419
|
-
|
|
420
|
-
|
|
550
|
+
# Extract env vars from ALL steps in the job (not just pytest).
|
|
551
|
+
# Flag-based awk avoids the range-collapse bug: /pat1/,/pat2/ collapses
|
|
552
|
+
# to a single line when the header (e.g. " test-api:") matches both
|
|
553
|
+
# patterns simultaneously. The flag form sets p=1 on the header line,
|
|
554
|
+
# prints body lines while p=1, and clears p when the next sibling job
|
|
555
|
+
# header (same indentation, lowercase start) is seen. <!-- Added: forge#310 -->
|
|
556
|
+
CI_ENVS=$(awk -v pat="^ ${JOB}:" 'BEGIN{p=0} $0~pat{p=1; print; next} p && /^ [a-z]/{p=0} p{print}' "$CI_WF" 2>/dev/null | grep -E "PYTHONPATH|DATABASE_URL|REDIS_URL|TESTING" | sed 's/^ *//' | sort)
|
|
557
|
+
DEPLOY_ENVS=$(awk -v pat="^ ${JOB}:" 'BEGIN{p=0} $0~pat{p=1; print; next} p && /^ [a-z]/{p=0} p{print}' "$DEPLOY_WF" 2>/dev/null | grep -E "PYTHONPATH|DATABASE_URL|REDIS_URL|TESTING" | sed 's/^ *//' | sort)
|
|
421
558
|
|
|
422
559
|
# Check for PYTHONPATH specifically — the exact var that caused the #11356 failure
|
|
423
560
|
CI_PYPATH=$(echo "$CI_ENVS" | grep "PYTHONPATH" || echo "(not set)")
|
|
@@ -430,16 +567,16 @@ if [ -n "$WORKFLOW_FILES" ] || [ "$IS_STAGING_PR" = "true" ]; then
|
|
|
430
567
|
fi
|
|
431
568
|
|
|
432
569
|
# Check for dependency installation steps that exist in one but not the other
|
|
433
|
-
CI_INSTALLS=$(
|
|
434
|
-
DEPLOY_INSTALLS=$(
|
|
570
|
+
CI_INSTALLS=$(awk -v pat="^ ${JOB}:" 'BEGIN{p=0} $0~pat{p=1; print; next} p && /^ [a-z]/{p=0} p{print}' "$CI_WF" 2>/dev/null | grep -c "poetry install\|pip install\|npm install" || echo 0)
|
|
571
|
+
DEPLOY_INSTALLS=$(awk -v pat="^ ${JOB}:" 'BEGIN{p=0} $0~pat{p=1; print; next} p && /^ [a-z]/{p=0} p{print}' "$DEPLOY_WF" 2>/dev/null | grep -c "poetry install\|pip install\|npm install" || echo 0)
|
|
435
572
|
if [ "$CI_INSTALLS" != "$DEPLOY_INSTALLS" ]; then
|
|
436
573
|
echo " WARNING: Different number of dependency install steps in '$JOB' — ci.yml has $CI_INSTALLS, deploy has $DEPLOY_INSTALLS"
|
|
437
574
|
echo " ACTION: Read both files and verify all dependencies needed by tests are installed in both workflows."
|
|
438
575
|
fi
|
|
439
576
|
|
|
440
577
|
# Check step names — if CI has a step that deploy doesn't, flag it
|
|
441
|
-
CI_STEPS=$(
|
|
442
|
-
DEPLOY_STEPS=$(
|
|
578
|
+
CI_STEPS=$(awk -v pat="^ ${JOB}:" 'BEGIN{p=0} $0~pat{p=1; print; next} p && /^ [a-z]/{p=0} p{print}' "$CI_WF" 2>/dev/null | grep "- name:" | sed 's/.*- name: //' | sort)
|
|
579
|
+
DEPLOY_STEPS=$(awk -v pat="^ ${JOB}:" 'BEGIN{p=0} $0~pat{p=1; print; next} p && /^ [a-z]/{p=0} p{print}' "$DEPLOY_WF" 2>/dev/null | grep "- name:" | sed 's/.*- name: //' | sort)
|
|
443
580
|
MISSING_IN_DEPLOY=$(comm -23 <(echo "$CI_STEPS") <(echo "$DEPLOY_STEPS") 2>/dev/null || true)
|
|
444
581
|
if [ -n "$MISSING_IN_DEPLOY" ]; then
|
|
445
582
|
echo " WARNING: Steps in ci.yml '$JOB' missing from deploy-production.yml:"
|
|
@@ -568,7 +705,10 @@ gh api repos/{owner}/{repo}/issues/$ARGUMENTS/comments --jq '.[-10:] | .[].body[
|
|
|
568
705
|
**Skip if**: Only 1 agent OR total findings ≤ 3.
|
|
569
706
|
|
|
570
707
|
```bash
|
|
571
|
-
|
|
708
|
+
# Extract structured finding IDs from FINDING HTML comments.
|
|
709
|
+
# Uses jq's scan() (POSIX-portable, no grep -oP required).
|
|
710
|
+
ALL_FINDINGS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
|
|
711
|
+
--jq '[.[].body | scan("<!-- FINDING:([^>]+) -->") | .[0]] | join("\n")')
|
|
572
712
|
AGENT_COUNT=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '[.[] | select(.body | test("REVIEW-FINDINGS-START"))] | length')
|
|
573
713
|
FINDING_COUNT=$(echo "$ALL_FINDINGS" | grep -c '.' || echo 0)
|
|
574
714
|
```
|
|
@@ -595,9 +735,13 @@ REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
|
595
735
|
HAS_SYNTHESIS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '.[].body' | grep -c 'REVIEW-FINDINGS-SYNTHESIZED-START' || echo 0)
|
|
596
736
|
|
|
597
737
|
if [ "$HAS_SYNTHESIS" -gt 0 ]; then
|
|
598
|
-
|
|
738
|
+
# Extract finding IDs from synthesized block using jq scan() — no grep -oP needed
|
|
739
|
+
FINDINGS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
|
|
740
|
+
--jq '[.[] | select(.body | test("REVIEW-FINDINGS-SYNTHESIZED-START")) | .body | scan("<!-- FINDING:([^>]+) -->") | .[0]] | join("\n")')
|
|
599
741
|
else
|
|
600
|
-
|
|
742
|
+
# Extract finding IDs from all agent comments using jq scan() — portable, no grep -oP
|
|
743
|
+
FINDINGS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
|
|
744
|
+
--jq '[.[].body | scan("<!-- FINDING:([^>]+) -->") | .[0]] | join("\n")')
|
|
601
745
|
fi
|
|
602
746
|
```
|
|
603
747
|
|
|
@@ -616,10 +760,12 @@ If still 0: review is clean — skip to Phase 7.
|
|
|
616
760
|
### 6C: Create Issues
|
|
617
761
|
|
|
618
762
|
```bash
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
gh label create "
|
|
622
|
-
gh label create "
|
|
763
|
+
# Colors match the canonical ForgeDock label manifest (bin/labels.json).
|
|
764
|
+
# Run `npx forgedock labels setup` to bootstrap all managed labels at once.
|
|
765
|
+
gh label create "review-finding" --color "D93F0B" --description "Defect or improvement found during automated PR review. Managed by ForgeDock." --force -R {GH_REPO} 2>/dev/null
|
|
766
|
+
gh label create "needs-validation" --color "FBCA04" --description "Review finding awaiting human validation. Managed by ForgeDock." --force -R {GH_REPO} 2>/dev/null
|
|
767
|
+
gh label create "validated" --color "0E8A16" --description "Review finding confirmed as a real issue. Managed by ForgeDock." --force -R {GH_REPO} 2>/dev/null
|
|
768
|
+
gh label create "false-positive" --color "CCCCCC" --description "Review finding dismissed as a false positive. Managed by ForgeDock." --force -R {GH_REPO} 2>/dev/null
|
|
623
769
|
```
|
|
624
770
|
|
|
625
771
|
**Milestone detection:**
|
|
@@ -693,7 +839,7 @@ else
|
|
|
693
839
|
' 2>/dev/null | head -1)
|
|
694
840
|
if [ -n "$REGRESSION" ]; then
|
|
695
841
|
echo "REGRESSION: Previously fixed in #${REGRESSION} — elevating priority"
|
|
696
|
-
# Create with regression warning and P1
|
|
842
|
+
# Create with regression warning and priority:P1 label
|
|
697
843
|
fi
|
|
698
844
|
fi
|
|
699
845
|
```
|
|
@@ -701,7 +847,7 @@ fi
|
|
|
701
847
|
**Rules:**
|
|
702
848
|
- Open `review-finding` issue at same file within ±5 lines → **skip** (do not create duplicate)
|
|
703
849
|
- Open `review-finding` issue at same file with similar title (3+ shared keywords) → **skip** (likely same finding despite line drift)
|
|
704
|
-
- Closed `review-finding` at same file within ±5 lines → create with regression warning, elevate to P1
|
|
850
|
+
- Closed `review-finding` at same file within ±5 lines → create with regression warning, elevate to `priority:P1`
|
|
705
851
|
|
|
706
852
|
**For each finding** (that passes dedup), create issue:
|
|
707
853
|
```bash
|
|
@@ -757,7 +903,7 @@ ISSUE_EOF
|
|
|
757
903
|
)" --json number --jq '.number')
|
|
758
904
|
```
|
|
759
905
|
|
|
760
|
-
Labels: `review-finding` + `needs-validation` + priority (`P1` CONFIRMED, `P2` LIKELY, `P3` POSSIBLE).
|
|
906
|
+
Labels: `review-finding` + `needs-validation` + priority (`priority:P1` CONFIRMED, `priority:P2` LIKELY, `priority:P3` POSSIBLE).
|
|
761
907
|
|
|
762
908
|
**Add to project board:**
|
|
763
909
|
```bash
|
|
@@ -922,7 +1068,8 @@ fi
|
|
|
922
1068
|
|
|
923
1069
|
```bash
|
|
924
1070
|
CURRENT_SHA=$(gh pr view $ARGUMENTS --json headRefOid --jq '.headRefOid')
|
|
925
|
-
REVIEW_IS_STALE
|
|
1071
|
+
REVIEW_IS_STALE="false"
|
|
1072
|
+
if [ "$CURRENT_SHA" != "$REVIEW_SHA" ]; then REVIEW_IS_STALE="true"; fi
|
|
926
1073
|
```
|
|
927
1074
|
|
|
928
1075
|
```bash
|
package/commands/rollback.md
CHANGED
|
@@ -9,6 +9,7 @@ argument-hint: [PR number to revert, or "last" for most recent deploy]
|
|
|
9
9
|
|
|
10
10
|
**Config variables used by this command** (set in `forge.yaml`):
|
|
11
11
|
- `{REPO_PATH}` ← `paths.root` — project repository root
|
|
12
|
+
- `{DEPLOY_WORKFLOW}` ← `deploy.workflow` (optional) — GitHub Actions workflow filename for fast-track deploys (e.g., `hotfix-deploy.yml`). When absent or empty, the workflow-trigger suggestion is omitted.
|
|
12
13
|
|
|
13
14
|
You are the pipeline's emergency rollback system. When a shipped feature or fix causes production issues, this command creates a revert PR and fast-tracks it through the pipeline.
|
|
14
15
|
|
|
@@ -112,12 +113,29 @@ If conflicts cannot be auto-resolved, report to user and STOP.
|
|
|
112
113
|
|
|
113
114
|
### Step 2C: Verify the revert compiles
|
|
114
115
|
|
|
116
|
+
Read `forge.yaml → verification.commands` for project-specific tool commands:
|
|
117
|
+
|
|
115
118
|
```bash
|
|
116
|
-
# Python
|
|
117
|
-
|
|
119
|
+
# Python compile check (always runs — no config needed)
|
|
120
|
+
for f in {reverted_python_files}; do
|
|
121
|
+
python3 -m py_compile "$f" 2>&1
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
# Python format check (config-driven)
|
|
125
|
+
PYTHON_FORMAT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'format:' | head -1 | sed "s/.*format: *['\"]//;s/['\"].*//")
|
|
126
|
+
if [ -n "$PYTHON_FORMAT" ]; then
|
|
127
|
+
eval "$PYTHON_FORMAT" 2>&1 | head -20
|
|
128
|
+
else
|
|
129
|
+
echo "SKIPPED — python.format not configured in verification.commands"
|
|
130
|
+
fi
|
|
118
131
|
|
|
119
|
-
# TypeScript
|
|
120
|
-
|
|
132
|
+
# TypeScript typecheck (config-driven)
|
|
133
|
+
TS_TYPECHECK=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'typecheck:' | head -1 | sed "s/.*typecheck: *['\"]//;s/['\"].*//")
|
|
134
|
+
if [ -n "$TS_TYPECHECK" ]; then
|
|
135
|
+
eval "$TS_TYPECHECK" 2>&1 | head -20
|
|
136
|
+
else
|
|
137
|
+
echo "SKIPPED — typescript.typecheck not configured in verification.commands"
|
|
138
|
+
fi
|
|
121
139
|
```
|
|
122
140
|
|
|
123
141
|
---
|
|
@@ -160,10 +178,11 @@ PR_EOF
|
|
|
160
178
|
```bash
|
|
161
179
|
gh pr merge {REVERT_PR} --merge
|
|
162
180
|
```
|
|
163
|
-
- Then trigger hotfix deploy:
|
|
181
|
+
- Then trigger hotfix deploy (requires `deploy.workflow` set in `forge.yaml`):
|
|
164
182
|
```bash
|
|
165
183
|
echo "Revert merged to main. CI/CD will deploy automatically."
|
|
166
|
-
echo "For faster deploy, trigger
|
|
184
|
+
echo "For faster deploy, trigger deploy workflow: gh workflow run {deploy.workflow} --ref main -f {deploy.workflow_inputs.services}={affected} -f {deploy.workflow_inputs.reason}=\"Rollback PR #{PR_NUMBER}\""
|
|
185
|
+
# If deploy.workflow is not configured in forge.yaml, CI/CD will deploy automatically on the next push to main.
|
|
167
186
|
```
|
|
168
187
|
|
|
169
188
|
**If not urgent**:
|
|
@@ -192,7 +211,7 @@ fi
|
|
|
192
211
|
|
|
193
212
|
```bash
|
|
194
213
|
gh issue create --title "fix: {original_title} (reverted — needs new approach)" \
|
|
195
|
-
--label "bug,P1" \
|
|
214
|
+
--label "bug,priority:P1" \
|
|
196
215
|
--body "$(cat <<'BODY_EOF'
|
|
197
216
|
## Problem
|
|
198
217
|
|