forgedock 1.1.1 → 1.1.3

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 (174) hide show
  1. package/README.md +53 -8
  2. package/bin/batch-runner.mjs +339 -0
  3. package/bin/demo.mjs +92 -7
  4. package/bin/engine/phases.mjs +24 -2
  5. package/bin/engine/state.mjs +8 -3
  6. package/bin/engine-cli.mjs +177 -5
  7. package/bin/forgedock.mjs +409 -26
  8. package/bin/hooks/interactive-engine.mjs +509 -0
  9. package/bin/hooks/pre-tool-use.mjs +498 -0
  10. package/bin/hooks/subagent-stop-enforce.mjs +197 -0
  11. package/bin/journey.mjs +72 -14
  12. package/bin/registry.mjs +139 -0
  13. package/bin/report.mjs +447 -0
  14. package/bin/runner.mjs +70 -20
  15. package/bin/settings-hook.mjs +235 -1
  16. package/commands/adopt.md +4 -1
  17. package/commands/analytics.md +4 -1
  18. package/commands/audit-agents.md +110 -27
  19. package/commands/audit.md +20 -1
  20. package/commands/autopilot.md +518 -361
  21. package/commands/changelog.md +2 -1
  22. package/commands/ci-audit.md +2 -1
  23. package/commands/cleanup.md +161 -1
  24. package/commands/compat-audit.md +2 -1
  25. package/commands/deploy-info.md +58 -1
  26. package/commands/deploy-pr.md +485 -0
  27. package/commands/design-bench.md +5 -1
  28. package/commands/design-render-critique-loop.md +1 -1
  29. package/commands/diagnose.md +2 -1
  30. package/commands/explain.md +2 -1
  31. package/commands/fix-ci.md +577 -0
  32. package/commands/forge-stats.md +121 -2
  33. package/commands/forgedock-init.md +1 -1
  34. package/commands/geo-audit.md +3 -0
  35. package/commands/incident-response.md +2 -1
  36. package/commands/issue.md +23 -2
  37. package/commands/milestone.md +77 -12
  38. package/commands/optimize.md +2 -1
  39. package/commands/orchestrate/config.md +67 -0
  40. package/commands/orchestrate/phase-1-resolve.md +159 -0
  41. package/commands/orchestrate/phase-2-triage.md +126 -0
  42. package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
  43. package/commands/orchestrate/phase-3-dependency.md +591 -0
  44. package/commands/orchestrate/phase-4-execution.md +962 -0
  45. package/commands/orchestrate/phase-5-cleanup.md +47 -0
  46. package/commands/orchestrate/phase-6-report.md +198 -0
  47. package/commands/orchestrate/safety.md +62 -0
  48. package/commands/orchestrate.md +25 -2045
  49. package/commands/pipeline-health/config.md +34 -0
  50. package/commands/pipeline-health/phase-1-context.md +180 -0
  51. package/commands/pipeline-health/phase-2-metrics.md +1729 -0
  52. package/commands/pipeline-health/phase-3-analyze.md +204 -0
  53. package/commands/pipeline-health/phase-4-proposals.md +117 -0
  54. package/commands/pipeline-health/phase-5-report.md +284 -0
  55. package/commands/pipeline-health/phase-6-summary.md +52 -0
  56. package/commands/pipeline-health.md +25 -2401
  57. package/commands/pipeline-resume.md +24 -1
  58. package/commands/pipeline-status.md +65 -0
  59. package/commands/qa-sweep.md +4 -1
  60. package/commands/quality-gate.md +248 -10
  61. package/commands/recover-orphans.md +576 -0
  62. package/commands/replay.md +2 -1
  63. package/commands/review-pr-agents/api.md +135 -0
  64. package/commands/review-pr-agents/auth.md +137 -0
  65. package/commands/review-pr-agents/billing.md +101 -0
  66. package/commands/review-pr-agents/concurrency.md +145 -0
  67. package/commands/review-pr-agents/database.md +103 -0
  68. package/commands/review-pr-agents/frontend.md +89 -0
  69. package/commands/review-pr-agents/infra.md +385 -0
  70. package/commands/review-pr-agents/protocols.md +185 -0
  71. package/commands/review-pr-agents/scraper.md +185 -0
  72. package/commands/review-pr-agents/security.md +406 -0
  73. package/commands/review-pr-agents.md +45 -1722
  74. package/commands/review-pr-staging.md +196 -40
  75. package/commands/review-pr.md +591 -52
  76. package/commands/rollback.md +2 -1
  77. package/commands/scope.md +2 -1
  78. package/commands/security-audit.md +2 -1
  79. package/commands/signal-planner.md +623 -0
  80. package/commands/sync-ecosystem.md +1 -0
  81. package/commands/test-gate.md +17 -1
  82. package/commands/validate.md +2 -1
  83. package/commands/work-on/build/architect.md +28 -0
  84. package/commands/work-on/build/context.md +2 -0
  85. package/commands/work-on/build/implement.md +3 -1
  86. package/commands/work-on/build/validate.md +26 -7
  87. package/commands/work-on/build.md +163 -24
  88. package/commands/work-on/close.md +130 -3
  89. package/commands/work-on/decompose.md +19 -1
  90. package/commands/work-on/investigate.md +117 -2
  91. package/commands/work-on/review.md +24 -4
  92. package/commands/work-on-monolithic.md +7 -2
  93. package/commands/work-on.md +202 -4
  94. package/docs/BRAND.md +14 -7
  95. package/docs/CI.md +16 -2
  96. package/docs/CODEX.md +36 -1
  97. package/docs/CONFIG.md +5 -8
  98. package/docs/FORGE-PROTOCOL.md +15 -1064
  99. package/docs/anthropic-devrel-outreach.md +2 -2
  100. package/docs/articles/command-decomposition-token-savings.md +214 -0
  101. package/docs/articles/model-release-playbook.md +131 -0
  102. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
  103. package/docs/claude-breakpoints.json +75 -5
  104. package/docs/design/abc-benchmark.md +1 -1
  105. package/docs/design/design-spec-schema.md +1 -1
  106. package/docs/design/divergent-generation.md +1 -1
  107. package/docs/design/eval-gate-runbook.md +178 -0
  108. package/docs/design/show-hn-draft.md +26 -1
  109. package/docs/design/token-cost-baseline-1298.md +59 -0
  110. package/docs/eval/README.md +120 -0
  111. package/docs/eval/corpus/seed-v1.json +6 -0
  112. package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
  113. package/docs/site/annotations-explained.md +24 -0
  114. package/docs/site/command-learning-path.md +51 -14
  115. package/docs/site/command-reference.md +386 -111
  116. package/docs/site/for-companies.md +95 -0
  117. package/docs/site/forge-annotation-protocol.md +12 -256
  118. package/docs/site/getting-started.md +43 -4
  119. package/docs/site/github-is-the-memory.md +113 -0
  120. package/docs/site/how-it-works.md +3 -13
  121. package/docs/site/index.md +9 -1
  122. package/docs/spec/eval-run-result.md +147 -0
  123. package/docs/spec/forge-protocol-v1.md +441 -18
  124. package/docs/spec/label-state-machine.md +55 -0
  125. package/docs/spec/review-protocol.md +158 -0
  126. package/docs/spec-graph-schema.md +28 -0
  127. package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
  128. package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
  129. package/examples/forgedock-demo/README.md +41 -22
  130. package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
  131. package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
  132. package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
  133. package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
  134. package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
  135. package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
  136. package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
  137. package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
  138. package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
  139. package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
  140. package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
  141. package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
  142. package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
  143. package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
  144. package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
  145. package/examples/forgedock-demo/scripts/smoke.js +18 -1
  146. package/examples/forgedock-demo/src/db.js +36 -11
  147. package/examples/forgedock-demo/src/routes/notes.js +106 -9
  148. package/examples/forgedock-demo/src/server.js +4 -1
  149. package/package.json +3 -1
  150. package/packages/protocol/LICENSE +21 -0
  151. package/packages/protocol/src/cli.js +135 -0
  152. package/packages/protocol/src/emit.js +146 -0
  153. package/packages/protocol/src/index.js +43 -0
  154. package/packages/protocol/src/parse.js +254 -0
  155. package/packages/protocol/src/types.js +171 -0
  156. package/packages/protocol/src/validate.js +122 -0
  157. package/scripts/bench-topology-cost.mjs +372 -0
  158. package/scripts/check-command-docs-drift.sh +198 -0
  159. package/scripts/check-protocol-restatements.sh +176 -0
  160. package/scripts/check-registry/README.md +72 -0
  161. package/scripts/check-registry/manifest.json +6 -0
  162. package/scripts/code-index.sh +454 -0
  163. package/scripts/conformance-check.mjs +168 -0
  164. package/scripts/doctor-pipeline-state.sh +484 -0
  165. package/scripts/eval-gate-baseline.json +17 -0
  166. package/scripts/eval-gate-scorecard.mjs +242 -0
  167. package/scripts/eval-scorecard-aggregator.mjs +252 -0
  168. package/scripts/flaky-quarantine.sh +241 -0
  169. package/scripts/forge-annotation.sh +567 -0
  170. package/scripts/issue-dedup.sh +236 -0
  171. package/scripts/validate-annotation-node.mjs +233 -0
  172. package/scripts/worktree-lifecycle.sh +3 -3
  173. package/templates/devdocs/agent/using-forgedock.md +14 -0
  174. package/docs/site/.vitepress/config.mjs +0 -85
@@ -0,0 +1,576 @@
1
+ ---
2
+ description: Scan for pipeline-orphaned issues stuck in intermediate workflow states and recover them — diagnose each orphan's actual GitHub state, apply recovery actions, clean up worktrees
3
+ argument-hint: [--dry-run | --since <hours> | --issue <number>]
4
+ ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
7
+
8
+ # /recover-orphans — Pipeline Orphan Recovery
9
+
10
+ **Input**: $ARGUMENTS
11
+
12
+ Scan ALL open issues with intermediate workflow labels for orphaned state — issues where the agent died mid-pipeline (context expired, rate-limited, crashed) and no active agent is continuing. Diagnose each orphan's actual GitHub state and apply the appropriate recovery action.
13
+
14
+ **Agent model policy**: `model: "sonnet"`. Fallback: `model: "opus"` if rate-limited.
15
+ **NEVER use plan mode (EnterPlanMode).**
16
+ **NEVER use the Agent tool** — recover-orphans re-enters the pipeline via `Skill(skill="work-on", ...)` and `Skill(skill="review-pr", ...)` only.
17
+
18
+ <!-- FORGE:SPEC_LOADED — recover-orphans.md loaded and active. Agent is bound by this spec. -->
19
+
20
+ ---
21
+
22
+ ## Config Resolution
23
+
24
+ Read `forge.yaml` at the project root before running any commands:
25
+
26
+ ```bash
27
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
28
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
29
+ GH_FLAG="-R $GH_REPO"
30
+ REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
31
+ WORKTREE_BASE=$(yq '.paths.worktree_base' "$CONFIG_FILE")
32
+ STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
33
+ ```
34
+
35
+ If `forge.yaml` is missing: stop and tell the user to run `npx forgedock init` to generate it.
36
+
37
+ ---
38
+
39
+ ## Argument Parsing
40
+
41
+ ```bash
42
+ DRY_RUN=false
43
+ SINCE_HOURS=""
44
+ TARGET_ISSUE=""
45
+
46
+ # Parse flags
47
+ for arg in $ARGUMENTS; do
48
+ case "$arg" in
49
+ --dry-run) DRY_RUN=true ;;
50
+ --since) : ;; # value follows
51
+ --issue) : ;; # value follows
52
+ esac
53
+ done
54
+
55
+ # Parse --since <hours> and --issue <number> (value follows flag)
56
+ PREV=""
57
+ for arg in $ARGUMENTS; do
58
+ if [ "$PREV" = "--since" ]; then
59
+ SINCE_HOURS="$arg"
60
+ elif [ "$PREV" = "--issue" ]; then
61
+ TARGET_ISSUE="$arg"
62
+ fi
63
+ PREV="$arg"
64
+ done
65
+
66
+ echo "=== /recover-orphans: $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
67
+ echo "DRY_RUN=$DRY_RUN | SINCE_HOURS=${SINCE_HOURS:-all} | TARGET_ISSUE=${TARGET_ISSUE:-all}"
68
+ ```
69
+
70
+ | Flag | Effect |
71
+ |------|--------|
72
+ | (none) | Scan all intermediate-state open issues |
73
+ | `--dry-run` | Report-only — print what would be done, no mutations |
74
+ | `--since <hours>` | Only scan issues not updated in the last N hours (default: all) |
75
+ | `--issue <number>` | Recover a single specific issue |
76
+
77
+ ---
78
+
79
+ ## Phase 1: Find Orphans
80
+
81
+ **Note**: `gh issue list --label` uses AND semantics when multiple labels are given. To find issues with ANY intermediate workflow label, query each label separately and merge results.
82
+
83
+ ```bash
84
+ # Compute stale cutoff if --since was provided
85
+ CUTOFF=""
86
+ if [ -n "$SINCE_HOURS" ]; then
87
+ CUTOFF=$(python3 -c "
88
+ from datetime import datetime, timedelta, timezone
89
+ h = int('$SINCE_HOURS')
90
+ print((datetime.now(timezone.utc) - timedelta(hours=h)).strftime('%Y-%m-%dT%H:%M:%SZ'))
91
+ " 2>/dev/null || echo "")
92
+ fi
93
+
94
+ if [ -n "$TARGET_ISSUE" ]; then
95
+ # Single-issue mode
96
+ ORPHAN_LIST="$TARGET_ISSUE"
97
+ echo "Single-issue mode: targeting #$TARGET_ISSUE"
98
+ else
99
+ # Fleet scan: query each workflow label separately, merge, deduplicate
100
+ ORPHAN_JSON=$(
101
+ for LABEL in "workflow:investigating" "workflow:ready-to-build" "workflow:building" "workflow:in-review"; do
102
+ gh issue list ${GH_FLAG} \
103
+ --state open \
104
+ --label "$LABEL" \
105
+ --limit 100 \
106
+ --json number,title,labels,updatedAt
107
+ done | jq -s '
108
+ flatten |
109
+ unique_by(.number) |
110
+ sort_by(.updatedAt) | reverse |
111
+ .[]
112
+ ')
113
+
114
+ # Apply --since filter if specified
115
+ if [ -n "$CUTOFF" ]; then
116
+ ORPHAN_JSON=$(echo "$ORPHAN_JSON" | jq -s --arg cutoff "$CUTOFF" '
117
+ [.[] | select(.updatedAt < $cutoff)] | .[]
118
+ ')
119
+ fi
120
+
121
+ ORPHAN_LIST=$(echo "$ORPHAN_JSON" | jq -r '.number' | sort -un)
122
+ fi
123
+
124
+ ORPHAN_COUNT=$(echo "$ORPHAN_LIST" | grep -c '[0-9]' 2>/dev/null || echo 0)
125
+ echo "Orphan candidates found: $ORPHAN_COUNT"
126
+ ```
127
+
128
+ If `ORPHAN_COUNT` is 0: print `No orphaned issues found — pipeline is clean.` and STOP.
129
+
130
+ ---
131
+
132
+ ## Phase 2: Diagnose Each Orphan
133
+
134
+ For each issue number in `ORPHAN_LIST`, run the full diagnostic and determine the recovery action.
135
+
136
+ ```bash
137
+ # Diagnosis result arrays (accumulated for Phase 3 and Phase 5 report)
138
+ declare -A DIAG_ACTION # issue_num -> action name
139
+ declare -A DIAG_REASON # issue_num -> human-readable reason
140
+ declare -A DIAG_PR_NUM # issue_num -> associated PR number (if any)
141
+ declare -A DIAG_BRANCH # issue_num -> associated branch (if any)
142
+
143
+ for NUM in $ORPHAN_LIST; do
144
+ echo ""
145
+ echo "--- Diagnosing #$NUM ---"
146
+
147
+ # Read current issue state
148
+ ISSUE=$(gh issue view "$NUM" ${GH_FLAG} \
149
+ --json number,title,labels,state,updatedAt,body 2>/dev/null)
150
+ if [ -z "$ISSUE" ]; then
151
+ echo "#$NUM: Could not fetch — skipping"
152
+ continue
153
+ fi
154
+
155
+ ISSUE_STATE=$(echo "$ISSUE" | jq -r '.state')
156
+ ISSUE_TITLE=$(echo "$ISSUE" | jq -r '.title')
157
+ ISSUE_LABELS=$(echo "$ISSUE" | jq -r '[.labels[].name] | join(", ")')
158
+ WORKFLOW_LABEL=$(echo "$ISSUE" | jq -r '[.labels[].name | select(startswith("workflow:"))] | first // "none"')
159
+
160
+ echo "#$NUM: $ISSUE_TITLE"
161
+ echo " State: $ISSUE_STATE | Labels: $ISSUE_LABELS"
162
+
163
+ # Skip if already in terminal state (race condition: label changed since query)
164
+ if [ "$ISSUE_STATE" = "CLOSED" ]; then
165
+ DIAG_ACTION[$NUM]="skip"
166
+ DIAG_REASON[$NUM]="Issue already closed"
167
+ continue
168
+ fi
169
+ if echo "$ISSUE_LABELS" | grep -qE "workflow:merged|workflow:invalid"; then
170
+ DIAG_ACTION[$NUM]="skip"
171
+ DIAG_REASON[$NUM]="Already in terminal state: $ISSUE_LABELS"
172
+ continue
173
+ fi
174
+
175
+ # Check for merged PR referencing this issue
176
+ MERGED_PR=$(gh pr list ${GH_FLAG} \
177
+ --state merged \
178
+ --search "Closes #$NUM" \
179
+ --json number \
180
+ --jq '.[0].number' 2>/dev/null)
181
+
182
+ if [ -z "$MERGED_PR" ]; then
183
+ # Also check body/title for issue reference pattern
184
+ MERGED_PR=$(gh pr list ${GH_FLAG} \
185
+ --state merged \
186
+ --search "#$NUM" \
187
+ --limit 20 \
188
+ --json number,body \
189
+ --jq ".[] | select(.body | test(\"Closes #${NUM}|closes #${NUM}|Fix #${NUM}|fix #${NUM}\")) | .number" \
190
+ 2>/dev/null | head -1)
191
+ fi
192
+
193
+ if [ -n "$MERGED_PR" ]; then
194
+ DIAG_ACTION[$NUM]="label-cleanup"
195
+ DIAG_REASON[$NUM]="PR #$MERGED_PR already merged — update labels and close issue"
196
+ DIAG_PR_NUM[$NUM]="$MERGED_PR"
197
+ echo " Diagnosis: LABEL-CLEANUP (PR #$MERGED_PR already merged)"
198
+ continue
199
+ fi
200
+
201
+ # Check for an open PR on any branch associated with this issue
202
+ # Strategy: look for branches with the issue number in the name
203
+ ISSUE_BRANCH=$(git ls-remote --heads origin 2>/dev/null | grep "/$NUM" | sed 's|.*refs/heads/||' | head -1)
204
+ if [ -z "$ISSUE_BRANCH" ]; then
205
+ # Also check recent PR list for a PR referencing this issue
206
+ OPEN_PR_JSON=$(gh pr list ${GH_FLAG} \
207
+ --state open \
208
+ --search "#$NUM" \
209
+ --limit 20 \
210
+ --json number,headRefName,reviewDecision,statusCheckRollup \
211
+ 2>/dev/null)
212
+ OPEN_PR_NUM=$(echo "$OPEN_PR_JSON" | jq -r ".[] | select(.body | test(\"#${NUM}\")) | .number" 2>/dev/null | head -1)
213
+ else
214
+ OPEN_PR_JSON=$(gh pr list ${GH_FLAG} \
215
+ --state open \
216
+ --head "$ISSUE_BRANCH" \
217
+ --json number,headRefName,reviewDecision,statusCheckRollup \
218
+ 2>/dev/null)
219
+ OPEN_PR_NUM=$(echo "$OPEN_PR_JSON" | jq -r '.[0].number' 2>/dev/null)
220
+ fi
221
+
222
+ # If open PR found, diagnose PR state
223
+ if [ -n "$OPEN_PR_NUM" ]; then
224
+ DIAG_BRANCH[$NUM]="${ISSUE_BRANCH:-unknown}"
225
+ DIAG_PR_NUM[$NUM]="$OPEN_PR_NUM"
226
+
227
+ # Read PR details
228
+ PR_DETAIL=$(gh pr view "$OPEN_PR_NUM" ${GH_FLAG} \
229
+ --json number,headRefName,reviewDecision,statusCheckRollup,state 2>/dev/null)
230
+ PR_REVIEW=$(echo "$PR_DETAIL" | jq -r '.reviewDecision // "REVIEW_REQUIRED"')
231
+ PR_CI=$(echo "$PR_DETAIL" | jq -r '
232
+ if (.statusCheckRollup == null or (.statusCheckRollup | length) == 0) then "UNKNOWN"
233
+ elif ([.statusCheckRollup[] | select(.conclusion == "FAILURE" or .conclusion == "ERROR")] | length) > 0 then "FAILED"
234
+ elif ([.statusCheckRollup[] | select(.status == "IN_PROGRESS" or .status == "QUEUED" or .status == "PENDING")] | length) > 0 then "PENDING"
235
+ elif ([.statusCheckRollup[] | select(.conclusion == "SUCCESS")] | length) > 0 then "SUCCESS"
236
+ else "UNKNOWN"
237
+ end')
238
+ BRANCH_NAME=$(echo "$PR_DETAIL" | jq -r '.headRefName // ""')
239
+ DIAG_BRANCH[$NUM]="$BRANCH_NAME"
240
+
241
+ echo " PR #$OPEN_PR_NUM found — reviewDecision=$PR_REVIEW | CI=$PR_CI"
242
+
243
+ if [ "$PR_REVIEW" = "APPROVED" ] && [ "$PR_CI" = "SUCCESS" ]; then
244
+ DIAG_ACTION[$NUM]="merge-pr"
245
+ DIAG_REASON[$NUM]="PR #$OPEN_PR_NUM approved + CI green — merge and close"
246
+ echo " Diagnosis: MERGE-PR (approved + CI green)"
247
+ elif [ "$PR_CI" = "FAILED" ]; then
248
+ DIAG_ACTION[$NUM]="escalate-ci"
249
+ DIAG_REASON[$NUM]="PR #$OPEN_PR_NUM has CI failures — manual intervention needed"
250
+ echo " Diagnosis: ESCALATE-CI (CI failed, needs /fix-ci or manual fix)"
251
+ elif [ "$PR_REVIEW" = "CHANGES_REQUESTED" ]; then
252
+ DIAG_ACTION[$NUM]="escalate-changes"
253
+ DIAG_REASON[$NUM]="PR #$OPEN_PR_NUM has requested changes — needs human review of findings"
254
+ echo " Diagnosis: ESCALATE-CHANGES (reviewer requested changes)"
255
+ else
256
+ DIAG_ACTION[$NUM]="review-pr"
257
+ DIAG_REASON[$NUM]="PR #$OPEN_PR_NUM exists but not reviewed — invoke /review-pr"
258
+ echo " Diagnosis: REVIEW-PR (open PR awaiting review)"
259
+ fi
260
+ continue
261
+ fi
262
+
263
+ # No open or merged PR — check if a branch exists with commits
264
+ BRANCH_EXISTS=false
265
+ BRANCH_NAME=""
266
+ if [ -n "$ISSUE_BRANCH" ]; then
267
+ BRANCH_EXISTS=true
268
+ BRANCH_NAME="$ISSUE_BRANCH"
269
+ else
270
+ # Try common naming patterns
271
+ for PATTERN in "fix/.*${NUM}" "feat/.*${NUM}" "refactor/.*${NUM}"; do
272
+ FOUND=$(git ls-remote --heads origin 2>/dev/null | grep -oE "fix/[^ ]*${NUM}[^ ]*|feat/[^ ]*${NUM}[^ ]*|refactor/[^ ]*${NUM}[^ ]*" | head -1)
273
+ if [ -n "$FOUND" ]; then
274
+ BRANCH_EXISTS=true
275
+ BRANCH_NAME="$FOUND"
276
+ break
277
+ fi
278
+ done
279
+ fi
280
+
281
+ if [ "$BRANCH_EXISTS" = "true" ] && [ -n "$BRANCH_NAME" ]; then
282
+ DIAG_BRANCH[$NUM]="$BRANCH_NAME"
283
+ # Check if branch has commits beyond the base
284
+ BRANCH_COMMITS=$(git log "origin/${STAGING_BRANCH}..origin/${BRANCH_NAME}" --oneline 2>/dev/null | wc -l | tr -d ' ')
285
+ if [ "${BRANCH_COMMITS:-0}" -gt 0 ]; then
286
+ DIAG_ACTION[$NUM]="create-pr"
287
+ DIAG_REASON[$NUM]="Branch $BRANCH_NAME has $BRANCH_COMMITS commits but no PR — resume /work-on to create PR"
288
+ echo " Diagnosis: CREATE-PR (branch has commits, no PR)"
289
+ else
290
+ DIAG_ACTION[$NUM]="reset-labels"
291
+ DIAG_REASON[$NUM]="Branch $BRANCH_NAME exists but has no commits beyond base — reset labels to unworked"
292
+ echo " Diagnosis: RESET-LABELS (empty branch)"
293
+ fi
294
+ else
295
+ # No branch, no PR — pure label orphan
296
+ DIAG_ACTION[$NUM]="reset-labels"
297
+ DIAG_REASON[$NUM]="No branch or PR found — reset workflow labels so issue is unworked"
298
+ echo " Diagnosis: RESET-LABELS (no branch, no PR)"
299
+ fi
300
+
301
+ done
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Phase 3: Apply Recovery
307
+
308
+ For each diagnosed issue, apply the recovery action. All mutating actions are skipped when `DRY_RUN=true`.
309
+
310
+ ```bash
311
+ RECOVERY_RESULTS=""
312
+
313
+ for NUM in $ORPHAN_LIST; do
314
+ ACTION="${DIAG_ACTION[$NUM]:-skip}"
315
+ REASON="${DIAG_REASON[$NUM]:-unknown}"
316
+ PR_NUM="${DIAG_PR_NUM[$NUM]:-}"
317
+ BRANCH="${DIAG_BRANCH[$NUM]:-}"
318
+
319
+ echo ""
320
+ echo "--- Recovering #$NUM (action: $ACTION) ---"
321
+ echo " Reason: $REASON"
322
+
323
+ case "$ACTION" in
324
+
325
+ skip)
326
+ echo " Skipped: $REASON"
327
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | skip | ${REASON} |\n"
328
+ ;;
329
+
330
+ label-cleanup)
331
+ # PR already merged — update labels and close issue
332
+ echo " Applying label-cleanup: marking workflow:merged and closing issue"
333
+ if [ "$DRY_RUN" = "true" ]; then
334
+ echo " [DRY-RUN] Would: gh issue edit $NUM --add-label workflow:merged --remove-label intermediate"
335
+ echo " [DRY-RUN] Would: gh issue close $NUM with merged comment"
336
+ else
337
+ gh issue edit "$NUM" ${GH_FLAG} \
338
+ --add-label "workflow:merged" \
339
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" \
340
+ 2>/dev/null || true
341
+ gh issue close "$NUM" ${GH_FLAG} \
342
+ --comment "Closed by /recover-orphans: PR #${PR_NUM} was already merged. Labels corrected." \
343
+ 2>/dev/null || true
344
+ echo " Done: #$NUM closed with workflow:merged"
345
+ fi
346
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | label-cleanup | PR #${PR_NUM} merged — closed issue |\n"
347
+ ;;
348
+
349
+ merge-pr)
350
+ # Open PR is approved + CI green — merge it
351
+ echo " Applying merge-pr: merging PR #$PR_NUM"
352
+ if [ "$DRY_RUN" = "true" ]; then
353
+ echo " [DRY-RUN] Would: gh pr merge $PR_NUM --merge --auto"
354
+ else
355
+ MERGE_RESULT=$(gh pr merge "$PR_NUM" ${GH_FLAG} --merge --auto 2>&1)
356
+ MERGE_EXIT=$?
357
+ echo " Merge result (exit $MERGE_EXIT): $MERGE_RESULT"
358
+ if [ $MERGE_EXIT -eq 0 ]; then
359
+ # Close the issue explicitly (Closes # only auto-closes on default branch)
360
+ gh issue close "$NUM" ${GH_FLAG} \
361
+ --comment "Closed by /recover-orphans: PR #${PR_NUM} merged." \
362
+ 2>/dev/null || true
363
+ gh issue edit "$NUM" ${GH_FLAG} \
364
+ --add-label "workflow:merged" \
365
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review,workflow:invalid,workflow:decomposed" \
366
+ 2>/dev/null || true
367
+ fi
368
+ fi
369
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | merge-pr | PR #${PR_NUM} merged |\n"
370
+ ;;
371
+
372
+ review-pr)
373
+ # Open PR awaiting review — invoke /review-pr
374
+ echo " Applying review-pr: invoking /review-pr on PR #$PR_NUM"
375
+ if [ "$DRY_RUN" = "true" ]; then
376
+ echo " [DRY-RUN] Would: Skill(skill='review-pr', args='$PR_NUM --auto-merge --issue $NUM --gh-flag $GH_FLAG')"
377
+ else
378
+ Skill(skill="review-pr", args="${PR_NUM} --auto-merge --issue ${NUM} --gh-flag ${GH_FLAG}")
379
+ # After review: update label
380
+ gh issue edit "$NUM" ${GH_FLAG} --add-label "workflow:in-review" \
381
+ --remove-label "workflow:building" 2>/dev/null || true
382
+ fi
383
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | review-pr | PR #${PR_NUM} submitted for review |\n"
384
+ ;;
385
+
386
+ create-pr)
387
+ # Branch has commits but no PR — resume /work-on to create PR
388
+ echo " Applying create-pr: resuming /work-on to advance from build to PR creation"
389
+ if [ "$DRY_RUN" = "true" ]; then
390
+ echo " [DRY-RUN] Would: Skill(skill='work-on', args='$NUM')"
391
+ else
392
+ Skill(skill="work-on", args="${NUM}")
393
+ fi
394
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | create-pr | Resumed /work-on — branch $BRANCH has commits, no PR |\n"
395
+ ;;
396
+
397
+ reset-labels)
398
+ # No branch, no PR, or empty branch — reset to unworked state
399
+ echo " Applying reset-labels: removing intermediate workflow labels"
400
+ if [ "$DRY_RUN" = "true" ]; then
401
+ echo " [DRY-RUN] Would: remove workflow:investigating, workflow:ready-to-build, workflow:building, workflow:in-review"
402
+ else
403
+ gh issue edit "$NUM" ${GH_FLAG} \
404
+ --remove-label "workflow:investigating,workflow:ready-to-build,workflow:building,workflow:in-review" \
405
+ 2>/dev/null || true
406
+ gh issue comment "$NUM" ${GH_FLAG} \
407
+ --body "<!-- FORGE:ORPHAN_RECOVERED -->
408
+ ## Orphan Recovery Applied
409
+
410
+ **Action**: Label reset — no branch or PR found (or branch had no commits).
411
+ **Recovered at**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
412
+ **Recovered by**: /recover-orphans
413
+
414
+ Issue returned to unworked state. Run \`/work-on ${NUM}\` to restart the pipeline." \
415
+ 2>/dev/null || true
416
+ echo " Done: #$NUM labels reset to unworked"
417
+ fi
418
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | reset-labels | No progress found — labels cleared |\n"
419
+ ;;
420
+
421
+ escalate-ci)
422
+ # CI failed on open PR — add needs-human
423
+ echo " Escalating: PR #$PR_NUM has CI failures — adding needs-human label"
424
+ if [ "$DRY_RUN" = "true" ]; then
425
+ echo " [DRY-RUN] Would: gh issue edit $NUM --add-label needs-human; gh issue comment with CI failure info"
426
+ else
427
+ gh issue edit "$NUM" ${GH_FLAG} --add-label "needs-human" 2>/dev/null || true
428
+ gh issue comment "$NUM" ${GH_FLAG} \
429
+ --body "<!-- FORGE:ORPHAN_ESCALATED -->
430
+ ## Orphan Recovery: Escalated (CI Failure)
431
+
432
+ **PR**: #${PR_NUM}
433
+ **Reason**: CI checks failed on the open PR. Automated recovery cannot proceed past a CI failure.
434
+ **Escalated at**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
435
+
436
+ **Next steps**:
437
+ 1. Review CI failures: \`gh pr view ${PR_NUM} --web\`
438
+ 2. Fix failing checks, then run \`/work-on ${NUM}\` to resume
439
+ 3. Or run \`/fix-ci ${NUM}\` if that command is available" \
440
+ 2>/dev/null || true
441
+ fi
442
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | escalate-ci | PR #${PR_NUM} CI failed — needs-human added |\n"
443
+ ;;
444
+
445
+ escalate-changes)
446
+ # Reviewer requested changes — add needs-human
447
+ echo " Escalating: PR #$PR_NUM has requested changes — needs human review"
448
+ if [ "$DRY_RUN" = "true" ]; then
449
+ echo " [DRY-RUN] Would: gh issue edit $NUM --add-label needs-human"
450
+ else
451
+ gh issue edit "$NUM" ${GH_FLAG} --add-label "needs-human" 2>/dev/null || true
452
+ gh issue comment "$NUM" ${GH_FLAG} \
453
+ --body "<!-- FORGE:ORPHAN_ESCALATED -->
454
+ ## Orphan Recovery: Escalated (Changes Requested)
455
+
456
+ **PR**: #${PR_NUM}
457
+ **Reason**: A reviewer has requested changes on PR #${PR_NUM}. Human review of the requested changes is required.
458
+ **Escalated at**: $(date -u +%Y-%m-%dT%H:%M:%SZ)
459
+
460
+ **Next steps**:
461
+ 1. Review requested changes: \`gh pr view ${PR_NUM} --web\`
462
+ 2. Address the feedback, then remove \`needs-human\` label to allow pipeline to continue" \
463
+ 2>/dev/null || true
464
+ fi
465
+ RECOVERY_RESULTS="${RECOVERY_RESULTS}| #${NUM} | escalate-changes | PR #${PR_NUM} reviewer requested changes |\n"
466
+ ;;
467
+
468
+ esac
469
+ done
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Phase 4: Worktree Cleanup
475
+
476
+ Prune orphaned worktrees — those with no corresponding open PR. Follows the same safe pattern as `/cleanup branches`.
477
+
478
+ ```bash
479
+ echo ""
480
+ echo "=== Phase 4: Worktree Cleanup ==="
481
+
482
+ if [ -z "$WORKTREE_BASE" ] || [ ! -d "$WORKTREE_BASE" ]; then
483
+ echo "WORKTREE_BASE not configured or directory not found — skipping worktree cleanup"
484
+ else
485
+ cd "$REPO_PATH" 2>/dev/null || true
486
+
487
+ WORKTREE_REMOVED=0
488
+ WORKTREE_KEPT=0
489
+
490
+ while IFS= read -r WT_PATH; do
491
+ # Skip the main worktree (repo root)
492
+ [ "$WT_PATH" = "$REPO_PATH" ] && continue
493
+ # Only manage worktrees under WORKTREE_BASE
494
+ case "$WT_PATH" in
495
+ "$WORKTREE_BASE"*) : ;;
496
+ *) continue ;;
497
+ esac
498
+
499
+ BRANCH=$(git -C "$WT_PATH" branch --show-current 2>/dev/null || echo "")
500
+ if [ -z "$BRANCH" ]; then
501
+ echo " SKIP: $WT_PATH — detached HEAD, leaving as-is"
502
+ WORKTREE_KEPT=$((WORKTREE_KEPT + 1))
503
+ continue
504
+ fi
505
+
506
+ # Check if branch has an open PR
507
+ OPEN_PR_COUNT=$(gh pr list ${GH_FLAG} --head "$BRANCH" --state open --json number --jq 'length' 2>/dev/null || echo "0")
508
+
509
+ # Check if branch has a merged PR
510
+ MERGED_PR=$(gh pr list ${GH_FLAG} --head "$BRANCH" --state merged --json number --jq '.[0].number' 2>/dev/null || echo "")
511
+
512
+ if [ "${OPEN_PR_COUNT:-0}" -gt 0 ]; then
513
+ echo " KEEP: $WT_PATH (branch: $BRANCH) — has open PR"
514
+ WORKTREE_KEPT=$((WORKTREE_KEPT + 1))
515
+ elif [ -n "$MERGED_PR" ]; then
516
+ echo " STALE: $WT_PATH (branch: $BRANCH) — PR #$MERGED_PR merged"
517
+ if [ "$DRY_RUN" = "true" ]; then
518
+ echo " [DRY-RUN] Would: git worktree remove $WT_PATH --force"
519
+ echo " [DRY-RUN] Would: git branch -D $BRANCH"
520
+ else
521
+ git worktree remove "$WT_PATH" --force 2>/dev/null || true
522
+ git branch -D "$BRANCH" 2>/dev/null || true
523
+ echo " Removed worktree: $WT_PATH (PR #$MERGED_PR was merged)"
524
+ WORKTREE_REMOVED=$((WORKTREE_REMOVED + 1))
525
+ fi
526
+ else
527
+ echo " UNKNOWN: $WT_PATH (branch: $BRANCH) — no PR found, leaving as-is"
528
+ WORKTREE_KEPT=$((WORKTREE_KEPT + 1))
529
+ fi
530
+ done < <(git worktree list --porcelain 2>/dev/null | grep "^worktree " | sed 's/^worktree //')
531
+
532
+ echo "Worktree cleanup: removed=$WORKTREE_REMOVED kept=$WORKTREE_KEPT"
533
+ fi
534
+ ```
535
+
536
+ ---
537
+
538
+ ## Phase 5: Report
539
+
540
+ Print a structured recovery report.
541
+
542
+ ```bash
543
+ echo ""
544
+ echo "========================================"
545
+ echo " /recover-orphans — Recovery Report"
546
+ echo "========================================"
547
+ echo ""
548
+ echo "Scan timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
549
+ echo "Dry-run: $DRY_RUN"
550
+ echo "Orphans scanned: $ORPHAN_COUNT"
551
+ echo ""
552
+ echo "## Recovery Actions"
553
+ echo ""
554
+ echo "| Issue | Action | Outcome |"
555
+ echo "|-------|--------|---------|"
556
+ printf '%b' "$RECOVERY_RESULTS"
557
+ echo ""
558
+ echo "## Worktrees"
559
+ echo "See Phase 4 output above for worktree-specific actions."
560
+ echo ""
561
+ echo "## Next Steps"
562
+ echo ""
563
+ echo "For issues with action 'reset-labels':"
564
+ echo " Run /work-on <issue-number> to restart the pipeline from the beginning."
565
+ echo ""
566
+ echo "For issues with action 'escalate-ci' or 'escalate-changes':"
567
+ echo " Address the manual intervention required, then remove the needs-human label."
568
+ echo ""
569
+ echo "For issues with action 'create-pr':"
570
+ echo " /work-on has been re-invoked — monitor the issue for workflow:in-review label."
571
+ echo ""
572
+
573
+ if [ "$DRY_RUN" = "true" ]; then
574
+ echo "DRY-RUN MODE: No changes were made. Remove --dry-run to apply recovery actions."
575
+ fi
576
+ ```
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  description: Read-only playback of a completed pipeline run — shows FORGE annotations phase by phase with timestamps
3
3
  argument-hint: <issue-number>
4
+ install: extras
4
5
  ---
5
6
 
6
7
  # /replay — Pipeline Run Playback
@@ -9,7 +10,7 @@ argument-hint: <issue-number>
9
10
 
10
11
  Replay a completed pipeline run. Fetches all FORGE annotations posted during the pipeline and displays them chronologically — phase headings, timestamps, and key structured fields. Read-only: nothing is written, no labels changed.
11
12
 
12
- **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
13
+ **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
13
14
 
14
15
  ---
15
16