forgedock 1.0.5 → 1.0.7

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 (42) hide show
  1. package/README.md +7 -1
  2. package/bin/forgedock.mjs +496 -364
  3. package/bin/labels.json +152 -0
  4. package/commands/audit-agents.md +4 -2
  5. package/commands/autopilot.md +19 -11
  6. package/commands/cleanup.md +27 -12
  7. package/commands/deploy-info.md +36 -17
  8. package/commands/incident-response.md +15 -10
  9. package/commands/issue.md +8 -6
  10. package/commands/milestone.md +97 -48
  11. package/commands/orchestrate.md +109 -65
  12. package/commands/pipeline-health.md +8 -1
  13. package/commands/qa-sweep.md +14 -3
  14. package/commands/review-pr-agents.md +1 -1
  15. package/commands/review-pr-staging.md +129 -23
  16. package/commands/review-pr.md +237 -90
  17. package/commands/rollback.md +26 -7
  18. package/commands/security-audit.md +27 -25
  19. package/commands/sync-ecosystem.md +2 -2
  20. package/commands/work-on/build/architect.md +154 -2
  21. package/commands/work-on/build/context.md +131 -3
  22. package/commands/work-on/build/validate.md +32 -13
  23. package/commands/work-on/decompose.md +2 -0
  24. package/commands/work-on-monolithic.md +9 -5
  25. package/commands/work-on.md +67 -23
  26. package/docs/CONFIG.md +117 -0
  27. package/package.json +2 -1
  28. package/scripts/verify-route-registration.sh +36 -15
  29. package/scripts/verify-sops-chain.sh +18 -0
  30. package/templates/devdocs/README.md +96 -0
  31. package/templates/devdocs/agent/using-forgedock.md +245 -0
  32. package/templates/devdocs/agent/using-github.md +325 -0
  33. package/templates/devdocs/project/architecture.md +122 -0
  34. package/templates/devdocs/project/conventions.md +158 -0
  35. package/templates/devdocs/project/custom-instructions.md +73 -0
  36. package/templates/devdocs/project/glossary.md +84 -0
  37. package/templates/devdocs/project/stack.md +80 -0
  38. package/templates/devdocs/templates/commit-convention.md +147 -0
  39. package/templates/devdocs/templates/issue.md +141 -0
  40. package/templates/devdocs/templates/pull-request.md +115 -0
  41. package/templates/devdocs/templates/review-finding.md +147 -0
  42. package/bin/github-app-manifest.json +0 -24
@@ -25,14 +25,23 @@ GH_FLAG="-R $GH_REPO"
25
25
  REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
26
26
  PROJECT_NAME=$(yq '.project.name' "$CONFIG_FILE")
27
27
  STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
28
+ DEFAULT_BRANCH=$(yq '.branches.default' "$CONFIG_FILE")
28
29
  PROJECT_BOARD_OWNER=$(yq '.project_board.owner // .project.owner' "$CONFIG_FILE")
29
- PROJECT_NUMBER=$(yq '.project_board.project_number // "1"' "$CONFIG_FILE")
30
- PROJECT_ID=$(yq '.project_board.project_id' "$CONFIG_FILE")
30
+ PROJECT_NUMBER=$(yq '.project_board.project_number // empty' "$CONFIG_FILE" 2>/dev/null || echo "")
31
+ PROJECT_ID=$(yq '.project_board.project_id // empty' "$CONFIG_FILE" 2>/dev/null || echo "")
32
+ # Project board field and option IDs — empty string when project_board section is absent
33
+ STATUS_FIELD_ID=$(yq '.project_board.field_ids.status // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
34
+ LANE_FIELD_ID=$(yq '.project_board.field_ids.lane // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
35
+ COMPONENT_FIELD_ID=$(yq '.project_board.field_ids.component // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
36
+ PRIORITY_FIELD_ID=$(yq '.project_board.field_ids.priority // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
37
+ STATUS_TODO_OPTION_ID=$(yq '.project_board.option_ids.status.todo // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
38
+ LANE_FEATURE_OPTION_ID=$(yq '.project_board.option_ids.lane.feature // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
39
+ PRIORITY_OPTION_ID="" # Resolved per-issue from the issue's priority label — see Step 6B
31
40
  # Build satellite repo map from repos.satellites list
32
41
  # Each satellite: { prefix, repo, staging_branch }
33
42
  ```
34
43
 
35
- All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{PROJECT_NAME}`, `{STAGING_BRANCH}`, `{PROJECT_BOARD_OWNER}`, `{PROJECT_NUMBER}`, and `{PROJECT_ID}` references below are populated from `forge.yaml`.
44
+ All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{PROJECT_NAME}`, `{STAGING_BRANCH}`, `{DEFAULT_BRANCH}`, `{PROJECT_BOARD_OWNER}`, `{PROJECT_NUMBER}`, `{PROJECT_ID}`, `{STATUS_FIELD_ID}`, `{LANE_FIELD_ID}`, `{COMPONENT_FIELD_ID}`, `{PRIORITY_FIELD_ID}`, `{STATUS_TODO_OPTION_ID}`, and `{LANE_FEATURE_OPTION_ID}` references below are populated from `forge.yaml`.
36
45
 
37
46
  ---
38
47
 
@@ -101,40 +110,45 @@ Capture the milestone number from the response.
101
110
 
102
111
  ```bash
103
112
  cd {REPO_PATH}
104
- git fetch origin main
105
- git branch milestone/{slug} origin/main
113
+ git fetch origin $DEFAULT_BRANCH
114
+ git branch milestone/{slug} origin/$DEFAULT_BRANCH
106
115
  git push origin milestone/{slug}
107
116
  ```
108
117
 
109
118
  ### Step 4: Scope decomposition
110
119
 
111
- Use a Task sub-agent to analyze the milestone scope and propose issues:
112
-
113
- > You are planning the scope for milestone "{TITLE}" on the {PROJECT_NAME} platform.
114
- >
115
- > **Milestone description**: {DESCRIPTION}
116
- >
117
- > **Your job**: Break this milestone into concrete, implementable GitHub issues. Each issue should be:
118
- > - Independently shippable (produces a working PR on its own)
119
- > - Specific enough for the `/work-on` pipeline to pick up and implement
120
- > - Ordered by dependencies (if B needs A's API, A comes first)
121
- >
122
- > **Project ecosystem context** (from `forge.yaml`):
123
- > - **Primary repo** (`{GH_REPO}`): The main project codebase at `{REPO_PATH}`
124
- > - **Tech stack**: {review.tech_stack from forge.yaml, or read CLAUDE.md for project context}
125
- > - **Satellite repos**: Read from `forge.yaml → repos.satellites` (each has a prefix and repo)
126
- >
127
- > Check the existing codebase at `{REPO_PATH}` to understand current architecture before proposing changes.
128
- >
129
- > **Output format**: Numbered list of proposed issues, each with:
130
- > - **Title**: Actionable, specific
131
- > - **Repo**: Which repo this issue belongs to (default or satellite prefix from forge.yaml)
132
- > - **Type**: `feature`, `bug`, `refactor`, `infra`
133
- > - **Priority**: P1-P3
134
- > - **Size**: S (1-2 files), M (3-5 files), L (6+ files)
135
- > - **Dependencies**: Which other issues must be done first (by number)
136
- > - **Scope**: Key files/modules affected
137
- > - **Acceptance criteria**: 2-3 testable requirements
120
+ **Investigation-gated decomposition** — do NOT skip code reads. The single-pass shallow-planning approach (one agent, whole milestone, no per-issue code reads) produces under-specified issues that ship incomplete. This step mandates per-proposed-issue investigation before any issue body is written. See `work-on/decompose.md` Phase D0 for the reference pattern that enforces this gate. <!-- Added: forge#293 -->
121
+
122
+ **4A: First pass — enumerate proposed issues (no code reads yet)**
123
+
124
+ Analyze the milestone description and produce a preliminary list of proposed issues. At this stage, only the title, type, and rough dependency order are needed. Do NOT write issue bodies yet.
125
+
126
+ **4B: Per-issue investigation (MANDATORY before writing any issue body)**
127
+
128
+ For each proposed issue in the preliminary list, MUST read the actual code before writing the issue body:
129
+
130
+ 1. **Identify affected files**: Read the files the issue will touch. Start with the files named in the milestone description, then expand to callers and related modules.
131
+ 2. **Enumerate all call sites**: For coverage/refactor tasks, grep for every occurrence of the pattern being changed. Do NOT estimate — enumerate. Example: `grep -rn 'poetry run\|npx ' commands/ --include='*.md' | wc -l` to count call sites before writing a "make X config-driven" issue.
132
+ 3. **Write exhaustive acceptance criteria**: For coverage/refactor tasks, acceptance criteria MUST be falsifiable. A `grep`/absence assertion qualifies (e.g., `` `grep -rn 'hardcoded_value' commands/` returns no matches ``). The number of criteria must match the scope — there is no cap. Two criteria for a 14-call-site refactor is not acceptable.
133
+ 4. **Identify override/companion files**: If a file needs a change, its companion files (config overrides, prod variants, sibling modules) almost certainly do too. List all of them.
134
+
135
+ **4C: Output format — per-issue, after code read**
136
+
137
+ For each proposed issue, after completing the per-issue investigation in Step 4B:
138
+
139
+ - **Title**: Actionable, specific (conventional commit prefix: `fix:`, `feat:`, `refactor:`)
140
+ - **Repo**: Which repo this issue belongs to (default or satellite prefix from forge.yaml)
141
+ - **Type**: `feature`, `bug`, `refactor`, `infra`
142
+ - **Priority**: P1-P3
143
+ - **Size**: S (1-2 files), M (3-5 files), L (6+ files)
144
+ - **Dependencies**: Which other issues must be done first (by number)
145
+ - **Affected files**: Full list of files enumerated during the Step 4B code read (not estimated)
146
+ - **Acceptance criteria**: Exhaustive and falsifiable. For coverage/refactor tasks: one criterion per affected call site or pattern, written as a grep/absence assertion. No cap on criteria count — the list must be complete enough that a reviewer can verify coverage without reading the implementation.
147
+
148
+ **Project ecosystem context** (from `forge.yaml`):
149
+ - **Primary repo** (`{GH_REPO}`): The main project codebase at `{REPO_PATH}`
150
+ - **Tech stack**: {review.tech_stack from forge.yaml, or read CLAUDE.md for project context}
151
+ - **Satellite repos**: Read from `forge.yaml → repos.satellites` (each has a prefix and repo)
138
152
 
139
153
  ### Step 5: Review with user
140
154
 
@@ -144,10 +158,14 @@ Present the proposed issues to the user. Ask:
144
158
 
145
159
  ### Step 6: Create issues and assign to milestone
146
160
 
161
+ **Issue body standard**: Use the **Pipeline Issue Template** from `issue.md` Phase 3D as the body structure for every issue created here. Do NOT use a bespoke inline template — the Pipeline Issue Template is the single canonical standard for all automated issue creation across the pipeline. The body content (Problem, Root Cause, Affected Files, Acceptance Criteria, Context, Dependencies) comes from the per-issue investigation in Step 4B. <!-- Added: forge#293 -->
162
+
147
163
  For each approved issue, create it in the **correct repo** based on the scope analysis:
148
164
 
149
165
  ```bash
150
166
  # For default repo issues:
167
+ # Body content derives from the Step 4B per-issue investigation.
168
+ # Structure MUST match the Pipeline Issue Template in issue.md Phase 3D.
151
169
  gh issue create {GH_FLAG} \
152
170
  --title "{fix|feat|refactor}: {issue_title}" \
153
171
  --label "{type},{priority}" \
@@ -155,20 +173,21 @@ gh issue create {GH_FLAG} \
155
173
  --body "$(cat <<'BODY_EOF'
156
174
  ## Problem
157
175
 
158
- {1-3 sentences: what needs to be built or fixed for this milestone. What's missing or broken.}
176
+ {1-3 sentences: what needs to be built or fixed for this milestone. Specific — derived from Step 4B code read, not milestone description alone.}
159
177
 
160
178
  ## Root Cause (if known)
161
179
 
162
- {Why this needs to change — architecture gap, missing feature, technical debt. If a new feature: "New capability required for {MILESTONE_TITLE}."}
180
+ {Why this needs to change — architecture gap, missing feature, technical debt. Reference specific file:line where possible. If a new feature: "New capability required for {MILESTONE_TITLE}."}
163
181
 
164
182
  ## Affected Files
165
183
 
166
- Files that need changes:
184
+ Files that need changes (full list from Step 4B code read — ordered by dependency):
167
185
  1. `{filepath}` — {what needs to change}
168
186
  2. `{filepath}` — {what needs to change}
169
187
 
170
188
  ## Acceptance Criteria
171
189
 
190
+ {Exhaustive and falsifiable. For coverage/refactor tasks: one criterion per affected call site or pattern, written as a grep/absence assertion. No cap — list must be complete.}
172
191
  - [ ] {Specific, testable criterion}
173
192
  - [ ] {Specific, testable criterion}
174
193
  - [ ] No regression in {related feature}
@@ -186,6 +205,7 @@ BODY_EOF
186
205
 
187
206
  # For satellite repo issues (MCP, n8n) — create in the satellite repo:
188
207
  # Note: GitHub milestones are per-repo, so satellite issues reference the milestone by name only
208
+ # Body structure MUST match the Pipeline Issue Template in issue.md Phase 3D.
189
209
  gh issue create -R {SATELLITE_REPO} \
190
210
  --title "{fix|feat|refactor}: {issue_title}" \
191
211
  --label "{type},{priority}" \
@@ -196,16 +216,17 @@ gh issue create -R {SATELLITE_REPO} \
196
216
 
197
217
  ## Root Cause (if known)
198
218
 
199
- {Why this needs to change. If a new feature: "New capability required for {MILESTONE_TITLE}."}
219
+ {Why this needs to change. Reference specific file:line where possible. If a new feature: "New capability required for {MILESTONE_TITLE}."}
200
220
 
201
221
  ## Affected Files
202
222
 
203
- Files that need changes:
223
+ Files that need changes (full list from Step 4B code read):
204
224
  1. `{filepath}` — {what needs to change}
205
225
  2. `{filepath}` — {what needs to change}
206
226
 
207
227
  ## Acceptance Criteria
208
228
 
229
+ {Exhaustive and falsifiable. No cap — coverage/refactor tasks require one criterion per affected call site.}
209
230
  - [ ] {Specific, testable criterion}
210
231
  - [ ] {Specific, testable criterion}
211
232
 
@@ -223,19 +244,47 @@ BODY_EOF
223
244
 
224
245
  ### Step 6B: Add issues to Project board
225
246
 
226
- For each created issue, add it to the GitHub Project board with initial fields. Reference `forge.yaml → project_board` for field IDs, or `docs/CONFIG.md` → "Project Board" for lookup commands.
247
+ **Skip if `project_board` is not configured** — check resolved vars before proceeding:
227
248
 
228
249
  ```bash
229
- for ISSUE_NUM in {created_issue_numbers}; do
230
- ISSUE_URL="https://github.com/{GH_REPO}/issues/${ISSUE_NUM}"
231
- ITEM_ID=$(gh project item-add {PROJECT_NUMBER} --owner {PROJECT_BOARD_OWNER} --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
232
- if [ -n "$ITEM_ID" ]; then
233
- gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_STATUS_FIELD_ID} --single-select-option-id {PROJECT_BOARD_STATUS_TODO_ID} 2>/dev/null || true # Status=Todo
234
- gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_LANE_FIELD_ID} --single-select-option-id {PROJECT_BOARD_LANE_FEATURE_ID} 2>/dev/null || true # Lane=Feature
235
- gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_COMPONENT_FIELD_ID} --single-select-option-id {COMPONENT_OPTION_ID} 2>/dev/null || true # Component (from forge.yaml → project_board.field_ids)
236
- gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_PRIORITY_FIELD_ID} --single-select-option-id {PRIORITY_OPTION_ID} 2>/dev/null || true # Priority (from label)
237
- fi
238
- done
250
+ if [ -z "$PROJECT_BOARD_OWNER" ] || [ -z "$PROJECT_ID" ] || [ -z "$PROJECT_NUMBER" ]; then
251
+ echo "INFO: project_board not configured in forge.yaml — skipping board sync for created issues"
252
+ else
253
+ # Resolve component option ID for this repo from forge.yaml → project_board.components
254
+ COMPONENT_OPTION_ID=$(yq '.project_board.components[] | select(.repo == "'"$GH_REPO"'") | .option_id' "$CONFIG_FILE" 2>/dev/null || echo "")
255
+
256
+ for ISSUE_NUM in {created_issue_numbers}; do
257
+ ISSUE_URL="https://github.com/$GH_REPO/issues/${ISSUE_NUM}"
258
+ ITEM_ID=$(gh project item-add "$PROJECT_NUMBER" --owner "$PROJECT_BOARD_OWNER" --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
259
+ if [ -n "$ITEM_ID" ]; then
260
+ # Resolve priority option ID from the issue's priority label
261
+ ISSUE_PRIORITY=$(gh issue view "$ISSUE_NUM" "$GH_FLAG" --json labels \
262
+ --jq '[.labels[].name | select(startswith("priority:"))] | .[0] | ltrimstr("priority:") | ascii_downcase' 2>/dev/null || echo "")
263
+ # Validate ISSUE_PRIORITY matches expected pattern before use as yq key path <!-- Added: forge#300 -->
264
+ PRIORITY_OPTION_ID=""
265
+ if [[ "$ISSUE_PRIORITY" =~ ^p[0-3]$ ]]; then
266
+ PRIORITY_OPTION_ID=$(yq '.project_board.option_ids.priority.'"$ISSUE_PRIORITY"' // ""' "$CONFIG_FILE" 2>/dev/null || echo "")
267
+ fi
268
+
269
+ if [ -n "$STATUS_FIELD_ID" ] && [ -n "$STATUS_TODO_OPTION_ID" ]; then
270
+ gh project item-edit --project-id "$PROJECT_ID" --id "$ITEM_ID" \
271
+ --field-id "$STATUS_FIELD_ID" --single-select-option-id "$STATUS_TODO_OPTION_ID" 2>/dev/null || true # Status=Todo
272
+ fi
273
+ if [ -n "$LANE_FIELD_ID" ] && [ -n "$LANE_FEATURE_OPTION_ID" ]; then
274
+ gh project item-edit --project-id "$PROJECT_ID" --id "$ITEM_ID" \
275
+ --field-id "$LANE_FIELD_ID" --single-select-option-id "$LANE_FEATURE_OPTION_ID" 2>/dev/null || true # Lane=Feature
276
+ fi
277
+ if [ -n "$COMPONENT_FIELD_ID" ] && [ -n "$COMPONENT_OPTION_ID" ]; then
278
+ gh project item-edit --project-id "$PROJECT_ID" --id "$ITEM_ID" \
279
+ --field-id "$COMPONENT_FIELD_ID" --single-select-option-id "$COMPONENT_OPTION_ID" 2>/dev/null || true # Component (from forge.yaml → project_board.components)
280
+ fi
281
+ if [ -n "$PRIORITY_FIELD_ID" ] && [ -n "$PRIORITY_OPTION_ID" ]; then
282
+ gh project item-edit --project-id "$PROJECT_ID" --id "$ITEM_ID" \
283
+ --field-id "$PRIORITY_FIELD_ID" --single-select-option-id "$PRIORITY_OPTION_ID" 2>/dev/null || true # Priority (from issue label)
284
+ fi
285
+ fi
286
+ done
287
+ fi
239
288
  ```
240
289
 
241
290
  ### Step 7: Report
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Orchestrate parallel work on multiple issues or an entire milestone — spawns sub-agents that each run the full /work-on pipeline
3
- argument-hint: [milestone <slug> | #1 #2 #3 | next <N> | fast-lane | P0]
3
+ argument-hint: [milestone <slug> | #1 #2 #3 | next <N> | fast-lane | priority:P0]
4
4
  ---
5
5
 
6
6
  # /orchestrate — Multi-Issue Parallel Orchestrator
@@ -37,11 +37,12 @@ GH_FLAG="-R $GH_REPO"
37
37
  REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
38
38
  PROJECT_NAME=$(yq '.project.name' "$CONFIG_FILE")
39
39
  STAGING_BRANCH=$(yq '.branches.staging' "$CONFIG_FILE")
40
+ DEFAULT_BRANCH=$(yq '.branches.default' "$CONFIG_FILE")
40
41
  # Build satellite repo map from repos.satellites list
41
42
  # Each satellite: { prefix, repo, staging_branch }
42
43
  ```
43
44
 
44
- All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{PROJECT_NAME}`, and `{STAGING_BRANCH}` references below are populated from `forge.yaml`.
45
+ All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{PROJECT_NAME}`, `{STAGING_BRANCH}`, and `{DEFAULT_BRANCH}` references below are populated from `forge.yaml`.
45
46
 
46
47
  ---
47
48
 
@@ -70,10 +71,10 @@ Parse `$ARGUMENTS` to determine which issues to work on:
70
71
  |-------|------------|
71
72
  | `milestone <slug>` | All open issues assigned to that GitHub milestone (default repo) |
72
73
  | `#1 #2 #3` or `1 2 3` | Specific issue numbers, optionally repo-prefixed (e.g., `#123 mcp:5 n8n:12`) |
73
- | `next <N>` | Top N priority open issues (P0 first, then P1, etc.) |
74
+ | `next <N>` | Top N priority open issues (priority:P0 first, then priority:P1, etc.) |
74
75
  | `next <N> all-repos` | Top N across ALL ecosystem repos |
75
76
  | `fast-lane` or `fast` | All open fast-lane issues (no milestone, bugs/fixes) |
76
- | `P0` or `P1` | All open issues with that priority label |
77
+ | `priority:P0` or `priority:P1` | All open issues with that priority label |
77
78
  | `mcp:fast` or `n8n:next 3` | Repo-scoped queries |
78
79
  | `<slug>` (no keyword) | Try milestone first, then fall back to label search |
79
80
 
@@ -230,6 +231,8 @@ Add the newly created issues to the issue set. Re-check each one:
230
231
  - Tag them as `IMPLEMENTATION` (investigations don't spawn more investigations)
231
232
  - They inherit the milestone from their parent investigation if applicable
232
233
 
234
+ **Issue body standard**: Any new issues created by investigation agents MUST use the **Pipeline Issue Template** from `issue.md` Phase 3D as their body structure. Investigation agents that produce issues as output should be prompted to use that template — not ad-hoc body formats — so every spawned issue enters the pipeline with the correct structure (Problem, Root Cause, Affected Files, Acceptance Criteria). Verify spawned issues have all mandatory sections before adding them to the batch; if sections are missing, the `/issue` body-validation step (Phase 4C.5) will repair them. <!-- Added: forge#293 -->
235
+
233
236
  **Updated issue set** = original `IMPLEMENTATION` issues + newly spawned issues from investigations.
234
237
 
235
238
  ### Step 2E: Report Wave 0 results to user
@@ -275,18 +278,19 @@ For each issue, estimate which domains it touches based on title, body, and labe
275
278
  for NUM in {issue_numbers}; do
276
279
  ISSUE=$(gh issue view $NUM --json title,body,labels --jq '{title: .title, labels: [.labels[].name], body: (.body[:300])}')
277
280
  echo "=== #$NUM ==="
278
- echo "$ISSUE" | grep -qiE "credit|billing|pricing|stripe|tier.*cost|charge|refund" && echo " BILLING" || true
281
+ echo "$ISSUE" | grep -qiE "credit|billing|pricing|stripe|charge|refund" && echo " BILLING" || true
279
282
  echo "$ISSUE" | grep -qiE "auth|session|jwt|login|permission|oauth" && echo " AUTH" || true
280
- echo "$ISSUE" | grep -qiE "scrape|tier|proxy|playwright|playbook|worker|unified_consumer|penetrat" && echo " SCRAPING" || true
283
+ echo "$ISSUE" | grep -qiE "worker|queue|job|task|background|consumer" && echo " WORKER" || true
281
284
  echo "$ISSUE" | grep -qiE "migration|\.sql|database|postgres|alembic" && echo " DATABASE" || true
282
285
  echo "$ISSUE" | grep -qiE "component|page|layout|dashboard|ui|ux|frontend|web/src" && echo " FRONTEND" || true
283
286
  echo "$ISSUE" | grep -qiE "docker|deploy|traefik|nginx|ci|cd|infra|github.action" && echo " INFRA" || true
284
- echo "$ISSUE" | grep -qiE "cortex|llm|extract|schema|format" && echo " CORTEX" || true
287
+ echo "$ISSUE" | grep -qiE "llm|extract|schema|format|embedding|model" && echo " AI" || true
288
+ # For project-specific domains, configure keywords in forge.yaml → review.domains and extend above
285
289
  done
286
290
  ```
287
291
 
288
292
  **Use domain info for wave planning:**
289
- - Issues in the SAME domain (especially SCRAPING, BILLING, DATABASE) are more likely to touch the same files → prefer sequential within a wave or adjacent waves
293
+ - Issues in the SAME domain (especially WORKER, BILLING, DATABASE) are more likely to touch the same files → prefer sequential within a wave or adjacent waves
290
294
  - Issues in DIFFERENT domains are more likely independent → safe to parallelize
291
295
  - BILLING + AUTH issues should be prioritized early (security-critical)
292
296
  - **DATABASE issues are ALWAYS serialized — hard rule, no exceptions.** Multiple agents writing migrations simultaneously will produce duplicate migration numbers (e.g., two `0067_*.sql` files), which breaks the migration runner. Every DATABASE issue must be in its own wave. If 3 DATABASE issues are in a batch: Wave 1 runs issue A, Wave 2 runs issue B, Wave 3 runs issue C.
@@ -363,11 +367,17 @@ When two issues modify different files that **import from the same utility/init
363
367
 
364
368
  **Apply inferences:**
365
369
  ```
366
- # High-fan-in files — if ANY issue touches these, serialize it with all same-service issues:
370
+ # High-fan-in files — if ANY issue touches these, serialize it with all same-service issues.
371
+ # Read layout paths from forge.yaml review.layout; fall back to sensible generic defaults.
372
+ # Example (pseudo-code — adapt to your forge.yaml parsing method):
373
+ # API_MAIN = forge_yaml.review.layout.api_main ?? "services/api/app/main.py"
374
+ # WORKER_MAIN = forge_yaml.review.layout.worker_main ?? "services/worker/worker/main.py"
375
+ # PAGES_ROOT = forge_yaml.review.layout.pages ?? "web/src/app"
376
+
367
377
  HIGH_FAN_IN = [
368
- "services/api/app/main.py",
369
- "services/worker/worker/unified_consumer.py",
370
- "web/src/app/layout.tsx",
378
+ API_MAIN, # e.g. "services/api/app/main.py" — router/middleware registration
379
+ WORKER_MAIN, # e.g. "services/worker/worker/main.py" — worker entrypoint (set forge.yaml review.layout.worker_main)
380
+ PAGES_ROOT + "/layout.tsx", # e.g. "web/src/app/layout.tsx" — root layout for all pages
371
381
  "docker-compose.yml",
372
382
  "docker-compose.prod.yml",
373
383
  ".env.example"
@@ -403,7 +413,7 @@ Build the final conflict graph by merging signals from all four layers:
403
413
  | Layer 4: Low confidence + same domain | **Conservative** | Serialize |
404
414
  | Layer 4: Low confidence + no domain | **Conservative** | Serialize with prior wave |
405
415
 
406
- **This supplements, not replaces, the domain keyword estimation.** Domain tags still help with broad sequencing decisions. Multi-layer conflict detection catches the specific cases keywords miss (e.g., two issues that both modify files in `services/api/app/models/` where one is labeled SCRAPING and the other BILLING — Layer 2 catches this even though Layer 1 shows no direct file overlap).
416
+ **This supplements, not replaces, the domain keyword estimation.** Domain tags still help with broad sequencing decisions. Multi-layer conflict detection catches the specific cases keywords miss (e.g., two issues that both modify files in `services/api/app/models/` where one is labeled WORKER and the other BILLING — Layer 2 catches this even though Layer 1 shows no direct file overlap).
407
417
 
408
418
  ### Step 3D: Build the execution plan
409
419
 
@@ -445,15 +455,15 @@ Wave 2 (after deps): #2634 (enable daemon — after #2633 fixes memory leak), #2
445
455
  ### Domain Distribution
446
456
  | Domain | Issues | Notes |
447
457
  |--------|--------|-------|
448
- | SCRAPING | {N} | {High overlap risk / Independent targets} |
449
458
  | FRONTEND | {N} | {Independent pages / Shared components} |
450
459
  | BILLING | {N} | {Critical — prioritize in Wave 1} |
451
460
  | DATABASE | {N} | {Sequential — migration order matters} |
452
461
  | AUTH | {N} | {Critical — Wave 1} |
462
+ | WORKER | {N} | {High overlap risk within worker service} |
463
+ | AI | {N} | {Independent} |
453
464
  | INFRA | {N} | {Independent} |
454
- | CORTEX | {N} | {Independent} |
455
465
 
456
- (Omit rows with 0 issues.)
466
+ (Omit rows with 0 issues. Add project-specific domain rows from forge.yaml → review.domains.)
457
467
 
458
468
  ### Implementation Waves
459
469
 
@@ -488,12 +498,18 @@ Proceed? (yes / adjust / pick specific issues)
488
498
  ```bash
489
499
  # Capture staging baseline before launching the wave
490
500
  git fetch origin
491
- STAGING_LINES_BEFORE=$(git diff --stat origin/main...origin/staging 2>/dev/null \
492
- | tail -1 \
493
- | grep -oP '\d+ insertion' \
494
- | grep -oP '\d+' \
495
- || echo "0")
496
- echo "Staging baseline before Wave {N}: ${STAGING_LINES_BEFORE} lines ahead of main"
501
+ if [ "$DEFAULT_BRANCH" = "$STAGING_BRANCH" ]; then
502
+ # Single-branch repo — staging and default are the same; diff is always zero
503
+ STAGING_LINES_BEFORE=0
504
+ echo "Staging baseline before Wave {N}: skipped — single-branch repo (staging == default)"
505
+ else
506
+ STAGING_LINES_BEFORE=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
507
+ | tail -1 \
508
+ | grep -oP '\d+ insertion' \
509
+ | grep -oP '\d+' \
510
+ || echo "0")
511
+ echo "Staging baseline before Wave {N}: ${STAGING_LINES_BEFORE} lines ahead of $DEFAULT_BRANCH"
512
+ fi
497
513
  ```
498
514
 
499
515
  Store `STAGING_LINES_BEFORE` per wave. After the wave completes (after Step 4C), run the integrity check:
@@ -501,27 +517,32 @@ Store `STAGING_LINES_BEFORE` per wave. After the wave completes (after Step 4C),
501
517
  ```bash
502
518
  # Check staging integrity after wave completes (run after Step 4C)
503
519
  git fetch origin
504
- STAGING_LINES_AFTER=$(git diff --stat origin/main...origin/staging 2>/dev/null \
505
- | tail -1 \
506
- | grep -oP '\d+ insertion' \
507
- | grep -oP '\d+' \
508
- || echo "0")
509
- STAGING_GROWTH=$((STAGING_LINES_AFTER - STAGING_LINES_BEFORE))
510
-
511
- echo "Staging after Wave {N}: ${STAGING_LINES_AFTER} lines (+${STAGING_GROWTH} vs baseline)"
512
-
513
- # Alert if growth exceeds expected delta
514
- # Expected: only the lines from PRs merged in this wave
515
- # Alert threshold: growth > 500 lines more than the sum of changed lines from wave PRs
516
- # (Use 0 as threshold if no PRs merged — any growth is unexpected)
517
- EXPECTED_DELTA={SUM_OF_PR_LINE_COUNTS_THIS_WAVE} # set from PR diffs collected in 4B
518
- UNEXPECTED_GROWTH=$((STAGING_GROWTH - EXPECTED_DELTA))
519
- if [ "$UNEXPECTED_GROWTH" -gt 500 ]; then
520
- echo "ALERT: Staging grew by ${STAGING_GROWTH} lines (+${UNEXPECTED_GROWTH} beyond expected ${EXPECTED_DELTA})."
521
- echo "This may indicate milestone-code contamination via agent merge commits."
522
- echo "Review: git log --oneline --merges origin/main..origin/staging"
523
- echo "Do NOT merge staging → main until the unexpected growth is investigated."
524
- # Do NOT auto-stop — alert the user and let them decide
520
+ if [ "$DEFAULT_BRANCH" = "$STAGING_BRANCH" ]; then
521
+ STAGING_LINES_AFTER=0
522
+ STAGING_GROWTH=0
523
+ echo "Staging integrity check: skipped — single-branch repo (staging == default)"
524
+ else
525
+ STAGING_LINES_AFTER=$(git diff --stat origin/$DEFAULT_BRANCH...origin/$STAGING_BRANCH 2>/dev/null \
526
+ | tail -1 \
527
+ | grep -oP '\d+ insertion' \
528
+ | grep -oP '\d+' \
529
+ || echo "0")
530
+ STAGING_GROWTH=$((STAGING_LINES_AFTER - STAGING_LINES_BEFORE))
531
+ echo "Staging after Wave {N}: ${STAGING_LINES_AFTER} lines (+${STAGING_GROWTH} vs baseline)"
532
+
533
+ # Alert if growth exceeds expected delta
534
+ # Expected: only the lines from PRs merged in this wave
535
+ # Alert threshold: growth > 500 lines more than the sum of changed lines from wave PRs
536
+ # (Use 0 as threshold if no PRs merged — any growth is unexpected)
537
+ EXPECTED_DELTA={SUM_OF_PR_LINE_COUNTS_THIS_WAVE} # set from PR diffs collected in 4B
538
+ UNEXPECTED_GROWTH=$((STAGING_GROWTH - EXPECTED_DELTA))
539
+ if [ "$UNEXPECTED_GROWTH" -gt 500 ]; then
540
+ echo "ALERT: Staging grew by ${STAGING_GROWTH} lines (+${UNEXPECTED_GROWTH} beyond expected ${EXPECTED_DELTA})."
541
+ echo "This may indicate milestone-code contamination via agent merge commits."
542
+ echo "Review: git log --oneline --merges origin/$DEFAULT_BRANCH..origin/$STAGING_BRANCH"
543
+ echo "Do NOT merge $STAGING_BRANCH → $DEFAULT_BRANCH until the unexpected growth is investigated."
544
+ # Do NOT auto-stop — alert the user and let them decide
545
+ fi
525
546
  fi
526
547
  ```
527
548
 
@@ -738,7 +759,7 @@ for FINDING_NUM in {spawned_finding_numbers}; do
738
759
  FINDING_DATA=$(gh issue view $FINDING_NUM -R {GH_REPO} --json labels,title,body \
739
760
  --jq '{labels: [.labels[].name], title: .title, body: .body}')
740
761
 
741
- PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("P")) | .' | head -1)
762
+ PRIORITY=$(echo "$FINDING_DATA" | jq -r '.labels[] | select(startswith("priority:P")) | ltrimstr("priority:")' | head -1)
742
763
  TITLE=$(echo "$FINDING_DATA" | jq -r '.title')
743
764
 
744
765
  # Heuristic 1: Generation check — source issue has review-finding label (always defer, even for P1/P2)
@@ -799,7 +820,14 @@ They remain open in GitHub — they will be picked up by future `/orchestrate` o
799
820
 
800
821
  **When to run**: After each wave completes, IF the batch targets a milestone branch AND any `.tsx`/`.ts` files were changed by agents in the completed wave.
801
822
 
823
+ All tool commands are read from `forge.yaml → verification.commands`; each step logs `SKIPPED — not configured` when the corresponding key is absent rather than silently passing.
824
+
802
825
  ```bash
826
+ # Read toolchain commands from forge.yaml
827
+ TS_TYPECHECK=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'typecheck:' | head -1 | sed "s/.*typecheck: *['\"]//;s/['\"].*//")
828
+ TS_BUILD=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ typescript:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'build:' | head -1 | sed "s/.*build: *['\"]//;s/['\"].*//")
829
+ PYTHON_FORMAT=$(awk '/^ commands:/{f=1;next} f && /^[^ \t]/{exit} f' forge.yaml 2>/dev/null | awk '/^ python:/{f=1;next} f && /^ [^ \t]/{exit} f' | grep 'format:' | head -1 | sed "s/.*format: *['\"]//;s/['\"].*//")
830
+
803
831
  # Check if this is a milestone batch with TypeScript changes
804
832
  MILESTONE_BRANCH="milestone/{milestone_slug}"
805
833
  TS_CHANGED=$(git diff origin/{DEFAULT_BRANCH}...origin/${MILESTONE_BRANCH} --name-only | grep -E '\.(tsx?|jsx?)$' | head -1)
@@ -809,20 +837,31 @@ if [ -n "$TS_CHANGED" ]; then
809
837
  cd {REPO_PATH}
810
838
  git fetch origin ${MILESTONE_BRANCH}
811
839
  git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
812
- cd web && npx tsc --noEmit 2>&1 | head -30
813
- TSC_EXIT=$?
814
- if [ "$TSC_EXIT" -eq 0 ]; then
815
- npx next build 2>&1 | tail -30
840
+
841
+ if [ -n "$TS_TYPECHECK" ]; then
842
+ eval "$TS_TYPECHECK" 2>&1 | head -30
843
+ TSC_EXIT=$?
844
+ else
845
+ echo "SKIPPED — typescript.typecheck not configured in verification.commands"
846
+ TSC_EXIT=0
847
+ fi
848
+
849
+ if [ "$TSC_EXIT" -eq 0 ] && [ -n "$TS_BUILD" ]; then
850
+ eval "$TS_BUILD" 2>&1 | tail -30
816
851
  BUILD_EXIT=$?
852
+ elif [ -z "$TS_BUILD" ]; then
853
+ echo "SKIPPED — typescript.build not configured in verification.commands"
854
+ BUILD_EXIT=0
817
855
  fi
818
- cd .. && git checkout - 2>/dev/null
856
+
857
+ git checkout - 2>/dev/null
819
858
 
820
859
  if [ "$TSC_EXIT" -ne 0 ]; then
821
860
  echo "BLOCKING: TypeScript errors on ${MILESTONE_BRANCH} after Wave {N}."
822
861
  echo "Fix type errors before starting next wave."
823
862
  elif [ "${BUILD_EXIT:-0}" -ne 0 ]; then
824
- echo "BLOCKING: next build failed on ${MILESTONE_BRANCH} after Wave {N}."
825
- echo "SSG prerender or build errors — fix before starting next wave."
863
+ echo "BLOCKING: build failed on ${MILESTONE_BRANCH} after Wave {N}."
864
+ echo "Build/prerender errors — fix before starting next wave."
826
865
  fi
827
866
  fi
828
867
 
@@ -832,20 +871,25 @@ if [ -n "$PY_CHANGED" ]; then
832
871
  echo "=== Integration Build Gate (Python): Wave {N} ==="
833
872
  cd {REPO_PATH}
834
873
  git checkout origin/${MILESTONE_BRANCH} --detach 2>/dev/null
835
- cd services/api && poetry run black --check app/ 2>&1 | tail -10
836
- BLACK_EXIT=$?
837
- poetry run isort --check app/ 2>&1 | tail -10
838
- ISORT_EXIT=$?
839
- cd ../.. && git checkout - 2>/dev/null
840
874
 
841
- if [ "$BLACK_EXIT" -ne 0 ] || [ "$ISORT_EXIT" -ne 0 ]; then
875
+ if [ -n "$PYTHON_FORMAT" ]; then
876
+ eval "$PYTHON_FORMAT" 2>&1 | tail -10
877
+ FORMAT_EXIT=$?
878
+ else
879
+ echo "SKIPPED — python.format not configured in verification.commands"
880
+ FORMAT_EXIT=0
881
+ fi
882
+
883
+ git checkout - 2>/dev/null
884
+
885
+ if [ "$FORMAT_EXIT" -ne 0 ]; then
842
886
  echo "WARNING: Python formatting issues on ${MILESTONE_BRANCH} after Wave {N}."
843
887
  echo "Not blocking but should be fixed before milestone→staging."
844
888
  fi
845
889
  fi
846
890
  ```
847
891
 
848
- **If the gate fails**: Report the errors to the user. Do NOT proceed to the next wave. The accumulated milestone branch has integration errors that will only get worse with more PRs on top. `next build` failures are BLOCKING (not just `tsc`) — SSG prerender crashes are invisible to type checking.
892
+ **If the gate fails**: Report the errors to the user. Do NOT proceed to the next wave. The accumulated milestone branch has integration errors that will only get worse with more PRs on top. Build failures are BLOCKING — SSG/prerender crashes are invisible to typecheck alone — configure `typescript.build` in `verification.commands` to catch them.
849
893
 
850
894
  ### Step 4E: Advance to next wave
851
895
 
@@ -1051,12 +1095,12 @@ Aggregate into the batch-level analytics for Step 6B.
1051
1095
  → Gets all unmilestoned bugs/fixes
1052
1096
  → Groups by independence, runs in waves
1053
1097
 
1054
- ### "orchestrate milestone cortex-recursive-loop"
1055
- → Fetches all open issues in "Cortex Recursive Loop" milestone
1056
- → Detects #2644 is an investigation issue (title: "Investigate: Complete the autonomous recursive loop...")
1057
- → Wave 0: Runs `/work-on 2644` — investigation creates 5 new issues (#2650-#2654)
1058
- → Re-fetches milestone issues, now includes #2650-#2654
1059
- → Wave 1: #2633 (P0 orphaned queues), #2636 (invalidation), #2650, #2651 (all independent)
1060
- → Wave 2: #2634 (enable daemon, after #2633), #2652 (depends on #2650)
1098
+ ### "orchestrate milestone user-auth-v2"
1099
+ → Fetches all open issues in "User Auth v2" milestone
1100
+ → Detects #42 is an investigation issue (title: "Investigate: session expiry race condition under high concurrency")
1101
+ → Wave 0: Runs `/work-on 42` — investigation creates 3 new issues (#55-#57)
1102
+ → Re-fetches milestone issues, now includes #55-#57
1103
+ → Wave 1: #38 (P0 token refresh bug), #41 (session store migration), #55, #56 (all independent)
1104
+ → Wave 2: #39 (enable refresh rotation — after #38 fixes token bug), #57 (depends on #55)
1061
1105
  → Wave 3: remaining issues
1062
1106
  → Final report shows investigation spawned issues and their results
@@ -15,7 +15,14 @@ Read project identity from `forge.yaml` before running any phase:
15
15
 
16
16
  ```bash
17
17
  CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
18
- FORGE_REPO=$(yq e '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
18
+ GH_REPO=$(yq e '.project.owner + "/" + .project.repo' "$CONFIG_FILE")
19
+ GH_FLAG="-R $GH_REPO"
20
+ # FORGE_REPO: the self-pipeline repo where pipeline-health issues are filed.
21
+ # Set project.forge_repo in forge.yaml if your pipeline repo differs from GH_REPO.
22
+ # Example: project:
23
+ # forge_repo: "my-org/my-forge"
24
+ FORGE_REPO=$(yq '.project.forge_repo // ""' "$CONFIG_FILE")
25
+ [ -z "$FORGE_REPO" ] && FORGE_REPO="$GH_REPO"
19
26
  FORGE_HOME=$(yq e '.paths.root' "$CONFIG_FILE")
20
27
  echo "Forge repo: $FORGE_REPO"
21
28
  echo "Forge home: $FORGE_HOME"
@@ -170,8 +170,19 @@ fi
170
170
  **Dynamic discovery from filesystem** (NOT hardcoded):
171
171
 
172
172
  ```bash
173
- cd $PROJECT_ROOT/web/src/app
174
- find . -name "page.tsx" -o -name "page.js" | sort
173
+ # Read pages root from forge.yaml review.layout.pages, fall back to web/src/app
174
+ PAGES_ROOT=$(yq '.review.layout.pages // "web/src/app"' $PROJECT_ROOT/forge.yaml 2>/dev/null || echo "web/src/app")
175
+
176
+ cd $PROJECT_ROOT/$PAGES_ROOT
177
+ PAGES=$(find . -name "page.tsx" -o -name "page.js" | sort)
178
+
179
+ # Loud failure when zero pages found — prevents silent no-op qa-sweep runs
180
+ if [ -z "$PAGES" ]; then
181
+ echo "ERROR: Zero pages discovered under $PAGES_ROOT. If your project uses a different layout, set review.layout.pages in forge.yaml (e.g., 'apps/web/src/app' or 'src/pages')."
182
+ exit 1
183
+ fi
184
+
185
+ echo "$PAGES"
175
186
  ```
176
187
 
177
188
  Convert paths to routes: `./dashboard/billing/page.tsx` → `/dashboard/billing`
@@ -180,7 +191,7 @@ Convert paths to routes: `./dashboard/billing/page.tsx` → `/dashboard/billing`
180
191
 
181
192
  **Resolve dynamic routes** (`[param]`) by fetching real IDs from API. Test invalid IDs too.
182
193
 
183
- **For `diff` mode**: `git diff --name-only origin/main...origin/staging -- web/src/` → map to affected pages.
194
+ **For `diff` mode**: `git diff --name-only origin/main...origin/staging -- $PAGES_ROOT/` → map to affected pages.
184
195
 
185
196
  **Present test plan to user** (page count by category, journey count, estimated waves). Wait for confirmation.
186
197
 
@@ -987,7 +987,7 @@ done
987
987
  gh pr comment [PR_NUMBER] --body "$(cat <<'EOF'
988
988
  ## Scraper Logic Audit
989
989
 
990
- ### Components Affected: [tiers/playbooks/cortex/validation/detection-keywords/capacity-constants/api-gate-semantics/cross-component-gate]
990
+ ### Components Affected: [list the specific service/worker/api components touched — e.g. job-queue/rate-limiter/validation/gate-semantics/cross-component-gate]
991
991
 
992
992
  ### Tier Flow Analysis
993
993
  [Trace the tier selection and escalation with file:line references]