forgedock 1.8.2 → 1.8.3
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/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/orchestrate/config.md +1 -0
- package/commands/orchestrate/phase-1-resolve.md +1 -0
- package/commands/orchestrate/phase-2-triage.md +1 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +1 -0
- package/commands/orchestrate/phase-3-dependency.md +2 -0
- package/commands/orchestrate/phase-4-execution.md +63 -31
- package/commands/orchestrate/phase-5-cleanup.md +1 -0
- package/commands/orchestrate/phase-6-report.md +1 -0
- package/commands/orchestrate/safety.md +1 -0
- package/commands/pipeline-health/config.md +1 -0
- package/commands/pipeline-health/phase-1-context.md +1 -0
- package/commands/pipeline-health/phase-2-metrics.md +1 -0
- package/commands/pipeline-health/phase-3-analyze.md +1 -0
- package/commands/pipeline-health/phase-4-proposals.md +1 -0
- package/commands/pipeline-health/phase-5-report.md +1 -0
- package/commands/pipeline-health/phase-6-summary.md +1 -0
- package/commands/recover-orphans.md +56 -9
- package/commands/review-pr-agents/api.md +1 -0
- package/commands/review-pr-agents/auth.md +1 -0
- package/commands/review-pr-agents/billing.md +1 -0
- package/commands/review-pr-agents/concurrency.md +1 -0
- package/commands/review-pr-agents/database.md +1 -0
- package/commands/review-pr-agents/frontend.md +1 -0
- package/commands/review-pr-agents/infra.md +1 -0
- package/commands/review-pr-agents/protocols.md +1 -0
- package/commands/review-pr-agents/scraper.md +1 -0
- package/commands/review-pr-agents/security.md +1 -0
- package/commands/review-pr-agents/spec-cli.md +1 -0
- package/commands/review-pr-agents.md +4 -0
- package/commands/review-pr-staging.md +1 -0
- package/commands/work-on/build/architect.md +1 -0
- package/commands/work-on/build/context.md +1 -0
- package/commands/work-on/build/implement.md +1 -0
- package/commands/work-on/build/validate.md +1 -0
- package/commands/work-on/build.md +1 -0
- package/commands/work-on/close.md +1 -0
- package/commands/work-on/decompose.md +1 -0
- package/commands/work-on/investigate.md +1 -0
- package/commands/work-on/remediate.md +1 -0
- package/commands/work-on/review.md +1 -0
- package/package.json +1 -1
- package/scripts/check-spec-markers.sh +10 -0
- package/commands/work-on-monolithic.md +0 -367
package/commands/design-bench.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Render a design from its FORGE:DESIGN_SPEC, critique the rendered pixels against the perceptual negatives, and iterate until it passes or the budget is spent — the missing reward signal.
|
|
3
4
|
argument-hint: "[<issue-number>|<spec-path>] [--max-iters <n>] [--viewports desktop,mobile]"
|
|
4
5
|
install: internal
|
package/commands/design.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Route a design GitHub issue through the UI Taste Harness — investigate → architect → implement → critique loop → user-feedback loop → close — accumulating FORGE:DESIGN_* annotations and driving the design:* label state machine to design:shipped.
|
|
3
4
|
argument-hint: <issue-number> | <brief-name>
|
|
4
5
|
install: internal
|
package/commands/forge-stats.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
install: core
|
|
3
4
|
---
|
|
4
5
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
@@ -1393,6 +1394,7 @@ fi
|
|
|
1393
1394
|
# hold_merged_trail, resolve_orch_login, trail_escalation_state, release_merged_trail (reverify_merged_trail is
|
|
1394
1395
|
# nested inside the classifier). It reports a missing helper on stderr and classifies GATED. The same trusted-author
|
|
1395
1396
|
# filter (OWNER/MEMBER/COLLABORATOR or the orchestrator login) applies to the release/fail markers on wake.
|
|
1397
|
+
# forge#3169: merged + needs-human with no escalation record classifies GATED on wake (never keyed on the verifier).
|
|
1396
1398
|
declare -A ISSUE_CLASS
|
|
1397
1399
|
declare -A ISSUE_FILES
|
|
1398
1400
|
DONE_ISSUES=()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
install: core
|
|
3
4
|
---
|
|
4
5
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
@@ -1342,21 +1343,19 @@ For an OpenCode completion, retain `OPENCODE_DISPATCH_MAP[{NUMBER}]` until the t
|
|
|
1342
1343
|
```bash
|
|
1343
1344
|
# hold_merged_trail <issue> <trail-text> (forge#3156): the single escalation path for a merged issue whose phase
|
|
1344
1345
|
# trail failed or could not be verified. Used by Step 4B item 0 and by the classifier's wake re-verification.
|
|
1345
|
-
# Order matters: the
|
|
1346
|
-
#
|
|
1347
|
-
#
|
|
1346
|
+
# Order matters (forge#3169): the escalation COMMENT is posted FIRST (re-read immediately before each post, skipped when
|
|
1347
|
+
# an ACTIVE escalation already exists, verified ACTIVE by re-read after), then the `needs-human` label is written
|
|
1348
|
+
# (read-back verified, 3 tries). The comment is the durable audit record, independent of the verifier, so a label-only
|
|
1349
|
+
# hold is not the normal state and a human release always has an anchor to be recorded against. Returns 0 only when both
|
|
1350
|
+
# halves are verified; otherwise returns 1 and stderr names which half failed (the label is still attempted after a
|
|
1351
|
+
# comment failure, because holding dependents is the safe side).
|
|
1348
1352
|
hold_merged_trail() {
|
|
1349
|
-
local N="$1" TXT="$2" TRY HELD=1
|
|
1350
|
-
for TRY in 1 2 3; do
|
|
1351
|
-
gh issue edit "$N" -R {GH_REPO} --add-label "needs-human" >/dev/null 2>&1 || true # <!-- allowlist:check-command-side-effects -->
|
|
1352
|
-
if gh issue view "$N" -R {GH_REPO} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null \
|
|
1353
|
-
| grep -qE '(^|,)needs-human(,|$)'; then HELD=0; break; fi
|
|
1354
|
-
done
|
|
1355
|
-
[ "$HELD" -ne 0 ] && echo "PHASE TRAIL HOLD FAILED: #${N} — needs-human label could not be verified; dependents are NOT held. Hold them manually." >&2
|
|
1353
|
+
local N="$1" TXT="$2" TRY COMMENTED=1 HELD=1
|
|
1356
1354
|
for TRY in 1 2; do
|
|
1357
1355
|
# forge#3157: dedupe only against an ACTIVE escalation, so a re-escalation after a recorded release posts a new one.
|
|
1356
|
+
# forge#3169: re-read right before each post (cache entry removed) to narrow the concurrent-classifier duplicate window.
|
|
1358
1357
|
[ -n "${TRAIL_CACHE_DIR:-}" ] && rm -f "$TRAIL_CACHE_DIR/trail-$N"
|
|
1359
|
-
[ "$(trail_escalation_state "$N")" = "ACTIVE" ]
|
|
1358
|
+
if [ "$(trail_escalation_state "$N")" = "ACTIVE" ]; then COMMENTED=0; break; fi
|
|
1360
1359
|
gh issue comment "$N" -R {GH_REPO} --body "<!-- FORGE:PHASE_TRAIL_FAILED -->
|
|
1361
1360
|
#${N} merged with an incomplete phase trail (or one that could not be verified), detected by /orchestrate.
|
|
1362
1361
|
|
|
@@ -1364,10 +1363,19 @@ hold_merged_trail() {
|
|
|
1364
1363
|
${TXT}
|
|
1365
1364
|
\`\`\`
|
|
1366
1365
|
|
|
1367
|
-
The PR is already merged, so \`/work-on\` cannot re-run the missing phases. Review the merged change, then remove \`needs-human\` to release dependents held as \`blocked-on-human-merge\`." >/dev/null 2>&1 || true
|
|
1366
|
+
The PR is already merged, so \`/work-on\` cannot re-run the missing phases. Review the merged change, then remove \`needs-human\` to release dependents held as \`blocked-on-human-merge\`." >/dev/null 2>&1 || true # <!-- allowlist:check-command-side-effects -->
|
|
1367
|
+
[ -n "${TRAIL_CACHE_DIR:-}" ] && rm -f "$TRAIL_CACHE_DIR/trail-$N"
|
|
1368
|
+
if [ "$(trail_escalation_state "$N")" = "ACTIVE" ]; then COMMENTED=0; break; fi
|
|
1369
|
+
done
|
|
1370
|
+
for TRY in 1 2 3; do
|
|
1371
|
+
gh issue edit "$N" -R {GH_REPO} --add-label "needs-human" >/dev/null 2>&1 || true # <!-- allowlist:check-command-side-effects -->
|
|
1372
|
+
if gh issue view "$N" -R {GH_REPO} --json labels --jq '[.labels[].name] | join(",")' 2>/dev/null \
|
|
1373
|
+
| grep -qE '(^|,)needs-human(,|$)'; then HELD=0; break; fi
|
|
1368
1374
|
done
|
|
1369
1375
|
[ -n "${TRAIL_CACHE_DIR:-}" ] && rm -f "$TRAIL_CACHE_DIR/trail-$N"
|
|
1370
|
-
|
|
1376
|
+
[ "$COMMENTED" -ne 0 ] && echo "PHASE TRAIL HOLD FAILED: #${N} — escalation comment could not be verified (audit record missing); hold dependents manually." >&2
|
|
1377
|
+
[ "$HELD" -ne 0 ] && echo "PHASE TRAIL HOLD FAILED: #${N} — needs-human label could not be verified; dependents are NOT held. Hold them manually." >&2
|
|
1378
|
+
[ "$COMMENTED" -eq 0 ] && [ "$HELD" -eq 0 ]
|
|
1371
1379
|
}
|
|
1372
1380
|
|
|
1373
1381
|
# resolve_orch_login (forge#3168): prints the login of the identity that authors the orchestrator's own FAILED/RELEASED
|
|
@@ -1410,6 +1418,8 @@ trail_escalation_state() {
|
|
|
1410
1418
|
|
|
1411
1419
|
# release_merged_trail <issue> (forge#3157): record that a human cleared a merged-trail hold, so the escalation
|
|
1412
1420
|
# stops gating. Call only after classify_predecessor_state reported DONE for a merged issue whose escalation is ACTIVE.
|
|
1421
|
+
# Only an ACTIVE escalation is released here: hold_merged_trail posts the comment before the label (forge#3169), so a
|
|
1422
|
+
# human release of a label-only (ABSENT) hold is not expected and is not recorded (avoids a stray RELEASED marker).
|
|
1413
1423
|
# Idempotent (no-op unless ACTIVE) and never run in dry-run. The comment must not contain the escalation marker text.
|
|
1414
1424
|
release_merged_trail() {
|
|
1415
1425
|
local N="$1"
|
|
@@ -1452,13 +1462,29 @@ classify_predecessor_state() {
|
|
|
1452
1462
|
|
|
1453
1463
|
# reverify_merged_trail: re-run the phase-trail verifier for a merged predecessor that has NO escalation
|
|
1454
1464
|
# record (forge#3156), so wake reconstruction does not trust the `workflow:merged` label alone. Prints OK
|
|
1455
|
-
# when the trail verifies, UNVERIFIED otherwise.
|
|
1456
|
-
#
|
|
1457
|
-
#
|
|
1465
|
+
# when the trail verifies, UNVERIFIED otherwise. Fails closed: a wake runs in a fresh shell where FORGE_ROOT is
|
|
1466
|
+
# unset, so the helper resolves it itself (canonical bootstrap, only when unset); if it is still unresolved or
|
|
1467
|
+
# the verifier is missing, the result is UNVERIFIED for every predecessor, never OK.
|
|
1458
1468
|
reverify_merged_trail() {
|
|
1459
|
-
local P="$1"
|
|
1469
|
+
local P="$1" RC
|
|
1470
|
+
if [ -z "${FORGE_ROOT:-}" ]; then
|
|
1471
|
+
# FORGE_ROOT bootstrap (canonical; keep byte-identical across specs, guarded by scripts/forge-root.test.sh)
|
|
1472
|
+
FORGE_ROOT=""
|
|
1473
|
+
if [ -n "${FORGEDOCK_HOME:-}" ]; then case "$FORGEDOCK_HOME" in /*) FORGE_ROOT="$FORGEDOCK_HOME" ;; esac; else
|
|
1474
|
+
# Portable to bash 3.2 (macOS), BSD/GNU coreutils and zsh: no mapfile, no sort -V, no bare globs (zsh aborts on no match).
|
|
1475
|
+
_l="$HOME/.claude/commands/work-on.md"; _l="$(readlink -f "$_l" 2>/dev/null || readlink "$_l" 2>/dev/null)"; [ -n "$_l" ] && _l="$(dirname "$(dirname "$_l")")"
|
|
1476
|
+
# newest cached version first: numeric major.minor.patch of the version dir name only; a release outranks its pre-release (1.10.0 > 1.9.0 > 1.9.0-rc1)
|
|
1477
|
+
_v="$(find -L "$HOME/.claude/plugins/cache" -mindepth 3 -maxdepth 3 -type d 2>/dev/null | awk -F/ '$(NF-1)=="forgedock"{v=$NF;p=index(v,"-");r=1;if(p){v=substr(v,1,p-1);r=0};split(v,a,".");printf "%d %d %d %d %s\n",a[1],a[2],a[3],r,$0}' | sort -k1,1nr -k2,2nr -k3,3nr -k4,4nr | cut -d' ' -f5-)"
|
|
1478
|
+
_m="$(find -L "$HOME/.claude/plugins/marketplaces" -mindepth 1 -maxdepth 1 -type d -iname '*forgedock*' 2>/dev/null)"
|
|
1479
|
+
# '${CLAUDE_PLUGIN_ROOT}' is substituted by Claude Code when it loads a plugin spec (the exact spelling only, never as an env var), so a running plugin resolves to its own root first; unsubstituted (other runtimes) it stays a literal that the /* check rejects.
|
|
1480
|
+
_k="$(printf '%s\n' '${CLAUDE_PLUGIN_ROOT}' "${FORGE_HOME:-}" "$_l" "$_v" "$_m")"
|
|
1481
|
+
while IFS= read -r _c; do
|
|
1482
|
+
case "$_c" in /*) [ -z "$FORGE_ROOT" ] && [ -f "$_c/scripts/verify-phase-trail.sh" ] && [ -f "$_c/scripts/lint-dispatch-prompt.sh" ] && [ -f "$_c/scripts/is-docs-only.sh" ] && [ -f "$_c/bin/engine/resolve.mjs" ] && [ -f "$_c/bin/engine/orchestrate-canary.mjs" ] && [ -f "$_c/bin/engine/admission.mjs" ] && FORGE_ROOT="$_c" ;; esac
|
|
1483
|
+
done <<< "$_k"
|
|
1484
|
+
fi
|
|
1485
|
+
fi
|
|
1460
1486
|
if [ -z "${FORGE_ROOT:-}" ] || [ ! -f "$FORGE_ROOT/scripts/verify-phase-trail.sh" ]; then
|
|
1461
|
-
|
|
1487
|
+
echo "UNVERIFIED"; return
|
|
1462
1488
|
fi
|
|
1463
1489
|
bash "$FORGE_ROOT/scripts/verify-phase-trail.sh" "$P" -R {GH_REPO} --code-diff >/dev/null 2>&1; RC=$?
|
|
1464
1490
|
[ "$RC" -eq 0 ] && echo "OK" || echo "UNVERIFIED"
|
|
@@ -1469,13 +1495,14 @@ classify_predecessor_state() {
|
|
|
1469
1495
|
elif echo "$PRED_LABELS" | grep -qx "workflow:merged" && echo "$PRED_LABELS" | grep -qx "needs-human"; then
|
|
1470
1496
|
# forge#3148: GATED when Step 4B item 0 escalated a merged issue with an incomplete phase trail
|
|
1471
1497
|
# (the code is in the base branch, but /work-on cannot repair a merged issue, so a human must review it;
|
|
1472
|
-
# dependents wait until a human clears needs-human).
|
|
1473
|
-
#
|
|
1474
|
-
#
|
|
1498
|
+
# dependents wait until a human clears needs-human). forge#3169: the hold is NOT keyed on the verifier. A passing
|
|
1499
|
+
# verifier does not mean "not held" (an agent-reported bypass verifies clean), and hold_merged_trail posts the
|
|
1500
|
+
# comment before the label, so merged + needs-human with no record is a failed/partial hold: fail closed (GATED).
|
|
1501
|
+
# A human release is the removal of needs-human, recorded by the release sweep via release_merged_trail.
|
|
1475
1502
|
local TRAIL_ESC
|
|
1476
1503
|
TRAIL_ESC=$(trail_escalation_state "$PRED")
|
|
1477
1504
|
case "$TRAIL_ESC" in
|
|
1478
|
-
ABSENT)
|
|
1505
|
+
ABSENT) echo "GATED" ;; # forge#3169: no record yet; never trust the verifier to release a labelled hold
|
|
1479
1506
|
RELEASED) echo "DONE" ;; # forge#3157: a human release was recorded after the last escalation; this needs-human is unrelated
|
|
1480
1507
|
*) echo "GATED" ;; # ACTIVE or UNREADABLE: fail closed
|
|
1481
1508
|
esac
|
|
@@ -1483,16 +1510,20 @@ classify_predecessor_state() {
|
|
|
1483
1510
|
# forge#3156: no needs-human. A recorded escalation (ACTIVE or RELEASED) means a human already released the hold
|
|
1484
1511
|
# (DONE, forge#3157: the sweep records it via release_merged_trail); otherwise re-verify the trail so a merged
|
|
1485
1512
|
# predecessor that item 0 never got to escalate (orchestrator died mid-hold, wake reconstruction) is not trusted
|
|
1486
|
-
# on the label alone.
|
|
1513
|
+
# on the label alone. forge#3169: hold only when the record re-reads exactly ABSENT immediately before (an
|
|
1514
|
+
# UNREADABLE read classifies GATED without writing), so a concurrent classifier or a recorded release is not
|
|
1515
|
+
# overridden and at most one escalation comment is posted in the common case.
|
|
1487
1516
|
case "$(trail_escalation_state "$PRED")" in
|
|
1488
1517
|
ACTIVE|RELEASED) echo "DONE" ;;
|
|
1518
|
+
UNREADABLE) echo "GATED" ;;
|
|
1489
1519
|
*)
|
|
1490
|
-
if [ "$(reverify_merged_trail "$PRED"
|
|
1520
|
+
if [ "$(reverify_merged_trail "$PRED")" = "OK" ]; then
|
|
1491
1521
|
echo "DONE"
|
|
1492
1522
|
else
|
|
1493
1523
|
# Unverified trail with no hold on record: establish it now so a human can release it.
|
|
1494
1524
|
# Output is discarded (stdout is the classification), but a failed hold is reported on stderr, not swallowed.
|
|
1495
|
-
[ "${
|
|
1525
|
+
[ -n "${TRAIL_CACHE_DIR:-}" ] && rm -f "$TRAIL_CACHE_DIR/trail-$PRED"
|
|
1526
|
+
[ "${DRY_RUN:-false}" = "true" ] || [ "$(trail_escalation_state "$PRED")" != "ABSENT" ] || hold_merged_trail "$PRED" "PHASE_TRAIL: FAIL (re-verified at wake classification)" >/dev/null || echo "classify_predecessor_state: hold on #${PRED} could not be verified" >&2
|
|
1496
1527
|
echo "GATED"
|
|
1497
1528
|
fi ;;
|
|
1498
1529
|
esac
|
|
@@ -1881,11 +1912,11 @@ done
|
|
|
1881
1912
|
|
|
1882
1913
|
On `PHASE_TRAIL: FAIL`, classify the issue as a **bypass**, never `DONE`. Classify ONLY on objective evidence — the trail verdict above and the issue's labels. Do NOT reclassify an issue from a worker's own narrative ("ran inline", "dispatched reviewers directly", "skipped an optional step"): every phase runs forked and the merge itself is gated (CI green on the exact merged commit, reviewed head or docs-only delta, phase trail), so a merged issue whose trail passes is DONE. Field evidence: narrative-based holds flagged spec-compliant behaviour and put 5 of 6 merged issues in a stress batch on needs-human. Surface it in the operator status update (`PHASE BYPASS: #{NUMBER} — missing: ...`) and do not cascade it as a satisfied predecessor.
|
|
1883
1914
|
|
|
1884
|
-
**Do NOT re-dispatch a merged issue** (forge#3148): `/work-on` Phase 0B stops on a closed or `workflow:merged` issue before its resume preflight, so a fresh agent exits without repairing anything and the predecessor would stay unresolved forever. The PR is already merged, so the gap can only be resolved by a human. Escalate it instead, which classifies it **GATED** (`classify_predecessor_state` holds `workflow:merged` + `needs-human`
|
|
1915
|
+
**Do NOT re-dispatch a merged issue** (forge#3148): `/work-on` Phase 0B stops on a closed or `workflow:merged` issue before its resume preflight, so a fresh agent exits without repairing anything and the predecessor would stay unresolved forever. The PR is already merged, so the gap can only be resolved by a human. Escalate it instead, which classifies it **GATED** (`classify_predecessor_state` holds `workflow:merged` + `needs-human` with no recorded release, regardless of the verifier; the comment is posted first, then the label, both verified), so item 6.5 tracks its dependents as `blocked-on-human-merge` instead of dispatching them. A merged issue whose trail could not be read (`TRAIL_RC -ge 2`, handled above) is escalated by the same block, so it is gated the same way. A bypass reported by the agent on a `workflow:merged` issue whose trail verifies is handled the same way (set `TRAIL_RC=1` before this block):
|
|
1885
1916
|
```bash
|
|
1886
1917
|
# Only a merged issue is escalated here; an unmerged bypass is resumed by item 2 (see below).
|
|
1887
1918
|
# Any non-zero rc on a merged issue escalates: rc 1 (missing phases) and rc >= 2 (unverifiable trail) alike,
|
|
1888
|
-
# because classify_predecessor_state() gates a merged predecessor
|
|
1919
|
+
# because classify_predecessor_state() gates a merged predecessor on the escalation record plus the needs-human label.
|
|
1889
1920
|
# forge#3156: also escalate when the label read itself failed: an unreadable issue cannot be proven unmerged, so
|
|
1890
1921
|
# fail closed instead of posting nothing. forge#3168: key on TRAIL_LABELS_READ_OK, not an empty TRAIL_LABELS, so a
|
|
1891
1922
|
# successfully read, label-less, non-merged issue is never escalated (read OK + empty labels => no escalation).
|
|
@@ -1893,10 +1924,11 @@ done
|
|
|
1893
1924
|
if [ "${DRY_RUN:-false}" = "true" ]; then
|
|
1894
1925
|
echo "[DRY-RUN] Would add needs-human and post FORGE:PHASE_TRAIL_FAILED on merged #{NUMBER}"
|
|
1895
1926
|
else
|
|
1896
|
-
# hold_merged_trail (defined with classify_predecessor_state)
|
|
1897
|
-
#
|
|
1898
|
-
#
|
|
1899
|
-
|
|
1927
|
+
# hold_merged_trail (defined with classify_predecessor_state) posts the escalation comment FIRST (re-read before
|
|
1928
|
+
# posting, verified), then writes `needs-human` (read-back verified, retried). A merged + needs-human issue with no
|
|
1929
|
+
# comment classifies GATED regardless of the verifier. A failed half is reported on stderr and the operator must
|
|
1930
|
+
# hold dependents manually (residual: a human clearing the label after both comment attempts failed).
|
|
1931
|
+
hold_merged_trail {NUMBER} "${TRAIL}" || echo "PHASE TRAIL ESCALATION FAILED: #{NUMBER} — comment or needs-human not verified." >&2
|
|
1900
1932
|
fi
|
|
1901
1933
|
fi
|
|
1902
1934
|
```
|
|
@@ -317,7 +317,9 @@ done
|
|
|
317
317
|
|
|
318
318
|
For each diagnosed issue, apply the recovery action. All mutating actions are skipped when `DRY_RUN=true`.
|
|
319
319
|
|
|
320
|
-
**Claim before re-entering the pipeline** (forge#3158): the `review-pr` first-refusal resume and `create-pr` re-enter `/work-on` inline. A repeated or concurrent sweep, or a live `/orchestrate` agent, could otherwise run the pipeline twice on one issue. Every inline `/work-on` resume MUST go through `claim_orphan` first and
|
|
320
|
+
**Claim before re-entering the pipeline** (forge#3158): the `review-pr` first-refusal resume and `create-pr` re-enter `/work-on` inline. A repeated or concurrent sweep, or a live `/orchestrate` agent, could otherwise run the pipeline twice on one issue. Every inline `/work-on` resume MUST go through `claim_orphan` first and run inside `resume_orphan_inline`, which releases the claim on every exit. A sweep is not an orchestrator, so it takes an issue-scoped `FORGE:RECOVERY_CLAIM` marker (visible to other sweeps) and defers to any live orchestrator signal (a fresh `FORGE:HEARTBEAT`, which `/work-on --under-orchestration` posts at every phase entry).
|
|
321
|
+
|
|
322
|
+
**Claim refresh and guaranteed release** (forge#3171): liveness is judged from the claim comment's `updated_at`, and an inline resume posts no `FORGE:HEARTBEAT` (that is gated on `--under-orchestration`, which sweeps do not pass). So the claim is kept alive by editing it: `claim_orphan` records the claim comment id, and `start_claim_keepalive` runs `refresh_orphan` in a background loop every `RECOVERY_CLAIM_TTL_MIN / 3` minutes while the resume runs. `refresh_orphan` PATCHes the claim body, keeping the `**Sweep: id**` line and appending a changing `Refreshed: <UTC>` line, so `updated_at` always advances. Refresh is best-effort; if the keepalive is lost the claim degrades to the plain TTL behavior. `resume_orphan_inline` registers `trap ... release_orphan ... EXIT` in a subshell, so the claim is released always, on success and when the `Skill(...)` call fails, and `release_orphan` stops the keepalive first and is idempotent (it never posts the release marker twice). The keepalive and the `Skill(...)` call belong to the same sweep run. DRY_RUN makes every one of these helpers a no-op.
|
|
321
323
|
|
|
322
324
|
```bash
|
|
323
325
|
RECOVERY_CLAIM_TTL_MIN="${RECOVERY_CLAIM_TTL_MIN:-30}" # a claim or heartbeat older than this is treated as dead
|
|
@@ -328,7 +330,7 @@ SWEEP_ID="sweep-$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
|
|
328
330
|
# Both reads run under pipefail in a subshell: the pipeline status would otherwise be jq's, and a gh failure
|
|
329
331
|
# (rate limit, 403) would yield "[]" with exit 0, indistinguishable from "no live holder".
|
|
330
332
|
claim_orphan() {
|
|
331
|
-
local num="$1" now cutoff comments live
|
|
333
|
+
local num="$1" now cutoff comments live resp
|
|
332
334
|
[ "$DRY_RUN" = "true" ] && return 0 # dry-run: no claim comment is posted (callers already skip dry-run resumes)
|
|
333
335
|
now=$(date -u +%s); cutoff=$(( now - RECOVERY_CLAIM_TTL_MIN * 60 ))
|
|
334
336
|
comments=$(set -o pipefail; gh api --paginate "repos/${GH_REPO}/issues/${num}/comments" 2>/dev/null \
|
|
@@ -349,10 +351,13 @@ claim_orphan() {
|
|
|
349
351
|
return 1
|
|
350
352
|
fi
|
|
351
353
|
|
|
352
|
-
|
|
354
|
+
# Post via the API so the comment id is known: refresh_orphan needs it. The Sweep line must stay line 2.
|
|
355
|
+
CLAIM_BODY="<!-- FORGE:RECOVERY_CLAIM -->
|
|
353
356
|
**Sweep: ${SWEEP_ID}**
|
|
354
|
-
Holder: /recover-orphans — resuming /work-on #${num}. Other sweeps and dispatchers: do not re-enter this issue until the matching release marker is posted."
|
|
355
|
-
|
|
357
|
+
Holder: /recover-orphans — resuming /work-on #${num}. Other sweeps and dispatchers: do not re-enter this issue until the matching release marker is posted."
|
|
358
|
+
resp=$(gh api -X POST "repos/${GH_REPO}/issues/${num}/comments" -f body="$CLAIM_BODY" --jq '.id' 2>/dev/null) \
|
|
359
|
+
&& [ -n "$resp" ] || { CLAIM_SKIP_REASON="could not post FORGE:RECOVERY_CLAIM"; return 1; }
|
|
360
|
+
CLAIM_COMMENT_ID="$resp"; CLAIM_HELD_NUM="$num" # set before the race check so release_orphan covers the race-lost paths
|
|
356
361
|
|
|
357
362
|
# Race check: re-read; the earliest unreleased claim wins. Losing means another sweep claimed first.
|
|
358
363
|
local first
|
|
@@ -371,12 +376,56 @@ Holder: /recover-orphans — resuming /work-on #${num}. Other sweeps and dispatc
|
|
|
371
376
|
return 0
|
|
372
377
|
}
|
|
373
378
|
|
|
379
|
+
# refresh_orphan <issue> — best-effort: edit the claim comment so its updated_at advances. The body must change
|
|
380
|
+
# every time (an identical-body edit may not bump updated_at). Never carries FORGE:HEARTBEAT or the RELEASED marker.
|
|
381
|
+
refresh_orphan() {
|
|
382
|
+
[ "$DRY_RUN" = "true" ] && return 0
|
|
383
|
+
[ -n "${CLAIM_COMMENT_ID:-}" ] && [ "${CLAIM_HELD_NUM:-}" = "$1" ] || return 0
|
|
384
|
+
gh api -X PATCH "repos/${GH_REPO}/issues/comments/${CLAIM_COMMENT_ID}" \
|
|
385
|
+
-f body="${CLAIM_BODY}
|
|
386
|
+
Refreshed: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >/dev/null 2>&1 || true
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# start_claim_keepalive <issue> — non-blocking background refresh every TTL/3 (strictly under the TTL).
|
|
390
|
+
start_claim_keepalive() {
|
|
391
|
+
[ "$DRY_RUN" = "true" ] && return 0
|
|
392
|
+
stop_claim_keepalive
|
|
393
|
+
local num="$1" interval=$(( RECOVERY_CLAIM_TTL_MIN * 60 / 3 ))
|
|
394
|
+
[ "$interval" -ge 1 ] || interval=1
|
|
395
|
+
( while sleep "$interval"; do refresh_orphan "$num"; done ) >/dev/null 2>&1 &
|
|
396
|
+
KEEPALIVE_PID=$!
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
stop_claim_keepalive() {
|
|
400
|
+
[ -n "${KEEPALIVE_PID:-}" ] || return 0
|
|
401
|
+
pkill -P "$KEEPALIVE_PID" 2>/dev/null || true # the pending sleep
|
|
402
|
+
kill "$KEEPALIVE_PID" 2>/dev/null || true
|
|
403
|
+
wait "$KEEPALIVE_PID" 2>/dev/null || true
|
|
404
|
+
unset KEEPALIVE_PID
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# release_orphan <issue> — stops the keepalive first; idempotent (the release marker is posted at most once per claim).
|
|
374
408
|
release_orphan() {
|
|
375
409
|
[ "$DRY_RUN" = "true" ] && return 0
|
|
410
|
+
stop_claim_keepalive
|
|
411
|
+
[ "${CLAIM_HELD_NUM:-}" = "$1" ] || return 0
|
|
412
|
+
unset CLAIM_HELD_NUM CLAIM_COMMENT_ID
|
|
376
413
|
gh issue comment "$1" ${GH_FLAG} --body "<!-- FORGE:RECOVERY_CLAIM_RELEASED -->
|
|
377
414
|
**Sweep: ${SWEEP_ID}**
|
|
378
415
|
Released by /recover-orphans." >/dev/null 2>&1 || true
|
|
379
416
|
}
|
|
417
|
+
|
|
418
|
+
# resume_orphan_inline <issue> — run after claim_orphan succeeded. The subshell EXIT trap releases the claim always:
|
|
419
|
+
# on success and when the Skill call fails. The keepalive runs alongside the Skill call and is stopped by the release.
|
|
420
|
+
resume_orphan_inline() {
|
|
421
|
+
local num="$1"
|
|
422
|
+
(
|
|
423
|
+
trap 'release_orphan "$num"' EXIT
|
|
424
|
+
start_claim_keepalive "$num"
|
|
425
|
+
refresh_orphan "$num"
|
|
426
|
+
Skill(skill="{FORGE_SKILL_PREFIX}work-on", args="${num}")
|
|
427
|
+
)
|
|
428
|
+
}
|
|
380
429
|
```
|
|
381
430
|
|
|
382
431
|
```bash
|
|
@@ -510,8 +559,7 @@ See the latest FORGE:PHASE_TRAIL_FAILED comment for the missing phases. Re-run e
|
|
|
510
559
|
# First refusal: resume /work-on, whose resume preflight re-runs each missing phase via Skill(...)
|
|
511
560
|
# and then re-enters review (work-on.md Phase 0B resume → work-on/review.md).
|
|
512
561
|
if claim_orphan "$NUM"; then
|
|
513
|
-
|
|
514
|
-
release_orphan "$NUM"
|
|
562
|
+
resume_orphan_inline "$NUM" # releases the claim on every exit
|
|
515
563
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} refused (phase trail) — resumed /work-on to re-run missing phases |\n"
|
|
516
564
|
else
|
|
517
565
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} refused (phase trail) — /work-on resume skipped: ${CLAIM_SKIP_REASON} |\n"
|
|
@@ -545,8 +593,7 @@ See the latest FORGE:PHASE_TRAIL_FAILED comment for the missing phases. Re-run e
|
|
|
545
593
|
echo " [DRY-RUN] Would: Skill(skill='{FORGE_SKILL_PREFIX}work-on', args='$NUM')"
|
|
546
594
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | Would resume /work-on — branch $BRANCH has commits, no PR |\n"
|
|
547
595
|
elif claim_orphan "$NUM"; then
|
|
548
|
-
|
|
549
|
-
release_orphan "$NUM"
|
|
596
|
+
resume_orphan_inline "$NUM" # releases the claim on every exit
|
|
550
597
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | Resumed /work-on — branch $BRANCH has commits, no PR |\n"
|
|
551
598
|
else
|
|
552
599
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | /work-on resume skipped: ${CLAIM_SKIP_REASON} |\n"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Pre-implementation architecture planning — traces ALL affected code paths and produces an ordered implementation plan before any code is written
|
|
3
4
|
argument-hint: "{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --repo-path {WORKTREE} --files \"<space-separated>\""
|
|
4
5
|
context: fork
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Pre-implementation context gathering — surfaces historical findings, bug patterns, and related code paths before the builder writes any code
|
|
3
4
|
argument-hint: "{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG} --repo-path {WORKTREE} --files \"<space-separated>\" [--functions \"<names>\"]"
|
|
4
5
|
context: fork
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Implementation agent — writes code, makes commits, posts builder comment
|
|
3
4
|
argument-hint: "{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --worktree {WORKTREE} --branch {BRANCH} --base {PR_BASE} [--fix-acceptance \"<failed checks>\"]"
|
|
4
5
|
context: fork
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Validation agent — quality gate loop, format/verify, proxy check, deploy check
|
|
3
4
|
argument-hint: "{NUMBER} --repo {GH_REPO} --gh-flag \"{GH_FLAG}\" --worktree {WORKTREE} --branch {BRANCH} --base {PR_BASE} --files \"<changed files>\""
|
|
4
5
|
context: fork
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Close subcommand — update project board, final issue body, parent tracker, summary report, trajectory log
|
|
3
4
|
argument-hint: "[issue number] [--repo GH_REPO] [--gh-flag GH_FLAG] [--pr PR_NUMBER] [--base PR_BASE] [--branch BRANCH] [--worktree WORKTREE_PATH] --terminal-state merged|investigation|decomposed|invalid"
|
|
4
5
|
context: fork
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
user-invocable: false
|
|
2
3
|
description: Remediate subcommand — checkout a needs-human PR, fix review findings, re-review, and re-gate with a FORGE:REMEDIATION paper trail
|
|
3
4
|
argument-hint: "[PR number] [--issue N] [--repo GH_REPO] [--gh-flag GH_FLAG] [--base PR_BASE]"
|
|
4
5
|
context: fork
|
package/package.json
CHANGED
|
@@ -284,6 +284,16 @@ if [ -d "$WORKFLOWS_DIR" ]; then
|
|
|
284
284
|
done < <(find "$WORKFLOWS_DIR" -name '*.yml' | sort)
|
|
285
285
|
fi
|
|
286
286
|
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
# Merged-trail hold must not be keyed on the verifier (forge#3169)
|
|
289
|
+
# ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
P4_SPEC="$COMMANDS_DIR/orchestrate/phase-4-execution.md"
|
|
292
|
+
if [ -f "$P4_SPEC" ] && grep -qE 'ABSENT\) +\[ "\$\(reverify_merged_trail "\$PRED"[^)]*\)" = "OK" \] *&& *echo "DONE"' "$P4_SPEC"; then
|
|
293
|
+
echo "HIGH | $P4_SPEC | merged + needs-human + ABSENT classifies DONE via the verifier (must fail closed to GATED)" >&2
|
|
294
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
295
|
+
fi
|
|
296
|
+
|
|
287
297
|
# ---------------------------------------------------------------------------
|
|
288
298
|
# Summary
|
|
289
299
|
# ---------------------------------------------------------------------------
|
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: "[BENCHMARK] Monolithic work-on — single-prompt pipeline with no Skill sub-phase boundaries"
|
|
3
|
-
argument-hint: "[issue number]"
|
|
4
|
-
install: internal
|
|
5
|
-
---
|
|
6
|
-
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
7
|
-
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
8
|
-
|
|
9
|
-
# /work-on-monolithic — Full Issue Pipeline (Single Prompt)
|
|
10
|
-
|
|
11
|
-
> **Skill names**: `{FORGE_SKILL_PREFIX}` is `forgedock:` (plugin install) or empty (`install.sh`), resolved once per run by `commands/work-on.md` § Skill Name Resolution. If the skill is not found under either name, STOP and report "skill not found" — never run the phase inline.
|
|
12
|
-
|
|
13
|
-
**Input**: $ARGUMENTS
|
|
14
|
-
|
|
15
|
-
Orchestrator for the full issue lifecycle: investigate → build → review → merge → close. GitHub issues are the persistent context layer — read existing comments before starting, write structured reports back, use `workflow:*` labels to track state.
|
|
16
|
-
|
|
17
|
-
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
18
|
-
**NEVER use plan mode (EnterPlanMode).**
|
|
19
|
-
|
|
20
|
-
> **Relationship to `/work-on` (canonical path)**: This file is a `[BENCHMARK]` reference variant. It documents the same inline-execution model that `/work-on` Phase 3 and `work-on/build.md` use as their **default** for STANDARD/fast-lane issues — all build sub-phases (context → architect → implement → validate) run inline in the current context window with no `Skill()` sub-agent spawns. The difference is that this file omits the worktree lifecycle, `FORGE:CONTRACT` posting, and the Spawn-Decision exception path for large builds. Use `/work-on` for production runs; use this file for baseline benchmarking or when comparing single-prompt token cost against the modular path. See `work-on/build.md` (Canonical Build Path section) for the reconciled build topology. <!-- Added: forge#1276 -->
|
|
21
|
-
**NEVER use the Agent tool** — this spec executes all phases inline as a single monolithic prompt. The Agent tool would spawn a subprocess outside this context, breaking the single-prompt contract and losing all accumulated pipeline state.
|
|
22
|
-
|
|
23
|
-
<!-- FORGE:SPEC_LOADED — work-on-monolithic.md loaded and active. Agent is bound by this spec. -->
|
|
24
|
-
|
|
25
|
-
### Compaction Resilience
|
|
26
|
-
|
|
27
|
-
1. Write state to GitHub after EVERY significant step
|
|
28
|
-
2. Re-read GitHub state at the START of each phase (don't rely on in-memory context)
|
|
29
|
-
3. After compaction: re-read issue (body + comments + labels) to reconstruct state
|
|
30
|
-
4. Key principle: A NEW session running this command should pick up where the last left off by reading GitHub state alone
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Pipeline Rules
|
|
35
|
-
|
|
36
|
-
- **NEVER merge to main.** PRs target `staging` (fast lane) or `milestone/{slug}` (feature lane).
|
|
37
|
-
- **`Closes #N` does not auto-close for non-default-branch PRs.** You MUST explicitly `gh issue close`.
|
|
38
|
-
- **Review findings are NOT merge blockers.** They become separate issues.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Project Configuration
|
|
43
|
-
|
|
44
|
-
Read `forge.yaml` from the repository root. If missing, tell the user to run `npx forgedock init` and stop.
|
|
45
|
-
|
|
46
|
-
Resolve the following variables from `forge.yaml` before running any pipeline phase:
|
|
47
|
-
|
|
48
|
-
| Variable | Source in forge.yaml | Description |
|
|
49
|
-
|----------|----------------------|-------------|
|
|
50
|
-
| `GH_REPO` | `project.owner` + `project.repo` | Full GitHub repo identifier (`owner/repo`) |
|
|
51
|
-
| `REPO_PATH` | `paths.root` | Absolute path to the local repository |
|
|
52
|
-
| `STAGING_BRANCH` | `branches.staging` | Branch for fast-lane PRs (no milestone) |
|
|
53
|
-
| `WORKTREE_BASE` | `paths.worktree_base` | Directory where git worktrees are created |
|
|
54
|
-
| `GH_FLAG` | _(empty for default repo; `-R owner/repo` for satellites)_ | Extra flag for `gh` CLI satellite repo calls |
|
|
55
|
-
|
|
56
|
-
**Multi-repo routing** (optional): If `forge.yaml` contains a `repos.satellites` list, build the prefix routing table from it. Each satellite entry has `prefix`, `repo`, `staging_branch`, and `local_path`. Issues without a prefix route to the default `GH_REPO`. Satellite repos have no staging — their fast-lane PRs go to their configured `staging_branch` (typically `main`).
|
|
57
|
-
|
|
58
|
-
If no `repos.satellites` section exists, only the primary repo is available — all issues route to `GH_REPO`.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Phase 0: Resolve Issue & Load Context
|
|
63
|
-
|
|
64
|
-
### 0A: Parse input
|
|
65
|
-
Extract project prefix and issue number.
|
|
66
|
-
|
|
67
|
-
### 0B: Load issue + existing context
|
|
68
|
-
```bash
|
|
69
|
-
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,comments,milestone
|
|
70
|
-
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --jq '.[] | {id: .id, author: .user.login, body: .body}'
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**Check**: state (closed → STOP), terminal labels (`workflow:merged`/`workflow:invalid` → STOP), existing agent comments (`FORGE:INVESTIGATOR`, `FORGE:CONTRACT`, `FORGE:BUILDER`, `FORGE:TRAJECTORY`), parent tracker status.
|
|
74
|
-
|
|
75
|
-
**Determine resume point**: No comments → Phase 1. Investigation exists + ready-to-build → Phase 3. Builder:COMPLETE + no PR → Phase 4. Builder without :COMPLETE (partial/interrupted build) + no PR → Phase 3 (partial-build cleanup). Builder + PR open → Phase 5. PR merged + issue open → close issue.
|
|
76
|
-
|
|
77
|
-
**Classify lane**: Milestone → feature lane (`milestone/{slug}`). No milestone → fast lane (`staging`).
|
|
78
|
-
|
|
79
|
-
**Source branch for review-findings**: Parse `**Code branch**: \`{branch}\`` from body. Branch from there, not main.
|
|
80
|
-
|
|
81
|
-
### 0C: Sync to Project board
|
|
82
|
-
Add issue to project, set Status=In Progress, Lane, Component, Priority, Workflow=Investigating.
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## Phase 1: Investigation
|
|
87
|
-
|
|
88
|
-
**Skip if**: `<!-- FORGE:INVESTIGATOR -->` exists with `<!-- INVESTIGATION:COMPLETE -->`.
|
|
89
|
-
|
|
90
|
-
### 1A: Set label
|
|
91
|
-
```bash
|
|
92
|
-
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:investigating"
|
|
93
|
-
gh issue edit {NUMBER} {GH_FLAG} --remove-label "workflow:ready-to-build,workflow:building,workflow:in-review" 2>/dev/null || true
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### 1B: Investigate the issue
|
|
97
|
-
|
|
98
|
-
Mission: Validate whether the issue is real. Assume description is wrong until proven otherwise.
|
|
99
|
-
|
|
100
|
-
**Domain file hints** (start search here):
|
|
101
|
-
|
|
102
|
-
Configure domain-to-file mappings for your project in `forge.yaml → review.key_paths`. The table below shows the standard layout — override any entry that doesn't match your codebase:
|
|
103
|
-
|
|
104
|
-
| Domain | Default key files (override via forge.yaml) |
|
|
105
|
-
|--------|---------------------------------------------|
|
|
106
|
-
| BILLING | `routers/billing.py`, `core/pricing.py`, `services/payment_service.py` |
|
|
107
|
-
| WORKER | `worker/main.py`, `worker/queues.py`, `worker/tasks.py` |
|
|
108
|
-
| AUTH | `core/auth.py`, `routers/auth.py`, `dependencies.py` |
|
|
109
|
-
| DATABASE | `infra/migrations/`, `models/`, `db/` |
|
|
110
|
-
| FRONTEND | `web/src/app/`, `web/src/components/`, `web/src/lib/` |
|
|
111
|
-
| AI | `services/ai_client.py`, `routers/ai.py` |
|
|
112
|
-
| INFRA | `.github/workflows/`, `docker-compose.yml`, `infra/traefik/` |
|
|
113
|
-
|
|
114
|
-
If `forge.yaml → review.key_paths` is present, use those mappings instead of the defaults above.
|
|
115
|
-
|
|
116
|
-
**Steps**: Check right branch → read domain files → verify claims → git blame → determine root cause → identify affected files → validate proposed fixes against full system stack.
|
|
117
|
-
|
|
118
|
-
**Fix-Approach Validation**: If issue proposes a fix, don't adopt it as spec. Trace through target system's middleware, auth, routing.
|
|
119
|
-
|
|
120
|
-
**Domain Context Discovery** (narrow scope, max 5 files):
|
|
121
|
-
```bash
|
|
122
|
-
git log --oneline --all -30 -- {affected_files} | grep -oP '#\d+' | sort -u
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Output format**: Verdict (CONFIRMED/PARTIAL/INVALID), Confidence, Severity, Task Type, What Was Claimed, What We Found, Root Cause, Affected Files, Evidence, Recommendation.
|
|
126
|
-
|
|
127
|
-
### 1C: Post investigation comment
|
|
128
|
-
```bash
|
|
129
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
|
|
130
|
-
<!-- INVESTIGATION:COMPLETE -->
|
|
131
|
-
## Investigation Report
|
|
132
|
-
{report}"
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### 1D: Update labels
|
|
136
|
-
CONFIRMED/PARTIAL → add `workflow:ready-to-build`, continue to Phase 3.
|
|
137
|
-
INVALID → close issue, add `workflow:invalid`, STOP.
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## Phase 3: Build
|
|
142
|
-
|
|
143
|
-
**Skip if**: `<!-- FORGE:BUILDER -->` exists.
|
|
144
|
-
|
|
145
|
-
### 3A: Re-read state from GitHub (MANDATORY)
|
|
146
|
-
```bash
|
|
147
|
-
gh issue view {NUMBER} {GH_FLAG} --json body,labels
|
|
148
|
-
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body'
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### 3B: Classify task type
|
|
152
|
-
|
|
153
|
-
| Signal | Type | Approach |
|
|
154
|
-
|--------|------|----------|
|
|
155
|
-
| UI/UX, feature + web/ files | UI/UX | `frontend-design` skill |
|
|
156
|
-
| Feature + services/ | Backend Feature | Implement directly |
|
|
157
|
-
| Feature + both | Full-Stack | Backend first, then frontend-design |
|
|
158
|
-
| Bug + web/ | Frontend Fix | Direct |
|
|
159
|
-
| Bug + services/ | Backend Fix | Direct |
|
|
160
|
-
| Refactor/docs | Maintenance | Direct |
|
|
161
|
-
|
|
162
|
-
Then classify COMPLEXITY_BAND with the same table as `work-on/build.md` B0.5 (TRIVIAL: single doc/config file, no logic; STANDARD: 1–5 files, existing patterns; COMPLEX: 6+ files, new abstractions, cross-service, schema changes) and post the `FORGE:FAST_PATH` marker. `scripts/verify-phase-trail.sh` reads the band from it and fails the review-pr merge gate when it is absent (forge#3148):
|
|
163
|
-
|
|
164
|
-
```bash
|
|
165
|
-
FP_BODY="<!-- FORGE:FAST_PATH -->
|
|
166
|
-
## Fast-Path Classification
|
|
167
|
-
|
|
168
|
-
**COMPLEXITY_BAND**: {TRIVIAL|STANDARD|COMPLEX}
|
|
169
|
-
**Task type**: {TASK_TYPE}
|
|
170
|
-
**Affected file count**: {N}
|
|
171
|
-
**Rationale**: {one-sentence explanation of classification decision}
|
|
172
|
-
**Phases skipped**: {3C.5, 3C.6 for TRIVIAL | none — full pipeline}"
|
|
173
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "$FP_BODY" # <!-- allowlist:check-command-side-effects -->
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### 3C: Builder Contract (MANDATORY)
|
|
177
|
-
Post `<!-- FORGE:CONTRACT -->` comment with: task type, proposed approach, deliverables table (file/change/why), acceptance criteria, quality considerations.
|
|
178
|
-
|
|
179
|
-
### 3C.5: Context Gathering (max 2 minutes — MANDATORY for STANDARD/COMPLEX, skip for TRIVIAL)
|
|
180
|
-
|
|
181
|
-
Surface institutional memory before writing code. For each affected file:
|
|
182
|
-
```bash
|
|
183
|
-
# Recent agent work on these files
|
|
184
|
-
git log --oneline --all -30 -- {affected_files} | grep -oP '#\d+' | sort -u | head -5
|
|
185
|
-
# Check for review findings on same files
|
|
186
|
-
for ISSUE_NUM in {related_issue_numbers}; do
|
|
187
|
-
gh api repos/{GH_REPO}/issues/${ISSUE_NUM}/comments --jq '.[] | select(.body | contains("FORGE:")) | .body' 2>/dev/null | head -20
|
|
188
|
-
done
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Post `<!-- FORGE:CONTEXT -->` comment with findings. For STANDARD/COMPLEX it is required even when no relevant history is found (say so in the comment): the merge gate checks for it.
|
|
192
|
-
|
|
193
|
-
### 3C.6: Architecture Plan (MANDATORY for STANDARD/COMPLEX — skip for TRIVIAL)
|
|
194
|
-
|
|
195
|
-
For multi-file changes, trace ALL affected code paths before writing code:
|
|
196
|
-
- Map the call chain: entry point → middleware → handler → service → model → response
|
|
197
|
-
- Identify every file that needs to change
|
|
198
|
-
- Order implementation steps to avoid broken intermediate states
|
|
199
|
-
|
|
200
|
-
Post `<!-- FORGE:ARCHITECT -->` comment with the implementation plan.
|
|
201
|
-
|
|
202
|
-
Skip this step only when COMPLEXITY_BAND is TRIVIAL. For STANDARD/COMPLEX, the merge gate checks for the `FORGE:ARCHITECT` comment.
|
|
203
|
-
|
|
204
|
-
### 3D: Set building label
|
|
205
|
-
```bash
|
|
206
|
-
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:building" --remove-label "workflow:ready-to-build,workflow:investigating"
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### 3E: Create worktree
|
|
210
|
-
Branch slug from title (lowercase, hyphenated, max 40 chars). Prefix: `fix/` (bugs) or `feat/` (features).
|
|
211
|
-
- Review-finding → branch from `origin/{SOURCE_BRANCH}`
|
|
212
|
-
- Fast lane → branch from `origin/{STAGING_BRANCH}`
|
|
213
|
-
- Feature lane → branch from `origin/milestone/{slug}`
|
|
214
|
-
|
|
215
|
-
```bash
|
|
216
|
-
cd {REPO_PATH}
|
|
217
|
-
git fetch origin
|
|
218
|
-
BRANCH="fix/{slug}-{NUMBER}"
|
|
219
|
-
git worktree add {WORKTREE_BASE}/{BRANCH} -b {BRANCH} origin/{PR_BASE}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### 3F: Implement
|
|
223
|
-
Route by task type. Read the investigation report and contract. Implement the fix/feature.
|
|
224
|
-
|
|
225
|
-
### 3F.5: Quality Gate
|
|
226
|
-
Invoke quality-gate on changed files:
|
|
227
|
-
```
|
|
228
|
-
Skill(skill="{FORGE_SKILL_PREFIX}quality-gate", args="{changed_files} --worktree {WORKTREE_PATH}")
|
|
229
|
-
```
|
|
230
|
-
Fix HIGH/MEDIUM findings. Max 2 iterations. Skip for 1-file config/docs edits.
|
|
231
|
-
|
|
232
|
-
Then post the `FORGE:QUALITY_GATE` marker with the real gate result (same contract as `work-on/build/validate.md` V1/V5). Post it even when the gate was skipped (`**Result**: PASS (skipped — single config/docs file)`). The merge gate requires it unless the whole diff is docs-only (forge#3148):
|
|
233
|
-
|
|
234
|
-
```bash
|
|
235
|
-
QG_BODY="<!-- FORGE:QUALITY_GATE -->
|
|
236
|
-
## Quality Gate Result
|
|
237
|
-
|
|
238
|
-
**Result**: {PASS|FAIL}
|
|
239
|
-
**Iterations**: {N}
|
|
240
|
-
**Commands run**: {quality-gate invocation actually executed}
|
|
241
|
-
**Findings remaining**: {none | summary}"
|
|
242
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "$QG_BODY" # <!-- allowlist:check-command-side-effects -->
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
If the gate still reports HIGH/MEDIUM findings after 2 iterations, post `**Result**: FAIL`, add `needs-human`, and STOP.
|
|
246
|
-
|
|
247
|
-
### 3G: Format and verify
|
|
248
|
-
- Python: `black` + `isort` + `py_compile`
|
|
249
|
-
- TypeScript (primary repo): `prettier --write` + `tsc --noEmit` (BLOCKING if fails)
|
|
250
|
-
- TypeScript (satellite): `npm run build` + `prettier --write`
|
|
251
|
-
|
|
252
|
-
### 3H: Frontend proxy wiring check (MANDATORY)
|
|
253
|
-
All client-side fetch/useSWR/apiFetch must use `/api/...` proxy routes, NEVER `/api/v1/...` directly.
|
|
254
|
-
|
|
255
|
-
### 3I: Deployment completeness check (MANDATORY)
|
|
256
|
-
Skip if no new env vars introduced.
|
|
257
|
-
|
|
258
|
-
For each new env var, verify present in ALL required locations:
|
|
259
|
-
|
|
260
|
-
| Location | Required for |
|
|
261
|
-
|----------|-------------|
|
|
262
|
-
| `.env.example` | All new vars |
|
|
263
|
-
| Secrets backend (see `deploy.secrets_backend`) | Secret vars — skip if backend is `none` or unset |
|
|
264
|
-
| `app/env_validation.py` | API service vars (if project has one) |
|
|
265
|
-
|
|
266
|
-
**Secrets backend check** *(trigger: `deploy.secrets_backend == "sops"`)*:
|
|
267
|
-
|
|
268
|
-
If the project uses SOPS, verify the new var is present in all SOPS chain locations:
|
|
269
|
-
- `infra/secrets/prod.enc.yaml` — SOPS-encrypted secret store
|
|
270
|
-
- `infra/decrypt-secrets.sh` ENV_MAPPING — maps SOPS key to env var name
|
|
271
|
-
|
|
272
|
-
If `deploy.secrets_backend` is absent or not `sops`, skip these checks and log:
|
|
273
|
-
> `SKIP: SOPS chain check — deploy.secrets_backend is not "sops". Configure deploy.secrets_backend in forge.yaml to enable.`
|
|
274
|
-
|
|
275
|
-
### 3J: Commit
|
|
276
|
-
Conventional prefix (fix/feat/refactor/docs). Reference #{NUMBER} in the commit message.
|
|
277
|
-
|
|
278
|
-
### 3K: Post implementation comment
|
|
279
|
-
Post `<!-- FORGE:BUILDER -->` with: branch, commit, files changed, approach, changes list, testing checklist.
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
## Phase 4: PR Creation
|
|
284
|
-
|
|
285
|
-
### 4A: Push branch
|
|
286
|
-
```bash
|
|
287
|
-
cd {WORKTREE_PATH} && git push -u origin {BRANCH}
|
|
288
|
-
```
|
|
289
|
-
If fails: try `--force-with-lease`. If still fails: post comment, add `needs-human`, STOP.
|
|
290
|
-
|
|
291
|
-
### 4B: Determine PR target
|
|
292
|
-
No milestone → `staging`. Has milestone → `milestone/{slug}`. NEVER `main`.
|
|
293
|
-
|
|
294
|
-
### 4C: Create PR
|
|
295
|
-
```bash
|
|
296
|
-
gh pr create {GH_FLAG} --base {PR_BASE} --title "{Fix|Feat}: {description} (#{NUMBER})" --body "..."
|
|
297
|
-
```
|
|
298
|
-
Include `Closes #{NUMBER}`.
|
|
299
|
-
|
|
300
|
-
### 4D: Update labels
|
|
301
|
-
```bash
|
|
302
|
-
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:in-review" --remove-label "workflow:building"
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## Phase 5: Auto-Review
|
|
308
|
-
|
|
309
|
-
### 5A: Re-read state from GitHub (MANDATORY)
|
|
310
|
-
### 5B: Invoke /review-pr with --auto-merge
|
|
311
|
-
```
|
|
312
|
-
if DRY_RUN=true:
|
|
313
|
-
record "Would invoke review-pr --auto-merge for PR #{PR_NUMBER}; skipped (dry-run)."
|
|
314
|
-
else:
|
|
315
|
-
Skill(skill="{FORGE_SKILL_PREFIX}review-pr", args="{PR_NUMBER} --auto-merge --issue {NUMBER} --base {PR_BASE} --gh-flag {GH_FLAG}")
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### 5C: Verify merge and close (recovery)
|
|
319
|
-
|
|
320
|
-
**`REVIEW_RESULT: status: PHASE_TRAIL_FAILED`** (forge#3148): review-pr's Phase 8 refused the merge because a phase marker is missing. The PR is intentionally unmerged — do NOT run the `gh pr merge` recovery below, which would merge around the gate. Re-run each phase named on the `MISSING: <marker> -> <action>` lines (never hand-post a marker), then re-run 5B once. If it returns `PHASE_TRAIL_FAILED` again, add `needs-human` and STOP; review-pr has already posted the `FORGE:PHASE_TRAIL_FAILED` comment.
|
|
321
|
-
|
|
322
|
-
**`REVIEW_RESULT: status: BLOCKED` with a phase-trail or auto-merge-gate blocker** (any blocker containing "phase trail", or "auto-merge requires --issue"; forge#3147): the gate refused or could not run. Do NOT re-run phases and do NOT run the manual merge below. Add `needs-human` and STOP.
|
|
323
|
-
|
|
324
|
-
Otherwise:
|
|
325
|
-
- PR MERGED + issue CLOSED → proceed to Phase 6/7
|
|
326
|
-
- PR MERGED + issue OPEN → close issue manually
|
|
327
|
-
- PR NOT MERGED + review returned `REVIEW_RESULT: status: COMPLETE` → run `scripts/wait-ci-green.sh {PR_NUMBER} {GH_FLAG}` (resolved from the plugin root); only on exit 0 merge with `--merge --match-head-commit <the CI_GATE_HEAD it printed>` and close the issue. A non-zero gate (or a "ci gate" / "stale review" blocker) → do NOT merge; post the gate output, add `needs-human`, STOP. If the merge fails → post comment, add `needs-human`, STOP.
|
|
328
|
-
- PR NOT MERGED + any other, missing, or unparseable `REVIEW_RESULT` → do NOT merge (an unrecognized refusal must never fall through to a manual merge). Add `needs-human` and STOP.
|
|
329
|
-
|
|
330
|
-
### 5D: Project board update (Workflow=Merged, Status=Done)
|
|
331
|
-
|
|
332
|
-
### 5E: Final issue body update
|
|
333
|
-
Check off remaining items.
|
|
334
|
-
|
|
335
|
-
---
|
|
336
|
-
|
|
337
|
-
## Phase 6: Parent Tracker Update (Sub-Issues Only)
|
|
338
|
-
|
|
339
|
-
**Skip if**: Not a sub-issue.
|
|
340
|
-
|
|
341
|
-
Check off this sub-issue in parent body. If ALL sub-issues closed → close parent with `workflow:merged`.
|
|
342
|
-
|
|
343
|
-
---
|
|
344
|
-
|
|
345
|
-
## Phase 7: Summary & Trajectory
|
|
346
|
-
|
|
347
|
-
### 7A: Report
|
|
348
|
-
```
|
|
349
|
-
## Done: #{NUMBER} — {TITLE}
|
|
350
|
-
- Investigation: {verdict} ({confidence})
|
|
351
|
-
- Lane: {FAST/FEATURE}
|
|
352
|
-
- Fix: {branch} → PR #{PR_NUMBER} → merged to `{PR_BASE}`
|
|
353
|
-
- Files changed: {count}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### 7B: Trajectory Log (MANDATORY)
|
|
357
|
-
Post `<!-- FORGE:TRAJECTORY -->` comment with phase-by-phase results table, decisions, anomalies.
|
|
358
|
-
|
|
359
|
-
---
|
|
360
|
-
|
|
361
|
-
## Error Handling
|
|
362
|
-
|
|
363
|
-
- Worktree exists: reuse or clean up
|
|
364
|
-
- PR creation fails: check if branch pushed, if PR already exists
|
|
365
|
-
- Merge conflicts: report to user, do NOT auto-resolve
|
|
366
|
-
- gh CLI fails: check `gh auth status`
|
|
367
|
-
- Label missing: create it
|