forgedock 1.1.1 → 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 +53 -8
- package/bin/batch-runner.mjs +339 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +20 -0
- package/bin/engine/state.mjs +8 -3
- package/bin/engine-cli.mjs +177 -5
- package/bin/forgedock.mjs +409 -26
- 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 +72 -14
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +68 -19
- package/bin/settings-hook.mjs +235 -1
- 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 +80 -8
- 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 +58 -1
- package/commands/design-bench.md +5 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +121 -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 +23 -2
- 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 -2045
- 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 +248 -10
- 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 +591 -52
- 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 +3 -1
- package/commands/work-on/build/validate.md +26 -7
- package/commands/work-on/build.md +163 -24
- package/commands/work-on/close.md +130 -3
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +24 -4
- package/commands/work-on-monolithic.md +7 -2
- package/commands/work-on.md +202 -4
- package/docs/BRAND.md +14 -7
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +5 -8
- 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 +43 -4
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +9 -1
- 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/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 +3 -1
- 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/worktree-lifecycle.sh +3 -3
- package/templates/devdocs/agent/using-forgedock.md +14 -0
- package/docs/site/.vitepress/config.mjs +0 -85
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
---
|
|
2
|
+
install: internal
|
|
3
|
+
---
|
|
4
|
+
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
6
|
+
|
|
7
|
+
# /orchestrate — Phase 4: Streaming DAG Execution
|
|
8
|
+
|
|
9
|
+
## Phase 4: Streaming DAG Execution
|
|
10
|
+
|
|
11
|
+
### Step 4A-pre: Staging baseline tracking (MANDATORY — continuous)
|
|
12
|
+
|
|
13
|
+
**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.
|
|
14
|
+
|
|
15
|
+
**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/*`.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Capture initial staging baseline before first dispatch
|
|
19
|
+
git fetch origin
|
|
20
|
+
if [ "$DEFAULT_BRANCH" = "$STAGING_BRANCH" ]; then
|
|
21
|
+
STAGING_LINES_BASELINE=0
|
|
22
|
+
echo "Staging baseline: skipped — single-branch repo (staging == default)"
|
|
23
|
+
else
|
|
24
|
+
STAGING_LINES_BASELINE=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
|
|
25
|
+
| tail -1 \
|
|
26
|
+
| grep -oP '\d+ insertion' \
|
|
27
|
+
| grep -oP '\d+' \
|
|
28
|
+
|| echo "0")
|
|
29
|
+
echo "Staging baseline: ${STAGING_LINES_BASELINE} lines ahead of $DEFAULT_BRANCH"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Track cumulative expected growth from merged PRs
|
|
33
|
+
CUMULATIVE_EXPECTED_DELTA=0
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Per-agent-completion integrity check** (run in Step 4B after each agent merges a PR targeting staging):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# After agent completes and its PR merges to staging:
|
|
40
|
+
git fetch origin
|
|
41
|
+
if [ "$DEFAULT_BRANCH" != "$STAGING_BRANCH" ]; then
|
|
42
|
+
STAGING_LINES_NOW=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
|
|
43
|
+
| tail -1 \
|
|
44
|
+
| grep -oP '\d+ insertion' \
|
|
45
|
+
| grep -oP '\d+' \
|
|
46
|
+
|| echo "0")
|
|
47
|
+
STAGING_TOTAL_GROWTH=$((STAGING_LINES_NOW - STAGING_LINES_BASELINE))
|
|
48
|
+
|
|
49
|
+
# Add this PR's line count to cumulative expected delta
|
|
50
|
+
CUMULATIVE_EXPECTED_DELTA=$((CUMULATIVE_EXPECTED_DELTA + {THIS_PR_LINE_COUNT}))
|
|
51
|
+
|
|
52
|
+
UNEXPECTED_GROWTH=$((STAGING_TOTAL_GROWTH - CUMULATIVE_EXPECTED_DELTA))
|
|
53
|
+
if [ "$UNEXPECTED_GROWTH" -gt 500 ]; then
|
|
54
|
+
echo "ALERT: Staging grew by ${STAGING_TOTAL_GROWTH} lines (+${UNEXPECTED_GROWTH} beyond expected ${CUMULATIVE_EXPECTED_DELTA})."
|
|
55
|
+
echo "This may indicate milestone-code contamination via agent merge commits."
|
|
56
|
+
echo "Review: git log --oneline --merges origin/$DEFAULT_BRANCH..origin/$STAGING_BRANCH"
|
|
57
|
+
echo "Do NOT merge $STAGING_BRANCH → $DEFAULT_BRANCH until the unexpected growth is investigated."
|
|
58
|
+
# Do NOT auto-stop — alert the user and let them decide
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If `UNEXPECTED_GROWTH > 500`, report the alert clearly before dispatching any more agents. The user confirms whether to continue.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### Step 4A.pre.0: Pre-create milestone branches for ready issues (MANDATORY before classify-lane) <!-- Added: forge#901 -->
|
|
68
|
+
|
|
69
|
+
**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.
|
|
70
|
+
|
|
71
|
+
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.
|
|
72
|
+
|
|
73
|
+
**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).
|
|
74
|
+
|
|
75
|
+
**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 -->
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Pre-create the origin milestone branch for every distinct milestone referenced by ready issues.
|
|
79
|
+
# Slugification MUST byte-match scripts/classify-lane.sh — otherwise a branch is created that
|
|
80
|
+
# the classifier will not select. Keep these two slug pipelines identical.
|
|
81
|
+
git fetch origin
|
|
82
|
+
|
|
83
|
+
# Collect distinct milestone titles among the ready issues
|
|
84
|
+
declare -A SEEN_MILESTONE_SLUG
|
|
85
|
+
for NUM in {ready_issue_numbers}; do
|
|
86
|
+
MILESTONE_TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null || echo "")
|
|
87
|
+
[ -z "$MILESTONE_TITLE" ] && continue # fast-lane issue — no milestone branch needed
|
|
88
|
+
|
|
89
|
+
# Slugify — IDENTICAL to classify-lane.sh: lowercase → spaces-to-hyphens →
|
|
90
|
+
# strip non-[a-z0-9-] → collapse hyphens → strip leading/trailing hyphens.
|
|
91
|
+
SLUG=$(echo "$MILESTONE_TITLE" \
|
|
92
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
93
|
+
| tr ' ' '-' \
|
|
94
|
+
| tr -cd 'a-z0-9-' \
|
|
95
|
+
| sed 's/--*/-/g' \
|
|
96
|
+
| sed 's/^-//;s/-$//')
|
|
97
|
+
|
|
98
|
+
# Empty-slug guard (matches classify-lane.sh): a title with no ASCII letters/digits/hyphens
|
|
99
|
+
# would produce "milestone/", an invalid ref. Skip and let classify-lane surface the error.
|
|
100
|
+
if [ -z "$SLUG" ]; then
|
|
101
|
+
echo "WARN: milestone title '$MILESTONE_TITLE' (issue #$NUM) produced an empty slug — skipping pre-creation; classify-lane will hard-fail." >&2
|
|
102
|
+
continue
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# De-dupe: only attempt creation once per milestone
|
|
106
|
+
[ -n "${SEEN_MILESTONE_SLUG[$SLUG]:-}" ] && continue
|
|
107
|
+
SEEN_MILESTONE_SLUG[$SLUG]=1
|
|
108
|
+
|
|
109
|
+
LANE="milestone/$SLUG"
|
|
110
|
+
if git ls-remote --exit-code origin "$LANE" >/dev/null 2>&1; then
|
|
111
|
+
echo "Milestone branch '$LANE' already exists on origin — no action."
|
|
112
|
+
continue
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Create-if-absent from the default branch (matches /milestone create).
|
|
116
|
+
# $DEFAULT_BRANCH is resolved from forge.yaml at the top of this command.
|
|
117
|
+
echo "Pre-creating milestone branch '$LANE' from origin/$DEFAULT_BRANCH …"
|
|
118
|
+
if git push origin "origin/$DEFAULT_BRANCH:refs/heads/$LANE" 2>/dev/null; then
|
|
119
|
+
echo "Created milestone branch '$LANE'."
|
|
120
|
+
elif git ls-remote --exit-code origin "$LANE" >/dev/null 2>&1; then
|
|
121
|
+
# A concurrent orchestrator (or an agent) created it first — harmless. Never force-push.
|
|
122
|
+
echo "Milestone branch '$LANE' was created concurrently — proceeding with the existing branch."
|
|
123
|
+
else
|
|
124
|
+
echo "ERROR: failed to pre-create milestone branch '$LANE' from origin/$DEFAULT_BRANCH." >&2
|
|
125
|
+
echo " classify-lane.sh will hard-fail for issues in this milestone until the branch exists." >&2
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
### Step 4A.pre: Classify lane for each issue (MANDATORY before dispatching agents)
|
|
133
|
+
|
|
134
|
+
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.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Requires classify-lane.sh to be available at ~/.claude/scripts/classify-lane.sh
|
|
138
|
+
# (installed by `npx forgedock` — see bin/forgedock.mjs linkScripts step)
|
|
139
|
+
# Fallback: bash "$FORGE_HOME/scripts/classify-lane.sh" if ~/.claude/scripts/ is unavailable
|
|
140
|
+
|
|
141
|
+
declare -A ISSUE_LANE
|
|
142
|
+
declare -A ISSUE_PR_BASE
|
|
143
|
+
|
|
144
|
+
# Batch-level accumulators for review-finding cascade control (Step 4C) and
|
|
145
|
+
# Completion Sweep (Step 4F). Declared here so they persist across ALL agent
|
|
146
|
+
# completions — Step 4C runs per-agent and must NOT re-initialize these.
|
|
147
|
+
DEFERRED_FINDINGS=()
|
|
148
|
+
QUEUED_FINDINGS=()
|
|
149
|
+
declare -A DEFERRED_REASONS
|
|
150
|
+
declare -A AGENT_ISSUE_MAP
|
|
151
|
+
|
|
152
|
+
for NUM in {ready_issue_numbers}; do
|
|
153
|
+
PR_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$NUM" -R {GH_REPO}) || {
|
|
154
|
+
echo "ERROR: classify-lane.sh failed for #$NUM — adding needs-human label and skipping" >&2
|
|
155
|
+
gh issue edit "$NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
# Derive LANE label from PR_BASE
|
|
159
|
+
if [ "$PR_BASE" = "staging" ]; then
|
|
160
|
+
LANE="fast-lane"
|
|
161
|
+
else
|
|
162
|
+
LANE="feature-lane"
|
|
163
|
+
fi
|
|
164
|
+
ISSUE_LANE[$NUM]="$LANE"
|
|
165
|
+
ISSUE_PR_BASE[$NUM]="$PR_BASE"
|
|
166
|
+
echo "#$NUM → lane=$LANE, PR_BASE=$PR_BASE"
|
|
167
|
+
done
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
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 -->
|
|
171
|
+
|
|
172
|
+
### Step 4A: Dispatch ready issues
|
|
173
|
+
|
|
174
|
+
**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.**
|
|
175
|
+
|
|
176
|
+
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.
|
|
177
|
+
|
|
178
|
+
**One agent per issue.** Do NOT group multiple issues into a single agent. `/work-on` handles branching, labels, and PRs per-issue.
|
|
179
|
+
|
|
180
|
+
**Copy this template. Fill in variables. Do not modify the structure:**
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Agent(
|
|
184
|
+
subagent_type="general-purpose",
|
|
185
|
+
model="sonnet",
|
|
186
|
+
description="Work on {PROJECT_PREFIX}#{NUMBER}",
|
|
187
|
+
run_in_background=true,
|
|
188
|
+
prompt="You are working on GitHub issue #{NUMBER} for the {PROJECT_NAME} project.
|
|
189
|
+
|
|
190
|
+
**Project**: {PROJECT_NAME}
|
|
191
|
+
**Repository**: {GH_REPO}
|
|
192
|
+
**Repo path**: {REPO_PATH}
|
|
193
|
+
|
|
194
|
+
**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.
|
|
195
|
+
|
|
196
|
+
**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.
|
|
197
|
+
|
|
198
|
+
**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.
|
|
199
|
+
|
|
200
|
+
**IMPORTANT RULES**:
|
|
201
|
+
- **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.
|
|
202
|
+
- For default repo issues: `Skill(skill='work-on', args='{NUMBER}')`
|
|
203
|
+
- For satellite repo issues: `Skill(skill='work-on', args='{SATELLITE_PREFIX}:{NUMBER}')` (prefix from forge.yaml → repos.satellites)
|
|
204
|
+
- NEVER bypass /work-on with manual git/gh commands — the label updates and structured comments are critical for tracking
|
|
205
|
+
- NEVER target `main` for PRs targeting the default repo. Use `{STAGING_BRANCH}` for fast-lane issues, or `milestone/{slug}` for milestone issues.
|
|
206
|
+
- Satellite repos (MCP, n8n) have no staging branch — fast-lane PRs go to `main` for those.
|
|
207
|
+
- If the issue is INVALID after investigation, close it with a comment explaining why
|
|
208
|
+
- If you hit merge conflicts or blockers, post a comment on the issue and STOP — do not force anything
|
|
209
|
+
- Do not interact with the user — you are running autonomously in the background
|
|
210
|
+
- **NEVER ask the user questions** — you are a background agent. If review finds issues, auto-fix simple ones and proceed. For complex findings on **low-risk domains**, merge anyway and create follow-up issues. For complex findings on **high-risk domains** (AUTH, BILLING, DATABASE, or any domain tagged as security-critical in Step 3B), add the `needs-human` label and stop — do NOT merge. High-risk `needs-human` halts are surfaced as `⚠ Blocked` in the Phase 5 completion report.
|
|
211
|
+
|
|
212
|
+
**LABEL-STATE LOOP CONTRACT — enforce after EVERY Skill return**:
|
|
213
|
+
After EVERY `Skill(skill='work-on', ...)` call returns, immediately check the issue's current workflow label:
|
|
214
|
+
```bash
|
|
215
|
+
gh issue view {NUMBER} -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith("workflow:"))]'
|
|
216
|
+
```
|
|
217
|
+
**Terminal labels** (only these allow you to stop): `workflow:merged`, `workflow:invalid`
|
|
218
|
+
**Terminal condition also**: `needs-human` label present OR issue state is `closed`
|
|
219
|
+
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.
|
|
220
|
+
|
|
221
|
+
**CRITICAL — SOURCE BRANCH DETECTION**:
|
|
222
|
+
- If the issue has the `review-finding` label, read the issue body for `**Code branch**: \`{branch}\``
|
|
223
|
+
- If found, that is the SOURCE_BRANCH — the code ONLY exists on that branch (e.g., `staging`), NOT on `origin/main`
|
|
224
|
+
- Investigation MUST use `git show origin/{SOURCE_BRANCH}:{filepath}` to verify the code exists
|
|
225
|
+
- Worktree MUST branch from `origin/{SOURCE_BRANCH}`, NOT `origin/main`
|
|
226
|
+
- PR target is `{SOURCE_BRANCH}` (the fix goes back to where the code lives)
|
|
227
|
+
|
|
228
|
+
**LANE**: {LANE} (PR target: {PR_BASE})
|
|
229
|
+
**Issue title**: {ISSUE_TITLE}
|
|
230
|
+
{GIST_CONTEXT}
|
|
231
|
+
"
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**`{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 -->
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Build GIST_CONTEXT for an issue
|
|
239
|
+
GIST_CONTEXT=""
|
|
240
|
+
|
|
241
|
+
# Preferred path: a deconflicted per-issue synthesis brief from Phase 2.5.
|
|
242
|
+
SYNTHESIS_BRIEF=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
243
|
+
--jq '[.[] | select(.body | contains("<!-- FORGE:SYNTHESIS_BRIEF -->"))] | last | .body // ""' 2>/dev/null)
|
|
244
|
+
|
|
245
|
+
if [ -n "$SYNTHESIS_BRIEF" ]; then
|
|
246
|
+
# Phase 2.5 ran and reconciled competing recommendations for this issue.
|
|
247
|
+
# Inject the deconflicted brief INSTEAD of the raw milestone-index gist dump.
|
|
248
|
+
GIST_CONTEXT="
|
|
249
|
+
**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.
|
|
250
|
+
${SYNTHESIS_BRIEF}"
|
|
251
|
+
else
|
|
252
|
+
# Fallback: Phase 2.5 did not run (0/1 investigations). Use the raw gist behavior.
|
|
253
|
+
PARENT_INV=$(gh issue view {NUMBER} -R {GH_REPO} --json body --jq '.body' \
|
|
254
|
+
| grep -oP '(?i)parent[: ]*#\K\d+|spawned from[: ]*#\K\d+' | head -1)
|
|
255
|
+
|
|
256
|
+
if [ -n "$PARENT_INV" ] && [ -n "${INVESTIGATION_GISTS[$PARENT_INV]:-}" ]; then
|
|
257
|
+
GIST_CONTEXT="
|
|
258
|
+
**CONTEXT FROM PRIOR INVESTIGATION**: Investigation #${PARENT_INV} produced Knowledge Gist(s) with findings relevant to this issue:
|
|
259
|
+
$(echo "${INVESTIGATION_GISTS[$PARENT_INV]}" | while IFS= read -r url; do echo "- ${url}"; done)
|
|
260
|
+
Fetch the Gist content during the context-gathering phase for implementation guidance."
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Include milestone index URL if available (from Step 2C.5)
|
|
264
|
+
if [ -n "$MILESTONE_INDEX_URL" ]; then
|
|
265
|
+
GIST_CONTEXT="${GIST_CONTEXT}
|
|
266
|
+
|
|
267
|
+
**MILESTONE KNOWLEDGE INDEX**: All investigation findings for this milestone are aggregated in a single index Gist:
|
|
268
|
+
- ${MILESTONE_INDEX_URL}
|
|
269
|
+
The context-gathering phase can fetch this index to discover all investigation Gists for the milestone."
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
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 -->
|
|
275
|
+
|
|
276
|
+
**Hot-copy CONTRACT context** (extends `{GIST_CONTEXT}` for milestone-lane issues where the parent issue already carries a `FORGE:CONTRACT` annotation): <!-- Added: forge#1277 -->
|
|
277
|
+
|
|
278
|
+
When a DAG node issue was spawned from a decomposition (parent issue has `workflow:decomposed` label and the child issue body references `**Parent**: #NNN`), the parent issue may already have a `FORGE:CONTRACT` annotation that was posted before decomposition. Inject a scoped excerpt into the child's prompt so the child does not re-fetch it.
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Hot-copy: inject parent CONTRACT annotation excerpt into GIST_CONTEXT (milestone lane only)
|
|
282
|
+
PARENT_NUM=$(gh issue view {NUMBER} -R {GH_REPO} --json body --jq '.body' \
|
|
283
|
+
| grep -oP '(?i)\*\*Parent\*\*[: ]*#\K\d+' | head -1)
|
|
284
|
+
|
|
285
|
+
if [ -n "$PARENT_NUM" ]; then
|
|
286
|
+
PARENT_CONTRACT=$(gh api repos/{GH_REPO}/issues/${PARENT_NUM}/comments \
|
|
287
|
+
--jq '[.[] | select(.body | contains("<!-- FORGE:CONTRACT -->"))] | last | .body // ""' 2>/dev/null \
|
|
288
|
+
| head -40) # Scope: first 40 lines — Proposed Approach + Deliverables table only
|
|
289
|
+
|
|
290
|
+
if [ -n "$PARENT_CONTRACT" ]; then
|
|
291
|
+
GIST_CONTEXT="${GIST_CONTEXT}
|
|
292
|
+
|
|
293
|
+
**HOT COPY — PARENT FORGE:CONTRACT** (from parent issue #${PARENT_NUM}; do not re-fetch — durable record is on that issue):
|
|
294
|
+
${PARENT_CONTRACT}"
|
|
295
|
+
fi
|
|
296
|
+
fi
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
If the issue has no parent reference, or the parent has no `FORGE:CONTRACT` annotation, this block produces no output and `GIST_CONTEXT` is unchanged. The hot-copy is an optimization — the durable annotation on the parent issue remains the authoritative record for compaction recovery.
|
|
300
|
+
|
|
301
|
+
**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:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
# After the single-message batch spawn, capture each returned ID:
|
|
305
|
+
AGENT_ISSUE_MAP[{NUMBER}] = <agent_id returned by Agent()>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`AGENT_ISSUE_MAP` starts empty and accumulates entries as agents are spawned. For parallel dispatch (all Agent() calls in one message), capture the returned IDs from the batch response — one entry per issue — before entering Step 4B's monitoring loop. Without this capture, `resume=` calls in Steps 4B and 4B.5 will have no agent ID to reference and the resume will fail. <!-- Added: forge#1083 -->
|
|
309
|
+
|
|
310
|
+
**Launch all ready agents simultaneously** by putting multiple Agent tool calls in a single message. Use `run_in_background=true` so they execute in parallel.
|
|
311
|
+
|
|
312
|
+
### Step 4B: Monitor completions and dispatch newly ready issues
|
|
313
|
+
|
|
314
|
+
You will be automatically notified when each background agent completes. **Do NOT use `sleep` loops to poll for completion.** Instead, wait for the automatic notification. When you receive a notification that an agent completed, immediately process it.
|
|
315
|
+
|
|
316
|
+
**Successor dispatch latency is measured from `agent_completed`, not from orchestrator polling.** The moment you receive an `agent_completed` notification for issue N, that is t=0 for dispatching N's successors. Any successor whose predecessors are all now terminal MUST be dispatched in the same response that processes the notification — not after a poll cycle, not after a sleep. This is the design property that makes streaming DAG execution faster than wave-based execution. <!-- Added: forge#1251 -->
|
|
317
|
+
|
|
318
|
+
**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.
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# After each agent completion, check for newly ready issues:
|
|
322
|
+
for BLOCKED_NUM in {all_blocked_issue_numbers}; do
|
|
323
|
+
ALL_PREDS_DONE=true
|
|
324
|
+
for PRED in {predecessors_of_BLOCKED_NUM}; do
|
|
325
|
+
PRED_STATE=$(gh issue view $PRED -R {GH_REPO} --json labels,state --jq '{state: .state, workflow: [.labels[].name | select(startswith("workflow:"))]}')
|
|
326
|
+
# Terminal states: workflow:merged, workflow:invalid, needs-human, or state=CLOSED
|
|
327
|
+
if ! echo "$PRED_STATE" | grep -qE 'workflow:merged|workflow:invalid|needs-human|CLOSED'; then
|
|
328
|
+
ALL_PREDS_DONE=false
|
|
329
|
+
break
|
|
330
|
+
fi
|
|
331
|
+
done
|
|
332
|
+
if [ "$ALL_PREDS_DONE" = "true" ]; then
|
|
333
|
+
echo "#{BLOCKED_NUM} is now READY — all predecessors resolved. Dispatching."
|
|
334
|
+
# Add to dispatch batch for this completion cycle
|
|
335
|
+
fi
|
|
336
|
+
done
|
|
337
|
+
# Run Steps 4A.pre.0 → 4A.pre → 4A for newly ready issues (batch them in a single message)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**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:
|
|
341
|
+
|
|
342
|
+
1. **Check if the agent completed the FULL pipeline** — not just one phase:
|
|
343
|
+
```bash
|
|
344
|
+
# Check final workflow state — only workflow:merged or workflow:invalid means truly done
|
|
345
|
+
FINAL_STATE=$(gh issue view $NUM -R {GH_REPO} --json labels,state --jq '{state: .state, workflow: [.labels[].name | select(startswith("workflow:"))]}')
|
|
346
|
+
echo "#{NUM}: $FINAL_STATE"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
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**:
|
|
350
|
+
```
|
|
351
|
+
Agent(
|
|
352
|
+
resume=AGENT_ISSUE_MAP[{NUMBER}],
|
|
353
|
+
description="Resume #{NUMBER} pipeline",
|
|
354
|
+
run_in_background=true,
|
|
355
|
+
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."
|
|
356
|
+
)
|
|
357
|
+
```
|
|
358
|
+
**Resume ALL stalled agents in a single message** (parallel resume). Do not wait between resumes.
|
|
359
|
+
|
|
360
|
+
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.
|
|
361
|
+
|
|
362
|
+
4. **Record completed results**: Success (PR merged), Invalid (issue closed), Blocked (needs human), or Error
|
|
363
|
+
|
|
364
|
+
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.
|
|
365
|
+
|
|
366
|
+
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.
|
|
367
|
+
|
|
368
|
+
7. **Verify pipeline compliance** — for each truly completed issue, check that the agent used `/work-on`:
|
|
369
|
+
```bash
|
|
370
|
+
LABELS=$(gh issue view $NUM -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith("workflow:"))] | length')
|
|
371
|
+
COMMENTS=$(gh api repos/{GH_REPO}/issues/${NUM}/comments --jq '[.[] | select(.body | test("FORGE:INVESTIGATOR|FORGE:BUILDER"))] | length')
|
|
372
|
+
if [ "$LABELS" -eq 0 ] || [ "$COMMENTS" -eq 0 ]; then
|
|
373
|
+
echo "PIPELINE FAILURE: #{NUM} — agent bypassed /work-on (no labels or structured comments)"
|
|
374
|
+
fi
|
|
375
|
+
```
|
|
376
|
+
If an agent bypassed the pipeline, report it as a **failure** regardless of whether a PR exists.
|
|
377
|
+
|
|
378
|
+
8. **Post a brief status update** to the user after each agent reaches terminal state:
|
|
379
|
+
```
|
|
380
|
+
✓ #{NUMBER} — {title} → PR #{PR} merged to {target}
|
|
381
|
+
✗ #{NUMBER} — {title} → {reason for failure}
|
|
382
|
+
⚠ #{NUMBER} — {title} → PIPELINE BYPASS (no /work-on — PR invalid)
|
|
383
|
+
⏳ Progress: {completed}/{total} complete, {active} active, {blocked} blocked
|
|
384
|
+
→ Dispatched #{NEWLY_READY} (predecessor #{PRED} completed)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
9. **Run staging integrity check** (from Step 4A-pre) if the completed agent merged a PR targeting staging.
|
|
388
|
+
|
|
389
|
+
**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.
|
|
390
|
+
|
|
391
|
+
**Anti-pattern — DO NOT DO THIS:**
|
|
392
|
+
- `sleep 60/120/180/300` loops to check status — you will be notified automatically
|
|
393
|
+
- Spawning separate "progress check" agents — they waste tokens and add noise
|
|
394
|
+
- Reading agent JSONL output files to check progress — use GitHub labels as the source of truth
|
|
395
|
+
- Polling the same status check repeatedly on a timer
|
|
396
|
+
- Waiting for a "batch" of completions before checking for newly ready issues — check after EVERY completion
|
|
397
|
+
|
|
398
|
+
### Step 4B.5: Time-Based Stall Detection
|
|
399
|
+
|
|
400
|
+
**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.
|
|
401
|
+
|
|
402
|
+
**When to run** (NOT a sleep loop — two trigger points only):
|
|
403
|
+
1. On every background agent completion event (run BEFORE the terminal-state check in Step 4B)
|
|
404
|
+
2. Before posting any "waiting for agents..." status update to the user
|
|
405
|
+
|
|
406
|
+
**Do NOT poll on a timer. Do NOT use sleep. Run at these two trigger points only.**
|
|
407
|
+
|
|
408
|
+
**Read stall timeout from config**:
|
|
409
|
+
```bash
|
|
410
|
+
STALL_TIMEOUT=$(yq '.pipeline.stall_timeout_minutes // 15' forge.yaml 2>/dev/null || echo 15)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**For each non-terminal agent in the current batch**:
|
|
414
|
+
```bash
|
|
415
|
+
for NUM in {active_issue_numbers}; do
|
|
416
|
+
# Skip issues already in terminal state
|
|
417
|
+
TERMINAL=$(gh issue view $NUM -R {GH_REPO} --json labels \
|
|
418
|
+
--jq '[.labels[].name | select(. == "workflow:merged" or . == "workflow:invalid" or . == "needs-human")] | length')
|
|
419
|
+
[ "$TERMINAL" -gt 0 ] && continue
|
|
420
|
+
|
|
421
|
+
# Get last activity timestamp — prefer last comment (catches FORGE:HEARTBEAT updates)
|
|
422
|
+
LAST_ACTIVITY=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
423
|
+
--jq '.[-1].updated_at // empty' 2>/dev/null)
|
|
424
|
+
# Fall back to issue updated_at if no comments
|
|
425
|
+
if [ -z "$LAST_ACTIVITY" ]; then
|
|
426
|
+
LAST_ACTIVITY=$(gh issue view $NUM -R {GH_REPO} --json updatedAt --jq '.updatedAt')
|
|
427
|
+
fi
|
|
428
|
+
|
|
429
|
+
# Compute elapsed minutes (GNU date — adjust for macOS: date -j -f "%Y-%m-%dT%H:%M:%SZ")
|
|
430
|
+
LAST_EPOCH=$(date -d "$LAST_ACTIVITY" +%s 2>/dev/null \
|
|
431
|
+
|| date -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_ACTIVITY" +%s 2>/dev/null)
|
|
432
|
+
NOW_EPOCH=$(date +%s)
|
|
433
|
+
ELAPSED_MIN=$(( (NOW_EPOCH - LAST_EPOCH) / 60 ))
|
|
434
|
+
|
|
435
|
+
if [ "$ELAPSED_MIN" -gt "$STALL_TIMEOUT" ]; then
|
|
436
|
+
# Count prior stall events on this issue
|
|
437
|
+
STALL_COUNT=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
438
|
+
--jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length')
|
|
439
|
+
|
|
440
|
+
CURRENT_STATE=$(gh issue view $NUM -R {GH_REPO} --json labels \
|
|
441
|
+
--jq '[.labels[].name | select(startswith("workflow:"))] | .[0] // "unknown"')
|
|
442
|
+
|
|
443
|
+
if [ "$STALL_COUNT" -lt 2 ]; then
|
|
444
|
+
# Auto-resume: post stall annotation and re-invoke /work-on
|
|
445
|
+
RESUME_ATTEMPT=$(( STALL_COUNT + 1 ))
|
|
446
|
+
gh issue comment $NUM -R {GH_REPO} --body "<!-- FORGE:STALL_DETECTED -->
|
|
447
|
+
## Stall Detected
|
|
448
|
+
|
|
449
|
+
**Issue**: #${NUM}
|
|
450
|
+
**Elapsed since last activity**: ${ELAPSED_MIN} min (threshold: ${STALL_TIMEOUT} min)
|
|
451
|
+
**Current workflow state**: ${CURRENT_STATE}
|
|
452
|
+
**Auto-resume attempt**: ${RESUME_ATTEMPT} of 2
|
|
453
|
+
**Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
454
|
+
|
|
455
|
+
# Resume the agent — collect all resumes and launch in a single message (see Step 4B rule)
|
|
456
|
+
# STALL_RESUME_LIST is accumulated and launched in parallel after the loop
|
|
457
|
+
STALL_RESUME_LIST="$STALL_RESUME_LIST $NUM"
|
|
458
|
+
else
|
|
459
|
+
# 2+ prior stalls — auto-resume exhausted, escalate to needs-human
|
|
460
|
+
gh issue edit $NUM -R {GH_REPO} --add-label "needs-human"
|
|
461
|
+
gh issue comment $NUM -R {GH_REPO} --body "<!-- FORGE:STALL_DETECTED -->
|
|
462
|
+
## Stall Escalated — Needs Human Intervention
|
|
463
|
+
|
|
464
|
+
Issue #${NUM} has been auto-resumed ${STALL_COUNT} times without reaching a terminal state. Auto-resume limit (2) exhausted. Manual intervention required.
|
|
465
|
+
|
|
466
|
+
**Last workflow state**: ${CURRENT_STATE}
|
|
467
|
+
**Total elapsed since last activity**: ${ELAPSED_MIN} min
|
|
468
|
+
**Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
469
|
+
echo "STALL ESCALATED: #{NUM} → needs-human (${STALL_COUNT} prior resumes)"
|
|
470
|
+
fi
|
|
471
|
+
fi
|
|
472
|
+
done
|
|
473
|
+
|
|
474
|
+
# Launch all stall resumes in parallel (single message — same rule as Step 4B)
|
|
475
|
+
# For each NUM in $STALL_RESUME_LIST, call Agent(resume=AGENT_ISSUE_MAP[NUM], run_in_background=true, ...)
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Resume all stalled agents in a single message** (parallel). Use the same `Agent(resume=...)` pattern as Step 4B — do not wait between individual resumes.
|
|
479
|
+
|
|
480
|
+
**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.
|
|
481
|
+
|
|
482
|
+
### Step 4C: Collect review-finding issues from completed agents
|
|
483
|
+
|
|
484
|
+
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.
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
# Method 1: Read TRAJECTORY comments from completed issues for "Finding issues" row
|
|
488
|
+
for NUM in {completed_issue_number}; do
|
|
489
|
+
gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
490
|
+
--jq '.[] | select(.body | contains("FORGE:TRAJECTORY")) | .body' 2>/dev/null \
|
|
491
|
+
| grep -oP 'Finding issues\s*\|\s*#?\K\d+[^|]*' | grep -oP '\d+' | sort -u
|
|
492
|
+
done
|
|
493
|
+
|
|
494
|
+
# Method 2 (fallback): Check for recently created review-finding issues that reference PRs from this batch
|
|
495
|
+
gh issue list -R {GH_REPO} --state open --label "review-finding" --limit 20 \
|
|
496
|
+
--json number,title,body,createdAt \
|
|
497
|
+
--jq "[.[] | select(.createdAt > \"$(date -u -d '2 hours ago' +%Y-%m-%dT%H:%M:%SZ)\")]"
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**If review-finding issues were spawned:**
|
|
501
|
+
|
|
502
|
+
**Cascade control (MANDATORY — run before folding findings into the DAG):**
|
|
503
|
+
|
|
504
|
+
For each spawned finding, determine whether it should be **executed** or **deferred**:
|
|
505
|
+
|
|
506
|
+
**Evaluation order** (first matching rule wins):
|
|
507
|
+
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.
|
|
508
|
+
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.
|
|
509
|
+
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.
|
|
510
|
+
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.
|
|
511
|
+
|
|
512
|
+
**Defer** (do NOT add to the DAG) if rules 1, 3, or 4 match.
|
|
513
|
+
|
|
514
|
+
**Execute** (add to the DAG) if:
|
|
515
|
+
- Rule 2 matches (P1 or P2)
|
|
516
|
+
- None of the defer rules matched (generation 1, P3 with no file overlap, not a keyword match)
|
|
517
|
+
|
|
518
|
+
**Before running the loop, build the batch file list (MANDATORY for Heuristic 3):**
|
|
519
|
+
|
|
520
|
+
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.
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
# Build ALL_BATCH_FILES: collect file paths from ALL batch issues (completed + queued)
|
|
524
|
+
# Use the same extraction pattern as Step 3C Layer 1
|
|
525
|
+
ALL_BATCH_FILES=""
|
|
526
|
+
for NUM in {all_batch_issue_numbers}; do
|
|
527
|
+
# Try INVESTIGATOR comment first (most reliable source of affected files)
|
|
528
|
+
FILES=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
529
|
+
--jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body' 2>/dev/null \
|
|
530
|
+
| grep -oP '`[^`]*\.(py|tsx?|jsx?|sql|json|ya?ml|sh|md)`' | tr -d '`' | sort -u)
|
|
531
|
+
# Fall back to issue body if no investigator comment
|
|
532
|
+
if [ -z "$FILES" ]; then
|
|
533
|
+
FILES=$(gh issue view $NUM -R {GH_REPO} --json body --jq '.body' \
|
|
534
|
+
| grep -oP '`[^`]*\.(py|tsx?|jsx?|sql|json|ya?ml|sh|md)`' | tr -d '`' | sort -u)
|
|
535
|
+
fi
|
|
536
|
+
ALL_BATCH_FILES=$(printf '%s\n%s' "$ALL_BATCH_FILES" "$FILES")
|
|
537
|
+
done
|
|
538
|
+
ALL_BATCH_FILES=$(echo "$ALL_BATCH_FILES" | sort -u | grep -v '^$')
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
# For each finding, check its priority label and generation
|
|
543
|
+
# NOTE: DEFERRED_FINDINGS, QUEUED_FINDINGS, and DEFERRED_REASONS are declared at
|
|
544
|
+
# batch scope in Step 4A.pre — do NOT re-initialize them here (Step 4C runs per-agent).
|
|
545
|
+
for FINDING_NUM in {spawned_finding_numbers}; do
|
|
546
|
+
FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body \
|
|
547
|
+
--jq '{labels: [.labels[].name], title: .title, body: .body}')
|
|
548
|
+
|
|
549
|
+
PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("priority:P")) | ltrimstr("priority:")' | head -1)
|
|
550
|
+
TITLE=$(echo "$FINDING_DATA" | jq -r '.title')
|
|
551
|
+
|
|
552
|
+
# Heuristic 1: Generation check — source issue has review-finding label (always defer, even for P1/P2)
|
|
553
|
+
if SOURCE_NUM=$(echo "$FINDING_DATA" | jq -r '.body' | grep -oP '(?i)spawned from issue #\K\d+|source issue[: #]+\K\d+' | head -1) && \
|
|
554
|
+
[ -n "$SOURCE_NUM" ] && \
|
|
555
|
+
gh issue view $SOURCE_NUM -R {GH_REPO} --json labels --jq '[.labels[].name]' 2>/dev/null | grep -q "review-finding"; then
|
|
556
|
+
DEFER=true; DEFER_REASON="generation >= 2 (source #${SOURCE_NUM} is also a review-finding)"
|
|
557
|
+
# Priority override: P1 or P2 always execute — skip remaining heuristics
|
|
558
|
+
elif [ "$PRIORITY" = "P1" ] || [ "$PRIORITY" = "P2" ]; then
|
|
559
|
+
DEFER=false
|
|
560
|
+
# Heuristic 2: Comment/typo keyword (only applies to P3 and below)
|
|
561
|
+
elif echo "$TITLE" | grep -qi "comment\|typo"; then
|
|
562
|
+
DEFER=true; DEFER_REASON="comment/typo heuristic"
|
|
563
|
+
# Heuristic 3: P3 + same-file overlap
|
|
564
|
+
elif [ "$PRIORITY" = "P3" ]; then
|
|
565
|
+
# Extract file target from finding body (look for code block or backtick path)
|
|
566
|
+
FINDING_FILE=$(echo "$FINDING_DATA" | jq -r '.body' | grep -oP '`[^\`]+\.(py|ts|tsx|sh|md)`' | head -1 | tr -d '`')
|
|
567
|
+
if [ -n "$FINDING_FILE" ] && echo "$ALL_BATCH_FILES" | grep -qF "$FINDING_FILE"; then
|
|
568
|
+
DEFER=true; DEFER_REASON="P3 + same file as batch: $FINDING_FILE"
|
|
569
|
+
else
|
|
570
|
+
DEFER=false
|
|
571
|
+
fi
|
|
572
|
+
else
|
|
573
|
+
DEFER=false
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
if [ "$DEFER" = "true" ]; then
|
|
577
|
+
DEFERRED_FINDINGS+=($FINDING_NUM)
|
|
578
|
+
DEFERRED_REASONS[$FINDING_NUM]="$DEFER_REASON"
|
|
579
|
+
echo "Deferred #${FINDING_NUM}: $DEFER_REASON"
|
|
580
|
+
else
|
|
581
|
+
QUEUED_FINDINGS+=($FINDING_NUM)
|
|
582
|
+
fi
|
|
583
|
+
done
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**For queued (non-deferred) findings:**
|
|
587
|
+
|
|
588
|
+
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.
|
|
589
|
+
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.
|
|
590
|
+
3. **Report to user:**
|
|
591
|
+
```
|
|
592
|
+
Agent #{COMPLETED} spawned {count} new finding issues: #{A}, #{B}
|
|
593
|
+
Added to DAG: #{A} (predecessors: {}), #{B} (predecessors: {#{X}})
|
|
594
|
+
Deferred (cascade control): #{C} (P3 same-file), #{D} (comment heuristic)
|
|
595
|
+
```
|
|
596
|
+
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.
|
|
597
|
+
|
|
598
|
+
**For deferred findings:**
|
|
599
|
+
|
|
600
|
+
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.
|
|
601
|
+
|
|
602
|
+
**If no review-finding issues were spawned:** Continue monitoring for the next agent completion.
|
|
603
|
+
|
|
604
|
+
### Step 4C.5: Milestone lane-consistency check (periodic) <!-- Added: forge#901 -->
|
|
605
|
+
|
|
606
|
+
**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.
|
|
607
|
+
|
|
608
|
+
**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.
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
# For each distinct milestone in the batch, assert all of its feature-lane PRs share one base.
|
|
612
|
+
for NUM in {all_batch_issue_numbers}; do
|
|
613
|
+
MILESTONE_TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null || echo "")
|
|
614
|
+
[ -z "$MILESTONE_TITLE" ] && continue
|
|
615
|
+
|
|
616
|
+
SLUG=$(echo "$MILESTONE_TITLE" \
|
|
617
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
618
|
+
| tr ' ' '-' \
|
|
619
|
+
| tr -cd 'a-z0-9-' \
|
|
620
|
+
| sed 's/--*/-/g' \
|
|
621
|
+
| sed 's/^-//;s/-$//')
|
|
622
|
+
[ -z "$SLUG" ] && continue
|
|
623
|
+
EXPECTED_BASE="milestone/$SLUG"
|
|
624
|
+
|
|
625
|
+
# Collect the base branch of every PR that closes an issue in this milestone.
|
|
626
|
+
# Iterate the milestone's issues and read each one's linked PR base.
|
|
627
|
+
# Exclude CLOSED-unmerged PRs: a closed-but-not-merged PR is a superseded/abandoned
|
|
628
|
+
# routing attempt and does NOT reflect the live lane. Keep only OPEN (in-flight) and
|
|
629
|
+
# MERGED (landed) PRs. `gh pr list --state` cannot combine open+merged, so query all
|
|
630
|
+
# and drop CLOSED in jq.
|
|
631
|
+
BASES=$(gh pr list -R {GH_REPO} --state all --search "milestone:\"$MILESTONE_TITLE\"" \
|
|
632
|
+
--json baseRefName,state --jq '.[] | select(.state != "CLOSED") | .baseRefName' 2>/dev/null | sort -u)
|
|
633
|
+
# Fallback: if PR search by milestone is unavailable, derive from the issues' linked PRs.
|
|
634
|
+
if [ -z "$BASES" ]; then
|
|
635
|
+
BASES=$(for IN in {all_batch_issue_numbers}; do
|
|
636
|
+
IM=$(gh issue view "$IN" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null)
|
|
637
|
+
[ "$IM" = "$MILESTONE_TITLE" ] || continue
|
|
638
|
+
gh pr list -R {GH_REPO} --state all --search "$IN in:body" \
|
|
639
|
+
--json baseRefName,state --jq '.[] | select(.state != "CLOSED") | .baseRefName' 2>/dev/null
|
|
640
|
+
done | sort -u)
|
|
641
|
+
fi
|
|
642
|
+
|
|
643
|
+
STRAY_BASES=$(echo "$BASES" | grep -v "^${EXPECTED_BASE}\$" | grep -v '^$' || true)
|
|
644
|
+
if [ -n "$STRAY_BASES" ]; then
|
|
645
|
+
echo "ALERT: milestone '$MILESTONE_TITLE' has feature-lane PRs split across multiple base branches." >&2
|
|
646
|
+
echo " Expected base: $EXPECTED_BASE" >&2
|
|
647
|
+
echo " Found bases:" >&2
|
|
648
|
+
echo "$BASES" | sed 's/^/ - /' >&2
|
|
649
|
+
echo " This indicates a branch-routing split — reconcile the stray PRs onto $EXPECTED_BASE" >&2
|
|
650
|
+
echo " (rebase/cherry-pick the stray branch onto the milestone branch) before the milestone ships." >&2
|
|
651
|
+
# Do NOT auto-stop or auto-resolve — surface the alert and let the user decide.
|
|
652
|
+
else
|
|
653
|
+
echo "Lane-consistency OK: all '$MILESTONE_TITLE' PRs target $EXPECTED_BASE."
|
|
654
|
+
fi
|
|
655
|
+
done
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
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.
|
|
659
|
+
|
|
660
|
+
### Step 4D: Milestone integration build gate (MANDATORY — periodic for milestone batches)
|
|
661
|
+
|
|
662
|
+
**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.
|
|
663
|
+
|
|
664
|
+
**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.
|
|
665
|
+
|
|
666
|
+
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.
|
|
667
|
+
|
|
668
|
+
```bash
|
|
669
|
+
# Read toolchain commands from forge.yaml
|
|
670
|
+
TS_TYPECHECK=$(yq '.verification.commands.typescript.typecheck // ""' forge.yaml 2>/dev/null || echo '')
|
|
671
|
+
TS_BUILD=$(yq '.verification.commands.typescript.build // ""' forge.yaml 2>/dev/null || echo '')
|
|
672
|
+
PYTHON_FORMAT=$(yq '.verification.commands.python.format // ""' forge.yaml 2>/dev/null || echo '')
|
|
673
|
+
|
|
674
|
+
# Check if this is a milestone batch with TypeScript changes
|
|
675
|
+
MILESTONE_BRANCH="milestone/{milestone_slug}"
|
|
676
|
+
TS_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.(tsx?|jsx?)$' | head -1)
|
|
677
|
+
|
|
678
|
+
if [ -n "$TS_CHANGED" ]; then
|
|
679
|
+
echo "=== Integration Build Gate (TypeScript): batch checkpoint ==="
|
|
680
|
+
cd {REPO_PATH}
|
|
681
|
+
git fetch origin ${MILESTONE_BRANCH}
|
|
682
|
+
git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
|
|
683
|
+
|
|
684
|
+
if [ -n "$TS_TYPECHECK" ]; then
|
|
685
|
+
eval "$TS_TYPECHECK" 2>&1 | head -30
|
|
686
|
+
TSC_EXIT=$?
|
|
687
|
+
else
|
|
688
|
+
echo "SKIPPED — typescript.typecheck not configured in verification.commands"
|
|
689
|
+
TSC_EXIT=0
|
|
690
|
+
fi
|
|
691
|
+
|
|
692
|
+
if [ "$TSC_EXIT" -eq 0 ] && [ -n "$TS_BUILD" ]; then
|
|
693
|
+
eval "$TS_BUILD" 2>&1 | tail -30
|
|
694
|
+
BUILD_EXIT=$?
|
|
695
|
+
elif [ -z "$TS_BUILD" ]; then
|
|
696
|
+
echo "SKIPPED — typescript.build not configured in verification.commands"
|
|
697
|
+
BUILD_EXIT=0
|
|
698
|
+
fi
|
|
699
|
+
|
|
700
|
+
git checkout - 2>/dev/null
|
|
701
|
+
|
|
702
|
+
if [ "$TSC_EXIT" -ne 0 ]; then
|
|
703
|
+
echo "BLOCKING: TypeScript errors on ${MILESTONE_BRANCH} after batch checkpoint."
|
|
704
|
+
echo "Fix type errors before dispatching more milestone agents."
|
|
705
|
+
elif [ "${BUILD_EXIT:-0}" -ne 0 ]; then
|
|
706
|
+
echo "BLOCKING: build failed on ${MILESTONE_BRANCH} after batch checkpoint."
|
|
707
|
+
echo "Build/prerender errors — fix before dispatching more milestone agents."
|
|
708
|
+
fi
|
|
709
|
+
fi
|
|
710
|
+
|
|
711
|
+
# Python format check
|
|
712
|
+
PY_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.py$' | head -1)
|
|
713
|
+
if [ -n "$PY_CHANGED" ]; then
|
|
714
|
+
echo "=== Integration Build Gate (Python): batch checkpoint ==="
|
|
715
|
+
cd {REPO_PATH}
|
|
716
|
+
git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
|
|
717
|
+
|
|
718
|
+
if [ -n "$PYTHON_FORMAT" ]; then
|
|
719
|
+
eval "$PYTHON_FORMAT" 2>&1 | tail -10
|
|
720
|
+
FORMAT_EXIT=$?
|
|
721
|
+
else
|
|
722
|
+
echo "SKIPPED — python.format not configured in verification.commands"
|
|
723
|
+
FORMAT_EXIT=0
|
|
724
|
+
fi
|
|
725
|
+
|
|
726
|
+
git checkout - 2>/dev/null
|
|
727
|
+
|
|
728
|
+
if [ "$FORMAT_EXIT" -ne 0 ]; then
|
|
729
|
+
echo "WARNING: Python formatting issues on ${MILESTONE_BRANCH} after batch checkpoint."
|
|
730
|
+
echo "Not blocking but should be fixed before milestone→staging."
|
|
731
|
+
fi
|
|
732
|
+
fi
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
**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.
|
|
736
|
+
|
|
737
|
+
### Step 4E: Handle individual agent failures
|
|
738
|
+
|
|
739
|
+
If an agent reports failure or error:
|
|
740
|
+
- **Merge conflict**: Report to user, mark issue as needing human attention
|
|
741
|
+
- **Invalid issue**: Already handled by the agent (closed with comment) — just report it
|
|
742
|
+
- **Build/test failure**: Report the error, suggest manual intervention
|
|
743
|
+
- **Agent timeout**: Report which issue timed out, suggest re-running with `/work-on #{N}`
|
|
744
|
+
- **Dependency cascade**: Mark all transitive dependents in the DAG as "skipped — dependency #{X} failed"
|
|
745
|
+
|
|
746
|
+
**Do NOT retry failed agents automatically.** Report the failure and let the user decide.
|
|
747
|
+
|
|
748
|
+
### Step 4F: Completion Sweep (deferred review-spawned findings) <!-- Added: forge#1105 -->
|
|
749
|
+
|
|
750
|
+
**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.
|
|
751
|
+
|
|
752
|
+
**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.
|
|
753
|
+
|
|
754
|
+
**Step 4F.1: Classify deferred findings into permanent vs re-evaluable**
|
|
755
|
+
|
|
756
|
+
```bash
|
|
757
|
+
PERMANENT_DEFERRED=()
|
|
758
|
+
SWEEP_CANDIDATES=()
|
|
759
|
+
|
|
760
|
+
for FINDING_NUM in "${DEFERRED_FINDINGS[@]}"; do
|
|
761
|
+
DEFER_REASON="${DEFERRED_REASONS[$FINDING_NUM]}"
|
|
762
|
+
|
|
763
|
+
# Generation >= 2 deferrals are PERMANENT — unbounded cascade prevention
|
|
764
|
+
if echo "$DEFER_REASON" | grep -qi "generation"; then
|
|
765
|
+
PERMANENT_DEFERRED+=($FINDING_NUM)
|
|
766
|
+
else
|
|
767
|
+
# All other deferrals (comment/typo, P3 same-file) are re-evaluable
|
|
768
|
+
SWEEP_CANDIDATES+=($FINDING_NUM)
|
|
769
|
+
fi
|
|
770
|
+
done
|
|
771
|
+
|
|
772
|
+
echo "Completion sweep: ${#SWEEP_CANDIDATES[@]} re-evaluable, ${#PERMANENT_DEFERRED[@]} permanent"
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Step 4F.2: Re-evaluate sweep candidates**
|
|
776
|
+
|
|
777
|
+
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.
|
|
778
|
+
|
|
779
|
+
```bash
|
|
780
|
+
SWEEP_EXECUTE=()
|
|
781
|
+
SWEEP_STILL_DEFERRED=()
|
|
782
|
+
|
|
783
|
+
for FINDING_NUM in "${SWEEP_CANDIDATES[@]}"; do
|
|
784
|
+
FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body,state \
|
|
785
|
+
--jq '{labels: [.labels[].name], title: .title, body: .body, state: .state}')
|
|
786
|
+
|
|
787
|
+
# Skip if already closed (resolved by another process)
|
|
788
|
+
STATE=$(echo "$FINDING_DATA" | jq -r '.state')
|
|
789
|
+
[ "$STATE" = "CLOSED" ] && continue
|
|
790
|
+
|
|
791
|
+
PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("priority:P")) | ltrimstr("priority:")' | head -1)
|
|
792
|
+
TITLE=$(echo "$FINDING_DATA" | jq -r '.title')
|
|
793
|
+
|
|
794
|
+
# Re-apply heuristics against the drained DAG (no active batch files)
|
|
795
|
+
# Comment/typo heuristic still applies — these are cosmetic regardless of DAG state
|
|
796
|
+
if echo "$TITLE" | grep -qi "comment\|typo"; then
|
|
797
|
+
SWEEP_STILL_DEFERRED+=($FINDING_NUM)
|
|
798
|
+
echo "Sweep: #${FINDING_NUM} still deferred (comment/typo — cosmetic)"
|
|
799
|
+
else
|
|
800
|
+
# P3 same-file overlap no longer applies (DAG is drained, no active files)
|
|
801
|
+
# All other findings are safe to execute
|
|
802
|
+
SWEEP_EXECUTE+=($FINDING_NUM)
|
|
803
|
+
echo "Sweep: #${FINDING_NUM} cleared for execution (file overlap resolved)"
|
|
804
|
+
fi
|
|
805
|
+
done
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
**Step 4F.3: Dispatch cleared findings**
|
|
809
|
+
|
|
810
|
+
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).
|
|
811
|
+
|
|
812
|
+
**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.
|
|
813
|
+
|
|
814
|
+
**Step 4F.3.pre: Classify lane for each sweep finding (MANDATORY before dispatching)**
|
|
815
|
+
|
|
816
|
+
Run `classify-lane.sh` per finding — same pattern as Step 4A.pre:
|
|
817
|
+
|
|
818
|
+
```bash
|
|
819
|
+
declare -A SWEEP_LANE
|
|
820
|
+
declare -A SWEEP_PR_BASE
|
|
821
|
+
|
|
822
|
+
for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
|
|
823
|
+
SWEEP_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$FINDING_NUM" -R {GH_REPO}) || {
|
|
824
|
+
echo "ERROR: classify-lane.sh failed for #$FINDING_NUM — adding needs-human and skipping" >&2
|
|
825
|
+
gh issue edit "$FINDING_NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
826
|
+
continue
|
|
827
|
+
}
|
|
828
|
+
if [ "$SWEEP_BASE" = "staging" ]; then
|
|
829
|
+
SWEEP_LANE[$FINDING_NUM]="fast-lane"
|
|
830
|
+
else
|
|
831
|
+
SWEEP_LANE[$FINDING_NUM]="feature-lane"
|
|
832
|
+
fi
|
|
833
|
+
SWEEP_PR_BASE[$FINDING_NUM]="$SWEEP_BASE"
|
|
834
|
+
echo "#$FINDING_NUM → lane=${SWEEP_LANE[$FINDING_NUM]}, PR_BASE=$SWEEP_BASE"
|
|
835
|
+
done
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
**Step 4F.3.dispatch: Dispatch with full Step 4A template**
|
|
839
|
+
|
|
840
|
+
```bash
|
|
841
|
+
if [ ${#SWEEP_EXECUTE[@]} -gt 0 ]; then
|
|
842
|
+
echo "Completion sweep: dispatching ${#SWEEP_EXECUTE[@]} cleared findings"
|
|
843
|
+
|
|
844
|
+
# Build sweep DAG — same conflict detection as Step 3C Layers 1-4
|
|
845
|
+
# but only among the swept findings (no original batch issues remain active)
|
|
846
|
+
# Dispatch ready findings, monitor completions using the same Step 4B loop
|
|
847
|
+
# This is a SINGLE pass — findings spawned during the sweep are NOT swept again
|
|
848
|
+
# (they follow the standard Step 4C triage: queued or deferred for next run)
|
|
849
|
+
|
|
850
|
+
# NOTE: Step 4A.pre.0 (milestone branch pre-creation) is intentionally skipped here.
|
|
851
|
+
# Swept findings are always review-finding issues that target an already-existing branch
|
|
852
|
+
# (staging or a milestone branch). The branch was created when the original batch ran —
|
|
853
|
+
# it always exists by the time the sweep runs.
|
|
854
|
+
|
|
855
|
+
for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
|
|
856
|
+
# Skip any finding whose classify-lane call failed (needs-human already set above)
|
|
857
|
+
[ -z "${SWEEP_PR_BASE[$FINDING_NUM]:-}" ] && continue
|
|
858
|
+
|
|
859
|
+
FINDING_TITLE=$(gh issue view "$FINDING_NUM" -R {GH_REPO} --json title --jq '.title' 2>/dev/null || echo "")
|
|
860
|
+
|
|
861
|
+
# Build GIST_CONTEXT for sweep finding — same as Step 4A's *fallback* (raw-gist) path.
|
|
862
|
+
# The Phase 2.5 FORGE:SYNTHESIS_BRIEF preference is intentionally NOT applied here:
|
|
863
|
+
# sweep findings are freshly-created review-finding issues that never received a
|
|
864
|
+
# synthesis brief (Phase 2.5 runs only over the original batch's investigations), so
|
|
865
|
+
# there is nothing to prefer. Keep this block in sync with 4A's fallback branch only.
|
|
866
|
+
GIST_CONTEXT=""
|
|
867
|
+
PARENT_INV=$(gh issue view "$FINDING_NUM" -R {GH_REPO} --json body --jq '.body' \
|
|
868
|
+
| grep -oP '(?i)parent[: ]*#\K\d+|spawned from[: ]*#\K\d+' | head -1)
|
|
869
|
+
|
|
870
|
+
if [ -n "$PARENT_INV" ] && [ -n "${INVESTIGATION_GISTS[$PARENT_INV]:-}" ]; then
|
|
871
|
+
GIST_CONTEXT="
|
|
872
|
+
**CONTEXT FROM PRIOR INVESTIGATION**: Investigation #${PARENT_INV} produced Knowledge Gist(s) with findings relevant to this issue:
|
|
873
|
+
$(echo "${INVESTIGATION_GISTS[$PARENT_INV]}" | while IFS= read -r url; do echo "- ${url}"; done)
|
|
874
|
+
Fetch the Gist content during the context-gathering phase for implementation guidance."
|
|
875
|
+
fi
|
|
876
|
+
|
|
877
|
+
if [ -n "$MILESTONE_INDEX_URL" ]; then
|
|
878
|
+
GIST_CONTEXT="${GIST_CONTEXT}
|
|
879
|
+
|
|
880
|
+
**MILESTONE KNOWLEDGE INDEX**: All investigation findings for this milestone are aggregated in a single index Gist:
|
|
881
|
+
- ${MILESTONE_INDEX_URL}
|
|
882
|
+
The context-gathering phase can fetch this index to discover all investigation Gists for the milestone."
|
|
883
|
+
fi
|
|
884
|
+
|
|
885
|
+
# Use the full Step 4A template verbatim — copied here so sweep agents receive
|
|
886
|
+
# the complete pipeline contract. Keep in sync with Step 4A when the template changes.
|
|
887
|
+
Agent(
|
|
888
|
+
subagent_type="general-purpose",
|
|
889
|
+
model="sonnet",
|
|
890
|
+
description="Work on {PROJECT_PREFIX}#${FINDING_NUM}",
|
|
891
|
+
run_in_background=true,
|
|
892
|
+
prompt="You are working on GitHub issue #${FINDING_NUM} for the {PROJECT_NAME} project.
|
|
893
|
+
|
|
894
|
+
**Project**: {PROJECT_NAME}
|
|
895
|
+
**Repository**: {GH_REPO}
|
|
896
|
+
**Repo path**: {REPO_PATH}
|
|
897
|
+
|
|
898
|
+
**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.
|
|
899
|
+
|
|
900
|
+
**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.
|
|
901
|
+
|
|
902
|
+
**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.
|
|
903
|
+
|
|
904
|
+
**IMPORTANT RULES**:
|
|
905
|
+
- **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.
|
|
906
|
+
- For default repo issues: \`Skill(skill='work-on', args='${FINDING_NUM}')\`
|
|
907
|
+
- For satellite repo issues: \`Skill(skill='work-on', args='{SATELLITE_PREFIX}:${FINDING_NUM}')\` (prefix from forge.yaml → repos.satellites)
|
|
908
|
+
- NEVER bypass /work-on with manual git/gh commands — the label updates and structured comments are critical for tracking
|
|
909
|
+
- NEVER target \`main\` for PRs targeting the default repo. Use \`{STAGING_BRANCH}\` for fast-lane issues, or \`milestone/{slug}\` for milestone issues.
|
|
910
|
+
- Satellite repos (MCP, n8n) have no staging branch — fast-lane PRs go to \`main\` for those.
|
|
911
|
+
- If the issue is INVALID after investigation, close it with a comment explaining why
|
|
912
|
+
- If you hit merge conflicts or blockers, post a comment on the issue and STOP — do not force anything
|
|
913
|
+
- Do not interact with the user — you are running autonomously in the background
|
|
914
|
+
- **NEVER ask the user questions** — you are a background agent. If review finds issues, auto-fix simple ones and proceed. For complex findings on **low-risk domains**, merge anyway and create follow-up issues. For complex findings on **high-risk domains** (AUTH, BILLING, DATABASE, or any domain tagged as security-critical in Step 3B), add the `needs-human` label and stop — do NOT merge. High-risk `needs-human` halts are surfaced as `⚠ Blocked` in the Phase 5 completion report.
|
|
915
|
+
|
|
916
|
+
**LABEL-STATE LOOP CONTRACT — enforce after EVERY Skill return**:
|
|
917
|
+
After EVERY \`Skill(skill='work-on', ...)\` call returns, immediately check the issue's current workflow label:
|
|
918
|
+
\`\`\`bash
|
|
919
|
+
gh issue view ${FINDING_NUM} -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith(\"workflow:\"))]'
|
|
920
|
+
\`\`\`
|
|
921
|
+
**Terminal labels** (only these allow you to stop): \`workflow:merged\`, \`workflow:invalid\`
|
|
922
|
+
**Terminal condition also**: \`needs-human\` label present OR issue state is \`closed\`
|
|
923
|
+
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.
|
|
924
|
+
|
|
925
|
+
**CRITICAL — SOURCE BRANCH DETECTION**:
|
|
926
|
+
- If the issue has the \`review-finding\` label, read the issue body for \`**Code branch**: \\\`{branch}\\\`\`
|
|
927
|
+
- If found, that is the SOURCE_BRANCH — the code ONLY exists on that branch (e.g., \`staging\`), NOT on \`origin/main\`
|
|
928
|
+
- Investigation MUST use \`git show origin/{SOURCE_BRANCH}:{filepath}\` to verify the code exists
|
|
929
|
+
- Worktree MUST branch from \`origin/{SOURCE_BRANCH}\`, NOT \`origin/main\`
|
|
930
|
+
- PR target is \`{SOURCE_BRANCH}\` (the fix goes back to where the code lives)
|
|
931
|
+
|
|
932
|
+
**LANE**: ${SWEEP_LANE[$FINDING_NUM]} (PR target: ${SWEEP_PR_BASE[$FINDING_NUM]})
|
|
933
|
+
**Issue title**: ${FINDING_TITLE}
|
|
934
|
+
${GIST_CONTEXT}
|
|
935
|
+
"
|
|
936
|
+
)
|
|
937
|
+
done
|
|
938
|
+
|
|
939
|
+
# Monitor sweep agents using the same Step 4B completion loop
|
|
940
|
+
# IMPORTANT: Findings spawned by sweep agents are NOT re-swept —
|
|
941
|
+
# they follow standard Step 4C triage to prevent recursive cascades
|
|
942
|
+
fi
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
**Step 4F.4: Report sweep results**
|
|
946
|
+
|
|
947
|
+
```
|
|
948
|
+
Completion Sweep Results:
|
|
949
|
+
Dispatched: #{A}, #{B} (file overlap cleared after DAG drain)
|
|
950
|
+
Still deferred (cosmetic): #{C} (comment/typo)
|
|
951
|
+
Permanently deferred (gen2): #{D} (generation >= 2 cascade cap)
|
|
952
|
+
```
|
|
953
|
+
|
|
954
|
+
**After sweep agents complete** (or if no findings were dispatched): proceed to Phase 5.
|
|
955
|
+
|
|
956
|
+
**Anti-patterns — DO NOT DO THIS:**
|
|
957
|
+
- Re-sweeping findings spawned during the sweep itself — this creates unbounded recursion. Sweep is a single pass.
|
|
958
|
+
- Overriding generation >= 2 deferrals — the cascade cap is absolute.
|
|
959
|
+
- Skipping the sweep because "there are only a few" deferred findings — even one deferred finding represents unresolved work.
|
|
960
|
+
|
|
961
|
+
---
|
|
962
|
+
|