forgedock 1.1.7 → 1.1.9
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 +10 -33
- package/bin/cinema.mjs +23 -5
- package/bin/engine/invariants.mjs +361 -0
- package/bin/engine-cli.mjs +137 -11
- package/bin/env-detect.mjs +249 -0
- package/bin/forge-utils.mjs +364 -4
- package/bin/forgedock.mjs +1064 -135
- package/bin/hooks/pre-tool-use.mjs +182 -0
- package/bin/hooks/session-start.mjs +68 -3
- package/bin/init-detect.mjs +101 -12
- package/bin/journey.mjs +917 -16
- package/bin/labels.json +10 -0
- package/bin/recall.mjs +557 -0
- package/bin/registry.mjs +91 -3
- package/bin/runner.mjs +38 -2
- package/bin/tui.mjs +49 -3
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +8 -8
- package/commands/audit.md +1 -1
- package/commands/autopilot.md +958 -77
- package/commands/ci-audit.md +1 -1
- package/commands/cleanup.md +139 -55
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/explain.md +1 -1
- package/commands/fix-ci.md +1 -1
- package/commands/forgedock-init.md +1 -1
- package/commands/incident-response.md +3 -3
- package/commands/issue.md +1 -1
- package/commands/optimize.md +586 -1
- package/commands/orchestrate/config.md +13 -3
- package/commands/orchestrate/phase-1-resolve.md +36 -26
- package/commands/orchestrate/phase-2-triage.md +1 -1
- package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
- package/commands/orchestrate/phase-3-dependency.md +587 -48
- package/commands/orchestrate/phase-4-execution.md +1031 -57
- package/commands/orchestrate/phase-5-cleanup.md +1 -1
- package/commands/orchestrate/phase-6-report.md +248 -11
- package/commands/orchestrate/safety.md +2 -2
- package/commands/pipeline-health/phase-2-metrics.md +305 -1
- package/commands/pipeline-health/phase-4-proposals.md +127 -0
- package/commands/qa-sweep.md +1 -1
- package/commands/quality-gate.md +418 -1
- package/commands/recover-orphans.md +5 -5
- package/commands/replay.md +1 -1
- package/commands/review-pr-agents/api.md +2 -2
- package/commands/review-pr-agents/auth.md +2 -2
- package/commands/review-pr-agents/billing.md +2 -2
- package/commands/review-pr-agents/concurrency.md +2 -2
- package/commands/review-pr-agents/database.md +2 -2
- package/commands/review-pr-agents/frontend.md +2 -2
- package/commands/review-pr-agents/infra.md +2 -2
- package/commands/review-pr-agents/protocols.md +1 -1
- package/commands/review-pr-agents/scraper.md +2 -2
- package/commands/review-pr-agents/security.md +2 -2
- package/commands/review-pr-staging.md +41 -3
- package/commands/review-pr.md +462 -19
- package/commands/rollback.md +1 -1
- package/commands/scope.md +1 -1
- package/commands/security-audit.md +1 -1
- package/commands/spec-doctor.md +429 -0
- package/commands/test-gate.md +1 -1
- package/commands/validate.md +53 -1
- package/commands/work-on/build/architect.md +78 -1
- package/commands/work-on/build/context.md +360 -22
- package/commands/work-on/build/implement.md +3 -1
- package/commands/work-on/build/validate.md +29 -1
- package/commands/work-on/build.md +75 -6
- package/commands/work-on/close.md +624 -10
- package/commands/work-on/decompose.md +1 -1
- package/commands/work-on/investigate.md +235 -5
- package/commands/work-on/remediate.md +331 -0
- package/commands/work-on/review.md +1 -1
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +205 -66
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +1 -1
- package/docs/CONFIG.md +186 -0
- package/docs/FORGE-PROTOCOL.md +101 -0
- package/docs/site/command-reference.md +31 -1
- package/docs/site/getting-started.md +9 -34
- package/docs/site/index.md +1 -1
- package/docs/spec/forge-protocol-v1.md +97 -5
- package/docs/spec/label-state-machine.md +6 -1
- package/package.json +3 -2
- package/packages/protocol/src/cli.js +374 -84
- package/packages/protocol/src/types.js +75 -1
- package/scripts/build-knowledge-index.mjs +1761 -0
- package/scripts/calibration.mjs +996 -0
- package/scripts/check-command-docs-drift.sh +4 -2
- package/scripts/danger-zones.mjs +789 -0
- package/scripts/doctor-pipeline-state.sh +88 -2
- package/scripts/select-fix-targets.sh +234 -0
- package/scripts/transition-label.sh +245 -6
- package/scripts/refresh-bot-token.sh +0 -97
package/commands/quality-gate.md
CHANGED
|
@@ -13,7 +13,7 @@ You are a quality gate that runs AFTER implementation but BEFORE commit. Your jo
|
|
|
13
13
|
|
|
14
14
|
You do NOT post to GitHub. You do NOT create issues. You return findings directly to the builder agent that spawned you.
|
|
15
15
|
|
|
16
|
-
**Agent model policy**: `model: "
|
|
16
|
+
**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.
|
|
17
17
|
**NEVER use plan mode (EnterPlanMode).**
|
|
18
18
|
|
|
19
19
|
<!-- FORGE:SPEC_LOADED — quality-gate.md loaded and active. Agent is bound by this spec. -->
|
|
@@ -53,6 +53,7 @@ This quality gate uses **domain detection** to adapt to your project's actual te
|
|
|
53
53
|
| FORGE_GRAPH | `commands/*.md` specs or `scripts/*` files change (ForgeDock self-consistency) |
|
|
54
54
|
| CONFIG_SCHEMA | Config files for external tools (`traefik/`, `infra/nginx/`, `k8s/`, `terraform/`, `*.conf`, `*.toml` in infra paths, or `docker-compose*.yml` with service definition changes) |
|
|
55
55
|
| BILLING | Files under `billing/`, `credits/`, `subscription`, `ledger`, or `payment` paths |
|
|
56
|
+
| WIRE_THROUGH | Any code file where the diff adds new conditional lines (`if`/`elif`/`else`/`guard`/`feature flag`) — triggers wire-through proof check <!-- Added: forge#1731 --> |
|
|
56
57
|
|
|
57
58
|
**Stack-agnostic coverage**: The example domains above reflect common patterns caught in production; they are not requirements. A Go or Ruby project benefits from SECURITY checks. A Node.js project benefits from FRONTEND, PROXY, and DEPLOY checks. Any project benefits from DATABASE and WORKFLOW checks when those file types are present. The stack-specific examples (Python routers, SOPS secrets chain, FastAPI layouts, appleboy SSH deploys) are illustrative — the domain detection system applies the applicable subset to any codebase.
|
|
58
59
|
|
|
@@ -107,6 +108,7 @@ Before running checks, classify the changed files into domains. This avoids runn
|
|
|
107
108
|
| `*.py` in `routers/` where the diff removes a line containing an or-gate condition (lines starting with `-` containing `if.*or`) | ROUTER_BUG |
|
|
108
109
|
| `commands/*.md` or `scripts/*` files (ForgeDock repo only — dogfoods its own spec graph) | FORGE_GRAPH |
|
|
109
110
|
| Files under `traefik/`, `infra/nginx/`, `k8s/`, `terraform/`, or files matching `*.conf`, `*.toml` in infra paths, or `docker-compose*.yml` with service definition changes | CONFIG_SCHEMA |
|
|
111
|
+
| Executable code files (`.py`, `.ts`, `.tsx`, `.js`, `.sh`) where the diff adds new `if`/`elif`/`else`/guard/feature-flag conditional lines — NOT `.md` prose spec files | WIRE_THROUGH *(universal — see 2G.8)* <!-- Added: forge#1731 --> |
|
|
110
112
|
|
|
111
113
|
**Apply the classification:**
|
|
112
114
|
|
|
@@ -212,6 +214,22 @@ for f in "${CHANGED_FILES_ARR[@]}"; do
|
|
|
212
214
|
esac
|
|
213
215
|
done
|
|
214
216
|
|
|
217
|
+
# Check for newly added conditional paths in code files — wire-through proof trigger <!-- Added: forge#1731 -->
|
|
218
|
+
# Scoped to NEWLY ADDED lines only (lines starting with '+' in the diff, excluding the '+++' header).
|
|
219
|
+
# Pre-existing conditionals are never flagged — only additions.
|
|
220
|
+
# Applies to executable code files ONLY (.py, .ts, .tsx, .js, .sh) — NOT .md prose spec files,
|
|
221
|
+
# which use 'if'/'else' in natural language and pseudocode that is never executed.
|
|
222
|
+
while IFS= read -r f; do
|
|
223
|
+
[ -z "$f" ] && continue
|
|
224
|
+
NEW_CONDITIONALS=$(git diff HEAD -- "$f" 2>/dev/null \
|
|
225
|
+
| grep -E '^\+' | grep -v '^+++' \
|
|
226
|
+
| grep -E '^\+\s*(if |elif |else:?\s*$|if\(|elif\()|\bguard\b|\bfeature.?flag\b|FEATURE_FLAG|ENABLE_[A-Z_]+\s*=|DISABLE_[A-Z_]+\s*=')
|
|
227
|
+
if [ -n "$NEW_CONDITIONALS" ]; then
|
|
228
|
+
DOMAINS="$DOMAINS WIRE_THROUGH"
|
|
229
|
+
break
|
|
230
|
+
fi
|
|
231
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.(py|ts|tsx|js|sh)$')
|
|
232
|
+
|
|
215
233
|
# SECURITY is always included for any code file
|
|
216
234
|
DOMAINS="SECURITY $DOMAINS"
|
|
217
235
|
|
|
@@ -246,7 +264,11 @@ Run ONLY the checks whose domain was identified in Step 1.5. Skip checks for dom
|
|
|
246
264
|
- **2P (External tool config schema)**: Run if `CONFIG_SCHEMA` in DOMAINS
|
|
247
265
|
- **2Q (Billing)**: Run if `BILLING` in DOMAINS
|
|
248
266
|
- **2R (Registry checks)**: ALWAYS run — executes all promoted checks from `scripts/check-registry/manifest.json` <!-- Added: forge#1331 -->
|
|
267
|
+
- **2R.5 (Adaptive gate.d checks)**: ALWAYS run when `adaptive_scripts.enabled` — executes `.forgedock/scripts/gate.d/*.sh`; each script is repo-specific and was promoted from a recurring finding pattern <!-- Added: forge#1739 -->
|
|
268
|
+
- **2G.8 (Wire-through proof)**: Run if `WIRE_THROUGH` in DOMAINS — demands each newly added conditional path be demonstrably reachable <!-- Added: forge#1731 -->
|
|
269
|
+
- **2G.9 (Danger-zone recurrence)**: Run if `FORGE_GRAPH` in DOMAINS — cross-references injected danger-zone rule cards from FORGE:CONTEXT against the diff; violations tagged `known-pattern-recurrence` (HIGH) <!-- Added: forge#1744 -->
|
|
249
270
|
- **2S (Test failure classification)**: Run if any Step 2 check invoked a test suite and it failed — classifies the failure as PRE_BROKEN, FLAKY, or REAL using `scripts/flaky-quarantine.sh` <!-- Added: forge#1336 -->
|
|
271
|
+
- **2T (Subscribed pattern card rules)**: ALWAYS run when `pattern_feeds.enabled` is true — injects Tier-B (warning-only) learned rules from subscribed exchange cards that have a `gate.d` check template matching any changed file's stack <!-- Added: forge#1746 -->
|
|
250
272
|
|
|
251
273
|
### 2A: Security (ALL files)
|
|
252
274
|
|
|
@@ -469,6 +491,30 @@ done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.(tsx?|jsx?)$' | grep -v
|
|
|
469
491
|
|
|
470
492
|
### 2G: Cross-service integration (shell scripts with curl/wget)
|
|
471
493
|
|
|
494
|
+
<!-- Added: forge#1739 — Tier-B learned-rules injection -->
|
|
495
|
+
|
|
496
|
+
**Tier-B learned-rules injection (SHELL domain)**: Before running host-header and localhost checks, load any repo-specific prose rules from `devdocs/learned-rules/SHELL.md`. These are Tier-B patterns — non-mechanizable defect descriptions that have recurred ≥3 times and been promoted by `/optimize` for inclusion in this domain scan. Inject the content as additional context for the LLM scan, bounded to ~200 tokens (approximately 30 lines). Skip gracefully if the file is absent.
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
# Tier-B learned-rules injection for SHELL domain
|
|
500
|
+
SHELL_LEARNED_RULES=""
|
|
501
|
+
SHELL_RULES_PATH="{WORKTREE_PATH}/devdocs/learned-rules/SHELL.md"
|
|
502
|
+
if [ -f "$SHELL_RULES_PATH" ]; then
|
|
503
|
+
# Bound to first 30 lines (~200 tokens) — enough for pattern descriptions
|
|
504
|
+
SHELL_LEARNED_RULES=$(head -30 "$SHELL_RULES_PATH" 2>/dev/null || true)
|
|
505
|
+
if [ -n "$SHELL_LEARNED_RULES" ]; then
|
|
506
|
+
echo "2G: Injecting Tier-B learned rules from devdocs/learned-rules/SHELL.md ($(wc -l < "$SHELL_RULES_PATH" 2>/dev/null || echo '?') total lines, first 30 loaded)"
|
|
507
|
+
# The learned rules are used as additional LLM scan context below.
|
|
508
|
+
# Each rule entry is a prose description of a recurrent defect class.
|
|
509
|
+
# When scanning shell scripts below, treat any pattern matching a
|
|
510
|
+
# learned-rule description as a MEDIUM finding (advisory, not blocking)
|
|
511
|
+
# unless the rule explicitly marks it HIGH.
|
|
512
|
+
fi
|
|
513
|
+
fi
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Apply Tier-B learned rules**: For each changed `*.sh` file, if `SHELL_LEARNED_RULES` is non-empty, perform an additional LLM-assisted scan: read the shell file diff and the injected rules, emit any matching findings as `SHELL-LEARNED-{slug} | {SEVERITY} | {file} | {rule description}`. This scan is bounded — read only the diff lines (not the full file) and the injected rule content. Skip if no rule content was loaded.
|
|
517
|
+
|
|
472
518
|
For shell scripts that make HTTP requests to internal services:
|
|
473
519
|
1. Read the target service's middleware to verify the Host header will be accepted
|
|
474
520
|
2. Check that auth tokens are included if the endpoint requires them
|
|
@@ -572,6 +618,155 @@ fi
|
|
|
572
618
|
|
|
573
619
|
Report each conflict as **HIGH** — a shadowed native command is a silent user-facing regression (the native command becomes unreachable without warning). The builder must rename the conflicting file before the commit. Blocklist updates are in `scripts/check-native-conflicts.sh` (NATIVE_COMMANDS array).
|
|
574
620
|
|
|
621
|
+
### 2G.8: Wire-through proof for newly added conditional paths
|
|
622
|
+
|
|
623
|
+
<!-- Added: forge#1731 -->
|
|
624
|
+
|
|
625
|
+
**Triggered when**: `WIRE_THROUGH` in DOMAINS — the diff adds new conditional lines (`if`/`elif`/`else`/guard/feature-flag) in executable code files (`.py`, `.ts`, `.tsx`, `.js`, `.sh`). Markdown spec files are explicitly excluded — prose docs use `if`/`else` as natural language, not executable conditionals.
|
|
626
|
+
|
|
627
|
+
**Why this matters**: A recurring defect class ships dead-on-arrival: guards, feature flags, validators, and error branches that are added to the codebase but never actually execute. Examples: #1230 (orchestrate Layer 5 `LAYER1_FILES` never populated — dead code), #1522 (validator pointed at wrong path — permanent no-op), #1244 (guard added to fix Layer 5 itself never fires), #1580 (hook reads wrong transcript schema). Nothing in the quality gate previously asked the one question that kills this class: *prove this new conditional actually runs.*
|
|
628
|
+
|
|
629
|
+
**Scope boundary — newly added lines ONLY**: This check scans lines beginning with `+` in the diff (excluding `+++` file-header lines). Pre-existing conditionals are **never** flagged. A conditional that existed before this diff is outside scope regardless of whether it has tests.
|
|
630
|
+
|
|
631
|
+
**Acceptance criteria for each new conditional**:
|
|
632
|
+
|
|
633
|
+
Each newly added `if`/`elif`/`else` block or guard construct must satisfy AT LEAST ONE of:
|
|
634
|
+
|
|
635
|
+
1. **Test present in the diff**: The same diff (or any test file in the diff) contains a call that exercises the guarded path — a test function, an invocation with a parameter that triggers the condition, or an assertion on the conditional branch's output.
|
|
636
|
+
2. **Execution trace annotation**: The new conditional line is immediately preceded or followed by a comment `# WIRE:PROVEN — <method>` where `<method>` describes how the path was verified to fire (e.g., `# WIRE:PROVEN — manual: ran with --debug flag, confirmed guard triggered`).
|
|
637
|
+
3. **Trivial re-guard** (auto-exempt): The conditional is a null-check, length-check, or type-check on a value already validated upstream on the same code path AND the new condition's body is a `return`, `continue`, `break`, `pass`, or single-line assignment with no side effects. These are defensive re-guards with no new behavior.
|
|
638
|
+
|
|
639
|
+
**Check logic**:
|
|
640
|
+
|
|
641
|
+
```bash
|
|
642
|
+
# WIRE_THROUGH check — runs when WIRE_THROUGH in DOMAINS
|
|
643
|
+
WIRE_THROUGH_FINDINGS=""
|
|
644
|
+
|
|
645
|
+
while IFS= read -r f; do
|
|
646
|
+
[ -z "$f" ] && continue
|
|
647
|
+
# Extract newly added conditional lines from the diff (+ lines, not +++ header)
|
|
648
|
+
NEW_CONDS=$(git diff HEAD -- "$f" 2>/dev/null \
|
|
649
|
+
| grep -E '^\+' | grep -v '^+++' \
|
|
650
|
+
| grep -nE '\bif\b|\belif\b|\belse\b|guard|feature.?flag|FEATURE_FLAG|ENABLE_|DISABLE_')
|
|
651
|
+
|
|
652
|
+
[ -z "$NEW_CONDS" ] && continue
|
|
653
|
+
|
|
654
|
+
# For each new conditional line, check for acceptance criteria
|
|
655
|
+
while IFS= read -r cond_line; do
|
|
656
|
+
[ -z "$cond_line" ] && continue
|
|
657
|
+
LINENO=$(echo "$cond_line" | cut -d: -f1 | tr -d '+')
|
|
658
|
+
|
|
659
|
+
# Criterion 3 auto-exempt: trivial re-guard (null/len/type check with no-side-effect body)
|
|
660
|
+
COND_TEXT=$(echo "$cond_line" | sed 's/^+//')
|
|
661
|
+
if echo "$COND_TEXT" | grep -qE '\bis (None|null|undefined)\b|\blen\s*\(|\bisinstance\s*\(|\btypeof\b|\bif not\b|\bif !\b'; then
|
|
662
|
+
# Check the body is trivial (next line is return/continue/break/pass/simple assign)
|
|
663
|
+
# Use git diff context to read adjacent lines — simplified: check annotation only
|
|
664
|
+
NEXT_CONTEXT=$(git diff HEAD -- "$f" 2>/dev/null | grep -A2 "^+${COND_TEXT}" | tail -2)
|
|
665
|
+
if echo "$NEXT_CONTEXT" | grep -qE '^\+\s*(return|continue|break|pass|[a-zA-Z_]+ = [^(]+$)'; then
|
|
666
|
+
continue # Auto-exempt trivial re-guard
|
|
667
|
+
fi
|
|
668
|
+
fi
|
|
669
|
+
|
|
670
|
+
# Criterion 2: WIRE:PROVEN annotation present in diff near this line
|
|
671
|
+
if git diff HEAD -- "$f" 2>/dev/null | grep -qE '^\+.*#\s*WIRE:PROVEN'; then
|
|
672
|
+
continue # Annotated — accepted
|
|
673
|
+
fi
|
|
674
|
+
|
|
675
|
+
# Criterion 1: test exercising this path present anywhere in the diff
|
|
676
|
+
# Look for test patterns added in the diff (test_, def test, it(, describe(, assert)
|
|
677
|
+
DIFF_HAS_TEST=$(git diff HEAD -- 2>/dev/null | grep -E '^\+' | grep -v '^+++' \
|
|
678
|
+
| grep -E 'def test_|test\(|it\(|describe\(|assert |expect\(' | head -1)
|
|
679
|
+
if [ -n "$DIFF_HAS_TEST" ]; then
|
|
680
|
+
continue # Test present in diff — accepted
|
|
681
|
+
fi
|
|
682
|
+
|
|
683
|
+
# None of the criteria met — emit a finding
|
|
684
|
+
WIRE_THROUGH_FINDINGS="${WIRE_THROUGH_FINDINGS}
|
|
685
|
+
WIRE_THROUGH | HIGH | $f:${LINENO:-?} | Newly added conditional '$(echo "$COND_TEXT" | xargs | head -c 80)' has no wire-through proof. Add a test that triggers this path, OR annotate with '# WIRE:PROVEN — <method>', OR verify it qualifies as a trivial re-guard (null/len/type check with no-side-effect body)."
|
|
686
|
+
done <<< "$NEW_CONDS"
|
|
687
|
+
done < <(echo {CHANGED_FILES} | tr ' ' '\n' | grep -E '\.(py|ts|tsx|js|sh)$')
|
|
688
|
+
|
|
689
|
+
# Emit findings
|
|
690
|
+
if [ -n "$WIRE_THROUGH_FINDINGS" ]; then
|
|
691
|
+
echo "$WIRE_THROUGH_FINDINGS"
|
|
692
|
+
fi
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Severity**: **HIGH** — a guard that never fires is functionally equivalent to dead code and defeats the purpose of the defensive check. The review agent has consistently flagged this class (see motivation issues above). Blocking at gate eliminates the 1–2 week detection lag.
|
|
696
|
+
|
|
697
|
+
**Auto-exempt patterns** (builder does not need to prove these):
|
|
698
|
+
- Null/None/undefined checks where body is `return`/`continue`/`break`/`pass`
|
|
699
|
+
- Length checks where body is a single-line assignment or early return
|
|
700
|
+
- Type checks (`isinstance`, `typeof`) where body is a single-line coercion
|
|
701
|
+
|
|
702
|
+
**Escaping the check legitimately**:
|
|
703
|
+
- Add a test in the same diff that exercises the conditional
|
|
704
|
+
- Add `# WIRE:PROVEN — <method>` immediately before or after the new conditional
|
|
705
|
+
- Ensure the conditional qualifies as a trivial re-guard (see criterion 3 above)
|
|
706
|
+
|
|
707
|
+
Do NOT suppress the finding by making the check less strict or adding fake no-op tests. The intent is a real proof that the path is reachable. <!-- Added: forge#1731 -->
|
|
708
|
+
|
|
709
|
+
### 2G.9: Danger-zone recurrence check (FORGE_GRAPH domain) <!-- Added: forge#1744 -->
|
|
710
|
+
|
|
711
|
+
**Triggered when**: `FORGE_GRAPH` in DOMAINS (changed files include `commands/*.md` specs or `scripts/*` files). Skips silently when the FORGE:CONTEXT comment on the issue contains no `### Danger-Zone Rule Cards` section (i.e., Phase C0.5 injected zero cards — nothing to cross-reference).
|
|
712
|
+
|
|
713
|
+
**Why this matters**: When Phase C0.5 injects danger-zone rule cards into the builder's context, those cards represent the highest-value risk knowledge for the files being changed. A violation of an injected rule card is the most embarrassing category of pipeline failure — the system knew about the recurring pattern and the builder violated it anyway. Detecting this at the quality gate (pre-merge) rather than at review (post-PR) is the highest-leverage intervention point. Each violation tagged `known-pattern-recurrence` feeds directly into pipeline-health metrics for A/B evaluation (cards-on vs cards-off first-review finding rate).
|
|
714
|
+
|
|
715
|
+
```bash
|
|
716
|
+
# 2G.9: Danger-zone recurrence check
|
|
717
|
+
# Step 1: Read injected danger-zone cards from FORGE:CONTEXT comment on the issue
|
|
718
|
+
CONTEXT_COMMENT=$(gh api repos/{GH_REPO}/issues/{ISSUE_NUMBER}/comments \
|
|
719
|
+
--jq '[.[] | select(.body | contains("<!-- FORGE:CONTEXT -->"))] | last | .body // ""' 2>/dev/null \
|
|
720
|
+
| timeout 10 cat || echo '')
|
|
721
|
+
|
|
722
|
+
# Extract the Danger-Zone Rule Cards section
|
|
723
|
+
DZ_SECTION=$(echo "$CONTEXT_COMMENT" \
|
|
724
|
+
| sed -n '/^### Danger-Zone Rule Cards/,/^### /p' \
|
|
725
|
+
| head -30)
|
|
726
|
+
|
|
727
|
+
if [ -z "$DZ_SECTION" ] || ! echo "$DZ_SECTION" | grep -q '^- '; then
|
|
728
|
+
echo "2G.9: no injected danger-zone cards in FORGE:CONTEXT — skipping recurrence check"
|
|
729
|
+
else
|
|
730
|
+
echo "2G.9: checking diff against injected danger-zone rule cards..."
|
|
731
|
+
|
|
732
|
+
# Extract pattern keywords from each card (text after "recurring: " up to " (" or ";")
|
|
733
|
+
# Card format: {file} — {N} findings/90d — recurring: {pattern} (#{issue}); rule: {prevention}
|
|
734
|
+
CARD_PATTERNS=$(echo "$DZ_SECTION" \
|
|
735
|
+
| grep '^- ' \
|
|
736
|
+
| sed -n 's/.*recurring: \([^;(]*\).*/\1/p' \
|
|
737
|
+
| sed 's/[[:space:]]*$//' \
|
|
738
|
+
| grep -v '^$' \
|
|
739
|
+
| head -10)
|
|
740
|
+
|
|
741
|
+
if [ -z "$CARD_PATTERNS" ]; then
|
|
742
|
+
echo "2G.9: no pattern keywords extracted from cards — skipping"
|
|
743
|
+
else
|
|
744
|
+
# Step 2: Read the diff and check for pattern keyword matches
|
|
745
|
+
DIFF_CONTENT=$(cd {WORKTREE_PATH} && git diff --cached 2>/dev/null || git diff HEAD 2>/dev/null)
|
|
746
|
+
|
|
747
|
+
while IFS= read -r pattern; do
|
|
748
|
+
[ -z "$pattern" ] && continue
|
|
749
|
+
# Escape pattern for grep (strip regex-unsafe chars, use literal substring match)
|
|
750
|
+
SAFE_PATTERN=$(echo "$pattern" | head -c 60 | sed 's/[.*+?^${}()|[\]\\]/\\&/g')
|
|
751
|
+
[ -z "$SAFE_PATTERN" ] && continue
|
|
752
|
+
|
|
753
|
+
# Check if the diff introduces lines matching the pattern (added lines only)
|
|
754
|
+
MATCH=$(echo "$DIFF_CONTENT" | grep -E '^\+' | grep -v '^+++' | grep -i "$SAFE_PATTERN" | head -3)
|
|
755
|
+
if [ -n "$MATCH" ]; then
|
|
756
|
+
MATCH_PREVIEW=$(echo "$MATCH" | head -1 | head -c 120)
|
|
757
|
+
echo "FORGE_GRAPH | HIGH | known-pattern-recurrence | Diff matches injected rule card pattern '${pattern}': ${MATCH_PREVIEW} — this is a known-pattern-recurrence: the pipeline injected this rule card and the implementation violated it anyway. Fix before merging."
|
|
758
|
+
fi
|
|
759
|
+
done <<< "$CARD_PATTERNS"
|
|
760
|
+
|
|
761
|
+
echo "2G.9: recurrence check complete"
|
|
762
|
+
fi
|
|
763
|
+
fi
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
**Severity**: **HIGH** — `known-pattern-recurrence` is the highest embarrassment class. The pipeline had the risk knowledge in context and the implementation violated it regardless. This represents a failure of the context injection mechanism's effectiveness, and every recurrence degrades the A/B signal that measures whether memory injection changes agent behavior. The builder must fix the violation before the gate can pass.
|
|
767
|
+
|
|
768
|
+
**Note**: This check fires only for FORGE_GRAPH domain changes (ForgeDock spec files). For application code changes, violations of injected rule cards are surfaced by the review agents in Phase 5 — the quality gate cross-reference here is specific to pipeline spec changes where the FORGE:CONTEXT from the issue is the definitive context source. <!-- Added: forge#1744 -->
|
|
769
|
+
|
|
575
770
|
### 2H: Asyncio cancellation safety (Python async code)
|
|
576
771
|
|
|
577
772
|
**Triggered when**: changed Python files contain `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel`.
|
|
@@ -1165,6 +1360,228 @@ If any registry check produces findings, include them in the Step 3 findings lis
|
|
|
1165
1360
|
|
|
1166
1361
|
---
|
|
1167
1362
|
|
|
1363
|
+
## Step 2R.5: Adaptive gate.d checks (when adaptive_scripts.enabled)
|
|
1364
|
+
|
|
1365
|
+
<!-- Added: forge#1739 -->
|
|
1366
|
+
|
|
1367
|
+
**Purpose**: Run all repo-specific learned checks that have been promoted from recurring review findings into `.forgedock/scripts/gate.d/`. Unlike the static `scripts/check-registry/` (Step 2R), gate.d scripts live in the per-repo adaptive layer and are generated by `/optimize` from validated Ledger patterns. Each script represents a defect class that appeared ≥3 times in this specific codebase — it catches in milliseconds what previously required a full review cycle to detect.
|
|
1368
|
+
|
|
1369
|
+
**Triggered when**: `adaptive_scripts.enabled` is `true` in `forge.yaml` (default: true) AND the `gate.d/` subdirectory exists under `adaptive_scripts.directory`.
|
|
1370
|
+
|
|
1371
|
+
```bash
|
|
1372
|
+
# Read adaptive_scripts config from forge.yaml
|
|
1373
|
+
ADAPTIVE_ENABLED=$(yq '.adaptive_scripts.enabled // "true"' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null || echo 'true')
|
|
1374
|
+
ADAPTIVE_DIR_REL=$(yq '.adaptive_scripts.directory // ".forgedock/scripts"' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null || echo '.forgedock/scripts')
|
|
1375
|
+
ADAPTIVE_DIR="{WORKTREE_PATH}/${ADAPTIVE_DIR_REL}"
|
|
1376
|
+
|
|
1377
|
+
# Bounds check: reject adaptive_scripts.directory values that escape the worktree root
|
|
1378
|
+
WORKTREE_NORM=$(realpath -m "{WORKTREE_PATH}" 2>/dev/null || echo "{WORKTREE_PATH}")
|
|
1379
|
+
ADAPTIVE_DIR_NORM=$(realpath -m "$ADAPTIVE_DIR" 2>/dev/null || echo "$ADAPTIVE_DIR")
|
|
1380
|
+
if [[ "$ADAPTIVE_DIR_NORM" != "${WORKTREE_NORM}/"* ]]; then
|
|
1381
|
+
echo "2R.5: WARN — adaptive_scripts.directory resolves outside worktree root ('$ADAPTIVE_DIR_NORM') — skipping gate.d execution"
|
|
1382
|
+
ADAPTIVE_ENABLED="false"
|
|
1383
|
+
fi
|
|
1384
|
+
|
|
1385
|
+
GATED_FINDINGS=""
|
|
1386
|
+
|
|
1387
|
+
if [ "$ADAPTIVE_ENABLED" != "false" ]; then
|
|
1388
|
+
GATE_D_DIR="${ADAPTIVE_DIR}/gate.d"
|
|
1389
|
+
|
|
1390
|
+
if [ ! -d "$GATE_D_DIR" ]; then
|
|
1391
|
+
echo "2R.5: No gate.d directory found at $GATE_D_DIR — skipping adaptive checks."
|
|
1392
|
+
else
|
|
1393
|
+
# Generate unified diff file for scripts to consume
|
|
1394
|
+
DIFF_PATH=$(mktemp /tmp/forge-gate-diff.XXXXXX 2>/dev/null || echo "/tmp/forge-gate-diff.$$")
|
|
1395
|
+
git -C "{WORKTREE_PATH}" diff HEAD -- {CHANGED_FILES} > "$DIFF_PATH" 2>/dev/null || true
|
|
1396
|
+
|
|
1397
|
+
GATE_D_COUNT=$(find "$GATE_D_DIR" -maxdepth 1 -name "*.sh" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
1398
|
+
|
|
1399
|
+
if [ "$GATE_D_COUNT" -eq 0 ]; then
|
|
1400
|
+
echo "2R.5: gate.d directory exists but is empty — no adaptive checks to run."
|
|
1401
|
+
else
|
|
1402
|
+
echo "2R.5: Running $GATE_D_COUNT adaptive gate.d check(s) from $GATE_D_DIR ..."
|
|
1403
|
+
|
|
1404
|
+
# Run each gate.d script in lexicographic order
|
|
1405
|
+
while IFS= read -r script; do
|
|
1406
|
+
[ -z "$script" ] && continue
|
|
1407
|
+
SLUG=$(basename "$script" .sh)
|
|
1408
|
+
|
|
1409
|
+
if [ ! -x "$script" ]; then
|
|
1410
|
+
echo "2R.5: WARN — gate.d script $SLUG is not executable — skipping (run: chmod +x $script)"
|
|
1411
|
+
continue
|
|
1412
|
+
fi
|
|
1413
|
+
|
|
1414
|
+
# Run with per-script 30s timeout; pass diff path and worktree root
|
|
1415
|
+
# timeout exit code 124 = timed out; treat as fail-closed (HIGH finding)
|
|
1416
|
+
GATE_OUTPUT=$(timeout 30 bash "$script" "$DIFF_PATH" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1417
|
+
GATE_EXIT=$?
|
|
1418
|
+
|
|
1419
|
+
if [ "$GATE_EXIT" -eq 124 ]; then
|
|
1420
|
+
# Timeout — fail closed: block the gate with HIGH finding
|
|
1421
|
+
TIMEOUT_FINDING="GATD-${SLUG}-timeout | HIGH | (gate.d) | gate.d script '$SLUG' timed out after 30s — failing closed. Investigate and optimize the script or increase timeout via gate.d contract."
|
|
1422
|
+
GATED_FINDINGS="${GATED_FINDINGS}
|
|
1423
|
+
${TIMEOUT_FINDING}"
|
|
1424
|
+
echo "2R.5: TIMEOUT — $SLUG (30s) — fail-closed HIGH finding added"
|
|
1425
|
+
elif [ "$GATE_EXIT" -eq 1 ]; then
|
|
1426
|
+
# Script fired — findings on stdout
|
|
1427
|
+
while IFS= read -r line; do
|
|
1428
|
+
[ -z "$line" ] && continue
|
|
1429
|
+
GATED_FINDINGS="${GATED_FINDINGS}
|
|
1430
|
+
${line}"
|
|
1431
|
+
done <<< "$GATE_OUTPUT"
|
|
1432
|
+
echo "2R.5: FAIL — $SLUG matched"
|
|
1433
|
+
elif [ "$GATE_EXIT" -eq 0 ] || [ "$GATE_EXIT" -eq 2 ]; then
|
|
1434
|
+
# 0 = pass, 2 = inapplicable (skip silently)
|
|
1435
|
+
echo "2R.5: PASS — $SLUG"
|
|
1436
|
+
else
|
|
1437
|
+
echo "2R.5: WARN — $SLUG exited with unexpected code $GATE_EXIT (expected 0, 1, or 2)"
|
|
1438
|
+
fi
|
|
1439
|
+
done < <(find "$GATE_D_DIR" -maxdepth 1 -name "*.sh" -type f | sort)
|
|
1440
|
+
|
|
1441
|
+
fi
|
|
1442
|
+
|
|
1443
|
+
# Clean up temp diff file
|
|
1444
|
+
rm -f "$DIFF_PATH" 2>/dev/null || true
|
|
1445
|
+
fi
|
|
1446
|
+
fi
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
If any gate.d check produces findings, include them in the Step 3 findings list prefixed with `GATD-{slug}` (e.g. `GATD-stderr-contamination | HIGH | scripts/doctor.sh | ...`). HIGH-severity findings are blocking; MEDIUM are advisory; LOW are advisory only.
|
|
1450
|
+
|
|
1451
|
+
**gate.d script contract** (for authors of promoted checks):
|
|
1452
|
+
- `$1` = absolute path to the unified diff file (`git diff HEAD`)
|
|
1453
|
+
- `$2` = absolute path to the worktree root
|
|
1454
|
+
- stdout = findings lines, one per line: `SLUG | HIGH|MEDIUM|LOW | FILE | MESSAGE`
|
|
1455
|
+
- Exit 0 = no findings / passed; Exit 1 = findings emitted; Exit 2 = inapplicable (diff has no relevant content)
|
|
1456
|
+
- 30-second wall-time budget enforced by quality-gate; timeout = fail-closed HIGH finding
|
|
1457
|
+
- Do NOT write to disk, do NOT make network requests, do NOT modify any files
|
|
1458
|
+
|
|
1459
|
+
---
|
|
1460
|
+
|
|
1461
|
+
## Step 2T: Subscribed pattern card rules (when pattern_feeds enabled)
|
|
1462
|
+
|
|
1463
|
+
<!-- Added: forge#1746 -->
|
|
1464
|
+
|
|
1465
|
+
**Triggered when**: `pattern_feeds.enabled` is `true` in `forge.yaml` AND the local ledger contains at least one card with `origin: <feed-slug>` AND a `gate.d` check template.
|
|
1466
|
+
|
|
1467
|
+
**Purpose**: Cards subscribed from exchange repos (see `forge.yaml → pattern_feeds`) may include a `gate.d` check template — a bash snippet that catches the pattern described in the card. When the changed files include a stack that matches the card's `stacks:` field, the check template is seeded as a Tier-B (warning-only) learned rule and injected into this gate run. This gives cross-repo validated patterns gate enforcement without requiring a human to manually promote them first.
|
|
1468
|
+
|
|
1469
|
+
**CRITICAL — advisory-only**: Step 2T findings are NEVER blocking (HIGH). They are always MEDIUM (advisory) regardless of the card's stated severity. A finding becomes blocking only after the card is promoted to a local validated finding (via the standard 3-occurrence threshold in `/pipeline-health` Phase 4 + Step 2R). This prevents imported patterns from blocking the builder until the pattern has been validated against the local codebase.
|
|
1470
|
+
|
|
1471
|
+
**Fail-safe on malformed templates**: If a `gate.d` check template is absent, empty, syntactically invalid, or exits with an unexpected code, log a warning and continue. Never fail-open silently — log every skip with a reason.
|
|
1472
|
+
|
|
1473
|
+
```bash
|
|
1474
|
+
# Read pattern_feeds config
|
|
1475
|
+
FEEDS_ENABLED=$(yq '.pattern_feeds.enabled // "false"' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null || echo 'false')
|
|
1476
|
+
LEDGER_PATH=$(yq '.pattern_feeds.ledger_path // ".forge/ledger"' "{WORKTREE_PATH}/forge.yaml" 2>/dev/null || echo '.forge/ledger')
|
|
1477
|
+
LEDGER_DIR="{WORKTREE_PATH}/${LEDGER_PATH}"
|
|
1478
|
+
|
|
1479
|
+
SUBSCRIBED_FINDINGS=""
|
|
1480
|
+
|
|
1481
|
+
if [ "$FEEDS_ENABLED" != "true" ]; then
|
|
1482
|
+
echo "2T: pattern_feeds.enabled is not true — skipping subscribed card rules."
|
|
1483
|
+
else
|
|
1484
|
+
if [ ! -d "$LEDGER_DIR" ]; then
|
|
1485
|
+
echo "2T: Ledger directory not found at $LEDGER_DIR — skipping. Run scripts/build-knowledge-index.mjs to populate."
|
|
1486
|
+
else
|
|
1487
|
+
# Discover subscribed cards: ledger entries with origin != "" (imported from exchange)
|
|
1488
|
+
SUBSCRIBED_CARDS=$(find "$LEDGER_DIR" -name "*.json" -type f 2>/dev/null | xargs grep -l '"origin"' 2>/dev/null | head -50)
|
|
1489
|
+
|
|
1490
|
+
if [ -z "$SUBSCRIBED_CARDS" ]; then
|
|
1491
|
+
echo "2T: No subscribed cards found in ledger — skipping."
|
|
1492
|
+
else
|
|
1493
|
+
CARD_COUNT=$(echo "$SUBSCRIBED_CARDS" | wc -l | tr -d ' ')
|
|
1494
|
+
echo "2T: Found $CARD_COUNT subscribed card(s) — checking for applicable gate.d templates..."
|
|
1495
|
+
|
|
1496
|
+
while IFS= read -r card_file; do
|
|
1497
|
+
[ -z "$card_file" ] && continue
|
|
1498
|
+
|
|
1499
|
+
# Read card metadata
|
|
1500
|
+
CARD_SLUG=$(jq -r '.slug // ""' "$card_file" 2>/dev/null)
|
|
1501
|
+
CARD_ORIGIN=$(jq -r '.origin // ""' "$card_file" 2>/dev/null)
|
|
1502
|
+
CARD_STACKS=$(jq -r '.stacks // [] | .[]' "$card_file" 2>/dev/null | tr '\n' ' ' | xargs)
|
|
1503
|
+
GATE_TEMPLATE=$(jq -r '.gate_template // ""' "$card_file" 2>/dev/null)
|
|
1504
|
+
PREVENTION_RULE=$(jq -r '.prevention // ""' "$card_file" 2>/dev/null)
|
|
1505
|
+
|
|
1506
|
+
[ -z "$CARD_SLUG" ] && continue
|
|
1507
|
+
[ -z "$GATE_TEMPLATE" ] && {
|
|
1508
|
+
echo "2T: SKIP $CARD_SLUG (origin: $CARD_ORIGIN) — no gate.d template"
|
|
1509
|
+
continue
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
# Stack filter: check if any changed file's extension matches the card's stacks
|
|
1513
|
+
# Stack detection: node/typescript=.ts/.tsx/.js/.mjs, python=.py,
|
|
1514
|
+
# bash/gha=.sh/.yml/.yaml, go=.go, rust=.rs, generic=any
|
|
1515
|
+
STACK_MATCH=false
|
|
1516
|
+
for STACK in $CARD_STACKS; do
|
|
1517
|
+
case "$STACK" in
|
|
1518
|
+
node|typescript)
|
|
1519
|
+
echo "{CHANGED_FILES}" | tr ' ' '\n' | grep -qE '\.(ts|tsx|js|mjs|cjs)$' && STACK_MATCH=true ;;
|
|
1520
|
+
python)
|
|
1521
|
+
echo "{CHANGED_FILES}" | tr ' ' '\n' | grep -qE '\.py$' && STACK_MATCH=true ;;
|
|
1522
|
+
bash)
|
|
1523
|
+
echo "{CHANGED_FILES}" | tr ' ' '\n' | grep -qE '\.sh$' && STACK_MATCH=true ;;
|
|
1524
|
+
gha)
|
|
1525
|
+
echo "{CHANGED_FILES}" | tr ' ' '\n' | grep -qE '\.ya?ml$' && STACK_MATCH=true ;;
|
|
1526
|
+
go)
|
|
1527
|
+
echo "{CHANGED_FILES}" | tr ' ' '\n' | grep -qE '\.go$' && STACK_MATCH=true ;;
|
|
1528
|
+
rust)
|
|
1529
|
+
echo "{CHANGED_FILES}" | tr ' ' '\n' | grep -qE '\.rs$' && STACK_MATCH=true ;;
|
|
1530
|
+
generic)
|
|
1531
|
+
STACK_MATCH=true ;;
|
|
1532
|
+
esac
|
|
1533
|
+
[ "$STACK_MATCH" = "true" ] && break
|
|
1534
|
+
done
|
|
1535
|
+
|
|
1536
|
+
if [ "$STACK_MATCH" = "false" ]; then
|
|
1537
|
+
echo "2T: SKIP $CARD_SLUG — no stack match (card stacks: $CARD_STACKS)"
|
|
1538
|
+
continue
|
|
1539
|
+
fi
|
|
1540
|
+
|
|
1541
|
+
# Validate template syntax (bash -n = syntax-only check, no execution)
|
|
1542
|
+
TEMPLATE_FILE=$(mktemp /tmp/forge-gate-template-XXXXXX.sh 2>/dev/null || echo "/tmp/forge-gate-template-$$.sh")
|
|
1543
|
+
echo "$GATE_TEMPLATE" > "$TEMPLATE_FILE" 2>/dev/null
|
|
1544
|
+
if ! bash -n "$TEMPLATE_FILE" 2>/dev/null; then
|
|
1545
|
+
echo "2T: WARN — $CARD_SLUG (origin: $CARD_ORIGIN) has invalid gate.d template syntax — skipping"
|
|
1546
|
+
rm -f "$TEMPLATE_FILE" 2>/dev/null || true
|
|
1547
|
+
continue
|
|
1548
|
+
fi
|
|
1549
|
+
chmod +x "$TEMPLATE_FILE" 2>/dev/null
|
|
1550
|
+
|
|
1551
|
+
# Run template with 15s timeout; pass changed files list and worktree path
|
|
1552
|
+
TEMPLATE_OUTPUT=$(timeout 15 bash "$TEMPLATE_FILE" "{CHANGED_FILES}" "{WORKTREE_PATH}" 2>/dev/null)
|
|
1553
|
+
TEMPLATE_EXIT=$?
|
|
1554
|
+
rm -f "$TEMPLATE_FILE" 2>/dev/null || true
|
|
1555
|
+
|
|
1556
|
+
if [ "$TEMPLATE_EXIT" -eq 124 ]; then
|
|
1557
|
+
echo "2T: WARN — $CARD_SLUG template timed out (15s) — skipping"
|
|
1558
|
+
elif [ "$TEMPLATE_EXIT" -eq 1 ]; then
|
|
1559
|
+
# Template fired — emit as MEDIUM (advisory) regardless of card severity
|
|
1560
|
+
while IFS= read -r line; do
|
|
1561
|
+
[ -z "$line" ] && continue
|
|
1562
|
+
# Rewrite any HIGH severity from card template to MEDIUM (advisory only)
|
|
1563
|
+
SAFE_LINE=$(echo "$line" | sed 's/| HIGH |/| MEDIUM |/g')
|
|
1564
|
+
SUBSCRIBED_FINDINGS="${SUBSCRIBED_FINDINGS}
|
|
1565
|
+
SUBSCRIBED-${CARD_SLUG} | MEDIUM | (from exchange:${CARD_ORIGIN}) | ${SAFE_LINE}"
|
|
1566
|
+
done <<< "$TEMPLATE_OUTPUT"
|
|
1567
|
+
echo "2T: ADVISORY — $CARD_SLUG matched (origin: $CARD_ORIGIN). Prevention rule: ${PREVENTION_RULE}"
|
|
1568
|
+
echo "2T: NOTE — This is an imported pattern (Tier-B advisory). It becomes blocking only after local promotion via /pipeline-health Phase 4."
|
|
1569
|
+
elif [ "$TEMPLATE_EXIT" -eq 0 ] || [ "$TEMPLATE_EXIT" -eq 2 ]; then
|
|
1570
|
+
echo "2T: PASS — $CARD_SLUG (no match)"
|
|
1571
|
+
else
|
|
1572
|
+
echo "2T: WARN — $CARD_SLUG template exited with unexpected code $TEMPLATE_EXIT — skipping"
|
|
1573
|
+
fi
|
|
1574
|
+
|
|
1575
|
+
done <<< "$SUBSCRIBED_CARDS"
|
|
1576
|
+
fi
|
|
1577
|
+
fi
|
|
1578
|
+
fi
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
If any Step 2T template produces findings, include them in the Step 3 findings list prefixed `SUBSCRIBED-{slug}`. All Step 2T findings are MEDIUM (advisory) — they never block the commit. Include the prevention rule and origin feed in the finding message so the builder can understand the cross-repo context.
|
|
1582
|
+
|
|
1583
|
+
---
|
|
1584
|
+
|
|
1168
1585
|
## Step 2S: Test failure classification (run when tests fail in Step 2)
|
|
1169
1586
|
|
|
1170
1587
|
<!-- Added: forge#1336 -->
|
|
@@ -11,7 +11,7 @@ argument-hint: [--dry-run | --since <hours> | --issue <number>]
|
|
|
11
11
|
|
|
12
12
|
Scan ALL open issues with intermediate workflow labels for orphaned state — issues where the agent died mid-pipeline (context expired, rate-limited, crashed) and no active agent is continuing. Diagnose each orphan's actual GitHub state and apply the appropriate recovery action.
|
|
13
13
|
|
|
14
|
-
**Agent model policy**: `model: "sonnet"
|
|
14
|
+
**Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet". Fallback: `model: "opus"` if rate-limited.
|
|
15
15
|
**NEVER use plan mode (EnterPlanMode).**
|
|
16
16
|
**NEVER use the Agent tool** — recover-orphans re-enters the pipeline via `Skill(skill="work-on", ...)` and `Skill(skill="review-pr", ...)` only.
|
|
17
17
|
|
|
@@ -336,7 +336,7 @@ for NUM in $ORPHAN_LIST; do
|
|
|
336
336
|
else
|
|
337
337
|
gh issue edit "$NUM" ${GH_FLAG} \
|
|
338
338
|
--add-label "workflow:merged" \
|
|
339
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" \
|
|
339
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" \
|
|
340
340
|
2>/dev/null || true
|
|
341
341
|
gh issue close "$NUM" ${GH_FLAG} \
|
|
342
342
|
--comment "Closed by /recover-orphans: PR #${PR_NUM} was already merged. Labels corrected." \
|
|
@@ -362,7 +362,7 @@ for NUM in $ORPHAN_LIST; do
|
|
|
362
362
|
2>/dev/null || true
|
|
363
363
|
gh issue edit "$NUM" ${GH_FLAG} \
|
|
364
364
|
--add-label "workflow:merged" \
|
|
365
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" \
|
|
365
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" \
|
|
366
366
|
2>/dev/null || true
|
|
367
367
|
fi
|
|
368
368
|
fi
|
|
@@ -378,7 +378,7 @@ for NUM in $ORPHAN_LIST; do
|
|
|
378
378
|
Skill(skill="review-pr", args="${PR_NUM} --auto-merge --issue ${NUM} --gh-flag ${GH_FLAG}")
|
|
379
379
|
# After review: update label
|
|
380
380
|
gh issue edit "$NUM" ${GH_FLAG} --add-label "workflow:in-review" \
|
|
381
|
-
--remove-label "workflow:building" 2>/dev/null || true
|
|
381
|
+
--remove-label "workflow:building,workflow:awaiting-merge" 2>/dev/null || true
|
|
382
382
|
fi
|
|
383
383
|
RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} submitted for review |\n"
|
|
384
384
|
;;
|
|
@@ -401,7 +401,7 @@ for NUM in $ORPHAN_LIST; do
|
|
|
401
401
|
echo " [DRY-RUN] Would: remove workflow:investigating, workflow:ready-to-build, workflow:building, workflow:in-review"
|
|
402
402
|
else
|
|
403
403
|
gh issue edit "$NUM" ${GH_FLAG} \
|
|
404
|
-
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review" \
|
|
404
|
+
--remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge" \
|
|
405
405
|
2>/dev/null || true
|
|
406
406
|
gh issue comment "$NUM" ${GH_FLAG} \
|
|
407
407
|
--body "<!-- FORGE:ORPHAN_RECOVERED -->
|
package/commands/replay.md
CHANGED
|
@@ -10,7 +10,7 @@ install: extras
|
|
|
10
10
|
|
|
11
11
|
Replay a completed pipeline run. Fetches all FORGE annotations posted during the pipeline and displays them chronologically — phase headings, timestamps, and key structured fields. Read-only: nothing is written, no labels changed.
|
|
12
12
|
|
|
13
|
-
**Agent model policy**: `model: "
|
|
13
|
+
**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.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: New or modified routers/routes, OR SDK/OpenAPI files changed (`sdk/`, `openapi*.json`, `openapi-versions/`)
|
|
14
|
-
**Type**: `general-purpose` | **Model**: `
|
|
14
|
+
**Type**: `general-purpose` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: AUTH domain detected
|
|
14
|
-
**Type**: `security-exploit-auditor` | **Model**: `
|
|
14
|
+
**Type**: `security-exploit-auditor` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: BILLING domain detected
|
|
14
|
-
**Type**: `codebase-explorer` | **Model**: `
|
|
14
|
+
**Type**: `codebase-explorer` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: CONCURRENCY domain detected OR BILLING domain detected
|
|
14
|
-
**Type**: `general-purpose` | **Model**: `
|
|
14
|
+
**Type**: `general-purpose` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: DATABASE domain detected
|
|
14
|
-
**Type**: `general-purpose` | **Model**: `
|
|
14
|
+
**Type**: `general-purpose` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: WEB service touched
|
|
14
|
-
**Type**: `codebase-explorer` | **Model**: `
|
|
14
|
+
**Type**: `codebase-explorer` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
install:
|
|
2
|
+
install: core
|
|
3
3
|
---
|
|
4
4
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
5
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -11,7 +11,7 @@ install: internal
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
**Trigger**: INFRA service touched
|
|
14
|
-
**Type**: `general-purpose` | **Model**: `
|
|
14
|
+
**Type**: `general-purpose` | **Model**: `{SUBAGENT_MODEL}`
|
|
15
15
|
|
|
16
16
|
**Prompt template:**
|
|
17
17
|
```
|