forgedock 1.0.19 → 1.0.21
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 +16 -1
- package/commands/ci-audit.md +469 -0
- package/commands/orchestrate.md +563 -156
- package/commands/quality-gate.md +79 -1
- package/commands/review-pr-agents.md +48 -0
- package/docs/comparison.md +135 -0
- package/docs/design/developer-objections-ai-context-tools.md +244 -0
- package/docs/design/messaging-angles-research.md +179 -0
- package/docs/design/show-hn-draft.md +86 -0
- package/package.json +2 -2
package/commands/orchestrate.md
CHANGED
|
@@ -13,9 +13,9 @@ argument-hint: [milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P0
|
|
|
13
13
|
|
|
14
14
|
1. **Every agent MUST invoke `/work-on` via the Skill tool.** You do NOT write implementation prompts. You copy the Phase 4A template verbatim and fill in the `{VARIABLES}`. Nothing else. No custom prompts. No "just read and edit" shortcuts. The Skill tool invocation is what triggers labels, investigation comments, structured review, and trajectory tracking. Without it, the agent's work has no paper trail and is worthless.
|
|
15
15
|
|
|
16
|
-
2. **You are a dispatcher, not a builder.** You resolve issues,
|
|
16
|
+
2. **You are a dispatcher, not a builder.** You resolve issues, build the dependency DAG, spawn agents, and report results. You NEVER read code, edit files, or implement fixes yourself.
|
|
17
17
|
|
|
18
|
-
3. **After each
|
|
18
|
+
3. **After each agent completes, verify it used `/work-on`.** Check that completed issues have `workflow:*` labels and structured comments. If an agent bypassed the pipeline, report it as a failure.
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
@@ -131,7 +131,7 @@ If a `workflow:decomposed` issue is found, automatically expand it to its open s
|
|
|
131
131
|
**What the orchestrator SHOULD do:**
|
|
132
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
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
|
|
134
|
+
- Flag potential overlap in the DAG plan for the user's awareness, but NEVER act on it
|
|
135
135
|
|
|
136
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
137
|
|
|
@@ -274,7 +274,7 @@ done
|
|
|
274
274
|
|
|
275
275
|
### Step 3B: Domain estimation
|
|
276
276
|
|
|
277
|
-
For each issue, estimate which domains it touches based on title, body, and labels. This improves
|
|
277
|
+
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).
|
|
278
278
|
|
|
279
279
|
```bash
|
|
280
280
|
for NUM in {issue_numbers}; do
|
|
@@ -291,11 +291,11 @@ for NUM in {issue_numbers}; do
|
|
|
291
291
|
done
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
**Use domain info for
|
|
295
|
-
- Issues in the SAME domain (especially WORKER, BILLING, DATABASE) are more likely to touch the same files →
|
|
294
|
+
**Use domain info for DAG edge construction:**
|
|
295
|
+
- Issues in the SAME domain (especially WORKER, BILLING, DATABASE) are more likely to touch the same files → add predecessor edges to serialize them
|
|
296
296
|
- Issues in DIFFERENT domains are more likely independent → safe to parallelize
|
|
297
297
|
- BILLING + AUTH issues should be prioritized early (security-critical)
|
|
298
|
-
- **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.
|
|
298
|
+
- **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.
|
|
299
299
|
|
|
300
300
|
**Store domain tags per issue** for use in the plan presentation (Step 3E).
|
|
301
301
|
|
|
@@ -323,9 +323,9 @@ gh issue view $NUM --json body --jq '.body' | grep -oP '`[^`]*\.(py|tsx?|jsx?|sq
|
|
|
323
323
|
```
|
|
324
324
|
|
|
325
325
|
**Cross-reference all extracted file lists:**
|
|
326
|
-
- If two issues share ANY affected file →
|
|
326
|
+
- If two issues share ANY affected file → one MUST be a predecessor of the other (serialized)
|
|
327
327
|
- The issue with lower issue number goes first (stable ordering), unless an explicit `Depends on #` says otherwise
|
|
328
|
-
- Add a conflict note to the
|
|
328
|
+
- Add a conflict note to the DAG plan: "#{A} and #{B} both modify `{file}` — #{A} is predecessor of #{B}"
|
|
329
329
|
|
|
330
330
|
#### Layer 2: Directory-proximity detection
|
|
331
331
|
|
|
@@ -395,10 +395,10 @@ When file extraction yields **fewer than 2 file paths** for an issue (common for
|
|
|
395
395
|
|
|
396
396
|
**Rules:**
|
|
397
397
|
- If an issue has 0-1 extracted file paths AND shares a domain tag (from Step 3B) with another issue → serialize them
|
|
398
|
-
- If an issue has 0 extracted file paths AND no domain tag could be determined → serialize it
|
|
398
|
+
- 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)
|
|
399
399
|
- Add a note to the plan: "#{N} — low file-extraction confidence, serialized conservatively"
|
|
400
400
|
|
|
401
|
-
**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 one
|
|
401
|
+
**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.
|
|
402
402
|
|
|
403
403
|
#### Combining all layers
|
|
404
404
|
|
|
@@ -413,29 +413,148 @@ Build the final conflict graph by merging signals from all four layers:
|
|
|
413
413
|
| Layer 3: High-fan-in file touched | **Probable conflict** | Serialize with same-service issues |
|
|
414
414
|
| Layer 3: Shared model/init pattern | **Probable conflict** | Serialize |
|
|
415
415
|
| Layer 4: Low confidence + same domain | **Conservative** | Serialize |
|
|
416
|
-
| Layer 4: Low confidence + no domain | **Conservative** |
|
|
416
|
+
| Layer 4: Low confidence + no domain | **Conservative** | Add predecessor edge to most recent issue |
|
|
417
417
|
|
|
418
418
|
**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).
|
|
419
419
|
|
|
420
|
-
### Step 3D: Build the
|
|
420
|
+
### Step 3D: Build the dependency DAG
|
|
421
421
|
|
|
422
|
-
|
|
422
|
+
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.
|
|
423
423
|
|
|
424
|
-
**
|
|
425
|
-
- Investigation issues already ran in
|
|
426
|
-
-
|
|
427
|
-
-
|
|
428
|
-
- **
|
|
429
|
-
-
|
|
430
|
-
-
|
|
424
|
+
**DAG construction rules:**
|
|
425
|
+
- Investigation issues already ran in Phase 2 — they are NOT included in this DAG
|
|
426
|
+
- Each issue starts with an empty predecessor set
|
|
427
|
+
- **Explicit dependencies**: If issue B says "Depends on #A" or "Blocked by #A", add A to B's predecessors
|
|
428
|
+
- **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.
|
|
429
|
+
- **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.
|
|
430
|
+
- **Conservative fallback edges**: Low-confidence issues (Layer 4) get edges to same-domain issues as per Step 3C rules.
|
|
431
|
+
- **No artificial concurrency limit** — all issues with empty predecessor sets dispatch simultaneously. The only constraints are file overlap and explicit dependencies.
|
|
431
432
|
|
|
432
|
-
**
|
|
433
|
+
**Terminology:**
|
|
434
|
+
- **Ready issues**: Issues whose predecessor set is empty (all predecessors have reached terminal state or were never added)
|
|
435
|
+
- **Blocked issues**: Issues with one or more unresolved predecessors
|
|
436
|
+
- **Critical path**: The longest chain of dependent issues in the DAG — determines minimum wall-clock time
|
|
437
|
+
|
|
438
|
+
**Example DAG:**
|
|
433
439
|
```
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
440
|
+
Phase 2 (already done): #2644 (investigation) → spawned #2645, #2646, #2647
|
|
441
|
+
|
|
442
|
+
Dependency graph:
|
|
443
|
+
#2633 (orphaned queues) → predecessors: {} ← READY
|
|
444
|
+
#2636 (invalidation sub) → predecessors: {} ← READY
|
|
445
|
+
#2645 (new finding) → predecessors: {} ← READY
|
|
446
|
+
#2646 (new finding) → predecessors: {} ← READY
|
|
447
|
+
#2634 (enable daemon) → predecessors: {#2633} ← blocked until #2633 completes
|
|
448
|
+
#2647 (depends on #2645) → predecessors: {#2645} ← blocked until #2645 completes
|
|
449
|
+
|
|
450
|
+
Critical path: #2633 → #2634 (2 steps) or #2645 → #2647 (2 steps)
|
|
451
|
+
Initial dispatch: #2633, #2636, #2645, #2646 (all ready — launched simultaneously)
|
|
437
452
|
```
|
|
438
453
|
|
|
454
|
+
**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.
|
|
455
|
+
|
|
456
|
+
### Step 3D.5: Cycle Detection (MANDATORY) <!-- Added: forge#1085 -->
|
|
457
|
+
|
|
458
|
+
**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.
|
|
459
|
+
|
|
460
|
+
**Algorithm**: Kahn's topological sort. Runs in O(V+E) — negligible overhead for typical batch sizes.
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# --- Step 3D.5: Cycle Detection ---
|
|
464
|
+
# Inputs:
|
|
465
|
+
# ISSUES[] — array of all issue numbers in the DAG
|
|
466
|
+
# PREDECESSORS[N] — space-separated list of predecessor issue numbers for issue N
|
|
467
|
+
|
|
468
|
+
# Step 1: Compute in-degree for each issue
|
|
469
|
+
declare -A IN_DEGREE
|
|
470
|
+
for NUM in "${ISSUES[@]}"; do
|
|
471
|
+
IN_DEGREE[$NUM]=0
|
|
472
|
+
done
|
|
473
|
+
for NUM in "${ISSUES[@]}"; do
|
|
474
|
+
for PRED in ${PREDECESSORS[$NUM]:-}; do
|
|
475
|
+
IN_DEGREE[$NUM]=$(( ${IN_DEGREE[$NUM]:-0} + 1 ))
|
|
476
|
+
done
|
|
477
|
+
done
|
|
478
|
+
|
|
479
|
+
# Step 2: Seed the queue with zero-in-degree issues (no predecessors)
|
|
480
|
+
KAHN_QUEUE=()
|
|
481
|
+
for NUM in "${ISSUES[@]}"; do
|
|
482
|
+
[ "${IN_DEGREE[$NUM]}" -eq 0 ] && KAHN_QUEUE+=("$NUM")
|
|
483
|
+
done
|
|
484
|
+
|
|
485
|
+
# Step 3: Process queue — reduce successor in-degrees, enqueue newly freed issues
|
|
486
|
+
PROCESSED_COUNT=0
|
|
487
|
+
PROCESSED_ORDER=()
|
|
488
|
+
while [ "${#KAHN_QUEUE[@]}" -gt 0 ]; do
|
|
489
|
+
# Dequeue
|
|
490
|
+
CURRENT="${KAHN_QUEUE[0]}"
|
|
491
|
+
KAHN_QUEUE=("${KAHN_QUEUE[@]:1}")
|
|
492
|
+
PROCESSED_ORDER+=("$CURRENT")
|
|
493
|
+
PROCESSED_COUNT=$(( PROCESSED_COUNT + 1 ))
|
|
494
|
+
|
|
495
|
+
# Reduce in-degree of all issues that depend on CURRENT (i.e., CURRENT is in their PREDECESSORS)
|
|
496
|
+
for SUCCESSOR in "${ISSUES[@]}"; do
|
|
497
|
+
for PRED in ${PREDECESSORS[$SUCCESSOR]:-}; do
|
|
498
|
+
if [ "$PRED" = "$CURRENT" ]; then
|
|
499
|
+
IN_DEGREE[$SUCCESSOR]=$(( ${IN_DEGREE[$SUCCESSOR]} - 1 ))
|
|
500
|
+
[ "${IN_DEGREE[$SUCCESSOR]}" -eq 0 ] && KAHN_QUEUE+=("$SUCCESSOR")
|
|
501
|
+
fi
|
|
502
|
+
done
|
|
503
|
+
done
|
|
504
|
+
done
|
|
505
|
+
|
|
506
|
+
# Step 4: Any issue not processed has in-degree > 0 — part of a cycle
|
|
507
|
+
CYCLE_ISSUES=()
|
|
508
|
+
EXCLUDED_CYCLE=()
|
|
509
|
+
for NUM in "${ISSUES[@]}"; do
|
|
510
|
+
FOUND=false
|
|
511
|
+
for P in "${PROCESSED_ORDER[@]}"; do [ "$P" = "$NUM" ] && FOUND=true && break; done
|
|
512
|
+
[ "$FOUND" = "false" ] && CYCLE_ISSUES+=("$NUM")
|
|
513
|
+
done
|
|
514
|
+
|
|
515
|
+
# Step 5: Handle cycles
|
|
516
|
+
if [ "${#CYCLE_ISSUES[@]}" -gt 0 ]; then
|
|
517
|
+
echo "CYCLE DETECTED in dependency graph — the following issues form a circular dependency:"
|
|
518
|
+
for C in "${CYCLE_ISSUES[@]}"; do
|
|
519
|
+
echo " #${C}: predecessors=[${PREDECESSORS[$C]}]"
|
|
520
|
+
# Label each cyclic issue needs-human
|
|
521
|
+
gh issue edit "$C" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
522
|
+
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
|
|
523
|
+
# Remove from DAG — store in EXCLUDED_CYCLE for Step 3E reporting
|
|
524
|
+
EXCLUDED_CYCLE+=("$C")
|
|
525
|
+
# Remove from ISSUES array for all downstream processing
|
|
526
|
+
# Use exact-match filter loop — pattern substitution (${array[@]/pattern}) leaves blank
|
|
527
|
+
# slots and corrupts partial matches (e.g., removing 100 changes 1000 to 0).
|
|
528
|
+
NEW_ISSUES=()
|
|
529
|
+
for I in "${ISSUES[@]}"; do
|
|
530
|
+
[ "$I" != "$C" ] && NEW_ISSUES+=("$I")
|
|
531
|
+
done
|
|
532
|
+
ISSUES=("${NEW_ISSUES[@]}")
|
|
533
|
+
done
|
|
534
|
+
echo ""
|
|
535
|
+
echo "These issues have been labeled needs-human and excluded from the DAG."
|
|
536
|
+
echo "Fix their dependency declarations and re-run /orchestrate."
|
|
537
|
+
else
|
|
538
|
+
echo "DAG cycle check: PASS — no cycles detected. Proceeding with ${#PROCESSED_ORDER[@]} issues."
|
|
539
|
+
fi
|
|
540
|
+
|
|
541
|
+
# Guard: if all issues were cyclic, ISSUES[] is now empty — abort before presenting an empty plan
|
|
542
|
+
if [ "${#ISSUES[@]}" -eq 0 ]; then
|
|
543
|
+
echo ""
|
|
544
|
+
echo "ERROR: All issues in this batch form circular dependencies and have been excluded."
|
|
545
|
+
echo "Every issue has been labeled needs-human."
|
|
546
|
+
echo "Fix the Depends on / Blocked by declarations so no cycle exists, then re-run /orchestrate."
|
|
547
|
+
exit 1
|
|
548
|
+
fi
|
|
549
|
+
# --- End Step 3D.5 ---
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**After this step**:
|
|
553
|
+
- `ISSUES[]` contains only acyclic issues — safe to dispatch
|
|
554
|
+
- `EXCLUDED_CYCLE[]` contains cyclic issue numbers — reported in Step 3E, never dispatched
|
|
555
|
+
- If `EXCLUDED_CYCLE` is non-empty, report it clearly in the Step 3E plan before asking for user confirmation
|
|
556
|
+
- 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 -->
|
|
557
|
+
|
|
439
558
|
### Step 3E: Present the plan to the user
|
|
440
559
|
|
|
441
560
|
```
|
|
@@ -443,103 +562,117 @@ Wave 2 (after deps): #2634 (enable daemon — after #2633 fixes memory leak), #2
|
|
|
443
562
|
|
|
444
563
|
**Scope**: {milestone name / "N issues" / "fast-lane"}
|
|
445
564
|
**Total issues**: {count} ({investigation_count} investigations + {implementation_count} implementations)
|
|
446
|
-
**
|
|
565
|
+
**Execution model**: Dependency-graph streaming (issues dispatch as predecessors complete)
|
|
447
566
|
|
|
448
567
|
{IF investigations exist:}
|
|
449
|
-
###
|
|
568
|
+
### Investigations (run first, may spawn new issues)
|
|
450
569
|
| # | Title | Expected Output |
|
|
451
570
|
|---|-------|----------------|
|
|
452
|
-
| #{INV1} | {title} | New issues → folded into
|
|
571
|
+
| #{INV1} | {title} | New issues → folded into dependency graph |
|
|
453
572
|
|
|
454
|
-
### Implementation
|
|
573
|
+
### Implementation (after investigations complete)
|
|
455
574
|
{END IF}
|
|
456
575
|
|
|
457
576
|
### Domain Distribution
|
|
458
577
|
| Domain | Issues | Notes |
|
|
459
578
|
|--------|--------|-------|
|
|
460
579
|
| FRONTEND | {N} | {Independent pages / Shared components} |
|
|
461
|
-
| BILLING | {N} | {Critical —
|
|
462
|
-
| DATABASE | {N} | {
|
|
463
|
-
| AUTH | {N} | {Critical —
|
|
580
|
+
| BILLING | {N} | {Critical — dispatches immediately} |
|
|
581
|
+
| DATABASE | {N} | {Serialized chain — migration order matters} |
|
|
582
|
+
| AUTH | {N} | {Critical — dispatches immediately} |
|
|
464
583
|
| WORKER | {N} | {High overlap risk within worker service} |
|
|
465
584
|
| AI | {N} | {Independent} |
|
|
466
585
|
| INFRA | {N} | {Independent} |
|
|
467
586
|
|
|
468
587
|
(Omit rows with 0 issues. Add project-specific domain rows from forge.yaml → review.domains.)
|
|
469
588
|
|
|
470
|
-
###
|
|
589
|
+
### Dependency Graph
|
|
471
590
|
|
|
472
|
-
|
|
|
473
|
-
|
|
474
|
-
|
|
|
475
|
-
|
|
|
476
|
-
|
|
|
591
|
+
| Issue | Predecessors | Domain | Status |
|
|
592
|
+
|-------|-------------|--------|--------|
|
|
593
|
+
| #{A} | — | FRONTEND | Ready (dispatches immediately) |
|
|
594
|
+
| #{B} | — | BILLING | Ready (dispatches immediately) |
|
|
595
|
+
| #{C} | — | WORKER | Ready (dispatches immediately) |
|
|
596
|
+
| #{D} | #{A} | FRONTEND | Blocked (waits for #{A} only) |
|
|
597
|
+
| #{E} | — | DATABASE | Ready (dispatches immediately) |
|
|
598
|
+
| #{F} | #{E} | DATABASE | Blocked (serialized — waits for #{E}) |
|
|
477
599
|
|
|
478
|
-
**
|
|
600
|
+
**Critical path**: #{E} → #{F} (2 steps, determines minimum wall-clock time)
|
|
601
|
+
**Initial dispatch**: #{A}, #{B}, #{C}, #{E} (all predecessors resolved)
|
|
602
|
+
**Streaming**: #{D} dispatches as soon as #{A} completes — does NOT wait for #{B}, #{C}, or #{E}
|
|
603
|
+
|
|
604
|
+
**Note**: Investigations may create additional issues that will be automatically added to the dependency graph. The final graph will be confirmed after investigations complete.
|
|
479
605
|
|
|
480
606
|
**Excluded** (already in progress / ineligible):
|
|
481
607
|
- #{X} — {reason}
|
|
482
608
|
|
|
609
|
+
{IF EXCLUDED_CYCLE is non-empty:}
|
|
610
|
+
### ⚠ Circular Dependencies Detected — Manual Fix Required
|
|
611
|
+
|
|
612
|
+
The following issues form a circular dependency chain and **cannot be dispatched** until the cycle is resolved:
|
|
613
|
+
|
|
614
|
+
| Issue | Depends On | Problem |
|
|
615
|
+
|-------|------------|---------|
|
|
616
|
+
{rows: each EXCLUDED_CYCLE issue, its predecessor list, "mutual dependency — forms cycle with #{other}"}
|
|
617
|
+
|
|
618
|
+
**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.
|
|
619
|
+
{END IF}
|
|
620
|
+
|
|
483
621
|
Proceed? (yes / adjust / pick specific issues)
|
|
484
622
|
```
|
|
485
623
|
|
|
486
624
|
**Wait for user confirmation before spawning agents.** This is the checkpoint — once agents launch, they run autonomously.
|
|
487
625
|
|
|
488
|
-
**After confirmation**: If
|
|
626
|
+
**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.
|
|
489
627
|
|
|
490
628
|
---
|
|
491
629
|
|
|
492
|
-
## Phase 4:
|
|
630
|
+
## Phase 4: Streaming DAG Execution
|
|
493
631
|
|
|
494
|
-
### Step 4A-pre:
|
|
632
|
+
### Step 4A-pre: Staging baseline tracking (MANDATORY — continuous)
|
|
495
633
|
|
|
496
|
-
**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.
|
|
634
|
+
**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.
|
|
497
635
|
|
|
498
|
-
**When to run**:
|
|
636
|
+
**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/*`.
|
|
499
637
|
|
|
500
638
|
```bash
|
|
501
|
-
# Capture staging baseline before
|
|
639
|
+
# Capture initial staging baseline before first dispatch
|
|
502
640
|
git fetch origin
|
|
503
641
|
if [ "$DEFAULT_BRANCH" = "$STAGING_BRANCH" ]; then
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
echo "Staging baseline before Wave {N}: skipped — single-branch repo (staging == default)"
|
|
642
|
+
STAGING_LINES_BASELINE=0
|
|
643
|
+
echo "Staging baseline: skipped — single-branch repo (staging == default)"
|
|
507
644
|
else
|
|
508
|
-
|
|
645
|
+
STAGING_LINES_BASELINE=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
|
|
509
646
|
| tail -1 \
|
|
510
647
|
| grep -oP '\d+ insertion' \
|
|
511
648
|
| grep -oP '\d+' \
|
|
512
649
|
|| echo "0")
|
|
513
|
-
echo "Staging baseline
|
|
650
|
+
echo "Staging baseline: ${STAGING_LINES_BASELINE} lines ahead of $DEFAULT_BRANCH"
|
|
514
651
|
fi
|
|
652
|
+
|
|
653
|
+
# Track cumulative expected growth from merged PRs
|
|
654
|
+
CUMULATIVE_EXPECTED_DELTA=0
|
|
515
655
|
```
|
|
516
656
|
|
|
517
|
-
|
|
657
|
+
**Per-agent-completion integrity check** (run in Step 4B after each agent merges a PR targeting staging):
|
|
518
658
|
|
|
519
659
|
```bash
|
|
520
|
-
#
|
|
660
|
+
# After agent completes and its PR merges to staging:
|
|
521
661
|
git fetch origin
|
|
522
|
-
if [ "$DEFAULT_BRANCH"
|
|
523
|
-
|
|
524
|
-
STAGING_GROWTH=0
|
|
525
|
-
echo "Staging integrity check: skipped — single-branch repo (staging == default)"
|
|
526
|
-
else
|
|
527
|
-
STAGING_LINES_AFTER=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
|
|
662
|
+
if [ "$DEFAULT_BRANCH" != "$STAGING_BRANCH" ]; then
|
|
663
|
+
STAGING_LINES_NOW=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
|
|
528
664
|
| tail -1 \
|
|
529
665
|
| grep -oP '\d+ insertion' \
|
|
530
666
|
| grep -oP '\d+' \
|
|
531
667
|
|| echo "0")
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
# (Use 0 as threshold if no PRs merged — any growth is unexpected)
|
|
539
|
-
EXPECTED_DELTA={SUM_OF_PR_LINE_COUNTS_THIS_WAVE} # set from PR diffs collected in 4B
|
|
540
|
-
UNEXPECTED_GROWTH=$((STAGING_GROWTH - EXPECTED_DELTA))
|
|
668
|
+
STAGING_TOTAL_GROWTH=$((STAGING_LINES_NOW - STAGING_LINES_BASELINE))
|
|
669
|
+
|
|
670
|
+
# Add this PR's line count to cumulative expected delta
|
|
671
|
+
CUMULATIVE_EXPECTED_DELTA=$((CUMULATIVE_EXPECTED_DELTA + {THIS_PR_LINE_COUNT}))
|
|
672
|
+
|
|
673
|
+
UNEXPECTED_GROWTH=$((STAGING_TOTAL_GROWTH - CUMULATIVE_EXPECTED_DELTA))
|
|
541
674
|
if [ "$UNEXPECTED_GROWTH" -gt 500 ]; then
|
|
542
|
-
echo "ALERT: Staging grew by ${
|
|
675
|
+
echo "ALERT: Staging grew by ${STAGING_TOTAL_GROWTH} lines (+${UNEXPECTED_GROWTH} beyond expected ${CUMULATIVE_EXPECTED_DELTA})."
|
|
543
676
|
echo "This may indicate milestone-code contamination via agent merge commits."
|
|
544
677
|
echo "Review: git log --oneline --merges origin/$DEFAULT_BRANCH..origin/$STAGING_BRANCH"
|
|
545
678
|
echo "Do NOT merge $STAGING_BRANCH → $DEFAULT_BRANCH until the unexpected growth is investigated."
|
|
@@ -548,29 +681,29 @@ else
|
|
|
548
681
|
fi
|
|
549
682
|
```
|
|
550
683
|
|
|
551
|
-
If `UNEXPECTED_GROWTH > 500`, report the alert clearly before
|
|
684
|
+
If `UNEXPECTED_GROWTH > 500`, report the alert clearly before dispatching any more agents. The user confirms whether to continue.
|
|
552
685
|
|
|
553
686
|
---
|
|
554
687
|
|
|
555
|
-
### Step 4A.pre.0: Pre-create milestone branches for
|
|
688
|
+
### Step 4A.pre.0: Pre-create milestone branches for ready issues (MANDATORY before classify-lane) <!-- Added: forge#901 -->
|
|
556
689
|
|
|
557
|
-
**WHY THIS EXISTS**: Feature-lane milestone branches were created lazily — by whichever feature-lane agent reached its build phase first. When
|
|
690
|
+
**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.
|
|
558
691
|
|
|
559
|
-
The fix is deterministic: create every milestone branch the
|
|
692
|
+
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.
|
|
560
693
|
|
|
561
|
-
**When to run**: Before the classify-lane loop in Step 4A.pre, for every
|
|
694
|
+
**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).
|
|
562
695
|
|
|
563
|
-
**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
|
|
696
|
+
**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 -->
|
|
564
697
|
|
|
565
698
|
```bash
|
|
566
|
-
# Pre-create the origin milestone branch for every distinct milestone referenced by
|
|
699
|
+
# Pre-create the origin milestone branch for every distinct milestone referenced by ready issues.
|
|
567
700
|
# Slugification MUST byte-match scripts/classify-lane.sh — otherwise a branch is created that
|
|
568
701
|
# the classifier will not select. Keep these two slug pipelines identical.
|
|
569
702
|
git fetch origin
|
|
570
703
|
|
|
571
|
-
# Collect distinct milestone titles among the
|
|
704
|
+
# Collect distinct milestone titles among the ready issues
|
|
572
705
|
declare -A SEEN_MILESTONE_SLUG
|
|
573
|
-
for NUM in {
|
|
706
|
+
for NUM in {ready_issue_numbers}; do
|
|
574
707
|
MILESTONE_TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json milestone --jq '.milestone.title // empty' 2>/dev/null || echo "")
|
|
575
708
|
[ -z "$MILESTONE_TITLE" ] && continue # fast-lane issue — no milestone branch needed
|
|
576
709
|
|
|
@@ -615,11 +748,11 @@ for NUM in {wave_issue_numbers}; do
|
|
|
615
748
|
done
|
|
616
749
|
```
|
|
617
750
|
|
|
618
|
-
This step is the deterministic counterpart to fix #2 (atomic create-if-absent in the classifier): by guaranteeing the branch exists before the
|
|
751
|
+
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.
|
|
619
752
|
|
|
620
|
-
### Step 4A.pre: Classify lane for each issue (MANDATORY before
|
|
753
|
+
### Step 4A.pre: Classify lane for each issue (MANDATORY before dispatching agents)
|
|
621
754
|
|
|
622
|
-
Before building agent prompts, run `classify-lane.sh` for every issue in the current
|
|
755
|
+
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.
|
|
623
756
|
|
|
624
757
|
```bash
|
|
625
758
|
# Requires classify-lane.sh to be available at ~/.claude/scripts/classify-lane.sh
|
|
@@ -629,7 +762,15 @@ Before building agent prompts, run `classify-lane.sh` for every issue in the cur
|
|
|
629
762
|
declare -A ISSUE_LANE
|
|
630
763
|
declare -A ISSUE_PR_BASE
|
|
631
764
|
|
|
632
|
-
for
|
|
765
|
+
# Batch-level accumulators for review-finding cascade control (Step 4C) and
|
|
766
|
+
# Completion Sweep (Step 4F). Declared here so they persist across ALL agent
|
|
767
|
+
# completions — Step 4C runs per-agent and must NOT re-initialize these.
|
|
768
|
+
DEFERRED_FINDINGS=()
|
|
769
|
+
QUEUED_FINDINGS=()
|
|
770
|
+
declare -A DEFERRED_REASONS
|
|
771
|
+
declare -A AGENT_ISSUE_MAP
|
|
772
|
+
|
|
773
|
+
for NUM in {ready_issue_numbers}; do
|
|
633
774
|
PR_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$NUM" -R {GH_REPO}) || {
|
|
634
775
|
echo "ERROR: classify-lane.sh failed for #$NUM — adding needs-human label and skipping" >&2
|
|
635
776
|
gh issue edit "$NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
@@ -649,11 +790,11 @@ done
|
|
|
649
790
|
|
|
650
791
|
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 -->
|
|
651
792
|
|
|
652
|
-
### Step 4A:
|
|
793
|
+
### Step 4A: Dispatch ready issues
|
|
653
794
|
|
|
654
795
|
**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.**
|
|
655
796
|
|
|
656
|
-
For each issue
|
|
797
|
+
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.
|
|
657
798
|
|
|
658
799
|
**One agent per issue.** Do NOT group multiple issues into a single agent. `/work-on` handles branching, labels, and PRs per-issue.
|
|
659
800
|
|
|
@@ -712,7 +853,7 @@ If the label is NOT terminal (e.g., `workflow:investigating`, `workflow:ready-to
|
|
|
712
853
|
)
|
|
713
854
|
```
|
|
714
855
|
|
|
715
|
-
**`{GIST_CONTEXT}` generation**: For each issue
|
|
856
|
+
**`{GIST_CONTEXT}` generation**: For each issue being dispatched, check if it was spawned by an investigation that has a Knowledge Gist (from Step 2C.5). If so, include the Gist URL(s) in the agent prompt:
|
|
716
857
|
|
|
717
858
|
```bash
|
|
718
859
|
# Build GIST_CONTEXT for an issue
|
|
@@ -739,12 +880,43 @@ fi
|
|
|
739
880
|
|
|
740
881
|
If `GIST_CONTEXT` is empty (no parent investigation or milestone index found), the variable resolves to a blank line in the template — no impact on the agent prompt. <!-- Updated: forge#341 -->
|
|
741
882
|
|
|
742
|
-
**
|
|
883
|
+
**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:
|
|
884
|
+
|
|
885
|
+
```
|
|
886
|
+
# After the single-message batch spawn, capture each returned ID:
|
|
887
|
+
AGENT_ISSUE_MAP[{NUMBER}] = <agent_id returned by Agent()>
|
|
888
|
+
```
|
|
743
889
|
|
|
744
|
-
|
|
890
|
+
`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 -->
|
|
891
|
+
|
|
892
|
+
**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.
|
|
893
|
+
|
|
894
|
+
### Step 4B: Monitor completions and dispatch newly ready issues
|
|
745
895
|
|
|
746
896
|
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.
|
|
747
897
|
|
|
898
|
+
**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.
|
|
899
|
+
|
|
900
|
+
```bash
|
|
901
|
+
# After each agent completion, check for newly ready issues:
|
|
902
|
+
for BLOCKED_NUM in {all_blocked_issue_numbers}; do
|
|
903
|
+
ALL_PREDS_DONE=true
|
|
904
|
+
for PRED in {predecessors_of_BLOCKED_NUM}; do
|
|
905
|
+
PRED_STATE=$(gh issue view $PRED -R {GH_REPO} --json labels,state --jq '{state: .state, workflow: [.labels[].name | select(startswith("workflow:"))]}')
|
|
906
|
+
# Terminal states: workflow:merged, workflow:invalid, needs-human, or state=CLOSED
|
|
907
|
+
if ! echo "$PRED_STATE" | grep -qE 'workflow:merged|workflow:invalid|needs-human|CLOSED'; then
|
|
908
|
+
ALL_PREDS_DONE=false
|
|
909
|
+
break
|
|
910
|
+
fi
|
|
911
|
+
done
|
|
912
|
+
if [ "$ALL_PREDS_DONE" = "true" ]; then
|
|
913
|
+
echo "#{BLOCKED_NUM} is now READY — all predecessors resolved. Dispatching."
|
|
914
|
+
# Add to dispatch batch for this completion cycle
|
|
915
|
+
fi
|
|
916
|
+
done
|
|
917
|
+
# Run Steps 4A.pre.0 → 4A.pre → 4A for newly ready issues (batch them in a single message)
|
|
918
|
+
```
|
|
919
|
+
|
|
748
920
|
**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:
|
|
749
921
|
|
|
750
922
|
1. **Check if the agent completed the FULL pipeline** — not just one phase:
|
|
@@ -757,7 +929,7 @@ You will be automatically notified when each background agent completes. **Do NO
|
|
|
757
929
|
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**:
|
|
758
930
|
```
|
|
759
931
|
Agent(
|
|
760
|
-
resume=
|
|
932
|
+
resume=AGENT_ISSUE_MAP[{NUMBER}],
|
|
761
933
|
description="Resume #{NUMBER} pipeline",
|
|
762
934
|
run_in_background=true,
|
|
763
935
|
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."
|
|
@@ -765,11 +937,15 @@ You will be automatically notified when each background agent completes. **Do NO
|
|
|
765
937
|
```
|
|
766
938
|
**Resume ALL stalled agents in a single message** (parallel resume). Do not wait between resumes.
|
|
767
939
|
|
|
768
|
-
3. **Track resume cycles per agent.** If an agent has been resumed
|
|
940
|
+
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.
|
|
769
941
|
|
|
770
942
|
4. **Record completed results**: Success (PR merged), Invalid (issue closed), Blocked (needs human), or Error
|
|
771
943
|
|
|
772
|
-
5. **
|
|
944
|
+
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.
|
|
945
|
+
|
|
946
|
+
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.
|
|
947
|
+
|
|
948
|
+
7. **Verify pipeline compliance** — for each truly completed issue, check that the agent used `/work-on`:
|
|
773
949
|
```bash
|
|
774
950
|
LABELS=$(gh issue view $NUM -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith("workflow:"))] | length')
|
|
775
951
|
COMMENTS=$(gh api repos/{GH_REPO}/issues/${NUM}/comments --jq '[.[] | select(.body | test("FORGE:INVESTIGATOR|FORGE:BUILDER"))] | length')
|
|
@@ -779,19 +955,25 @@ You will be automatically notified when each background agent completes. **Do NO
|
|
|
779
955
|
```
|
|
780
956
|
If an agent bypassed the pipeline, report it as a **failure** regardless of whether a PR exists.
|
|
781
957
|
|
|
782
|
-
|
|
958
|
+
8. **Post a brief status update** to the user after each agent reaches terminal state:
|
|
783
959
|
```
|
|
784
960
|
✓ #{NUMBER} — {title} → PR #{PR} merged to {target}
|
|
785
961
|
✗ #{NUMBER} — {title} → {reason for failure}
|
|
786
962
|
⚠ #{NUMBER} — {title} → PIPELINE BYPASS (no /work-on — PR invalid)
|
|
787
|
-
⏳
|
|
963
|
+
⏳ Progress: {completed}/{total} complete, {active} active, {blocked} blocked
|
|
964
|
+
→ Dispatched #{NEWLY_READY} (predecessor #{PRED} completed)
|
|
788
965
|
```
|
|
789
966
|
|
|
967
|
+
9. **Run staging integrity check** (from Step 4A-pre) if the completed agent merged a PR targeting staging.
|
|
968
|
+
|
|
969
|
+
**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.
|
|
970
|
+
|
|
790
971
|
**Anti-pattern — DO NOT DO THIS:**
|
|
791
972
|
- `sleep 60/120/180/300` loops to check status — you will be notified automatically
|
|
792
973
|
- Spawning separate "progress check" agents — they waste tokens and add noise
|
|
793
974
|
- Reading agent JSONL output files to check progress — use GitHub labels as the source of truth
|
|
794
975
|
- Polling the same status check repeatedly on a timer
|
|
976
|
+
- Waiting for a "batch" of completions before checking for newly ready issues — check after EVERY completion
|
|
795
977
|
|
|
796
978
|
### Step 4B.5: Time-Based Stall Detection
|
|
797
979
|
|
|
@@ -799,7 +981,7 @@ You will be automatically notified when each background agent completes. **Do NO
|
|
|
799
981
|
|
|
800
982
|
**When to run** (NOT a sleep loop — two trigger points only):
|
|
801
983
|
1. On every background agent completion event (run BEFORE the terminal-state check in Step 4B)
|
|
802
|
-
2. Before posting any "waiting for
|
|
984
|
+
2. Before posting any "waiting for agents..." status update to the user
|
|
803
985
|
|
|
804
986
|
**Do NOT poll on a timer. Do NOT use sleep. Run at these two trigger points only.**
|
|
805
987
|
|
|
@@ -808,9 +990,9 @@ You will be automatically notified when each background agent completes. **Do NO
|
|
|
808
990
|
STALL_TIMEOUT=$(yq '.pipeline.stall_timeout_minutes // 15' forge.yaml 2>/dev/null || echo 15)
|
|
809
991
|
```
|
|
810
992
|
|
|
811
|
-
**For each non-terminal agent in the current
|
|
993
|
+
**For each non-terminal agent in the current batch**:
|
|
812
994
|
```bash
|
|
813
|
-
for NUM in {
|
|
995
|
+
for NUM in {active_issue_numbers}; do
|
|
814
996
|
# Skip issues already in terminal state
|
|
815
997
|
TERMINAL=$(gh issue view $NUM -R {GH_REPO} --json labels \
|
|
816
998
|
--jq '[.labels[].name | select(. == "workflow:merged" or . == "workflow:invalid" or . == "needs-human")] | length')
|
|
@@ -870,26 +1052,26 @@ Issue #${NUM} has been auto-resumed ${STALL_COUNT} times without reaching a term
|
|
|
870
1052
|
done
|
|
871
1053
|
|
|
872
1054
|
# Launch all stall resumes in parallel (single message — same rule as Step 4B)
|
|
873
|
-
# For each NUM in $STALL_RESUME_LIST, call Agent(resume=
|
|
1055
|
+
# For each NUM in $STALL_RESUME_LIST, call Agent(resume=AGENT_ISSUE_MAP[NUM], run_in_background=true, ...)
|
|
874
1056
|
```
|
|
875
1057
|
|
|
876
1058
|
**Resume all stalled agents in a single message** (parallel). Use the same `Agent(resume=...)` pattern as Step 4B — do not wait between individual resumes.
|
|
877
1059
|
|
|
878
1060
|
**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.
|
|
879
1061
|
|
|
880
|
-
### Step 4C: Collect review-finding issues from completed
|
|
1062
|
+
### Step 4C: Collect review-finding issues from completed agents
|
|
881
1063
|
|
|
882
|
-
|
|
1064
|
+
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.
|
|
883
1065
|
|
|
884
1066
|
```bash
|
|
885
1067
|
# Method 1: Read TRAJECTORY comments from completed issues for "Finding issues" row
|
|
886
|
-
for NUM in {
|
|
1068
|
+
for NUM in {completed_issue_number}; do
|
|
887
1069
|
gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
888
1070
|
--jq '.[] | select(.body | contains("FORGE:TRAJECTORY")) | .body' 2>/dev/null \
|
|
889
1071
|
| grep -oP 'Finding issues\s*\|\s*#?\K\d+[^|]*' | grep -oP '\d+' | sort -u
|
|
890
1072
|
done
|
|
891
1073
|
|
|
892
|
-
# Method 2 (fallback): Check for recently created review-finding issues that reference PRs from this
|
|
1074
|
+
# Method 2 (fallback): Check for recently created review-finding issues that reference PRs from this batch
|
|
893
1075
|
gh issue list -R {GH_REPO} --state open --label "review-finding" --limit 20 \
|
|
894
1076
|
--json number,title,body,createdAt \
|
|
895
1077
|
--jq "[.[] | select(.createdAt > \"$(date -u -d '2 hours ago' +%Y-%m-%dT%H:%M:%SZ)\")]"
|
|
@@ -897,7 +1079,7 @@ gh issue list -R {GH_REPO} --state open --label "review-finding" --limit 20 \
|
|
|
897
1079
|
|
|
898
1080
|
**If review-finding issues were spawned:**
|
|
899
1081
|
|
|
900
|
-
**Cascade control (MANDATORY — run before folding findings into
|
|
1082
|
+
**Cascade control (MANDATORY — run before folding findings into the DAG):**
|
|
901
1083
|
|
|
902
1084
|
For each spawned finding, determine whether it should be **executed** or **deferred**:
|
|
903
1085
|
|
|
@@ -905,17 +1087,17 @@ For each spawned finding, determine whether it should be **executed** or **defer
|
|
|
905
1087
|
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.
|
|
906
1088
|
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.
|
|
907
1089
|
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.
|
|
908
|
-
4. **P3 + same-file overlap**: Finding is labeled `P3` AND the file it targets overlaps with ANY file already in the current or
|
|
1090
|
+
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.
|
|
909
1091
|
|
|
910
|
-
**Defer** (do NOT add to
|
|
1092
|
+
**Defer** (do NOT add to the DAG) if rules 1, 3, or 4 match.
|
|
911
1093
|
|
|
912
|
-
**Execute** (add to
|
|
1094
|
+
**Execute** (add to the DAG) if:
|
|
913
1095
|
- Rule 2 matches (P1 or P2)
|
|
914
1096
|
- None of the defer rules matched (generation 1, P3 with no file overlap, not a keyword match)
|
|
915
1097
|
|
|
916
1098
|
**Before running the loop, build the batch file list (MANDATORY for Heuristic 3):**
|
|
917
1099
|
|
|
918
|
-
Collect all file paths from every issue in the current batch — both completed
|
|
1100
|
+
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.
|
|
919
1101
|
|
|
920
1102
|
```bash
|
|
921
1103
|
# Build ALL_BATCH_FILES: collect file paths from ALL batch issues (completed + queued)
|
|
@@ -938,6 +1120,8 @@ ALL_BATCH_FILES=$(echo "$ALL_BATCH_FILES" | sort -u | grep -v '^$')
|
|
|
938
1120
|
|
|
939
1121
|
```bash
|
|
940
1122
|
# For each finding, check its priority label and generation
|
|
1123
|
+
# NOTE: DEFERRED_FINDINGS, QUEUED_FINDINGS, and DEFERRED_REASONS are declared at
|
|
1124
|
+
# batch scope in Step 4A.pre — do NOT re-initialize them here (Step 4C runs per-agent).
|
|
941
1125
|
for FINDING_NUM in {spawned_finding_numbers}; do
|
|
942
1126
|
FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body \
|
|
943
1127
|
--jq '{labels: [.labels[].name], title: .title, body: .body}')
|
|
@@ -971,6 +1155,7 @@ for FINDING_NUM in {spawned_finding_numbers}; do
|
|
|
971
1155
|
|
|
972
1156
|
if [ "$DEFER" = "true" ]; then
|
|
973
1157
|
DEFERRED_FINDINGS+=($FINDING_NUM)
|
|
1158
|
+
DEFERRED_REASONS[$FINDING_NUM]="$DEFER_REASON"
|
|
974
1159
|
echo "Deferred #${FINDING_NUM}: $DEFER_REASON"
|
|
975
1160
|
else
|
|
976
1161
|
QUEUED_FINDINGS+=($FINDING_NUM)
|
|
@@ -980,28 +1165,27 @@ done
|
|
|
980
1165
|
|
|
981
1166
|
**For queued (non-deferred) findings:**
|
|
982
1167
|
|
|
983
|
-
1. **Add them to the
|
|
1168
|
+
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.
|
|
984
1169
|
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.
|
|
985
1170
|
3. **Report to user:**
|
|
986
1171
|
```
|
|
987
|
-
|
|
988
|
-
|
|
1172
|
+
Agent #{COMPLETED} spawned {count} new finding issues: #{A}, #{B}
|
|
1173
|
+
Added to DAG: #{A} (predecessors: {}), #{B} (predecessors: {#{X}})
|
|
989
1174
|
Deferred (cascade control): #{C} (P3 same-file), #{D} (comment heuristic)
|
|
990
|
-
Adding queued findings to Wave {N+1} (or new wave if no remaining waves).
|
|
991
1175
|
```
|
|
992
|
-
4. **Re-run file-overlap detection** (Step 3C) on the expanded issue set — finding issues may conflict with
|
|
1176
|
+
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.
|
|
993
1177
|
|
|
994
1178
|
**For deferred findings:**
|
|
995
1179
|
|
|
996
|
-
|
|
1180
|
+
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.
|
|
997
1181
|
|
|
998
|
-
**If no review-finding issues were spawned:** Continue
|
|
1182
|
+
**If no review-finding issues were spawned:** Continue monitoring for the next agent completion.
|
|
999
1183
|
|
|
1000
|
-
### Step 4C.5: Milestone lane-consistency check (
|
|
1184
|
+
### Step 4C.5: Milestone lane-consistency check (periodic) <!-- Added: forge#901 -->
|
|
1001
1185
|
|
|
1002
1186
|
**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.
|
|
1003
1187
|
|
|
1004
|
-
**When to run**: After
|
|
1188
|
+
**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.
|
|
1005
1189
|
|
|
1006
1190
|
```bash
|
|
1007
1191
|
# For each distinct milestone in the batch, assert all of its feature-lane PRs share one base.
|
|
@@ -1051,13 +1235,13 @@ for NUM in {all_batch_issue_numbers}; do
|
|
|
1051
1235
|
done
|
|
1052
1236
|
```
|
|
1053
1237
|
|
|
1054
|
-
Report any `ALERT` lines prominently before
|
|
1238
|
+
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.
|
|
1055
1239
|
|
|
1056
|
-
### Step 4D: Milestone integration build gate (MANDATORY
|
|
1240
|
+
### Step 4D: Milestone integration build gate (MANDATORY — periodic for milestone batches)
|
|
1057
1241
|
|
|
1058
|
-
**WHY THIS EXISTS**: Session Intelligence milestone shipped 116 PRs across multiple
|
|
1242
|
+
**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.
|
|
1059
1243
|
|
|
1060
|
-
**When to run**: After
|
|
1244
|
+
**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.
|
|
1061
1245
|
|
|
1062
1246
|
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.
|
|
1063
1247
|
|
|
@@ -1072,7 +1256,7 @@ MILESTONE_BRANCH="milestone/{milestone_slug}"
|
|
|
1072
1256
|
TS_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.(tsx?|jsx?)$' | head -1)
|
|
1073
1257
|
|
|
1074
1258
|
if [ -n "$TS_CHANGED" ]; then
|
|
1075
|
-
echo "=== Integration Build Gate (TypeScript):
|
|
1259
|
+
echo "=== Integration Build Gate (TypeScript): batch checkpoint ==="
|
|
1076
1260
|
cd {REPO_PATH}
|
|
1077
1261
|
git fetch origin ${MILESTONE_BRANCH}
|
|
1078
1262
|
git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
|
|
@@ -1096,18 +1280,18 @@ if [ -n "$TS_CHANGED" ]; then
|
|
|
1096
1280
|
git checkout - 2>/dev/null
|
|
1097
1281
|
|
|
1098
1282
|
if [ "$TSC_EXIT" -ne 0 ]; then
|
|
1099
|
-
echo "BLOCKING: TypeScript errors on ${MILESTONE_BRANCH} after
|
|
1100
|
-
echo "Fix type errors before
|
|
1283
|
+
echo "BLOCKING: TypeScript errors on ${MILESTONE_BRANCH} after batch checkpoint."
|
|
1284
|
+
echo "Fix type errors before dispatching more milestone agents."
|
|
1101
1285
|
elif [ "${BUILD_EXIT:-0}" -ne 0 ]; then
|
|
1102
|
-
echo "BLOCKING: build failed on ${MILESTONE_BRANCH} after
|
|
1103
|
-
echo "Build/prerender errors — fix before
|
|
1286
|
+
echo "BLOCKING: build failed on ${MILESTONE_BRANCH} after batch checkpoint."
|
|
1287
|
+
echo "Build/prerender errors — fix before dispatching more milestone agents."
|
|
1104
1288
|
fi
|
|
1105
1289
|
fi
|
|
1106
1290
|
|
|
1107
1291
|
# Python format check
|
|
1108
1292
|
PY_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.py$' | head -1)
|
|
1109
1293
|
if [ -n "$PY_CHANGED" ]; then
|
|
1110
|
-
echo "=== Integration Build Gate (Python):
|
|
1294
|
+
echo "=== Integration Build Gate (Python): batch checkpoint ==="
|
|
1111
1295
|
cd {REPO_PATH}
|
|
1112
1296
|
git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
|
|
1113
1297
|
|
|
@@ -1122,32 +1306,234 @@ if [ -n "$PY_CHANGED" ]; then
|
|
|
1122
1306
|
git checkout - 2>/dev/null
|
|
1123
1307
|
|
|
1124
1308
|
if [ "$FORMAT_EXIT" -ne 0 ]; then
|
|
1125
|
-
echo "WARNING: Python formatting issues on ${MILESTONE_BRANCH} after
|
|
1309
|
+
echo "WARNING: Python formatting issues on ${MILESTONE_BRANCH} after batch checkpoint."
|
|
1126
1310
|
echo "Not blocking but should be fixed before milestone→staging."
|
|
1127
1311
|
fi
|
|
1128
1312
|
fi
|
|
1129
1313
|
```
|
|
1130
1314
|
|
|
1131
|
-
**If the gate fails**: Report the errors to the user. Do NOT
|
|
1132
|
-
|
|
1133
|
-
### Step 4E: Advance to next wave
|
|
1315
|
+
**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.
|
|
1134
1316
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
1. Check if any failures in the current wave block the next wave (dependency failures)
|
|
1138
|
-
2. If a dependency failed, skip dependent issues in the next wave and report them as "skipped — dependency #{X} failed"
|
|
1139
|
-
3. Launch the next wave (same process as Step 4A)
|
|
1140
|
-
|
|
1141
|
-
### Step 4F: Handle individual agent failures
|
|
1317
|
+
### Step 4E: Handle individual agent failures
|
|
1142
1318
|
|
|
1143
1319
|
If an agent reports failure or error:
|
|
1144
1320
|
- **Merge conflict**: Report to user, mark issue as needing human attention
|
|
1145
1321
|
- **Invalid issue**: Already handled by the agent (closed with comment) — just report it
|
|
1146
1322
|
- **Build/test failure**: Report the error, suggest manual intervention
|
|
1147
1323
|
- **Agent timeout**: Report which issue timed out, suggest re-running with `/work-on #{N}`
|
|
1324
|
+
- **Dependency cascade**: Mark all transitive dependents in the DAG as "skipped — dependency #{X} failed"
|
|
1148
1325
|
|
|
1149
1326
|
**Do NOT retry failed agents automatically.** Report the failure and let the user decide.
|
|
1150
1327
|
|
|
1328
|
+
### Step 4F: Completion Sweep (deferred review-spawned findings) <!-- Added: forge#1105 -->
|
|
1329
|
+
|
|
1330
|
+
**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.
|
|
1331
|
+
|
|
1332
|
+
**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.
|
|
1333
|
+
|
|
1334
|
+
**Step 4F.1: Classify deferred findings into permanent vs re-evaluable**
|
|
1335
|
+
|
|
1336
|
+
```bash
|
|
1337
|
+
PERMANENT_DEFERRED=()
|
|
1338
|
+
SWEEP_CANDIDATES=()
|
|
1339
|
+
|
|
1340
|
+
for FINDING_NUM in "${DEFERRED_FINDINGS[@]}"; do
|
|
1341
|
+
DEFER_REASON="${DEFERRED_REASONS[$FINDING_NUM]}"
|
|
1342
|
+
|
|
1343
|
+
# Generation >= 2 deferrals are PERMANENT — unbounded cascade prevention
|
|
1344
|
+
if echo "$DEFER_REASON" | grep -qi "generation"; then
|
|
1345
|
+
PERMANENT_DEFERRED+=($FINDING_NUM)
|
|
1346
|
+
else
|
|
1347
|
+
# All other deferrals (comment/typo, P3 same-file) are re-evaluable
|
|
1348
|
+
SWEEP_CANDIDATES+=($FINDING_NUM)
|
|
1349
|
+
fi
|
|
1350
|
+
done
|
|
1351
|
+
|
|
1352
|
+
echo "Completion sweep: ${#SWEEP_CANDIDATES[@]} re-evaluable, ${#PERMANENT_DEFERRED[@]} permanent"
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
**Step 4F.2: Re-evaluate sweep candidates**
|
|
1356
|
+
|
|
1357
|
+
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.
|
|
1358
|
+
|
|
1359
|
+
```bash
|
|
1360
|
+
SWEEP_EXECUTE=()
|
|
1361
|
+
SWEEP_STILL_DEFERRED=()
|
|
1362
|
+
|
|
1363
|
+
for FINDING_NUM in "${SWEEP_CANDIDATES[@]}"; do
|
|
1364
|
+
FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body,state \
|
|
1365
|
+
--jq '{labels: [.labels[].name], title: .title, body: .body, state: .state}')
|
|
1366
|
+
|
|
1367
|
+
# Skip if already closed (resolved by another process)
|
|
1368
|
+
STATE=$(echo "$FINDING_DATA" | jq -r '.state')
|
|
1369
|
+
[ "$STATE" = "CLOSED" ] && continue
|
|
1370
|
+
|
|
1371
|
+
PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("priority:P")) | ltrimstr("priority:")' | head -1)
|
|
1372
|
+
TITLE=$(echo "$FINDING_DATA" | jq -r '.title')
|
|
1373
|
+
|
|
1374
|
+
# Re-apply heuristics against the drained DAG (no active batch files)
|
|
1375
|
+
# Comment/typo heuristic still applies — these are cosmetic regardless of DAG state
|
|
1376
|
+
if echo "$TITLE" | grep -qi "comment\|typo"; then
|
|
1377
|
+
SWEEP_STILL_DEFERRED+=($FINDING_NUM)
|
|
1378
|
+
echo "Sweep: #${FINDING_NUM} still deferred (comment/typo — cosmetic)"
|
|
1379
|
+
else
|
|
1380
|
+
# P3 same-file overlap no longer applies (DAG is drained, no active files)
|
|
1381
|
+
# All other findings are safe to execute
|
|
1382
|
+
SWEEP_EXECUTE+=($FINDING_NUM)
|
|
1383
|
+
echo "Sweep: #${FINDING_NUM} cleared for execution (file overlap resolved)"
|
|
1384
|
+
fi
|
|
1385
|
+
done
|
|
1386
|
+
```
|
|
1387
|
+
|
|
1388
|
+
**Step 4F.3: Dispatch cleared findings**
|
|
1389
|
+
|
|
1390
|
+
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).
|
|
1391
|
+
|
|
1392
|
+
**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.
|
|
1393
|
+
|
|
1394
|
+
**Step 4F.3.pre: Classify lane for each sweep finding (MANDATORY before dispatching)**
|
|
1395
|
+
|
|
1396
|
+
Run `classify-lane.sh` per finding — same pattern as Step 4A.pre:
|
|
1397
|
+
|
|
1398
|
+
```bash
|
|
1399
|
+
declare -A SWEEP_LANE
|
|
1400
|
+
declare -A SWEEP_PR_BASE
|
|
1401
|
+
|
|
1402
|
+
for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
|
|
1403
|
+
SWEEP_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$FINDING_NUM" -R {GH_REPO}) || {
|
|
1404
|
+
echo "ERROR: classify-lane.sh failed for #$FINDING_NUM — adding needs-human and skipping" >&2
|
|
1405
|
+
gh issue edit "$FINDING_NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
|
|
1406
|
+
continue
|
|
1407
|
+
}
|
|
1408
|
+
if [ "$SWEEP_BASE" = "staging" ]; then
|
|
1409
|
+
SWEEP_LANE[$FINDING_NUM]="fast-lane"
|
|
1410
|
+
else
|
|
1411
|
+
SWEEP_LANE[$FINDING_NUM]="feature-lane"
|
|
1412
|
+
fi
|
|
1413
|
+
SWEEP_PR_BASE[$FINDING_NUM]="$SWEEP_BASE"
|
|
1414
|
+
echo "#$FINDING_NUM → lane=${SWEEP_LANE[$FINDING_NUM]}, PR_BASE=$SWEEP_BASE"
|
|
1415
|
+
done
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
**Step 4F.3.dispatch: Dispatch with full Step 4A template**
|
|
1419
|
+
|
|
1420
|
+
```bash
|
|
1421
|
+
if [ ${#SWEEP_EXECUTE[@]} -gt 0 ]; then
|
|
1422
|
+
echo "Completion sweep: dispatching ${#SWEEP_EXECUTE[@]} cleared findings"
|
|
1423
|
+
|
|
1424
|
+
# Build sweep DAG — same conflict detection as Step 3C Layers 1-4
|
|
1425
|
+
# but only among the swept findings (no original batch issues remain active)
|
|
1426
|
+
# Dispatch ready findings, monitor completions using the same Step 4B loop
|
|
1427
|
+
# This is a SINGLE pass — findings spawned during the sweep are NOT swept again
|
|
1428
|
+
# (they follow the standard Step 4C triage: queued or deferred for next run)
|
|
1429
|
+
|
|
1430
|
+
# NOTE: Step 4A.pre.0 (milestone branch pre-creation) is intentionally skipped here.
|
|
1431
|
+
# Swept findings are always review-finding issues that target an already-existing branch
|
|
1432
|
+
# (staging or a milestone branch). The branch was created when the original batch ran —
|
|
1433
|
+
# it always exists by the time the sweep runs.
|
|
1434
|
+
|
|
1435
|
+
for FINDING_NUM in "${SWEEP_EXECUTE[@]}"; do
|
|
1436
|
+
# Skip any finding whose classify-lane call failed (needs-human already set above)
|
|
1437
|
+
[ -z "${SWEEP_PR_BASE[$FINDING_NUM]:-}" ] && continue
|
|
1438
|
+
|
|
1439
|
+
FINDING_TITLE=$(gh issue view "$FINDING_NUM" -R {GH_REPO} --json title --jq '.title' 2>/dev/null || echo "")
|
|
1440
|
+
|
|
1441
|
+
# Build GIST_CONTEXT for sweep finding — same as Step 4A generation block
|
|
1442
|
+
GIST_CONTEXT=""
|
|
1443
|
+
PARENT_INV=$(gh issue view "$FINDING_NUM" -R {GH_REPO} --json body --jq '.body' \
|
|
1444
|
+
| grep -oP '(?i)parent[: ]*#\K\d+|spawned from[: ]*#\K\d+' | head -1)
|
|
1445
|
+
|
|
1446
|
+
if [ -n "$PARENT_INV" ] && [ -n "${INVESTIGATION_GISTS[$PARENT_INV]:-}" ]; then
|
|
1447
|
+
GIST_CONTEXT="
|
|
1448
|
+
**CONTEXT FROM PRIOR INVESTIGATION**: Investigation #${PARENT_INV} produced Knowledge Gist(s) with findings relevant to this issue:
|
|
1449
|
+
$(echo "${INVESTIGATION_GISTS[$PARENT_INV]}" | while IFS= read -r url; do echo "- ${url}"; done)
|
|
1450
|
+
Fetch the Gist content during the context-gathering phase for implementation guidance."
|
|
1451
|
+
fi
|
|
1452
|
+
|
|
1453
|
+
if [ -n "$MILESTONE_INDEX_URL" ]; then
|
|
1454
|
+
GIST_CONTEXT="${GIST_CONTEXT}
|
|
1455
|
+
|
|
1456
|
+
**MILESTONE KNOWLEDGE INDEX**: All investigation findings for this milestone are aggregated in a single index Gist:
|
|
1457
|
+
- ${MILESTONE_INDEX_URL}
|
|
1458
|
+
The context-gathering phase can fetch this index to discover all investigation Gists for the milestone."
|
|
1459
|
+
fi
|
|
1460
|
+
|
|
1461
|
+
# Use the full Step 4A template verbatim — copied here so sweep agents receive
|
|
1462
|
+
# the complete pipeline contract. Keep in sync with Step 4A when the template changes.
|
|
1463
|
+
Agent(
|
|
1464
|
+
subagent_type="general-purpose",
|
|
1465
|
+
model="sonnet",
|
|
1466
|
+
description="Work on {PROJECT_PREFIX}#${FINDING_NUM}",
|
|
1467
|
+
run_in_background=true,
|
|
1468
|
+
prompt="You are working on GitHub issue #${FINDING_NUM} for the {PROJECT_NAME} project.
|
|
1469
|
+
|
|
1470
|
+
**Project**: {PROJECT_NAME}
|
|
1471
|
+
**Repository**: {GH_REPO}
|
|
1472
|
+
**Repo path**: {REPO_PATH}
|
|
1473
|
+
|
|
1474
|
+
**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.
|
|
1475
|
+
|
|
1476
|
+
**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.
|
|
1477
|
+
|
|
1478
|
+
**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.
|
|
1479
|
+
|
|
1480
|
+
**IMPORTANT RULES**:
|
|
1481
|
+
- **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.
|
|
1482
|
+
- For default repo issues: \`Skill(skill='work-on', args='${FINDING_NUM}')\`
|
|
1483
|
+
- For satellite repo issues: \`Skill(skill='work-on', args='{SATELLITE_PREFIX}:${FINDING_NUM}')\` (prefix from forge.yaml → repos.satellites)
|
|
1484
|
+
- NEVER bypass /work-on with manual git/gh commands — the label updates and structured comments are critical for tracking
|
|
1485
|
+
- NEVER target \`main\` for PRs targeting the default repo. Use \`{STAGING_BRANCH}\` for fast-lane issues, or \`milestone/{slug}\` for milestone issues.
|
|
1486
|
+
- Satellite repos (MCP, n8n) have no staging branch — fast-lane PRs go to \`main\` for those.
|
|
1487
|
+
- If the issue is INVALID after investigation, close it with a comment explaining why
|
|
1488
|
+
- If you hit merge conflicts or blockers, post a comment on the issue and STOP — do not force anything
|
|
1489
|
+
- Do not interact with the user — you are running autonomously in the background
|
|
1490
|
+
- **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.
|
|
1491
|
+
|
|
1492
|
+
**LABEL-STATE LOOP CONTRACT — enforce after EVERY Skill return**:
|
|
1493
|
+
After EVERY \`Skill(skill='work-on', ...)\` call returns, immediately check the issue's current workflow label:
|
|
1494
|
+
\`\`\`bash
|
|
1495
|
+
gh issue view ${FINDING_NUM} -R {GH_REPO} --json labels --jq '[.labels[].name | select(startswith(\"workflow:\"))]'
|
|
1496
|
+
\`\`\`
|
|
1497
|
+
**Terminal labels** (only these allow you to stop): \`workflow:merged\`, \`workflow:invalid\`
|
|
1498
|
+
**Terminal condition also**: \`needs-human\` label present OR issue state is \`closed\`
|
|
1499
|
+
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.
|
|
1500
|
+
|
|
1501
|
+
**CRITICAL — SOURCE BRANCH DETECTION**:
|
|
1502
|
+
- If the issue has the \`review-finding\` label, read the issue body for \`**Code branch**: \\\`{branch}\\\`\`
|
|
1503
|
+
- If found, that is the SOURCE_BRANCH — the code ONLY exists on that branch (e.g., \`staging\`), NOT on \`origin/main\`
|
|
1504
|
+
- Investigation MUST use \`git show origin/{SOURCE_BRANCH}:{filepath}\` to verify the code exists
|
|
1505
|
+
- Worktree MUST branch from \`origin/{SOURCE_BRANCH}\`, NOT \`origin/main\`
|
|
1506
|
+
- PR target is \`{SOURCE_BRANCH}\` (the fix goes back to where the code lives)
|
|
1507
|
+
|
|
1508
|
+
**LANE**: ${SWEEP_LANE[$FINDING_NUM]} (PR target: ${SWEEP_PR_BASE[$FINDING_NUM]})
|
|
1509
|
+
**Issue title**: ${FINDING_TITLE}
|
|
1510
|
+
${GIST_CONTEXT}
|
|
1511
|
+
"
|
|
1512
|
+
)
|
|
1513
|
+
done
|
|
1514
|
+
|
|
1515
|
+
# Monitor sweep agents using the same Step 4B completion loop
|
|
1516
|
+
# IMPORTANT: Findings spawned by sweep agents are NOT re-swept —
|
|
1517
|
+
# they follow standard Step 4C triage to prevent recursive cascades
|
|
1518
|
+
fi
|
|
1519
|
+
```
|
|
1520
|
+
|
|
1521
|
+
**Step 4F.4: Report sweep results**
|
|
1522
|
+
|
|
1523
|
+
```
|
|
1524
|
+
Completion Sweep Results:
|
|
1525
|
+
Dispatched: #{A}, #{B} (file overlap cleared after DAG drain)
|
|
1526
|
+
Still deferred (cosmetic): #{C} (comment/typo)
|
|
1527
|
+
Permanently deferred (gen2): #{D} (generation >= 2 cascade cap)
|
|
1528
|
+
```
|
|
1529
|
+
|
|
1530
|
+
**After sweep agents complete** (or if no findings were dispatched): proceed to Phase 5.
|
|
1531
|
+
|
|
1532
|
+
**Anti-patterns — DO NOT DO THIS:**
|
|
1533
|
+
- Re-sweeping findings spawned during the sweep itself — this creates unbounded recursion. Sweep is a single pass.
|
|
1534
|
+
- Overriding generation >= 2 deferrals — the cascade cap is absolute.
|
|
1535
|
+
- Skipping the sweep because "there are only a few" deferred findings — even one deferred finding represents unresolved work.
|
|
1536
|
+
|
|
1151
1537
|
---
|
|
1152
1538
|
|
|
1153
1539
|
## Phase 5: Post-Batch Cleanup
|
|
@@ -1191,7 +1577,7 @@ Include the cleanup summary in the final report (Phase 6). If cleanup found prob
|
|
|
1191
1577
|
|
|
1192
1578
|
## Phase 6: Consolidated Report
|
|
1193
1579
|
|
|
1194
|
-
After ALL
|
|
1580
|
+
After ALL issues reach terminal state AND cleanup completes, present a final summary.
|
|
1195
1581
|
|
|
1196
1582
|
### Step 6A: Collect trajectory data
|
|
1197
1583
|
|
|
@@ -1212,7 +1598,7 @@ Aggregate into the batch-level analytics for Step 6B.
|
|
|
1212
1598
|
**Scope**: {milestone / issue list}
|
|
1213
1599
|
**Duration**: {approximate time}
|
|
1214
1600
|
|
|
1215
|
-
###
|
|
1601
|
+
### Investigations (Phase 2)
|
|
1216
1602
|
{IF investigations ran:}
|
|
1217
1603
|
| # | Investigation | Issues Created | Result |
|
|
1218
1604
|
|---|-------------|---------------|--------|
|
|
@@ -1232,17 +1618,31 @@ Aggregate into the batch-level analytics for Step 6B.
|
|
|
1232
1618
|
|
|
1233
1619
|
### Review-Spawned Issues
|
|
1234
1620
|
|
|
1235
|
-
{IF review findings were created during any
|
|
1621
|
+
{IF review findings were created during any agent run:}
|
|
1236
1622
|
| # | Finding | Source Issue | Source PR | Status |
|
|
1237
1623
|
|---|---------|-------------|-----------|--------|
|
|
1238
|
-
| #{F1} | {title} | #{A} | PR #{PR} | ✓ Merged (
|
|
1624
|
+
| #{F1} | {title} | #{A} | PR #{PR} | ✓ Merged (in-batch) / ✓ Swept (completion sweep) / ⏳ Deferred (cosmetic) / ⛔ Deferred (gen2 cascade cap) |
|
|
1239
1625
|
|
|
1240
1626
|
{ELSE: "No review findings created during this batch."}
|
|
1241
1627
|
|
|
1628
|
+
### Completion Sweep <!-- Added: forge#1105 -->
|
|
1629
|
+
- **Re-evaluated**: {N} deferred findings re-checked after DAG drain
|
|
1630
|
+
- **Dispatched**: {N} findings cleared and executed (file overlap resolved)
|
|
1631
|
+
- **Still deferred (cosmetic)**: {N} comment/typo findings (low-value, safe to leave)
|
|
1632
|
+
- **Permanently deferred (gen2)**: {N} generation >= 2 findings (cascade cap — requires manual `/work-on`)
|
|
1633
|
+
|
|
1634
|
+
{IF permanently deferred > 0:}
|
|
1635
|
+
**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:
|
|
1636
|
+
{list of permanently deferred issue numbers and titles}
|
|
1637
|
+
|
|
1638
|
+
{IF cosmetic deferred > 0:}
|
|
1639
|
+
**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}`:
|
|
1640
|
+
{list of cosmetic deferred issue numbers and titles}
|
|
1641
|
+
|
|
1242
1642
|
### Summary
|
|
1243
1643
|
- **Investigations**: {N} completed, spawned {M} new issues
|
|
1244
|
-
- **Review findings**: {N} spawned, {M} resolved in-batch, {K} deferred
|
|
1245
|
-
- **Succeeded**: {N} issues resolved (implementation)
|
|
1644
|
+
- **Review findings**: {N} spawned, {M} resolved in-batch, {K} swept, {J} deferred (cosmetic), {L} deferred (gen2)
|
|
1645
|
+
- **Succeeded**: {N} issues resolved (implementation + sweep)
|
|
1246
1646
|
- **Failed**: {N} issues need attention
|
|
1247
1647
|
- **Skipped**: {N} issues (dependency failures)
|
|
1248
1648
|
|
|
@@ -1259,7 +1659,7 @@ Aggregate into the batch-level analytics for Step 6B.
|
|
|
1259
1659
|
|-------|-------|-------|--------|-------|---------|--------------|
|
|
1260
1660
|
{per-agent rows from audit}
|
|
1261
1661
|
|
|
1262
|
-
**
|
|
1662
|
+
**Batch efficiency**: {avg_idle_pct}% idle time
|
|
1263
1663
|
**Total resume cycles**: {sum_resumes} across {agent_count} agents
|
|
1264
1664
|
**Clean agents** (no stalls): {clean_count}/{agent_count}
|
|
1265
1665
|
|
|
@@ -1283,7 +1683,7 @@ Aggregate into the batch-level analytics for Step 6B.
|
|
|
1283
1683
|
**Systematic issues** (flag if detected):
|
|
1284
1684
|
- False positive rate > 30% → review agents may need tuning
|
|
1285
1685
|
- Invalidation rate < 10% or > 35% → investigation calibration off
|
|
1286
|
-
- Same-domain merge conflicts
|
|
1686
|
+
- Same-domain merge conflicts between concurrent agents → domain serialization edges too loose
|
|
1287
1687
|
- Contract divergences > 20% → investigation quality may need improvement
|
|
1288
1688
|
- **Idle% > 50%** → agents stalling at phase boundaries; check work-on routing loop
|
|
1289
1689
|
- **Avg resumes > 1** → orchestrator having to compensate for agent stops
|
|
@@ -1304,10 +1704,10 @@ Aggregate into the batch-level analytics for Step 6B.
|
|
|
1304
1704
|
4. **Always confirm with user before launching** — Step 3E is the mandatory checkpoint
|
|
1305
1705
|
5. **No retries** — if an agent fails, report it and move on
|
|
1306
1706
|
6. **Respect existing work** — skip issues already being worked on (`workflow:building`, `workflow:in-review`)
|
|
1307
|
-
7. **Dependency failures cascade** — if A fails and B depends on A,
|
|
1707
|
+
7. **Dependency failures cascade** — if A fails and B depends on A, all transitive dependents of A are skipped (not attempted)
|
|
1308
1708
|
8. **Always run post-batch cleanup** — Phase 5 is mandatory. Never skip `/cleanup all` after orchestration.
|
|
1309
1709
|
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.
|
|
1310
|
-
10. **
|
|
1710
|
+
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.
|
|
1311
1711
|
|
|
1312
1712
|
---
|
|
1313
1713
|
|
|
@@ -1315,31 +1715,38 @@ Aggregate into the batch-level analytics for Step 6B.
|
|
|
1315
1715
|
|
|
1316
1716
|
### "orchestrate milestone api-expansion"
|
|
1317
1717
|
→ Fetches all open issues in "API Expansion v1" milestone
|
|
1318
|
-
→
|
|
1319
|
-
→
|
|
1718
|
+
→ Builds DAG: #1526 → #1527 → #1528 → #1529 (chain), #1530 (no predecessors)
|
|
1719
|
+
→ Confirms with user, dispatches #1526 + #1530 immediately
|
|
1720
|
+
→ When #1526 completes, dispatches #1527 immediately (doesn't wait for #1530)
|
|
1721
|
+
→ Streaming continues until all issues reach terminal state
|
|
1320
1722
|
|
|
1321
1723
|
### "orchestrate #1533 #1250"
|
|
1322
1724
|
→ Fetches those 2 issues, no deps between them
|
|
1323
|
-
→
|
|
1725
|
+
→ DAG: both have empty predecessor sets
|
|
1726
|
+
→ Confirms, dispatches both simultaneously
|
|
1324
1727
|
|
|
1325
1728
|
### "orchestrate next 5"
|
|
1326
|
-
→ Gets top 5 priority issues,
|
|
1327
|
-
→
|
|
1729
|
+
→ Gets top 5 priority issues, builds DAG
|
|
1730
|
+
→ Confirms, dispatches all ready issues immediately
|
|
1731
|
+
→ Dependent issues dispatch as predecessors complete
|
|
1328
1732
|
|
|
1329
1733
|
### "orchestrate P0"
|
|
1330
1734
|
→ Gets all P0 issues, runs them all (presumably urgent)
|
|
1331
|
-
→
|
|
1735
|
+
→ DAG: likely all independent — dispatches simultaneously
|
|
1736
|
+
→ Confirms, executes
|
|
1332
1737
|
|
|
1333
1738
|
### "orchestrate fast-lane"
|
|
1334
1739
|
→ Gets all unmilestoned bugs/fixes
|
|
1335
|
-
→
|
|
1740
|
+
→ Builds DAG from file-conflict and domain-serialization edges
|
|
1741
|
+
→ Dispatches all ready issues, streams dependent issues as predecessors complete
|
|
1336
1742
|
|
|
1337
1743
|
### "orchestrate milestone user-auth-v2"
|
|
1338
1744
|
→ Fetches all open issues in "User Auth v2" milestone
|
|
1339
1745
|
→ Detects #42 is an investigation issue (title: "Investigate: session expiry race condition under high concurrency")
|
|
1340
|
-
→
|
|
1746
|
+
→ Phase 2: Runs `/work-on 42` — investigation creates 3 new issues (#55-#57)
|
|
1341
1747
|
→ Re-fetches milestone issues, now includes #55-#57
|
|
1342
|
-
→
|
|
1343
|
-
→
|
|
1344
|
-
→
|
|
1345
|
-
→
|
|
1748
|
+
→ Builds DAG: #38, #41, #55, #56 have no predecessors (ready); #39 depends on #38; #57 depends on #55
|
|
1749
|
+
→ Dispatches #38, #41, #55, #56 simultaneously
|
|
1750
|
+
→ When #38 completes, #39 dispatches immediately (doesn't wait for #41, #55, or #56)
|
|
1751
|
+
→ When #55 completes, #57 dispatches immediately (doesn't wait for others)
|
|
1752
|
+
→ Final report shows investigation spawned issues and streaming execution results
|