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.
Files changed (97) hide show
  1. package/README.md +10 -33
  2. package/bin/cinema.mjs +23 -5
  3. package/bin/engine/invariants.mjs +361 -0
  4. package/bin/engine-cli.mjs +137 -11
  5. package/bin/env-detect.mjs +249 -0
  6. package/bin/forge-utils.mjs +364 -4
  7. package/bin/forgedock.mjs +1064 -135
  8. package/bin/hooks/pre-tool-use.mjs +182 -0
  9. package/bin/hooks/session-start.mjs +68 -3
  10. package/bin/init-detect.mjs +101 -12
  11. package/bin/journey.mjs +917 -16
  12. package/bin/labels.json +10 -0
  13. package/bin/recall.mjs +557 -0
  14. package/bin/registry.mjs +91 -3
  15. package/bin/runner.mjs +38 -2
  16. package/bin/tui.mjs +49 -3
  17. package/commands/adopt.md +1 -1
  18. package/commands/analytics.md +8 -8
  19. package/commands/audit.md +1 -1
  20. package/commands/autopilot.md +958 -77
  21. package/commands/ci-audit.md +1 -1
  22. package/commands/cleanup.md +139 -55
  23. package/commands/compat-audit.md +1 -1
  24. package/commands/deploy-info.md +1 -1
  25. package/commands/deploy-pr.md +1 -1
  26. package/commands/diagnose.md +1 -1
  27. package/commands/explain.md +1 -1
  28. package/commands/fix-ci.md +1 -1
  29. package/commands/forgedock-init.md +1 -1
  30. package/commands/incident-response.md +3 -3
  31. package/commands/issue.md +1 -1
  32. package/commands/optimize.md +586 -1
  33. package/commands/orchestrate/config.md +13 -3
  34. package/commands/orchestrate/phase-1-resolve.md +36 -26
  35. package/commands/orchestrate/phase-2-triage.md +1 -1
  36. package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
  37. package/commands/orchestrate/phase-3-dependency.md +587 -48
  38. package/commands/orchestrate/phase-4-execution.md +1031 -57
  39. package/commands/orchestrate/phase-5-cleanup.md +1 -1
  40. package/commands/orchestrate/phase-6-report.md +248 -11
  41. package/commands/orchestrate/safety.md +2 -2
  42. package/commands/pipeline-health/phase-2-metrics.md +305 -1
  43. package/commands/pipeline-health/phase-4-proposals.md +127 -0
  44. package/commands/qa-sweep.md +1 -1
  45. package/commands/quality-gate.md +418 -1
  46. package/commands/recover-orphans.md +5 -5
  47. package/commands/replay.md +1 -1
  48. package/commands/review-pr-agents/api.md +2 -2
  49. package/commands/review-pr-agents/auth.md +2 -2
  50. package/commands/review-pr-agents/billing.md +2 -2
  51. package/commands/review-pr-agents/concurrency.md +2 -2
  52. package/commands/review-pr-agents/database.md +2 -2
  53. package/commands/review-pr-agents/frontend.md +2 -2
  54. package/commands/review-pr-agents/infra.md +2 -2
  55. package/commands/review-pr-agents/protocols.md +1 -1
  56. package/commands/review-pr-agents/scraper.md +2 -2
  57. package/commands/review-pr-agents/security.md +2 -2
  58. package/commands/review-pr-staging.md +41 -3
  59. package/commands/review-pr.md +462 -19
  60. package/commands/rollback.md +1 -1
  61. package/commands/scope.md +1 -1
  62. package/commands/security-audit.md +1 -1
  63. package/commands/spec-doctor.md +429 -0
  64. package/commands/test-gate.md +1 -1
  65. package/commands/validate.md +53 -1
  66. package/commands/work-on/build/architect.md +78 -1
  67. package/commands/work-on/build/context.md +360 -22
  68. package/commands/work-on/build/implement.md +3 -1
  69. package/commands/work-on/build/validate.md +29 -1
  70. package/commands/work-on/build.md +75 -6
  71. package/commands/work-on/close.md +624 -10
  72. package/commands/work-on/decompose.md +1 -1
  73. package/commands/work-on/investigate.md +235 -5
  74. package/commands/work-on/remediate.md +331 -0
  75. package/commands/work-on/review.md +1 -1
  76. package/commands/work-on-monolithic.md +1 -1
  77. package/commands/work-on.md +205 -66
  78. package/docs/CI.md +2 -2
  79. package/docs/CODEX.md +1 -1
  80. package/docs/CONFIG.md +186 -0
  81. package/docs/FORGE-PROTOCOL.md +101 -0
  82. package/docs/site/command-reference.md +31 -1
  83. package/docs/site/getting-started.md +9 -34
  84. package/docs/site/index.md +1 -1
  85. package/docs/spec/forge-protocol-v1.md +97 -5
  86. package/docs/spec/label-state-machine.md +6 -1
  87. package/package.json +3 -2
  88. package/packages/protocol/src/cli.js +374 -84
  89. package/packages/protocol/src/types.js +75 -1
  90. package/scripts/build-knowledge-index.mjs +1761 -0
  91. package/scripts/calibration.mjs +996 -0
  92. package/scripts/check-command-docs-drift.sh +4 -2
  93. package/scripts/danger-zones.mjs +789 -0
  94. package/scripts/doctor-pipeline-state.sh +88 -2
  95. package/scripts/select-fix-targets.sh +234 -0
  96. package/scripts/transition-label.sh +245 -6
  97. package/scripts/refresh-bot-token.sh +0 -97
@@ -12,7 +12,7 @@ install: internal
12
12
 
13
13
  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.
14
14
 
15
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
15
+ **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.
16
16
  **NEVER use plan mode (EnterPlanMode).**
17
17
 
18
18
  > **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 -->
@@ -11,7 +11,7 @@ argument-hint: [issue number or "next" to pick highest priority]
11
11
 
12
12
  Orchestrator for the full issue lifecycle: investigate → decompose (if needed) → 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.
13
13
 
14
- **Agent model policy**: `model: "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
+ **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.
15
15
  **NEVER use plan mode (EnterPlanMode).**
16
16
  **NEVER use the Agent tool** — this spec uses `Skill(...)` for sub-phase dispatch. The Agent tool spawns opaque subprocesses that bypass phase protocols, skip FORGE annotations, and cannot be constrained by allowed-tools. Always use `Skill(skill="...", args="...")` for sub-phase invocations.
17
17
 
@@ -30,10 +30,16 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
30
30
  ### Compaction Resilience
31
31
 
32
32
  1. Write state to GitHub after EVERY significant step
33
- 2. Re-read GitHub state at the START of each phase (don't rely on in-memory context)
33
+ 2. Read full GitHub state (issue body + comments + labels) ONCE, in Phase 0B. Carry those values in-context for the rest of the run — do NOT re-fetch them at later phase boundaries. The only trigger for a fresh re-read of something already read this session is a **detected compaction**: if a later phase needs a value (issue body, a specific FORGE:* comment, labels) that this session does not actually have in its visible context — because compaction dropped the earlier tool output, or this is a brand-new session resuming mid-pipeline — re-fetch ONLY that missing value, not the full state.
34
34
  3. After compaction: re-read issue (body + comments + labels) to reconstruct state
35
35
  4. Key principle: A NEW session running `/work-on {number}` should pick up where the last left off by reading GitHub state alone
36
36
 
37
+ **Session state cache convention**: Phases 1A.5, 3A, and 5A previously re-ran `gh issue view`/`gh api .../comments` unconditionally "for safety." That default is now inverted: reuse the value if this session already produced it (e.g., you already have `ISSUE_BODY` from Phase 0B, or already read the FORGE:INVESTIGATOR comment during Phase 1). Only issue the `gh` call if the value is genuinely absent from context. This is what makes re-reads free for a normal single-turn run while remaining exactly as resilient across a real compaction or resume. (Phase 6A's body read is a deliberate exception — see its note — because it immediately precedes a body-mutating write and Phase 5 can involve an external `/review-pr` process.)
38
+
39
+ ### Orchestration Flag
40
+
41
+ `UNDER_ORCHESTRATION` — resolved once in Phase 0A. Defaults to `false` (solo run). Set to `true` when the invocation args include `--under-orchestration` (this is how `/orchestrate` dispatches `/work-on`; see `commands/orchestrate/phase-4-execution.md`). This flag gates the 4 heartbeat comments below — they exist solely to feed `/orchestrate`'s Step 4B.5 time-based stall detector (which reads the last comment's `updated_at`) and have no consumer in a solo run.
42
+
37
43
  ### Universal Phase Dispatcher
38
44
 
39
45
  <!-- FORGE:DISPATCHER — This is the SINGLE source of truth for phase transitions. Every phase boundary references this section. -->
@@ -62,6 +68,7 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
62
68
  - `workflow:merged` label is set
63
69
  - `workflow:invalid` label is set
64
70
  - `needs-human` label is set
71
+ - `workflow:awaiting-merge` label is set (remediated + re-reviewed, awaiting a human merge decision — see #1810) <!-- Added: forge#1810 -->
65
72
  - `workflow:decomposed` label is set (sub-issues spawned)
66
73
  - Issue state is CLOSED with terminal label
67
74
 
@@ -75,7 +82,7 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
75
82
 
76
83
  <!-- FORGE:SPAWN_POLICY — Canonical spawn-decision table. Sibling specs (orchestrate.md, review-pr.md) link to this section. Sub-issues #1276–#1279 reference this table. -->
77
84
 
78
- **Default: run inline.** Every skill, phase, and sub-agent runs inline in the current context unless one of the three criteria below explicitly applies. A sub-agent buys exactly two things — parallelism and context isolation. If neither is needed, forking is waste.
85
+ **Default: run inline.** Every skill, phase, and sub-agent runs inline in the current context unless one of the four criteria below explicitly applies. A sub-agent buys exactly three things — parallelism, context isolation, and (below) prompt-cache preservation. If none is needed, forking is waste.
79
86
 
80
87
  ### Spawn-Decision Table
81
88
 
@@ -84,8 +91,9 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
84
91
  | a | **Parallel fan-out** — two or more independent work units can execute concurrently and the total wall time saving justifies the fork overhead | YES — spawn one sub-agent per work unit | `/orchestrate` dispatching multiple `/work-on` agents; `review-pr` spawning domain-specific reviewers in parallel |
85
92
  | b | **Fresh-context isolation** — the work unit is a structured review or audit whose value depends on seeing the artefact without the builder's accumulated context bias, AND the review result is load-bearing for the merge decision | YES — spawn a dedicated sub-agent | Phase 5C review-fork when build context is large (see Row c for the quantitative threshold) |
86
93
  | c | **Parent context near overflow** — the parent agent has made ≥20 Skill invocations OR the build changed ≥10 files, meaning delegating review inline risks a mid-review token overflow | YES — spawn a fresh sub-agent for review | Phase 5C: `Skill(skill="work-on/review", …)` instead of direct `review-pr` invocation |
94
+ | d | **Prompt-cache TTL** — the sub-operation (multi-domain review, multi-iteration quality-gate) is expected to run for several minutes, longer than Anthropic's ~5-minute prompt-cache TTL. Leaving it inline lets the parent's already-large accumulated context (investigation/contract/context/architect/implement annotations) sit idle past the TTL; the parent's next turn then re-hydrates that entire context **uncached**, roughly doubling effective token cost for that turn. This is independent of build size — a 1-file fix idles the parent exactly as long as a 20-file one once the sub-operation starts running | YES — spawn a fresh sub-agent, **unconditionally**, regardless of file count or Skill-invocation count | Phase 5C review (always forks — Row d supersedes Row c's threshold as the controlling reason); Phase 3G quality-gate loop (forks under Row d even though it never qualified under Row c) <!-- Added: forge#1825 --> |
87
95
 
88
- **If none of the three rows match: run inline.** Do not fork for convenience, narrative clarity, or to avoid reading a large file. Context cost of a fork (spawning, context reconstruction, result aggregation) is paid every time, even when parallelism or isolation adds no value.
96
+ **If none of the four rows match: run inline.** Do not fork for convenience, narrative clarity, or to avoid reading a large file. Context cost of a fork (spawning, context reconstruction, result aggregation) is paid every time, even when parallelism, isolation, or cache preservation adds no value.
89
97
 
90
98
  ### Depth Budget
91
99
 
@@ -97,15 +105,30 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
97
105
  |-------|-------|-------|
98
106
  | 1 | `/orchestrate` | Top-level dispatcher — never implements directly |
99
107
  | 2 | `/work-on` | Issue pipeline — runs build phases inline |
100
- | 3 | Parallel reviewers (Row a/b/c fork) | Domain review agents spawned by Phase 5C |
108
+ | 3 | Parallel reviewers (Row a/b/c/d fork); quality-gate loop (Row d fork) | Domain review agents spawned by Phase 5C; quality-gate sub-agent spawned by Phase 3G |
101
109
 
102
- **Build phases (3A–3M) run inline at depth 2** — they are sequential sub-phases of `/work-on`, not independent agents. Forking the build into a sub-agent (depth 3) is a violation of Row a/b/c unless the build itself fans out independently scoped work units.
110
+ **Build phases (3A–3M) run inline at depth 2** — they are sequential sub-phases of `/work-on`, not independent agents. Forking the build into a sub-agent (depth 3) is a violation of Row a/b/c/d unless the build itself fans out independently scoped work units, **except** the Phase 3G quality-gate loop, which forks unconditionally under Row d (cache-TTL economics) regardless of build size. <!-- Updated: forge#1825 -->
103
111
 
104
112
  **Depth 4–5 are reserved** for exceptional cases (e.g. an orchestrate agent that itself spawns an orchestrate agent for a sub-milestone). Agents that reach depth 4 MUST log a justification comment on the relevant issue.
105
113
 
106
114
  ### Phase 5C Cross-Reference
107
115
 
108
- The existing Phase 5C context-budget check (≥10 changed files OR ≥20 Skill invocations → spawn `work-on/review` sub-agent) is an instance of **Row (c)** above. The quantitative thresholds are not changed — only their framing: they are now a specific application of the spawn-decision table, not a standalone ad-hoc rule.
116
+ The Phase 5C review fork is now unconditional, controlled by **Row (d)** (prompt-cache TTL economics — independent of build size). It was previously gated by **Row (c)** alone (≥10 changed files OR ≥20 Skill invocations, i.e. parent-context-overflow risk); that threshold is preserved as a *documented, non-gating* reason the fork is also correct for large builds, but no longer determines *whether* the fork happens — Row (d) means it always does. <!-- Updated: forge#1825 (previously: "quantitative thresholds are not changed") -->
117
+
118
+ ### Phase 3G Cross-Reference
119
+
120
+ The Phase 3G quality-gate loop forks unconditionally under **Row (d)**: it scans 14+ domains across up to 3 iterations, long enough to idle the parent's accumulated context past the prompt-cache TTL regardless of how many files changed. Unlike Phase 5C, quality-gate never had a Row (c) (file-count) exception — Row (d) is the sole justification for forking it. <!-- Added: forge#1825 -->
121
+
122
+ ### Model and Effort Tiering — What Actually Applies
123
+
124
+ <!-- FORGE:MODEL_TIER_NOTE — Canonical explanation of the real vs. aspirational tiering mechanism. Every work-on/*.md "Agent model policy" line cross-references this section instead of restating it. -->
125
+
126
+ Every `work-on/*.md` sub-phase file carries an "Agent model policy" line naming a `model` and an `effort`. These are two different mechanisms with two different scopes, and only one of them changes anything for an in-process `Skill()` call:
127
+
128
+ - **`effort` is real and applies per `Skill()` invocation.** It is genuine reasoning-depth tuning on the model already running the session, gated correctly on Claude Code >= 2.1.154. Setting `effort: low` on a sub-phase file that is mechanical end-to-end (deterministic label edits, annotation posting, board sync — no root-cause analysis, no architecture planning) is a real, no-fork-required cost reduction.
129
+ - **`model` overrides are NOT functional for `Skill()`-dispatched sub-phases.** The `Skill` tool executes "within the main conversation" — it has no model parameter and does not fork a new agent/session, so a sub-phase file cannot switch the model that's already generating the current run. The only tool with real model-override semantics is `Agent(model=...)` (see `tool_param_value_permission_rules`, Claude Code >= 2.1.178) — and HARD RULE #2 above explicitly forbids using the `Agent` tool for `/work-on` sub-phase dispatch, to keep the FORGE-annotation paper trail and phase protocol intact. In practice, the only place a `/work-on` run's model is genuinely chosen is `/orchestrate`'s single `Agent(model=..., ...)` spawn for the entire run (see `commands/orchestrate/phase-4-execution.md`) — every internal `Skill()` call inside that run inherits that same model, with no per-sub-phase override.
130
+
131
+ **What this means concretely**: a sub-phase file's "Agent model policy" line documents *effort* tiering that genuinely takes effect, plus a *model* value that is aspirational/no-op unless that file is one day dispatched via `Agent(...)` instead of `Skill(...)`. Because mechanical bits (label transitions, `FORGE:CHECKPOINT` writes, heartbeat posts, task-type classification) are interleaved with reasoning-heavy content within the same `Skill()`-invoked file in most sub-phases, they cannot be selectively downtiered without either degrading the reasoning phases sharing that file, or extracting them into a brand-new fork — which the Spawn-Decision Policy above correctly discourages for operations this small (a single `gh issue edit` call does not clear Row a/b/c/d). Do not add a `model: "haiku"` claim to a sub-phase file expecting it to have effect; only add `effort: low`, and only when the whole file is mechanical end-to-end. <!-- Added: forge#1827 -->
109
132
 
110
133
  ---
111
134
 
@@ -216,13 +239,49 @@ fi
216
239
  Worktree/branch-already-exists and stale-label conditions are surfaced later (Phase 3E worktree creation and the `## Error Handling` section) with their own recovery guidance in `docs/site/troubleshooting.md`.
217
240
 
218
241
  ### 0A: Parse input
219
- Extract project prefix and issue number. If `next`/`pick`: list open issues sorted by priority, skip `needs-human` and `workflow:decomposed`, pick highest priority.
242
+ Extract project prefix and issue number. If `next`/`pick`: list open issues sorted by priority, skip `needs-human`, `workflow:decomposed`, and `workflow:awaiting-merge`, pick highest priority.
243
+
244
+ **Resolve `UNDER_ORCHESTRATION`**: `true` if the invocation args contain `--under-orchestration`, else `false`. This is a single parse done once, here — every later gated block (heartbeats) just checks this variable, no re-parsing.
220
245
 
221
246
  **Optional pre-flight**: Before committing to the full pipeline, run `/scope {NUMBER}` to get a complexity estimate (affected files, blast radius, risk flags, and decomposition recommendation). Especially useful for large or ambiguous issues.
222
247
 
223
- ### 0A.5: Post Heartbeat Annotation
248
+ ### 0A.1: Remediation Mode Detection (`--remediate`) <!-- Added: forge#1813 -->
249
+
250
+ **Check first, before any other Phase 0 routing** — if `$ARGUMENTS` contains `--remediate`, this is NOT a normal issue-pipeline invocation. The first positional argument is a **PR number**, not an issue number:
251
+
252
+ ```bash
253
+ if echo "$ARGUMENTS" | grep -qE -- '--remediate\b'; then
254
+ REMEDIATE_PR_NUMBER=$(echo "$ARGUMENTS" | grep -oP '^\s*\K[0-9]+' | head -1)
255
+ REMEDIATE_ISSUE_FLAG=""
256
+ REMEDIATE_ISSUE_NUMBER=$(echo "$ARGUMENTS" | grep -oP -- '--issue\s+\K[0-9]+' | head -1)
257
+ [ -n "$REMEDIATE_ISSUE_NUMBER" ] && REMEDIATE_ISSUE_FLAG="--issue ${REMEDIATE_ISSUE_NUMBER}"
258
+
259
+ if [ -z "$REMEDIATE_PR_NUMBER" ]; then
260
+ echo "ERROR: --remediate requires a PR number as the first argument, e.g. /work-on 1234 --remediate"
261
+ exit 1
262
+ fi
263
+
264
+ echo "Remediation mode: routing PR #${REMEDIATE_PR_NUMBER} to work-on/remediate (issue flag: ${REMEDIATE_ISSUE_FLAG:-<resolved from PR body>})"
265
+ fi
266
+ ```
267
+
268
+ If detected, dispatch immediately and STOP — do NOT fall through to Phase 0B's normal issue-number resume logic (an issue number is not even known yet; `work-on/remediate.md` Phase M0 resolves it):
269
+
270
+ ```
271
+ Skill(skill="work-on/remediate", args="${REMEDIATE_PR_NUMBER} ${REMEDIATE_ISSUE_FLAG} --repo {GH_REPO} --gh-flag {GH_FLAG}")
272
+ ```
273
+
274
+ **After `REMEDIATE_RESULT` returns, STOP unconditionally** — do not run any further Phase 0–7 logic in this file. `work-on/remediate.md` is self-contained: when `re_gate_outcome: AUTO-LANDED`, it drives its own close phase internally (Phase M8 invokes `Skill("work-on:close", ...)` directly) before returning. For every other outcome (`HELD-AWAITING-MERGE`, `RE-ESCALATED`, `UNFIXABLE`, `BLOCKED`, `ALREADY_DONE`), the issue is already at a terminal state (`workflow:awaiting-merge` or `needs-human`, or already closed) per the Universal Phase Dispatcher — nothing further to do.
275
+
276
+ This mode is reachable both standalone (a human or script running `/work-on <pr> --remediate` directly) and via the orchestrator (`commands/orchestrate/phase-4-execution.md` item 6.4 auto-dispatches the identical `Skill(skill='work-on', args='{PR} --remediate --issue {N} ...')` invocation against a `needs-human`-gated predecessor's own PR).
224
277
 
225
- Post a lightweight activity signal immediately after resolving the issue number. This gives the stall detector (orchestrate Step 4B.5) a fresh timestamp to compare against `STALL_TIMEOUT`. Without this, the stall detector can only see the last structured comment (INVESTIGATOR, BUILDER, etc.) which may be hours old during a valid long-running phase.
278
+ **Skip this entire section if `--remediate` is absent from `$ARGUMENTS`** — proceed to the normal parse below.
279
+
280
+ ### 0A.5: Post Heartbeat Annotation (orchestration-only)
281
+
282
+ **Skip entirely if `UNDER_ORCHESTRATION` is `false`** — a solo run has no stall detector polling comment timestamps, so this write has zero consumer. Do not post it "just in case."
283
+
284
+ When `UNDER_ORCHESTRATION` is `true`: post a lightweight activity signal immediately after resolving the issue number. This gives the stall detector (orchestrate Step 4B.5) a fresh timestamp to compare against `STALL_TIMEOUT`. Without this, the stall detector can only see the last structured comment (INVESTIGATOR, BUILDER, etc.) which may be hours old during a valid long-running phase.
226
285
 
227
286
  ```bash
228
287
  PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
@@ -232,9 +291,9 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
232
291
  **Issue**: #{NUMBER}"
233
292
  ```
234
293
 
235
- **Also post at major phase entry points** (Phases 1, 3, and 5) — replace `Phase 0` with the correct phase name in each case. These mid-pipeline heartbeats ensure the stall detector sees recent activity during long phases (e.g., a build phase running for 20 minutes is not falsely classified as stalled). Inline snippets are embedded at Phase 1A, Phase 3A, and Phase 5A — agents resuming mid-pipeline encounter them without reading this section. <!-- Added: forge#740 -->
294
+ **Also post at major phase entry points** (Phases 1, 3, and 5) — replace `Phase 0` with the correct phase name in each case, and same `UNDER_ORCHESTRATION` gate. These mid-pipeline heartbeats ensure the stall detector sees recent activity during long phases (e.g., a build phase running for 20 minutes is not falsely classified as stalled). Inline snippets are embedded at Phase 1A, Phase 3A, and Phase 5A — agents resuming mid-pipeline encounter them without reading this section. <!-- Added: forge#740 -->
236
295
 
237
- **Skip if**: Issue already has a terminal label (`workflow:merged`, `workflow:invalid`, `needs-human`) — no heartbeat needed on a completed issue.
296
+ **Skip if**: Issue already has a terminal label (`workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`) — no heartbeat needed on a completed issue. (This is in addition to, not instead of, the `UNDER_ORCHESTRATION` gate above.)
238
297
 
239
298
  ### 0B: Load issue + existing context
240
299
  ```bash
@@ -242,7 +301,7 @@ gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,comments,
242
301
  gh api repos/{GH_REPO}/issues/{NUMBER}/comments --jq '.[] | {id: .id, author: .user.login, body: .body}'
243
302
  ```
244
303
 
245
- **Check**: state (closed → STOP), terminal labels (`workflow:merged`/`workflow:invalid` → STOP), existing agent comments (`FORGE:INVESTIGATOR`, `FORGE:DECOMPOSED`, `FORGE:CONTRACT`, `FORGE:BUILDER`, `FORGE:TRAJECTORY`, `FORGE:DECISION_RECORD`), parent tracker status, sub-issue status.
304
+ **Check**: state (closed → STOP), terminal labels (`workflow:merged`/`workflow:invalid`/`workflow:awaiting-merge` → STOP), existing agent comments (`FORGE:INVESTIGATOR`, `FORGE:DECOMPOSED`, `FORGE:CONTRACT`, `FORGE:BUILDER`, `FORGE:TRAJECTORY`, `FORGE:DECISION_RECORD`), parent tracker status, sub-issue status.
246
305
 
247
306
  **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 → Phase 6.
248
307
 
@@ -272,6 +331,8 @@ fi
272
331
  | `CLOSE` | Phase 6 (skip Phase 1–5) |
273
332
  | *(absent or unrecognized)* | Fall back to prose heuristics above |
274
333
 
334
+ Note: Phase 1D no longer writes `next_phase: BUILD`/`DECOMPOSE` CHECKPOINT comments (removed as redundant with the `workflow:ready-to-build`/`workflow:decomposed` label transition — see Phase 1D). Those two rows remain here only to route older, pre-existing CHECKPOINT comments correctly; new runs land on the prose-heuristic fallback for those two cases instead, which is equally precise. `REVIEW` and `CLOSE` are still written (Phase 3M and Phase 5D) because each covers a real gap before the corresponding label transition.
335
+
275
336
  **If no checkpoint exists**: fall back to prose resume heuristics in Phase 0B above — treat as fresh start at Phase 1.
276
337
 
277
338
  **Classify lane**: Milestone → feature lane (`milestone/{slug}`). No milestone → fast lane (`staging`).
@@ -471,12 +532,12 @@ case "$TIER" in
471
532
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} investigating ;;
472
533
  prose)
473
534
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:investigating" \
474
- --remove-label "workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
535
+ --remove-label "workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
475
536
  ;;
476
537
  esac
477
538
  ```
478
539
 
479
- **Post Phase 1 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
540
+ **Post Phase 1 heartbeat** (skip unless `UNDER_ORCHESTRATION` is `true`; also skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
480
541
  ```bash
481
542
  PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
482
543
  gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
@@ -491,6 +552,8 @@ Before investigation begins, verify the issue body contains the four mandatory p
491
552
 
492
553
  **Skip if**: All four sections (`## Problem`, `## Affected Files`, `## Expected Behavior`, `## Acceptance Criteria`) are already present.
493
554
 
555
+ **Reuse `ISSUE_BODY` from Phase 0B** — it was already read in full there. Only re-fetch with the command below if `ISSUE_BODY` is not actually present in this session's context (compaction or a fresh resume).
556
+
494
557
  ```bash
495
558
  ISSUE_BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body')
496
559
 
@@ -748,7 +811,7 @@ case "$TIER" in
748
811
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} ready-to-build ;;
749
812
  prose)
750
813
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:ready-to-build" \
751
- --remove-label "workflow:investigating,workflow:building,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
814
+ --remove-label "workflow:investigating,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
752
815
  ;;
753
816
  esac
754
817
  ```
@@ -778,14 +841,7 @@ Inspect the subcommand output above for errors. <!-- forge#1418 -->"
778
841
  fi
779
842
  ```
780
843
 
781
- Write machine-readable phase checkpoint (MUST execute immediately after label transition, before continuing):
782
- ```bash
783
- CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
784
- gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
785
- \`\`\`json
786
- {\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"BUILD\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
787
- \`\`\`"
788
- ```
844
+ **No separate CHECKPOINT write here** — the `workflow:ready-to-build` label set above already fully disambiguates the resume point ("Investigation exists + ready-to-build → Phase 3" per Phase 0B's prose heuristics); a `next_phase: BUILD` CHECKPOINT comment would duplicate that signal with an extra write. <!-- Removed redundant FORGE:CHECKPOINT write: forge#1826 -->
789
845
  <!-- FORGE:PHASE_COMPLETE — Investigation routed to build. See Universal Phase Dispatcher: next phase is Phase 3. Not terminal — continue immediately. -->
790
846
  → Continue to Phase 3.
791
847
 
@@ -797,19 +853,12 @@ case "$TIER" in
797
853
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} decomposed ;;
798
854
  prose)
799
855
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:decomposed" \
800
- --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid" 2>/dev/null || true
856
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid" 2>/dev/null || true
801
857
  ;;
802
858
  esac
803
859
  ```
804
860
 
805
- Write machine-readable phase checkpoint (MUST execute immediately after label transition, before continuing):
806
- ```bash
807
- CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
808
- gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
809
- \`\`\`json
810
- {\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"DECOMPOSE\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
811
- \`\`\`"
812
- ```
861
+ **No separate CHECKPOINT write here** — the `workflow:decomposed` label set above is itself a terminal state for this run; nothing needs to resume past it, so a `next_phase: DECOMPOSE` CHECKPOINT comment adds no information. <!-- Removed redundant FORGE:CHECKPOINT write: forge#1826 -->
813
862
  <!-- FORGE:PHASE_COMPLETE — Investigation routed to decomposition. See Universal Phase Dispatcher: next phase is Phase 2. Not terminal — continue immediately. -->
814
863
  → Continue to Phase 2 (Decomposition).
815
864
 
@@ -821,7 +870,7 @@ case "$TIER" in
821
870
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} invalid ;;
822
871
  prose)
823
872
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:invalid" \
824
- --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:decomposed" 2>/dev/null || true
873
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:decomposed" 2>/dev/null || true
825
874
  ;;
826
875
  esac
827
876
  gh issue close {NUMBER} {GH_FLAG} --comment "Closing as invalid: {reason from investigation}"
@@ -927,7 +976,7 @@ case "$TIER" in
927
976
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} decomposed ;;
928
977
  prose)
929
978
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:decomposed" \
930
- --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:merged,workflow:invalid" 2>/dev/null || true
979
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid" 2>/dev/null || true
931
980
  ;;
932
981
  esac
933
982
  ```
@@ -940,7 +989,7 @@ esac
940
989
 
941
990
  <!-- FORGE:PHASE_COMPLETE — Entering Phase 3 (Build). See Universal Phase Dispatcher: sub-phases 3A–3M execute in sequence. No sub-phase completion is terminal. -->
942
991
 
943
- **Canonical path**: Sub-phases 3A–3M run **inline** in the current context window for STANDARD and fast-lane issues. This is the single authoritative build topology. `work-on/build.md` and `work-on-monolithic.md` ([BENCHMARK]) describe the same inline model with different levels of detail; they are not separate competing paths. `Skill()` sub-agent spawns for build sub-phases are only permitted under the Spawn-Decision Table Row (c) exception (≥20 Skill invocations or ≥10 files changed before the build). <!-- Added: forge#1276 -->
992
+ **Canonical path**: Sub-phases 3A–3M run **inline** in the current context window for STANDARD and fast-lane issues, with one standing exception: 3G (quality gate) always forks to a sub-agent under Row (d). This is the single authoritative build topology. `work-on/build.md` and `work-on-monolithic.md` ([BENCHMARK]) describe the same inline model with different levels of detail; they are not separate competing paths. `Skill()`/`Agent()` sub-agent spawns for build sub-phases are only permitted under a Spawn-Decision Table Row (c) or Row (d) exception — Row (c): ≥20 Skill invocations or ≥10 files changed before the build; Row (d): the sub-phase's own runtime risks exceeding the prompt-cache TTL regardless of build size (3G's standing exception). <!-- Added: forge#1276, updated: forge#1825 -->
944
993
 
945
994
  **Skip if**: `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment. <!-- Added: forge#1305 — require completion marker, not mere presence of BUILDER annotation -->
946
995
 
@@ -956,9 +1005,9 @@ fi
956
1005
 
957
1006
  **CRITICAL: You MUST execute ALL sub-phases 3A–3M in order. Sub-phases 3C.5 (context) and 3C.6 (architect) are skipped ONLY for TRIVIAL tasks and Investigation tasks — see Phase 3B for classification. For STANDARD and COMPLEX tasks they post mandatory `FORGE:CONTEXT` and `FORGE:ARCHITECT` comments that Phase 3F reads as its primary input. Skipping them without a TRIVIAL/Investigation classification degrades build quality and causes review findings. After each sub-phase, continue to the next — no sub-phase is terminal.**
958
1007
 
959
- ### 3A: Re-read state from GitHub (MANDATORY)
1008
+ ### 3A: Confirm state (reuse in-context; re-read only on compaction)
960
1009
 
961
- **Post Phase 3 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
1010
+ **Post Phase 3 heartbeat** (skip unless `UNDER_ORCHESTRATION` is `true`; also skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
962
1011
  ```bash
963
1012
  PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
964
1013
  gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
@@ -967,6 +1016,8 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
967
1016
  **Issue**: #{NUMBER}"
968
1017
  ```
969
1018
 
1019
+ If this is a continuous run (Phase 1 already executed in this same session), you already have the issue view, the FORGE:INVESTIGATOR body, and know whether FORGE:BUILDER:COMPLETE/FORGE:FAST_PATH exist from having posted or checked for them earlier — reuse those values, do not re-fetch. Only run the block below if one of those values is genuinely missing from this session's context (fresh/resumed session after compaction, or checkpoint routing jumped straight here):
1020
+
970
1021
  ```bash
971
1022
  gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,milestone
972
1023
 
@@ -1093,7 +1144,9 @@ FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
1093
1144
 
1094
1145
  **The ONLY acceptable skip conditions** (all must be true): Issue is a 1-file config/docs edit with no code logic AND affected files have zero git history. In all other cases, run context gathering.
1095
1146
 
1096
- Run these queries (20s timeout each, 2 min total budget):
1147
+ **Batch execution (MANDATORY)**: C1, C2 (including the direct commit-body read and the pickaxe pass), C3, and C4 below are mutually independent — none consumes another's output. Issue ALL of their underlying `gh`/`git`/`grep` calls as a single batch of parallel tool calls in one message, not as four sequential steps. This is the same rule as the "independent tool calls → same message" convention used elsewhere in the pipeline; it turns a ~4x serial round-trip chain (each `gh api` call is 0.5-2s) into one wall-clock round-trip. The per-file loop inside C1 and the per-function loop inside C3 are themselves independent across iterations — include every iteration's call in the same batch rather than looping turn-by-turn. Total budget is still 20s per query / 2 min overall; batching only removes serialization, it does not change the timeout.
1148
+
1149
+ Queries to batch (20s timeout each, 2 min total budget):
1097
1150
 
1098
1151
  **C1: Past Review Findings on These Files**
1099
1152
  ```bash
@@ -1262,7 +1315,7 @@ case "$TIER" in
1262
1315
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} building ;;
1263
1316
  prose)
1264
1317
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:building" \
1265
- --remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
1318
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:in-review,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
1266
1319
  ;;
1267
1320
  esac
1268
1321
  ```
@@ -1385,6 +1438,41 @@ This advisory is informational — it does NOT block the commit. But the impleme
1385
1438
 
1386
1439
  Skip for 1-file config/docs edits.
1387
1440
 
1441
+ **Fork the loop unconditionally under Spawn-Decision Table Row (d)** (see Phase 3G Cross-Reference above): quality-gate scans 14+ domains across up to 3 iterations — long enough to idle the parent's already-large accumulated context past the prompt-cache's ~5-minute TTL, forcing an uncached re-hydration on the parent's next turn. This applies regardless of changed-file count; there is no small-build exception. Dispatch with the `Agent` tool, not a nested `Skill()` call — a nested `Skill()` call still executes inside the parent's own context and would not stop the parent from idling past the cache TTL while the loop runs. The sub-agent needs nothing from the parent's context: it works directly against files on disk at `{WORKTREE_PATH}`. <!-- Added: forge#1825 -->
1442
+
1443
+ ```
1444
+ Agent(
1445
+ subagent_type="general-purpose",
1446
+ model="{SUBAGENT_MODEL}",
1447
+ description="Quality gate for #{NUMBER}",
1448
+ run_in_background=false,
1449
+ prompt="Run the quality gate loop for issue #{NUMBER} in worktree {WORKTREE_PATH}.
1450
+ NEVER use plan mode (EnterPlanMode).
1451
+ Changed files: {CHANGED_FILES}
1452
+
1453
+ iteration = 0
1454
+ max_iterations = 3
1455
+ while iteration < max_iterations:
1456
+ iteration += 1
1457
+ Skill('quality-gate', args='{CHANGED_FILES} --worktree {WORKTREE_PATH}')
1458
+ if result == 'QUALITY GATE: PASS':
1459
+ GATE_PASSED = true
1460
+ break
1461
+ else:
1462
+ Fix each HIGH and MEDIUM finding directly in the files at {WORKTREE_PATH}
1463
+ Re-stage the fixes (do not commit, do not push)
1464
+
1465
+ Do not touch GitHub issue state — the caller handles labels/comments.
1466
+ Return exactly one line: 'GATE_RESULT: passed={true|false} iterations={N} summary={one-line summary of remaining findings if failed, else \"clean\"}'"
1467
+ )
1468
+ ```
1469
+
1470
+ Parse the returned `GATE_RESULT` line:
1471
+ - `passed=true` → `GATE_PASSED = true` → continue to sub-phase 3H.
1472
+ - `passed=false` (loop exhausted 3 iterations) → post "Quality Gate Failed After 3 Iterations" comment using the returned `summary` → add `needs-human` label → STOP.
1473
+
1474
+ **Fallback**: if the `Agent` tool is unavailable in the current runtime (partial install), run the loop inline as before —
1475
+
1388
1476
  ```
1389
1477
  iteration = 0
1390
1478
  max_iterations = 3
@@ -1404,15 +1492,17 @@ if iteration == max_iterations AND not PASS:
1404
1492
  Add needs-human label → STOP
1405
1493
  ```
1406
1494
 
1495
+ — and note in the builder comment that context isolation was degraded for this run.
1496
+
1407
1497
  # MUST CONTINUE to sub-phase 3H (Format and verify) — quality gate PASS is intermediate, NOT terminal. <!-- Added: forge#220 -->
1408
1498
 
1409
- **After quality gate completes (PASS or fixes applied): proceed immediately to sub-phase 3H below. Quality gate is an intermediate check — "PASS" means the code is clean, NOT that the build is done. Do NOT stop.**
1499
+ **After the sub-agent returns `passed=true`: proceed immediately to sub-phase 3H below. Quality gate is an intermediate check — "PASS" means the code is clean, NOT that the build is done. Do NOT stop.**
1410
1500
 
1411
- **After PASS: Do NOT re-read GitHub state, issue body, labels, or any file. Do NOT run any gh commands. Do NOT check PR status. Proceed directly to Phase 3H (Format and verify) below.** <!-- Added: forge#93 -->
1501
+ **After PASS: Do NOT re-read GitHub state, issue body, labels, or any file beyond what the sub-agent already changed on disk. Do NOT run any gh commands. Do NOT check PR status. Proceed directly to Phase 3H (Format and verify) below.** <!-- Added: forge#93 -->
1412
1502
 
1413
1503
  ### 3H: Format and verify
1414
1504
 
1415
- All tool commands are read from `forge.yaml → verification.commands`. When a key is absent, the step logs `SKIPPED — not configured in verification.commands` and continues rather than silently passing.
1505
+ All tool commands are read from `forge.yaml → verification.commands`. When a key is absent, the step logs `SKIPPED — not configured in verification.commands` and continues rather than silently passing. Before any test command executes (`learned.test_commands`, or a future `verification.commands.*.test` invocation), it is first checked against `verification.known_slow_tests` — a repo-declared list of patterns for suites known to hang or make live network/LLM calls — and skipped or narrowed to a safe subset on a match. When that config is absent, this gate is a no-op and behavior is unchanged. <!-- Added: forge#1861 -->
1416
1506
 
1417
1507
  **Track skipped checks** — initialize before any check runs:
1418
1508
  ```bash
@@ -1465,7 +1555,47 @@ fi
1465
1555
  ```
1466
1556
  Typecheck or build failures are BLOCKING.
1467
1557
 
1468
- **Learned test commands** — After all `verification.commands` steps complete, run any commands from `learned.test_commands` (captured from owner corrections in Phase 1D or set manually in forge.yaml):
1558
+ **Known-slow test gate** — Before running any test command below, check it against `verification.known_slow_tests` (a repo-declared list of test patterns known to hang or make live network/LLM calls). When absent or empty, this is a no-op and behavior is unchanged. <!-- Added: forge#1861 -->
1559
+
1560
+ ```bash
1561
+ # KNOWN_SLOW_TESTS read directly from verification.known_slow_tests (a static,
1562
+ # operator-declared config — unlike learned.test_commands, it is NOT part of the
1563
+ # agent-writable `learned:` section, so it is read inline here rather than
1564
+ # pre-loaded in Phase 0B.1).
1565
+ KNOWN_SLOW_TESTS=$(yq -o=json -I=0 '.verification.known_slow_tests // []' forge.yaml 2>/dev/null || echo '[]')
1566
+
1567
+ # apply_known_slow_filter <cmd> — echoes the command to actually run, or "" to
1568
+ # skip it entirely. Matching is substring match of `pattern` against the full
1569
+ # command text. Exactly one of skip/subset is expected per matched entry.
1570
+ apply_known_slow_filter() {
1571
+ local cmd="$1"
1572
+ local out="$cmd"
1573
+ if [ -n "$KNOWN_SLOW_TESTS" ] && [ "$KNOWN_SLOW_TESTS" != "[]" ] && [ "$KNOWN_SLOW_TESTS" != "null" ]; then
1574
+ while IFS= read -r entry; do
1575
+ [ -z "$entry" ] && continue
1576
+ pattern=$(echo "$entry" | yq '.pattern // ""')
1577
+ skip=$(echo "$entry" | yq '.skip // false')
1578
+ subset=$(echo "$entry" | yq '.subset // ""')
1579
+ reason=$(echo "$entry" | yq '.reason // "no reason given"')
1580
+ [ -z "$pattern" ] && continue
1581
+ case "$cmd" in
1582
+ *"$pattern"*)
1583
+ if [ "$skip" = "true" ]; then
1584
+ echo "SKIPPED — known-slow test matched pattern '$pattern' ($reason)" >&2
1585
+ out=""
1586
+ elif [ -n "$subset" ]; then
1587
+ echo "SUBSTITUTED — known-slow test matched pattern '$pattern' ($reason); running safe subset instead" >&2
1588
+ out="$subset"
1589
+ fi
1590
+ ;;
1591
+ esac
1592
+ done < <(echo "$KNOWN_SLOW_TESTS" | yq -o=json -I=0 '.[]' 2>/dev/null)
1593
+ fi
1594
+ echo "$out"
1595
+ }
1596
+ ```
1597
+
1598
+ **Learned test commands** — After all `verification.commands` steps complete, run any commands from `learned.test_commands` (captured from owner corrections in Phase 1D or set manually in forge.yaml), filtered through the known-slow gate above:
1469
1599
 
1470
1600
  ```bash
1471
1601
  # LEARNED_TEST_COMMANDS was set in Phase 0B.1 from forge.yaml → learned.test_commands
@@ -1475,11 +1605,15 @@ if [ -n "$LEARNED_TEST_COMMANDS" ] && [ "$LEARNED_TEST_COMMANDS" != "[]" ]; then
1475
1605
  # yq outputs each entry on its own line with -r flag
1476
1606
  echo "$LEARNED_TEST_COMMANDS" | yq '.[]' | while IFS= read -r cmd; do
1477
1607
  [ -z "$cmd" ] && continue
1478
- echo "Running learned command: $cmd"
1479
- eval "$cmd" 2>&1 | tail -30
1608
+ FILTERED_CMD=$(apply_known_slow_filter "$cmd")
1609
+ if [ -z "$FILTERED_CMD" ]; then
1610
+ continue
1611
+ fi
1612
+ echo "Running learned command: $FILTERED_CMD"
1613
+ eval "$FILTERED_CMD" 2>&1 | tail -30
1480
1614
  CMD_EXIT=$?
1481
1615
  if [ $CMD_EXIT -ne 0 ]; then
1482
- echo "FAILED (exit $CMD_EXIT): $cmd"
1616
+ echo "FAILED (exit $CMD_EXIT): $FILTERED_CMD"
1483
1617
  exit $CMD_EXIT
1484
1618
  fi
1485
1619
  done
@@ -1487,7 +1621,7 @@ else
1487
1621
  echo "No learned test commands configured — skipping"
1488
1622
  fi
1489
1623
  ```
1490
- Learned test command failures are BLOCKING (same as verification.commands failures). <!-- Added: forge#667 -->
1624
+ Learned test command failures are BLOCKING (same as verification.commands failures). A command matched and skipped by the known-slow gate is never executed and never counted as a failure. <!-- Added: forge#667, forge#1861 -->
1491
1625
 
1492
1626
  ### 3I: Frontend proxy wiring check (MANDATORY)
1493
1627
 
@@ -1739,7 +1873,7 @@ gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
1739
1873
 
1740
1874
  ### 4D: Create PR
1741
1875
  ```bash
1742
- gh pr create {GH_FLAG} --base {PR_BASE} --head {BRANCH} \
1876
+ PR_URL=$(gh pr create {GH_FLAG} --base {PR_BASE} --head {BRANCH} \
1743
1877
  --title "{Fix|Feat|Refactor}: {description}" \
1744
1878
  --body "## Summary
1745
1879
  {BRIEF_DESCRIPTION}
@@ -1753,10 +1887,11 @@ gh pr create {GH_FLAG} --base {PR_BASE} --head {BRANCH} \
1753
1887
  ---
1754
1888
  Closes #{NUMBER}
1755
1889
  **Implementation branch**: \`{BRANCH}\`
1756
- **Base**: \`{PR_BASE}\`"
1890
+ **Base**: \`{PR_BASE}\`")
1891
+ PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
1757
1892
  ```
1758
1893
 
1759
- `Closes #{NUMBER}` documents intent but does NOT auto-close for non-default-branch PRs.
1894
+ `Closes #{NUMBER}` documents intent but does NOT auto-close for non-default-branch PRs. Capture `PR_NUMBER` here — Phase 5A reuses it instead of re-querying `gh pr list`.
1760
1895
 
1761
1896
  If PR already exists for this branch, use the existing PR number.
1762
1897
 
@@ -1768,7 +1903,7 @@ case "$TIER" in
1768
1903
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} in-review ;;
1769
1904
  prose)
1770
1905
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:in-review" \
1771
- --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
1906
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:awaiting-merge,workflow:merged,workflow:invalid,workflow:decomposed" 2>/dev/null || true
1772
1907
  ;;
1773
1908
  esac
1774
1909
  ```
@@ -1777,9 +1912,9 @@ esac
1777
1912
 
1778
1913
  ## Phase 5: Auto-Review
1779
1914
 
1780
- ### 5A: Re-read state from GitHub (MANDATORY)
1915
+ ### 5A: Confirm state (reuse in-context; re-read only on compaction)
1781
1916
 
1782
- **Post Phase 5 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`):
1917
+ **Post Phase 5 heartbeat** (skip unless `UNDER_ORCHESTRATION` is `true`; also skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
1783
1918
  ```bash
1784
1919
  PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
1785
1920
  gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
@@ -1788,6 +1923,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
1788
1923
  **Issue**: #{NUMBER}"
1789
1924
  ```
1790
1925
 
1926
+ `PR_NUMBER` was already captured from `gh pr create`'s output in Phase 4D — reuse it. Only run the lookup below if `PR_NUMBER` is genuinely unset (resumed session after compaction, or checkpoint routing jumped straight to Phase 4/5):
1791
1927
  ```bash
1792
1928
  gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state
1793
1929
  PR_NUMBER=$(gh pr list {GH_FLAG} --head {BRANCH} --json number --jq '.[0].number')
@@ -1804,20 +1940,14 @@ PR #${PR_NUMBER} created targeting \`{PR_BASE}\`. Invoking /review-pr with --aut
1804
1940
 
1805
1941
  ### 5C: Invoke /review-pr with --auto-merge
1806
1942
 
1807
- **Context budget check** (run before invoking review-pr): <!-- Added: forge#93 -->
1943
+ **Always fork — Row (d) supersedes Row (c) as the controlling reason** (run before invoking review-pr): <!-- Updated: forge#1825 (previously threshold-gated by Row (c) alone — forge#93) -->
1808
1944
 
1809
- Large-context sessions that accumulated significant build history cause review-pr to hit the token limit mid-review. This check is an instance of **Spawn-Decision Policy Row (c)** (parent context near overflow) — see the [Spawn-Decision Table](#spawn-decision-table) for the general policy. Check the accumulated context before delegating:
1945
+ `/review-pr` spawns domain review agent(s) (observed at `effort: xhigh`) that run for minutes — long enough to idle the parent's accumulated context past the prompt cache's ~5-minute TTL under **Spawn-Decision Policy Row (d)**, regardless of build size — see the [Spawn-Decision Table](#spawn-decision-table). There is no small-build exception: review ALWAYS forks.
1810
1946
 
1811
- - If the build changed **≥10 files** OR this agent has made **≥20 Skill invocations** since it started: invoke `work-on/review` as a fresh sub-agent (via `Skill(skill="work-on/review", args="...")`) rather than calling review-pr directly. The sub-agent starts with a clean context window.
1812
- - Otherwise (small build, few skill calls): invoke review-pr directly as below.
1813
- - **Fallback**: if `work-on/review` is not available (partial install), invoke review-pr directly regardless of file count and add a note that context may be large.
1947
+ - ALWAYS invoke `work-on/review` as a fresh sub-agent (via `Skill(skill="work-on/review", args="...")`) rather than calling review-pr directly. The sub-agent starts with a clean context window and re-reads all needed state from GitHub (Phase R0) — it does not depend on anything the parent accumulated.
1948
+ - **Fallback only**: if `work-on/review` is not available (partial install), invoke review-pr directly and add a note in the progress comment that context isolation was degraded for this run.
1814
1949
 
1815
- **Direct invocation** (small builds — <10 changed files AND <20 Skill invocations):
1816
- ```
1817
- Skill(skill="review-pr", args="{PR_NUMBER} --auto-merge --issue {NUMBER} --base {PR_BASE} --gh-flag {GH_FLAG}")
1818
- ```
1819
-
1820
- **Sub-agent invocation** (large builds — ≥10 changed files OR ≥20 Skill invocations):
1950
+ **Sub-agent invocation** (always, regardless of changed-file count or Skill-invocation count):
1821
1951
 
1822
1952
  Before spawning, build a distilled hot-copy of the key annotations the review sub-agent would otherwise re-fetch from GitHub. This reduces gh round-trips in the child without replacing the durable FORGE annotation record. <!-- Added: forge#1277 -->
1823
1953
 
@@ -1852,6 +1982,11 @@ Skill(skill="work-on/review", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG
1852
1982
 
1853
1983
  The `{HOT_COPY_BLOCK}` is an optimization that avoids the child re-discovering context already held by the parent. The FORGE annotations on GitHub remain the durable, compaction-safe record. If the hot-copy block is empty (annotations not yet posted), the sub-agent falls back to reading them from GitHub as before.
1854
1984
 
1985
+ **Fallback invocation** (only when `work-on/review` is unavailable):
1986
+ ```
1987
+ Skill(skill="review-pr", args="{PR_NUMBER} --auto-merge --issue {NUMBER} --base {PR_BASE} --gh-flag {GH_FLAG}")
1988
+ ```
1989
+
1855
1990
  Review-pr handles: full domain-agent review → post findings as separate issues → merge PR. It does NOT close the issue or clean up the worktree — those run in Phase 6.
1856
1991
 
1857
1992
  ### 5D: Verify merge and close (recovery)
@@ -1888,6 +2023,8 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
1888
2023
 
1889
2024
  **Multi-phase guard**: Detect whether the issue has multiple phases. Only check off items belonging to the current completed phase.
1890
2025
 
2026
+ **This read is intentionally NOT covered by the session-state-cache rule** — the body is about to be rewritten below, and Phase 5's `/review-pr` invocation is an external process that can post comments/edits between the last read and here. Writing back a stale cached body would silently revert any concurrent change, so always fetch fresh immediately before a body mutation.
2027
+
1891
2028
  ```bash
1892
2029
  BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body')
1893
2030
  REMAINING_BEFORE=$(echo "$BODY" | grep -c '^- \[ \]' || true)
@@ -1924,7 +2061,7 @@ case "$TIER" in
1924
2061
  adaptive|universal) bash "$SCRIPT_PATH" {NUMBER} {GH_FLAG} merged ;;
1925
2062
  prose)
1926
2063
  gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:merged" \
1927
- --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" 2>/dev/null || true
2064
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:awaiting-merge,workflow:invalid,workflow:decomposed" 2>/dev/null || true
1928
2065
  ;;
1929
2066
  esac
1930
2067
  ```
@@ -1933,8 +2070,10 @@ esac
1933
2070
 
1934
2071
  **Skip if**: Not a sub-issue (no parent reference in body).
1935
2072
 
2073
+ Markdown emphasis markers (`**bold**`, `__bold__`, `*italic*`) are stripped before matching, since sub-issue bodies commonly render the label as `**Parent**: #NNN` and the bare label alternation below would otherwise fail to match past the emphasis characters:
1936
2074
  ```bash
1937
2075
  PARENT_REF=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body' \
2076
+ | sed -E 's/[*_]+//g' \
1938
2077
  | grep -iE '(part of|spawned from|sub-issue of|parent issue:?|parent:)\s*#[0-9]+' \
1939
2078
  | sed -n 's/.*#\([0-9][0-9]*\).*/\1/p' | head -1)
1940
2079
  ```
package/docs/CI.md CHANGED
@@ -20,7 +20,7 @@ The `forgedock-review.yml` workflow template triggers on every pull request (ope
20
20
  ## Prerequisites
21
21
 
22
22
  1. **Anthropic API key** — Claude Code runs against the Anthropic API. Get one at [console.anthropic.com](https://console.anthropic.com).
23
- 2. **ForgeDock installed** — The workflow installs ForgeDock automatically via `npx forgedock install --global` (CI uses global install so commands are available system-wide in the runner).
23
+ 2. **ForgeDock installed** — The workflow installs ForgeDock automatically via `npx forgedock install` (install is always global, so commands are available system-wide in the runner).
24
24
  3. **GitHub Actions enabled** — Standard for all public repos; enabled by default for private repos.
25
25
 
26
26
  ---
@@ -61,7 +61,7 @@ mkdir -p .github/workflows
61
61
  cp "$(npx forgedock which-dir)/templates/workflows/forgedock-review.yml" .github/workflows/forgedock-review.yml
62
62
  ```
63
63
 
64
- Or copy it manually — the file is in `~/.claude/commands/../templates/workflows/forgedock-review.yml` after running `npx forgedock install --global`.
64
+ Or copy it manually — the file is in `~/.claude/commands/../templates/workflows/forgedock-review.yml` after running `npx forgedock install`.
65
65
 
66
66
  ### 3. Commit and push
67
67
 
package/docs/CODEX.md CHANGED
@@ -8,7 +8,7 @@ Shared source:
8
8
  - `commands/**/*.md` remains the workflow spec for both runtimes
9
9
 
10
10
  Claude wrapper:
11
- - `install.sh` symlinks command files into the current project directory by default; pass `--global` to install into `~/.claude/commands/` for all projects
11
+ - `install.sh` symlinks command files into `~/.claude/commands/` for all projects (always global)
12
12
 
13
13
  Codex wrapper:
14
14
  - `install-codex.sh` generates namespaced skills in `~/.codex/skills`