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.
Files changed (44) hide show
  1. package/LICENSE +244 -0
  2. package/README.md +251 -0
  3. package/bin/forgedock.mjs +232 -0
  4. package/commands/analytics.md +777 -0
  5. package/commands/audit-agents.md +410 -0
  6. package/commands/audit.md +410 -0
  7. package/commands/autopilot.md +432 -0
  8. package/commands/cleanup.md +264 -0
  9. package/commands/deploy-info.md +292 -0
  10. package/commands/failure-recon.md +375 -0
  11. package/commands/forge-stats.md +183 -0
  12. package/commands/geo-audit.md +343 -0
  13. package/commands/incident-response.md +384 -0
  14. package/commands/issue.md +506 -0
  15. package/commands/milestone.md +599 -0
  16. package/commands/orchestrate.md +1044 -0
  17. package/commands/pipeline-health.md +2343 -0
  18. package/commands/qa-sweep.md +237 -0
  19. package/commands/quality-gate.md +777 -0
  20. package/commands/review-pr-agents.md +1711 -0
  21. package/commands/review-pr-staging.md +325 -0
  22. package/commands/review-pr.md +888 -0
  23. package/commands/rollback.md +253 -0
  24. package/commands/security-audit.md +529 -0
  25. package/commands/sync-ecosystem.md +527 -0
  26. package/commands/validate.md +131 -0
  27. package/commands/work-on/build/architect.md +291 -0
  28. package/commands/work-on/build/context.md +349 -0
  29. package/commands/work-on/build/implement.md +280 -0
  30. package/commands/work-on/build/validate.md +292 -0
  31. package/commands/work-on/build.md +262 -0
  32. package/commands/work-on/close.md +350 -0
  33. package/commands/work-on/decompose.md +254 -0
  34. package/commands/work-on/investigate.md +437 -0
  35. package/commands/work-on/review.md +245 -0
  36. package/commands/work-on-monolithic.md +298 -0
  37. package/commands/work-on.md +1089 -0
  38. package/docs/CODEX.md +92 -0
  39. package/docs/WORKFLOW.md +104 -0
  40. package/package.json +45 -0
  41. package/scripts/verify-env-vars.sh +201 -0
  42. package/scripts/verify-host-headers.sh +117 -0
  43. package/scripts/verify-route-registration.sh +118 -0
  44. 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
+ ```