forgedock 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +21 -3
  2. package/bin/forgedock.mjs +1992 -540
  3. package/bin/github-app-manifest.json.license +2 -0
  4. package/bin/hooks/session-start.mjs +4 -5
  5. package/bin/registry.mjs +20 -0
  6. package/bin/tui.mjs +172 -96
  7. package/commands/adopt.md +433 -0
  8. package/commands/analytics.md +2 -0
  9. package/commands/audit-agents.md +2 -0
  10. package/commands/audit.md +2 -0
  11. package/commands/changelog.md +354 -0
  12. package/commands/cleanup.md +2 -0
  13. package/commands/compat-audit.md +330 -0
  14. package/commands/deploy-info.md +6 -5
  15. package/commands/diagnose.md +483 -0
  16. package/commands/explain.md +349 -0
  17. package/commands/forge-stats.md +2 -0
  18. package/commands/forgedock-init.md +2 -0
  19. package/commands/geo-audit.md +2 -0
  20. package/commands/incident-response.md +2 -0
  21. package/commands/issue.md +2 -0
  22. package/commands/milestone.md +2 -0
  23. package/commands/optimize.md +411 -0
  24. package/commands/orchestrate.md +118 -0
  25. package/commands/pipeline-health.md +60 -1
  26. package/commands/qa-sweep.md +2 -0
  27. package/commands/quality-gate.md +37 -0
  28. package/commands/replay.md +263 -0
  29. package/commands/resume.md +264 -0
  30. package/commands/review-pr-agents.md +5 -1
  31. package/commands/review-pr-staging.md +2 -0
  32. package/commands/review-pr.md +85 -3
  33. package/commands/rollback.md +2 -0
  34. package/commands/scope.md +280 -0
  35. package/commands/security-audit.md +2 -0
  36. package/commands/status.md +262 -0
  37. package/commands/sync-ecosystem.md +2 -0
  38. package/commands/validate.md +2 -0
  39. package/commands/work-on/build/architect.md +128 -18
  40. package/commands/work-on/build/context.md +130 -17
  41. package/commands/work-on/build/implement.md +2 -0
  42. package/commands/work-on/build/validate.md +2 -0
  43. package/commands/work-on/build.md +32 -7
  44. package/commands/work-on/close.md +85 -0
  45. package/commands/work-on/decompose.md +2 -0
  46. package/commands/work-on/investigate.md +30 -3
  47. package/commands/work-on/review.md +13 -1
  48. package/commands/work-on-monolithic.md +2 -0
  49. package/commands/work-on.md +624 -36
  50. package/docs/BRAND.md +51 -0
  51. package/docs/CONFIG.md +94 -0
  52. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
  53. package/docs/claude-breakpoints.json +47 -0
  54. package/docs/site/getting-started.md +21 -0
  55. package/package.json +3 -3
  56. package/scripts/classify-lane.sh +145 -0
  57. package/scripts/transition-label.sh +161 -0
  58. package/scripts/validate-pr-target.sh +30 -0
  59. package/templates/devdocs/README.md +72 -0
  60. package/templates/devdocs/agent/using-forgedock.md +3 -0
  61. package/templates/devdocs/agent/using-github.md +3 -0
  62. package/templates/devdocs/index.yaml +162 -0
  63. package/templates/devdocs/project/architecture.md +3 -0
  64. package/templates/devdocs/project/conventions.md +3 -0
  65. package/templates/devdocs/project/custom-instructions.md +3 -0
  66. package/templates/devdocs/project/glossary.md +3 -0
  67. package/templates/devdocs/project/stack.md +3 -0
  68. package/templates/devdocs/templates/commit-convention.md +3 -0
  69. package/templates/devdocs/templates/issue.md +3 -0
  70. package/templates/devdocs/templates/pull-request.md +3 -0
  71. package/templates/devdocs/templates/review-finding.md +3 -0
@@ -0,0 +1,411 @@
1
+ ---
2
+ description: Analyze this repo and generate per-repo adaptive scripts from learned patterns, git history, and existing configuration
3
+ argument-hint: [--dry-run | --force | --no-registry]
4
+ ---
5
+
6
+ # /optimize — Per-Repo Adaptive Script Generation
7
+
8
+ **Input**: $ARGUMENTS
9
+
10
+ Proactively analyze the current repository and generate `.forgedock/scripts/` entries from `forge.yaml → learned:`, git commit history, and pipeline annotations — without waiting for a correction event. This is the proactive half of the learning loop; reactive capture happens in `/work-on` Phase 1D.
11
+
12
+ **Agent model policy**: Default `model: "sonnet"`. Fallback: `model: "opus"` if Sonnet is rate-limited.
13
+ **NEVER use plan mode (EnterPlanMode).**
14
+
15
+ ---
16
+
17
+ ## Argument Parsing
18
+
19
+ | Flag | Effect |
20
+ |------|--------|
21
+ | (none) | Full run: analyze → generate → registry → report |
22
+ | `--dry-run` | Analyze and report what would be generated; write nothing to disk |
23
+ | `--force` | Overwrite scripts even if confidence < 0.7 |
24
+ | `--no-registry` | Skip `registry.json` update |
25
+
26
+ Parse `$ARGUMENTS` and set:
27
+ ```
28
+ DRY_RUN = true if --dry-run present, else false
29
+ FORCE = true if --force present, else false
30
+ NO_REG = true if --no-registry present, else false
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Prerequisites
36
+
37
+ ### P1: Read forge.yaml
38
+
39
+ ```bash
40
+ REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
41
+ FORGE_YAML="$REPO_PATH/forge.yaml"
42
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$FORGE_YAML" 2>/dev/null)
43
+ SCRIPTS_DIR="$REPO_PATH/.forgedock/scripts"
44
+ ADAPTIVE_ENABLED=$(yq '.adaptive_scripts.enabled // true' "$FORGE_YAML" 2>/dev/null || echo "true")
45
+ ADAPTIVE_COMMIT=$(yq '.adaptive_scripts.commit // false' "$FORGE_YAML" 2>/dev/null || echo "false")
46
+ ```
47
+
48
+ If `$FORGE_YAML` is missing:
49
+ ```
50
+ STOP: forge.yaml not found. Run `npx forgedock init` to generate it, then re-run /optimize.
51
+ ```
52
+
53
+ If `ADAPTIVE_ENABLED` is `false`:
54
+ ```
55
+ STOP: adaptive_scripts.enabled is false in forge.yaml. Set it to true to use /optimize.
56
+ ```
57
+
58
+ ### P2: Ensure scripts directory exists
59
+
60
+ ```bash
61
+ mkdir -p "$SCRIPTS_DIR"
62
+ ```
63
+
64
+ If `.forgedock/scripts/` is not gitignored, note in the report (non-blocking).
65
+
66
+ ---
67
+
68
+ ## Phase 1: Pattern Analysis
69
+
70
+ Read all available signal sources. Build a `PATTERNS` map — keyed by script name, value is `{evidence, confidence, value}`.
71
+
72
+ **Confidence scale**:
73
+ - `1.0` — explicit value in `forge.yaml → learned:` (authoritative)
74
+ - `0.85` — value confirmed by 3+ independent signals (git history, FORGE:LEARNED annotations)
75
+ - `0.7` — value confirmed by 2 independent signals
76
+ - `0.5` — single signal (heuristic only)
77
+ - `<0.5` — insufficient evidence; skip unless `--force`
78
+
79
+ ### 1A: Read forge.yaml → learned: section
80
+
81
+ ```bash
82
+ LEARNED=$(yq '.learned // {}' "$FORGE_YAML" 2>/dev/null || echo '{}')
83
+ echo "Learned section: $LEARNED"
84
+ ```
85
+
86
+ Extract each known key:
87
+ ```bash
88
+ LEARNED_STAGING=$(yq '.learned.branch_targets.staging // ""' "$FORGE_YAML" 2>/dev/null)
89
+ LEARNED_TEST_CMDS=$(yq '.learned.test_commands // []' "$FORGE_YAML" -o json 2>/dev/null)
90
+ LEARNED_TEST_LOCS=$(yq '.learned.test_locations // []' "$FORGE_YAML" -o json 2>/dev/null)
91
+ LEARNED_LABEL_MAP=$(yq '.learned.label_map // {}' "$FORGE_YAML" -o json 2>/dev/null)
92
+ LEARNED_COMMIT_STYLE=$(yq '.learned.commit_style // ""' "$FORGE_YAML" 2>/dev/null)
93
+ ```
94
+
95
+ For each non-empty learned key, add to PATTERNS with confidence `1.0`:
96
+
97
+ | Non-empty key | Script to generate |
98
+ |---------------|--------------------|
99
+ | `branch_targets.staging` | `branch-targets.sh` |
100
+ | `test_commands` (non-empty array) | `run-tests.sh` |
101
+ | `test_locations` (non-empty array) | `find-tests.sh` |
102
+ | `label_map` (non-empty object) | `label-map.sh` |
103
+ | `commit_style` | `format-commit.sh` |
104
+
105
+ ### 1B: Analyze git commit history
106
+
107
+ ```bash
108
+ cd "$REPO_PATH"
109
+
110
+ # Sample last 50 commits for style analysis
111
+ COMMIT_SUBJECTS=$(git log --oneline -50 --format="%s" 2>/dev/null | head -50)
112
+ echo "$COMMIT_SUBJECTS"
113
+ ```
114
+
115
+ **Detect commit style**:
116
+ ```bash
117
+ CONV_COUNT=$(echo "$COMMIT_SUBJECTS" | grep -cE '^(feat|fix|docs|chore|refactor|test|style|ci|build|perf)\(' 2>/dev/null || echo 0)
118
+ CONV_SIMPLE=$(echo "$COMMIT_SUBJECTS" | grep -cE '^(feat|fix|docs|chore|refactor|test|style|ci|build|perf):' 2>/dev/null || echo 0)
119
+ TOTAL=$(echo "$COMMIT_SUBJECTS" | wc -l | tr -d ' ')
120
+
121
+ CONV_WITH_SCOPE_PCT=$(( (CONV_COUNT * 100) / (TOTAL + 1) ))
122
+ CONV_SIMPLE_PCT=$(( (CONV_SIMPLE * 100) / (TOTAL + 1) ))
123
+ ```
124
+
125
+ Determine `DETECTED_COMMIT_STYLE`:
126
+ - If `CONV_WITH_SCOPE_PCT >= 60` → `"conventional-with-scope"`, confidence `0.85`
127
+ - If `CONV_SIMPLE_PCT >= 60` → `"conventional"`, confidence `0.85`
128
+ - If `(CONV_COUNT + CONV_SIMPLE) < 20%` → `"plain"`, confidence `0.7`
129
+ - Otherwise → insufficient signal, skip `format-commit.sh` unless already in `learned:`
130
+
131
+ **If `LEARNED_COMMIT_STYLE` is non-empty**: use it (confidence `1.0`), skip git analysis for this signal.
132
+
133
+ **Detect test locations** (if `LEARNED_TEST_LOCS` is empty):
134
+ ```bash
135
+ # Look for test directories with actual test files
136
+ TEST_DIRS=""
137
+ for candidate in tests/ __tests__/ test/ spec/ src/__tests__/; do
138
+ COUNT=$(git ls-files "$candidate" 2>/dev/null | wc -l | tr -d ' ')
139
+ if [ "$COUNT" -gt "0" ]; then
140
+ TEST_DIRS="$TEST_DIRS $candidate"
141
+ fi
142
+ done
143
+ TEST_DIRS=$(echo "$TEST_DIRS" | xargs) # trim
144
+ ```
145
+
146
+ If `TEST_DIRS` is non-empty and differs from the default (`tests/`): add to PATTERNS for `find-tests.sh` with confidence `0.7`.
147
+
148
+ ### 1C: Mine FORGE:LEARNED annotations from closed issues
149
+
150
+ ```bash
151
+ # Read closed issues for repeated FORGE:LEARNED annotations (last 30 closed issues)
152
+ FORGE_LEARNED_BODIES=$(gh api "repos/$GH_REPO/issues?state=closed&per_page=30" \
153
+ --jq '[.[] | .number]' 2>/dev/null | jq -r '.[]' 2>/dev/null | \
154
+ while read -r n; do
155
+ gh api "repos/$GH_REPO/issues/$n/comments" \
156
+ --jq '.[] | select(.body | contains("FORGE:LEARNED")) | .body' 2>/dev/null
157
+ done | head -200)
158
+ echo "$FORGE_LEARNED_BODIES" | head -50
159
+ ```
160
+
161
+ **Extract corroborating signals from annotations**:
162
+ - Count occurrences of each staging branch override, test command, label remap, commit style across annotations
163
+ - Each repeated annotation (≥2 occurrences) raises confidence by `+0.15` for the matching pattern
164
+ - Cap confidence at `1.0`
165
+
166
+ **Practical limit**: Process at most 30 issues. If `gh` rate-limits, skip 1C and note in report.
167
+
168
+ ---
169
+
170
+ ## Phase 2: Script Generation
171
+
172
+ For each pattern in PATTERNS with `confidence >= 0.7` (or all if `--force`):
173
+
174
+ **Overwrite mode**: if a script already exists, read it first, then overwrite — never append. Log "updated" vs "created" in the report.
175
+
176
+ ### 2A: branch-targets.sh
177
+
178
+ Trigger: `branch_targets.staging` set in `learned:` (confidence `1.0`), OR git history shows consistent use of a non-default staging branch.
179
+
180
+ ```bash
181
+ cat > "$SCRIPTS_DIR/branch-targets.sh" << 'SCRIPT_EOF'
182
+ #!/usr/bin/env bash
183
+ # Auto-generated by /optimize — branch target overrides for this repo.
184
+ # Source: forge.yaml → learned.branch_targets
185
+ # Edit manually if your branch structure changes.
186
+
187
+ # STAGING_BRANCH: override the default staging branch for fast-lane PRs
188
+ echo "STAGING_BRANCH={LEARNED_STAGING_VALUE}"
189
+ SCRIPT_EOF
190
+ chmod +x "$SCRIPTS_DIR/branch-targets.sh"
191
+ ```
192
+
193
+ Replace `{LEARNED_STAGING_VALUE}` with the actual value from `learned.branch_targets.staging`.
194
+
195
+ ### 2B: run-tests.sh
196
+
197
+ Trigger: `test_commands` array non-empty in `learned:` (confidence `1.0`), OR FORGE:LEARNED annotations contain repeated test command corrections.
198
+
199
+ ```bash
200
+ cat > "$SCRIPTS_DIR/run-tests.sh" << 'SCRIPT_EOF'
201
+ #!/usr/bin/env bash
202
+ # Auto-generated by /optimize — test command sequence for this repo.
203
+ # Source: forge.yaml → learned.test_commands
204
+ # Edit manually if your test setup changes.
205
+
206
+ set -e
207
+
208
+ {LEARNED_TEST_COMMANDS_EXPANDED}
209
+ SCRIPT_EOF
210
+ chmod +x "$SCRIPTS_DIR/run-tests.sh"
211
+ ```
212
+
213
+ Expand `{LEARNED_TEST_COMMANDS_EXPANDED}` to one command per line, in array order.
214
+
215
+ ### 2C: find-tests.sh
216
+
217
+ Trigger: `test_locations` array non-empty in `learned:` (confidence `1.0`), OR git ls-files shows non-default test directories (confidence `0.7`).
218
+
219
+ ```bash
220
+ cat > "$SCRIPTS_DIR/find-tests.sh" << 'SCRIPT_EOF'
221
+ #!/usr/bin/env bash
222
+ # Auto-generated by /optimize — test file discovery for this repo.
223
+ # Source: forge.yaml → learned.test_locations
224
+ # Returns a list of test files matching this repo's actual test directory layout.
225
+
226
+ set -e
227
+
228
+ {FIND_COMMANDS_EXPANDED}
229
+ SCRIPT_EOF
230
+ chmod +x "$SCRIPTS_DIR/find-tests.sh"
231
+ ```
232
+
233
+ Expand `{FIND_COMMANDS_EXPANDED}` to `find {dir} -name "*.test.*" -o -name "*.spec.*" 2>/dev/null` for each location, plus glob patterns if specified. Patterns ending in `*` are expanded with `git ls-files`.
234
+
235
+ ### 2D: label-map.sh
236
+
237
+ Trigger: `label_map` non-empty in `learned:` (confidence `1.0`).
238
+
239
+ ```bash
240
+ cat > "$SCRIPTS_DIR/label-map.sh" << 'SCRIPT_EOF'
241
+ #!/usr/bin/env bash
242
+ # Auto-generated by /optimize — label name remapping for this repo.
243
+ # Source: forge.yaml → learned.label_map
244
+ # Usage: source this file, then use map_label() to resolve canonical → repo label.
245
+
246
+ # Associative array: canonical ForgeDock label → repo-specific label
247
+ declare -A LABEL_MAP=(
248
+ {LABEL_MAP_ENTRIES}
249
+ )
250
+
251
+ map_label() {
252
+ local canonical="$1"
253
+ echo "${LABEL_MAP[$canonical]:-$canonical}"
254
+ }
255
+ SCRIPT_EOF
256
+ chmod +x "$SCRIPTS_DIR/label-map.sh"
257
+ ```
258
+
259
+ Expand `{LABEL_MAP_ENTRIES}` to `["canonical"]="repo-specific"` pairs.
260
+
261
+ ### 2E: format-commit.sh
262
+
263
+ Trigger: `commit_style` set in `learned:` (confidence `1.0`), OR git history analysis produces `confidence >= 0.7`.
264
+
265
+ ```bash
266
+ cat > "$SCRIPTS_DIR/format-commit.sh" << 'SCRIPT_EOF'
267
+ #!/usr/bin/env bash
268
+ # Auto-generated by /optimize — commit message style enforcer for this repo.
269
+ # Source: forge.yaml → learned.commit_style (or git history analysis)
270
+ # Usage: ./format-commit.sh "type" "scope" "description" "issue_number"
271
+ # type: conventional prefix (feat, fix, refactor, docs, chore, etc.)
272
+ # scope: component or area being changed (e.g. auth, pipeline, commands)
273
+ # description: short imperative summary of the change
274
+ # issue_number: GitHub issue number (without #)
275
+ # Outputs: the correctly-formatted commit message for this repo.
276
+
277
+ set -e
278
+
279
+ STYLE="{COMMIT_STYLE}"
280
+ TYPE="${1:-}"
281
+ SCOPE="${2:-}"
282
+ DESCRIPTION="${3:-}"
283
+ ISSUE="${4:-}"
284
+
285
+ case "$STYLE" in
286
+ conventional-with-scope)
287
+ if [ -n "$SCOPE" ]; then
288
+ echo "${TYPE}(${SCOPE}): ${DESCRIPTION} (#${ISSUE})"
289
+ else
290
+ echo "${TYPE:-chore}: ${DESCRIPTION} (#${ISSUE})"
291
+ fi
292
+ ;;
293
+ conventional)
294
+ echo "${TYPE:-chore}: ${DESCRIPTION} (#${ISSUE})"
295
+ ;;
296
+ plain)
297
+ echo "${DESCRIPTION} (#${ISSUE})"
298
+ ;;
299
+ *)
300
+ echo "${DESCRIPTION}"
301
+ ;;
302
+ esac
303
+ SCRIPT_EOF
304
+ chmod +x "$SCRIPTS_DIR/format-commit.sh"
305
+ ```
306
+
307
+ Replace `{COMMIT_STYLE}` with the detected or learned value.
308
+
309
+ ---
310
+
311
+ ## Phase 3: Registry Update
312
+
313
+ Skip if `--no-registry`.
314
+
315
+ Write (overwrite) `.forgedock/scripts/registry.json`:
316
+
317
+ ```bash
318
+ GENERATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
319
+
320
+ cat > "$SCRIPTS_DIR/registry.json" << JSON_EOF
321
+ {
322
+ "schema_version": 1,
323
+ "generated_at": "${GENERATED_AT}",
324
+ "generated_by": "/optimize",
325
+ "scripts": {
326
+ {REGISTRY_ENTRIES}
327
+ }
328
+ }
329
+ JSON_EOF
330
+ ```
331
+
332
+ Build `{REGISTRY_ENTRIES}` from the PATTERNS map. For each generated script:
333
+ ```json
334
+ "branch-targets": {
335
+ "path": "branch-targets.sh",
336
+ "confidence": 1.0,
337
+ "evidence": "forge.yaml → learned.branch_targets.staging",
338
+ "generated_at": "2026-03-15T10:30:00Z"
339
+ }
340
+ ```
341
+
342
+ Comma-separate all entries. If `--dry-run`: print the registry JSON to stdout instead of writing it.
343
+
344
+ ---
345
+
346
+ ## Phase 4: Report
347
+
348
+ Output a human-readable summary. Do NOT commit anything — `adaptive_scripts.commit` is respected:
349
+
350
+ ```bash
351
+ echo ""
352
+ echo "=== /optimize Report ==="
353
+ echo "Run at: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
354
+ echo "Repo: $GH_REPO"
355
+ echo "Scripts dir: $SCRIPTS_DIR"
356
+ if [ "$DRY_RUN" = "true" ]; then
357
+ echo "Mode: DRY RUN — no files written"
358
+ fi
359
+ echo ""
360
+ echo "--- Patterns detected ---"
361
+ ```
362
+
363
+ For each pattern in PATTERNS (sorted by confidence descending):
364
+ ```
365
+ {script_name}.sh confidence={confidence} source={evidence} action={created|updated|skipped|would-create}
366
+ ```
367
+
368
+ For patterns with `confidence < 0.7` that were skipped:
369
+ ```
370
+ {script_name}.sh SKIPPED (confidence={confidence} < 0.7 — run with --force to generate)
371
+ ```
372
+
373
+ Then:
374
+ ```
375
+ --- Summary ---
376
+ Scripts created: N
377
+ Scripts updated: N
378
+ Scripts skipped: N
379
+ Registry: {written|skipped}
380
+ Commit: NOT done (adaptive_scripts.commit=false — review scripts before committing)
381
+
382
+ --- Next steps ---
383
+ 1. Review generated scripts in .forgedock/scripts/
384
+ 2. Run each script manually to verify output
385
+ 3. If correct: git add .forgedock/scripts/ && git commit -m "feat(scripts): adaptive scripts from /optimize"
386
+ 4. If not: edit scripts directly or update forge.yaml → learned: and re-run /optimize
387
+ ```
388
+
389
+ If no patterns were detected at all:
390
+ ```
391
+ No patterns detected with sufficient confidence.
392
+
393
+ Possible reasons:
394
+ - forge.yaml → learned: is empty (no corrections captured yet)
395
+ - Git history is too short (< 10 commits) to detect commit style
396
+ - Test directories match the default (tests/) — no override needed
397
+
398
+ Run /work-on on a few issues to let the pipeline capture corrections, then re-run /optimize.
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Idempotency Guarantee
404
+
405
+ Running `/optimize` twice produces the same output. Specifically:
406
+ - Scripts are always overwritten, never appended to
407
+ - `registry.json` is always overwritten with current analysis results
408
+ - Generated timestamps in scripts and registry will update on each run (this is expected)
409
+ - Content of scripts changes only if the underlying patterns change
410
+
411
+ To verify: run `/optimize` twice and compare outputs — the only differences should be the `generated_at` timestamps.
@@ -2,6 +2,8 @@
2
2
  description: Orchestrate parallel work on multiple issues or an entire milestone — spawns sub-agents that each run the full /work-on pipeline
3
3
  argument-hint: [milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P0]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /orchestrate — Multi-Issue Parallel Orchestrator
7
9
 
@@ -550,6 +552,38 @@ If `UNEXPECTED_GROWTH > 500`, report the alert clearly before launching the next
550
552
 
551
553
  ---
552
554
 
555
+ ### Step 4A.pre: Classify lane for each issue (MANDATORY before spawning agents)
556
+
557
+ Before building agent prompts, run `classify-lane.sh` for every issue in the current wave to compute `{LANE}` and `{PR_BASE}` deterministically. The script output is authoritative — the LLM MUST NOT override or reason around it.
558
+
559
+ ```bash
560
+ # Requires classify-lane.sh to be available at ~/.claude/scripts/classify-lane.sh
561
+ # (installed by `npx forgedock` — see bin/forgedock.mjs linkScripts step)
562
+ # Fallback: bash "$FORGE_HOME/scripts/classify-lane.sh" if ~/.claude/scripts/ is unavailable
563
+
564
+ declare -A ISSUE_LANE
565
+ declare -A ISSUE_PR_BASE
566
+
567
+ for NUM in {wave_issue_numbers}; do
568
+ PR_BASE=$(bash ~/.claude/scripts/classify-lane.sh "$NUM" -R {GH_REPO}) || {
569
+ echo "ERROR: classify-lane.sh failed for #$NUM — adding needs-human label and skipping" >&2
570
+ gh issue edit "$NUM" -R {GH_REPO} --add-label "needs-human" 2>/dev/null || true
571
+ continue
572
+ }
573
+ # Derive LANE label from PR_BASE
574
+ if [ "$PR_BASE" = "staging" ]; then
575
+ LANE="fast-lane"
576
+ else
577
+ LANE="feature-lane"
578
+ fi
579
+ ISSUE_LANE[$NUM]="$LANE"
580
+ ISSUE_PR_BASE[$NUM]="$PR_BASE"
581
+ echo "#$NUM → lane=$LANE, PR_BASE=$PR_BASE"
582
+ done
583
+ ```
584
+
585
+ 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 -->
586
+
553
587
  ### Step 4A: Launch a wave
554
588
 
555
589
  **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.**
@@ -694,6 +728,90 @@ You will be automatically notified when each background agent completes. **Do NO
694
728
  - Reading agent JSONL output files to check progress — use GitHub labels as the source of truth
695
729
  - Polling the same status check repeatedly on a timer
696
730
 
731
+ ### Step 4B.5: Time-Based Stall Detection
732
+
733
+ **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.
734
+
735
+ **When to run** (NOT a sleep loop — two trigger points only):
736
+ 1. On every background agent completion event (run BEFORE the terminal-state check in Step 4B)
737
+ 2. Before posting any "waiting for wave..." status update to the user
738
+
739
+ **Do NOT poll on a timer. Do NOT use sleep. Run at these two trigger points only.**
740
+
741
+ **Read stall timeout from config**:
742
+ ```bash
743
+ STALL_TIMEOUT=$(yq '.pipeline.stall_timeout_minutes // 15' forge.yaml 2>/dev/null || echo 15)
744
+ ```
745
+
746
+ **For each non-terminal agent in the current wave**:
747
+ ```bash
748
+ for NUM in {active_wave_issue_numbers}; do
749
+ # Skip issues already in terminal state
750
+ TERMINAL=$(gh issue view $NUM -R {GH_REPO} --json labels \
751
+ --jq '[.labels[].name | select(. == "workflow:merged" or . == "workflow:invalid" or . == "needs-human")] | length')
752
+ [ "$TERMINAL" -gt 0 ] && continue
753
+
754
+ # Get last activity timestamp — prefer last comment (catches FORGE:HEARTBEAT updates)
755
+ LAST_ACTIVITY=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
756
+ --jq '.[-1].updated_at // empty' 2>/dev/null)
757
+ # Fall back to issue updated_at if no comments
758
+ if [ -z "$LAST_ACTIVITY" ]; then
759
+ LAST_ACTIVITY=$(gh issue view $NUM -R {GH_REPO} --json updatedAt --jq '.updatedAt')
760
+ fi
761
+
762
+ # Compute elapsed minutes (GNU date — adjust for macOS: date -j -f "%Y-%m-%dT%H:%M:%SZ")
763
+ LAST_EPOCH=$(date -d "$LAST_ACTIVITY" +%s 2>/dev/null \
764
+ || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$LAST_ACTIVITY" +%s 2>/dev/null)
765
+ NOW_EPOCH=$(date +%s)
766
+ ELAPSED_MIN=$(( (NOW_EPOCH - LAST_EPOCH) / 60 ))
767
+
768
+ if [ "$ELAPSED_MIN" -gt "$STALL_TIMEOUT" ]; then
769
+ # Count prior stall events on this issue
770
+ STALL_COUNT=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
771
+ --jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length')
772
+
773
+ CURRENT_STATE=$(gh issue view $NUM -R {GH_REPO} --json labels \
774
+ --jq '[.labels[].name | select(startswith("workflow:"))] | .[0] // "unknown"')
775
+
776
+ if [ "$STALL_COUNT" -lt 2 ]; then
777
+ # Auto-resume: post stall annotation and re-invoke /work-on
778
+ RESUME_ATTEMPT=$(( STALL_COUNT + 1 ))
779
+ gh issue comment $NUM -R {GH_REPO} --body "<!-- FORGE:STALL_DETECTED -->
780
+ ## Stall Detected
781
+
782
+ **Issue**: #${NUM}
783
+ **Elapsed since last activity**: ${ELAPSED_MIN} min (threshold: ${STALL_TIMEOUT} min)
784
+ **Current workflow state**: ${CURRENT_STATE}
785
+ **Auto-resume attempt**: ${RESUME_ATTEMPT} of 2
786
+ **Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
787
+
788
+ # Resume the agent — collect all resumes and launch in a single message (see Step 4B rule)
789
+ # STALL_RESUME_LIST is accumulated and launched in parallel after the loop
790
+ STALL_RESUME_LIST="$STALL_RESUME_LIST $NUM"
791
+ else
792
+ # 2+ prior stalls — auto-resume exhausted, escalate to needs-human
793
+ gh issue edit $NUM -R {GH_REPO} --add-label "needs-human"
794
+ gh issue comment $NUM -R {GH_REPO} --body "<!-- FORGE:STALL_DETECTED -->
795
+ ## Stall Escalated — Needs Human Intervention
796
+
797
+ Issue #${NUM} has been auto-resumed ${STALL_COUNT} times without reaching a terminal state. Auto-resume limit (2) exhausted. Manual intervention required.
798
+
799
+ **Last workflow state**: ${CURRENT_STATE}
800
+ **Total elapsed since last activity**: ${ELAPSED_MIN} min
801
+ **Timestamp**: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
802
+ echo "STALL ESCALATED: #{NUM} → needs-human (${STALL_COUNT} prior resumes)"
803
+ fi
804
+ fi
805
+ done
806
+
807
+ # Launch all stall resumes in parallel (single message — same rule as Step 4B)
808
+ # For each NUM in $STALL_RESUME_LIST, call Agent(resume="{AGENT_ID}", run_in_background=true, ...)
809
+ ```
810
+
811
+ **Resume all stalled agents in a single message** (parallel). Use the same `Agent(resume=...)` pattern as Step 4B — do not wait between individual resumes.
812
+
813
+ **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.
814
+
697
815
  ### Step 4C: Collect review-finding issues from completed wave
698
816
 
699
817
  When ALL agents in the current wave have completed, check if any `/work-on` runs spawned review-finding issues during their review phase. These are new work items that should be folded into the remaining waves.
@@ -2,6 +2,8 @@
2
2
  description: Self-analysis — measures pipeline performance, correlates with prompt changes, proposes improvements
3
3
  argument-hint: [project repo slug or "all"]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /pipeline-health — Forge Self-Analysis
7
9
 
@@ -541,6 +543,57 @@ fi
541
543
 
542
544
  **Why this matters**: Outcome metrics (findings per PR, fix-up rate) measure what the pipeline produces. Orchestration efficiency measures how much wasted motion occurs getting there. An agent that takes 40 minutes to do 8 minutes of actual work has 80% idle time — all of which is pipeline overhead. Reducing stall time directly improves throughput without changing code quality. This is the only metric that reveals whether the routing loop in `work-on.md` is functioning or causing agents to stop and wait.
543
545
 
546
+ **Step 4 — Batch stall rate (from FORGE:STALL_DETECTED annotations)**:
547
+
548
+ Query issues that received a `<!-- FORGE:STALL_DETECTED -->` comment within the analysis window. This measures how often the orchestrator's time-based stall detector fired — distinct from the resume-cycle count (which comes from completion-event stalls).
549
+
550
+ ```bash
551
+ # Denominator: issues that entered the pipeline in the window (have any workflow:* label)
552
+ # Using pipeline-active issues avoids inflating the denominator with all 500 issues in window
553
+ BATCH_TOTAL=$(gh issue list -R $GH_REPO \
554
+ --state all --limit 500 \
555
+ --json number,createdAt,labels \
556
+ --jq "[.[] | select(.createdAt > \"$SINCE\") | select(.labels | map(.name) | any(startswith(\"workflow:\")))] | length" \
557
+ 2>/dev/null || echo 0)
558
+
559
+ # Enumerate pipeline-active issue numbers for per-issue comment queries
560
+ PIPELINE_ISSUE_NUMS=$(gh issue list -R $GH_REPO \
561
+ --state all --limit 500 \
562
+ --json number,createdAt,labels \
563
+ --jq "[.[] | select(.createdAt > \"$SINCE\") | select(.labels | map(.name) | any(startswith(\"workflow:\"))) | .number]" \
564
+ 2>/dev/null || echo "[]")
565
+
566
+ # Count issues with at least one FORGE:STALL_DETECTED comment
567
+ # Use wc -l pattern (not variable accumulation) to avoid bash subshell scope loss
568
+ STALL_COUNT=$(echo "$PIPELINE_ISSUE_NUMS" | jq -r '.[]' | while read -r NUM; do
569
+ STALL_COMMENTS=$(gh api repos/$GH_REPO/issues/${NUM}/comments \
570
+ --jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length' 2>/dev/null || echo 0)
571
+ [ "$STALL_COMMENTS" -gt 0 ] && echo "$NUM"
572
+ done | wc -l)
573
+
574
+ # Count issues that escalated (2+ stall comments = auto-resume exhausted)
575
+ ESCALATED_COUNT=$(echo "$PIPELINE_ISSUE_NUMS" | jq -r '.[]' | while read -r NUM; do
576
+ STALL_COMMENTS=$(gh api repos/$GH_REPO/issues/${NUM}/comments \
577
+ --jq '[.[] | select(.body | contains("FORGE:STALL_DETECTED"))] | length' 2>/dev/null || echo 0)
578
+ [ "$STALL_COMMENTS" -ge 2 ] && echo "$NUM"
579
+ done | wc -l)
580
+
581
+ # Compute batch stall rate
582
+ if [ "$BATCH_TOTAL" -gt 0 ]; then
583
+ BATCH_STALL_RATE=$(echo "scale=1; $STALL_COUNT * 100 / $BATCH_TOTAL" | bc 2>/dev/null || echo "N/A")
584
+ echo "Batch stall rate: ${BATCH_STALL_RATE}% (${STALL_COUNT}/${BATCH_TOTAL} pipeline issues stalled) — target: < 10%"
585
+ echo "Escalated to needs-human: ${ESCALATED_COUNT} issues"
586
+ else
587
+ BATCH_STALL_RATE="N/A"
588
+ echo "Batch stall rate: N/A (no pipeline issues in window or FORGE:STALL_DETECTED not yet deployed)"
589
+ fi
590
+ export BATCH_STALL_RATE STALL_COUNT BATCH_TOTAL ESCALATED_COUNT
591
+ ```
592
+
593
+ **Target**: `BATCH_STALL_RATE` < 10% (stalled issues / total issues in window). A rate above 10% indicates systemic pipeline unreliability — agents stopping without reaching terminal state frequently enough to require orchestrator intervention.
594
+
595
+ **Note**: `BATCH_STALL_RATE` requires `FORGE:STALL_DETECTED` comments, which are written by the stall detector in `/orchestrate` Step 4B.5. If the stall detector is not deployed, this metric returns N/A and falls back to the resume-cycle proxy from Step 3.
596
+
544
597
  ### 2L: Post-deploy failure metrics (pipeline escape signal)
545
598
 
546
599
  `/audit` creates `audit-finding` labeled issues in the Forge repo whenever a defect escapes the pipeline and reaches staging or production. Each finding is tagged with a `**Failure point**:` field indicating which pipeline phase failed (INVESTIGATION, IMPLEMENTATION, REVIEW, REVIEW_FALSE_NEG, DEPLOY_GATE, etc.). Aggregating these reveals the post-deploy defect rate and review escape rate — signals invisible to the pre-merge metrics above.
@@ -2100,6 +2153,8 @@ gh issue create -R $FORGE_REPO \
2100
2153
  | — Avg agent idle% | ?% | < 30% | ✅/⚠️/❌ |
2101
2154
  | — Avg resume cycles per agent | ? | < 1 | ✅/⚠️/❌ |
2102
2155
  | — Clean agent rate | ?% | > 60% | ✅/⚠️/❌ |
2156
+ | — Batch stall rate | ?% (?/? issues) | < 10% | ✅/⚠️/❌ |
2157
+ | — Escalated to needs-human | ? issues | 0 | ✅/⚠️/❌ |
2103
2158
  | Original work ratio | ?% | > 70% | ✅/⚠️/❌ |
2104
2159
  | Milestone merge success | ?% | 80%+ | ✅/⚠️/❌ |
2105
2160
  | Issue close velocity | ? days | < 2 days | ✅/⚠️/❌ |
@@ -2123,13 +2178,15 @@ _If no trajectory data available in window: "No FORGE:TRAJECTORY comments found
2123
2178
 
2124
2179
  ## Orchestration Efficiency
2125
2180
 
2126
- _From Phase 2K — persisted FORGE:AUDIT-AGENTS summaries ([N] sessions, [M] agents total)_
2181
+ _From Phase 2K — persisted FORGE:AUDIT-AGENTS summaries ([N] sessions, [M] agents total) + FORGE:STALL_DETECTED annotations_
2127
2182
 
2128
2183
  | Metric | Value | Target | Status |
2129
2184
  |--------|-------|--------|--------|
2130
2185
  | Avg agent idle% | ?% | < 30% | ✅/⚠️/❌ |
2131
2186
  | Avg resume cycles per agent | ? | < 1 | ✅/⚠️/❌ |
2132
2187
  | Clean agent rate | ?% | > 60% | ✅/⚠️/❌ |
2188
+ | Batch stall rate | ?% (?/? issues) | < 10% | ✅/⚠️/❌ |
2189
+ | Escalated to needs-human | ? issues | 0 | ✅/⚠️/❌ |
2133
2190
 
2134
2191
  **Top stall boundaries** (phase transitions causing the most stalls):
2135
2192
  | Boundary | Occurrences |
@@ -2140,6 +2197,8 @@ _From Phase 2K — persisted FORGE:AUDIT-AGENTS summaries ([N] sessions, [M] age
2140
2197
 
2141
2198
  _If no FORGE:AUDIT-AGENTS data available: "No persisted audit-agents summaries found in window. Run \`audit-agents --persist\` after orchestration runs to enable this metric. Proxy: [N] issues had resume/stall anomalies in trajectory data."_
2142
2199
 
2200
+ _Batch stall rate: sourced from \`FORGE:STALL_DETECTED\` comments (requires \`/orchestrate\` Step 4B.5 to be active). N/A if stall detector is not deployed._
2201
+
2143
2202
  ## Transcript Flow Metrics
2144
2203
 
2145
2204
  _From Phase 2N — JSONL transcript analytics ([N] pipeline sessions, [M] subagent sessions in window)_
@@ -2,6 +2,8 @@
2
2
  description: Full platform QA sweep — auto-discovers every page, tests every UI element via browser automation, creates GitHub issues for all findings
3
3
  argument-hint: [all | dashboard | marketing | page <route> | journey | visual | functional | a11y]
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /qa-sweep — Platform-Wide QA Testing
7
9