forgedock 1.0.0
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/LICENSE +244 -0
- package/README.md +251 -0
- package/bin/forgedock.mjs +232 -0
- package/commands/analytics.md +777 -0
- package/commands/audit-agents.md +410 -0
- package/commands/audit.md +410 -0
- package/commands/autopilot.md +432 -0
- package/commands/cleanup.md +264 -0
- package/commands/deploy-info.md +292 -0
- package/commands/failure-recon.md +375 -0
- package/commands/forge-stats.md +183 -0
- package/commands/geo-audit.md +343 -0
- package/commands/incident-response.md +384 -0
- package/commands/issue.md +506 -0
- package/commands/milestone.md +599 -0
- package/commands/orchestrate.md +1044 -0
- package/commands/pipeline-health.md +2343 -0
- package/commands/qa-sweep.md +237 -0
- package/commands/quality-gate.md +777 -0
- package/commands/review-pr-agents.md +1711 -0
- package/commands/review-pr-staging.md +325 -0
- package/commands/review-pr.md +888 -0
- package/commands/rollback.md +253 -0
- package/commands/security-audit.md +529 -0
- package/commands/sync-ecosystem.md +527 -0
- package/commands/validate.md +131 -0
- package/commands/work-on/build/architect.md +291 -0
- package/commands/work-on/build/context.md +349 -0
- package/commands/work-on/build/implement.md +280 -0
- package/commands/work-on/build/validate.md +292 -0
- package/commands/work-on/build.md +262 -0
- package/commands/work-on/close.md +350 -0
- package/commands/work-on/decompose.md +254 -0
- package/commands/work-on/investigate.md +437 -0
- package/commands/work-on/review.md +245 -0
- package/commands/work-on-monolithic.md +298 -0
- package/commands/work-on.md +1089 -0
- package/docs/CODEX.md +92 -0
- package/docs/WORKFLOW.md +104 -0
- package/package.json +45 -0
- package/scripts/verify-env-vars.sh +201 -0
- package/scripts/verify-host-headers.sh +117 -0
- package/scripts/verify-route-registration.sh +118 -0
- package/scripts/verify-sops-chain.sh +163 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Decompose subcommand — break a complex issue into ordered sub-issues, post FORGE:DECOMPOSED, stop
|
|
3
|
+
argument-hint: [issue number] [--repo GH_REPO] [--gh-flag GH_FLAG]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# work-on/decompose — Decomposition Subcommand
|
|
7
|
+
|
|
8
|
+
**Input**: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
**Invoked by**: `work-on.md` routing loop, when `INVESTIGATE_RESULT.decompose = YES`.
|
|
11
|
+
**Output**: Create sub-issues, update parent tracker, post `<!-- FORGE:DECOMPOSED -->` comment, set labels. STOP — each sub-issue runs its own /work-on.
|
|
12
|
+
|
|
13
|
+
**Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
|
|
14
|
+
**NEVER use plan mode (EnterPlanMode).**
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Inputs
|
|
19
|
+
|
|
20
|
+
Parse from $ARGUMENTS:
|
|
21
|
+
- `{NUMBER}` — issue number (required)
|
|
22
|
+
- `--repo {GH_REPO}` — GitHub repo (e.g. `RapierCraftStudios/forge`)
|
|
23
|
+
- `--gh-flag {GH_FLAG}` — gh CLI repo flag (e.g. `-R RapierCraftStudios/forge`)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Phase D0: Load State from GitHub (MANDATORY)
|
|
28
|
+
|
|
29
|
+
Re-read current state before doing anything:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,milestone
|
|
33
|
+
|
|
34
|
+
# Read investigation report (required — contains decomposition plan)
|
|
35
|
+
gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
36
|
+
--jq '.[] | select(.body | (contains("FORGE:INVESTIGATOR") or contains("ALTERLAB:INVESTIGATOR"))) | .body'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Validation**:
|
|
40
|
+
- If FORGE:INVESTIGATOR / ALTERLAB:INVESTIGATOR comment is absent → EXIT with `DECOMPOSE_RESULT: status: BLOCKED`, blocker: "No investigation report found — run investigate first"
|
|
41
|
+
- If investigation report has no Decomposition Assessment section, OR the assessment does not list any sub-issues → EXIT with `DECOMPOSE_RESULT: status: BLOCKED`, blocker: "Investigation report has no decomposition plan — re-run investigate with explicit decomposition scope"
|
|
42
|
+
|
|
43
|
+
Extract from investigation report:
|
|
44
|
+
- Decomposition Assessment section: list of proposed sub-issues with titles and dependencies
|
|
45
|
+
- Milestone (from issue metadata)
|
|
46
|
+
- Priority label (P0/P1/P2) from issue labels
|
|
47
|
+
|
|
48
|
+
Extract milestone title for sub-issue creation:
|
|
49
|
+
```bash
|
|
50
|
+
MILESTONE_TITLE=$(gh issue view {NUMBER} {GH_FLAG} --json milestone --jq '.milestone.title // empty')
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Phase D1: Resume Check
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
59
|
+
--jq '.[] | select(.body | (contains("FORGE:DECOMPOSED") or contains("ALTERLAB:DECOMPOSED"))) | .body'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- If `<!-- FORGE:DECOMPOSED -->` or `<!-- ALTERLAB:DECOMPOSED -->` comment exists → decomposition already complete. EXIT with `DECOMPOSE_RESULT: status: ALREADY_DONE`.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Phase D1.5: Collect Parent Knowledge Gist URLs
|
|
67
|
+
|
|
68
|
+
Query the parent issue's comments for `FORGE:KNOWLEDGE_GIST` annotations created by Phase 1C.5 of the investigation. These URLs will be embedded in each sub-issue body so downstream agents can fetch prior investigation context.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
GIST_URLS=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
72
|
+
--jq '[.[] | select(.body | test("<!-- FORGE:KNOWLEDGE_GIST: https://")) | .body | capture("<!-- FORGE:KNOWLEDGE_GIST: (?<url>https://[^ ]+) -->").url] | unique | .[]')
|
|
73
|
+
|
|
74
|
+
if [ -n "$GIST_URLS" ]; then
|
|
75
|
+
echo "Found Knowledge Gist URL(s) on parent issue #${NUMBER}:"
|
|
76
|
+
echo "$GIST_URLS"
|
|
77
|
+
else
|
|
78
|
+
echo "No Knowledge Gist annotations found on parent issue #${NUMBER} — sub-issues will not include Prior Investigation section"
|
|
79
|
+
fi
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If `GIST_URLS` is non-empty, a `## Prior Investigation` section will be appended to each sub-issue body in Phase D3.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Phase D2: Design Sub-Issues
|
|
87
|
+
|
|
88
|
+
From the Decomposition Assessment in the investigation report, extract:
|
|
89
|
+
1. Sub-issue titles (in dependency order — independent issues first)
|
|
90
|
+
2. Dependencies between sub-issues (if issue B depends on issue A, A is created first)
|
|
91
|
+
3. Brief description for each sub-issue body
|
|
92
|
+
|
|
93
|
+
For each sub-issue, prepare:
|
|
94
|
+
- **Title**: from investigation report's proposed sub-issue title
|
|
95
|
+
- **Body**: brief description of scope + `**Parent**: #{NUMBER}` + dependency note if applicable
|
|
96
|
+
- **Labels**: inherit priority label (P0/P1/P2) from parent; do NOT copy workflow labels
|
|
97
|
+
- **Milestone**: same milestone title as parent (if parent has one)
|
|
98
|
+
|
|
99
|
+
**Ordering rule**: Create independent sub-issues first. If sub-issue B depends on A, create A first so its issue number can be referenced in B's body.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Phase D3: Create Sub-Issues
|
|
104
|
+
|
|
105
|
+
For each sub-issue (in dependency order):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
gh issue create {GH_FLAG} \
|
|
109
|
+
--title "{fix|feat|refactor}: {SUB_ISSUE_TITLE}" \
|
|
110
|
+
--body "$(cat <<'SUB_BODY_EOF'
|
|
111
|
+
## Problem
|
|
112
|
+
|
|
113
|
+
{1-3 sentences: what this sub-issue specifically addresses. What's wrong or what needs to be built for this sub-task.}
|
|
114
|
+
|
|
115
|
+
## Root Cause (if known)
|
|
116
|
+
|
|
117
|
+
{Specific root cause for this sub-task from the parent investigation. If unknown: "Root cause unknown — investigation needed."}
|
|
118
|
+
|
|
119
|
+
## Affected Files
|
|
120
|
+
|
|
121
|
+
Files that need changes:
|
|
122
|
+
1. `{filepath}` — {what needs to change}
|
|
123
|
+
2. `{filepath}` — {what needs to change}
|
|
124
|
+
|
|
125
|
+
## Acceptance Criteria
|
|
126
|
+
|
|
127
|
+
- [ ] {Specific, testable criterion}
|
|
128
|
+
- [ ] {Specific, testable criterion}
|
|
129
|
+
- [ ] No regression in {related feature}
|
|
130
|
+
|
|
131
|
+
## Context
|
|
132
|
+
|
|
133
|
+
**Parent**: #{NUMBER}
|
|
134
|
+
{If depends on another sub-issue: "**Depends on**: #{SUB_ISSUE_N} — {reason}"}
|
|
135
|
+
SUB_BODY_EOF
|
|
136
|
+
)" \
|
|
137
|
+
--label "{PRIORITY_LABEL}" \
|
|
138
|
+
--milestone "{MILESTONE_TITLE}"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Append Prior Investigation section** (conditional — only if `GIST_URLS` from Phase D1.5 is non-empty):
|
|
142
|
+
|
|
143
|
+
After creating each sub-issue, append the `## Prior Investigation` section containing all parent Gist URLs. This keeps the Gist references machine-readable for downstream agents.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
if [ -n "$GIST_URLS" ]; then
|
|
147
|
+
SUB_BODY=$(gh issue view {SUB_NUMBER} {GH_FLAG} --json body --jq '.body')
|
|
148
|
+
|
|
149
|
+
PRIOR_SECTION="
|
|
150
|
+
|
|
151
|
+
## Prior Investigation
|
|
152
|
+
|
|
153
|
+
Investigation findings from the parent issue are available as Knowledge Gists:
|
|
154
|
+
"
|
|
155
|
+
while IFS= read -r url; do
|
|
156
|
+
PRIOR_SECTION="${PRIOR_SECTION}
|
|
157
|
+
<!-- FORGE:PRIOR_GIST: ${url} -->
|
|
158
|
+
- ${url}"
|
|
159
|
+
done <<< "$GIST_URLS"
|
|
160
|
+
|
|
161
|
+
gh issue edit {SUB_NUMBER} {GH_FLAG} --body "${SUB_BODY}${PRIOR_SECTION}"
|
|
162
|
+
fi
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Capture the created issue number from the output URL for the tracker checklist.
|
|
166
|
+
|
|
167
|
+
If `--milestone` flag fails (milestone not found by name): omit the flag and note in the FORGE:DECOMPOSED comment that milestone assignment was skipped.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Phase D4: Update Parent Issue Body
|
|
172
|
+
|
|
173
|
+
Add a tracker checklist to the parent issue body showing all sub-issues in dependency order:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
CURRENT_BODY=$(gh issue view {NUMBER} {GH_FLAG} --json body --jq '.body')
|
|
177
|
+
|
|
178
|
+
TRACKER="
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Sub-Issue Tracker
|
|
183
|
+
|
|
184
|
+
{if sub-issue B depends on A, note it inline}
|
|
185
|
+
- [ ] #{SUB_ISSUE_1_NUMBER} — {SUB_ISSUE_1_TITLE}
|
|
186
|
+
- [ ] #{SUB_ISSUE_2_NUMBER} — {SUB_ISSUE_2_TITLE} _(depends on #{SUB_ISSUE_1_NUMBER})_
|
|
187
|
+
..."
|
|
188
|
+
|
|
189
|
+
gh issue edit {NUMBER} {GH_FLAG} --body "${CURRENT_BODY}${TRACKER}"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Phase D5: Post FORGE:DECOMPOSED Comment
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:DECOMPOSED -->
|
|
198
|
+
## Decomposition Complete
|
|
199
|
+
|
|
200
|
+
This issue has been broken into sub-issues. Each sub-issue runs through its own /work-on pipeline independently.
|
|
201
|
+
|
|
202
|
+
### Sub-Issues Created
|
|
203
|
+
|
|
204
|
+
{for each sub-issue, in dependency order:}
|
|
205
|
+
- #{SUB_ISSUE_NUMBER}: {TITLE}{if has dependency: _(depends on #{DEP_NUMBER})_}
|
|
206
|
+
|
|
207
|
+
### Decomposition Rationale
|
|
208
|
+
|
|
209
|
+
{brief summary of why this issue was decomposed and the dependency ordering chosen}
|
|
210
|
+
|
|
211
|
+
<!-- FORGE:DECOMPOSED:COMPLETE -->"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Phase D6: Update Labels
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
gh issue edit {NUMBER} {GH_FLAG} \
|
|
220
|
+
--add-label "workflow:decomposed" \
|
|
221
|
+
--remove-label "workflow:ready-to-build,workflow:building,workflow:investigating" 2>/dev/null || true
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Phase D7: STOP
|
|
227
|
+
|
|
228
|
+
Decomposition is a terminal route for the parent issue. Each sub-issue will be picked up separately by /work-on.
|
|
229
|
+
|
|
230
|
+
Return structured output to the router:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
DECOMPOSE_RESULT:
|
|
234
|
+
status: COMPLETE | ALREADY_DONE | BLOCKED
|
|
235
|
+
sub_issues: [{number}, ...]
|
|
236
|
+
comment_url: {url of FORGE:DECOMPOSED comment}
|
|
237
|
+
blocker: {description if status=BLOCKED}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Router behavior after DECOMPOSE_RESULT**: `break` — do not continue to build/review/close for the parent issue.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Output
|
|
245
|
+
|
|
246
|
+
The subcommand writes its results to GitHub (FORGE:DECOMPOSED comment + sub-issues created). The router breaks after this subcommand returns.
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
DECOMPOSE_RESULT:
|
|
250
|
+
status: COMPLETE | ALREADY_DONE | BLOCKED
|
|
251
|
+
sub_issues: [{number}, ...]
|
|
252
|
+
comment_url: {url of posted FORGE:DECOMPOSED comment}
|
|
253
|
+
blocker: {description if status=BLOCKED}
|
|
254
|
+
```
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Investigate a GitHub issue — validate it's real, determine root cause, post findings
|
|
3
|
+
argument-hint: [issue number] [--repo RapierCraftStudios/forge] [--gh-flag "-R RapierCraftStudios/forge"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /work-on:investigate — Issue Investigation Subcommand
|
|
7
|
+
|
|
8
|
+
**Input**: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Standalone investigation phase for the work-on pipeline. Validates whether an issue is real, determines root cause, posts a structured FORGE:INVESTIGATOR comment to GitHub, and updates workflow labels.
|
|
11
|
+
|
|
12
|
+
**Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
|
|
13
|
+
**NEVER use plan mode (EnterPlanMode).**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Inputs
|
|
18
|
+
|
|
19
|
+
Parse from $ARGUMENTS:
|
|
20
|
+
- `{NUMBER}` — issue number (required)
|
|
21
|
+
- `--repo {GH_REPO}` — GitHub repo (default: `RapierCraftStudios/forge` when running in Forge context; for AlterLab issues use `RapierCraft/AlterLab`)
|
|
22
|
+
- `--gh-flag {GH_FLAG}` — gh CLI repo flag (e.g. `-R RapierCraftStudios/forge`)
|
|
23
|
+
|
|
24
|
+
If called from `work-on`, these are passed through. If invoked standalone, defaults apply.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Phase 1A: Load Issue & Check Resume State
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,comments
|
|
32
|
+
gh api repos/{GH_REPO}/issues/{NUMBER}/comments --jq '.[] | {id: .id, body: .body}'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Resume logic**:
|
|
36
|
+
- If `<!-- FORGE:INVESTIGATOR -->` or `<!-- ALTERLAB:INVESTIGATOR -->` comment exists AND `<!-- INVESTIGATION:COMPLETE -->` is present in the SAME comment → investigation already complete, EXIT (return existing verdict to caller)
|
|
37
|
+
- If `<!-- FORGE:INVESTIGATOR -->` or `<!-- ALTERLAB:INVESTIGATOR -->` comment exists BUT `<!-- INVESTIGATION:COMPLETE -->` is ABSENT → investigation was interrupted, delete the partial comment and restart:
|
|
38
|
+
```bash
|
|
39
|
+
gh api repos/{GH_REPO}/issues/comments/{COMMENT_ID} -X DELETE
|
|
40
|
+
```
|
|
41
|
+
- If no investigator comment → proceed with fresh investigation
|
|
42
|
+
|
|
43
|
+
**Set label**:
|
|
44
|
+
```bash
|
|
45
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:investigating"
|
|
46
|
+
gh issue edit {NUMBER} {GH_FLAG} --remove-label "workflow:ready-to-build,workflow:building,workflow:in-review" 2>/dev/null || true
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Phase 1B: Investigate
|
|
52
|
+
|
|
53
|
+
**Mission**: Validate whether the issue is real. Assume description is wrong until proven otherwise.
|
|
54
|
+
|
|
55
|
+
### Resolve target repo and branch
|
|
56
|
+
|
|
57
|
+
For Forge issues (repo = `RapierCraftStudios/forge`):
|
|
58
|
+
- Key files: `commands/work-on.md`, `commands/review-pr.md`, `commands/quality-gate.md`, `commands/orchestrate.md`, `install.sh`, `CLAUDE.md`
|
|
59
|
+
|
|
60
|
+
For AlterLab issues (repo = `RapierCraft/AlterLab`):
|
|
61
|
+
|
|
62
|
+
| Domain | Key files |
|
|
63
|
+
|--------|-----------|
|
|
64
|
+
| BILLING | `routers/billing.py`, `core/pricing.py`, `services/credit_service.py` |
|
|
65
|
+
| SCRAPING | `unified_consumer.py`, `queues.py`, `domain_playbooks.json` |
|
|
66
|
+
| AUTH | `core/auth.py`, `routers/auth.py`, `dependencies.py` |
|
|
67
|
+
| DATABASE | `infra/migrations/`, `models/`, `db/` |
|
|
68
|
+
| FRONTEND | `web/src/app/`, `web/src/components/`, `web/src/lib/` |
|
|
69
|
+
| CORTEX | `cortex_client.py`, `routers/cortex.py` |
|
|
70
|
+
| INFRA | `.github/workflows/`, `docker-compose.yml`, `infra/traefik/` |
|
|
71
|
+
|
|
72
|
+
### Investigation steps
|
|
73
|
+
|
|
74
|
+
1. **Check the right branch** — read from the branch specified in the issue body (`**Code branch**: \`{branch}\``) if present
|
|
75
|
+
2. **Read domain files** — start with the key files for the affected domain
|
|
76
|
+
3. **Verify claims** — does the code actually have the problem described?
|
|
77
|
+
4. **Git blame** — trace when/why the relevant code was written
|
|
78
|
+
5. **Domain context discovery** (narrow scope only, 1–5 files):
|
|
79
|
+
```bash
|
|
80
|
+
git log --oneline --all -30 -- {affected_files} | grep -oP '#\d+' | sort -u
|
|
81
|
+
gh issue list -R {GH_REPO} --state closed --limit 8 --search "{function_name}"
|
|
82
|
+
```
|
|
83
|
+
Keep only file/function-level overlap. Max 5 related issues. Everything is a hint to verify, not a fact.
|
|
84
|
+
6. **Determine root cause** — what's actually broken or missing?
|
|
85
|
+
7. **Identify affected files** — full list of files that need changes
|
|
86
|
+
8. **Fix-approach validation** — if the issue proposes a fix, don't adopt it as spec. Trace through the target system's middleware, auth, routing, config. Cross-domain: if fix in domain A interacts with domain B, read domain B's files too.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Phase 1C: Post Investigation Comment
|
|
91
|
+
|
|
92
|
+
The comment MUST include `<!-- INVESTIGATION:COMPLETE -->` at the very end, AFTER all required sections are present. This marker signals the investigation finished successfully.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:INVESTIGATOR -->
|
|
96
|
+
## Investigation Report
|
|
97
|
+
|
|
98
|
+
**Verdict**: {CONFIRMED|PARTIAL|INVALID}
|
|
99
|
+
**Confidence**: {HIGH|MEDIUM|LOW}
|
|
100
|
+
**Severity**: {CRITICAL|HIGH|MEDIUM|LOW}
|
|
101
|
+
**Task Type**: {Bug Fix|Feature|Refactor|Maintenance|Investigation}
|
|
102
|
+
|
|
103
|
+
### What Was Claimed
|
|
104
|
+
{summary of what the issue describes}
|
|
105
|
+
|
|
106
|
+
### What We Found
|
|
107
|
+
{what the code actually shows}
|
|
108
|
+
|
|
109
|
+
### Root Cause
|
|
110
|
+
{specific root cause, with file:line references where applicable}
|
|
111
|
+
|
|
112
|
+
### Affected Files
|
|
113
|
+
{numbered list of files that need changes}
|
|
114
|
+
|
|
115
|
+
### Evidence
|
|
116
|
+
{specific findings — function names, line numbers, behavior observed}
|
|
117
|
+
|
|
118
|
+
### Recommendation
|
|
119
|
+
{what to build/fix, concrete and actionable}
|
|
120
|
+
|
|
121
|
+
### Related Issues
|
|
122
|
+
{if any found via domain context discovery, max 5}
|
|
123
|
+
|
|
124
|
+
### Decomposition Assessment
|
|
125
|
+
**{YES|NO}** — {reason}
|
|
126
|
+
{if YES: proposed sub-issues with titles and dependencies}
|
|
127
|
+
|
|
128
|
+
<!-- INVESTIGATION:COMPLETE -->"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Phase 1C.5: Create Knowledge Gist
|
|
134
|
+
|
|
135
|
+
**Skip if**: A comment containing `<!-- FORGE:KNOWLEDGE_GIST:` already exists on this issue.
|
|
136
|
+
|
|
137
|
+
After the FORGE:INVESTIGATOR comment is posted, create a structured GitHub Gist containing the investigation findings. The Gist provides a stable, linkable URL that downstream issues (siblings, children) can reference.
|
|
138
|
+
|
|
139
|
+
**This phase is non-blocking** — if Gist creation fails (auth error, rate limit, network), log the failure and continue to Phase 1D. Do NOT stall the pipeline for a knowledge artifact.
|
|
140
|
+
|
|
141
|
+
### Step 1: Check for existing Gist annotation
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
EXISTING_GIST=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
145
|
+
--jq '.[] | select(.body | contains("FORGE:KNOWLEDGE_GIST:")) | .body' | head -1)
|
|
146
|
+
|
|
147
|
+
if [ -n "$EXISTING_GIST" ]; then
|
|
148
|
+
echo "Knowledge Gist already exists — skipping creation"
|
|
149
|
+
# → Continue to Phase 1D
|
|
150
|
+
fi
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Step 2: Extract investigation content
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
INVESTIGATION_BODY=$(gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
|
|
157
|
+
--jq '.[] | select(.body | (contains("FORGE:INVESTIGATOR") or contains("ALTERLAB:INVESTIGATOR"))) | .body' | head -1)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Step 3: Generate filename and metadata
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
ISSUE_TITLE=$(gh issue view {NUMBER} {GH_FLAG} --json title --jq '.title')
|
|
164
|
+
MILESTONE=$(gh issue view {NUMBER} {GH_FLAG} --json milestone --jq '.milestone.title // "none"')
|
|
165
|
+
|
|
166
|
+
# Generate slug from title: lowercase, replace non-alphanumeric with hyphens, collapse, truncate
|
|
167
|
+
SLUG=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
|
168
|
+
|
|
169
|
+
# Derive repo short name from GH_REPO (e.g., "RapierCraftStudios/forge" → "forge")
|
|
170
|
+
REPO_SHORT=$(echo "{GH_REPO}" | sed 's|.*/||')
|
|
171
|
+
|
|
172
|
+
GIST_FILENAME="${REPO_SHORT}_${NUMBER}_${SLUG}.md"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Step 4: Build Gist content with frontmatter
|
|
176
|
+
|
|
177
|
+
Extract verdict, task type, and confidence from the investigation body, then compose the Gist:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
VERDICT=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Verdict\*\*: )\w+' | head -1)
|
|
181
|
+
TASK_TYPE=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Task Type\*\*: ).+' | head -1)
|
|
182
|
+
CONFIDENCE=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Confidence\*\*: )\w+' | head -1)
|
|
183
|
+
SEVERITY=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Severity\*\*: )\w+' | head -1)
|
|
184
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
185
|
+
|
|
186
|
+
GIST_CONTENT=$(cat <<GIST_EOF
|
|
187
|
+
---
|
|
188
|
+
issue: ${NUMBER}
|
|
189
|
+
repo: {GH_REPO}
|
|
190
|
+
milestone: ${MILESTONE}
|
|
191
|
+
verdict: ${VERDICT}
|
|
192
|
+
task_type: ${TASK_TYPE}
|
|
193
|
+
confidence: ${CONFIDENCE}
|
|
194
|
+
severity: ${SEVERITY}
|
|
195
|
+
created: ${TIMESTAMP}
|
|
196
|
+
source: FORGE:INVESTIGATOR
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
# Investigation: ${ISSUE_TITLE} (#${NUMBER})
|
|
200
|
+
|
|
201
|
+
${INVESTIGATION_BODY}
|
|
202
|
+
GIST_EOF
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Step 5: Create secret Gist
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
GIST_URL=$(echo "$GIST_CONTENT" | gh gist create \
|
|
210
|
+
-f "$GIST_FILENAME" \
|
|
211
|
+
-d "Investigation findings for ${REPO_SHORT}#${NUMBER}: ${ISSUE_TITLE}" \
|
|
212
|
+
- 2>/dev/null)
|
|
213
|
+
|
|
214
|
+
if [ -z "$GIST_URL" ]; then
|
|
215
|
+
echo "WARNING: Gist creation failed — continuing without knowledge artifact"
|
|
216
|
+
# → Continue to Phase 1D (non-blocking)
|
|
217
|
+
fi
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Step 6: Post Gist URL annotation
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
if [ -n "$GIST_URL" ]; then
|
|
224
|
+
gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:KNOWLEDGE_GIST: ${GIST_URL} -->
|
|
225
|
+
## Knowledge Gist Created
|
|
226
|
+
|
|
227
|
+
Investigation findings persisted as a linkable artifact.
|
|
228
|
+
|
|
229
|
+
**Gist**: ${GIST_URL}
|
|
230
|
+
**Filename**: \`${GIST_FILENAME}\`
|
|
231
|
+
|
|
232
|
+
_This Gist can be referenced by downstream issues for context transfer._"
|
|
233
|
+
fi
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
→ Continue to Phase 1C.6.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Phase 1C.6: Update Milestone Index Gist
|
|
241
|
+
|
|
242
|
+
**Skip if**: The issue has no milestone (`MILESTONE` is `"none"` or empty).
|
|
243
|
+
|
|
244
|
+
After the per-issue Knowledge Gist is created (Phase 1C.5), update the milestone-level index Gist. The index aggregates all investigation Gist URLs for a milestone into a single reference document. Any agent working on a milestone issue can fetch one index URL to get full context across all investigations.
|
|
245
|
+
|
|
246
|
+
**This phase is non-blocking** — if index creation or update fails, log the warning and continue to Phase 1D. Do NOT stall the pipeline for the index.
|
|
247
|
+
|
|
248
|
+
### Step 1: Check milestone and skip conditions
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
MILESTONE=$(gh issue view {NUMBER} {GH_FLAG} --json milestone --jq '.milestone.title // "none"')
|
|
252
|
+
MILESTONE_NUM=$(gh issue view {NUMBER} {GH_FLAG} --json milestone --jq '.milestone.number // empty')
|
|
253
|
+
|
|
254
|
+
if [ "$MILESTONE" = "none" ] || [ -z "$MILESTONE_NUM" ]; then
|
|
255
|
+
echo "No milestone on issue #${NUMBER} — skipping milestone index update"
|
|
256
|
+
# → Continue to Phase 1D
|
|
257
|
+
fi
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Step 2: Read milestone description for existing index
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
MILESTONE_DESC=$(gh api repos/{GH_REPO}/milestones/${MILESTONE_NUM} --jq '.description // ""')
|
|
264
|
+
EXISTING_INDEX_URL=$(echo "$MILESTONE_DESC" | grep -oP '(?<=<!-- FORGE:MILESTONE_INDEX: )https://[^ ]+(?= -->)' | head -1)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Step 3: Build index entry for this issue
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
ISSUE_TITLE=$(gh issue view {NUMBER} {GH_FLAG} --json title --jq '.title')
|
|
271
|
+
VERDICT=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Verdict\*\*: )\w+' | head -1)
|
|
272
|
+
SEVERITY=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Severity\*\*: )\w+' | head -1)
|
|
273
|
+
TASK_TYPE=$(echo "$INVESTIGATION_BODY" | grep -oP '(?<=\*\*Task Type\*\*: ).+' | head -1)
|
|
274
|
+
RECOMMENDATION=$(echo "$INVESTIGATION_BODY" | sed -n '/^### Recommendation/,/^### /p' | head -5 | tail -n +2 | tr '\n' ' ' | cut -c1-120)
|
|
275
|
+
|
|
276
|
+
# GIST_URL comes from Phase 1C.5 (may be empty if Gist creation failed)
|
|
277
|
+
INDEX_ENTRY="| #${NUMBER} | ${ISSUE_TITLE} | ${VERDICT} / ${SEVERITY} | ${TASK_TYPE} | ${GIST_URL:-_no gist_} | ${RECOMMENDATION:-_see investigation_} |"
|
|
278
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Step 4a: Create new index Gist (no existing index)
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
if [ -z "$EXISTING_INDEX_URL" ]; then
|
|
285
|
+
MILESTONE_SLUG=$(echo "$MILESTONE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
|
286
|
+
REPO_SHORT=$(echo "{GH_REPO}" | sed 's|.*/||')
|
|
287
|
+
INDEX_FILENAME="${REPO_SHORT}_milestone_${MILESTONE_SLUG}_index.md"
|
|
288
|
+
|
|
289
|
+
INDEX_CONTENT=$(cat <<INDEX_EOF
|
|
290
|
+
---
|
|
291
|
+
type: milestone-index
|
|
292
|
+
repo: {GH_REPO}
|
|
293
|
+
milestone: ${MILESTONE}
|
|
294
|
+
milestone_number: ${MILESTONE_NUM}
|
|
295
|
+
last_updated: ${TIMESTAMP}
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
# Milestone Index: ${MILESTONE}
|
|
299
|
+
|
|
300
|
+
Investigation findings index for all issues in this milestone.
|
|
301
|
+
|
|
302
|
+
| Issue | Title | Verdict / Severity | Task Type | Gist URL | Key Finding |
|
|
303
|
+
|-------|-------|--------------------|-----------|----------|-------------|
|
|
304
|
+
${INDEX_ENTRY}
|
|
305
|
+
INDEX_EOF
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
INDEX_URL=$(echo "$INDEX_CONTENT" | gh gist create \
|
|
309
|
+
-f "$INDEX_FILENAME" \
|
|
310
|
+
-d "Milestone index: ${MILESTONE} (${REPO_SHORT})" \
|
|
311
|
+
- 2>/dev/null)
|
|
312
|
+
|
|
313
|
+
if [ -z "$INDEX_URL" ]; then
|
|
314
|
+
echo "WARNING: Milestone index Gist creation failed — continuing without index"
|
|
315
|
+
# → Continue to Phase 1D (non-blocking)
|
|
316
|
+
fi
|
|
317
|
+
fi
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Step 4b: Update existing index Gist (index already exists)
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
if [ -n "$EXISTING_INDEX_URL" ]; then
|
|
324
|
+
INDEX_GIST_ID=$(echo "$EXISTING_INDEX_URL" | grep -oP '[a-f0-9]{20,}' | tail -1)
|
|
325
|
+
|
|
326
|
+
if [ -z "$INDEX_GIST_ID" ]; then
|
|
327
|
+
echo "WARNING: Could not extract Gist ID from index URL — skipping update"
|
|
328
|
+
# → Continue to Phase 1D
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# Fetch current index content
|
|
332
|
+
CURRENT_INDEX=$(gh gist view "$INDEX_GIST_ID" --raw 2>/dev/null)
|
|
333
|
+
|
|
334
|
+
if [ -z "$CURRENT_INDEX" ]; then
|
|
335
|
+
echo "WARNING: Could not fetch existing index Gist — skipping update"
|
|
336
|
+
# → Continue to Phase 1D
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# Check if this issue is already in the index
|
|
340
|
+
if echo "$CURRENT_INDEX" | grep -q "| #${NUMBER} |"; then
|
|
341
|
+
echo "Issue #${NUMBER} already in milestone index — skipping"
|
|
342
|
+
INDEX_URL="$EXISTING_INDEX_URL"
|
|
343
|
+
# → Continue to Phase 1D
|
|
344
|
+
else
|
|
345
|
+
# Update the last_updated timestamp in frontmatter
|
|
346
|
+
UPDATED_INDEX=$(echo "$CURRENT_INDEX" | sed "s|^last_updated:.*|last_updated: ${TIMESTAMP}|")
|
|
347
|
+
|
|
348
|
+
# Append new entry to the table
|
|
349
|
+
UPDATED_INDEX="${UPDATED_INDEX}
|
|
350
|
+
${INDEX_ENTRY}"
|
|
351
|
+
|
|
352
|
+
# Determine the filename from the existing Gist
|
|
353
|
+
INDEX_FILENAME=$(gh api gists/${INDEX_GIST_ID} --jq '.files | keys[0]' 2>/dev/null)
|
|
354
|
+
if [ -z "$INDEX_FILENAME" ]; then
|
|
355
|
+
INDEX_FILENAME="milestone_index.md"
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# Update the Gist
|
|
359
|
+
echo "$UPDATED_INDEX" | gh gist edit "$INDEX_GIST_ID" -f "$INDEX_FILENAME" - 2>/dev/null
|
|
360
|
+
if [ $? -eq 0 ]; then
|
|
361
|
+
INDEX_URL="$EXISTING_INDEX_URL"
|
|
362
|
+
echo "Milestone index Gist updated: ${INDEX_URL}"
|
|
363
|
+
else
|
|
364
|
+
echo "WARNING: Failed to update milestone index Gist — continuing"
|
|
365
|
+
INDEX_URL="$EXISTING_INDEX_URL"
|
|
366
|
+
fi
|
|
367
|
+
fi
|
|
368
|
+
fi
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Step 5: Store index URL in milestone description
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
if [ -n "$INDEX_URL" ]; then
|
|
375
|
+
if [ -n "$EXISTING_INDEX_URL" ]; then
|
|
376
|
+
# Replace existing annotation with updated URL (in case Gist was recreated)
|
|
377
|
+
UPDATED_DESC=$(echo "$MILESTONE_DESC" | sed "s|<!-- FORGE:MILESTONE_INDEX: [^ ]* -->|<!-- FORGE:MILESTONE_INDEX: ${INDEX_URL} -->|")
|
|
378
|
+
else
|
|
379
|
+
# Append annotation to milestone description
|
|
380
|
+
UPDATED_DESC="${MILESTONE_DESC}
|
|
381
|
+
|
|
382
|
+
<!-- FORGE:MILESTONE_INDEX: ${INDEX_URL} -->"
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
gh api repos/{GH_REPO}/milestones/${MILESTONE_NUM} \
|
|
386
|
+
-X PATCH \
|
|
387
|
+
-f description="$UPDATED_DESC" 2>/dev/null
|
|
388
|
+
|
|
389
|
+
if [ $? -eq 0 ]; then
|
|
390
|
+
echo "Milestone description updated with index URL: ${INDEX_URL}"
|
|
391
|
+
else
|
|
392
|
+
echo "WARNING: Failed to update milestone description — index URL not stored"
|
|
393
|
+
fi
|
|
394
|
+
fi
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
→ Continue to Phase 1D.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Phase 1D: Update Labels & Return Verdict
|
|
402
|
+
|
|
403
|
+
**CONFIRMED or PARTIAL with decompose: NO**:
|
|
404
|
+
```bash
|
|
405
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:ready-to-build" --remove-label "workflow:investigating"
|
|
406
|
+
```
|
|
407
|
+
Return verdict to caller (work-on routing loop proceeds to build).
|
|
408
|
+
|
|
409
|
+
**CONFIRMED or PARTIAL with decompose: YES**:
|
|
410
|
+
```bash
|
|
411
|
+
gh issue edit {NUMBER} {GH_FLAG} --remove-label "workflow:investigating"
|
|
412
|
+
```
|
|
413
|
+
Do NOT add `workflow:ready-to-build` — the routing loop will invoke `work-on:decompose` based on the `decompose: YES` return value.
|
|
414
|
+
Return verdict to caller (work-on routing loop proceeds to decompose).
|
|
415
|
+
|
|
416
|
+
**INVALID**:
|
|
417
|
+
```bash
|
|
418
|
+
gh issue edit {NUMBER} {GH_FLAG} --add-label "workflow:invalid" --remove-label "workflow:investigating"
|
|
419
|
+
gh issue close {NUMBER} {GH_FLAG} --comment "Closing as invalid: {reason from investigation}"
|
|
420
|
+
```
|
|
421
|
+
Return INVALID to caller (work-on stops).
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Output
|
|
426
|
+
|
|
427
|
+
The subcommand writes its results to GitHub (FORGE:INVESTIGATOR comment). Output this structured block — the routing loop in `work-on.md` will read this result, re-evaluate state, and continue to the next phase. This subcommand is complete; control returns to the router's loop iteration.
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
INVESTIGATE_RESULT:
|
|
431
|
+
verdict: {CONFIRMED|PARTIAL|INVALID}
|
|
432
|
+
confidence: {HIGH|MEDIUM|LOW}
|
|
433
|
+
decompose: {YES|NO}
|
|
434
|
+
comment_url: {url of posted comment}
|
|
435
|
+
gist_url: {url of knowledge gist, or null if creation failed/skipped}
|
|
436
|
+
milestone_index_url: {url of milestone index gist, or null if no milestone/creation failed}
|
|
437
|
+
```
|