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.
- package/README.md +21 -3
- package/bin/forgedock.mjs +1992 -540
- package/bin/github-app-manifest.json.license +2 -0
- package/bin/hooks/session-start.mjs +4 -5
- package/bin/registry.mjs +20 -0
- package/bin/tui.mjs +172 -96
- package/commands/adopt.md +433 -0
- package/commands/analytics.md +2 -0
- package/commands/audit-agents.md +2 -0
- package/commands/audit.md +2 -0
- package/commands/changelog.md +354 -0
- package/commands/cleanup.md +2 -0
- package/commands/compat-audit.md +330 -0
- package/commands/deploy-info.md +6 -5
- package/commands/diagnose.md +483 -0
- package/commands/explain.md +349 -0
- package/commands/forge-stats.md +2 -0
- package/commands/forgedock-init.md +2 -0
- package/commands/geo-audit.md +2 -0
- package/commands/incident-response.md +2 -0
- package/commands/issue.md +2 -0
- package/commands/milestone.md +2 -0
- package/commands/optimize.md +411 -0
- package/commands/orchestrate.md +118 -0
- package/commands/pipeline-health.md +60 -1
- package/commands/qa-sweep.md +2 -0
- package/commands/quality-gate.md +37 -0
- package/commands/replay.md +263 -0
- package/commands/resume.md +264 -0
- package/commands/review-pr-agents.md +5 -1
- package/commands/review-pr-staging.md +2 -0
- package/commands/review-pr.md +85 -3
- package/commands/rollback.md +2 -0
- package/commands/scope.md +280 -0
- package/commands/security-audit.md +2 -0
- package/commands/status.md +262 -0
- package/commands/sync-ecosystem.md +2 -0
- package/commands/validate.md +2 -0
- package/commands/work-on/build/architect.md +128 -18
- package/commands/work-on/build/context.md +130 -17
- package/commands/work-on/build/implement.md +2 -0
- package/commands/work-on/build/validate.md +2 -0
- package/commands/work-on/build.md +32 -7
- package/commands/work-on/close.md +85 -0
- package/commands/work-on/decompose.md +2 -0
- package/commands/work-on/investigate.md +30 -3
- package/commands/work-on/review.md +13 -1
- package/commands/work-on-monolithic.md +2 -0
- package/commands/work-on.md +624 -36
- package/docs/BRAND.md +51 -0
- package/docs/CONFIG.md +94 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +174 -0
- package/docs/claude-breakpoints.json +47 -0
- package/docs/site/getting-started.md +21 -0
- package/package.json +3 -3
- package/scripts/classify-lane.sh +145 -0
- package/scripts/transition-label.sh +161 -0
- package/scripts/validate-pr-target.sh +30 -0
- package/templates/devdocs/README.md +72 -0
- package/templates/devdocs/agent/using-forgedock.md +3 -0
- package/templates/devdocs/agent/using-github.md +3 -0
- package/templates/devdocs/index.yaml +162 -0
- package/templates/devdocs/project/architecture.md +3 -0
- package/templates/devdocs/project/conventions.md +3 -0
- package/templates/devdocs/project/custom-instructions.md +3 -0
- package/templates/devdocs/project/glossary.md +3 -0
- package/templates/devdocs/project/stack.md +3 -0
- package/templates/devdocs/templates/commit-convention.md +3 -0
- package/templates/devdocs/templates/issue.md +3 -0
- package/templates/devdocs/templates/pull-request.md +3 -0
- package/templates/devdocs/templates/review-finding.md +3 -0
package/commands/quality-gate.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|