forgedock 1.16.2 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/engine/outcome-codes.mjs +114 -0
- package/bin/engine/phases.mjs +211 -100
- package/bin/engine/projector.mjs +48 -1
- package/bin/engine/runlog.mjs +20 -5
- package/bin/engine/telemetry-seam.mjs +62 -0
- package/bin/engine/telemetry-seam.test.mjs +60 -0
- package/bin/engine/telemetry.mjs +148 -0
- package/bin/engine/telemetry.test.mjs +120 -0
- package/bin/engine-cli.mjs +17 -1
- package/bin/engine.mjs +227 -48
- package/bin/forgedock.mjs +8 -2
- package/bin/hooks/interactive-engine.mjs +10 -0
- package/bin/hooks/pre-tool-use.mjs +220 -0
- package/bin/runner.mjs +70 -4
- package/bin/stats/aggregate.mjs +231 -0
- package/bin/stats/cache.mjs +40 -0
- package/bin/stats/github-source.mjs +211 -0
- package/bin/stats/index.mjs +154 -0
- package/bin/stats/local-source.mjs +79 -0
- package/bin/stats/package.json +6 -0
- package/bin/stats/render.mjs +82 -0
- package/bin/stats/stats.test.mjs +312 -0
- package/commands/cleanup.md +90 -2
- package/commands/deploy-info.md +8 -4
- package/commands/deploy-pr.md +64 -0
- package/commands/fix-ci.md +20 -6
- package/commands/milestone.md +34 -17
- package/commands/orchestrate/phase-1-resolve.md +4 -1
- package/commands/orchestrate/phase-4-execution.md +28 -6
- package/commands/orchestrate/phase-5-cleanup.md +18 -2
- package/commands/pipeline-health/phase-1-context.md +2 -0
- package/commands/pipeline-health/phase-2-metrics.md +12 -5
- package/commands/pipeline-status.md +21 -1
- package/commands/review-pr-agents/protocols.md +1 -0
- package/commands/review-pr-agents.md +10 -2
- package/commands/review-pr-staging.md +196 -496
- package/commands/review-pr.md +512 -3134
- package/commands/test-gate.md +25 -10
- package/commands/work-on/build/architect.md +13 -1
- package/commands/work-on/build/implement.md +28 -6
- package/commands/work-on/build/validate.md +5 -2
- package/commands/work-on/build.md +197 -145
- package/commands/work-on/close.md +66 -20
- package/commands/work-on/decompose.md +113 -34
- package/commands/work-on/investigate.md +15 -14
- package/commands/work-on/remediate.md +49 -19
- package/commands/work-on/review.md +7 -5
- package/commands/work-on.md +1 -2
- package/docs/CONFIG.md +76 -24
- package/docs/OUTCOME-CODES.md +53 -0
- package/docs/RELEASE-NOTES.md +24 -0
- package/docs/STATS.md +63 -0
- package/docs/TELEMETRY.md +65 -0
- package/docs/WORK-ON-RUNTIME.md +19 -0
- package/docs/site/command-reference.md +1 -1
- package/docs/spec/forge-protocol-v1.md +2 -2
- package/docs/spec/label-state-machine.md +1 -1
- package/package.json +3 -2
- package/packages/protocol/src/phases.js +5 -5
- package/scripts/builder-resume-match.test.sh +58 -0
- package/scripts/cap-findings.sh +110 -0
- package/scripts/cap-findings.test.sh +130 -0
- package/scripts/check-spec-bash.sh +5 -1
- package/scripts/check-spec-bash.test.sh +20 -0
- package/scripts/check-spec-markers.sh +2 -0
- package/scripts/classify-finding.sh +65 -16
- package/scripts/classify-finding.test.sh +43 -1
- package/scripts/classify-grep-exit.sh +20 -0
- package/scripts/classify-grep-exit.test.sh +46 -0
- package/scripts/close-parent-c3.test.sh +123 -0
- package/scripts/cost-report.sh +146 -0
- package/scripts/cost-report.test.sh +80 -0
- package/scripts/diff-size.sh +97 -3
- package/scripts/diff-size.test.sh +59 -2
- package/scripts/doctor-pipeline-state.sh +43 -0
- package/scripts/file-deferred-followups.sh +159 -0
- package/scripts/file-deferred-followups.test.sh +106 -0
- package/scripts/fixtures/cost-report/a-merged1.jsonl +6 -0
- package/scripts/fixtures/cost-report/a-merged2-remediated.jsonl +7 -0
- package/scripts/fixtures/cost-report/a-needs-human.jsonl +4 -0
- package/scripts/fixtures/cost-report/b-merged.jsonl +3 -0
- package/scripts/fixtures/cost-report/c-merged-attempt-usage.jsonl +8 -0
- package/scripts/fixtures/cost-report/hydrated-no-usage.jsonl +3 -0
- package/scripts/fixtures/cost-report/old-log-no-version.jsonl +3 -0
- package/scripts/fixtures/cost-report/unfinished-no-terminal.jsonl +2 -0
- package/scripts/fixtures/cost-report/unfinished-reopened.jsonl +5 -0
- package/scripts/forge-root.test.sh +18 -11
- package/scripts/lib/fence-bootstrap.sh +4 -3
- package/scripts/lib/pr-issue-number.sh +18 -0
- package/scripts/lib/review-template-resolve.sh +41 -0
- package/scripts/lib/telemetry.sh +168 -0
- package/scripts/publish-staging-sync.test.sh +48 -0
- package/scripts/remediate-liveness.sh +42 -6
- package/scripts/resolve-deploy-gate.sh +32 -6
- package/scripts/resolve-deploy-gate.test.sh +42 -1
- package/scripts/review-acceptance-gate.sh +16 -7
- package/scripts/review-acceptance-gate.test.sh +13 -0
- package/scripts/review-finalize.sh +1427 -0
- package/scripts/review-finalize.test.sh +833 -0
- package/scripts/review-pr-delta-wiring.test.sh +19 -72
- package/scripts/review-pr-domain-map.test.sh +23 -13
- package/scripts/review-pr-gate2.test.sh +24 -65
- package/scripts/review-pr-staging-finding-filter.test.sh +44 -0
- package/scripts/review-pr-staging-scope.test.sh +56 -80
- package/scripts/review-prepare.sh +553 -0
- package/scripts/review-prepare.test.sh +288 -0
- package/scripts/review-roster.sh +186 -0
- package/scripts/review-roster.test.sh +185 -0
- package/scripts/run-shell-tests.sh +3 -0
- package/scripts/telemetry.test.sh +106 -0
- package/scripts/trusted-comments.test.sh +9 -2
- package/scripts/wait-ci-green.sh +8 -1
- package/scripts/wait-ci-green.test.sh +3 -3
- package/templates/forge.yaml.minimal +9 -0
package/commands/test-gate.md
CHANGED
|
@@ -439,23 +439,38 @@ BASELINE_RESULTS=""
|
|
|
439
439
|
|
|
440
440
|
# Run the same tests against the base branch state to establish baseline
|
|
441
441
|
# If base-branch tests also fail, those are pre-existing failures — not batch-introduced
|
|
442
|
-
|
|
443
|
-
|
|
442
|
+
# Use a detached worktree at the base branch — never stash/checkout in the main checkout
|
|
443
|
+
BASE_WT=$(mktemp -d)
|
|
444
|
+
git -C "$REPO_PATH" worktree add --detach "$BASE_WT" "origin/${BASE_BRANCH}" 2>/dev/null \
|
|
445
|
+
|| { echo "WARNING: could not create baseline worktree — baseline unavailable (failures will count as batch-introduced)"; rmdir "$BASE_WT" 2>/dev/null; BASE_WT=""; }
|
|
446
|
+
# A fresh worktree has no installed deps or local env files: link the main checkout's (read-only use), at the repo
|
|
447
|
+
# root and in every cluster working_dir, so a baseline cluster fails only for code reasons, never for a missing install.
|
|
448
|
+
if [ -n "$BASE_WT" ]; then
|
|
449
|
+
{ echo .; yq '.verification.integration_tests[].working_dir // "."' "$CONFIG_FILE" 2>/dev/null; } | sort -u | while IFS= read -r _wd; do
|
|
450
|
+
for _dep in node_modules .venv venv .env .env.local .env.test; do
|
|
451
|
+
[ -e "${REPO_PATH}/${_wd}/${_dep}" ] && [ ! -e "${BASE_WT}/${_wd}/${_dep}" ] && [ -d "${BASE_WT}/${_wd}" ] \
|
|
452
|
+
&& ln -s "${REPO_PATH}/${_wd}/${_dep}" "${BASE_WT}/${_wd}/${_dep}"
|
|
453
|
+
done
|
|
454
|
+
done # one working_dir per line: a path with spaces is never word-split
|
|
455
|
+
fi
|
|
444
456
|
|
|
445
457
|
for i in $(seq 0 $((CLUSTER_COUNT - 1))); do
|
|
446
458
|
CLUSTER=$(yq ".verification.integration_tests[${i}].cluster" "$CONFIG_FILE")
|
|
447
459
|
CMD=$(yq ".verification.integration_tests[${i}].command" "$CONFIG_FILE")
|
|
448
460
|
WORKING_DIR=$(yq ".verification.integration_tests[${i}].working_dir // \".\"" "$CONFIG_FILE")
|
|
449
461
|
|
|
450
|
-
if
|
|
462
|
+
if [ -z "$BASE_WT" ]; then
|
|
463
|
+
# No baseline: never claim pre-existing. A bundle failure in this cluster counts as batch-introduced (fail closed).
|
|
464
|
+
BASELINE_RESULTS="${BASELINE_RESULTS}\n- ${CLUSTER}: UNAVAILABLE"
|
|
465
|
+
elif (cd "${BASE_WT}/${WORKING_DIR}" && eval "$CMD") 2>&1; then
|
|
451
466
|
BASELINE_RESULTS="${BASELINE_RESULTS}\n- ${CLUSTER}: PASS"
|
|
452
467
|
else
|
|
453
468
|
BASELINE_RESULTS="${BASELINE_RESULTS}\n- ${CLUSTER}: FAIL (pre-existing)"
|
|
454
469
|
fi
|
|
455
470
|
done
|
|
456
471
|
|
|
457
|
-
#
|
|
458
|
-
git
|
|
472
|
+
# Remove the baseline worktree (the main checkout was never touched)
|
|
473
|
+
[ -n "$BASE_WT" ] && git -C "$REPO_PATH" worktree remove --force "$BASE_WT" 2>/dev/null || true
|
|
459
474
|
|
|
460
475
|
echo "Baseline results:"
|
|
461
476
|
echo -e "$BASELINE_RESULTS"
|
|
@@ -475,10 +490,10 @@ while IFS= read -r result_line; do
|
|
|
475
490
|
if echo "$STATUS" | grep -q "^FAIL"; then
|
|
476
491
|
# Check if this cluster also failed in baseline
|
|
477
492
|
BASELINE_STATUS=$(echo -e "$BASELINE_RESULTS" | grep "^- ${CLUSTER}:" | grep -oP ':\s+\K.+' || echo "PASS")
|
|
478
|
-
if echo "$BASELINE_STATUS" | grep -q "^
|
|
479
|
-
#
|
|
493
|
+
if ! echo "$BASELINE_STATUS" | grep -q "^FAIL"; then
|
|
494
|
+
# Baseline PASS (or UNAVAILABLE: no proof it was pre-existing) but fails in the bundle — batch-introduced failure
|
|
480
495
|
BATCH_FAILURES="${BATCH_FAILURES}\n${CLUSTER}"
|
|
481
|
-
echo "BATCH-INTRODUCED FAILURE: cluster '${CLUSTER}' (baseline:
|
|
496
|
+
echo "BATCH-INTRODUCED FAILURE: cluster '${CLUSTER}' (baseline: ${BASELINE_STATUS} → bundle: FAIL)"
|
|
482
497
|
else
|
|
483
498
|
echo "PRE-EXISTING FAILURE: cluster '${CLUSTER}' (baseline: FAIL → bundle: FAIL — not this batch)"
|
|
484
499
|
fi
|
|
@@ -534,7 +549,7 @@ else
|
|
|
534
549
|
cat <<ISSUE_EOF > "$TESTGATE_FAIL_BODY_FILE"
|
|
535
550
|
## Problem
|
|
536
551
|
|
|
537
|
-
The \`/test-gate\` command detected a runtime failure in cluster \`${cluster}\` that was introduced by the staging→${DEFAULT_BRANCH} bundle.
|
|
552
|
+
The \`/test-gate\` command detected a runtime failure in cluster \`${cluster}\` that was introduced by the staging→${DEFAULT_BRANCH} bundle. It was not observed failing on \`${BASE_BRANCH}\` (baseline: PASS, or UNAVAILABLE when the baseline could not run — then treated as batch-introduced, fail closed).
|
|
538
553
|
|
|
539
554
|
## Root Cause (if known)
|
|
540
555
|
|
|
@@ -574,7 +589,7 @@ $(echo -e "$SOURCE_ISSUES_LIST")
|
|
|
574
589
|
\`\`\`
|
|
575
590
|
3. Observe failure. Compare against \`${BASE_BRANCH}\` baseline:
|
|
576
591
|
\`\`\`bash
|
|
577
|
-
git
|
|
592
|
+
git worktree add --detach "$BASE_WT" origin/${BASE_BRANCH} # run the command inside $BASE_WT
|
|
578
593
|
$(yq ".verification.integration_tests[] | select(.cluster == \"${cluster}\") | .command" "$CONFIG_FILE")
|
|
579
594
|
\`\`\`
|
|
580
595
|
ISSUE_EOF
|
|
@@ -719,6 +719,15 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:ARCHITECT -->
|
|
|
719
719
|
|---|------|----------------|-----------------|-----|
|
|
720
720
|
| 1 | {FILE} | {FUNCTION} | {CHANGE} | {REASON} |
|
|
721
721
|
|
|
722
|
+
### Scope Baseline
|
|
723
|
+
<!-- Machine-readable scope baseline read by build B5.5's post-build scope check (forge#3862). Every
|
|
724
|
+
path in ### Affected Paths (including tests, docs and config the plan changes), one per line as
|
|
725
|
+
- path (repo-relative; a trailing slash means every file under that directory). The keys sit alone
|
|
726
|
+
on their lines. ESTIMATED_DIFF_LINES is the plan's own estimate (code plus tests, a whole number). -->
|
|
727
|
+
PLANNED_FILES:
|
|
728
|
+
- {FILE}
|
|
729
|
+
ESTIMATED_DIFF_LINES: {N}
|
|
730
|
+
|
|
722
731
|
### Pattern Sweep
|
|
723
732
|
<!-- Pass-through from the FORGE:INVESTIGATOR '### Pattern Sweep' table (Phase A2.7).
|
|
724
733
|
Omit this section when the investigation report had none. -->
|
|
@@ -799,13 +808,16 @@ SKIP_BODY=$(cat <<SKIP_EOF
|
|
|
799
808
|
|
|
800
809
|
**Skipped**: {SKIP_REASON}. No cross-path consistency risk — the builder proceeds with the investigation report + context briefing.
|
|
801
810
|
|
|
811
|
+
### Scope Baseline
|
|
812
|
+
PLANNED_FILES:
|
|
813
|
+
|
|
802
814
|
<!-- FORGE:ARCHITECT:COMPLETE -->
|
|
803
815
|
SKIP_EOF
|
|
804
816
|
)
|
|
805
817
|
gh issue comment {NUMBER} {GH_FLAG} --body "$SKIP_BODY" # <!-- allowlist:check-command-side-effects -->
|
|
806
818
|
```
|
|
807
819
|
|
|
808
|
-
The empty-plan-with-marker changes no downstream behavior — the builder already falls back to the investigation report + contract when the plan is empty (see Integration Point below). The marker only makes the legitimate skip **observable** to the marker-only engine gate. This intentionally does **not** weaken crash detection: a genuinely interrupted architect run still posts no marker at all, so `detectOutcome` still fails-and-escalates it correctly.
|
|
820
|
+
The empty `PLANNED_FILES:` list means "no plan baseline": build B5.5 then skips the out-of-plan check (proceed, never block) and falls back to the investigation's `ESTIMATED_DIFF_LINES`. The empty-plan-with-marker changes no downstream behavior — the builder already falls back to the investigation report + contract when the plan is empty (see Integration Point below). The marker only makes the legitimate skip **observable** to the marker-only engine gate. This intentionally does **not** weaken crash detection: a genuinely interrupted architect run still posts no marker at all, so `detectOutcome` still fails-and-escalates it correctly.
|
|
809
821
|
|
|
810
822
|
---
|
|
811
823
|
|
|
@@ -69,15 +69,36 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
69
69
|
--jq '.[] | select(.body | contains("FORGE:CONTEXT")) | .body'
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
**Resume check
|
|
73
|
-
- If `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment → implementation is done — EXIT and print `IMPLEMENT_RESULT: status: ALREADY_DONE` as the final reply.
|
|
74
|
-
- 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:
|
|
72
|
+
**Resume check** (current-cycle slice only, same as build.md B0 <!-- Added: forge#3666 -->): every read below runs on the comments AFTER the newest trusted `<!-- FORGE:CYCLE: n -->` marker, so a cycle-1 `BUILDER:COMPLETE` never yields `ALREADY_DONE` in cycle 2 (the build would then proceed on an empty branch). `cycle_slice` is declared verbatim from build.md B0:
|
|
75
73
|
```bash
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
cycle_slice() {
|
|
75
|
+
jq -s --arg assoc "${FORGE_TRAIL_TRUSTED_ASSOCIATIONS-OWNER,MEMBER,COLLABORATOR}" --arg logins "${FORGE_TRAIL_TRUSTED_LOGINS-}" '[.[][]] as $c
|
|
76
|
+
| ($assoc | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))) as $A
|
|
77
|
+
| ($logins | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))) as $L
|
|
78
|
+
| ([$c | to_entries[]
|
|
79
|
+
| select(((.value.author_association // "") as $x | $A | index($x) != null) or ((.value.user.type // "") == "Bot") or ((.value.user.login // "") as $x | $L | index($x) != null))
|
|
80
|
+
| select((.value.body // "") | test("\\A\\s*<!--\\s*FORGE:CYCLE:") and test("(?m)^[ \\t]*<!--[ \\t]*FORGE:CYCLE:[ \\t]*[0-9]+[ \\t]*-->[ \\t]*$"))
|
|
81
|
+
| .key] | last) as $i
|
|
82
|
+
| if $i == null then $c else $c[($i + 1):] end'
|
|
83
|
+
}
|
|
84
|
+
# Capture the read first (as build.md B0): piped straight into jq -s, a failed gh call still yields `[]` and exit 0.
|
|
85
|
+
COMMENTS_JSON=$(gh api --paginate repos/{GH_REPO}/issues/{NUMBER}/comments) \
|
|
86
|
+
&& CYCLE_SLICE=$(printf '%s' "$COMMENTS_JSON" | cycle_slice) && [ -n "$CYCLE_SLICE" ] \
|
|
87
|
+
|| { echo "IMPLEMENT_RESULT: status: BLOCKED (cycle-lookup-unavailable: could not read FORGE:CYCLE)"; exit 1; } # a failed read is NEVER "no marker"
|
|
88
|
+
BUILDER_DONE=$(printf '%s' "$CYCLE_SLICE" | jq '[.[] | select((.body // "") | startswith("<!-- FORGE:BUILDER")) | select((.user.type // "") == "Bot" or ((.author_association // "") | IN("OWNER","MEMBER","COLLABORATOR"))) | select(.body | contains("<!-- FORGE:BUILDER:COMPLETE -->"))] | length')
|
|
89
|
+
```
|
|
90
|
+
- If `BUILDER_DONE` > 0 (a `<!-- FORGE:BUILDER:COMPLETE -->` BUILDER comment in the current-cycle slice) → implementation is done — EXIT and print `IMPLEMENT_RESULT: status: ALREADY_DONE` as the final reply.
|
|
91
|
+
- If `<!-- FORGE:BUILDER -->` exists in the current-cycle slice 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:
|
|
92
|
+
Select with ONE anchored, trusted selector (body must start with the marker; author must be a Bot or OWNER/MEMBER/COLLABORATOR; mirrors `scripts/trusted-comments.sh`). A comment that merely mentions the marker (INVESTIGATOR, CONTEXT, ARCHITECT) never matches. Delete ONLY when exactly one non-complete candidate exists and it carries no other FORGE phase marker; unparsable, zero, or multiple matches delete nothing (fail closed). <!-- Added: forge#3668 -->
|
|
93
|
+
```bash
|
|
94
|
+
# only a current-cycle partial is ever deleted; prior-cycle comments are never touched
|
|
95
|
+
BLD=$(printf '%s' "$CYCLE_SLICE" | jq -s '[.[][] | select((.body // "") | startswith("<!-- FORGE:BUILDER -->")) | select((.user.type // "") == "Bot" or ((.author_association // "") | IN("OWNER","MEMBER","COLLABORATOR"))) | {id: .id, complete: (.body | contains("<!-- FORGE:BUILDER:COMPLETE -->")), other: (.body | test("<!-- FORGE:(INVESTIGATOR|CONTEXT|CONTRACT|ARCHITECT)"))}]') || BLD=""
|
|
96
|
+
PARTIAL_ID=$(printf '%s' "$BLD" | jq -r '[.[] | select(.complete | not)] | if length == 1 and (.[0].other | not) then .[0].id else empty end' 2>/dev/null)
|
|
78
97
|
if [ -n "$PARTIAL_ID" ]; then
|
|
79
98
|
gh api repos/{GH_REPO}/issues/comments/$PARTIAL_ID -X DELETE
|
|
80
99
|
echo "Deleted partial FORGE:BUILDER comment (no FORGE:BUILDER:COMPLETE) — restarting implementation"
|
|
100
|
+
else
|
|
101
|
+
echo "builder resume: no single safe partial BUILDER comment — deleting nothing"
|
|
81
102
|
fi
|
|
82
103
|
```
|
|
83
104
|
|
|
@@ -144,6 +165,7 @@ Work in `{WORKTREE_PATH}`. Follow the contract deliverables table exactly — im
|
|
|
144
165
|
- For each acceptance criterion in the contract: implement it, then mentally verify it's met
|
|
145
166
|
- Do NOT add unrequested scope — contract out-of-scope items stay out of scope
|
|
146
167
|
- **Library callback verification**: When writing a lambda or callable that will be passed to a library/framework parameter (e.g., `prepared_statement_name_func=lambda: ""`, `key=lambda x: ...`), you MUST verify the expected calling convention BEFORE writing it. Check the library's default value for that parameter, its documentation, or its source code. A lambda with wrong arity causes `TypeError` at runtime — this is invisible to static analysis and linting. The P0 incident from PR #14391 was caused by `lambda _: ""` (1 arg) passed where SQLAlchemy expects 0 args.
|
|
168
|
+
- **Scratch placement (venvs, caches)**: create virtualenvs, package caches and other large scratch data under `"$TMPDIR/forge-venv-{NUMBER}"` (the engine exports a per-run, disk-backed `TMPDIR`) or inside the worktree. Never use a literal `/tmp/<name>` path: a size-limited `/tmp` fails concurrent lanes with EDQUOT/ENOSPC. Remove what you created when the run finishes. <!-- Added: forge#3572 -->
|
|
147
169
|
- **Worktree-aware path derivation**: When writing shell code that derives repository paths, ALWAYS use `git rev-parse --show-toplevel` for the repo root in regular checkouts, or `git rev-parse --git-common-dir` (then `dirname`) to get the shared `.git` directory when the context may be a linked worktree. For worktree cleanup code specifically, ALWAYS use `--git-common-dir` — `--show-toplevel` returns the worktree path itself, NOT the main repo root. NEVER use `pwd`, relative paths, or `dirname` chains on `--show-toplevel` output for repo root derivation. Test path logic for both regular checkouts and linked worktrees. (Ref: review-findings #104, #105 — 4 PATH_DERIVATION defects, 15% of all review findings)
|
|
148
170
|
- **State machine completeness verification**: When implementing routing logic, state transition tables, or phase dispatch code (e.g., adding a `Skill()` call in a routing loop, adding a new phase to a state machine, creating a new subcommand file), you MUST run three checks BEFORE staging: (1) **Routing target existence** — for each `Skill("subcommand", ...)` call or file reference in routing logic, verify the target file exists at `commands/{subcommand-path}.md`; (2) **State reachability** — for each declared state or phase, verify at least one prior state or entry condition in the router routes to it; (3) **Subcommand invocation wiring** — for each `commands/work-on/*.md` file that declares its invocation condition (e.g., `Invoked by work-on.md routing loop, when X`), verify that condition is actually handled in the router. A missing target file, an unreachable state, or a declared-but-unwired subcommand will not surface until review. (Ref: review-findings #85, #116, #137 — 4 ROUTING defects, 15% of all review findings)
|
|
149
171
|
- **Migration safety checklist** *(trigger: diff includes `*.sql` files or files under a `migrations/` path)*: When any migration file is in the diff, verify ALL of the following BEFORE staging. Fix each violation inline — do not defer to review or the quality gate: (a) **Rollback file**: a corresponding down/rollback migration file exists (e.g. `0042_down_*.sql`, `rollback_*.sql`) or the migration is explicitly self-reversing (DROP of a previously-added column); (b) **NOT NULL safety**: any `ADD COLUMN ... NOT NULL` either includes a `DEFAULT` clause or is preceded by a backfill step — a NOT NULL column without DEFAULT locks the table and fails on existing rows; (c) **Constraint name consistency**: constraint names in the migration match ORM model declarations — a mismatch causes `alembic stamp` and FK introspection to silently diverge; (d) **CREATE TRIGGER idempotency**: every `CREATE TRIGGER` uses `CREATE OR REPLACE TRIGGER` or is preceded by `DROP TRIGGER IF EXISTS` — a bare `CREATE TRIGGER` fails on re-run in test and CI environments; (e) **Migration prefix uniqueness**: confirm the new file's numeric prefix does not already exist in the `infra/migrations/` tree (`ls infra/migrations/*.sql | xargs -n1 basename | grep -oE '^[0-9]+' | sort | uniq -d` prints duplicates) — a duplicate prefix hard-fails the deploy gate regardless of file content. <!-- Added: forge#373 -->
|
|
@@ -284,7 +306,7 @@ git add {CHANGED_FILES}
|
|
|
284
306
|
|
|
285
307
|
Do NOT run `git commit` here. The commit (with conventional commit message and issue reference) is made by validate.md Phase V5 after `GATE_PASSED=true`.
|
|
286
308
|
|
|
287
|
-
**Informational diff size** <!-- Added: forge#3450 -->: after staging, read the changed-line count so the builder comment and result carry it. This is a hint only. Implement never gates or decides: the
|
|
309
|
+
**Informational diff size** <!-- Added: forge#3450 -->: after staging, read the changed-line count so the builder comment and result carry it. This is a hint only. Implement never gates or decides: the scope check lives in build B5.5, which re-measures authoritatively against the plan (it needs only the `diff_lines=` key here). Leave `DIFF_LINES` empty when the script is unavailable or fails.
|
|
288
310
|
|
|
289
311
|
```bash
|
|
290
312
|
# <Script resolution block from work-on/build.md, verbatim>
|
|
@@ -153,6 +153,8 @@ gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human" # allowlist:check-com
|
|
|
153
153
|
|
|
154
154
|
Run after quality gate passes. All tool commands are read from `forge.yaml → verification.commands`. `forge.yaml` is usually gitignored, so it is absent from the worktree: every block below resolves `FORGE_CFG` to the worktree copy if present, else the main checkout's `forge.yaml` (via `git rev-parse --git-common-dir`). Before this, every verification command, learned test command and the SOPS chain check were silently skipped in worktree builds; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing.
|
|
155
155
|
|
|
156
|
+
**Scratch placement (venvs, caches)** <!-- Added: forge#3572 -->: any virtualenv, package cache or other large scratch data a verification command needs goes under `"$TMPDIR/forge-venv-{NUMBER}"` (the engine exports a per-run, disk-backed `TMPDIR`) or inside the worktree, never a literal `/tmp/<name>` path. A size-limited `/tmp` fails concurrent lanes with EDQUOT/ENOSPC. Remove what you created when validation finishes.
|
|
157
|
+
|
|
156
158
|
**Track skipped checks** — initialize before any check runs:
|
|
157
159
|
```bash
|
|
158
160
|
SKIPPED_CHECKS=""
|
|
@@ -710,8 +712,9 @@ else
|
|
|
710
712
|
fi
|
|
711
713
|
|
|
712
714
|
# Find the FORGE:BUILDER comment posted by implement.md Phase I6
|
|
713
|
-
|
|
714
|
-
|
|
715
|
+
# Anchored, trusted, single-match selector (forge#3668): zero/many/unparsable → empty → append skipped.
|
|
716
|
+
BLD=$(gh api --paginate repos/{GH_REPO}/issues/{NUMBER}/comments | jq -s '[.[][] | select((.body // "") | startswith("<!-- FORGE:BUILDER -->")) | select((.user.type // "") == "Bot" or ((.author_association // "") | IN("OWNER","MEMBER","COLLABORATOR"))) | {id: .id, complete: (.body | contains("<!-- FORGE:BUILDER:COMPLETE -->")), other: (.body | test("<!-- FORGE:(INVESTIGATOR|CONTEXT|CONTRACT|ARCHITECT)"))}]') || BLD=""
|
|
717
|
+
BUILDER_COMMENT_ID=$(printf '%s' "$BLD" | jq -r '[.[] | select(.complete | not)] | if length == 1 and (.[0].other | not) then .[0].id else empty end' 2>/dev/null)
|
|
715
718
|
|
|
716
719
|
if [ -n "$BUILDER_COMMENT_ID" ]; then
|
|
717
720
|
CURRENT_BODY=$(gh api repos/{GH_REPO}/issues/comments/$BUILDER_COMMENT_ID --jq '.body')
|