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.
- package/README.md +255 -211
- package/bin/batch-runner.mjs +339 -0
- package/bin/cinema.mjs +320 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +168 -0
- package/bin/engine/projector.mjs +32 -0
- package/bin/engine/reconcile.mjs +11 -0
- package/bin/engine/runlog.mjs +79 -0
- package/bin/engine/state.mjs +32 -0
- package/bin/engine-cli.mjs +222 -0
- package/bin/engine.mjs +138 -0
- package/bin/forgedock.mjs +896 -1540
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +841 -0
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +136 -20
- package/bin/settings-hook.mjs +346 -0
- package/bin/tui.mjs +6 -3
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +84 -12
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +63 -4
- package/commands/design-bench.md +6 -1
- package/commands/design-render-critique-loop.md +2 -1
- package/commands/design.md +1 -0
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +122 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +24 -3
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2027
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +326 -42
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +612 -56
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +19 -5
- package/commands/work-on/build/validate.md +62 -6
- package/commands/work-on/build.md +187 -30
- package/commands/work-on/close.md +132 -5
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +48 -4
- package/commands/work-on-monolithic.md +8 -2
- package/commands/work-on.md +247 -7
- package/docs/BRAND.md +16 -5
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +44 -42
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +58 -12
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +10 -2
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
- package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
- package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
- package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +12 -4
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/verify-env-vars.sh +18 -2
- package/scripts/verify-host-headers.sh +4 -4
- package/scripts/verify-route-registration.sh +2 -2
- package/scripts/worktree-lifecycle.sh +202 -0
- package/templates/devdocs/agent/using-forgedock.md +14 -0
package/commands/orchestrate.md
CHANGED
|
@@ -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
|
-
|
|
12
|
+
This file is the slim dispatcher. Detailed phase content lives in `commands/orchestrate/`.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## Execution Order
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Read and execute phases in sequence. Each phase file is self-contained.
|
|
17
17
|
|
|
18
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
-
|
|
1215
|
-
|
|
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.
|