forgedock 1.0.22 → 1.1.2

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 (184) hide show
  1. package/README.md +255 -211
  2. package/bin/batch-runner.mjs +339 -0
  3. package/bin/cinema.mjs +320 -0
  4. package/bin/demo.mjs +92 -7
  5. package/bin/engine/phases.mjs +168 -0
  6. package/bin/engine/projector.mjs +32 -0
  7. package/bin/engine/reconcile.mjs +11 -0
  8. package/bin/engine/runlog.mjs +79 -0
  9. package/bin/engine/state.mjs +32 -0
  10. package/bin/engine-cli.mjs +222 -0
  11. package/bin/engine.mjs +138 -0
  12. package/bin/forgedock.mjs +896 -1540
  13. package/bin/hooks/interactive-engine.mjs +509 -0
  14. package/bin/hooks/pre-tool-use.mjs +498 -0
  15. package/bin/hooks/subagent-stop-enforce.mjs +197 -0
  16. package/bin/journey.mjs +841 -0
  17. package/bin/registry.mjs +139 -0
  18. package/bin/report.mjs +447 -0
  19. package/bin/runner.mjs +136 -20
  20. package/bin/settings-hook.mjs +346 -0
  21. package/bin/tui.mjs +6 -3
  22. package/commands/adopt.md +4 -1
  23. package/commands/analytics.md +4 -1
  24. package/commands/audit-agents.md +110 -27
  25. package/commands/audit.md +20 -1
  26. package/commands/autopilot.md +84 -12
  27. package/commands/changelog.md +2 -1
  28. package/commands/ci-audit.md +2 -1
  29. package/commands/cleanup.md +161 -1
  30. package/commands/compat-audit.md +2 -1
  31. package/commands/deploy-info.md +63 -4
  32. package/commands/design-bench.md +6 -1
  33. package/commands/design-render-critique-loop.md +2 -1
  34. package/commands/design.md +1 -0
  35. package/commands/diagnose.md +2 -1
  36. package/commands/explain.md +2 -1
  37. package/commands/forge-stats.md +122 -2
  38. package/commands/forgedock-init.md +1 -1
  39. package/commands/geo-audit.md +3 -0
  40. package/commands/incident-response.md +2 -1
  41. package/commands/issue.md +24 -3
  42. package/commands/milestone.md +77 -12
  43. package/commands/optimize.md +2 -1
  44. package/commands/orchestrate/config.md +67 -0
  45. package/commands/orchestrate/phase-1-resolve.md +159 -0
  46. package/commands/orchestrate/phase-2-triage.md +126 -0
  47. package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
  48. package/commands/orchestrate/phase-3-dependency.md +591 -0
  49. package/commands/orchestrate/phase-4-execution.md +962 -0
  50. package/commands/orchestrate/phase-5-cleanup.md +47 -0
  51. package/commands/orchestrate/phase-6-report.md +198 -0
  52. package/commands/orchestrate/safety.md +62 -0
  53. package/commands/orchestrate.md +25 -2027
  54. package/commands/pipeline-health/config.md +34 -0
  55. package/commands/pipeline-health/phase-1-context.md +180 -0
  56. package/commands/pipeline-health/phase-2-metrics.md +1729 -0
  57. package/commands/pipeline-health/phase-3-analyze.md +204 -0
  58. package/commands/pipeline-health/phase-4-proposals.md +117 -0
  59. package/commands/pipeline-health/phase-5-report.md +284 -0
  60. package/commands/pipeline-health/phase-6-summary.md +52 -0
  61. package/commands/pipeline-health.md +25 -2401
  62. package/commands/pipeline-resume.md +24 -1
  63. package/commands/pipeline-status.md +65 -0
  64. package/commands/qa-sweep.md +4 -1
  65. package/commands/quality-gate.md +326 -42
  66. package/commands/replay.md +2 -1
  67. package/commands/review-pr-agents/api.md +135 -0
  68. package/commands/review-pr-agents/auth.md +137 -0
  69. package/commands/review-pr-agents/billing.md +101 -0
  70. package/commands/review-pr-agents/concurrency.md +145 -0
  71. package/commands/review-pr-agents/database.md +103 -0
  72. package/commands/review-pr-agents/frontend.md +89 -0
  73. package/commands/review-pr-agents/infra.md +385 -0
  74. package/commands/review-pr-agents/protocols.md +185 -0
  75. package/commands/review-pr-agents/scraper.md +185 -0
  76. package/commands/review-pr-agents/security.md +406 -0
  77. package/commands/review-pr-agents.md +45 -1722
  78. package/commands/review-pr-staging.md +196 -40
  79. package/commands/review-pr.md +612 -56
  80. package/commands/rollback.md +2 -1
  81. package/commands/scope.md +2 -1
  82. package/commands/security-audit.md +2 -1
  83. package/commands/signal-planner.md +623 -0
  84. package/commands/sync-ecosystem.md +1 -0
  85. package/commands/test-gate.md +17 -1
  86. package/commands/validate.md +2 -1
  87. package/commands/work-on/build/architect.md +28 -0
  88. package/commands/work-on/build/context.md +2 -0
  89. package/commands/work-on/build/implement.md +19 -5
  90. package/commands/work-on/build/validate.md +62 -6
  91. package/commands/work-on/build.md +187 -30
  92. package/commands/work-on/close.md +132 -5
  93. package/commands/work-on/decompose.md +19 -1
  94. package/commands/work-on/investigate.md +117 -2
  95. package/commands/work-on/review.md +48 -4
  96. package/commands/work-on-monolithic.md +8 -2
  97. package/commands/work-on.md +247 -7
  98. package/docs/BRAND.md +16 -5
  99. package/docs/CI.md +2 -2
  100. package/docs/CODEX.md +36 -1
  101. package/docs/CONFIG.md +44 -42
  102. package/docs/FORGE-PROTOCOL.md +15 -1064
  103. package/docs/anthropic-devrel-outreach.md +2 -2
  104. package/docs/articles/command-decomposition-token-savings.md +214 -0
  105. package/docs/articles/model-release-playbook.md +131 -0
  106. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
  107. package/docs/claude-breakpoints.json +75 -5
  108. package/docs/design/abc-benchmark.md +1 -1
  109. package/docs/design/design-spec-schema.md +1 -1
  110. package/docs/design/divergent-generation.md +1 -1
  111. package/docs/design/eval-gate-runbook.md +178 -0
  112. package/docs/design/show-hn-draft.md +26 -1
  113. package/docs/design/token-cost-baseline-1298.md +59 -0
  114. package/docs/eval/README.md +117 -0
  115. package/docs/eval/corpus/seed-v1.json +6 -0
  116. package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
  117. package/docs/site/annotations-explained.md +24 -0
  118. package/docs/site/command-learning-path.md +51 -14
  119. package/docs/site/command-reference.md +339 -113
  120. package/docs/site/for-companies.md +95 -0
  121. package/docs/site/forge-annotation-protocol.md +12 -256
  122. package/docs/site/getting-started.md +58 -12
  123. package/docs/site/github-is-the-memory.md +113 -0
  124. package/docs/site/how-it-works.md +3 -13
  125. package/docs/site/index.md +10 -2
  126. package/docs/spec/eval-run-result.md +147 -0
  127. package/docs/spec/forge-protocol-v1.md +441 -18
  128. package/docs/spec/label-state-machine.md +55 -0
  129. package/docs/spec/review-protocol.md +158 -0
  130. package/docs/spec-graph-schema.md +28 -0
  131. package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
  132. package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
  133. package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
  134. package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
  135. package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
  136. package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
  137. package/examples/forgedock-demo/README.md +41 -22
  138. package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
  139. package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
  140. package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
  141. package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
  142. package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
  143. package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
  144. package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
  145. package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
  146. package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
  147. package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
  148. package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
  149. package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
  150. package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
  151. package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
  152. package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
  153. package/examples/forgedock-demo/scripts/smoke.js +18 -1
  154. package/examples/forgedock-demo/src/db.js +36 -11
  155. package/examples/forgedock-demo/src/routes/notes.js +106 -9
  156. package/examples/forgedock-demo/src/server.js +4 -1
  157. package/package.json +12 -4
  158. package/packages/protocol/LICENSE +21 -0
  159. package/packages/protocol/src/cli.js +135 -0
  160. package/packages/protocol/src/emit.js +146 -0
  161. package/packages/protocol/src/index.js +43 -0
  162. package/packages/protocol/src/parse.js +229 -0
  163. package/packages/protocol/src/types.js +171 -0
  164. package/packages/protocol/src/validate.js +122 -0
  165. package/scripts/bench-topology-cost.mjs +372 -0
  166. package/scripts/check-command-docs-drift.sh +198 -0
  167. package/scripts/check-protocol-restatements.sh +176 -0
  168. package/scripts/check-registry/README.md +72 -0
  169. package/scripts/check-registry/manifest.json +6 -0
  170. package/scripts/code-index.sh +454 -0
  171. package/scripts/conformance-check.mjs +168 -0
  172. package/scripts/doctor-pipeline-state.sh +484 -0
  173. package/scripts/eval-gate-baseline.json +17 -0
  174. package/scripts/eval-gate-scorecard.mjs +242 -0
  175. package/scripts/eval-scorecard-aggregator.mjs +252 -0
  176. package/scripts/flaky-quarantine.sh +241 -0
  177. package/scripts/forge-annotation.sh +567 -0
  178. package/scripts/issue-dedup.sh +236 -0
  179. package/scripts/validate-annotation-node.mjs +233 -0
  180. package/scripts/verify-env-vars.sh +18 -2
  181. package/scripts/verify-host-headers.sh +4 -4
  182. package/scripts/verify-route-registration.sh +2 -2
  183. package/scripts/worktree-lifecycle.sh +202 -0
  184. package/templates/devdocs/agent/using-forgedock.md +14 -0
@@ -9,2038 +9,36 @@ argument-hint: [milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P0
9
9
 
10
10
  **Input**: $ARGUMENTS
11
11
 
12
- ## HARD RULES — READ BEFORE ANYTHING ELSE
12
+ This file is the slim dispatcher. Detailed phase content lives in `commands/orchestrate/`.
13
13
 
14
- 1. **Every agent MUST invoke `/work-on` via the Skill tool.** You do NOT write implementation prompts. You copy the Phase 4A template verbatim and fill in the `{VARIABLES}`. Nothing else. No custom prompts. No "just read and edit" shortcuts. The Skill tool invocation is what triggers labels, investigation comments, structured review, and trajectory tracking. Without it, the agent's work has no paper trail and is worthless.
14
+ ## Execution Order
15
15
 
16
- 2. **You are a dispatcher, not a builder.** You resolve issues, build the dependency DAG, spawn agents, and report results. You NEVER read code, edit files, or implement fixes yourself.
16
+ Read and execute phases in sequence. Each phase file is self-contained.
17
17
 
18
- 3. **After each agent completes, verify it used `/work-on`.** Check that completed issues have `workflow:*` labels and structured comments. If an agent bypassed the pipeline, report it as a failure.
18
+ | Step | File | Description |
19
+ |------|------|-------------|
20
+ | 0 | `orchestrate/config.md` | Hard rules, config resolution, multi-repo support — READ FIRST |
21
+ | 1 | `orchestrate/phase-1-resolve.md` | Resolve the issue set from input |
22
+ | 2 | `orchestrate/phase-2-triage.md` | Investigation-first triage, Wave 0 |
23
+ | 2.5 | `orchestrate/phase-2.5-synthesis.md` | Investigation synthesis and deconfliction |
24
+ | 3 | `orchestrate/phase-3-dependency.md` | Dependency analysis, DAG construction, execution plan |
25
+ | 4 | `orchestrate/phase-4-execution.md` | Streaming DAG execution, agent dispatch, stall detection |
26
+ | 5 | `orchestrate/phase-5-cleanup.md` | Post-batch cleanup sweep and agent audit |
27
+ | 6 | `orchestrate/phase-6-report.md` | Consolidated report and pipeline summary |
28
+ | — | `orchestrate/safety.md` | Safety rules and examples (reference) |
19
29
 
20
- ---
21
-
22
- You are the top-level orchestrator. Your job is to take a batch of issues, plan the execution order, spawn parallel sub-agents (each running the full `/work-on` pipeline), and report consolidated results.
23
-
24
- **You have access to ALL tools** — Agent tool (critical), Task tool, Skill tool, Bash, everything. Use the Agent tool aggressively to parallelize work.
25
-
26
- **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`. User can override with `--model <name>`.
27
-
28
- ---
29
-
30
- ## Config Resolution
31
-
32
- Read `forge.yaml` at the project root to resolve all project-specific variables before running any commands:
33
-
34
- ```bash
35
- # Parse forge.yaml for project context
36
- CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
37
- GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
38
- GH_FLAG="-R $GH_REPO"
39
- REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
40
- PROJECT_NAME=$(yq '.project.name' "$CONFIG_FILE")
41
- STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
42
- DEFAULT_BRANCH=$(yq '.branches.default' "$CONFIG_FILE")
43
- # Build satellite repo map from repos.satellites list
44
- # Each satellite: { prefix, repo, staging_branch }
45
- ```
46
-
47
- All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{PROJECT_NAME}`, `{STAGING_BRANCH}`, and `{DEFAULT_BRANCH}` references below are populated from `forge.yaml`.
48
-
49
- ---
50
-
51
- ## Multi-Repo Support
52
-
53
- This orchestrator supports issues across multiple repositories. See `/work-on` → "Multi-Repo Support" and `forge.yaml → repos` for the full project registry and context variables.
54
-
55
- ### Cross-Repo Issue References
56
-
57
- Issues can be prefixed with a project shorthand derived from `forge.yaml → repos.satellites`:
58
- - `#123` or `123` → default repo (`{GH_REPO}`)
59
- - `{satellite_prefix}:5` → satellite repo (e.g., `mcp:5`, `n8n:12`) — prefixes and repos come from `forge.yaml → repos.satellites`
60
- - `all-repos` → Scan all configured repos for open issues (combine and prioritize)
61
-
62
- When spawning sub-agents, pass the project prefix so `/work-on` resolves to the correct repo.
63
-
64
- ---
65
-
66
- ## Phase 1: Resolve the Issue Set
67
-
68
- Parse `$ARGUMENTS` to determine which issues to work on:
69
-
70
- ### Input Patterns
71
-
72
- | Input | Resolution |
73
- |-------|------------|
74
- | `milestone <slug>` | All open issues assigned to that GitHub milestone (default repo) |
75
- | `#1 #2 #3` or `1 2 3` | Specific issue numbers, optionally repo-prefixed (e.g., `#123 mcp:5 n8n:12`) |
76
- | `next <N>` | Top N priority open issues (priority:P0 first, then priority:P1, etc.) |
77
- | `next <N> all-repos` | Top N across ALL ecosystem repos |
78
- | `fast-lane` or `fast` | All open fast-lane issues (no milestone, bugs/fixes) |
79
- | `priority:P0` or `priority:P1` | All open issues with that priority label |
80
- | `mcp:fast` or `n8n:next 3` | Repo-scoped queries |
81
- | `<slug>` (no keyword) | Try milestone first, then fall back to label search |
82
-
83
- ### Fetch the issues
84
-
85
- For each resolved repo, use the appropriate `-R` flag:
86
-
87
- ```bash
88
- # Default repo (no flag needed if using GH_FLAG="" for the default):
89
- gh issue list {GH_FLAG} --milestone "{TITLE}" --state open --limit 500 --json number,title,labels,milestone --jq '.[] | {number, title, labels: [.labels[].name], milestone: .milestone.title}'
90
-
91
- # Satellite repos — always include -R flag with the satellite repo from forge.yaml:
92
- # For each satellite in forge.yaml → repos.satellites:
93
- gh issue list -R {SATELLITE_REPO} --milestone "{TITLE}" --state open --limit 500 --json number,title,labels,milestone
94
-
95
- # For specific numbers with repo prefix:
96
- gh issue view {NUMBER} -R {SATELLITE_REPO} --json number,title,labels,state,milestone,body
97
-
98
- # For "all-repos" — fetch from all configured repos and combine:
99
- gh issue list {GH_FLAG} --state open --limit {N} --json number,title,labels,milestone
100
- # For each satellite in forge.yaml → repos.satellites:
101
- gh issue list -R {SATELLITE_REPO} --state open --limit 500 --json number,title,labels,milestone
102
- # Combine, sort by priority, take top N
103
- ```
104
-
105
- **Count validation**: After fetching issues, log the count returned. If a milestone query returns exactly 30 issues, warn the user: "WARNING: Exactly 30 issues returned — gh CLI default may have truncated results. Re-running with --limit 500 is recommended." (The --limit 500 above prevents this for standard runs, but verify if the count seems suspiciously low relative to milestone expectations.)
106
-
107
- **Tag each issue with its project prefix** in your internal tracking so sub-agents receive the correct repo context.
108
-
109
- ### Filter out ineligible issues
110
-
111
- Remove issues that should NOT be worked on:
112
- - Already closed or has `workflow:merged` label
113
- - Has `workflow:invalid` label
114
- - Has `needs-human` label (requires manual action)
115
- - Has `workflow:decomposed` label (parent tracker — its sub-issues should be picked up instead)
116
- - Has `workflow:building` or `workflow:in-review` label (already in progress)
117
- - Is an epic (`epic` label) — these are planning containers, not buildable
118
-
119
- If a `workflow:decomposed` issue is found, automatically expand it to its open sub-issues instead.
120
-
121
- ### CRITICAL: No duplicate detection at orchestrator level
122
-
123
- **The orchestrator NEVER closes, merges, or deduplicates issues.** Even if two issues look like duplicates (similar titles, same error message, same symptoms), they may target different code paths, different ORM objects, or different failure modes. Only a `/work-on` investigation agent — after reading the actual code — can determine whether an issue is truly a duplicate.
124
-
125
- **What the orchestrator must NOT do:**
126
- - Close an issue as "duplicate" based on title/symptom similarity
127
- - Skip an issue because another issue "covers it"
128
- - Merge two issues into one agent
129
- - Make ANY judgment about issue validity
130
-
131
- **What the orchestrator SHOULD do:**
132
- - If two issues look related, add a note in the agent prompt: "Note: #{OTHER} has similar symptoms — investigate whether this is the same root cause or a different code path"
133
- - Let both agents run independently — if one finds it's truly a duplicate, `/work-on`'s investigation phase will close it as invalid with evidence
134
- - Flag potential overlap in the DAG plan for the user's awareness, but NEVER act on it
135
-
136
- **Why**: Surface-level similarity hides critical differences. #3842 (api_key.id lazy load) and #4039 (user.id lazy load) had identical error messages but targeted completely different ORM objects. Closing #4039 as a "duplicate" would have left a customer-impacting P0 bug unfixed.
137
-
138
- ---
139
-
140
- ## Phase 2: Investigation-First Triage
141
-
142
- **Purpose**: Investigation issues produce NEW GitHub issues as their output. If the batch contains investigations, they must run FIRST so their output can be folded into the execution plan.
143
-
144
- **If no investigations are found in Step 2A, skip this entire phase and proceed directly to Phase 3.**
145
-
146
- ### Step 2A: Classify each issue
147
-
148
- For each issue in the resolved set, read its title and body to classify it:
149
-
150
- ```bash
151
- for NUM in {issue_numbers}; do
152
- gh issue view $NUM --json title,body,labels --jq '{title: .title, labels: [.labels[].name], body_preview: (.body[:500])}'
153
- done
154
- ```
155
-
156
- **Classification rules:**
157
-
158
- | Signal | Classification |
159
- |--------|---------------|
160
- | Title contains "Investigate", "Audit", "Research", "Evaluate", "Assess", "Deep dive" | **Investigation** |
161
- | Issue body is primarily questions (`- [ ]` checklist) with no concrete code changes | **Investigation** |
162
- | Issue body says "Deliverable: execution plan" or "create issues" | **Investigation** |
163
- | Issue has `enhancement` label + title starts with "Enable" + body describes toggling a feature flag | **Implementation** |
164
- | Issue has `bug`, `refactor`, `dead-code`, or `feature` label | **Implementation** |
165
-
166
- Tag each issue internally as `INVESTIGATION` or `IMPLEMENTATION`.
167
-
168
- ### Step 2B: Run investigations first (Wave 0)
169
-
170
- If ANY issues are classified as `INVESTIGATION`:
171
-
172
- 1. **Move them to Wave 0** — they run BEFORE all implementation issues
173
- 2. **Spawn Wave 0 agents** using the same agent template as Phase 4A, but emphasize that `/work-on` will detect the investigation task type and produce issues (not code)
174
- 3. **Wait for ALL Wave 0 agents to complete** before proceeding to Phase 3
175
-
176
- ### Step 2C: Collect newly created issues
177
-
178
- After Wave 0 completes, each investigation agent will have created new GitHub issues. Collect them:
179
-
180
- ```bash
181
- # For each investigation issue that completed successfully:
182
- # Read the FORGE:BUILDER comment to find created issue numbers
183
- for INV_NUM in {investigation_numbers}; do
184
- gh api repos/{GH_REPO}/issues/${INV_NUM}/comments \
185
- --jq '.[] | select(.body | contains("<!-- FORGE:BUILDER -->")) | .body' \
186
- | grep -oP '#\K\d+' | sort -u
187
- done
188
-
189
- # Also check: if the investigation issue had a milestone, the new issues should too
190
- # Verify milestone assignment:
191
- for NEW_NUM in {newly_created_numbers}; do
192
- gh issue view $NEW_NUM --json milestone --jq '.milestone.title // "NO MILESTONE"'
193
- done
194
- ```
195
-
196
- ### Step 2C.5: Collect Knowledge Gist URLs from investigations
197
-
198
- For each completed investigation issue, query its comments for `FORGE:KNOWLEDGE_GIST` annotations. Store the mapping of investigation issue number → Gist URL(s) so the agent template in Step 4A can include prior investigation context.
199
-
200
- ```bash
201
- # Build a map: investigation_number → gist_url(s)
202
- declare -A INVESTIGATION_GISTS
203
- for INV_NUM in {investigation_numbers}; do
204
- GIST_URLS=$(gh api repos/{GH_REPO}/issues/${INV_NUM}/comments \
205
- --jq '[.[] | select(.body | test("<!-- FORGE:KNOWLEDGE_GIST: https://")) | .body | capture("<!-- FORGE:KNOWLEDGE_GIST: (?<url>https://[^ ]+) -->").url] | unique | .[]')
206
- if [ -n "$GIST_URLS" ]; then
207
- INVESTIGATION_GISTS[$INV_NUM]="$GIST_URLS"
208
- echo "Investigation #${INV_NUM}: found Gist URL(s)"
209
- fi
210
- done
211
- ```
212
-
213
- When spawning agents for implementation issues in Step 4A, include any Gist URLs from parent/sibling investigations in the agent prompt's context block. See the `{GIST_CONTEXT}` variable in the agent template.
214
-
215
- **Milestone index Gist**: If the milestone has a `<!-- FORGE:MILESTONE_INDEX: {url} -->` annotation in its description, read the index URL and store it for inclusion in agent context. The milestone index aggregates all investigation Gist URLs for the milestone into a single reference.
216
-
217
- ```bash
218
- # Read milestone index URL from milestone description (if milestone exists)
219
- MILESTONE_INDEX_URL=""
220
- if [ -n "$MILESTONE_NUM" ]; then
221
- MILESTONE_DESC=$(gh api repos/{GH_REPO}/milestones/${MILESTONE_NUM} --jq '.description // ""' 2>/dev/null)
222
- MILESTONE_INDEX_URL=$(echo "$MILESTONE_DESC" | grep -oP '(?<=<!-- FORGE:MILESTONE_INDEX: )https://[^ ]+(?= -->)' | head -1)
223
- if [ -n "$MILESTONE_INDEX_URL" ]; then
224
- echo "Milestone index Gist found: ${MILESTONE_INDEX_URL}"
225
- fi
226
- fi
227
- ```
228
-
229
- ### Step 2D: Merge new issues into the batch
230
-
231
- Add the newly created issues to the issue set. Re-check each one:
232
- - Filter out any that are already ineligible (same rules as Phase 1)
233
- - Tag them as `IMPLEMENTATION` (investigations don't spawn more investigations)
234
- - They inherit the milestone from their parent investigation if applicable
235
-
236
- **Issue body standard**: Any new issues created by investigation agents MUST use the **Pipeline Issue Template** from `issue.md` Phase 3D as their body structure. Investigation agents that produce issues as output should be prompted to use that template — not ad-hoc body formats — so every spawned issue enters the pipeline with the correct structure (Problem, Root Cause, Affected Files, Acceptance Criteria). Verify spawned issues have all mandatory sections before adding them to the batch; if sections are missing, the `/issue` body-validation step (Phase 4C.5) will repair them. <!-- Added: forge#293 -->
237
-
238
- **Updated issue set** = original `IMPLEMENTATION` issues + newly spawned issues from investigations.
239
-
240
- ### Step 2E: Report Wave 0 results to user
241
-
242
- ```
243
- ## Wave 0: Investigations Complete
244
-
245
- | # | Investigation | Result | Issues Created |
246
- |---|--------------|--------|---------------|
247
- | #{INV1} | {title} | ✓ Closed | #{N1}, #{N2}, #{N3} |
248
- | #{INV2} | {title} | ✓ Closed | #{N4}, #{N5} |
249
-
250
- **New issues added to batch**: {count}
251
- **Updated total**: {original_count} → {new_total} issues
252
-
253
- Proceeding to dependency analysis with the expanded issue set...
254
- ```
255
-
256
- ---
257
-
258
- ## Phase 2.5: Investigation Synthesis
259
-
260
- <!-- Added: forge#1192 -->
261
-
262
- **Purpose**: Reconcile *competing recommendations* across investigation outputs BEFORE they fan out to implementation agents. Phase 3's conflict detection (Step 3C) deconflicts issues at the **file** layer only — it prevents git merge conflicts. It performs **zero semantic deconfliction**: two issues can touch entirely different files while proposing **contradictory approaches to the same problem**, and the file-overlap detector passes both straight to parallel dispatch. This phase closes that gap by clustering investigations semantically (by target subsystem, not by file) and arbitrating incompatible plans into a single decision — or serializing them so the second agent inherits the first's decision.
263
-
264
- **This phase operates ONLY on FORGE annotations and issue bodies. It NEVER reads code, and it NEVER closes, skips, or merges issues.** Reconciling plans/annotations is distinct from adjudicating *duplicate validity* (Safety Rule 9, the #3842/#4039 scar): the anti-dedup rule forbids the orchestrator from deciding two issues are the same bug and closing one — that call belongs to `/work-on` investigation agents examining actual code. Plan reconciliation touches neither code nor issue state; it only writes a synthesis brief annotation and adds `Depends on #X` serialization edges. It therefore does not violate Hard Rule 2 (dispatcher, not builder) or Safety Rule 9.
265
-
266
- **No-op guard**: This entire phase is skipped when the batch contains **0 or 1 investigations** — there is nothing to reconcile against. Proceed directly to Phase 3.
267
-
268
- ### Step 2.5A: No-op guard
269
-
270
- ```bash
271
- # Count investigations that completed in Wave 0 (Phase 2), regardless of whether each
272
- # emitted a Knowledge Gist. Use the full completed-investigation set (the same
273
- # {investigation_numbers} that Steps 2C.5, 2.5B and 2.5C iterate) — NOT the
274
- # INVESTIGATION_GISTS map, which only holds gist-producing investigations and would
275
- # undercount when a genuine investigation reached a recommendation without a gist.
276
- # If < 2, skip synthesis entirely.
277
- INVESTIGATION_NUMS=( {investigation_numbers} )
278
- INVESTIGATION_COUNT=${#INVESTIGATION_NUMS[@]}
279
- if [ "$INVESTIGATION_COUNT" -lt 2 ]; then
280
- echo "Phase 2.5 skipped: ${INVESTIGATION_COUNT} investigation(s) in batch — nothing to reconcile. Proceeding to Phase 3."
281
- SYNTHESIS_RAN=false
282
- RECONCILED_COUNT=0
283
- # Skip to Phase 3.
284
- else
285
- SYNTHESIS_RAN=true
286
- fi
287
- ```
288
-
289
- If `SYNTHESIS_RAN` is false, do NOT execute Steps 2.5B–2.5D — proceed directly to Phase 3. Step 4A's `{GIST_CONTEXT}` generation will fall back to the existing raw-gist behavior (no synthesis brief exists).
290
-
291
- ### Step 2.5B: Cluster investigations by target subsystem
292
-
293
- For each investigation that completed in Wave 0, read its `FORGE:INVESTIGATOR` comment (and the newly spawned implementation issue bodies from Step 2D) and extract its **Recommendation** and **Affected Files / target subsystem** — NOT to compare files for merge conflicts, but to group investigations that operate on the **same conceptual surface** (e.g. "auth session lifecycle", "credit metering", "orchestrate DAG construction").
294
-
295
- ```bash
296
- # For each investigation, pull its recommendation + affected-files block (annotations only — no code reads)
297
- declare -A INV_RECOMMENDATION
298
- declare -A INV_SUBSYSTEM
299
- for INV_NUM in {investigation_numbers}; do
300
- INV_BODY=$(gh api repos/{GH_REPO}/issues/${INV_NUM}/comments \
301
- --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body' 2>/dev/null | head -1)
302
- # Extract the Recommendation section (annotation prose only)
303
- INV_RECOMMENDATION[$INV_NUM]=$(echo "$INV_BODY" | awk '/^### Recommendation/{p=1;next}/^### /{p=0}p')
304
- # Derive a coarse subsystem tag from Affected Files directories + title keywords
305
- INV_SUBSYSTEM[$INV_NUM]=$(echo "$INV_BODY" \
306
- | grep -oP '`[^`]+/[^`]+`' | xargs -r -n1 dirname 2>/dev/null | sort | uniq -c | sort -rn | head -1)
307
- done
308
- ```
309
-
310
- **Cluster rule**: Two investigations are in the same cluster when they share a target subsystem (overlapping affected-file directories OR the same domain tag from Step 3B applied to their recommendations). Clustering is by **conceptual surface**, deliberately coarser than Step 3C's file-level analysis — the goal is to surface plan-level contradictions the file layer cannot see.
311
-
312
- **Forward reference — related future signal** <!-- Added: forge#1196 -->: This clustering step (Step 2.5B) runs before Phase 3, so no Layer 5 data exists yet at this point — do not treat it as an input here. For readers extending this clustering rule in the future: Step 3C Layer 5 (historical co-change coupling, computed later in Phase 3) is a related signal worth reusing — two investigations whose affected files have historically co-changed would be good candidates for the same subsystem cluster. This is purely a forward reference, not a functional dependency or phase-reordering.
313
-
314
- ### Step 2.5C: Detect and resolve competing recommendations
315
-
316
- Within each cluster (2+ investigations on the same subsystem), compare the **Recommendation** sections for incompatibility — e.g. one recommends adding a cache layer while another recommends removing caching from the same path; one proposes a new abstraction another proposes to delete. This is a semantic comparison of *proposed approaches*, read purely from the annotation prose.
317
-
318
- For each detected conflict, resolve it in exactly ONE of two ways:
319
-
320
- 1. **Arbitration decision** — When the two recommendations are directly incompatible and one is clearly correct given the combined evidence, record a single deconflicted decision for BOTH issues. State which approach wins and why. This decision is written into each affected issue's `FORGE:SYNTHESIS_BRIEF` (Step 2.5D) — it does NOT close either issue; both still run, but against a reconciled plan.
321
- 2. **Serialization edge** — When the approaches are interdependent (the second issue's correct approach depends on what the first decides) or arbitration cannot pick a winner from annotations alone, add a `Depends on #{FIRST}` marker to the SECOND issue's body so the two serialize. The second agent then inherits the first's merged result during its own investigation/context phase.
322
-
323
- ```bash
324
- # Serialization is expressed as a standard "Depends on #X" edge so Step 3A consumes it
325
- # with no new plumbing (see Step 3A dependency-marker parsing).
326
- RECONCILED_COUNT=0
327
- N_ARBITRATED=0
328
- N_SERIALIZED=0
329
- for CONFLICT in "${DETECTED_CONFLICTS[@]}"; do
330
- # CONFLICT = "FIRST SECOND RESOLUTION" where RESOLUTION is "arbitrate" or "serialize"
331
- set -- $CONFLICT; FIRST=$1; SECOND=$2; RESOLUTION=$3
332
- if [ "$RESOLUTION" = "serialize" ]; then
333
- # Reverse-direction cycle guard: before adding "#SECOND depends on #FIRST", check
334
- # whether #FIRST already declares "Depends on #SECOND". If it does, the requested
335
- # edge would close a 2-node cycle (#FIRST -> #SECOND -> #FIRST) that Step 3D.5's
336
- # cycle detector would later have to exclude from dispatch entirely (both issues
337
- # stuck behind needs-human). Skip the edge and fall back to arbitration-in-place
338
- # instead, so both issues still run.
339
- FIRST_BODY=$(gh issue view $FIRST -R {GH_REPO} --json body --jq '.body')
340
- if echo "$FIRST_BODY" | grep -qiE "depends on #${SECOND}\b"; then
341
- echo "Phase 2.5: skipping serialization edge #${FIRST} -> #${SECOND}: reverse edge #${SECOND} -> #${FIRST} already exists (would create a cycle). Falling back to arbitration-in-place."
342
- RESOLUTION="arbitrate"
343
- else
344
- SECOND_BODY=$(gh issue view $SECOND -R {GH_REPO} --json body --jq '.body')
345
- if ! echo "$SECOND_BODY" | grep -qiE "depends on #${FIRST}\b"; then
346
- gh issue edit $SECOND -R {GH_REPO} \
347
- --body "${SECOND_BODY}
348
-
349
- Depends on #${FIRST}
350
- <!-- Serialized by orchestrate Phase 2.5: competing recommendation reconciled via dependency edge. -->"
351
- fi
352
- fi
353
- fi
354
- # Re-check RESOLUTION (may have been downgraded from "serialize" to "arbitrate" above)
355
- # so the breakdown counters and the Step 2.5D per-issue decision recording both reflect
356
- # the resolution that was actually applied, not the one originally proposed.
357
- if [ "$RESOLUTION" = "serialize" ]; then
358
- N_SERIALIZED=$((N_SERIALIZED + 1))
359
- else
360
- N_ARBITRATED=$((N_ARBITRATED + 1))
361
- fi
362
- RECONCILED_COUNT=$((RECONCILED_COUNT + 1))
363
- done
364
- echo "Phase 2.5 reconciled ${RECONCILED_COUNT} competing recommendation(s) (${N_ARBITRATED} arbitrated, ${N_SERIALIZED} serialized)."
365
- ```
366
-
367
- **MUST NOT**: close, skip, or merge any issue on the basis of a detected conflict. Two issues with competing recommendations are BOTH valid work items — Phase 2.5 makes their plans coherent, it does not eliminate either. (This is the Safety Rule 9 boundary — see the Purpose note above.)
368
-
369
- ### Step 2.5D: Emit one deconflicted brief per issue
370
-
371
- For each implementation issue about to be dispatched, write a single `FORGE:SYNTHESIS_BRIEF` annotation containing ONLY the reconciled context relevant to *that* issue — the arbitration decisions affecting it and pointers to the specific sibling investigation Gists it actually needs. This replaces injecting the entire aggregated milestone-index gist (which forces each agent to independently re-arbitrate the same contradictions, wasting tokens and producing nondeterministic cross-PR incoherence).
372
-
373
- ```bash
374
- for ISSUE_NUM in {implementation_issue_numbers}; do
375
- # Assemble the per-issue brief: arbitration decisions touching this issue's subsystem +
376
- # only the relevant sibling gist URLs (not the full milestone-index dump)
377
- BRIEF_BODY="Reconciled context for this issue (see orchestrate Phase 2.5):
378
- ${PER_ISSUE_DECISIONS[$ISSUE_NUM]}"
379
- gh issue comment $ISSUE_NUM -R {GH_REPO} --body "<!-- FORGE:SYNTHESIS_BRIEF -->
380
- ## Synthesis Brief
381
-
382
- ${BRIEF_BODY}
383
-
384
- <!-- FORGE:SYNTHESIS_BRIEF:COMPLETE -->"
385
- done
386
- ```
387
-
388
- The `FORGE:SYNTHESIS_BRIEF` annotation is consumed by Step 4A's `{GIST_CONTEXT}` generation (which prefers it over the raw milestone-index gist when present) and its reconciled count (`RECONCILED_COUNT`) feeds the Step 6B `Competing recommendations reconciled (Phase 2.5)` metric.
389
-
390
- **Report**: Post a brief Phase 2.5 summary to the user before proceeding to Phase 3:
391
-
392
- ```
393
- ## Phase 2.5: Investigation Synthesis
394
-
395
- **Investigations reconciled**: {INVESTIGATION_COUNT}
396
- **Competing recommendations detected**: {RECONCILED_COUNT}
397
- - Arbitrated in place: {N_ARBITRATED} (includes any serialization edges downgraded by the reverse-cycle guard)
398
- - Serialized via dependency edge: {N_SERIALIZED}
399
- **Per-issue synthesis briefs emitted**: {N_briefs}
400
-
401
- Proceeding to dependency analysis with a deconflicted plan set...
402
- ```
403
-
404
- ---
405
-
406
- ## Phase 3: Dependency Analysis & Execution Plan
407
-
408
- ### Step 3A: Analyze explicit dependencies
409
-
410
- For each issue (including newly spawned ones from Phase 2), check:
411
- 1. **Explicit dependencies**: Issue body contains "Depends on #X" or "Blocked by #X"
412
- 2. **Milestone ordering**: Issues within a milestone may have a natural order (foundation → features → UI)
413
- 3. **File conflicts**: If two issues modify the same files, they should be sequential (not parallel)
414
- 4. **Parent-child links**: If issue body contains "Parent investigation: #{X}", it was spawned by an investigation — no special ordering needed unless explicitly stated
415
-
416
- Read each issue's body briefly to check for dependency markers:
417
- ```bash
418
- for NUM in {issue_numbers}; do
419
- gh issue view $NUM --json body --jq '.body' | grep -iE 'depends on|blocked by|after #|parent investigation' || echo "no deps"
420
- done
421
- ```
422
-
423
- ### Step 3B: Domain estimation
424
-
425
- For each issue, estimate which domains it touches based on title, body, and labels. This improves DAG construction — issues in the same domain likely touch the same files and should be serialized (one becomes a predecessor of the other).
426
-
427
- ```bash
428
- for NUM in {issue_numbers}; do
429
- ISSUE=$(gh issue view $NUM --json title,body,labels --jq '{title: .title, labels: [.labels[].name], body: (.body[:300])}')
430
- echo "=== #$NUM ==="
431
- echo "$ISSUE" | grep -qiE "credit|billing|pricing|stripe|charge|refund" && echo " BILLING" || true
432
- echo "$ISSUE" | grep -qiE "auth|session|jwt|login|permission|oauth" && echo " AUTH" || true
433
- echo "$ISSUE" | grep -qiE "worker|queue|job|task|background|consumer" && echo " WORKER" || true
434
- echo "$ISSUE" | grep -qiE "migration|\.sql|database|postgres|alembic" && echo " DATABASE" || true
435
- echo "$ISSUE" | grep -qiE "component|page|layout|dashboard|ui|ux|frontend|web/src" && echo " FRONTEND" || true
436
- echo "$ISSUE" | grep -qiE "docker|deploy|traefik|nginx|ci|cd|infra|github.action" && echo " INFRA" || true
437
- echo "$ISSUE" | grep -qiE "llm|extract|schema|format|embedding|model" && echo " AI" || true
438
- # For project-specific domains, configure keywords in forge.yaml → review.domains and extend above
439
- done
440
- ```
441
-
442
- **Use domain info for DAG edge construction:**
443
- - Issues in the SAME domain (especially WORKER, BILLING, DATABASE) are more likely to touch the same files → add predecessor edges to serialize them
444
- - Issues in DIFFERENT domains are more likely independent → safe to parallelize
445
- - BILLING + AUTH issues should be prioritized early (security-critical)
446
- - **DATABASE issues are ALWAYS serialized — hard rule, no exceptions.** Multiple agents writing migrations simultaneously will produce duplicate migration numbers (e.g., two `0067_*.sql` files), which breaks the migration runner. DATABASE issues form a linear predecessor chain in the DAG. If 3 DATABASE issues are in a batch: A has no predecessors, B has {A} as predecessor, C has {B} as predecessor.
447
-
448
- **Store domain tags per issue** for use in the plan presentation (Step 3E).
449
-
450
- ### Step 3C: Multi-layer conflict detection
451
-
452
- Domain estimation (above) catches broad category overlap but misses cases where two issues modify the exact same file without mentioning the same keywords, or where two issues touch different files that share indirect dependencies (imports, config, barrel exports). This step uses three layers of structural analysis to catch conflicts the keyword heuristic misses.
453
-
454
- #### Layer 1: Explicit file-overlap extraction
455
-
456
- **For issues that already have an INVESTIGATOR comment** (from Wave 0 or a prior session), extract their Affected Files list. **For issues WITHOUT an investigation comment**, fall back to parsing the issue body for file paths. Both code paths accumulate into a single `LAYER1_FILES` array (declared once, before the loop) — this is the batch-wide file set that Layer 5's co-change query (below) reuses, per the "file list already extracted in Layer 1" reference in Layer 2 and Layer 5:
457
-
458
- ```bash
459
- LAYER1_FILES=()
460
- for NUM in {issue_numbers}; do
461
- echo "=== #$NUM ==="
462
- FILES_FOR_NUM=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
463
- --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body' 2>/dev/null \
464
- | grep -oP '`[^`]*\.(py|tsx?|jsx?|sql|json|ya?ml)`' | tr -d '`' | sort -u)
465
-
466
- # Fall back to parsing the issue body directly when no INVESTIGATOR comment exists yet.
467
- if [ -z "$FILES_FOR_NUM" ]; then
468
- FILES_FOR_NUM=$(gh issue view $NUM --json body --jq '.body' \
469
- | grep -oP '`[^`]*\.(py|tsx?|jsx?|sql|json|ya?ml)`' | tr -d '`' | sort -u)
470
- fi
471
-
472
- echo "$FILES_FOR_NUM"
473
-
474
- # Accumulate into the batch-wide array — read line-by-line so each extracted path
475
- # becomes one array element (paths here don't contain spaces, but this stays robust).
476
- while IFS= read -r f; do
477
- [ -n "$f" ] && LAYER1_FILES+=("$f")
478
- done <<< "$FILES_FOR_NUM"
479
- done
480
- ```
481
-
482
- **Cross-reference all extracted file lists:**
483
- - If two issues share ANY affected file → one MUST be a predecessor of the other (serialized)
484
- - The issue with lower issue number goes first (stable ordering), unless an explicit `Depends on #` says otherwise
485
- - Add a conflict note to the DAG plan: "#{A} and #{B} both modify `{file}` — #{A} is predecessor of #{B}"
486
-
487
- #### Layer 2: Directory-proximity detection
488
-
489
- Two issues that modify different files in the **same leaf directory** have a high probability of conflicting through shared `__init__.py`, `index.ts`, barrel re-exports, or tightly coupled sibling modules. This layer catches indirect conflicts that Layer 1 misses.
490
-
491
- **Extract the leaf directory for each affected file:**
492
-
493
- ```bash
494
- # For each issue's file list, extract unique directories
495
- for NUM in {issue_numbers}; do
496
- echo "=== #$NUM directories ==="
497
- # From the file list already extracted in Layer 1:
498
- echo "$FILES_FOR_NUM" | xargs -I{} dirname {} | sort -u
499
- done
500
- ```
501
-
502
- **Cross-reference directory lists:**
503
- - If two issues share a leaf directory AND that directory is "small" (contains fewer than 10 tracked files), flag as **probable conflict** → serialize
504
- - If the shared directory is a broad container (e.g., `services/api/app/routers/` with 15+ files), downgrade to **possible conflict** → serialize only if same domain tag (from Step 3B)
505
- - **Known high-conflict directories** (always serialize if shared):
506
- - `services/api/app/models/` (SQLAlchemy models often share Base, imports)
507
- - `services/api/app/core/` (shared dependencies)
508
- - `services/worker/worker/` (tightly coupled consumer modules)
509
- - `web/src/lib/` (shared utilities)
510
- - `shared/` (volume-mounted, affects all services)
511
- - `infra/migrations/` (already covered by DATABASE hard rule, but explicit here too)
512
-
513
- #### Layer 3: Shared-module inference
514
-
515
- When two issues modify different files that **import from the same utility/init module**, both agents often end up modifying that shared module (adding imports, updating re-exports). This creates merge conflicts invisible to Layer 1.
516
-
517
- **Heuristic rules (no git operations needed — pattern-based):**
518
-
519
- | Pattern | Inference | Action |
520
- |---------|-----------|--------|
521
- | Issue A modifies `routers/billing.py`, Issue B modifies `routers/auth.py` | Both likely import from `routers/__init__.py` or `dependencies.py` | Flag as possible conflict if same service |
522
- | Issue A modifies `models/user.py`, Issue B modifies `models/subscription.py` | Both likely modify `models/__init__.py` (SQLAlchemy model registry) | Flag as probable conflict → serialize |
523
- | Issue A modifies `web/src/components/X.tsx`, Issue B modifies `web/src/components/Y.tsx` | May share `index.ts` barrel export | Flag only if same parent directory |
524
- | Issue A modifies `services/api/app/main.py` | `main.py` is a high-fan-in file (router registration, middleware) | Serialize with ANY other API issue |
525
- | Issue A modifies `docker-compose.yml` or `docker-compose.prod.yml` | Global config — any concurrent modification conflicts | Serialize with ALL other issues that touch infra |
526
-
527
- **Apply inferences:**
528
- ```
529
- # High-fan-in files — if ANY issue touches these, serialize it with all same-service issues.
530
- # Read layout paths from forge.yaml review.layout; fall back to sensible generic defaults.
531
- # Example (pseudo-code — adapt to your forge.yaml parsing method):
532
- # API_MAIN = forge_yaml.review.layout.api_main ?? "services/api/app/main.py"
533
- # WORKER_MAIN = forge_yaml.review.layout.worker_main ?? "services/worker/worker/main.py"
534
- # PAGES_ROOT = forge_yaml.review.layout.pages ?? "web/src/app"
535
-
536
- HIGH_FAN_IN = [
537
- API_MAIN, # e.g. "services/api/app/main.py" — router/middleware registration
538
- WORKER_MAIN, # e.g. "services/worker/worker/main.py" — worker entrypoint (set forge.yaml review.layout.worker_main)
539
- PAGES_ROOT + "/layout.tsx", # e.g. "web/src/app/layout.tsx" — root layout for all pages
540
- "docker-compose.yml",
541
- "docker-compose.prod.yml",
542
- ".env.example"
543
- ]
544
-
545
- # For each issue, check if affected files include a high-fan-in file
546
- # If yes: that issue cannot be parallelized with any other issue touching the same service
547
- ```
548
-
549
- #### Layer 4: Conservative fallback (low-confidence cases)
550
-
551
- When file extraction yields **fewer than 2 file paths** for an issue (common for issues without investigation comments, or issues described in prose without backtick-wrapped paths), the conflict detection has low confidence. Rather than assuming independence, apply conservative serialization.
552
-
553
- **Rules:**
554
- - If an issue has 0-1 extracted file paths AND shares a domain tag (from Step 3B) with another issue → serialize them
555
- - If an issue has 0 extracted file paths AND no domain tag could be determined → add it as a predecessor of the next same-domain issue, or if no domain match exists, serialize it after the most recently added issue (safest default)
556
- - Add a note to the plan: "#{N} — low file-extraction confidence, serialized conservatively"
557
-
558
- **Rationale**: The cost of a false-negative (two agents conflict → one fails with merge error, wasting the full agent run) far exceeds the cost of a false-positive (an issue waits for one predecessor before starting). Always err toward serialization when uncertain.
559
-
560
- #### Layer 5: Historical co-change coupling <!-- Added: forge#1196 --> <!-- Empty-set guard: forge#1206 -->
561
-
562
- Layers 1-4 infer conflict risk from structure — path overlap, directory nesting, hard-coded high-fan-in lists. They miss the case where two files with no directory or naming relationship have historically changed together in the same commits (e.g. `models/user.py` and `services/billing/charge.py`), and they over-serialize the inverse case where files merely sit near each other but have never actually co-changed. Git commit history answers both questions directly and empirically. This layer reads **commit metadata only** — the list of files touched per commit — never file contents, so it does not violate Hard Rule 2 ("dispatcher, not a builder... never read code"). This is the same category of operation already established as compliant in `commands/work-on/investigate.md` and `commands/work-on/build/context.md`, which mine `git log` for issue cross-referencing.
563
-
564
- **Bounded query** — restrict both the time window and the file set so this stays a small, deterministic, single-shot lookup (never a full-repo co-change matrix). An empty `ALL_AFFECTED_FILES` set MUST short-circuit before the query runs — passing an empty array to `git log --` does not mean "match nothing", it means "no pathspec restriction", which would silently widen the query to the entire repo:
565
-
566
- ```bash
567
- # Union of affected files across all issues in the CURRENT batch only (already
568
- # extracted per-issue in Layer 1) — never the whole repo. Built as an array
569
- # (not a newline-joined scalar) so each path survives as a single pathspec
570
- # argument below — a plain string here would be word-split and glob-expanded
571
- # by the shell when handed to `git log --`, silently mangling or dropping any
572
- # path containing a space or glob metacharacter.
573
- mapfile -t ALL_AFFECTED_FILES < <(printf '%s\n' "${LAYER1_FILES[@]}" | sort -u)
574
-
575
- # Guard: an empty array expands to nothing after `--`, which git interprets as
576
- # "no pathspec restriction" (i.e. the whole repo) rather than "match nothing".
577
- # Skip the query entirely in that case instead of letting it silently widen to
578
- # a full-repo scan — this can happen when upstream file-extraction (Layer 1)
579
- # yields zero paths for every issue in the batch.
580
- if [ "${#ALL_AFFECTED_FILES[@]}" -eq 0 ]; then
581
- echo "Layer 5: no affected files extracted by Layer 1 for this batch — skipping co-change query, falling back to Layers 1-4."
582
- else
583
- # Bounded window: last 90 days, capped at 200 commits — whichever is smaller.
584
- # Each commit's file list is delimited by a marker so co-occurring files can be
585
- # grouped per-commit in a single pass. The array is expanded quoted
586
- # ("${ALL_AFFECTED_FILES[@]}") so every path is passed as one literal argument.
587
- git log --name-only --since="90 days ago" --max-count=200 \
588
- --pretty=format:'---%H---' -- "${ALL_AFFECTED_FILES[@]}" \
589
- > /tmp/cochange_log.txt
590
-
591
- # Parse into commit → file-set groups, then increment a co-occurrence counter
592
- # for every unordered pair of files that appear in the SAME commit's file list.
593
- # (Illustrative — an agent executing this reads /tmp/cochange_log.txt and tallies
594
- # pairs; no separate script is shipped, matching the pseudo-code style of Layers 1-4.)
595
- fi
596
- ```
597
-
598
- **Scoring rule**: A file pair is **co-change coupled** when it appears together in **3 or more** of the commits captured by the bounded query above. A pair with **zero** co-occurrences across the entire window is **verified independent**.
599
-
600
- **Apply the signal:**
601
- - **High co-change pair spans two different issues in the batch** → add a serialization edge between them (same directed-edge convention as Layers 1-4: lower issue number is predecessor), OR, if the pair also carries competing investigation recommendations, flag it for Phase 2.5 arbitration instead of a blind serialization edge (see cross-reference in Step 2.5B below).
602
- - **Verified-independent pair** → MAY be used to downgrade an existing Layer 2 "broad directory + different domain" or Layer 4 "conservative fallback" serialization to parallel. This downgrade is **never** applied to Layer 1 (same-file hard conflict, which is ground truth from the current batch, not historical inference) or to Layer 3 high-fan-in-file edges (a file can be structurally high-risk even with a thin history window, e.g. a newly added `main.py`).
603
- - If `ALL_AFFECTED_FILES` is empty, the guard above skips the query and Layer 5 contributes nothing for the entire batch. If the bounded query runs but returns no commits (e.g. brand-new files, or window/pair-set too small to have history) → Layer 5 contributes nothing; fall back silently to Layers 1-4's existing verdict for that pair.
604
-
605
- **Rationale**: Empirical co-change is a strictly stronger signal than directory proximity or naming convention — it is the actual observed outcome the other layers are trying to approximate. Bounding the window and pair-set keeps the query cheap and deterministic while still catching the cross-directory conflicts Layers 1-4 structurally cannot see.
606
-
607
- #### Combining all layers
608
-
609
- Build the final conflict graph by merging signals from all five layers:
610
-
611
- | Signal | Strength | Action |
612
- |--------|----------|--------|
613
- | Layer 1: Same file | **Hard conflict** | Always serialize |
614
- | Layer 2: Same small directory | **Probable conflict** | Serialize |
615
- | Layer 2: Same broad directory + same domain | **Probable conflict** | Serialize |
616
- | Layer 2: Same broad directory + different domain | **Possible conflict** | Parallelize (accept risk) — unless Layer 5 shows high co-change, then serialize |
617
- | Layer 3: High-fan-in file touched | **Probable conflict** | Serialize with same-service issues |
618
- | Layer 3: Shared model/init pattern | **Probable conflict** | Serialize |
619
- | Layer 4: Low confidence + same domain | **Conservative** | Serialize |
620
- | Layer 4: Low confidence + no domain | **Conservative** | Add predecessor edge to most recent issue — unless Layer 5 shows verified independence, then parallelize |
621
- | Layer 5: High co-change (3+ shared commits, cross-issue file pair) | **Probable conflict** | Serialize (or route to Phase 2.5 arbitration if a competing-recommendation conflict is also present) |
622
- | Layer 5: Verified independent (zero shared commits) | **Downgrade signal** | Permits downgrading a Layer 2 "broad directory + different domain" or Layer 4 verdict to parallel — never overrides Layer 1 or Layer 3 |
623
-
624
- **This supplements, not replaces, the domain keyword estimation.** Domain tags still help with broad sequencing decisions. Multi-layer conflict detection catches the specific cases keywords miss (e.g., two issues that both modify files in `services/api/app/models/` where one is labeled WORKER and the other BILLING — Layer 2 catches this even though Layer 1 shows no direct file overlap; or two issues touching files in unrelated directories that Layer 5 shows have co-changed in 4 of the last 12 commits touching either file).
625
-
626
- ### Step 3D: Build the dependency DAG
627
-
628
- Build a **directed acyclic graph (DAG)** of per-issue dependencies. Each issue gets a `predecessors` set — the specific issues that must reach a terminal state before this issue can dispatch. This replaces the previous wave-grouping model where all issues in a wave had to complete before any issue in the next wave could start.
629
-
630
- **DAG construction rules:**
631
- - Investigation issues already ran in Phase 2 — they are NOT included in this DAG
632
- - Each issue starts with an empty predecessor set
633
- - **Explicit dependencies**: If issue B says "Depends on #A" or "Blocked by #A", add A to B's predecessors
634
- - **File-conflict edges**: If two issues share affected files (from Step 3C Layer 1), add a directed edge: lower issue number → higher issue number (unless explicit deps say otherwise). The later issue has the earlier issue in its predecessors.
635
- - **Domain serialization edges**: DATABASE issues form a linear chain (each has the previous DATABASE issue as its predecessor). Same-small-directory issues (Layer 2) and high-fan-in file issues (Layer 3) get directed edges as per Step 3C rules.
636
- - **Conservative fallback edges**: Low-confidence issues (Layer 4) get edges to same-domain issues as per Step 3C rules.
637
- - **Co-change coupling edges** <!-- Added: forge#1196 -->: High co-change file pairs (Layer 5, 3+ shared commits in the bounded window) that span two different issues get a directed edge using the same lower-issue-number-is-predecessor convention as Layer 1. Verified-independent pairs (Layer 5, zero shared commits) may instead REMOVE an edge that Layer 2 or Layer 4 would otherwise have added for that pair — Layer 1 and Layer 3 edges are never removed by a Layer 5 downgrade.
638
- - **No artificial concurrency limit** — all issues with empty predecessor sets dispatch simultaneously. The only constraints are file overlap, explicit dependencies, and co-change coupling.
639
-
640
- **Terminology:**
641
- - **Ready issues**: Issues whose predecessor set is empty (all predecessors have reached terminal state or were never added)
642
- - **Blocked issues**: Issues with one or more unresolved predecessors
643
- - **Critical path**: The longest chain of dependent issues in the DAG — determines minimum wall-clock time
644
-
645
- **Example DAG:**
646
- ```
647
- Phase 2 (already done): #2644 (investigation) → spawned #2645, #2646, #2647
648
-
649
- Dependency graph:
650
- #2633 (orphaned queues) → predecessors: {} ← READY
651
- #2636 (invalidation sub) → predecessors: {} ← READY
652
- #2645 (new finding) → predecessors: {} ← READY
653
- #2646 (new finding) → predecessors: {} ← READY
654
- #2634 (enable daemon) → predecessors: {#2633} ← blocked until #2633 completes
655
- #2647 (depends on #2645) → predecessors: {#2645} ← blocked until #2645 completes
656
-
657
- Critical path: #2633 → #2634 (2 steps) or #2645 → #2647 (2 steps)
658
- Initial dispatch: #2633, #2636, #2645, #2646 (all ready — launched simultaneously)
659
- ```
660
-
661
- **Key advantage over waves**: When #2633 completes, #2634 dispatches immediately — it does not wait for #2636, #2645, or #2646 to finish. Similarly, when #2645 completes, #2647 dispatches immediately regardless of other issues' status.
662
-
663
- ### Step 3D.5: Cycle Detection (MANDATORY) <!-- Added: forge#1085 -->
664
-
665
- **Run immediately after Step 3D's DAG edge construction, before presenting the plan (Step 3E).** This step validates that the predecessor graph is acyclic. Without it, mutual `Depends on` declarations (e.g., A depends on B AND B depends on A) cause both issues to remain permanently blocked in Step 4B's dispatch loop — no error, no timeout, indefinite deadlock.
666
-
667
- **Algorithm**: Kahn's topological sort. Runs in O(V+E) — negligible overhead for typical batch sizes.
668
-
669
- ```bash
670
- # --- Step 3D.5: Cycle Detection ---
671
- # Inputs:
672
- # ISSUES[] — array of all issue numbers in the DAG
673
- # PREDECESSORS[N] — space-separated list of predecessor issue numbers for issue N
674
-
675
- # Step 1: Compute in-degree for each issue
676
- declare -A IN_DEGREE
677
- for NUM in "${ISSUES[@]}"; do
678
- IN_DEGREE[$NUM]=0
679
- done
680
- for NUM in "${ISSUES[@]}"; do
681
- for PRED in ${PREDECESSORS[$NUM]:-}; do
682
- IN_DEGREE[$NUM]=$(( ${IN_DEGREE[$NUM]:-0} + 1 ))
683
- done
684
- done
685
-
686
- # Step 2: Seed the queue with zero-in-degree issues (no predecessors)
687
- KAHN_QUEUE=()
688
- for NUM in "${ISSUES[@]}"; do
689
- [ "${IN_DEGREE[$NUM]}" -eq 0 ] && KAHN_QUEUE+=("$NUM")
690
- done
691
-
692
- # Step 3: Process queue — reduce successor in-degrees, enqueue newly freed issues
693
- PROCESSED_COUNT=0
694
- PROCESSED_ORDER=()
695
- while [ "${#KAHN_QUEUE[@]}" -gt 0 ]; do
696
- # Dequeue
697
- CURRENT="${KAHN_QUEUE[0]}"
698
- KAHN_QUEUE=("${KAHN_QUEUE[@]:1}")
699
- PROCESSED_ORDER+=("$CURRENT")
700
- PROCESSED_COUNT=$(( PROCESSED_COUNT + 1 ))
701
-
702
- # Reduce in-degree of all issues that depend on CURRENT (i.e., CURRENT is in their PREDECESSORS)
703
- for SUCCESSOR in "${ISSUES[@]}"; do
704
- for PRED in ${PREDECESSORS[$SUCCESSOR]:-}; do
705
- if [ "$PRED" = "$CURRENT" ]; then
706
- IN_DEGREE[$SUCCESSOR]=$(( ${IN_DEGREE[$SUCCESSOR]} - 1 ))
707
- [ "${IN_DEGREE[$SUCCESSOR]}" -eq 0 ] && KAHN_QUEUE+=("$SUCCESSOR")
708
- fi
709
- done
710
- done
711
- done
712
-
713
- # Step 4: Any issue not processed has in-degree > 0 — part of a cycle
714
- CYCLE_ISSUES=()
715
- EXCLUDED_CYCLE=()
716
- for NUM in "${ISSUES[@]}"; do
717
- FOUND=false
718
- for P in "${PROCESSED_ORDER[@]}"; do [ "$P" = "$NUM" ] && FOUND=true && break; done
719
- [ "$FOUND" = "false" ] && CYCLE_ISSUES+=("$NUM")
720
- done
721
-
722
- # Step 5: Handle cycles
723
- if [ "${#CYCLE_ISSUES[@]}" -gt 0 ]; then
724
- echo "CYCLE DETECTED in dependency graph — the following issues form a circular dependency:"
725
- for C in "${CYCLE_ISSUES[@]}"; do
726
- echo " #${C}: predecessors=[${PREDECESSORS[$C]}]"
727
- # Label each cyclic issue needs-human
728
- gh issue edit "$C" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
729
- gh issue comment "$C" -R {GH_REPO} --body "**Cycle detected by /orchestrate**: This issue is part of a circular dependency chain involving issues: ${CYCLE_ISSUES[*]/#/#}. The orchestrator cannot dispatch it automatically. Please fix the \`Depends on\` declarations so that no cycle exists, then re-run /orchestrate." 2>/dev/null || true
730
- # Remove from DAG — store in EXCLUDED_CYCLE for Step 3E reporting
731
- EXCLUDED_CYCLE+=("$C")
732
- # Remove from ISSUES array for all downstream processing
733
- # Use exact-match filter loop — pattern substitution (${array[@]/pattern}) leaves blank
734
- # slots and corrupts partial matches (e.g., removing 100 changes 1000 to 0).
735
- NEW_ISSUES=()
736
- for I in "${ISSUES[@]}"; do
737
- [ "$I" != "$C" ] && NEW_ISSUES+=("$I")
738
- done
739
- ISSUES=("${NEW_ISSUES[@]}")
740
- done
741
- echo ""
742
- echo "These issues have been labeled needs-human and excluded from the DAG."
743
- echo "Fix their dependency declarations and re-run /orchestrate."
744
- else
745
- echo "DAG cycle check: PASS — no cycles detected. Proceeding with ${#PROCESSED_ORDER[@]} issues."
746
- fi
747
-
748
- # Guard: if all issues were cyclic, ISSUES[] is now empty — abort before presenting an empty plan
749
- if [ "${#ISSUES[@]}" -eq 0 ]; then
750
- echo ""
751
- echo "ERROR: All issues in this batch form circular dependencies and have been excluded."
752
- echo "Every issue has been labeled needs-human."
753
- echo "Fix the Depends on / Blocked by declarations so no cycle exists, then re-run /orchestrate."
754
- exit 1
755
- fi
756
- # --- End Step 3D.5 ---
757
- ```
758
-
759
- **After this step**:
760
- - `ISSUES[]` contains only acyclic issues — safe to dispatch
761
- - `EXCLUDED_CYCLE[]` contains cyclic issue numbers — reported in Step 3E, never dispatched
762
- - If `EXCLUDED_CYCLE` is non-empty, report it clearly in the Step 3E plan before asking for user confirmation
763
- - If `ISSUES[]` is empty after cycle exclusion (all issues were cyclic), the guard above aborts with `exit 1` — Step 3E is never reached with an empty plan <!-- Added: forge#1110 -->
764
-
765
- ### Step 3E: Present the plan to the user
766
-
767
- ```
768
- ## Orchestration Plan
769
-
770
- **Scope**: {milestone name / "N issues" / "fast-lane"}
771
- **Total issues**: {count} ({investigation_count} investigations + {implementation_count} implementations)
772
- **Execution model**: Dependency-graph streaming (issues dispatch as predecessors complete)
773
-
774
- {IF investigations exist:}
775
- ### Investigations (run first, may spawn new issues)
776
- | # | Title | Expected Output |
777
- |---|-------|----------------|
778
- | #{INV1} | {title} | New issues → folded into dependency graph |
779
-
780
- ### Implementation (after investigations complete)
781
- {END IF}
782
-
783
- ### Domain Distribution
784
- | Domain | Issues | Notes |
785
- |--------|--------|-------|
786
- | FRONTEND | {N} | {Independent pages / Shared components} |
787
- | BILLING | {N} | {Critical — dispatches immediately} |
788
- | DATABASE | {N} | {Serialized chain — migration order matters} |
789
- | AUTH | {N} | {Critical — dispatches immediately} |
790
- | WORKER | {N} | {High overlap risk within worker service} |
791
- | AI | {N} | {Independent} |
792
- | INFRA | {N} | {Independent} |
793
-
794
- (Omit rows with 0 issues. Add project-specific domain rows from forge.yaml → review.domains.)
795
-
796
- ### Dependency Graph
797
-
798
- | Issue | Predecessors | Domain | Status |
799
- |-------|-------------|--------|--------|
800
- | #{A} | — | FRONTEND | Ready (dispatches immediately) |
801
- | #{B} | — | BILLING | Ready (dispatches immediately) |
802
- | #{C} | — | WORKER | Ready (dispatches immediately) |
803
- | #{D} | #{A} | FRONTEND | Blocked (waits for #{A} only) |
804
- | #{E} | — | DATABASE | Ready (dispatches immediately) |
805
- | #{F} | #{E} | DATABASE | Blocked (serialized — waits for #{E}) |
806
-
807
- **Critical path**: #{E} → #{F} (2 steps, determines minimum wall-clock time)
808
- **Initial dispatch**: #{A}, #{B}, #{C}, #{E} (all predecessors resolved)
809
- **Streaming**: #{D} dispatches as soon as #{A} completes — does NOT wait for #{B}, #{C}, or #{E}
810
-
811
- **Note**: Investigations may create additional issues that will be automatically added to the dependency graph. The final graph will be confirmed after investigations complete.
812
-
813
- **Excluded** (already in progress / ineligible):
814
- - #{X} — {reason}
815
-
816
- {IF EXCLUDED_CYCLE is non-empty:}
817
- ### ⚠ Circular Dependencies Detected — Manual Fix Required
818
-
819
- The following issues form a circular dependency chain and **cannot be dispatched** until the cycle is resolved:
820
-
821
- | Issue | Depends On | Problem |
822
- |-------|------------|---------|
823
- {rows: each EXCLUDED_CYCLE issue, its predecessor list, "mutual dependency — forms cycle with #{other}"}
824
-
825
- **Action required**: Edit each issue's body to remove or correct the `Depends on` / `Blocked by` declarations so no cycle exists. Each issue has been labeled `needs-human`. After fixing, re-run `/orchestrate` to dispatch them.
826
- {END IF}
827
-
828
- Proceed? (yes / adjust / pick specific issues)
829
- ```
830
-
831
- **Wait for user confirmation before spawning agents.** This is the checkpoint — once agents launch, they run autonomously.
832
-
833
- **After confirmation**: If investigations exist, execute Phase 2B-E first. Then re-present the expanded plan (with newly spawned issues added to the dependency graph) for a quick confirmation before launching implementation.
834
-
835
- ---
836
-
837
- ## Phase 4: Streaming DAG Execution
838
-
839
- ### Step 4A-pre: Staging baseline tracking (MANDATORY — continuous)
840
-
841
- **WHY THIS EXISTS**: Milestone-code-onto-staging contamination incidents (see issue #150) produce unexpected growth on the staging branch that is otherwise invisible until after a deploy. In the streaming DAG model, there are no discrete wave boundaries — instead, track a running baseline and check after each agent completion.
842
-
843
- **When to run**: Capture the initial baseline before the first dispatch. Then re-check after every agent that merges a PR targeting `staging`. Skip for pure milestone-branch batches where all issues target `milestone/*`.
844
-
845
- ```bash
846
- # Capture initial staging baseline before first dispatch
847
- git fetch origin
848
- if [ "$DEFAULT_BRANCH" = "$STAGING_BRANCH" ]; then
849
- STAGING_LINES_BASELINE=0
850
- echo "Staging baseline: skipped — single-branch repo (staging == default)"
851
- else
852
- STAGING_LINES_BASELINE=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
853
- | tail -1 \
854
- | grep -oP '\d+ insertion' \
855
- | grep -oP '\d+' \
856
- || echo "0")
857
- echo "Staging baseline: ${STAGING_LINES_BASELINE} lines ahead of $DEFAULT_BRANCH"
858
- fi
859
-
860
- # Track cumulative expected growth from merged PRs
861
- CUMULATIVE_EXPECTED_DELTA=0
862
- ```
863
-
864
- **Per-agent-completion integrity check** (run in Step 4B after each agent merges a PR targeting staging):
865
-
866
- ```bash
867
- # After agent completes and its PR merges to staging:
868
- git fetch origin
869
- if [ "$DEFAULT_BRANCH" != "$STAGING_BRANCH" ]; then
870
- STAGING_LINES_NOW=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
871
- | tail -1 \
872
- | grep -oP '\d+ insertion' \
873
- | grep -oP '\d+' \
874
- || echo "0")
875
- STAGING_TOTAL_GROWTH=$((STAGING_LINES_NOW - STAGING_LINES_BASELINE))
876
-
877
- # Add this PR's line count to cumulative expected delta
878
- CUMULATIVE_EXPECTED_DELTA=$((CUMULATIVE_EXPECTED_DELTA + {THIS_PR_LINE_COUNT}))
879
-
880
- UNEXPECTED_GROWTH=$((STAGING_TOTAL_GROWTH - CUMULATIVE_EXPECTED_DELTA))
881
- if [ "$UNEXPECTED_GROWTH" -gt 500 ]; then
882
- echo "ALERT: Staging grew by ${STAGING_TOTAL_GROWTH} lines (+${UNEXPECTED_GROWTH} beyond expected ${CUMULATIVE_EXPECTED_DELTA})."
883
- echo "This may indicate milestone-code contamination via agent merge commits."
884
- echo "Review: git log --oneline --merges origin/$DEFAULT_BRANCH..origin/$STAGING_BRANCH"
885
- echo "Do NOT merge $STAGING_BRANCH → $DEFAULT_BRANCH until the unexpected growth is investigated."
886
- # Do NOT auto-stop — alert the user and let them decide
887
- fi
888
- fi
889
- ```
890
-
891
- If `UNEXPECTED_GROWTH > 500`, report the alert clearly before dispatching any more agents. The user confirms whether to continue.
892
-
893
- ---
894
-
895
- ### Step 4A.pre.0: Pre-create milestone branches for ready issues (MANDATORY before classify-lane) <!-- Added: forge#901 -->
896
-
897
- **WHY THIS EXISTS**: Feature-lane milestone branches were created lazily — by whichever feature-lane agent reached its build phase first. When multiple agents are dispatched simultaneously, they each run the lane check at roughly the same time. Every agent that runs before the branch is first pushed observes "branch absent" and is misrouted (hard-fail / `needs-human`, or fallback to staging in older code paths). The result is a single milestone's PRs scattered across the milestone branch and staging — a branch-routing nondeterminism that recurs under parallelism.
898
-
899
- The fix is deterministic: create every milestone branch the ready issues will target **once, up front, before any agent runs `classify-lane.sh`**. After this step, every agent's lane check sees the branch and routes consistently.
900
-
901
- **When to run**: Before the classify-lane loop in Step 4A.pre, for every dispatch group (initial ready set + each subsequent batch of newly unblocked issues). The step is a no-op for pure fast-lane issues (no issue in the group has a milestone).
902
-
903
- **Requires bash 4+**: This snippet uses an associative array (`declare -A SEEN_MILESTONE_SLUG`) to de-dupe milestone slugs, so it must run under bash 4 or newer. Under a non-bash POSIX shell (`sh`/dash), `declare -A` fails and the de-dupe silently no-ops. This degrades gracefully — branch creation stays correct because the `git ls-remote --exit-code` exists-check below still skips any milestone branch that already exists; the only effect is redundant, idempotent `ls-remote`/`push` attempts for milestones referenced by more than one issue. Run this command's blocks under bash 4+. <!-- Added: forge#901 -->
904
-
905
- ```bash
906
- # Pre-create the origin milestone branch for every distinct milestone referenced by ready issues.
907
- # Slugification MUST byte-match scripts/classify-lane.sh — otherwise a branch is created that
908
- # the classifier will not select. Keep these two slug pipelines identical.
909
- git fetch origin
910
-
911
- # Collect distinct milestone titles among the ready issues
912
- declare -A SEEN_MILESTONE_SLUG
913
- for NUM in {ready_issue_numbers}; do
914
- MILESTONE_TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null || echo "")
915
- [ -z "$MILESTONE_TITLE" ] && continue # fast-lane issue — no milestone branch needed
916
-
917
- # Slugify — IDENTICAL to classify-lane.sh: lowercase → spaces-to-hyphens →
918
- # strip non-[a-z0-9-] → collapse hyphens → strip leading/trailing hyphens.
919
- SLUG=$(echo "$MILESTONE_TITLE" \
920
- | tr '[:upper:]' '[:lower:]' \
921
- | tr ' ' '-' \
922
- | tr -cd 'a-z0-9-' \
923
- | sed 's/--*/-/g' \
924
- | sed 's/^-//;s/-$//')
925
-
926
- # Empty-slug guard (matches classify-lane.sh): a title with no ASCII letters/digits/hyphens
927
- # would produce "milestone/", an invalid ref. Skip and let classify-lane surface the error.
928
- if [ -z "$SLUG" ]; then
929
- echo "WARN: milestone title '$MILESTONE_TITLE' (issue #$NUM) produced an empty slug — skipping pre-creation; classify-lane will hard-fail." >&2
930
- continue
931
- fi
932
-
933
- # De-dupe: only attempt creation once per milestone
934
- [ -n "${SEEN_MILESTONE_SLUG[$SLUG]:-}" ] && continue
935
- SEEN_MILESTONE_SLUG[$SLUG]=1
936
-
937
- LANE="milestone/$SLUG"
938
- if git ls-remote --exit-code origin "$LANE" >/dev/null 2>&1; then
939
- echo "Milestone branch '$LANE' already exists on origin — no action."
940
- continue
941
- fi
942
-
943
- # Create-if-absent from the default branch (matches /milestone create).
944
- # $DEFAULT_BRANCH is resolved from forge.yaml at the top of this command.
945
- echo "Pre-creating milestone branch '$LANE' from origin/$DEFAULT_BRANCH …"
946
- if git push origin "origin/$DEFAULT_BRANCH:refs/heads/$LANE" 2>/dev/null; then
947
- echo "Created milestone branch '$LANE'."
948
- elif git ls-remote --exit-code origin "$LANE" >/dev/null 2>&1; then
949
- # A concurrent orchestrator (or an agent) created it first — harmless. Never force-push.
950
- echo "Milestone branch '$LANE' was created concurrently — proceeding with the existing branch."
951
- else
952
- echo "ERROR: failed to pre-create milestone branch '$LANE' from origin/$DEFAULT_BRANCH." >&2
953
- echo " classify-lane.sh will hard-fail for issues in this milestone until the branch exists." >&2
954
- fi
955
- done
956
- ```
957
-
958
- This step is the deterministic counterpart to fix #2 (atomic create-if-absent in the classifier): by guaranteeing the branch exists before the lane checks run, no agent can observe a missing branch. `classify-lane.sh`'s hard-fail is intentionally preserved as the phantom-slug gate for any path that bypasses this step.
959
-
960
- ### Step 4A.pre: Classify lane for each issue (MANDATORY before dispatching agents)
961
-
962
- Before building agent prompts, run `classify-lane.sh` for every issue in the current dispatch group to compute `{LANE}` and `{PR_BASE}` deterministically. The script output is authoritative — the LLM MUST NOT override or reason around it.
963
-
964
- ```bash
965
- # Requires classify-lane.sh to be available at ~/.claude/scripts/classify-lane.sh
966
- # (installed by `npx forgedock` — see bin/forgedock.mjs linkScripts step)
967
- # Fallback: bash "$FORGE_HOME/scripts/classify-lane.sh" if ~/.claude/scripts/ is unavailable
968
-
969
- declare -A ISSUE_LANE
970
- declare -A ISSUE_PR_BASE
30
+ ## Quick Reference
971
31
 
972
- # Batch-level accumulators for review-finding cascade control (Step 4C) and
973
- # Completion Sweep (Step 4F). Declared here so they persist across ALL agent
974
- # completions — Step 4C runs per-agent and must NOT re-initialize these.
975
- DEFERRED_FINDINGS=()
976
- QUEUED_FINDINGS=()
977
- declare -A DEFERRED_REASONS
978
- declare -A AGENT_ISSUE_MAP
979
-
980
- for NUM in {ready_issue_numbers}; do
981
- PR_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$NUM" -R {GH_REPO}) || {
982
- echo "ERROR: classify-lane.sh failed for #$NUM — adding needs-human label and skipping" >&2
983
- gh issue edit "$NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
984
- continue
985
- }
986
- # Derive LANE label from PR_BASE
987
- if [ "$PR_BASE" = "staging" ]; then
988
- LANE="fast-lane"
989
- else
990
- LANE="feature-lane"
991
- fi
992
- ISSUE_LANE[$NUM]="$LANE"
993
- ISSUE_PR_BASE[$NUM]="$PR_BASE"
994
- echo "#$NUM → lane=$LANE, PR_BASE=$PR_BASE"
995
- done
996
- ```
997
-
998
- Use `${ISSUE_LANE[$NUM]}` and `${ISSUE_PR_BASE[$NUM]}` to populate `{LANE}` and `{PR_BASE}` in the agent template below. Never substitute prose guesses for these values — the script output is the only valid source. <!-- Added: forge#677 -->
999
-
1000
- ### Step 4A: Dispatch ready issues
1001
-
1002
- **REMINDER: You MUST use the template below verbatim. Only fill in `{VARIABLES}`. Do NOT rewrite the agent prompt. Do NOT write custom implementation instructions. The agent MUST invoke `/work-on` via the Skill tool — this is the HARD RULE from the top of this file.**
1003
-
1004
- For each **ready** issue (all predecessors resolved or no predecessors), spawn an Agent sub-agent that runs the full `/work-on` pipeline. On the initial dispatch, this is every issue with an empty predecessor set. On subsequent dispatches (triggered by agent completions in Step 4B), this is every newly-unblocked issue.
1005
-
1006
- **One agent per issue.** Do NOT group multiple issues into a single agent. `/work-on` handles branching, labels, and PRs per-issue.
1007
-
1008
- **Copy this template. Fill in variables. Do not modify the structure:**
1009
-
1010
- ```
1011
- Agent(
1012
- subagent_type="general-purpose",
1013
- model="sonnet",
1014
- description="Work on {PROJECT_PREFIX}#{NUMBER}",
1015
- run_in_background=true,
1016
- prompt="You are working on GitHub issue #{NUMBER} for the {PROJECT_NAME} project.
1017
-
1018
- **Project**: {PROJECT_NAME}
1019
- **Repository**: {GH_REPO}
1020
- **Repo path**: {REPO_PATH}
1021
-
1022
- **YOUR MISSION**: Invoke `/work-on` via the Skill tool and let it run to completion. `/work-on` is a self-contained routing loop that handles the ENTIRE pipeline: investigate → build (context → architect → implement → validate) → review (push → PR → /review-pr --auto-merge) → close (project board → trajectory log → worktree cleanup). Do NOT intervene, compensate, or manually close issues — `/work-on` handles everything including issue closure and label updates in its close phase.
1023
-
1024
- **CRITICAL — DO NOT STOP EARLY**: /work-on runs as a multi-phase routing loop. Each phase (investigate, build, review, close) returns an intermediate result — these are NOT completion signals. You are NOT done until the issue reaches a terminal state: `workflow:merged`, `workflow:invalid`, or `needs-human`. If /work-on returns after only one phase (e.g., investigation), you MUST invoke it again immediately — it will re-read GitHub state and continue to the next phase. Keep invoking /work-on until it reaches a terminal state. Never output 'done' or stop after an intermediate result.
1025
-
1026
- **HOW REVIEW FINDINGS WORK**: /review-pr may create GitHub issues (with `review-finding` label) for findings it discovers. These are NOT blockers — they are separate work items that will go through their own /work-on pipeline later. The original PR should ALWAYS merge after review. The only exception is build errors (code doesn't compile) — those must be fixed before merging.
1027
-
1028
- **IMPORTANT RULES**:
1029
- - **MANDATORY**: You MUST use the Skill tool to invoke 'work-on' with args '{PROJECT_PREFIX}{NUMBER}'. Do NOT implement manually — /work-on handles the full pipeline including label state machine (workflow:investigating → workflow:building → workflow:in-review → workflow:merged), investigation reports, PR creation, and cleanup.
1030
- - For default repo issues: `Skill(skill='work-on', args='{NUMBER}')`
1031
- - For satellite repo issues: `Skill(skill='work-on', args='{SATELLITE_PREFIX}:{NUMBER}')` (prefix from forge.yaml → repos.satellites)
1032
- - NEVER bypass /work-on with manual git/gh commands — the label updates and structured comments are critical for tracking
1033
- - NEVER target `main` for PRs targeting the default repo. Use `{STAGING_BRANCH}` for fast-lane issues, or `milestone/{slug}` for milestone issues.
1034
- - Satellite repos (MCP, n8n) have no staging branch — fast-lane PRs go to `main` for those.
1035
- - If the issue is INVALID after investigation, close it with a comment explaining why
1036
- - If you hit merge conflicts or blockers, post a comment on the issue and STOP — do not force anything
1037
- - Do not interact with the user — you are running autonomously in the background
1038
- - **NEVER ask the user questions** — you are a background agent. If review finds issues, auto-fix simple ones and proceed. For complex findings, merge anyway and create follow-up issues.
1039
-
1040
- **LABEL-STATE LOOP CONTRACT — enforce after EVERY Skill return**:
1041
- After EVERY `Skill(skill='work-on', ...)` call returns, immediately check the issue's current workflow label:
1042
- ```bash
1043
- gh issue view {NUMBER} -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith("workflow:"))]'
1044
- ```
1045
- **Terminal labels** (only these allow you to stop): `workflow:merged`, `workflow:invalid`
1046
- **Terminal condition also**: `needs-human` label present OR issue state is `closed`
1047
- If the label is NOT terminal (e.g., `workflow:investigating`, `workflow:ready-to-build`, `workflow:building`, `workflow:in-review`), invoke `Skill(skill='work-on', args='{NUMBER}')` again immediately. The `/work-on` skill will re-read GitHub state and advance to the next phase. Do NOT output a summary, do NOT pause, do NOT ask for confirmation — just invoke it again.
1048
-
1049
- **CRITICAL — SOURCE BRANCH DETECTION**:
1050
- - If the issue has the `review-finding` label, read the issue body for `**Code branch**: \`{branch}\``
1051
- - If found, that is the SOURCE_BRANCH — the code ONLY exists on that branch (e.g., `staging`), NOT on `origin/main`
1052
- - Investigation MUST use `git show origin/{SOURCE_BRANCH}:{filepath}` to verify the code exists
1053
- - Worktree MUST branch from `origin/{SOURCE_BRANCH}`, NOT `origin/main`
1054
- - PR target is `{SOURCE_BRANCH}` (the fix goes back to where the code lives)
1055
-
1056
- **LANE**: {LANE} (PR target: {PR_BASE})
1057
- **Issue title**: {ISSUE_TITLE}
1058
- {GIST_CONTEXT}
1059
- "
1060
- )
1061
- ```
1062
-
1063
- **`{GIST_CONTEXT}` generation**: For each issue being dispatched, build the context block. **Prefer the deconflicted `FORGE:SYNTHESIS_BRIEF` (from Phase 2.5) when one exists** — it is a per-issue, already-reconciled brief that carries only the arbitration decisions and sibling investigation Gists relevant to *this* issue. Injecting it instead of the full aggregated milestone-index gist means the agent does not re-arbitrate the same contradictions (less token spend, less nondeterminism). Only when Phase 2.5 did not run (0/1 investigations — no brief exists) does this fall back to the raw parent-investigation + milestone-index gist behavior. <!-- Added: forge#1192 -->
1064
-
1065
- ```bash
1066
- # Build GIST_CONTEXT for an issue
1067
- GIST_CONTEXT=""
1068
-
1069
- # Preferred path: a deconflicted per-issue synthesis brief from Phase 2.5.
1070
- SYNTHESIS_BRIEF=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
1071
- --jq '[.[] | select(.body | contains("<!-- FORGE:SYNTHESIS_BRIEF -->"))] | last | .body // ""' 2>/dev/null)
1072
-
1073
- if [ -n "$SYNTHESIS_BRIEF" ]; then
1074
- # Phase 2.5 ran and reconciled competing recommendations for this issue.
1075
- # Inject the deconflicted brief INSTEAD of the raw milestone-index gist dump.
1076
- GIST_CONTEXT="
1077
- **RECONCILED CONTEXT (orchestrate Phase 2.5 synthesis brief)**: Competing investigation recommendations affecting this issue have already been reconciled. Use this deconflicted brief as your primary cross-investigation context — do NOT independently re-arbitrate the underlying investigations.
1078
- ${SYNTHESIS_BRIEF}"
1079
- else
1080
- # Fallback: Phase 2.5 did not run (0/1 investigations). Use the raw gist behavior.
1081
- PARENT_INV=$(gh issue view {NUMBER} -R {GH_REPO} --json body --jq '.body' \
1082
- | grep -oP '(?i)parent[: ]*#\K\d+|spawned from[: ]*#\K\d+' | head -1)
1083
-
1084
- if [ -n "$PARENT_INV" ] && [ -n "${INVESTIGATION_GISTS[$PARENT_INV]:-}" ]; then
1085
- GIST_CONTEXT="
1086
- **CONTEXT FROM PRIOR INVESTIGATION**: Investigation #${PARENT_INV} produced Knowledge Gist(s) with findings relevant to this issue:
1087
- $(echo "${INVESTIGATION_GISTS[$PARENT_INV]}" | while IFS= read -r url; do echo "- ${url}"; done)
1088
- Fetch the Gist content during the context-gathering phase for implementation guidance."
1089
- fi
1090
-
1091
- # Include milestone index URL if available (from Step 2C.5)
1092
- if [ -n "$MILESTONE_INDEX_URL" ]; then
1093
- GIST_CONTEXT="${GIST_CONTEXT}
1094
-
1095
- **MILESTONE KNOWLEDGE INDEX**: All investigation findings for this milestone are aggregated in a single index Gist:
1096
- - ${MILESTONE_INDEX_URL}
1097
- The context-gathering phase can fetch this index to discover all investigation Gists for the milestone."
1098
- fi
1099
- fi
1100
- ```
1101
-
1102
- If `GIST_CONTEXT` is empty (no synthesis brief, no parent investigation, and no milestone index found), the variable resolves to a blank line in the template — no impact on the agent prompt. <!-- Updated: forge#341, forge#1192 -->
1103
-
1104
- **Capture agent IDs after the batch spawn (MANDATORY)**: Each `Agent(...)` call returns an agent ID. Store each returned ID in `AGENT_ISSUE_MAP` keyed by issue number. This map is the only way to resume a stalled agent by ID in Steps 4B and 4B.5:
1105
-
1106
- ```
1107
- # After the single-message batch spawn, capture each returned ID:
1108
- AGENT_ISSUE_MAP[{NUMBER}] = <agent_id returned by Agent()>
1109
32
  ```
1110
-
1111
- `AGENT_ISSUE_MAP` starts empty and accumulates entries as agents are spawned. For parallel dispatch (all Agent() calls in one message), capture the returned IDs from the batch response — one entry per issue — before entering Step 4B's monitoring loop. Without this capture, `resume=` calls in Steps 4B and 4B.5 will have no agent ID to reference and the resume will fail. <!-- Added: forge#1083 -->
1112
-
1113
- **Launch all ready agents simultaneously** by putting multiple Agent tool calls in a single message. Use `run_in_background=true` so they execute in parallel.
1114
-
1115
- ### Step 4B: Monitor completions and dispatch newly ready issues
1116
-
1117
- You will be automatically notified when each background agent completes. **Do NOT use `sleep` loops to poll for completion.** Instead, wait for the automatic notification. When you receive a notification that an agent completed, immediately process it.
1118
-
1119
- **Core streaming dispatch loop**: After processing each agent completion, check the DAG for newly unblocked issues. If any issue now has all predecessors in a terminal state, dispatch it immediately (run Steps 4A.pre.0, 4A.pre, and 4A for the newly ready issues). This is the key difference from the wave model — issues dispatch as soon as their specific predecessors complete, not after an entire group finishes.
1120
-
1121
- ```bash
1122
- # After each agent completion, check for newly ready issues:
1123
- for BLOCKED_NUM in {all_blocked_issue_numbers}; do
1124
- ALL_PREDS_DONE=true
1125
- for PRED in {predecessors_of_BLOCKED_NUM}; do
1126
- PRED_STATE=$(gh issue view $PRED -R {GH_REPO} --json labels,state --jq '{state: .state, workflow: [.labels[].name | select(startswith("workflow:"))]}')
1127
- # Terminal states: workflow:merged, workflow:invalid, needs-human, or state=CLOSED
1128
- if ! echo "$PRED_STATE" | grep -qE 'workflow:merged|workflow:invalid|needs-human|CLOSED'; then
1129
- ALL_PREDS_DONE=false
1130
- break
1131
- fi
1132
- done
1133
- if [ "$ALL_PREDS_DONE" = "true" ]; then
1134
- echo "#{BLOCKED_NUM} is now READY — all predecessors resolved. Dispatching."
1135
- # Add to dispatch batch for this completion cycle
1136
- fi
1137
- done
1138
- # Run Steps 4A.pre.0 → 4A.pre → 4A for newly ready issues (batch them in a single message)
1139
- ```
1140
-
1141
- **CRITICAL — Stall detection and recovery**: Background agents sometimes stop mid-pipeline (`stop_reason=end_turn`) after completing a sub-phase (e.g., investigation completes but build never starts). This causes the agent to "complete" from the Agent tool's perspective even though the `/work-on` pipeline is only partially done. When you receive a completion notification:
1142
-
1143
- 1. **Check if the agent completed the FULL pipeline** — not just one phase:
1144
- ```bash
1145
- # Check final workflow state — only workflow:merged or workflow:invalid means truly done
1146
- FINAL_STATE=$(gh issue view $NUM -R {GH_REPO} --json labels,state --jq '{state: .state, workflow: [.labels[].name | select(startswith("workflow:"))]}')
1147
- echo "#{NUM}: $FINAL_STATE"
1148
- ```
1149
-
1150
- 2. **If the issue is NOT in a terminal state** (`workflow:merged`, `workflow:invalid`, or `needs-human`), the agent stalled mid-pipeline. **Resume it immediately**:
1151
- ```
1152
- Agent(
1153
- resume=AGENT_ISSUE_MAP[{NUMBER}],
1154
- description="Resume #{NUMBER} pipeline",
1155
- run_in_background=true,
1156
- prompt="The previous /work-on invocation stopped before completing the full pipeline. The issue is currently at {CURRENT_WORKFLOW_STATE}. Continue — invoke Skill(skill='work-on', args='{NUMBER}') to resume the routing loop from the current state. /work-on will re-read GitHub state and pick up where it left off."
1157
- )
1158
- ```
1159
- **Resume ALL stalled agents in a single message** (parallel resume). Do not wait between resumes.
1160
-
1161
- 3. **Track resume cycles per agent.** If an agent has been resumed 2+ times and still hasn't reached a terminal state, report it as a failure — do not resume again.
1162
-
1163
- 4. **Record completed results**: Success (PR merged), Invalid (issue closed), Blocked (needs human), or Error
1164
-
1165
- 5. **Check for newly unblocked issues** — run the DAG readiness check above. If any issues are now ready, dispatch them immediately (Steps 4A.pre.0 → 4A.pre → 4A). Batch all newly ready issues into a single dispatch message.
1166
-
1167
- 6. **Handle predecessor failures** — if a completed agent's issue FAILED (needs-human, invalid, or error), check for dependent issues in the DAG. Mark all transitive dependents as "skipped — dependency #{X} failed" and report them. Do NOT dispatch them.
1168
-
1169
- 7. **Verify pipeline compliance** — for each truly completed issue, check that the agent used `/work-on`:
1170
- ```bash
1171
- LABELS=$(gh issue view $NUM -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith("workflow:"))] | length')
1172
- COMMENTS=$(gh api repos/{GH_REPO}/issues/${NUM}/comments --jq '[.[] | select(.body | test("FORGE:INVESTIGATOR|FORGE:BUILDER"))] | length')
1173
- if [ "$LABELS" -eq 0 ] || [ "$COMMENTS" -eq 0 ]; then
1174
- echo "PIPELINE FAILURE: #{NUM} — agent bypassed /work-on (no labels or structured comments)"
1175
- fi
1176
- ```
1177
- If an agent bypassed the pipeline, report it as a **failure** regardless of whether a PR exists.
1178
-
1179
- 8. **Post a brief status update** to the user after each agent reaches terminal state:
1180
- ```
1181
- ✓ #{NUMBER} — {title} → PR #{PR} merged to {target}
1182
- ✗ #{NUMBER} — {title} → {reason for failure}
1183
- ⚠ #{NUMBER} — {title} → PIPELINE BYPASS (no /work-on — PR invalid)
1184
- ⏳ Progress: {completed}/{total} complete, {active} active, {blocked} blocked
1185
- → Dispatched #{NEWLY_READY} (predecessor #{PRED} completed)
1186
- ```
1187
-
1188
- 9. **Run staging integrity check** (from Step 4A-pre) if the completed agent merged a PR targeting staging.
1189
-
1190
- **Termination condition**: All issues in the DAG are in a terminal state (merged, invalid, needs-human, or skipped due to dependency failure). When this condition is met, check whether deferred review-spawned findings exist (accumulated in `DEFERRED_FINDINGS` during Step 4C). If deferred findings exist → proceed to Step 4F (Completion Sweep). If no deferred findings → proceed to Phase 5.
1191
-
1192
- **Anti-pattern — DO NOT DO THIS:**
1193
- - `sleep 60/120/180/300` loops to check status — you will be notified automatically
1194
- - Spawning separate "progress check" agents — they waste tokens and add noise
1195
- - Reading agent JSONL output files to check progress — use GitHub labels as the source of truth
1196
- - Polling the same status check repeatedly on a timer
1197
- - Waiting for a "batch" of completions before checking for newly ready issues — check after EVERY completion
1198
-
1199
- ### Step 4B.5: Time-Based Stall Detection
1200
-
1201
- **Purpose**: Catches agents that have stopped responding WITHOUT exiting (e.g., rate-limited, context-frozen, or silently hung). The reactive check in Step 4B only fires on agent completion — this check catches agents that never complete at all.
1202
-
1203
- **When to run** (NOT a sleep loop — two trigger points only):
1204
- 1. On every background agent completion event (run BEFORE the terminal-state check in Step 4B)
1205
- 2. Before posting any "waiting for agents..." status update to the user
1206
-
1207
- **Do NOT poll on a timer. Do NOT use sleep. Run at these two trigger points only.**
1208
-
1209
- **Read stall timeout from config**:
1210
- ```bash
1211
- STALL_TIMEOUT=$(yq '.pipeline.stall_timeout_minutes // 15' forge.yaml 2>/dev/null || echo 15)
33
+ Read: $FORGE_HOME/commands/orchestrate/config.md # ALWAYS READ FIRST
34
+ Read: $FORGE_HOME/commands/orchestrate/phase-1-resolve.md
35
+ Read: $FORGE_HOME/commands/orchestrate/phase-2-triage.md
36
+ Read: $FORGE_HOME/commands/orchestrate/phase-2.5-synthesis.md
37
+ Read: $FORGE_HOME/commands/orchestrate/phase-3-dependency.md
38
+ Read: $FORGE_HOME/commands/orchestrate/phase-4-execution.md
39
+ Read: $FORGE_HOME/commands/orchestrate/phase-5-cleanup.md
40
+ Read: $FORGE_HOME/commands/orchestrate/phase-6-report.md
1212
41
  ```
1213
42
 
1214
- **For each non-terminal agent in the current batch**:
1215
- ```bash
1216
- for NUM in {active_issue_numbers}; do
1217
- # Skip issues already in terminal state
1218
- TERMINAL=$(gh issue view $NUM -R {GH_REPO} --json labels \
1219
- --jq '[.labels[].name | select(. == "workflow:merged" or . == "workflow:invalid" or . == "needs-human")] | length')
1220
- [ "$TERMINAL" -gt 0 ] && continue
1221
-
1222
- # Get last activity timestamp — prefer last comment (catches FORGE:HEARTBEAT updates)
1223
- LAST_ACTIVITY=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
1224
- --jq '.[-1].updated_at // empty' 2>/dev/null)
1225
- # Fall back to issue updated_at if no comments
1226
- if [ -z "$LAST_ACTIVITY" ]; then
1227
- LAST_ACTIVITY=$(gh issue view $NUM -R {GH_REPO} --json updatedAt --jq '.updatedAt')
1228
- fi
1229
-
1230
- # Compute elapsed minutes (GNU date — adjust for macOS: date -j -f "%Y-%m-%dT%H:%M:%SZ")
1231
- LAST_EPOCH=$(date -d "$LAST_ACTIVITY" +%s 2>/dev/null \
1232
- || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_ACTIVITY" +%s 2>/dev/null)
1233
- NOW_EPOCH=$(date +%s)
1234
- ELAPSED_MIN=$(( (NOW_EPOCH - LAST_EPOCH) / 60 ))
1235
-
1236
- if [ "$ELAPSED_MIN" -gt "$STALL_TIMEOUT" ]; then
1237
- # Count prior stall events on this issue
1238
- STALL_COUNT=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
1239
- --jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length')
1240
-
1241
- CURRENT_STATE=$(gh issue view $NUM -R {GH_REPO} --json labels \
1242
- --jq '[.labels[].name | select(startswith("workflow:"))] | .[0] // "unknown"')
1243
-
1244
- if [ "$STALL_COUNT" -lt 2 ]; then
1245
- # Auto-resume: post stall annotation and re-invoke /work-on
1246
- RESUME_ATTEMPT=$(( STALL_COUNT + 1 ))
1247
- gh issue comment $NUM -R {GH_REPO} --body "<!-- FORGE:STALL_DETECTED -->
1248
- ## Stall Detected
1249
-
1250
- **Issue**: #${NUM}
1251
- **Elapsed since last activity**: ${ELAPSED_MIN} min (threshold: ${STALL_TIMEOUT} min)
1252
- **Current workflow state**: ${CURRENT_STATE}
1253
- **Auto-resume attempt**: ${RESUME_ATTEMPT} of 2
1254
- **Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
1255
-
1256
- # Resume the agent — collect all resumes and launch in a single message (see Step 4B rule)
1257
- # STALL_RESUME_LIST is accumulated and launched in parallel after the loop
1258
- STALL_RESUME_LIST="$STALL_RESUME_LIST $NUM"
1259
- else
1260
- # 2+ prior stalls — auto-resume exhausted, escalate to needs-human
1261
- gh issue edit $NUM -R {GH_REPO} --add-label "needs-human"
1262
- gh issue comment $NUM -R {GH_REPO} --body "<!-- FORGE:STALL_DETECTED -->
1263
- ## Stall Escalated — Needs Human Intervention
1264
-
1265
- Issue #${NUM} has been auto-resumed ${STALL_COUNT} times without reaching a terminal state. Auto-resume limit (2) exhausted. Manual intervention required.
1266
-
1267
- **Last workflow state**: ${CURRENT_STATE}
1268
- **Total elapsed since last activity**: ${ELAPSED_MIN} min
1269
- **Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
1270
- echo "STALL ESCALATED: #{NUM} → needs-human (${STALL_COUNT} prior resumes)"
1271
- fi
1272
- fi
1273
- done
1274
-
1275
- # Launch all stall resumes in parallel (single message — same rule as Step 4B)
1276
- # For each NUM in $STALL_RESUME_LIST, call Agent(resume=AGENT_ISSUE_MAP[NUM], run_in_background=true, ...)
1277
- ```
1278
-
1279
- **Resume all stalled agents in a single message** (parallel). Use the same `Agent(resume=...)` pattern as Step 4B — do not wait between individual resumes.
1280
-
1281
- **Track stall resume cycles separately** from completion-event resumes (Step 4B). If the same issue accumulates ≥ 2 `FORGE:STALL_DETECTED` comments AND still hasn't reached terminal state, do not resume again — the `needs-human` label is already set.
1282
-
1283
- ### Step 4C: Collect review-finding issues from completed agents
1284
-
1285
- After each agent reaches a terminal state, check if its `/work-on` run spawned review-finding issues during the review phase. These are new work items that should be added to the dependency DAG and dispatched when ready.
1286
-
1287
- ```bash
1288
- # Method 1: Read TRAJECTORY comments from completed issues for "Finding issues" row
1289
- for NUM in {completed_issue_number}; do
1290
- gh api repos/{GH_REPO}/issues/${NUM}/comments \
1291
- --jq '.[] | select(.body | contains("FORGE:TRAJECTORY")) | .body' 2>/dev/null \
1292
- | grep -oP 'Finding issues\s*\|\s*#?\K\d+[^|]*' | grep -oP '\d+' | sort -u
1293
- done
1294
-
1295
- # Method 2 (fallback): Check for recently created review-finding issues that reference PRs from this batch
1296
- gh issue list -R {GH_REPO} --state open --label "review-finding" --limit 20 \
1297
- --json number,title,body,createdAt \
1298
- --jq "[.[] | select(.createdAt > \"$(date -u -d '2 hours ago' +%Y-%m-%dT%H:%M:%SZ)\")]"
1299
- ```
1300
-
1301
- **If review-finding issues were spawned:**
1302
-
1303
- **Cascade control (MANDATORY — run before folding findings into the DAG):**
1304
-
1305
- For each spawned finding, determine whether it should be **executed** or **deferred**:
1306
-
1307
- **Evaluation order** (first matching rule wins):
1308
- 1. **Generation ≥ 2** (always defer, even for P1/P2): Finding was spawned by an issue that was itself a review-finding. Check the source issue's labels for `review-finding` — if the source has that label, the new finding is generation 2. Always defer. Rationale: gen-2+ cascade is theoretically unbounded — cap it here.
1309
- 2. **Priority override** (P1 or P2 → always execute): If the finding is labeled P1 or P2, skip all remaining heuristics and execute. Rationale: high-priority findings must never be suppressed by keyword matching.
1310
- 3. **Comment/typo heuristic** (P3 and below only): Finding title contains the word "comment" or "typo" (case-insensitive). These are 1-line cosmetic fixes that do not block other work.
1311
- 4. **P3 + same-file overlap**: Finding is labeled `P3` AND the file it targets overlaps with ANY file already in the current batch (active or queued in the DAG). Rationale: same-file P3 findings add predecessor edges that serialize agents — one finding per original issue increases wall-clock time with no proportional value.
1312
-
1313
- **Defer** (do NOT add to the DAG) if rules 1, 3, or 4 match.
1314
-
1315
- **Execute** (add to the DAG) if:
1316
- - Rule 2 matches (P1 or P2)
1317
- - None of the defer rules matched (generation 1, P3 with no file overlap, not a keyword match)
1318
-
1319
- **Before running the loop, build the batch file list (MANDATORY for Heuristic 3):**
1320
-
1321
- Collect all file paths from every issue in the current batch — both completed and remaining queued issues in the DAG. This produces `ALL_BATCH_FILES`, a newline-separated list of file paths used by Heuristic 3 to test same-file overlap.
1322
-
1323
- ```bash
1324
- # Build ALL_BATCH_FILES: collect file paths from ALL batch issues (completed + queued)
1325
- # Use the same extraction pattern as Step 3C Layer 1
1326
- ALL_BATCH_FILES=""
1327
- for NUM in {all_batch_issue_numbers}; do
1328
- # Try INVESTIGATOR comment first (most reliable source of affected files)
1329
- FILES=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
1330
- --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body' 2>/dev/null \
1331
- | grep -oP '`[^`]*\.(py|tsx?|jsx?|sql|json|ya?ml|sh|md)`' | tr -d '`' | sort -u)
1332
- # Fall back to issue body if no investigator comment
1333
- if [ -z "$FILES" ]; then
1334
- FILES=$(gh issue view $NUM -R {GH_REPO} --json body --jq '.body' \
1335
- | grep -oP '`[^`]*\.(py|tsx?|jsx?|sql|json|ya?ml|sh|md)`' | tr -d '`' | sort -u)
1336
- fi
1337
- ALL_BATCH_FILES=$(printf '%s\n%s' "$ALL_BATCH_FILES" "$FILES")
1338
- done
1339
- ALL_BATCH_FILES=$(echo "$ALL_BATCH_FILES" | sort -u | grep -v '^$')
1340
- ```
1341
-
1342
- ```bash
1343
- # For each finding, check its priority label and generation
1344
- # NOTE: DEFERRED_FINDINGS, QUEUED_FINDINGS, and DEFERRED_REASONS are declared at
1345
- # batch scope in Step 4A.pre — do NOT re-initialize them here (Step 4C runs per-agent).
1346
- for FINDING_NUM in {spawned_finding_numbers}; do
1347
- FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body \
1348
- --jq '{labels: [.labels[].name], title: .title, body: .body}')
1349
-
1350
- PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("priority:P")) | ltrimstr("priority:")' | head -1)
1351
- TITLE=$(echo "$FINDING_DATA" | jq -r '.title')
1352
-
1353
- # Heuristic 1: Generation check — source issue has review-finding label (always defer, even for P1/P2)
1354
- if SOURCE_NUM=$(echo "$FINDING_DATA" | jq -r '.body' | grep -oP '(?i)spawned from issue #\K\d+|source issue[: #]+\K\d+' | head -1) && \
1355
- [ -n "$SOURCE_NUM" ] && \
1356
- gh issue view $SOURCE_NUM -R {GH_REPO} --json labels --jq '[.labels[].name]' 2>/dev/null | grep -q "review-finding"; then
1357
- DEFER=true; DEFER_REASON="generation >= 2 (source #${SOURCE_NUM} is also a review-finding)"
1358
- # Priority override: P1 or P2 always execute — skip remaining heuristics
1359
- elif [ "$PRIORITY" = "P1" ] || [ "$PRIORITY" = "P2" ]; then
1360
- DEFER=false
1361
- # Heuristic 2: Comment/typo keyword (only applies to P3 and below)
1362
- elif echo "$TITLE" | grep -qi "comment\|typo"; then
1363
- DEFER=true; DEFER_REASON="comment/typo heuristic"
1364
- # Heuristic 3: P3 + same-file overlap
1365
- elif [ "$PRIORITY" = "P3" ]; then
1366
- # Extract file target from finding body (look for code block or backtick path)
1367
- FINDING_FILE=$(echo "$FINDING_DATA" | jq -r '.body' | grep -oP '`[^\`]+\.(py|ts|tsx|sh|md)`' | head -1 | tr -d '`')
1368
- if [ -n "$FINDING_FILE" ] && echo "$ALL_BATCH_FILES" | grep -qF "$FINDING_FILE"; then
1369
- DEFER=true; DEFER_REASON="P3 + same file as batch: $FINDING_FILE"
1370
- else
1371
- DEFER=false
1372
- fi
1373
- else
1374
- DEFER=false
1375
- fi
1376
-
1377
- if [ "$DEFER" = "true" ]; then
1378
- DEFERRED_FINDINGS+=($FINDING_NUM)
1379
- DEFERRED_REASONS[$FINDING_NUM]="$DEFER_REASON"
1380
- echo "Deferred #${FINDING_NUM}: $DEFER_REASON"
1381
- else
1382
- QUEUED_FINDINGS+=($FINDING_NUM)
1383
- fi
1384
- done
1385
- ```
1386
-
1387
- **For queued (non-deferred) findings:**
1388
-
1389
- 1. **Add them to the dependency DAG.** They are implementation issues — same as issues spawned by investigations in Phase 2. Compute their predecessor sets using the same conflict detection (Step 3C Layers 1-4) against all remaining blocked/active issues.
1390
- 2. **Respect source branch context.** Review-finding issues have `**Code branch**: \`{branch}\`` in their body — the `/work-on` agent will read this and branch from the right origin. No special handling needed from the orchestrator.
1391
- 3. **Report to user:**
1392
- ```
1393
- Agent #{COMPLETED} spawned {count} new finding issues: #{A}, #{B}
1394
- Added to DAG: #{A} (predecessors: {}), #{B} (predecessors: {#{X}})
1395
- Deferred (cascade control): #{C} (P3 same-file), #{D} (comment heuristic)
1396
- ```
1397
- 4. **Re-run file-overlap detection** (Step 3C) on the expanded issue set — finding issues may conflict with active or queued issues that touch the same files. Ready findings dispatch immediately via the standard Step 4B dispatch loop.
1398
-
1399
- **For deferred findings:**
1400
-
1401
- Track them in `DEFERRED_FINDINGS` for re-evaluation in Step 4F (Completion Sweep) after the DAG drains. Do NOT close or label them yet — the sweep will determine their final disposition.
1402
-
1403
- **If no review-finding issues were spawned:** Continue monitoring for the next agent completion.
1404
-
1405
- ### Step 4C.5: Milestone lane-consistency check (periodic) <!-- Added: forge#901 -->
1406
-
1407
- **WHY THIS EXISTS**: A milestone's feature-lane PRs must all target the same milestone branch. If a branch-routing race ever scatters them — some on the milestone branch, some on staging — the milestone branch becomes incomplete relative to staging, and the split is otherwise invisible until the milestone tries to ship. Step 4A.pre.0 prevents the split deterministically; this check detects any residual split so it surfaces immediately instead of at ship time.
1408
-
1409
- **When to run**: After every 3rd agent completion (or after all agents complete, whichever comes first), for any batch where at least one issue has a milestone. Skip for pure fast-lane batches. This check is **non-blocking** — it alerts; it does not auto-resolve or stop the pipeline.
1410
-
1411
- ```bash
1412
- # For each distinct milestone in the batch, assert all of its feature-lane PRs share one base.
1413
- for NUM in {all_batch_issue_numbers}; do
1414
- MILESTONE_TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null || echo "")
1415
- [ -z "$MILESTONE_TITLE" ] && continue
1416
-
1417
- SLUG=$(echo "$MILESTONE_TITLE" \
1418
- | tr '[:upper:]' '[:lower:]' \
1419
- | tr ' ' '-' \
1420
- | tr -cd 'a-z0-9-' \
1421
- | sed 's/--*/-/g' \
1422
- | sed 's/^-//;s/-$//')
1423
- [ -z "$SLUG" ] && continue
1424
- EXPECTED_BASE="milestone/$SLUG"
1425
-
1426
- # Collect the base branch of every PR that closes an issue in this milestone.
1427
- # Iterate the milestone's issues and read each one's linked PR base.
1428
- # Exclude CLOSED-unmerged PRs: a closed-but-not-merged PR is a superseded/abandoned
1429
- # routing attempt and does NOT reflect the live lane. Keep only OPEN (in-flight) and
1430
- # MERGED (landed) PRs. `gh pr list --state` cannot combine open+merged, so query all
1431
- # and drop CLOSED in jq.
1432
- BASES=$(gh pr list -R {GH_REPO} --state all --search "milestone:\"$MILESTONE_TITLE\"" \
1433
- --json baseRefName,state --jq '.[] | select(.state != "CLOSED") | .baseRefName' 2>/dev/null | sort -u)
1434
- # Fallback: if PR search by milestone is unavailable, derive from the issues' linked PRs.
1435
- if [ -z "$BASES" ]; then
1436
- BASES=$(for IN in {all_batch_issue_numbers}; do
1437
- IM=$(gh issue view "$IN" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null)
1438
- [ "$IM" = "$MILESTONE_TITLE" ] || continue
1439
- gh pr list -R {GH_REPO} --state all --search "$IN in:body" \
1440
- --json baseRefName,state --jq '.[] | select(.state != "CLOSED") | .baseRefName' 2>/dev/null
1441
- done | sort -u)
1442
- fi
1443
-
1444
- STRAY_BASES=$(echo "$BASES" | grep -v "^${EXPECTED_BASE}\$" | grep -v '^$' || true)
1445
- if [ -n "$STRAY_BASES" ]; then
1446
- echo "ALERT: milestone '$MILESTONE_TITLE' has feature-lane PRs split across multiple base branches." >&2
1447
- echo " Expected base: $EXPECTED_BASE" >&2
1448
- echo " Found bases:" >&2
1449
- echo "$BASES" | sed 's/^/ - /' >&2
1450
- echo " This indicates a branch-routing split — reconcile the stray PRs onto $EXPECTED_BASE" >&2
1451
- echo " (rebase/cherry-pick the stray branch onto the milestone branch) before the milestone ships." >&2
1452
- # Do NOT auto-stop or auto-resolve — surface the alert and let the user decide.
1453
- else
1454
- echo "Lane-consistency OK: all '$MILESTONE_TITLE' PRs target $EXPECTED_BASE."
1455
- fi
1456
- done
1457
- ```
1458
-
1459
- Report any `ALERT` lines prominently before dispatching more agents. Reconciliation of an existing split is a manual/`/milestone`-assisted step — this check only ensures the split is never silent.
1460
-
1461
- ### Step 4D: Milestone integration build gate (MANDATORY — periodic for milestone batches)
1462
-
1463
- **WHY THIS EXISTS**: Session Intelligence milestone shipped 116 PRs across multiple dispatches with zero integration testing. Each PR built in isolation — type errors from cross-PR interactions (wrong prop types, missing components, incompatible interfaces) were invisible until the milestone→staging merge broke the build with 4 distinct errors. This gate catches those failures early.
1464
-
1465
- **When to run**: After every 3rd milestone-targeted agent completion (or when all milestone issues are complete), IF the batch targets a milestone branch AND any `.tsx`/`.ts` files were changed by agents in the completed set. Running after every single agent would be too frequent — batch the check to reduce overhead while still catching integration errors before they accumulate.
1466
-
1467
- All tool commands are read from `forge.yaml → verification.commands`; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing.
1468
-
1469
- ```bash
1470
- # Read toolchain commands from forge.yaml
1471
- TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
1472
- TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
1473
- PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
1474
-
1475
- # Check if this is a milestone batch with TypeScript changes
1476
- MILESTONE_BRANCH="milestone/{milestone_slug}"
1477
- TS_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.(tsx?|jsx?)$' | head -1)
1478
-
1479
- if [ -n "$TS_CHANGED" ]; then
1480
- echo "=== Integration Build Gate (TypeScript): batch checkpoint ==="
1481
- cd {REPO_PATH}
1482
- git fetch origin ${MILESTONE_BRANCH}
1483
- git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
1484
-
1485
- if [ -n "$TS_TYPECHECK" ]; then
1486
- eval "$TS_TYPECHECK" 2>&1 | head -30
1487
- TSC_EXIT=$?
1488
- else
1489
- echo "SKIPPED — typescript.typecheck not configured in verification.commands"
1490
- TSC_EXIT=0
1491
- fi
1492
-
1493
- if [ "$TSC_EXIT" -eq 0 ] && [ -n "$TS_BUILD" ]; then
1494
- eval "$TS_BUILD" 2>&1 | tail -30
1495
- BUILD_EXIT=$?
1496
- elif [ -z "$TS_BUILD" ]; then
1497
- echo "SKIPPED — typescript.build not configured in verification.commands"
1498
- BUILD_EXIT=0
1499
- fi
1500
-
1501
- git checkout - 2>/dev/null
1502
-
1503
- if [ "$TSC_EXIT" -ne 0 ]; then
1504
- echo "BLOCKING: TypeScript errors on ${MILESTONE_BRANCH} after batch checkpoint."
1505
- echo "Fix type errors before dispatching more milestone agents."
1506
- elif [ "${BUILD_EXIT:-0}" -ne 0 ]; then
1507
- echo "BLOCKING: build failed on ${MILESTONE_BRANCH} after batch checkpoint."
1508
- echo "Build/prerender errors — fix before dispatching more milestone agents."
1509
- fi
1510
- fi
1511
-
1512
- # Python format check
1513
- PY_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.py$' | head -1)
1514
- if [ -n "$PY_CHANGED" ]; then
1515
- echo "=== Integration Build Gate (Python): batch checkpoint ==="
1516
- cd {REPO_PATH}
1517
- git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
1518
-
1519
- if [ -n "$PYTHON_FORMAT" ]; then
1520
- eval "$PYTHON_FORMAT" 2>&1 | tail -10
1521
- FORMAT_EXIT=$?
1522
- else
1523
- echo "SKIPPED — python.format not configured in verification.commands"
1524
- FORMAT_EXIT=0
1525
- fi
1526
-
1527
- git checkout - 2>/dev/null
1528
-
1529
- if [ "$FORMAT_EXIT" -ne 0 ]; then
1530
- echo "WARNING: Python formatting issues on ${MILESTONE_BRANCH} after batch checkpoint."
1531
- echo "Not blocking but should be fixed before milestone→staging."
1532
- fi
1533
- fi
1534
- ```
1535
-
1536
- **If the gate fails**: Report the errors to the user. Do NOT dispatch any more milestone-targeted agents until the integration errors are resolved. The accumulated milestone branch has integration errors that will only get worse with more PRs on top. Build failures are BLOCKING — SSG/prerender crashes are invisible to typecheck alone — configure `typescript.build` in `verification.commands` to catch them. Non-milestone (fast-lane) agents may continue dispatching normally.
1537
-
1538
- ### Step 4E: Handle individual agent failures
1539
-
1540
- If an agent reports failure or error:
1541
- - **Merge conflict**: Report to user, mark issue as needing human attention
1542
- - **Invalid issue**: Already handled by the agent (closed with comment) — just report it
1543
- - **Build/test failure**: Report the error, suggest manual intervention
1544
- - **Agent timeout**: Report which issue timed out, suggest re-running with `/work-on #{N}`
1545
- - **Dependency cascade**: Mark all transitive dependents in the DAG as "skipped — dependency #{X} failed"
1546
-
1547
- **Do NOT retry failed agents automatically.** Report the failure and let the user decide.
1548
-
1549
- ### Step 4F: Completion Sweep (deferred review-spawned findings) <!-- Added: forge#1105 -->
1550
-
1551
- **When to run**: After all DAG issues reach terminal state AND `DEFERRED_FINDINGS` is non-empty. Skip if no findings were deferred during this batch.
1552
-
1553
- **WHY THIS EXISTS**: Deferred findings accumulate during the batch because of file-overlap and cascade-control heuristics (Step 4C). But once the DAG drains, the conditions that caused deferral often no longer apply — completed issues no longer occupy files, so same-file overlap vanishes. Without this sweep, deferred findings silently pile up across runs and never get resolved.
1554
-
1555
- **Step 4F.1: Classify deferred findings into permanent vs re-evaluable**
1556
-
1557
- ```bash
1558
- PERMANENT_DEFERRED=()
1559
- SWEEP_CANDIDATES=()
1560
-
1561
- for FINDING_NUM in "${DEFERRED_FINDINGS[@]}"; do
1562
- DEFER_REASON="${DEFERRED_REASONS[$FINDING_NUM]}"
1563
-
1564
- # Generation >= 2 deferrals are PERMANENT — unbounded cascade prevention
1565
- if echo "$DEFER_REASON" | grep -qi "generation"; then
1566
- PERMANENT_DEFERRED+=($FINDING_NUM)
1567
- else
1568
- # All other deferrals (comment/typo, P3 same-file) are re-evaluable
1569
- SWEEP_CANDIDATES+=($FINDING_NUM)
1570
- fi
1571
- done
1572
-
1573
- echo "Completion sweep: ${#SWEEP_CANDIDATES[@]} re-evaluable, ${#PERMANENT_DEFERRED[@]} permanent"
1574
- ```
1575
-
1576
- **Step 4F.2: Re-evaluate sweep candidates**
1577
-
1578
- Re-run the Step 4C heuristics against the now-empty DAG. Since all original batch issues are in terminal state, the `ALL_BATCH_FILES` list for file-overlap detection is empty — P3 same-file deferrals will now pass.
1579
-
1580
- ```bash
1581
- SWEEP_EXECUTE=()
1582
- SWEEP_STILL_DEFERRED=()
1583
-
1584
- for FINDING_NUM in "${SWEEP_CANDIDATES[@]}"; do
1585
- FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body,state \
1586
- --jq '{labels: [.labels[].name], title: .title, body: .body, state: .state}')
1587
-
1588
- # Skip if already closed (resolved by another process)
1589
- STATE=$(echo "$FINDING_DATA" | jq -r '.state')
1590
- [ "$STATE" = "CLOSED" ] && continue
1591
-
1592
- PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("priority:P")) | ltrimstr("priority:")' | head -1)
1593
- TITLE=$(echo "$FINDING_DATA" | jq -r '.title')
1594
-
1595
- # Re-apply heuristics against the drained DAG (no active batch files)
1596
- # Comment/typo heuristic still applies — these are cosmetic regardless of DAG state
1597
- if echo "$TITLE" | grep -qi "comment\|typo"; then
1598
- SWEEP_STILL_DEFERRED+=($FINDING_NUM)
1599
- echo "Sweep: #${FINDING_NUM} still deferred (comment/typo — cosmetic)"
1600
- else
1601
- # P3 same-file overlap no longer applies (DAG is drained, no active files)
1602
- # All other findings are safe to execute
1603
- SWEEP_EXECUTE+=($FINDING_NUM)
1604
- echo "Sweep: #${FINDING_NUM} cleared for execution (file overlap resolved)"
1605
- fi
1606
- done
1607
- ```
1608
-
1609
- **Step 4F.3: Dispatch cleared findings**
1610
-
1611
- For each finding in `SWEEP_EXECUTE`, add it to a fresh sweep DAG and dispatch using the same Steps 4A.pre.0, 4A.pre, and 4A logic. Run file-overlap detection between the swept findings themselves (they may conflict with each other).
1612
-
1613
- **MANDATORY**: Use the full Step 4A agent template verbatim for each swept finding. Do NOT use a bare `prompt="Run /work-on N"` — that bypasses the label-state loop contract, source branch detection, and all pipeline enforcement rules. Swept findings are always `review-finding` issues that require source branch detection to route correctly.
1614
-
1615
- **Step 4F.3.pre: Classify lane for each sweep finding (MANDATORY before dispatching)**
1616
-
1617
- Run `classify-lane.sh` per finding — same pattern as Step 4A.pre:
1618
-
1619
- ```bash
1620
- declare -A SWEEP_LANE
1621
- declare -A SWEEP_PR_BASE
1622
-
1623
- for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
1624
- SWEEP_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$FINDING_NUM" -R {GH_REPO}) || {
1625
- echo "ERROR: classify-lane.sh failed for #$FINDING_NUM — adding needs-human and skipping" >&2
1626
- gh issue edit "$FINDING_NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
1627
- continue
1628
- }
1629
- if [ "$SWEEP_BASE" = "staging" ]; then
1630
- SWEEP_LANE[$FINDING_NUM]="fast-lane"
1631
- else
1632
- SWEEP_LANE[$FINDING_NUM]="feature-lane"
1633
- fi
1634
- SWEEP_PR_BASE[$FINDING_NUM]="$SWEEP_BASE"
1635
- echo "#$FINDING_NUM → lane=${SWEEP_LANE[$FINDING_NUM]}, PR_BASE=$SWEEP_BASE"
1636
- done
1637
- ```
1638
-
1639
- **Step 4F.3.dispatch: Dispatch with full Step 4A template**
1640
-
1641
- ```bash
1642
- if [ ${#SWEEP_EXECUTE[@]} -gt 0 ]; then
1643
- echo "Completion sweep: dispatching ${#SWEEP_EXECUTE[@]} cleared findings"
1644
-
1645
- # Build sweep DAG — same conflict detection as Step 3C Layers 1-4
1646
- # but only among the swept findings (no original batch issues remain active)
1647
- # Dispatch ready findings, monitor completions using the same Step 4B loop
1648
- # This is a SINGLE pass — findings spawned during the sweep are NOT swept again
1649
- # (they follow the standard Step 4C triage: queued or deferred for next run)
1650
-
1651
- # NOTE: Step 4A.pre.0 (milestone branch pre-creation) is intentionally skipped here.
1652
- # Swept findings are always review-finding issues that target an already-existing branch
1653
- # (staging or a milestone branch). The branch was created when the original batch ran —
1654
- # it always exists by the time the sweep runs.
1655
-
1656
- for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
1657
- # Skip any finding whose classify-lane call failed (needs-human already set above)
1658
- [ -z "${SWEEP_PR_BASE[$FINDING_NUM]:-}" ] && continue
1659
-
1660
- FINDING_TITLE=$(gh issue view "$FINDING_NUM" -R {GH_REPO} --json title --jq '.title' 2>/dev/null || echo "")
1661
-
1662
- # Build GIST_CONTEXT for sweep finding — same as Step 4A's *fallback* (raw-gist) path.
1663
- # The Phase 2.5 FORGE:SYNTHESIS_BRIEF preference is intentionally NOT applied here:
1664
- # sweep findings are freshly-created review-finding issues that never received a
1665
- # synthesis brief (Phase 2.5 runs only over the original batch's investigations), so
1666
- # there is nothing to prefer. Keep this block in sync with 4A's fallback branch only.
1667
- GIST_CONTEXT=""
1668
- PARENT_INV=$(gh issue view "$FINDING_NUM" -R {GH_REPO} --json body --jq '.body' \
1669
- | grep -oP '(?i)parent[: ]*#\K\d+|spawned from[: ]*#\K\d+' | head -1)
1670
-
1671
- if [ -n "$PARENT_INV" ] && [ -n "${INVESTIGATION_GISTS[$PARENT_INV]:-}" ]; then
1672
- GIST_CONTEXT="
1673
- **CONTEXT FROM PRIOR INVESTIGATION**: Investigation #${PARENT_INV} produced Knowledge Gist(s) with findings relevant to this issue:
1674
- $(echo "${INVESTIGATION_GISTS[$PARENT_INV]}" | while IFS= read -r url; do echo "- ${url}"; done)
1675
- Fetch the Gist content during the context-gathering phase for implementation guidance."
1676
- fi
1677
-
1678
- if [ -n "$MILESTONE_INDEX_URL" ]; then
1679
- GIST_CONTEXT="${GIST_CONTEXT}
1680
-
1681
- **MILESTONE KNOWLEDGE INDEX**: All investigation findings for this milestone are aggregated in a single index Gist:
1682
- - ${MILESTONE_INDEX_URL}
1683
- The context-gathering phase can fetch this index to discover all investigation Gists for the milestone."
1684
- fi
1685
-
1686
- # Use the full Step 4A template verbatim — copied here so sweep agents receive
1687
- # the complete pipeline contract. Keep in sync with Step 4A when the template changes.
1688
- Agent(
1689
- subagent_type="general-purpose",
1690
- model="sonnet",
1691
- description="Work on {PROJECT_PREFIX}#${FINDING_NUM}",
1692
- run_in_background=true,
1693
- prompt="You are working on GitHub issue #${FINDING_NUM} for the {PROJECT_NAME} project.
1694
-
1695
- **Project**: {PROJECT_NAME}
1696
- **Repository**: {GH_REPO}
1697
- **Repo path**: {REPO_PATH}
1698
-
1699
- **YOUR MISSION**: Invoke \`/work-on\` via the Skill tool and let it run to completion. \`/work-on\` is a self-contained routing loop that handles the ENTIRE pipeline: investigate → build (context → architect → implement → validate) → review (push → PR → /review-pr --auto-merge) → close (project board → trajectory log → worktree cleanup). Do NOT intervene, compensate, or manually close issues — \`/work-on\` handles everything including issue closure and label updates in its close phase.
1700
-
1701
- **CRITICAL — DO NOT STOP EARLY**: /work-on runs as a multi-phase routing loop. Each phase (investigate, build, review, close) returns an intermediate result — these are NOT completion signals. You are NOT done until the issue reaches a terminal state: \`workflow:merged\`, \`workflow:invalid\`, or \`needs-human\`. If /work-on returns after only one phase (e.g., investigation), you MUST invoke it again immediately — it will re-read GitHub state and continue to the next phase. Keep invoking /work-on until it reaches a terminal state. Never output 'done' or stop after an intermediate result.
1702
-
1703
- **HOW REVIEW FINDINGS WORK**: /review-pr may create GitHub issues (with \`review-finding\` label) for findings it discovers. These are NOT blockers — they are separate work items that will go through their own /work-on pipeline later. The original PR should ALWAYS merge after review. The only exception is build errors (code doesn't compile) — those must be fixed before merging.
1704
-
1705
- **IMPORTANT RULES**:
1706
- - **MANDATORY**: You MUST use the Skill tool to invoke 'work-on' with args '${FINDING_NUM}'. Do NOT implement manually — /work-on handles the full pipeline including label state machine (workflow:investigating → workflow:building → workflow:in-review → workflow:merged), investigation reports, PR creation, and cleanup.
1707
- - For default repo issues: \`Skill(skill='work-on', args='${FINDING_NUM}')\`
1708
- - For satellite repo issues: \`Skill(skill='work-on', args='{SATELLITE_PREFIX}:${FINDING_NUM}')\` (prefix from forge.yaml → repos.satellites)
1709
- - NEVER bypass /work-on with manual git/gh commands — the label updates and structured comments are critical for tracking
1710
- - NEVER target \`main\` for PRs targeting the default repo. Use \`{STAGING_BRANCH}\` for fast-lane issues, or \`milestone/{slug}\` for milestone issues.
1711
- - Satellite repos (MCP, n8n) have no staging branch — fast-lane PRs go to \`main\` for those.
1712
- - If the issue is INVALID after investigation, close it with a comment explaining why
1713
- - If you hit merge conflicts or blockers, post a comment on the issue and STOP — do not force anything
1714
- - Do not interact with the user — you are running autonomously in the background
1715
- - **NEVER ask the user questions** — you are a background agent. If review finds issues, auto-fix simple ones and proceed. For complex findings, merge anyway and create follow-up issues.
1716
-
1717
- **LABEL-STATE LOOP CONTRACT — enforce after EVERY Skill return**:
1718
- After EVERY \`Skill(skill='work-on', ...)\` call returns, immediately check the issue's current workflow label:
1719
- \`\`\`bash
1720
- gh issue view ${FINDING_NUM} -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith(\"workflow:\"))]'
1721
- \`\`\`
1722
- **Terminal labels** (only these allow you to stop): \`workflow:merged\`, \`workflow:invalid\`
1723
- **Terminal condition also**: \`needs-human\` label present OR issue state is \`closed\`
1724
- If the label is NOT terminal (e.g., \`workflow:investigating\`, \`workflow:ready-to-build\`, \`workflow:building\`, \`workflow:in-review\`), invoke \`Skill(skill='work-on', args='${FINDING_NUM}')\` again immediately. The \`/work-on\` skill will re-read GitHub state and advance to the next phase. Do NOT output a summary, do NOT pause, do NOT ask for confirmation — just invoke it again.
1725
-
1726
- **CRITICAL — SOURCE BRANCH DETECTION**:
1727
- - If the issue has the \`review-finding\` label, read the issue body for \`**Code branch**: \\\`{branch}\\\`\`
1728
- - If found, that is the SOURCE_BRANCH — the code ONLY exists on that branch (e.g., \`staging\`), NOT on \`origin/main\`
1729
- - Investigation MUST use \`git show origin/{SOURCE_BRANCH}:{filepath}\` to verify the code exists
1730
- - Worktree MUST branch from \`origin/{SOURCE_BRANCH}\`, NOT \`origin/main\`
1731
- - PR target is \`{SOURCE_BRANCH}\` (the fix goes back to where the code lives)
1732
-
1733
- **LANE**: ${SWEEP_LANE[$FINDING_NUM]} (PR target: ${SWEEP_PR_BASE[$FINDING_NUM]})
1734
- **Issue title**: ${FINDING_TITLE}
1735
- ${GIST_CONTEXT}
1736
- "
1737
- )
1738
- done
1739
-
1740
- # Monitor sweep agents using the same Step 4B completion loop
1741
- # IMPORTANT: Findings spawned by sweep agents are NOT re-swept —
1742
- # they follow standard Step 4C triage to prevent recursive cascades
1743
- fi
1744
- ```
1745
-
1746
- **Step 4F.4: Report sweep results**
1747
-
1748
- ```
1749
- Completion Sweep Results:
1750
- Dispatched: #{A}, #{B} (file overlap cleared after DAG drain)
1751
- Still deferred (cosmetic): #{C} (comment/typo)
1752
- Permanently deferred (gen2): #{D} (generation >= 2 cascade cap)
1753
- ```
1754
-
1755
- **After sweep agents complete** (or if no findings were dispatched): proceed to Phase 5.
1756
-
1757
- **Anti-patterns — DO NOT DO THIS:**
1758
- - Re-sweeping findings spawned during the sweep itself — this creates unbounded recursion. Sweep is a single pass.
1759
- - Overriding generation >= 2 deferrals — the cascade cap is absolute.
1760
- - Skipping the sweep because "there are only a few" deferred findings — even one deferred finding represents unresolved work.
1761
-
1762
- ---
1763
-
1764
- ## Phase 5: Post-Batch Cleanup
1765
-
1766
- **This phase is MANDATORY after every orchestration batch.** It prevents the rot that accumulates when multiple agents merge PRs in parallel — stale labels, orphaned worktrees, unclosed issues.
1767
-
1768
- ### Step 5A: Run cleanup sweep
1769
-
1770
- Invoke the `/cleanup` skill with `all` to sweep everything:
1771
-
1772
- ```
1773
- Skill(skill="cleanup", args="all")
1774
- ```
1775
-
1776
- This will:
1777
- - Fix stale workflow labels on any issues the agents left behind
1778
- - Close orphaned open issues whose PRs were merged (common when merging to `staging`)
1779
- - Remove worktrees created by agents in this batch
1780
- - Delete local/remote `fix/*` and `feat/*` branches whose PR has merged — detection uses merged-PR state (`gh pr list --state merged`) as the source of truth, so it covers feature-lane branches merged into a milestone branch as well as squash-merged branches, not just branches merged directly to staging
1781
- - Report milestones that hit 0 open issues — these are ready for `/milestone ship` (staging review + merge). Do NOT close them; closure happens after code reaches staging.
1782
- - Sync Project board state
1783
-
1784
- ### Step 5B: Run agent audit
1785
-
1786
- Invoke `/audit-agents` on this session to measure pipeline efficiency:
1787
-
1788
- ```
1789
- Skill(skill="audit-agents", args="latest")
1790
- ```
1791
-
1792
- Include the audit summary in the final report (Phase 6). Key metrics to surface:
1793
- - **Avg idle%** — percentage of time agents spent stalled vs working
1794
- - **Resume cycles** — how many times agents had to be resumed
1795
- - **Stall boundaries** — which phase transitions cause the most stalls
1796
-
1797
- ### Step 5C: Report cleanup results
1798
-
1799
- Include the cleanup summary in the final report (Phase 6). If cleanup found problems, call them out — they indicate agent pipeline failures that may need investigation.
1800
-
1801
- ---
1802
-
1803
- ## Phase 6: Consolidated Report
1804
-
1805
- After ALL issues reach terminal state AND cleanup completes, present a final summary.
1806
-
1807
- ### Step 6A: Collect trajectory data
1808
-
1809
- ```bash
1810
- for NUM in {all_completed_issue_numbers}; do
1811
- gh api repos/{GH_REPO}/issues/${NUM}/comments \
1812
- --jq '.[] | select(.body | contains("<!-- FORGE:TRAJECTORY -->")) | .body' 2>/dev/null
1813
- done
1814
- ```
1815
-
1816
- Aggregate into the batch-level analytics for Step 6B.
1817
-
1818
- **Also collect the machine-readable summary cards** (`<!-- FORGE:CARD {json} -->`, embedded in
1819
- each issue's `FORGE:TRAJECTORY` comment by `/work-on` close phase). These power the per-issue and
1820
- batch cards in Step 6C:
1821
-
1822
- ```bash
1823
- CARDS=""
1824
- for NUM in {all_completed_issue_numbers}; do
1825
- CARD=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
1826
- --jq '.[] | select(.body | contains("FORGE:CARD")) | .body' 2>/dev/null \
1827
- | sed -n 's/.*<!-- FORGE:CARD \(.*\) -->.*/\1/p' | head -1)
1828
- [ -n "$CARD" ] && CARDS="${CARDS}${CARD}"$'\n'
1829
- done
1830
- # CARDS is now a newline-delimited list of per-issue JSON objects (skip any issue whose
1831
- # card is absent — pre-card runs or non-merged terminal states degrade gracefully).
1832
- ```
1833
-
1834
- ### Step 6B: Present consolidated report
1835
-
1836
- ```
1837
- ## Orchestration Complete
1838
-
1839
- **Scope**: {milestone / issue list}
1840
- **Duration**: {approximate time}
1841
-
1842
- ### Investigations (Phase 2)
1843
- {IF investigations ran:}
1844
- | # | Investigation | Issues Created | Result |
1845
- |---|-------------|---------------|--------|
1846
- | #{INV1} | {title} | #{N1}, #{N2}, #{N3} | ✓ Closed |
1847
- {ELSE: "No investigations in this batch."}
1848
-
1849
- ### Implementation Results
1850
-
1851
- | # | Issue | Source | Result | PR | Target |
1852
- |---|-------|--------|--------|----|--------|
1853
- | #{A} | {title} | original | ✓ Merged | #{PR} | staging |
1854
- | #{B} | {title} | original | ✓ Merged | #{PR} | milestone/x |
1855
- | #{N1} | {title} | from #{INV1} | ✓ Merged | #{PR} | staging |
1856
- | #{C} | {title} | original | ✗ Invalid | — | — |
1857
- | #{D} | {title} | original | ⚠ Blocked | — | — |
1858
- | #{E} | {title} | original | ⏭ Skipped (dep) | — | — |
1859
-
1860
- ### Review-Spawned Issues
1861
-
1862
- {IF review findings were created during any agent run:}
1863
- | # | Finding | Source Issue | Source PR | Status |
1864
- |---|---------|-------------|-----------|--------|
1865
- | #{F1} | {title} | #{A} | PR #{PR} | ✓ Merged (in-batch) / ✓ Swept (completion sweep) / ⏳ Deferred (cosmetic) / ⛔ Deferred (gen2 cascade cap) |
1866
-
1867
- {ELSE: "No review findings created during this batch."}
1868
-
1869
- ### Completion Sweep <!-- Added: forge#1105 -->
1870
- - **Re-evaluated**: {N} deferred findings re-checked after DAG drain
1871
- - **Dispatched**: {N} findings cleared and executed (file overlap resolved)
1872
- - **Still deferred (cosmetic)**: {N} comment/typo findings (low-value, safe to leave)
1873
- - **Permanently deferred (gen2)**: {N} generation >= 2 findings (cascade cap — requires manual `/work-on`)
1874
-
1875
- {IF permanently deferred > 0:}
1876
- **Action required**: The following findings were permanently deferred due to the generation >= 2 cascade cap. They will NOT be picked up automatically — run `/work-on #{N}` manually or include them in the next `/orchestrate` batch:
1877
- {list of permanently deferred issue numbers and titles}
1878
-
1879
- {IF cosmetic deferred > 0:}
1880
- **Low-priority leftovers**: The following cosmetic findings remain open. They will be picked up by future runs or can be batched with `/orchestrate #{N1} #{N2}`:
1881
- {list of cosmetic deferred issue numbers and titles}
1882
-
1883
- ### Summary
1884
- - **Investigations**: {N} completed, spawned {M} new issues
1885
- - **Review findings**: {N} spawned, {M} resolved in-batch, {K} swept, {J} deferred (cosmetic), {L} deferred (gen2)
1886
- - **Succeeded**: {N} issues resolved (implementation + sweep)
1887
- - **Failed**: {N} issues need attention
1888
- - **Skipped**: {N} issues (dependency failures)
1889
-
1890
- ### Post-Batch Cleanup
1891
- - Labels fixed: {N}
1892
- - Orphaned issues closed: {N}
1893
- - Worktrees removed: {N}
1894
- - Branches pruned: {N}
1895
- - Milestones closed: {N}
1896
-
1897
- ### Agent Efficiency (from /audit-agents)
1898
-
1899
- | Agent | Issue | Total | Active | Idle% | Resumes | Stall Points |
1900
- |-------|-------|-------|--------|-------|---------|--------------|
1901
- {per-agent rows from audit}
1902
-
1903
- **Batch efficiency**: {avg_idle_pct}% idle time
1904
- **Total resume cycles**: {sum_resumes} across {agent_count} agents
1905
- **Clean agents** (no stalls): {clean_count}/{agent_count}
1906
-
1907
- ### Batch Trajectory Analytics
1908
-
1909
- | Metric | Value |
1910
- |--------|-------|
1911
- | Issues attempted | {N} |
1912
- | Investigation invalidation rate | {N_invalid}/{N_investigated} ({%}) |
1913
- | Contracts posted | {N} |
1914
- | Contract→code divergences | {N} (agents that updated contract mid-build) |
1915
- | Review findings created | {N_total} |
1916
- | Findings queued after cascade control | {N_queued}/{N_total} ({N_deferred} deferred — see Step 4C) |
1917
- | Competing recommendations reconciled (Phase 2.5) | {RECONCILED_COUNT} (investigation plans arbitrated in place + serialized) |
1918
- | Findings validated | {N} |
1919
- | False positives | {N} ({%}) |
1920
- | Anomalies flagged | {N} |
1921
-
1922
- **Domain breakdown**: {N} scraping, {N} frontend, {N} billing, ...
1923
- **Routing**: {N} fast-lane, {N} feature-lane
1924
-
1925
- > `Findings queued after cascade control` reports Step 4C's defer/execute triage split (`QUEUED_FINDINGS` vs `DEFERRED_FINDINGS`) — it is NOT a dedup/arbitration computation; no such step exists for review findings. `Competing recommendations reconciled` is populated by Phase 2.5 (`RECONCILED_COUNT`), which reconciles investigation plans, not review findings. It is `0` when the batch had 0–1 investigations (synthesis is a no-op). <!-- Added: forge#1192, forge#1193 -->
1926
-
1927
- **Systematic issues** (flag if detected):
1928
- - False positive rate > 30% → review agents may need tuning
1929
- - Invalidation rate < 10% or > 35% → investigation calibration off
1930
- - Same-domain merge conflicts between concurrent agents → domain serialization edges too loose
1931
- - Contract divergences > 20% → investigation quality may need improvement
1932
- - **Idle% > 50%** → agents stalling at phase boundaries; check work-on routing loop
1933
- - **Avg resumes > 1** → orchestrator having to compensate for agent stops
1934
-
1935
- ### Next Steps
1936
- {If milestone and all issues done: "Milestone ready to ship. Run `/milestone ship {slug}` when ready. The ship command includes a pre-merge hunk-loss audit (Step 2.5) that detects staging-only hunks in milestone-modified files and rebases the milestone branch to absorb them before creating the PR — protecting against squash-merge regressions."}
1937
- {If some failed: "Issues #{X}, #{Y} need manual attention. Re-run with `/work-on #{X}` after resolving blockers."}
1938
- {If fast-lane: "All fixes merged to staging. Merge staging → main via GitHub web UI when ready to deploy."}
1939
- ```
1940
-
1941
- ### Step 6C: Pipeline Summary Cards (the shareable moment)
1942
-
1943
- Render one compact summary card per completed issue (from the `CARDS` JSON collected in Step 6A),
1944
- then a single batch-level summary card. These are the shareable artifacts a developer screenshots
1945
- after an orchestration run. Print all cards to stdout.
1946
-
1947
- **Per-issue card** — emit one for each JSON object in `CARDS`. Use the same box-drawing style and
1948
- 51-column inner width as `work-on/close.md` Phase C4.5b. Read fields directly from the JSON
1949
- (`issue`, `title`, `pipeline`, `commits`, `additions`, `deletions`, `pr`, `pr_target`, `review`,
1950
- `elapsed_seconds`). Truncate long titles with `…`; render `null` numeric fields as `—`. The
1951
- `pipeline`/`status` field already encodes skipped phases (decomposed/invalid/blocked), so reflect
1952
- it verbatim. Skip issues with no card (graceful — pre-card or non-merged runs).
1953
-
1954
- **Batch summary card** — aggregate across all collected cards:
1955
-
1956
- ```
1957
- ╔═══════════════════════════════════════════════════╗
1958
- ║ ForgeDock Orchestration Complete ║
1959
- ╠═══════════════════════════════════════════════════╣
1960
- ║ ║
1961
- ║ Scope: {milestone / issue list} ║
1962
- ║ Issues: {N} merged · {M} blocked · {K} invalid ║
1963
- ║ Commits: {SUM_COMMITS} ({SUM_ADD} additions, {SUM_DEL} deletions) ║
1964
- ║ PRs: {N} merged ║
1965
- ║ Findings: {N} spawned · {M} resolved ║
1966
- ║ Time: {BATCH_ELAPSED} ║
1967
- ║ ║
1968
- ╚═══════════════════════════════════════════════════╝
1969
- ```
1970
-
1971
- Aggregate with `jq` over the collected cards (every sum degrades gracefully — `null`/missing
1972
- fields are treated as 0; never fabricate):
1973
-
1974
- ```bash
1975
- echo "$CARDS" | grep -v '^$' | jq -s '{
1976
- merged: (map(select(.status=="merged")) | length),
1977
- blocked: (map(select(.status=="blocked")) | length),
1978
- invalid: (map(select(.status=="invalid")) | length),
1979
- commits: (map(.commits // 0) | add),
1980
- adds: (map(.additions // 0) | add),
1981
- dels: (map(.deletions // 0) | add),
1982
- prs: (map(select(.pr != null)) | length),
1983
- elapsed: (map(.elapsed_seconds // 0) | add)
1984
- }'
1985
- ```
1986
-
1987
- The batch card's `Findings:` line is filled from the review-finding counts already computed in
1988
- the Summary section above. `BATCH_ELAPSED` is the wall-clock duration of the orchestration run
1989
- (Step 6B `Duration`), not the sum of per-issue elapsed times.
1990
-
1991
- ---
1992
-
1993
- ## Safety Rules
1994
-
1995
- 1. **Every agent MUST invoke `/work-on` via the Skill tool.** No custom prompts. No manual implementation. Copy the Phase 4A template, fill in variables, done. (See HARD RULES at top of file.)
1996
- 2. **NEVER merge anything to `main`** — agents merge to `staging` or `milestone/{slug}` only
1997
- 3. **No artificial concurrency limit** — spawn as many agents as there are independent issues. Only file overlap and dependencies require sequencing.
1998
- 4. **Always confirm with user before launching** — Step 3E is the mandatory checkpoint
1999
- 5. **No retries** — if an agent fails, report it and move on
2000
- 6. **Respect existing work** — skip issues already being worked on (`workflow:building`, `workflow:in-review`)
2001
- 7. **Dependency failures cascade** — if A fails and B depends on A, all transitive dependents of A are skipped (not attempted)
2002
- 8. **Always run post-batch cleanup** — Phase 5 is mandatory. Never skip `/cleanup all` after orchestration.
2003
- 9. **NEVER close/skip issues as duplicates** — only `/work-on` investigation agents can make that call after examining the actual code. The orchestrator delegates, it does not adjudicate. **This is distinct from Phase 2.5 plan reconciliation**: reconciling *competing recommendations* across investigation annotations (arbitrating incompatible plans, adding `Depends on #X` serialization edges) operates only on FORGE annotations and issue bodies — it never reads code and never closes/skips/merges an issue. Both issues in a reconciled conflict still run. Adjudicating *duplicate validity* (deciding two issues are the same bug and closing one) remains forbidden. <!-- Added: forge#1192 -->
2004
- 10. **Per-completion verification** — after each agent completes, check that it has `workflow:*` labels and structured comments. If an agent bypassed `/work-on`, report it as a pipeline failure.
2005
-
2006
- ---
2007
-
2008
- ## Examples
2009
-
2010
- ### "orchestrate milestone api-expansion"
2011
- → Fetches all open issues in "API Expansion v1" milestone
2012
- → Builds DAG: #1526 → #1527 → #1528 → #1529 (chain), #1530 (no predecessors)
2013
- → Confirms with user, dispatches #1526 + #1530 immediately
2014
- → When #1526 completes, dispatches #1527 immediately (doesn't wait for #1530)
2015
- → Streaming continues until all issues reach terminal state
2016
-
2017
- ### "orchestrate #1533 #1250"
2018
- → Fetches those 2 issues, no deps between them
2019
- → DAG: both have empty predecessor sets
2020
- → Confirms, dispatches both simultaneously
2021
-
2022
- ### "orchestrate next 5"
2023
- → Gets top 5 priority issues, builds DAG
2024
- → Confirms, dispatches all ready issues immediately
2025
- → Dependent issues dispatch as predecessors complete
2026
-
2027
- ### "orchestrate P0"
2028
- → Gets all P0 issues, runs them all (presumably urgent)
2029
- → DAG: likely all independent — dispatches simultaneously
2030
- → Confirms, executes
2031
-
2032
- ### "orchestrate fast-lane"
2033
- → Gets all unmilestoned bugs/fixes
2034
- → Builds DAG from file-conflict and domain-serialization edges
2035
- → Dispatches all ready issues, streams dependent issues as predecessors complete
2036
-
2037
- ### "orchestrate milestone user-auth-v2"
2038
- → Fetches all open issues in "User Auth v2" milestone
2039
- → Detects #42 is an investigation issue (title: "Investigate: session expiry race condition under high concurrency")
2040
- → Phase 2: Runs `/work-on 42` — investigation creates 3 new issues (#55-#57)
2041
- → Re-fetches milestone issues, now includes #55-#57
2042
- → Builds DAG: #38, #41, #55, #56 have no predecessors (ready); #39 depends on #38; #57 depends on #55
2043
- → Dispatches #38, #41, #55, #56 simultaneously
2044
- → When #38 completes, #39 dispatches immediately (doesn't wait for #41, #55, or #56)
2045
- → When #55 completes, #57 dispatches immediately (doesn't wait for others)
2046
- → Final report shows investigation spawned issues and streaming execution results
43
+ The orchestrator reads only the phase file(s) relevant to the current step rather than
44
+ loading the full 2300-line monolith upfront.