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
@@ -2,6 +2,8 @@
2
2
  description: Pre-commit quality check — catches defects the review would flag, so the builder can fix them before committing
3
3
  argument-hint: (invoked by /work-on, not directly)
4
4
  ---
5
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
7
 
6
8
  # /quality-gate — Pre-Commit Quality Check
7
9
 
@@ -15,6 +17,41 @@ You do NOT post to GitHub. You do NOT create issues. You return findings directl
15
17
 
16
18
  ---
17
19
 
20
+ ## Adaptive Design
21
+
22
+ This quality gate uses **domain detection** to adapt to your project's actual tech stack. Checks are not one-size-fits-all — they are selectively triggered based on the file types and patterns present in each change.
23
+
24
+ **How it works**:
25
+ 1. Changed files are classified into domains (AUTH, DEPLOY, DATABASE, FRONTEND, etc.) by pattern-matching against file paths, extensions, and diff content
26
+ 2. Only checks for detected domains are executed — irrelevant checks are skipped entirely
27
+ 3. A project that only changes markdown files runs zero domain checks; a project that changes only TypeScript components runs only SECURITY, FRONTEND, and PROXY checks
28
+
29
+ **Universal check** (always runs for any code file):
30
+ - **SECURITY** — SQL injection, SSRF, path traversal, hardcoded secrets, XSS, command injection
31
+
32
+ **Conditional checks** (triggered only when relevant patterns are detected):
33
+
34
+ | Domain | Triggered when |
35
+ |--------|----------------|
36
+ | AUTH | Files matching `*router*` or `route.ts` handlers |
37
+ | DEPLOY | Files referencing `os.getenv` or `process.env` |
38
+ | DATABASE | `.sql` files or files under `migrations/` |
39
+ | FRONTEND | `.tsx`/`.ts` files in client component paths |
40
+ | PROXY | Client components using `fetch`/`useSWR`/`apiFetch` |
41
+ | SHELL | `.sh` files or scripts using `curl`/`wget` |
42
+ | STRING_SETS | Python files in anti-detection or browser automation paths |
43
+ | CONCURRENCY | Python files using `asyncio.shield`, `asyncio.wait_for`, or `Task.cancel` |
44
+ | STATE | Python files with new module-level mutable variable assignments |
45
+ | CAPACITY | Worker/infra Python files with new size/limit/threshold constants |
46
+ | WORKFLOW | `.yml` files in `.github/workflows/` |
47
+ | IMPORT_RESOLUTION | Python files with new `from app.*` imports added outside `try:` blocks |
48
+ | INFRA | Dockerfiles or entrypoints with runtime UID changes |
49
+ | ROUTER_BUG | Router Python files where gate conditions are narrowed |
50
+
51
+ **Stack-agnostic coverage**: The example domains above reflect common patterns caught in production; they are not requirements. A Go or Ruby project benefits from SECURITY checks. A Node.js project benefits from FRONTEND, PROXY, and DEPLOY checks. Any project benefits from DATABASE and WORKFLOW checks when those file types are present. The stack-specific examples (Python routers, SOPS secrets chain, FastAPI layouts, appleboy SSH deploys) are illustrative — the domain detection system applies the applicable subset to any codebase.
52
+
53
+ ---
54
+
18
55
  ## Input Format
19
56
 
20
57
  The builder passes:
@@ -0,0 +1,263 @@
1
+ ---
2
+ description: Read-only playback of a completed pipeline run — shows FORGE annotations phase by phase with timestamps
3
+ argument-hint: <issue-number>
4
+ ---
5
+
6
+ # /replay — Pipeline Run Playback
7
+
8
+ **Input**: $ARGUMENTS (issue number)
9
+
10
+ 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
+ **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
13
+
14
+ ---
15
+
16
+ ## Config Preamble
17
+
18
+ ```bash
19
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
20
+ if [ -f "$CONFIG_FILE" ]; then
21
+ GH_OWNER=$(yq '.project.owner' "$CONFIG_FILE")
22
+ GH_REPO_NAME=$(yq '.project.repo' "$CONFIG_FILE")
23
+ GH_REPO="${GH_OWNER}/${GH_REPO_NAME}"
24
+ GH_FLAG="-R $GH_REPO"
25
+ else
26
+ echo "ERROR: forge.yaml not found."
27
+ echo "Run: npx forgedock init"
28
+ exit 1
29
+ fi
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Step 1: Parse Input
35
+
36
+ ```bash
37
+ NUMBER=$(echo "$ARGUMENTS" | grep -oP '^\s*#?\K\d+' | head -1)
38
+ if [ -z "$NUMBER" ]; then
39
+ echo "Usage: /replay <issue-number>"
40
+ echo "Example: /replay 42"
41
+ exit 1
42
+ fi
43
+ echo "Replaying pipeline run for issue #${NUMBER}..."
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Step 2: Fetch Issue Metadata
49
+
50
+ ```bash
51
+ ISSUE_JSON=$(gh issue view "$NUMBER" "$GH_FLAG" \
52
+ --json number,title,state,labels,createdAt,closedAt 2>/dev/null)
53
+
54
+ if [ -z "$ISSUE_JSON" ] || echo "$ISSUE_JSON" | grep -q '"message"'; then
55
+ echo "ERROR: Issue #${NUMBER} not found in ${GH_REPO}."
56
+ exit 1
57
+ fi
58
+
59
+ ISSUE_TITLE=$(echo "$ISSUE_JSON" | jq -r '.title')
60
+ ISSUE_STATE=$(echo "$ISSUE_JSON" | jq -r '.state')
61
+ ISSUE_CREATED=$(echo "$ISSUE_JSON" | jq -r '.createdAt')
62
+ ISSUE_CLOSED=$(echo "$ISSUE_JSON" | jq -r '.closedAt // "open"')
63
+ WORKFLOW_LABELS=$(echo "$ISSUE_JSON" | jq -r '[.labels[].name | select(startswith("workflow:"))] | join(", ")')
64
+
65
+ echo ""
66
+ echo "══════════════════════════════════════════════════════════════"
67
+ echo " Pipeline Replay — #${NUMBER}: ${ISSUE_TITLE}"
68
+ echo "══════════════════════════════════════════════════════════════"
69
+ echo " State : ${ISSUE_STATE}"
70
+ echo " Opened : ${ISSUE_CREATED}"
71
+ [ "$ISSUE_CLOSED" != "open" ] && echo " Closed : ${ISSUE_CLOSED}"
72
+ [ -n "$WORKFLOW_LABELS" ] && echo " Labels : ${WORKFLOW_LABELS}"
73
+ echo "══════════════════════════════════════════════════════════════"
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Step 3: Fetch All Comments
79
+
80
+ ```bash
81
+ # One API call — returns all comments in chronological order
82
+ ALL_COMMENTS=$(gh api "repos/${GH_REPO}/issues/${NUMBER}/comments" 2>/dev/null)
83
+
84
+ if [ -z "$ALL_COMMENTS" ] || [ "$ALL_COMMENTS" = "[]" ]; then
85
+ echo ""
86
+ echo " No comments on this issue. No pipeline run to replay."
87
+ exit 0
88
+ fi
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Step 4: Identify FORGE Annotations
94
+
95
+ ```bash
96
+ # Filter to comments that contain a FORGE pipeline annotation
97
+ FORGE_COMMENTS=$(echo "$ALL_COMMENTS" | jq -c '[.[] | select(.body | test("<!-- FORGE:(INVESTIGATOR|CONTRACT|CONTEXT|ARCHITECT|BUILDER|DECOMPOSED|TRAJECTORY)"))]')
98
+ FORGE_COUNT=$(echo "$FORGE_COMMENTS" | jq 'length')
99
+
100
+ if [ "$FORGE_COUNT" -eq 0 ]; then
101
+ echo ""
102
+ echo " No FORGE pipeline annotations found on issue #${NUMBER}."
103
+ echo " This issue was not processed by the /work-on pipeline,"
104
+ echo " or comments were posted before pipeline annotation was introduced."
105
+ exit 0
106
+ fi
107
+
108
+ echo ""
109
+ echo " Found ${FORGE_COUNT} pipeline annotation(s). Playing back..."
110
+ echo ""
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Step 5: Display Each Annotation
116
+
117
+ Phase mapping (inline): `INVESTIGATOR`→Phase 1, `CONTRACT`→Phase 3C, `CONTEXT`→Phase 3C.5, `ARCHITECT`→Phase 3C.6, `BUILDER`→Phase 3M, `DECOMPOSED`→Phase 2, `TRAJECTORY`→Phase 7.
118
+
119
+ For each FORGE comment in chronological order, print a formatted block. Use Python for robust multiline extraction from the comment body.
120
+
121
+ ```bash
122
+ echo "$FORGE_COMMENTS" | jq -c '.[]' | while IFS= read -r COMMENT; do
123
+ BODY=$(echo "$COMMENT" | jq -r '.body')
124
+ CREATED=$(echo "$COMMENT" | jq -r '.created_at')
125
+ AUTHOR=$(echo "$COMMENT" | jq -r '.user.login')
126
+
127
+ # Identify annotation type
128
+ ANNOTATION=$(echo "$BODY" | grep -oP '(?<=<!-- )FORGE:[A-Z:]+(?= -->| -->)' | head -1)
129
+
130
+ # Map to phase label
131
+ case "$ANNOTATION" in
132
+ "FORGE:INVESTIGATOR") PHASE_LABEL="Phase 1 — Investigation" ;;
133
+ "FORGE:CONTRACT") PHASE_LABEL="Phase 3C — Builder Contract" ;;
134
+ "FORGE:CONTEXT") PHASE_LABEL="Phase 3C.5 — Context Gathering" ;;
135
+ "FORGE:ARCHITECT") PHASE_LABEL="Phase 3C.6 — Architecture Plan" ;;
136
+ "FORGE:BUILDER") PHASE_LABEL="Phase 3M — Implementation" ;;
137
+ "FORGE:DECOMPOSED") PHASE_LABEL="Phase 2 — Decomposition" ;;
138
+ "FORGE:TRAJECTORY") PHASE_LABEL="Phase 7 — Pipeline Trajectory" ;;
139
+ *) PHASE_LABEL="$ANNOTATION" ;;
140
+ esac
141
+
142
+ # Format timestamp: 2025-01-15T14:32:00Z → 2025-01-15 14:32 UTC
143
+ TIMESTAMP=$(echo "$CREATED" | sed 's/T/ /; s/:[0-9][0-9]Z/ UTC/')
144
+
145
+ echo "┌─────────────────────────────────────────────────────────────"
146
+ echo "│ ${PHASE_LABEL}"
147
+ echo "│ ${TIMESTAMP} · @${AUTHOR}"
148
+ echo "├─────────────────────────────────────────────────────────────"
149
+
150
+ # Extract key fields per annotation type
151
+ ANNOTATION="$ANNOTATION" BODY_CONTENT="$BODY" python3 - <<'PYEOF'
152
+ import re, sys, os
153
+
154
+ body = os.environ.get('BODY_CONTENT', '')
155
+
156
+ def extract(pattern, default="—"):
157
+ m = re.search(pattern, body, re.MULTILINE | re.DOTALL)
158
+ return m.group(1).strip() if m else default
159
+
160
+ def count_lines(header):
161
+ m = re.search(r'### ' + header + r'\n(.*?)(?=\n###|\Z)', body, re.DOTALL)
162
+ if not m: return 0
163
+ return len([l for l in m.group(1).strip().splitlines() if l.strip()])
164
+
165
+ annotation = os.environ.get('ANNOTATION', '')
166
+
167
+ if annotation == "FORGE:INVESTIGATOR":
168
+ verdict = extract(r'\*\*Verdict\*\*:\s*(\S+)')
169
+ confidence = extract(r'\*\*Confidence\*\*:\s*(\S+)')
170
+ severity = extract(r'\*\*Severity\*\*:\s*(\S+)')
171
+ task_type = extract(r'\*\*Task Type\*\*:\s*([^\n]+)')
172
+ root_cause = extract(r'### Root Cause\n([^\n]+)')
173
+ decompose = extract(r'\*\*(YES|NO)\*\* —', '—')
174
+ print(f"│ Verdict : {verdict} ({confidence} confidence)")
175
+ print(f"│ Severity : {severity} | Task type: {task_type}")
176
+ print(f"│ Root cause : {root_cause[:80]}")
177
+ print(f"│ Decompose : {decompose}")
178
+
179
+ elif annotation == "FORGE:CONTRACT":
180
+ task_type = extract(r'\*\*Task type\*\*:\s*([^\n]+)')
181
+ approach = extract(r'### Proposed Approach\n([^\n]+)')
182
+ n_files = len(re.findall(r'^\|[^|]+\|[^|]+\|[^|]+\|', body, re.MULTILINE)) - 2 # subtract header+sep
183
+ n_criteria = len(re.findall(r'^- \[', body, re.MULTILINE))
184
+ print(f"│ Task type : {task_type}")
185
+ print(f"│ Approach : {approach[:80]}")
186
+ print(f"│ Deliverables: {max(0,n_files)} file(s) | Criteria: {n_criteria}")
187
+
188
+ elif annotation == "FORGE:CONTEXT":
189
+ pitfalls = extract(r'### Known Pitfalls[^\n]*\n([^\n]+)')
190
+ print(f"│ Pitfalls : {pitfalls[:80]}")
191
+ print(f"│ (Context gathered — see full comment for details)")
192
+
193
+ elif annotation == "FORGE:ARCHITECT":
194
+ n_paths = len(re.findall(r'^\| \d+', body, re.MULTILINE))
195
+ order_m = re.search(r'### Implementation Order\n(.*?)(?=\n###|\Z)', body, re.DOTALL)
196
+ n_steps = len([l for l in (order_m.group(1).strip().splitlines() if order_m else []) if re.match(r'^\d+\.', l.strip())])
197
+ print(f"│ Paths : {n_paths} affected path(s)")
198
+ print(f"│ Order : {n_steps} implementation step(s)")
199
+ print(f"│ (Full plan in comment)")
200
+
201
+ elif annotation == "FORGE:BUILDER":
202
+ branch = extract(r'\*\*Branch\*\*:\s*`([^`]+)`')
203
+ commits = extract(r'\*\*Commits\*\*:\s*([^\n]+)')
204
+ n_files = extract(r'\*\*Files changed\*\*:\s*(\d+)')
205
+ approach = extract(r'### Approach\n([^\n]+)')
206
+ print(f"│ Branch : {branch}")
207
+ print(f"│ Commits : {commits}")
208
+ print(f"│ Files : {n_files}")
209
+ print(f"│ Approach : {approach[:80]}")
210
+
211
+ elif annotation == "FORGE:DECOMPOSED":
212
+ subs_m = re.search(r'### Sub-Issues Created\n(.*?)(?=\n###|\Z)', body, re.DOTALL)
213
+ subs = [l.strip() for l in (subs_m.group(1).strip().splitlines() if subs_m else []) if l.strip().startswith('-')]
214
+ print(f"│ Sub-issues : {len(subs)}")
215
+ for s in subs[:5]:
216
+ print(f"│ {s}")
217
+
218
+ elif annotation == "FORGE:TRAJECTORY":
219
+ # Print the phase table lines verbatim (they're already formatted)
220
+ in_table = False
221
+ count = 0
222
+ for line in body.splitlines():
223
+ if '| Phase' in line or (in_table and line.startswith('|')):
224
+ in_table = True
225
+ print(f"│ {line}")
226
+ count += 1
227
+ elif in_table and not line.startswith('|'):
228
+ break
229
+ if count == 0:
230
+ print("│ (Trajectory table not found in comment)")
231
+ decisions = extract(r'\*\*Decisions\*\*:\s*([^\n]+)')
232
+ anomalies = extract(r'\*\*Anomalies\*\*:\s*([^\n]+)')
233
+ print(f"│ Decisions : {decisions[:80]}")
234
+ print(f"│ Anomalies : {anomalies[:80]}")
235
+
236
+ else:
237
+ # Unknown annotation — print first non-empty, non-marker line
238
+ for line in body.splitlines():
239
+ stripped = line.strip()
240
+ if stripped and not stripped.startswith('<!--') and not stripped.startswith('#'):
241
+ print(f"│ {stripped[:100]}")
242
+ break
243
+ PYEOF
244
+
245
+ echo "└─────────────────────────────────────────────────────────────"
246
+ echo ""
247
+ done
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Step 7: Summary Footer
253
+
254
+ ```bash
255
+ echo "══════════════════════════════════════════════════════════════"
256
+ echo " Replay complete — ${FORGE_COUNT} pipeline phase(s) shown"
257
+ echo " Issue: https://github.com/${GH_REPO}/issues/${NUMBER}"
258
+ echo "══════════════════════════════════════════════════════════════"
259
+ ```
260
+
261
+ ---
262
+
263
+ **Constraints**: Read-only — never writes to GitHub, never posts comments, never edits labels. Partial pipeline runs are shown as-is (missing phases are absent, not errors).
@@ -0,0 +1,264 @@
1
+ ---
2
+ description: Recover full pipeline context after compaction — find the last active issue, reconstruct FORGE annotations, and resume work
3
+ argument-hint: [issue number (optional — auto-detects if omitted)]
4
+ ---
5
+
6
+ # /resume — Context Recovery After Compaction
7
+
8
+ **Input**: $ARGUMENTS
9
+
10
+ You are the context-recovery agent. When a session ends or context is compressed, all working state is lost. Your job is to restore it instantly — find the most recently active issue, read all FORGE annotations, reconstruct the current pipeline phase, and re-enter the pipeline exactly where it left off.
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
+ ## Phase 0: Config Resolution
18
+
19
+ Read `forge.yaml` to configure all project-specific variables before running any commands:
20
+
21
+ ```bash
22
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
23
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
24
+ GH_FLAG="-R $GH_REPO"
25
+ REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
26
+ STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
27
+ ```
28
+
29
+ If `forge.yaml` is missing: stop and tell the user to run `npx forgedock init` to generate it.
30
+
31
+ ---
32
+
33
+ ## Phase 1: Find Active Issue
34
+
35
+ ### 1A: Parse input
36
+
37
+ Check `$ARGUMENTS`:
38
+
39
+ - **Explicit issue number** (e.g., `610`, `#610`): strip `#`, set `TARGET_NUMBER={number}`, skip to Phase 2.
40
+ - **Empty**: proceed with auto-detection below.
41
+
42
+ ### 1B: Query for in-progress issues
43
+
44
+ The `gh issue list --label` flag uses AND filtering when multiple labels are given. To find issues with ANY active workflow label, run one query per label and merge results:
45
+
46
+ ```bash
47
+ # Find open issues with any active workflow label, sorted by most recently updated
48
+ # (gh --label uses AND, so query each label separately and merge)
49
+ ACTIVE_ISSUES=$(
50
+ for LABEL in "workflow:investigating" "workflow:ready-to-build" "workflow:building" "workflow:in-review"; do
51
+ gh issue list {GH_FLAG} \
52
+ --state open \
53
+ --label "$LABEL" \
54
+ --limit 10 \
55
+ --json number,title,labels,updatedAt
56
+ done | jq -s '
57
+ flatten |
58
+ unique_by(.number) |
59
+ sort_by(.updatedAt) | reverse |
60
+ .[] | {
61
+ number,
62
+ title,
63
+ phase: (.labels | map(.name | select(startswith("workflow:"))) | first // "unknown"),
64
+ updatedAt
65
+ }
66
+ ')
67
+ echo "$ACTIVE_ISSUES"
68
+ ```
69
+
70
+ ### 1C: Select issue
71
+
72
+ **If one result**: use it — set `TARGET_NUMBER={number}`.
73
+
74
+ **If multiple results**: display a ranked list (most recently updated first), then select the top entry:
75
+
76
+ ```
77
+ ## Active Issues Found (most recent first)
78
+
79
+ | # | Issue | Phase | Last Updated |
80
+ |---|-------|-------|--------------|
81
+ | 1 | #{number}: {title} | {phase} | {updatedAt} |
82
+ | 2 | #{number}: {title} | {phase} | {updatedAt} |
83
+ ...
84
+
85
+ Auto-selecting #1 (most recently updated). Pass an explicit issue number to override.
86
+ ```
87
+
88
+ Set `TARGET_NUMBER` to the top-ranked issue number.
89
+
90
+ ### 1D: Fallback — no active label found
91
+
92
+ If `ACTIVE_ISSUES` is empty, search recent issues by FORGE annotation presence:
93
+
94
+ ```bash
95
+ # Look for recently updated open issues with FORGE annotations (pipeline may have stalled pre-label)
96
+ RECENT_ISSUES=$(gh issue list {GH_FLAG} \
97
+ --state open \
98
+ --limit 20 \
99
+ --json number,title,updatedAt \
100
+ --jq 'sort_by(.updatedAt) | reverse | .[0:5] | .[] | {number, title, updatedAt}')
101
+
102
+ # For each candidate, check if it has FORGE annotations
103
+ for NUMBER in $(echo "$RECENT_ISSUES" | jq -r '.number'); do
104
+ HAS_FORGE=$(gh api repos/{GH_REPO}/issues/$NUMBER/comments \
105
+ --jq '[.[] | select(.body | test("<!-- FORGE:"))] | length > 0' 2>/dev/null || echo "false")
106
+ if [ "$HAS_FORGE" = "true" ]; then
107
+ TARGET_NUMBER=$NUMBER
108
+ break
109
+ fi
110
+ done
111
+ ```
112
+
113
+ If still no candidate: display `No recent active issues found. To resume work on a specific issue, run: /resume {issue-number}` and STOP.
114
+
115
+ ---
116
+
117
+ ## Phase 2: Reconstruct Context
118
+
119
+ ### 2A: Load issue state
120
+
121
+ ```bash
122
+ ISSUE=$(gh issue view {TARGET_NUMBER} {GH_FLAG} \
123
+ --json number,title,body,labels,state,milestone,updatedAt)
124
+
125
+ ISSUE_TITLE=$(echo "$ISSUE" | jq -r '.title')
126
+ ISSUE_STATE=$(echo "$ISSUE" | jq -r '.state')
127
+ ISSUE_LABELS=$(echo "$ISSUE" | jq -r '[.labels[].name] | join(", ")')
128
+ ISSUE_MILESTONE=$(echo "$ISSUE" | jq -r '.milestone.title // "none"')
129
+
130
+ echo "Issue: #${TARGET_NUMBER} — $ISSUE_TITLE"
131
+ echo "State: $ISSUE_STATE | Labels: $ISSUE_LABELS | Milestone: $ISSUE_MILESTONE"
132
+ ```
133
+
134
+ If issue is CLOSED and has `workflow:merged` or `workflow:invalid` label: display completion status and STOP — no work remaining.
135
+
136
+ ### 2B: Read all FORGE annotations
137
+
138
+ Fetch all comments and extract the FORGE-annotated ones in pipeline order:
139
+
140
+ ```bash
141
+ COMMENTS=$(gh api repos/{GH_REPO}/issues/{TARGET_NUMBER}/comments \
142
+ --jq '.[] | select(.body | test("<!-- FORGE:")) | {
143
+ id: .id,
144
+ created_at: .created_at,
145
+ type: (.body | capture("<!-- FORGE:(?<t>[A-Z_:]+)") | .t),
146
+ body: .body
147
+ }')
148
+ ```
149
+
150
+ Extract specific annotations:
151
+
152
+ ```bash
153
+ # Investigation report
154
+ FORGE_INVESTIGATOR=$(gh api repos/{GH_REPO}/issues/{TARGET_NUMBER}/comments \
155
+ --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body' 2>/dev/null || echo "")
156
+
157
+ # Builder contract
158
+ FORGE_CONTRACT=$(gh api repos/{GH_REPO}/issues/{TARGET_NUMBER}/comments \
159
+ --jq '.[] | select(.body | contains("FORGE:CONTRACT")) | .body' 2>/dev/null || echo "")
160
+
161
+ # Implementation context
162
+ FORGE_CONTEXT=$(gh api repos/{GH_REPO}/issues/{TARGET_NUMBER}/comments \
163
+ --jq '.[] | select(.body | contains("FORGE:CONTEXT")) | .body' 2>/dev/null || echo "")
164
+
165
+ # Architecture plan
166
+ FORGE_ARCHITECT=$(gh api repos/{GH_REPO}/issues/{TARGET_NUMBER}/comments \
167
+ --jq '.[] | select(.body | contains("FORGE:ARCHITECT")) | .body' 2>/dev/null || echo "")
168
+
169
+ # Builder implementation report
170
+ FORGE_BUILDER=$(gh api repos/{GH_REPO}/issues/{TARGET_NUMBER}/comments \
171
+ --jq '.[] | select(.body | contains("FORGE:BUILDER")) | .body' 2>/dev/null || echo "")
172
+ ```
173
+
174
+ ### 2C: Determine current pipeline phase
175
+
176
+ Read labels and annotation presence to identify where the pipeline was interrupted:
177
+
178
+ ```bash
179
+ # Extract active workflow label
180
+ WORKFLOW_LABEL=$(echo "$ISSUE_LABELS" | grep -oP 'workflow:[a-z:-]+' | grep -v 'workflow:merged\|workflow:invalid\|workflow:decomposed' | head -1)
181
+
182
+ # Check annotation completion markers
183
+ HAS_INVESTIGATION=$([ -n "$FORGE_INVESTIGATOR" ] && echo "$FORGE_INVESTIGATOR" | grep -q "INVESTIGATION:COMPLETE" && echo "YES" || echo "NO")
184
+ HAS_CONTRACT=$([ -n "$FORGE_CONTRACT" ] && echo "YES" || echo "NO")
185
+ HAS_CONTEXT=$([ -n "$FORGE_CONTEXT" ] && echo "YES" || echo "NO")
186
+ HAS_ARCHITECT=$([ -n "$FORGE_ARCHITECT" ] && echo "YES" || echo "NO")
187
+ HAS_BUILDER=$([ -n "$FORGE_BUILDER" ] && echo "$FORGE_BUILDER" | grep -q "FORGE:BUILDER:COMPLETE" && echo "YES" || echo "NO")
188
+
189
+ # Check for open PR on this issue's branch
190
+ OPEN_PR=$(gh pr list {GH_FLAG} --state open --search "#{TARGET_NUMBER}" --limit 1 --json number,headRefName --jq '.[0]' 2>/dev/null || echo "")
191
+ ```
192
+
193
+ Map to current phase:
194
+
195
+ | Condition | Current Phase | Resume Action |
196
+ |-----------|---------------|---------------|
197
+ | `HAS_INVESTIGATION=NO` | Phase 1: Investigating | Reinvestigate |
198
+ | `HAS_INVESTIGATION=YES`, `WORKFLOW_LABEL=workflow:ready-to-build` | Phase 3: Build | Enter build from 3A |
199
+ | `HAS_INVESTIGATION=YES`, `HAS_BUILDER=NO`, `WORKFLOW_LABEL=workflow:building` | Phase 3: Build in progress | Enter build from 3A (will skip completed sub-phases) |
200
+ | `HAS_BUILDER=YES`, PR not found | Phase 4: PR Creation | Push branch + create PR |
201
+ | `OPEN_PR` found, `WORKFLOW_LABEL=workflow:in-review` | Phase 5: Auto-Review | Invoke review-pr |
202
+ | PR merged, issue still open | Phase 6: Close | Close issue, update board |
203
+
204
+ ### 2D: Display context summary
205
+
206
+ Emit a structured context block before resuming — this is what anchors the agent's working memory:
207
+
208
+ ```
209
+ ## Context Recovered — Issue #${TARGET_NUMBER}
210
+
211
+ **Title**: {ISSUE_TITLE}
212
+ **Lane**: {fast-lane | feature-lane (milestone: {MILESTONE})}
213
+ **Current Phase**: {CURRENT_PHASE}
214
+ **Active Label**: {WORKFLOW_LABEL}
215
+
216
+ ### Investigation Summary
217
+ {if HAS_INVESTIGATION=YES: extract "### What We Found" + "### Root Cause" + "### Affected Files" blocks from FORGE_INVESTIGATOR}
218
+ {if HAS_INVESTIGATION=NO: "Not yet investigated."}
219
+
220
+ ### Contract
221
+ {if HAS_CONTRACT=YES: extract "### Proposed Approach" + "### Deliverables" table from FORGE_CONTRACT}
222
+ {if HAS_CONTRACT=NO: "Contract not yet written."}
223
+
224
+ ### Architecture Plan
225
+ {if HAS_ARCHITECT=YES: extract "### Affected Paths" table + "### Implementation Order" from FORGE_ARCHITECT}
226
+ {if HAS_ARCHITECT=NO: "Architecture plan not yet written."}
227
+
228
+ ### Build Status
229
+ {if HAS_BUILDER=YES: extract "### Changes" + "### Acceptance Criteria Status" from FORGE_BUILDER}
230
+ {if HAS_BUILDER=NO: "Implementation not yet started."}
231
+
232
+ ### Open PR
233
+ {if OPEN_PR: "PR #{pr_number} → {headRefName} (open)"}
234
+ {if not OPEN_PR: "No PR yet."}
235
+
236
+ ### Resume Entry Point
237
+ → Will invoke /work-on {TARGET_NUMBER} — pipeline re-enters at {CURRENT_PHASE}
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Phase 3: Resume
243
+
244
+ Invoke `/work-on` to re-enter the pipeline. Work-on reads GitHub state independently and routes to the correct phase automatically.
245
+
246
+ ```
247
+ Skill("work-on", args="{TARGET_NUMBER}")
248
+ ```
249
+
250
+ **Why delegation works**: `/work-on` Phase 0B already implements "Determine resume point" — it reads issue labels and FORGE annotations to route to the correct phase. `/resume` provides the human-readable context summary, then hands off to `/work-on` for execution.
251
+
252
+ **Do NOT re-implement pipeline logic here.** The context summary above is for the agent's working memory only — the actual phase routing and execution is owned by `/work-on`.
253
+
254
+ ---
255
+
256
+ ## Error Handling
257
+
258
+ | Condition | Response |
259
+ |-----------|----------|
260
+ | `forge.yaml` missing | Stop: "Run \`npx forgedock init\` to generate forge.yaml" |
261
+ | No active issues + no FORGE annotations found | Stop: "No recent active issues found. Run \`/resume {number}\` with an explicit issue number, or \`/work-on next\` to start a new issue." |
262
+ | Issue is closed with `workflow:merged` | Stop: "Issue #{number} is already merged. Nothing to resume." |
263
+ | Issue is closed with `workflow:invalid` | Stop: "Issue #{number} was closed as invalid. Nothing to resume." |
264
+ | Issue has `needs-human` label | Stop: "Issue #{number} is blocked (needs-human). Review the blocking comment and resolve it manually before resuming." |
@@ -1,3 +1,6 @@
1
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
2
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
3
+
1
4
  # Agent Catalog for `/review-pr`
2
5
 
3
6
  This file is the Agent Catalog referenced by the `/review-pr` orchestrator. It is read via the `Read` tool during Phase 3C (agent dispatch). It contains:
@@ -1171,9 +1174,10 @@ If no API context is configured above, derive conventions from the changed files
1171
1174
  BASE=$(gh pr view [PR_NUMBER] --json baseRefName --jq '.baseRefName')
1172
1175
 
1173
1176
  # Find PRs merged to this base in the last 48 hours
1177
+ CUTOFF="$(date -u -d '48 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-48H +%Y-%m-%dT%H:%M:%SZ)"
1174
1178
  RECENT_MERGED=$(gh pr list --base "$BASE" --state merged --limit 20 \
1175
1179
  --json number,title,mergedAt,files \
1176
- --jq --arg cutoff "$(date -u -d '48 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-48H +%Y-%m-%dT%H:%M:%SZ)" \
1180
+ 2>/dev/null | jq --arg cutoff "$CUTOFF" \
1177
1181
  '.[] | select(.mergedAt > $cutoff) | {number, title, schema_files: [.files[].path | select(test("schemas?/|scrape\\.py|models\\.py"))]}' \
1178
1182
  2>/dev/null | head -40)
1179
1183
 
@@ -3,6 +3,8 @@ description: Staging review mode — comprehensive review of staging branch befo
3
3
  argument-hint: [PR number or "staging"]
4
4
  allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch
5
5
  ---
6
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
7
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
6
8
 
7
9
  # Staging Review
8
10