forgedock 1.0.22 → 1.1.1
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 +217 -218
- package/bin/cinema.mjs +320 -0
- package/bin/engine/phases.mjs +148 -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 +27 -0
- package/bin/engine-cli.mjs +50 -0
- package/bin/engine.mjs +138 -0
- package/bin/forgedock.mjs +464 -1491
- package/bin/journey.mjs +783 -0
- package/bin/runner.mjs +80 -13
- package/bin/settings-hook.mjs +112 -0
- package/bin/tui.mjs +6 -3
- package/commands/autopilot.md +4 -4
- package/commands/deploy-info.md +5 -3
- package/commands/design-bench.md +1 -0
- package/commands/design-render-critique-loop.md +1 -0
- package/commands/design.md +1 -0
- package/commands/forge-stats.md +1 -0
- package/commands/issue.md +1 -1
- package/commands/orchestrate.md +27 -9
- package/commands/quality-gate.md +80 -34
- package/commands/review-pr-agents.md +7 -7
- package/commands/review-pr.md +21 -4
- package/commands/work-on/build/implement.md +16 -4
- package/commands/work-on/build/validate.md +38 -1
- package/commands/work-on/build.md +27 -9
- package/commands/work-on/close.md +2 -2
- package/commands/work-on/review.md +24 -0
- package/commands/work-on-monolithic.md +1 -0
- package/commands/work-on.md +45 -3
- package/docs/BRAND.md +6 -2
- package/docs/CONFIG.md +39 -34
- package/docs/site/.vitepress/config.mjs +85 -0
- package/docs/site/getting-started.md +15 -8
- package/docs/site/index.md +1 -1
- 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/package.json +10 -4
- 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/commands/quality-gate.md
CHANGED
|
@@ -39,10 +39,10 @@ This quality gate uses **domain detection** to adapt to your project's actual te
|
|
|
39
39
|
| FRONTEND | `.tsx`/`.ts` files in client component paths |
|
|
40
40
|
| PROXY | Client components using `fetch`/`useSWR`/`apiFetch` |
|
|
41
41
|
| SHELL | `.sh` files or scripts using `curl`/`wget` |
|
|
42
|
-
| STRING_SETS | Python files in anti-detection or browser automation paths |
|
|
42
|
+
| 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
43
|
| CONCURRENCY | Python files using `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel` |
|
|
44
44
|
| STATE | Python files with new module-level mutable variable assignments |
|
|
45
|
-
| CAPACITY | Worker/infra Python files with new size/limit/threshold constants |
|
|
45
|
+
| 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
46
|
| WORKFLOW | `.yml` files in `.github/workflows/` |
|
|
47
47
|
| IMPORT_RESOLUTION | Python files with new `from app.*` imports added outside `try:` blocks |
|
|
48
48
|
| INFRA | Dockerfiles or entrypoints with runtime UID changes |
|
|
@@ -92,10 +92,10 @@ Before running checks, classify the changed files into domains. This avoids runn
|
|
|
92
92
|
| `*.tsx`, `*.ts` in `web/src/` (excluding `route.ts`) | FRONTEND |
|
|
93
93
|
| `*.tsx`, `*.ts` client components with fetch/useSWR | PROXY |
|
|
94
94
|
| `*.sh` files or scripts with `curl`/`wget` | SHELL |
|
|
95
|
-
| `*.py` in `anti_detection/`, `consumers/`, `browser/`, or `shared/detection/` | STRING_SETS |
|
|
95
|
+
| `*.py` in `anti_detection/`, `consumers/`, `browser/`, or `shared/detection/` | STRING_SETS *(scraping pack — opt-in, see 2J)* |
|
|
96
96
|
| `*.py` containing `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel` | CONCURRENCY |
|
|
97
97
|
| `*.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 |
|
|
98
|
+
| `*.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
99
|
| `*.yml` in `.github/workflows/` | WORKFLOW |
|
|
100
100
|
| `*.py` with new `from app.` import statements added outside `try:` blocks | IMPORT_RESOLUTION |
|
|
101
101
|
| `Dockerfile*` or `entrypoint*.sh` with added/changed `USER`, `su-exec`, `gosu`, or `setuid` | INFRA |
|
|
@@ -131,7 +131,12 @@ for f in "${CHANGED_FILES_ARR[@]}"; do
|
|
|
131
131
|
esac
|
|
132
132
|
case "$f" in
|
|
133
133
|
*anti_detection/*|*consumers/*|*browser/*|*shared/detection/*)
|
|
134
|
-
|
|
134
|
+
# STRING_SETS is a scraping-pack domain — opt-in via forge.yaml → quality_gate.optional_domains
|
|
135
|
+
# <!-- Updated: forge#1349 — not enabled by default; scraping-product-specific -->
|
|
136
|
+
if echo "$f" | grep -qE '\.py$'; then
|
|
137
|
+
_OPT_DOMAINS=$(grep -A5 'optional_domains:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null | grep -oE 'STRING_SETS' | head -1 || true)
|
|
138
|
+
[ -n "$_OPT_DOMAINS" ] && DOMAINS="$DOMAINS STRING_SETS"
|
|
139
|
+
fi
|
|
135
140
|
;;
|
|
136
141
|
esac
|
|
137
142
|
case "$f" in
|
|
@@ -149,10 +154,15 @@ grep -lE "asyncio\.shield|asyncio\.wait_for|Task\.cancel" {CHANGED_FILES} 2>/dev
|
|
|
149
154
|
grep -lE "^\w+ = (\{\}|\[\]|set\(\)|Lock\(\)|defaultdict|Counter)" {CHANGED_FILES} 2>/dev/null | grep -qE '\.py$' && DOMAINS="$DOMAINS STATE"
|
|
150
155
|
|
|
151
156
|
# Check for capacity constants in worker/infra/browser Python files
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
# CAPACITY is a scraping-pack domain — opt-in via forge.yaml → quality_gate.optional_domains
|
|
158
|
+
# <!-- Updated: forge#1349 — not enabled by default; scraping-product-specific -->
|
|
159
|
+
_OPT_CAPACITY=$(grep -A5 'optional_domains:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null | grep -oE 'CAPACITY' | head -1 || true)
|
|
160
|
+
if [ -n "$_OPT_CAPACITY" ]; then
|
|
161
|
+
while IFS= read -r f; do
|
|
162
|
+
[ -z "$f" ] && continue
|
|
163
|
+
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
|
|
164
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$' | grep -E 'services/worker/|infra/|browser/')
|
|
165
|
+
fi
|
|
156
166
|
|
|
157
167
|
# Check for env var usage in changed files
|
|
158
168
|
grep -lE "os\.getenv|process\.env" {CHANGED_FILES} 2>/dev/null && DOMAINS="$DOMAINS DEPLOY"
|
|
@@ -266,14 +276,26 @@ done
|
|
|
266
276
|
### 2B: Auth conventions (Python routers, TypeScript route handlers)
|
|
267
277
|
|
|
268
278
|
For new/modified endpoints:
|
|
269
|
-
1. Which auth dependency is used
|
|
270
|
-
2. Does the
|
|
271
|
-
|
|
279
|
+
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.
|
|
280
|
+
2. Does the endpoint check resource ownership (`user_id` or equivalent) before returning data?
|
|
281
|
+
|
|
282
|
+
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
283
|
|
|
273
284
|
```bash
|
|
285
|
+
# Read configured auth dependency names from forge.yaml (if present)
|
|
286
|
+
AUTH_DEPS=""
|
|
287
|
+
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
288
|
+
AUTH_DEPS=$(grep -A5 'domains:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
289
|
+
| grep -A3 'auth:' | grep -oE '[A-Za-z_]+User|[A-Za-z_]+Auth|get_[a-z_]+' | sort -u | tr '\n' '|' | sed 's/|$//')
|
|
290
|
+
fi
|
|
291
|
+
|
|
274
292
|
while IFS= read -r f; do
|
|
275
293
|
[ -z "$f" ] && continue
|
|
276
|
-
|
|
294
|
+
if [ -n "$AUTH_DEPS" ]; then
|
|
295
|
+
grep -nE "($AUTH_DEPS)|Depends\(get_" "$f" 2>/dev/null
|
|
296
|
+
else
|
|
297
|
+
grep -nE "Depends\(get_" "$f" 2>/dev/null
|
|
298
|
+
fi
|
|
277
299
|
grep -nE "@router\.(get|post|put|delete)" "$f" 2>/dev/null
|
|
278
300
|
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E 'router|route')
|
|
279
301
|
```
|
|
@@ -286,8 +308,13 @@ if [ -n "$NEW_ENVS" ]; then
|
|
|
286
308
|
for var in $NEW_ENVS; do
|
|
287
309
|
# Check if it exists in .env.example
|
|
288
310
|
grep -q "$var" .env.example 2>/dev/null || echo "DEPLOY: $var not in .env.example"
|
|
289
|
-
# Check
|
|
290
|
-
|
|
311
|
+
# Check decrypt-secrets.sh ENV_MAPPING only when the SOPS mapping script exists.
|
|
312
|
+
# Emitting a finding for a file that doesn't exist in the repo produces false BLOCKINGs
|
|
313
|
+
# on non-SOPS repos. Use verify-sops-chain.sh for the full SOPS chain check instead.
|
|
314
|
+
# <!-- Updated: forge#1349 — gate on file presence -->
|
|
315
|
+
if [ -f "{WORKTREE_PATH}/scripts/decrypt-secrets.sh" ]; then
|
|
316
|
+
grep -q "$var" "{WORKTREE_PATH}/scripts/decrypt-secrets.sh" 2>/dev/null || echo "DEPLOY: $var not in decrypt-secrets.sh ENV_MAPPING"
|
|
317
|
+
fi
|
|
291
318
|
done
|
|
292
319
|
fi
|
|
293
320
|
```
|
|
@@ -310,27 +337,23 @@ while IFS= read -r f; do
|
|
|
310
337
|
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep '\.sql$')
|
|
311
338
|
|
|
312
339
|
# Migration prefix collision scan (Deploy Gate — HIGH)
|
|
313
|
-
# Runs whenever any SQL file changed. Scans the
|
|
314
|
-
# numeric prefixes
|
|
315
|
-
#
|
|
340
|
+
# Runs whenever any SQL file changed. Scans the configured migrations directory for
|
|
341
|
+
# duplicate numeric prefixes. Duplicate prefixes can cause ordering conflicts during deploy.
|
|
342
|
+
# <!-- Updated: forge#1349 — removed stale validate-migration-order.sh reference (script does not exist) -->
|
|
316
343
|
MIGRATIONS_DIR="{WORKTREE_PATH}/infra/migrations"
|
|
344
|
+
# Support configured migration directory from forge.yaml (if set)
|
|
345
|
+
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
346
|
+
_CONFIGURED_MIGRATIONS=$(grep -E '^\s*migrations_dir:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
347
|
+
| head -1 | sed 's/.*migrations_dir:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
348
|
+
[ -n "$_CONFIGURED_MIGRATIONS" ] && MIGRATIONS_DIR="{WORKTREE_PATH}/${_CONFIGURED_MIGRATIONS}"
|
|
349
|
+
fi
|
|
317
350
|
if [ -d "$MIGRATIONS_DIR" ]; then
|
|
318
|
-
# Extract grandfathered prefixes from validate-migration-order.sh if it exists
|
|
319
|
-
GRANDFATHER_SCRIPT="{WORKTREE_PATH}/scripts/validate-migration-order.sh"
|
|
320
|
-
GRANDFATHERED=""
|
|
321
|
-
if [ -f "$GRANDFATHER_SCRIPT" ]; then
|
|
322
|
-
GRANDFATHERED=$(grep -oE '[0-9]{4}' "$GRANDFATHER_SCRIPT" | sort -u | tr '\n' ' ')
|
|
323
|
-
fi
|
|
324
351
|
# Find duplicate numeric prefixes across all migration files
|
|
325
352
|
DUPLICATE_PREFIXES=$(ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null \
|
|
326
353
|
| sed 's|.*/\([0-9]*\)_.*|\1|' \
|
|
327
354
|
| sort | uniq -d)
|
|
328
355
|
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
|
|
356
|
+
echo "DB-COLLISION | HIGH | $MIGRATIONS_DIR | Duplicate migration prefix $prefix — ordering conflict will occur during deploy. Classify as CRITICAL BLOCKER."
|
|
334
357
|
done
|
|
335
358
|
fi
|
|
336
359
|
```
|
|
@@ -393,10 +416,29 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.tsx?$' | grep -v 'route
|
|
|
393
416
|
|
|
394
417
|
### 2F: Frontend proxy wiring (client-side fetch calls)
|
|
395
418
|
|
|
419
|
+
The direct-backend path prefix to flag is read from `forge.yaml → review.layout.direct_api_prefix`.
|
|
420
|
+
If absent, defaults to `/api/v1/` (backward compatible). This prevents false findings on projects
|
|
421
|
+
that use a different API path scheme. <!-- Updated: forge#1349 — gate on forge.yaml, not hardcoded prefix -->
|
|
422
|
+
|
|
396
423
|
```bash
|
|
424
|
+
# Read configured direct API prefix from forge.yaml, fall back to /api/v1/
|
|
425
|
+
DIRECT_API_PREFIX="/api/v1/"
|
|
426
|
+
if [ -f "{WORKTREE_PATH}/forge.yaml" ]; then
|
|
427
|
+
_CONFIGURED_PREFIX=$(grep -E '^\s*direct_api_prefix:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
428
|
+
| head -1 | sed 's/.*direct_api_prefix:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
429
|
+
# Also check under review.layout section
|
|
430
|
+
if [ -z "$_CONFIGURED_PREFIX" ]; then
|
|
431
|
+
_CONFIGURED_PREFIX=$(grep -A 10 'layout:' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null \
|
|
432
|
+
| grep -E '^\s*direct_api_prefix:' \
|
|
433
|
+
| head -1 | sed 's/.*direct_api_prefix:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
434
|
+
fi
|
|
435
|
+
[ -n "$_CONFIGURED_PREFIX" ] && DIRECT_API_PREFIX="$_CONFIGURED_PREFIX"
|
|
436
|
+
fi
|
|
437
|
+
ESCAPED_PREFIX=$(printf '%s' "$DIRECT_API_PREFIX" | sed 's|/|\\/|g')
|
|
438
|
+
|
|
397
439
|
while IFS= read -r f; do
|
|
398
440
|
[ -z "$f" ] && continue
|
|
399
|
-
grep -nE
|
|
441
|
+
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
442
|
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.(tsx?|jsx?)$' | grep -v 'route\.ts$')
|
|
401
443
|
```
|
|
402
444
|
|
|
@@ -578,9 +620,11 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$')
|
|
|
578
620
|
|
|
579
621
|
---
|
|
580
622
|
|
|
581
|
-
### 2J: String literal consistency (anti-detection/scraping code)
|
|
623
|
+
### 2J: String literal consistency (anti-detection/scraping code) *(Scraping Pack — opt-in)*
|
|
624
|
+
|
|
625
|
+
> **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 -->
|
|
582
626
|
|
|
583
|
-
**Triggered when**: changed files include Python files in `anti_detection/`, `consumers/`, `browser/`, or `shared/detection/` directories.
|
|
627
|
+
**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
628
|
|
|
585
629
|
**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
630
|
|
|
@@ -670,9 +714,11 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.py$' | grep -E 'anti_de
|
|
|
670
714
|
|
|
671
715
|
---
|
|
672
716
|
|
|
673
|
-
### 2K: Capacity constant validation (worker/infra code)
|
|
717
|
+
### 2K: Capacity constant validation (worker/infra code) *(Scraping Pack — opt-in)*
|
|
718
|
+
|
|
719
|
+
> **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
720
|
|
|
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`.
|
|
721
|
+
**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
722
|
|
|
677
723
|
**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
724
|
|
|
@@ -353,7 +353,7 @@ For each hit: read the fallback value.
|
|
|
353
353
|
- Is `admin`, `password`, `changeme`, `secret`, `test`, or similar weak string: **CONFIRMED HIGH** — default credential.
|
|
354
354
|
- Is empty string (`${VAR:-}` or `${VAR:-""}`): **CONFIRMED HIGH** if field controls authentication.
|
|
355
355
|
|
|
356
|
-
**Required cross-checks when a credential fallback is found**: (1) Is the env var required in `app/env_validation.py`? (2) Is it in `scripts/decrypt-secrets.sh` ENV_MAPPING? (3) Is it marked required in `.env.example`? If ANY missing: the insecure fallback may be active in some environments.
|
|
356
|
+
**Required cross-checks when a credential fallback is found**: (1) Is the env var required in `app/env_validation.py`? (2) Is it in `scripts/decrypt-secrets.sh` ENV_MAPPING (if configured in your SOPS deploy chain — skip if absent)? (3) Is it marked required in `.env.example`? If ANY missing: the insecure fallback may be active in some environments.
|
|
357
357
|
**Confidence**: `CONFIRMED` — fallback value is objectively verifiable.
|
|
358
358
|
**Severity**: HIGH — insecure fallback credential is equivalent to a hardcoded credential for any environment missing the env var.
|
|
359
359
|
**Evidence**: Config files commonly use `${VAR:-placeholder}` patterns during development, where the placeholder is a sample credential or hash. If the env var is absent in any environment (staging, non-production, first deploy before secrets are injected), the placeholder becomes the active credential. Also covered by INFRA agent item 13 (deployment context + ENV_MAPPING cross-check); this item extends coverage to PRs not classified as INFRA domain.
|
|
@@ -801,14 +801,14 @@ CRITICAL: This is a billing system. Double-spend = revenue loss.
|
|
|
801
801
|
```
|
|
802
802
|
If `WHERE reserved_by IS NULL` appears in a SELECT that is followed by a separate UPDATE (not in the same atomic statement), this is a CONFIRMED HIGH finding — two concurrent sessions can both pass the read check before either writes.
|
|
803
803
|
|
|
804
|
-
8. **Cross-service flag staleness**: When a discount or pricing flag (
|
|
804
|
+
8. **Cross-service flag staleness**: When a discount or pricing flag (e.g. `has_active_subscription`, a `discount_type` field) is set by the API layer at job submission and read by the Worker layer at billing time, verify the flag is re-validated at debit time — not trusted from the queued job payload.
|
|
805
805
|
|
|
806
806
|
Search for discount flags passed through Redis/job payloads:
|
|
807
807
|
```bash
|
|
808
|
-
grep -rn "
|
|
808
|
+
grep -rn "discount.*flag\|flag.*discount\|subscription.*flag\|plan_type\|discount_type" services/api/ services/worker/ | head -20
|
|
809
809
|
# If the flag flows through a job payload and is not re-validated at billing: CONFIRMED HIGH
|
|
810
810
|
```
|
|
811
|
-
A race window exists when a flag is checked at submission but consumed at billing: the underlying condition (e.g.,
|
|
811
|
+
A race window exists when a flag is checked at submission but consumed at billing: the underlying condition (e.g., subscription status, entitlement) may have changed between the two operations. The fix must re-validate the flag atomically at the point of debit, not rely on a stale value from the job payload.
|
|
812
812
|
|
|
813
813
|
## Safe Patterns in This Codebase
|
|
814
814
|
```bash
|
|
@@ -1317,8 +1317,8 @@ You are auditing PR #[PR_NUMBER] for database changes in [PROJECT_NAME].
|
|
|
1317
1317
|
a. List ALL `*.sql` files in `infra/migrations/` on the PR's target branch (use `git ls-tree` or `ls`)
|
|
1318
1318
|
b. Extract the 4-digit numeric prefix from each filename (the leading digits before the first `_`)
|
|
1319
1319
|
c. Identify any prefix that appears more than once
|
|
1320
|
-
d.
|
|
1321
|
-
e. **DEPLOY GATE — CRITICAL**: If ANY non-
|
|
1320
|
+
d. Identify if the project maintains a grandfathered-duplicates allowlist (e.g., a config file or comment block listing known-safe legacy duplicate prefixes). If one exists, cross-reference against it. <!-- Updated: forge#1349 — removed stale validate-migration-order.sh reference (script does not exist in ForgeDock) -->
|
|
1321
|
+
e. **DEPLOY GATE — CRITICAL**: If ANY non-allowlisted duplicate prefix exists → flag as **CRITICAL BLOCKER** and reject the PR. **Do NOT apply migration runner reasoning here.** Deploy gates that enforce migration ordering hard-fail on any non-allowed duplicate regardless of whether the migration runner executes files correctly. The runner may handle duplicate filenames fine; the deploy script does not. A PR that passes this reasoning trap ("unique filenames, so the runner is safe") will still halt production deploy. The only safe classification is CRITICAL BLOCKER.
|
|
1322
1322
|
f. Additionally: if the PR adds new migration files, verify their prefixes don't collide with existing files in the directory
|
|
1323
1323
|
9. **FK and CHECK constraints**: New tables should have appropriate foreign keys and CHECK constraints. Missing FK allows orphaned rows; missing CHECK allows invalid enum values.
|
|
1324
1324
|
10. **asyncpg gotchas**:
|
|
@@ -1588,7 +1588,7 @@ If no infra context is configured above, derive the deployment model from the ch
|
|
|
1588
1588
|
|
|
1589
1589
|
**Cross-check when a credential fallback is found:**
|
|
1590
1590
|
1. Is the corresponding env var required (not optional) in `app/env_validation.py` (or equivalent startup validation)?
|
|
1591
|
-
2. Is the env var in `scripts/decrypt-secrets.sh` ENV_MAPPING (delivered from SOPS to `.env.production`)?
|
|
1591
|
+
2. Is the env var in `scripts/decrypt-secrets.sh` ENV_MAPPING (if configured in your SOPS deploy chain — skip if absent; delivered from SOPS to `.env.production`)?
|
|
1592
1592
|
3. Is the env var documented with a "REQUIRED" note in `.env.example`?
|
|
1593
1593
|
|
|
1594
1594
|
If ANY of these three are missing: the env var may legitimately be absent in some environments, making the insecure fallback active. Flag as **CONFIRMED HIGH** with all three cross-check results.
|
package/commands/review-pr.md
CHANGED
|
@@ -147,7 +147,7 @@ echo "=== DOMAINS ==="
|
|
|
147
147
|
echo "$DIFF" | grep -cE "SessionUser|CurrentUser|get_current_user|jwt|oauth|login|logout|Depends\(get_|x.forwarded.for|x_forwarded_for|forwarded_for|rate.limit.*ip|ip.*rate.limit|algorithm.*HS256|algorithm.*RS256|NEXTAUTH_SECRET|JWT_SECRET|admin.proxy" && echo "AUTH" || true
|
|
148
148
|
echo "$DIFF" | grep -cE "credit|balance|debit|reconcil|tier_cost|pricing|charge|refund|stripe|subscription" && echo "BILLING" || true
|
|
149
149
|
echo "$DIFF" | grep -cE "scrape|tier.*escalat|proxy|anti_bot|stealth|playwright|playbook" && echo "SCRAPING" || true
|
|
150
|
-
echo "$DIFF" | grep -cE "FOR UPDATE|atomic|transaction|pipeline|MULTI|distributed_lock|acquire_lock|reserved_by|promo.*claim|voucher.*redeem
|
|
150
|
+
echo "$DIFF" | grep -cE "FOR UPDATE|atomic|transaction|pipeline|MULTI|distributed_lock|acquire_lock|reserved_by|promo.*claim|voucher.*redeem" && echo "CONCURRENCY" || true
|
|
151
151
|
echo "$FILES" | grep -cE "migration|\.sql$" && echo "DATABASE" || true
|
|
152
152
|
echo "$DIFF" | grep -cE "create_async_engine|AsyncSession|connect_args|pool_size|prepared_statement|engine_from_config|sessionmaker" && echo "DB_CONFIG" || true
|
|
153
153
|
echo "$FILES" | grep -cE "router|routes" && echo "API_DESIGN" || true
|
|
@@ -397,7 +397,7 @@ For each changed file, identify its **activation path** — how does execution r
|
|
|
397
397
|
|
|
398
398
|
Map each changed file to its activation requirements.
|
|
399
399
|
|
|
400
|
-
**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)
|
|
400
|
+
**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) are the ForgeDock install defaults:
|
|
401
401
|
|
|
402
402
|
| `forge.yaml` key | Default | Used in table as |
|
|
403
403
|
|-----------------|---------|-----------------|
|
|
@@ -461,6 +461,23 @@ if [ "$BASH_AVAILABLE" = "true" ]; then
|
|
|
461
461
|
# Output is structured: "BLOCKING: ...", "WARNING: ...", "OK: ..." per line.
|
|
462
462
|
|
|
463
463
|
# 1. Route/router/middleware/shared-module/component registration
|
|
464
|
+
# Export forge.yaml layout overrides so verify-route-registration.sh uses project-configured
|
|
465
|
+
# paths instead of project defaults. The script supports these env vars (lines 36-44 of
|
|
466
|
+
# verify-route-registration.sh) but requires the caller to set them. <!-- Added: forge#1349 -->
|
|
467
|
+
if [ -f "$REPO_ROOT/forge.yaml" ]; then
|
|
468
|
+
_PAGES_ROOT=$(grep -A10 'layout:' "$REPO_ROOT/forge.yaml" 2>/dev/null \
|
|
469
|
+
| grep -E '^\s*pages:' | head -1 | sed 's/.*pages:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
470
|
+
_API_ROUTERS=$(grep -A10 'layout:' "$REPO_ROOT/forge.yaml" 2>/dev/null \
|
|
471
|
+
| grep -E '^\s*api_routers_dir:' | head -1 | sed 's/.*api_routers_dir:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
472
|
+
_API_MAIN=$(grep -A10 'layout:' "$REPO_ROOT/forge.yaml" 2>/dev/null \
|
|
473
|
+
| grep -E '^\s*api_main:' | head -1 | sed 's/.*api_main:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
474
|
+
_API_MIDDLEWARE=$(grep -A10 'layout:' "$REPO_ROOT/forge.yaml" 2>/dev/null \
|
|
475
|
+
| grep -E '^\s*api_middleware_dir:' | head -1 | sed 's/.*api_middleware_dir:[[:space:]]*//' | tr -d '"' | tr -d "'" | xargs)
|
|
476
|
+
[ -n "$_PAGES_ROOT" ] && export FORGE_PAGES_ROOT="$_PAGES_ROOT"
|
|
477
|
+
[ -n "$_API_ROUTERS" ] && export FORGE_API_ROUTERS_DIR="$_API_ROUTERS"
|
|
478
|
+
[ -n "$_API_MAIN" ] && export FORGE_API_MAIN="$_API_MAIN"
|
|
479
|
+
[ -n "$_API_MIDDLEWARE" ] && export FORGE_API_MIDDLEWARE_DIR="$_API_MIDDLEWARE"
|
|
480
|
+
fi
|
|
464
481
|
echo "=== Running: verify-route-registration.sh ==="
|
|
465
482
|
bash "$FORGE_HOME/scripts/verify-route-registration.sh" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
466
483
|
|
|
@@ -612,7 +629,7 @@ Format: `INTEG-N|CONFIRMED|HIGH|file:line|Changed code may be unreachable: {reas
|
|
|
612
629
|
DIFF=$(gh pr diff $ARGUMENTS)
|
|
613
630
|
FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
614
631
|
echo "=== RISK SIGNALS ==="
|
|
615
|
-
echo "$DIFF" | grep -cE "subprocess|exec|eval|system\(|popen|heredoc
|
|
632
|
+
echo "$DIFF" | grep -cE "subprocess|exec|eval|system\(|popen|heredoc" && echo " UNTRUSTED_INPUT_PROCESSING" || true
|
|
616
633
|
echo "$DIFF" | grep -cE "\.sh$|bash|shell|cron" && echo " SHELL_SCRIPT" || true
|
|
617
634
|
echo "$DIFF" | grep -cE "credit|balance|debit|charge|refund|stripe" && echo " FINANCIAL" || true
|
|
618
635
|
echo "$DIFF" | grep -cE "SessionUser|CurrentUser|jwt|oauth|password|token|secret|x.forwarded.for|x_forwarded_for|forwarded_for|rate.limit.*ip|ip.*rate.limit|algorithm.*HS256|algorithm.*RS256|NEXTAUTH_SECRET|JWT_SECRET|admin.proxy" && echo " AUTH_SENSITIVE" || true
|
|
@@ -1093,7 +1110,7 @@ else
|
|
|
1093
1110
|
fi
|
|
1094
1111
|
```
|
|
1095
1112
|
|
|
1096
|
-
**Important**: Phase 8 ONLY merges the PR. It does NOT close the issue, update labels, or clean up worktrees. When invoked via `/work-on`, those responsibilities belong to `work-on/close.md`
|
|
1113
|
+
**Important**: Phase 8 ONLY merges the PR. It does NOT close the issue, update labels, or clean up worktrees. When invoked via `/work-on`, those responsibilities belong to `work-on/close.md` (work-on.md Phase 6 — triggered when PR is merged and issue is still open). Doing them here would cause the issue to be closed with `workflow:merged` before Phase 6 runs, skipping the close phase entirely.
|
|
1097
1114
|
|
|
1098
1115
|
### 8B: Post-Merge Review Finding Demilestoning (Milestone PRs Only)
|
|
1099
1116
|
|
|
@@ -53,7 +53,17 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
53
53
|
--jq '.[] | select(.body | contains("FORGE:CONTEXT")) | .body'
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
**Resume check**:
|
|
56
|
+
**Resume check**:
|
|
57
|
+
- If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → implementation is done — EXIT and return `IMPLEMENT_RESULT: status: ALREADY_DONE` to caller.
|
|
58
|
+
- If `<!-- FORGE:BUILDER -->` exists BUT `<!-- FORGE:BUILDER:COMPLETE -->` is ABSENT → implementation was interrupted after the comment was posted but before the commit (validate.md V5). Delete the partial comment and restart from Phase I2:
|
|
59
|
+
```bash
|
|
60
|
+
PARTIAL_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
61
|
+
--jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
|
|
62
|
+
if [ -n "$PARTIAL_ID" ]; then
|
|
63
|
+
gh api repos/{GH_REPO}/issues/comments/$PARTIAL_ID -X DELETE
|
|
64
|
+
echo "Deleted partial FORGE:BUILDER comment (no FORGE:BUILDER:COMPLETE) — restarting implementation"
|
|
65
|
+
fi
|
|
66
|
+
```
|
|
57
67
|
|
|
58
68
|
**Primary guide**: If `<!-- FORGE:ARCHITECT -->` is present, it is the **primary implementation input**. Follow its ordered implementation list exactly — it defines which files change, in what order, and what consistency checks must pass. The investigation report and contract are secondary context.
|
|
59
69
|
|
|
@@ -225,6 +235,8 @@ gh issue edit {NUMBER} {GH_FLAG} --body "{UPDATED_BODY}"
|
|
|
225
235
|
|
|
226
236
|
## Phase I6: Post FORGE:BUILDER Comment
|
|
227
237
|
|
|
238
|
+
Post the implementation summary comment. **Do NOT include `<!-- FORGE:BUILDER:COMPLETE -->` here** — that marker is posted by `validate.md` Phase V5 after the commit succeeds. A crash between I6 and V5 would otherwise leave a completion-marked comment with no commit on the branch.
|
|
239
|
+
|
|
228
240
|
```bash
|
|
229
241
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILDER -->
|
|
230
242
|
## Implementation Complete
|
|
@@ -247,11 +259,11 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:BUILDER -->
|
|
|
247
259
|
- [ ] {Test scenario 2} [type:unit]
|
|
248
260
|
- [ ] {Test scenario 3} [type:e2e]
|
|
249
261
|
|
|
250
|
-
> **Test-type annotation** (optional): Append `[type:api]`, `[type:unit]`, `[type:e2e]`, or `[type:manual]` to each checklist item. The test gate reads this annotation directly and skips regex inference. Omit it to rely on regex classification fallback.
|
|
251
|
-
|
|
252
|
-
<!-- FORGE:BUILDER:COMPLETE -->"
|
|
262
|
+
> **Test-type annotation** (optional): Append `[type:api]`, `[type:unit]`, `[type:e2e]`, or `[type:manual]` to each checklist item. The test gate reads this annotation directly and skips regex inference. Omit it to rely on regex classification fallback."
|
|
253
263
|
```
|
|
254
264
|
|
|
265
|
+
**Note**: `<!-- FORGE:BUILDER:COMPLETE -->` is intentionally absent from this comment. It is appended to this comment by `validate.md` Phase V5 after the git commit completes — see `validate.md § Phase V5`. This ordering ensures the completion marker only exists when a real commit is present on the branch.
|
|
266
|
+
|
|
255
267
|
---
|
|
256
268
|
|
|
257
269
|
## Output
|
|
@@ -90,7 +90,12 @@ Needs human review before proceeding to commit.
|
|
|
90
90
|
|
|
91
91
|
## Phase V2: Format and Verify
|
|
92
92
|
|
|
93
|
-
Run after quality gate passes. All tool commands are read from `forge.yaml → verification.commands`; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing
|
|
93
|
+
Run after quality gate passes. All tool commands are read from `forge.yaml → verification.commands`; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing.
|
|
94
|
+
|
|
95
|
+
**Track skipped checks** — initialize before any check runs:
|
|
96
|
+
```bash
|
|
97
|
+
SKIPPED_CHECKS=""
|
|
98
|
+
```
|
|
94
99
|
|
|
95
100
|
**Python**:
|
|
96
101
|
```bash
|
|
@@ -101,6 +106,7 @@ if [ -n "$PYTHON_FORMAT" ]; then
|
|
|
101
106
|
eval "$PYTHON_FORMAT" 2>&1
|
|
102
107
|
else
|
|
103
108
|
echo "SKIPPED — python.format not configured in verification.commands"
|
|
109
|
+
SKIPPED_CHECKS="${SKIPPED_CHECKS:+$SKIPPED_CHECKS, }python.format"
|
|
104
110
|
fi
|
|
105
111
|
|
|
106
112
|
# Compile check always runs for Python files (no config needed — catches syntax errors)
|
|
@@ -120,6 +126,7 @@ if [ -n "$TS_FORMAT" ]; then
|
|
|
120
126
|
eval "$TS_FORMAT" 2>&1
|
|
121
127
|
else
|
|
122
128
|
echo "SKIPPED — typescript.format not configured in verification.commands"
|
|
129
|
+
SKIPPED_CHECKS="${SKIPPED_CHECKS:+$SKIPPED_CHECKS, }typescript.format"
|
|
123
130
|
fi
|
|
124
131
|
|
|
125
132
|
if [ -n "$TS_TYPECHECK" ]; then
|
|
@@ -130,6 +137,7 @@ elif [ -n "$TS_BUILD" ]; then
|
|
|
130
137
|
TS_EXIT=$?
|
|
131
138
|
else
|
|
132
139
|
echo "SKIPPED — typescript.typecheck and typescript.build not configured in verification.commands"
|
|
140
|
+
SKIPPED_CHECKS="${SKIPPED_CHECKS:+$SKIPPED_CHECKS, }typescript.typecheck/build"
|
|
133
141
|
TS_EXIT=0
|
|
134
142
|
fi
|
|
135
143
|
```
|
|
@@ -287,6 +295,32 @@ If `origin/{PR_BASE}` does not exist yet (new branch), skip this check — no co
|
|
|
287
295
|
git ls-remote --exit-code origin {PR_BASE} >/dev/null 2>&1 || echo "PR_BASE not on origin — skipping ancestry audit"
|
|
288
296
|
```
|
|
289
297
|
|
|
298
|
+
### V5 Post-Commit: Mark Build Complete (MANDATORY)
|
|
299
|
+
|
|
300
|
+
After the ancestry audit passes (or is skipped), append `<!-- FORGE:BUILDER:COMPLETE -->` to the existing FORGE:BUILDER comment. This is the **only** place this marker is written — it signals that a real commit exists on the branch and the build is safe to resume-skip. <!-- Added: forge#1305 -->
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Find the FORGE:BUILDER comment posted by implement.md Phase I6
|
|
304
|
+
BUILDER_COMMENT_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
305
|
+
--jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
|
|
306
|
+
|
|
307
|
+
if [ -n "$BUILDER_COMMENT_ID" ]; then
|
|
308
|
+
# Fetch current body and append the completion marker
|
|
309
|
+
CURRENT_BODY=$(gh api repos/{GH_REPO}/issues/comments/$BUILDER_COMMENT_ID --jq '.body')
|
|
310
|
+
UPDATED_BODY="${CURRENT_BODY}
|
|
311
|
+
|
|
312
|
+
<!-- FORGE:BUILDER:COMPLETE -->"
|
|
313
|
+
gh api repos/{GH_REPO}/issues/comments/$BUILDER_COMMENT_ID \
|
|
314
|
+
-X PATCH \
|
|
315
|
+
--field body="$UPDATED_BODY"
|
|
316
|
+
echo "FORGE:BUILDER:COMPLETE appended to comment $BUILDER_COMMENT_ID"
|
|
317
|
+
else
|
|
318
|
+
echo "WARNING: FORGE:BUILDER comment not found or already marked complete — skipping BUILDER:COMPLETE append"
|
|
319
|
+
fi
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Why here and not in implement.md**: The commit (`git commit`) runs in this phase (V5). Appending `:COMPLETE` after the commit ensures that a session crash between implement.md I6 (comment posted) and this step (commit) leaves a partial BUILDER comment without `:COMPLETE`. The next resume will detect the partial comment, delete it, and restart the build. See `implement.md § Phase I1 resume check`.
|
|
323
|
+
|
|
290
324
|
---
|
|
291
325
|
|
|
292
326
|
## Output
|
|
@@ -302,6 +336,9 @@ VALIDATE_RESULT:
|
|
|
302
336
|
deploy_completeness_fixes: [{VAR_NAME: location_added}, ...]
|
|
303
337
|
commits_added: [{SHA}, ...] # from V5 if any
|
|
304
338
|
blocker: {description if gate_passed=false}
|
|
339
|
+
verification_skipped: [] # empty when all configured checks ran; list of skipped check names otherwise
|
|
340
|
+
# e.g. ["python.format", "typescript.typecheck/build"]
|
|
341
|
+
# populated from SKIPPED_CHECKS in Phase V2
|
|
305
342
|
```
|
|
306
343
|
|
|
307
344
|
---
|
|
@@ -9,8 +9,8 @@ argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BA
|
|
|
9
9
|
|
|
10
10
|
**Input**: $ARGUMENTS
|
|
11
11
|
|
|
12
|
-
**Invoked by**: `work-on.md`
|
|
13
|
-
**Output**: Create worktree, post contract, sequence build subcommands, return result to
|
|
12
|
+
**Invoked by**: `work-on.md` Phase 3 — entered when the issue carries label `workflow:ready-to-build` or `workflow:building` (see Universal Phase Dispatcher in work-on.md).
|
|
13
|
+
**Output**: Create worktree, post contract, sequence build subcommands, return result to work-on.md.
|
|
14
14
|
|
|
15
15
|
**Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
|
|
16
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
@@ -46,7 +46,16 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
**Resume check**:
|
|
49
|
-
- If `<!-- FORGE:BUILDER -->` comment
|
|
49
|
+
- If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → build already complete. Return `BUILD_RESULT: status: ALREADY_DONE` to router.
|
|
50
|
+
- If `<!-- FORGE:BUILDER -->` exists BUT `<!-- FORGE:BUILDER:COMPLETE -->` is ABSENT → build was interrupted after the comment was posted but before the commit (validate.md V5). Delete the partial comment and restart from Phase B2 (contract): <!-- Added: forge#1305 -->
|
|
51
|
+
```bash
|
|
52
|
+
PARTIAL_ID=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
53
|
+
--jq '[.[] | select(.body | contains("FORGE:BUILDER") and (contains("FORGE:BUILDER:COMPLETE") | not))] | last | .id // ""')
|
|
54
|
+
if [ -n "$PARTIAL_ID" ]; then
|
|
55
|
+
gh api repos/{GH_REPO}/issues/comments/$PARTIAL_ID -X DELETE
|
|
56
|
+
echo "Deleted partial FORGE:BUILDER comment (no FORGE:BUILDER:COMPLETE) — restarting build from Phase B2"
|
|
57
|
+
fi
|
|
58
|
+
```
|
|
50
59
|
- If no `<!-- FORGE:INVESTIGATOR -->` comment with `<!-- INVESTIGATION:COMPLETE -->` → EXIT with `BUILD_RESULT: status: BLOCKED`, blocker: "Investigation not complete — run investigate first".
|
|
51
60
|
|
|
52
61
|
Extract from investigation report:
|
|
@@ -82,6 +91,14 @@ Append `-{NUMBER}` to ensure uniqueness: e.g. `fix/work-on-build-landing-file-85
|
|
|
82
91
|
|
|
83
92
|
- Review-finding issue → parse `**Code branch**: \`{branch}\`` from issue body; branch from `origin/{branch}`
|
|
84
93
|
- **Milestone review-finding hybrid lane** (ONLY when Code branch matches `milestone/*`): This is a high-risk lane. The worktree will carry the full milestone history. The PR target is `staging` (or the base specified). **DANGER: Agents MUST NOT use `git merge` to resolve any conflicts in this lane.** Merge-based conflict resolution will pull the entire milestone commit tree onto staging, contaminating it with unapproved code. Use `git rebase` or `git cherry-pick` only. If conflicts cannot be resolved without a merge, post a comment on the issue, add `needs-human`, and STOP.
|
|
94
|
+
- **Missing ref fallback**: After parsing, verify the Code branch still exists on remote. If not, fall back to the lane default (`staging` for fast lane, `milestone/{slug}` for feature lane) and note the fallback:
|
|
95
|
+
```bash
|
|
96
|
+
SOURCE_BRANCH="{CODE_BRANCH_FROM_ISSUE_BODY}"
|
|
97
|
+
if ! git ls-remote --exit-code origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
|
|
98
|
+
echo "WARNING: Code branch '$SOURCE_BRANCH' not found on remote — falling back to lane default '$PR_BASE'"
|
|
99
|
+
SOURCE_BRANCH="$PR_BASE"
|
|
100
|
+
fi
|
|
101
|
+
```
|
|
85
102
|
- Feature lane (has milestone) → branch from `origin/{PR_BASE}`
|
|
86
103
|
- Fast lane (no milestone) → branch from `origin/staging`
|
|
87
104
|
|
|
@@ -275,13 +292,14 @@ BUILD_RESULT:
|
|
|
275
292
|
|
|
276
293
|
## Integration Point in work-on.md
|
|
277
294
|
|
|
278
|
-
This module runs
|
|
295
|
+
This module runs during **Phase 3** of the work-on.md pipeline (label: `workflow:ready-to-build` or `workflow:building`). The full sequence is defined by the Universal Phase Dispatcher in work-on.md:
|
|
279
296
|
|
|
280
297
|
```
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
298
|
+
Phase 3 (Build) → [THIS MODULE] worktree + contract + context + architect + implement + validate
|
|
299
|
+
→ posts FORGE:BUILDER comment, writes FORGE:CHECKPOINT next_phase=REVIEW
|
|
300
|
+
Phase 4 (PR) → work-on:review — push branch, create PR, set workflow:in-review
|
|
301
|
+
Phase 5 (Review) → work-on:review — invoke /review-pr --auto-merge
|
|
302
|
+
Phase 6 (Close) → work-on:close — trajectory + parent tracker + summary + worktree cleanup
|
|
285
303
|
```
|
|
286
304
|
|
|
287
|
-
|
|
305
|
+
After this module posts `FORGE:BUILDER` and returns, work-on.md's Universal continuation rule re-reads the issue labels. Since the label is not yet terminal (`workflow:merged` / `workflow:invalid` / `needs-human`), it proceeds immediately to Phase 4 (PR Creation) and then Phase 5 (Auto-Review).
|
|
@@ -179,7 +179,7 @@ ISSUE_STATE=$(gh issue view {NUMBER} {GH_FLAG} --json state --jq '.state')
|
|
|
179
179
|
|
|
180
180
|
**If `REMAINING_AFTER > 0`** (multi-phase: uncompleted phases remain):
|
|
181
181
|
```bash
|
|
182
|
-
# Post phase-complete marker —
|
|
182
|
+
# Post phase-complete marker — work-on.md's Universal continuation rule will re-read labels and continue to the next phase
|
|
183
183
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:PHASE:COMPLETE -->
|
|
184
184
|
Phase complete. PR #{PR_NUMBER} merged to \`{PR_BASE}\`. ${REMAINING_AFTER} phase item(s) remain — leaving issue open for next pipeline iteration."
|
|
185
185
|
|
|
@@ -577,7 +577,7 @@ CLOSE_RESULT:
|
|
|
577
577
|
parent_closed: {true|false}
|
|
578
578
|
```
|
|
579
579
|
|
|
580
|
-
Where `PHASE_COMPLETE` means the current phase was closed but uncompleted phases remain — the issue is left OPEN for
|
|
580
|
+
Where `PHASE_COMPLETE` means the current phase was closed but uncompleted phases remain — the issue is left OPEN for work-on.md to re-evaluate on the next invocation (via Universal continuation rule: re-read labels, check terminal state, continue to next phase).
|
|
581
581
|
|
|
582
582
|
---
|
|
583
583
|
|
|
@@ -81,6 +81,30 @@ Do NOT push this branch. Human review required to identify the source of the mer
|
|
|
81
81
|
fi
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
## Phase R1: Non-Empty Commit Guard (MANDATORY — run before push) <!-- Added: forge#1305 -->
|
|
85
|
+
|
|
86
|
+
Before pushing, verify the branch has at least one commit ahead of the PR base. This is the last-line defense against the phantom-commit hazard: a session that resumed from a partial FORGE:BUILDER comment (without `:COMPLETE`) would have skipped the commit step and could otherwise push an empty branch.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd {WORKTREE_PATH}
|
|
90
|
+
# Count commits on this branch that are not reachable from origin/{PR_BASE}
|
|
91
|
+
COMMIT_COUNT=$(git rev-list --count HEAD ^origin/{PR_BASE} 2>/dev/null || echo "0")
|
|
92
|
+
if [ "$COMMIT_COUNT" -eq 0 ]; then
|
|
93
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "## Push Blocked — No Commits Ahead of Base
|
|
94
|
+
|
|
95
|
+
Branch \`{BRANCH}\` has 0 commits ahead of \`origin/{PR_BASE}\`. Pushing this branch would create an empty PR.
|
|
96
|
+
|
|
97
|
+
**Likely cause**: Build was interrupted after the FORGE:BUILDER comment was posted (implement.md Phase I6) but before the commit was created (validate.md Phase V5). The branch was pushed with no implementation on it.
|
|
98
|
+
|
|
99
|
+
**Resolution**: Delete this branch, re-run \`/work-on {NUMBER}\` to restart the build phase. The partial FORGE:BUILDER comment (lacking \`FORGE:BUILDER:COMPLETE\`) will be detected and deleted, and the build will restart cleanly.
|
|
100
|
+
|
|
101
|
+
<!-- FORGE:PUSH_BLOCKED_EMPTY_BRANCH -->"
|
|
102
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
103
|
+
exit 1
|
|
104
|
+
fi
|
|
105
|
+
echo "Commit count ahead of origin/{PR_BASE}: $COMMIT_COUNT — OK to push"
|
|
106
|
+
```
|
|
107
|
+
|
|
84
108
|
## Phase R1: Push Branch
|
|
85
109
|
|
|
86
110
|
```bash
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: "[BENCHMARK] Monolithic work-on — single-prompt pipeline with no Skill sub-phase boundaries"
|
|
3
3
|
argument-hint: [issue number]
|
|
4
|
+
install: internal
|
|
4
5
|
---
|
|
5
6
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
7
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|