forgedock 1.1.8 → 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-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/session-start.mjs +68 -3
- package/bin/init-detect.mjs +101 -12
- package/bin/journey.mjs +917 -16
- package/bin/recall.mjs +24 -7
- 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 +1 -1
- package/commands/ci-audit.md +1 -1
- package/commands/cleanup.md +87 -52
- 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 +1 -1
- package/commands/orchestrate/config.md +13 -3
- package/commands/orchestrate/phase-1-resolve.md +1 -1
- 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 +128 -20
- package/commands/orchestrate/phase-4-execution.md +598 -43
- package/commands/orchestrate/phase-5-cleanup.md +1 -1
- package/commands/orchestrate/phase-6-report.md +51 -3
- package/commands/orchestrate/safety.md +2 -2
- package/commands/qa-sweep.md +1 -1
- package/commands/quality-gate.md +1 -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 +71 -6
- package/commands/rollback.md +1 -1
- package/commands/scope.md +1 -1
- package/commands/security-audit.md +1 -1
- package/commands/test-gate.md +1 -1
- package/commands/validate.md +1 -1
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +9 -6
- package/commands/work-on/build/implement.md +1 -1
- package/commands/work-on/build/validate.md +1 -1
- package/commands/work-on/build.md +31 -6
- package/commands/work-on/close.md +3 -2
- package/commands/work-on/decompose.md +1 -1
- package/commands/work-on/investigate.md +14 -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 +194 -56
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +1 -1
- package/docs/CONFIG.md +186 -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/label-state-machine.md +1 -1
- package/package.json +2 -1
- package/packages/protocol/src/types.js +1 -1
- package/scripts/check-command-docs-drift.sh +4 -2
- package/scripts/danger-zones.mjs +19 -12
- package/scripts/transition-label.sh +101 -13
- package/scripts/refresh-bot-token.sh +0 -97
package/commands/work-on.md
CHANGED
|
@@ -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: "
|
|
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.
|
|
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. -->
|
|
@@ -76,7 +82,7 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
|
|
|
76
82
|
|
|
77
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. -->
|
|
78
84
|
|
|
79
|
-
**Default: run inline.** Every skill, phase, and sub-agent runs inline in the current context unless one of the
|
|
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.
|
|
80
86
|
|
|
81
87
|
### Spawn-Decision Table
|
|
82
88
|
|
|
@@ -85,8 +91,9 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
|
|
|
85
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 |
|
|
86
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) |
|
|
87
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 --> |
|
|
88
95
|
|
|
89
|
-
**If none of the
|
|
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.
|
|
90
97
|
|
|
91
98
|
### Depth Budget
|
|
92
99
|
|
|
@@ -98,15 +105,30 @@ Orchestrator for the full issue lifecycle: investigate → decompose (if needed)
|
|
|
98
105
|
|-------|-------|-------|
|
|
99
106
|
| 1 | `/orchestrate` | Top-level dispatcher — never implements directly |
|
|
100
107
|
| 2 | `/work-on` | Issue pipeline — runs build phases inline |
|
|
101
|
-
| 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 |
|
|
102
109
|
|
|
103
|
-
**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 -->
|
|
104
111
|
|
|
105
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.
|
|
106
113
|
|
|
107
114
|
### Phase 5C Cross-Reference
|
|
108
115
|
|
|
109
|
-
The
|
|
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 -->
|
|
110
132
|
|
|
111
133
|
---
|
|
112
134
|
|
|
@@ -219,11 +241,47 @@ Worktree/branch-already-exists and stale-label conditions are surfaced later (Ph
|
|
|
219
241
|
### 0A: Parse input
|
|
220
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.
|
|
221
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.
|
|
245
|
+
|
|
222
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.
|
|
223
247
|
|
|
224
|
-
### 0A.
|
|
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).
|
|
225
277
|
|
|
226
|
-
|
|
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.
|
|
227
285
|
|
|
228
286
|
```bash
|
|
229
287
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
@@ -233,9 +291,9 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
|
233
291
|
**Issue**: #{NUMBER}"
|
|
234
292
|
```
|
|
235
293
|
|
|
236
|
-
**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 -->
|
|
237
295
|
|
|
238
|
-
**Skip if**: Issue already has a terminal label (`workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`) — 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.)
|
|
239
297
|
|
|
240
298
|
### 0B: Load issue + existing context
|
|
241
299
|
```bash
|
|
@@ -273,6 +331,8 @@ fi
|
|
|
273
331
|
| `CLOSE` | Phase 6 (skip Phase 1–5) |
|
|
274
332
|
| *(absent or unrecognized)* | Fall back to prose heuristics above |
|
|
275
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
|
+
|
|
276
336
|
**If no checkpoint exists**: fall back to prose resume heuristics in Phase 0B above — treat as fresh start at Phase 1.
|
|
277
337
|
|
|
278
338
|
**Classify lane**: Milestone → feature lane (`milestone/{slug}`). No milestone → fast lane (`staging`).
|
|
@@ -477,7 +537,7 @@ case "$TIER" in
|
|
|
477
537
|
esac
|
|
478
538
|
```
|
|
479
539
|
|
|
480
|
-
**Post Phase 1 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
|
|
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`):
|
|
481
541
|
```bash
|
|
482
542
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
483
543
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
@@ -492,6 +552,8 @@ Before investigation begins, verify the issue body contains the four mandatory p
|
|
|
492
552
|
|
|
493
553
|
**Skip if**: All four sections (`## Problem`, `## Affected Files`, `## Expected Behavior`, `## Acceptance Criteria`) are already present.
|
|
494
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
|
+
|
|
495
557
|
```bash
|
|
496
558
|
ISSUE_BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body')
|
|
497
559
|
|
|
@@ -779,14 +841,7 @@ Inspect the subcommand output above for errors. <!-- forge#1418 -->"
|
|
|
779
841
|
fi
|
|
780
842
|
```
|
|
781
843
|
|
|
782
|
-
|
|
783
|
-
```bash
|
|
784
|
-
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
785
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
786
|
-
\`\`\`json
|
|
787
|
-
{\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"BUILD\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
788
|
-
\`\`\`"
|
|
789
|
-
```
|
|
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 -->
|
|
790
845
|
<!-- FORGE:PHASE_COMPLETE — Investigation routed to build. See Universal Phase Dispatcher: next phase is Phase 3. Not terminal — continue immediately. -->
|
|
791
846
|
→ Continue to Phase 3.
|
|
792
847
|
|
|
@@ -803,14 +858,7 @@ case "$TIER" in
|
|
|
803
858
|
esac
|
|
804
859
|
```
|
|
805
860
|
|
|
806
|
-
|
|
807
|
-
```bash
|
|
808
|
-
CHECKPOINT_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
809
|
-
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
810
|
-
\`\`\`json
|
|
811
|
-
{\"phase\": \"INVESTIGATION\", \"status\": \"COMPLETE\", \"next_phase\": \"DECOMPOSE\", \"timestamp\": \"${CHECKPOINT_TIMESTAMP}\"}
|
|
812
|
-
\`\`\`"
|
|
813
|
-
```
|
|
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 -->
|
|
814
862
|
<!-- FORGE:PHASE_COMPLETE — Investigation routed to decomposition. See Universal Phase Dispatcher: next phase is Phase 2. Not terminal — continue immediately. -->
|
|
815
863
|
→ Continue to Phase 2 (Decomposition).
|
|
816
864
|
|
|
@@ -941,7 +989,7 @@ esac
|
|
|
941
989
|
|
|
942
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. -->
|
|
943
991
|
|
|
944
|
-
**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
|
|
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 -->
|
|
945
993
|
|
|
946
994
|
**Skip if**: `<!-- FORGE:BUILDER:COMPLETE -->` is present in a BUILDER comment. <!-- Added: forge#1305 — require completion marker, not mere presence of BUILDER annotation -->
|
|
947
995
|
|
|
@@ -957,9 +1005,9 @@ fi
|
|
|
957
1005
|
|
|
958
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.**
|
|
959
1007
|
|
|
960
|
-
### 3A:
|
|
1008
|
+
### 3A: Confirm state (reuse in-context; re-read only on compaction)
|
|
961
1009
|
|
|
962
|
-
**Post Phase 3 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
|
|
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`):
|
|
963
1011
|
```bash
|
|
964
1012
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
965
1013
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
@@ -968,6 +1016,8 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
|
968
1016
|
**Issue**: #{NUMBER}"
|
|
969
1017
|
```
|
|
970
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
|
+
|
|
971
1021
|
```bash
|
|
972
1022
|
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,milestone
|
|
973
1023
|
|
|
@@ -1094,7 +1144,9 @@ FUNCTION_NAMES=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
|
1094
1144
|
|
|
1095
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.
|
|
1096
1146
|
|
|
1097
|
-
|
|
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):
|
|
1098
1150
|
|
|
1099
1151
|
**C1: Past Review Findings on These Files**
|
|
1100
1152
|
```bash
|
|
@@ -1386,6 +1438,41 @@ This advisory is informational — it does NOT block the commit. But the impleme
|
|
|
1386
1438
|
|
|
1387
1439
|
Skip for 1-file config/docs edits.
|
|
1388
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
|
+
|
|
1389
1476
|
```
|
|
1390
1477
|
iteration = 0
|
|
1391
1478
|
max_iterations = 3
|
|
@@ -1405,15 +1492,17 @@ if iteration == max_iterations AND not PASS:
|
|
|
1405
1492
|
Add needs-human label → STOP
|
|
1406
1493
|
```
|
|
1407
1494
|
|
|
1495
|
+
— and note in the builder comment that context isolation was degraded for this run.
|
|
1496
|
+
|
|
1408
1497
|
# MUST CONTINUE to sub-phase 3H (Format and verify) — quality gate PASS is intermediate, NOT terminal. <!-- Added: forge#220 -->
|
|
1409
1498
|
|
|
1410
|
-
**After
|
|
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.**
|
|
1411
1500
|
|
|
1412
|
-
**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 -->
|
|
1413
1502
|
|
|
1414
1503
|
### 3H: Format and verify
|
|
1415
1504
|
|
|
1416
|
-
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 -->
|
|
1417
1506
|
|
|
1418
1507
|
**Track skipped checks** — initialize before any check runs:
|
|
1419
1508
|
```bash
|
|
@@ -1466,7 +1555,47 @@ fi
|
|
|
1466
1555
|
```
|
|
1467
1556
|
Typecheck or build failures are BLOCKING.
|
|
1468
1557
|
|
|
1469
|
-
**
|
|
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:
|
|
1470
1599
|
|
|
1471
1600
|
```bash
|
|
1472
1601
|
# LEARNED_TEST_COMMANDS was set in Phase 0B.1 from forge.yaml → learned.test_commands
|
|
@@ -1476,11 +1605,15 @@ if [ -n "$LEARNED_TEST_COMMANDS" ] && [ "$LEARNED_TEST_COMMANDS" != "[]" ]; then
|
|
|
1476
1605
|
# yq outputs each entry on its own line with -r flag
|
|
1477
1606
|
echo "$LEARNED_TEST_COMMANDS" | yq '.[]' | while IFS= read -r cmd; do
|
|
1478
1607
|
[ -z "$cmd" ] && continue
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
|
1481
1614
|
CMD_EXIT=$?
|
|
1482
1615
|
if [ $CMD_EXIT -ne 0 ]; then
|
|
1483
|
-
echo "FAILED (exit $CMD_EXIT): $
|
|
1616
|
+
echo "FAILED (exit $CMD_EXIT): $FILTERED_CMD"
|
|
1484
1617
|
exit $CMD_EXIT
|
|
1485
1618
|
fi
|
|
1486
1619
|
done
|
|
@@ -1488,7 +1621,7 @@ else
|
|
|
1488
1621
|
echo "No learned test commands configured — skipping"
|
|
1489
1622
|
fi
|
|
1490
1623
|
```
|
|
1491
|
-
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 -->
|
|
1492
1625
|
|
|
1493
1626
|
### 3I: Frontend proxy wiring check (MANDATORY)
|
|
1494
1627
|
|
|
@@ -1740,7 +1873,7 @@ gh issue edit {NUMBER} {GH_FLAG} --add-label "needs-human"
|
|
|
1740
1873
|
|
|
1741
1874
|
### 4D: Create PR
|
|
1742
1875
|
```bash
|
|
1743
|
-
gh pr create {GH_FLAG} --base {PR_BASE} --head {BRANCH} \
|
|
1876
|
+
PR_URL=$(gh pr create {GH_FLAG} --base {PR_BASE} --head {BRANCH} \
|
|
1744
1877
|
--title "{Fix|Feat|Refactor}: {description}" \
|
|
1745
1878
|
--body "## Summary
|
|
1746
1879
|
{BRIEF_DESCRIPTION}
|
|
@@ -1754,10 +1887,11 @@ gh pr create {GH_FLAG} --base {PR_BASE} --head {BRANCH} \
|
|
|
1754
1887
|
---
|
|
1755
1888
|
Closes #{NUMBER}
|
|
1756
1889
|
**Implementation branch**: \`{BRANCH}\`
|
|
1757
|
-
**Base**: \`{PR_BASE}\`"
|
|
1890
|
+
**Base**: \`{PR_BASE}\`")
|
|
1891
|
+
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
|
|
1758
1892
|
```
|
|
1759
1893
|
|
|
1760
|
-
`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`.
|
|
1761
1895
|
|
|
1762
1896
|
If PR already exists for this branch, use the existing PR number.
|
|
1763
1897
|
|
|
@@ -1778,9 +1912,9 @@ esac
|
|
|
1778
1912
|
|
|
1779
1913
|
## Phase 5: Auto-Review
|
|
1780
1914
|
|
|
1781
|
-
### 5A:
|
|
1915
|
+
### 5A: Confirm state (reuse in-context; re-read only on compaction)
|
|
1782
1916
|
|
|
1783
|
-
**Post Phase 5 heartbeat** (skip if issue already has a terminal label — `workflow:merged`, `workflow:invalid`, `needs-human`, `workflow:awaiting-merge`):
|
|
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`):
|
|
1784
1918
|
```bash
|
|
1785
1919
|
PHASE_START_TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1786
1920
|
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
@@ -1789,6 +1923,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:HEARTBEAT -->
|
|
|
1789
1923
|
**Issue**: #{NUMBER}"
|
|
1790
1924
|
```
|
|
1791
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):
|
|
1792
1927
|
```bash
|
|
1793
1928
|
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state
|
|
1794
1929
|
PR_NUMBER=$(gh pr list {GH_FLAG} --head {BRANCH} --json number --jq '.[0].number')
|
|
@@ -1805,20 +1940,14 @@ PR #${PR_NUMBER} created targeting \`{PR_BASE}\`. Invoking /review-pr with --aut
|
|
|
1805
1940
|
|
|
1806
1941
|
### 5C: Invoke /review-pr with --auto-merge
|
|
1807
1942
|
|
|
1808
|
-
**
|
|
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) -->
|
|
1809
1944
|
|
|
1810
|
-
|
|
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.
|
|
1811
1946
|
|
|
1812
|
-
-
|
|
1813
|
-
-
|
|
1814
|
-
- **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.
|
|
1815
1949
|
|
|
1816
|
-
**
|
|
1817
|
-
```
|
|
1818
|
-
Skill(skill="review-pr", args="{PR_NUMBER} --auto-merge --issue {NUMBER} --base {PR_BASE} --gh-flag {GH_FLAG}")
|
|
1819
|
-
```
|
|
1820
|
-
|
|
1821
|
-
**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):
|
|
1822
1951
|
|
|
1823
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 -->
|
|
1824
1953
|
|
|
@@ -1853,6 +1982,11 @@ Skill(skill="work-on/review", args="{NUMBER} --repo {GH_REPO} --gh-flag {GH_FLAG
|
|
|
1853
1982
|
|
|
1854
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.
|
|
1855
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
|
+
|
|
1856
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.
|
|
1857
1991
|
|
|
1858
1992
|
### 5D: Verify merge and close (recovery)
|
|
@@ -1889,6 +2023,8 @@ gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:CHECKPOINT -->
|
|
|
1889
2023
|
|
|
1890
2024
|
**Multi-phase guard**: Detect whether the issue has multiple phases. Only check off items belonging to the current completed phase.
|
|
1891
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
|
+
|
|
1892
2028
|
```bash
|
|
1893
2029
|
BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body')
|
|
1894
2030
|
REMAINING_BEFORE=$(echo "$BODY" | grep -c '^- \[ \]' || true)
|
|
@@ -1934,8 +2070,10 @@ esac
|
|
|
1934
2070
|
|
|
1935
2071
|
**Skip if**: Not a sub-issue (no parent reference in body).
|
|
1936
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:
|
|
1937
2074
|
```bash
|
|
1938
2075
|
PARENT_REF=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body' \
|
|
2076
|
+
| sed -E 's/[*_]+//g' \
|
|
1939
2077
|
| grep -iE '(part of|spawned from|sub-issue of|parent issue:?|parent:)\s*#[0-9]+' \
|
|
1940
2078
|
| sed -n 's/.*#\([0-9][0-9]*\).*/\1/p' | head -1)
|
|
1941
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
|
|
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
|
|
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
|
|
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`
|