forgedock 1.1.7 → 1.1.9

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 (97) hide show
  1. package/README.md +10 -33
  2. package/bin/cinema.mjs +23 -5
  3. package/bin/engine/invariants.mjs +361 -0
  4. package/bin/engine-cli.mjs +137 -11
  5. package/bin/env-detect.mjs +249 -0
  6. package/bin/forge-utils.mjs +364 -4
  7. package/bin/forgedock.mjs +1064 -135
  8. package/bin/hooks/pre-tool-use.mjs +182 -0
  9. package/bin/hooks/session-start.mjs +68 -3
  10. package/bin/init-detect.mjs +101 -12
  11. package/bin/journey.mjs +917 -16
  12. package/bin/labels.json +10 -0
  13. package/bin/recall.mjs +557 -0
  14. package/bin/registry.mjs +91 -3
  15. package/bin/runner.mjs +38 -2
  16. package/bin/tui.mjs +49 -3
  17. package/commands/adopt.md +1 -1
  18. package/commands/analytics.md +8 -8
  19. package/commands/audit.md +1 -1
  20. package/commands/autopilot.md +958 -77
  21. package/commands/ci-audit.md +1 -1
  22. package/commands/cleanup.md +139 -55
  23. package/commands/compat-audit.md +1 -1
  24. package/commands/deploy-info.md +1 -1
  25. package/commands/deploy-pr.md +1 -1
  26. package/commands/diagnose.md +1 -1
  27. package/commands/explain.md +1 -1
  28. package/commands/fix-ci.md +1 -1
  29. package/commands/forgedock-init.md +1 -1
  30. package/commands/incident-response.md +3 -3
  31. package/commands/issue.md +1 -1
  32. package/commands/optimize.md +586 -1
  33. package/commands/orchestrate/config.md +13 -3
  34. package/commands/orchestrate/phase-1-resolve.md +36 -26
  35. package/commands/orchestrate/phase-2-triage.md +1 -1
  36. package/commands/orchestrate/phase-2.5-synthesis.md +1 -1
  37. package/commands/orchestrate/phase-3-dependency.md +587 -48
  38. package/commands/orchestrate/phase-4-execution.md +1031 -57
  39. package/commands/orchestrate/phase-5-cleanup.md +1 -1
  40. package/commands/orchestrate/phase-6-report.md +248 -11
  41. package/commands/orchestrate/safety.md +2 -2
  42. package/commands/pipeline-health/phase-2-metrics.md +305 -1
  43. package/commands/pipeline-health/phase-4-proposals.md +127 -0
  44. package/commands/qa-sweep.md +1 -1
  45. package/commands/quality-gate.md +418 -1
  46. package/commands/recover-orphans.md +5 -5
  47. package/commands/replay.md +1 -1
  48. package/commands/review-pr-agents/api.md +2 -2
  49. package/commands/review-pr-agents/auth.md +2 -2
  50. package/commands/review-pr-agents/billing.md +2 -2
  51. package/commands/review-pr-agents/concurrency.md +2 -2
  52. package/commands/review-pr-agents/database.md +2 -2
  53. package/commands/review-pr-agents/frontend.md +2 -2
  54. package/commands/review-pr-agents/infra.md +2 -2
  55. package/commands/review-pr-agents/protocols.md +1 -1
  56. package/commands/review-pr-agents/scraper.md +2 -2
  57. package/commands/review-pr-agents/security.md +2 -2
  58. package/commands/review-pr-staging.md +41 -3
  59. package/commands/review-pr.md +462 -19
  60. package/commands/rollback.md +1 -1
  61. package/commands/scope.md +1 -1
  62. package/commands/security-audit.md +1 -1
  63. package/commands/spec-doctor.md +429 -0
  64. package/commands/test-gate.md +1 -1
  65. package/commands/validate.md +53 -1
  66. package/commands/work-on/build/architect.md +78 -1
  67. package/commands/work-on/build/context.md +360 -22
  68. package/commands/work-on/build/implement.md +3 -1
  69. package/commands/work-on/build/validate.md +29 -1
  70. package/commands/work-on/build.md +75 -6
  71. package/commands/work-on/close.md +624 -10
  72. package/commands/work-on/decompose.md +1 -1
  73. package/commands/work-on/investigate.md +235 -5
  74. package/commands/work-on/remediate.md +331 -0
  75. package/commands/work-on/review.md +1 -1
  76. package/commands/work-on-monolithic.md +1 -1
  77. package/commands/work-on.md +205 -66
  78. package/docs/CI.md +2 -2
  79. package/docs/CODEX.md +1 -1
  80. package/docs/CONFIG.md +186 -0
  81. package/docs/FORGE-PROTOCOL.md +101 -0
  82. package/docs/site/command-reference.md +31 -1
  83. package/docs/site/getting-started.md +9 -34
  84. package/docs/site/index.md +1 -1
  85. package/docs/spec/forge-protocol-v1.md +97 -5
  86. package/docs/spec/label-state-machine.md +6 -1
  87. package/package.json +3 -2
  88. package/packages/protocol/src/cli.js +374 -84
  89. package/packages/protocol/src/types.js +75 -1
  90. package/scripts/build-knowledge-index.mjs +1761 -0
  91. package/scripts/calibration.mjs +996 -0
  92. package/scripts/check-command-docs-drift.sh +4 -2
  93. package/scripts/danger-zones.mjs +789 -0
  94. package/scripts/doctor-pipeline-state.sh +88 -2
  95. package/scripts/select-fix-targets.sh +234 -0
  96. package/scripts/transition-label.sh +245 -6
  97. package/scripts/refresh-bot-token.sh +0 -97
@@ -30,7 +30,7 @@ This command is designed to be run:
30
30
 
31
31
  This is NOT a PR review — it does not approve or block. It creates issues for each CI gap found.
32
32
 
33
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
33
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
34
34
  **NEVER use plan mode (EnterPlanMode).**
35
35
 
36
36
  ---
@@ -50,7 +50,7 @@ All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{STAGING_BRANCH}`, `{PROJECT_BOARD
50
50
  | `branches` | Prune worktrees and remote branches for merged PRs |
51
51
  | `milestones` | Report milestones with 0 open issues (advisory — never closes) |
52
52
  | `board` | Sync closed issues to Project board with correct terminal state |
53
- | `batch-p3` | Sweep: fold stale unbatched P3 review findings into domain-grouped batch issues |
53
+ | `batch-p3` | Sweep: fold stale unbatched P3 review findings into surface-area-grouped batch issues (same file, then leaf directory) |
54
54
  | `all` | All of the above, in order |
55
55
 
56
56
  ---
@@ -68,6 +68,9 @@ gh issue list {GH_FLAG} --state closed --label "workflow:in-review" --limit 100
68
68
  echo "=== Stale workflow:building ==="
69
69
  gh issue list {GH_FLAG} --state closed --label "workflow:building" --limit 100 --json number,title --jq '.[] | "#\(.number) — \(.title)"'
70
70
 
71
+ echo "=== Stale workflow:awaiting-merge ==="
72
+ gh issue list {GH_FLAG} --state closed --label "workflow:awaiting-merge" --limit 100 --json number,title --jq '.[] | "#\(.number) — \(.title)"'
73
+
71
74
  echo "=== Stale workflow:investigating ==="
72
75
  gh issue list {GH_FLAG} --state closed --label "workflow:investigating" --limit 100 --json number,title --jq '.[] | "#\(.number) — \(.title)"'
73
76
 
@@ -79,11 +82,11 @@ gh issue list {GH_FLAG} --state closed --label "needs-validation" --limit 100 --
79
82
 
80
83
  For each closed issue with a stale intermediate label:
81
84
 
82
- **Stale `workflow:in-review`, `workflow:building`** — these were merged but label wasn't updated:
85
+ **Stale `workflow:in-review`, `workflow:building`, `workflow:awaiting-merge`** — these were merged but label wasn't updated:
83
86
  ```bash
84
87
  for NUM in {stale_issue_numbers}; do
85
88
  gh issue edit $NUM {GH_FLAG} --add-label "workflow:merged"
86
- gh issue edit $NUM {GH_FLAG} --remove-label "workflow:in-review,workflow:building,needs-validation" 2>/dev/null || true
89
+ gh issue edit $NUM {GH_FLAG} --remove-label "workflow:in-review,workflow:building,workflow:awaiting-merge,needs-validation" 2>/dev/null || true
87
90
  done
88
91
  ```
89
92
 
@@ -91,11 +94,58 @@ done
91
94
  - If it has `workflow:invalid` already → just remove `workflow:investigating`
92
95
  - If closed normally → add `workflow:merged`, remove `workflow:investigating`
93
96
 
94
- **Stale `needs-validation`** — remove from all closed issues:
97
+ **Stale `needs-validation`** — backfill from FORGE:INVESTIGATOR verdict where available; strip-only for issues with no verdict comment: <!-- Added: forge#1730 -->
98
+
99
+ This is the H1 backfill sweep from the Design Decision (#1730). For each closed finding-issue with `needs-validation`:
100
+ 1. Fetch any `FORGE:INVESTIGATOR` comment on the issue
101
+ 2. Extract the `**Verdict**: ...` line
102
+ 3. `CONFIRMED` → apply `validated` + remove `needs-validation`
103
+ 4. `NOT-CONFIRMED` / `INVALID` / `PARTIAL` → apply `false-positive` + remove `needs-validation`
104
+ 5. No verdict comment (or unrecognized verdict) → strip `needs-validation` only (no label added — do not guess)
105
+
106
+ **H4 is forbidden**: Do NOT use file-overlap heuristics (whether a fix-PR touched the cited file) to infer validated/false-positive. H4 has ~10% error rate and poisons the corpus. If there is no FORGE:INVESTIGATOR comment, the issue goes to strip-only.
107
+
95
108
  ```bash
109
+ BACKFILL_VALIDATED=0
110
+ BACKFILL_FALSE_POSITIVE=0
111
+ BACKFILL_STRIP_ONLY=0
112
+
96
113
  for NUM in {needs_validation_numbers}; do
97
- gh issue edit $NUM {GH_FLAG} --remove-label "needs-validation" 2>/dev/null || true
114
+ # Fetch FORGE:INVESTIGATOR comment if any
115
+ INVESTIGATOR_COMMENT=$(gh api repos/{GH_REPO}/issues/$NUM/comments \
116
+ --jq '[.[] | select(.body | contains("FORGE:INVESTIGATOR"))] | last | .body // ""' 2>/dev/null || echo "")
117
+
118
+ if [ -n "$INVESTIGATOR_COMMENT" ]; then
119
+ # Extract verdict — matches "**Verdict**: CONFIRMED" etc.
120
+ VERDICT=$(echo "$INVESTIGATOR_COMMENT" | grep -oE '\*\*Verdict\*\*: [A-Za-z0-9_][A-Za-z0-9_-]*' | sed 's/^\*\*Verdict\*\*: //' | head -1)
121
+
122
+ case "$VERDICT" in
123
+ CONFIRMED)
124
+ gh issue edit $NUM {GH_FLAG} --add-label "validated" --remove-label "needs-validation" 2>/dev/null || true
125
+ echo "#$NUM: needs-validation → validated (verdict: CONFIRMED)"
126
+ BACKFILL_VALIDATED=$((BACKFILL_VALIDATED + 1))
127
+ ;;
128
+ NOT-CONFIRMED|INVALID|PARTIAL)
129
+ gh issue edit $NUM {GH_FLAG} --add-label "false-positive" --remove-label "needs-validation" 2>/dev/null || true
130
+ echo "#$NUM: needs-validation → false-positive (verdict: $VERDICT)"
131
+ BACKFILL_FALSE_POSITIVE=$((BACKFILL_FALSE_POSITIVE + 1))
132
+ ;;
133
+ *)
134
+ # Unrecognized or empty verdict — strip-only (no guessing)
135
+ gh issue edit $NUM {GH_FLAG} --remove-label "needs-validation" 2>/dev/null || true
136
+ echo "#$NUM: needs-validation stripped (no recognized verdict in INVESTIGATOR comment)"
137
+ BACKFILL_STRIP_ONLY=$((BACKFILL_STRIP_ONLY + 1))
138
+ ;;
139
+ esac
140
+ else
141
+ # No INVESTIGATOR comment — strip-only
142
+ gh issue edit $NUM {GH_FLAG} --remove-label "needs-validation" 2>/dev/null || true
143
+ echo "#$NUM: needs-validation stripped (no FORGE:INVESTIGATOR comment found)"
144
+ BACKFILL_STRIP_ONLY=$((BACKFILL_STRIP_ONLY + 1))
145
+ fi
98
146
  done
147
+
148
+ echo "Backfill complete: validated=$BACKFILL_VALIDATED false-positive=$BACKFILL_FALSE_POSITIVE strip-only=$BACKFILL_STRIP_ONLY"
99
149
  ```
100
150
 
101
151
  ### 1C: Report closed issues with NO workflow label
@@ -176,7 +226,7 @@ For each orphaned issue found:
176
226
  ```bash
177
227
  gh issue close $NUM {GH_FLAG} --comment "Closed by cleanup — PR #$MERGED_PR was already merged."
178
228
  gh issue edit $NUM {GH_FLAG} --add-label "workflow:merged"
179
- gh issue edit $NUM {GH_FLAG} --remove-label "workflow:in-review" 2>/dev/null || true
229
+ gh issue edit $NUM {GH_FLAG} --remove-label "workflow:in-review,workflow:awaiting-merge" 2>/dev/null || true
180
230
  ```
181
231
 
182
232
  Also check open issues with `workflow:building` — same pattern (search for merged PRs referencing them).
@@ -323,101 +373,132 @@ Do NOT call `gh api ... -X PATCH -f state=closed` on any milestone. This phase i
323
373
  ## Phase 4B: P3 Batch Sweep (if `batch-p3` or `all`)
324
374
 
325
375
  <!-- Added: forge#1333 -->
376
+ <!-- Extended: forge#1828 — brought into lockstep with the P3 batching rule extension in orchestrate/phase-1-resolve.md and orchestrate/phase-4-execution.md (forge#1818): default-batchable eligibility, surface-area grouping, two-tier threshold, extended safety exclusions -->
326
377
 
327
- **Purpose**: Fold stale unbatched P3 review findings into domain-grouped batch issues. Reduces pipeline overhead from individual per-finding full-pipeline runs.
378
+ **Purpose**: Fold stale unbatched P3 review findings into surface-area-grouped batch issues (same file first, leaf directory as broader fallback). Reduces pipeline overhead from individual per-finding full-pipeline runs. This sweep applies the same batching rule as `orchestrate/phase-1-resolve.md`'s "P3 Review-Finding Batching" section (the canonical definition) — this file is a periodic, cross-run pass over the same policy that Phase 1 (at `/orchestrate` start) and Phase 4C (mid-run cascade findings, `phase-4-execution.md`) already apply.
328
379
 
329
380
  **Skip if**: Input is not `batch-p3` and not `all`.
330
381
 
331
382
  ### Step 4B.1: Fetch open batchable P3 findings
332
383
 
384
+ Eligibility is **default-batchable**: any open `review-finding` + `priority:P3` issue qualifies unless excluded below. The `<!-- FORGE:BATCHABLE -->` marker (still appended by `review-pr.md` at finding-creation time) is honored when present but is no longer required — matching `phase-1-resolve.md`'s eligibility rule. <!-- Changed: forge#1828 — marker was previously mandatory -->
385
+
333
386
  ```bash
334
- # Find all open P3 review findings that carry the FORGE:BATCHABLE annotation
335
- # and are not already members of a batch (no "batch" label)
387
+ # Find all open, unbatched P3 review findings. Excludes findings already
388
+ # claimed by a batch ("batch" label), and applies the same extended safety
389
+ # exclusions (security, billing, anti-bot, auth) as the other two batching
390
+ # sites, using the identical jq test() (Oniguruma) patterns so classification
391
+ # cannot diverge between the three sweep locations. <!-- Changed: forge#1828 -->
336
392
  UNBATCHED_P3=$(gh issue list {GH_FLAG} \
337
393
  --state open \
338
394
  --label "review-finding,priority:P3" \
339
395
  --limit 500 \
340
396
  --json number,title,body,labels,createdAt \
341
- --jq '.[] | select(.body | test("<!-- FORGE:BATCHABLE -->"))
342
- | select(([.labels[].name] | any(. == "batch")) | not)
343
- | select(([.labels[].name] | any(. == "security" or . == "billing")) | not)
344
- | select((.title | test("security|billing|payment|stripe"; "i")) | not)')
397
+ --jq '.[] | select(([.labels[].name] | any(. == "batch")) | not)
398
+ | select((.title | test("security|billing|anti-bot|auth"; "i")) | not)
399
+ | select(.body | test("## Problem[\\s\\S]{0,500}(security|billing|anti-bot|auth)"; "i") | not)
400
+ | select(([.labels[].name] | any(. == "security" or . == "billing" or . == "anti-bot" or . == "auth")) | not)')
345
401
 
346
402
  echo "Unbatched batchable P3 findings: $(echo "$UNBATCHED_P3" | jq -s 'length')"
347
403
  ```
348
404
 
349
- ### Step 4B.2: Group by domain
405
+ ### Step 4B.2: Group by surface area (same file, then leaf directory)
350
406
 
351
- For each finding, extract the domain from the first affected file path under `## Affected Files`:
407
+ <!-- Changed: forge#1828 — was coarse domain grouping (`cut -d/ -f1,2,3`); now surface area, matching phase-1-resolve.md -->
352
408
 
353
- ```bash
354
- # Domain = top-level directory of the first affected file
355
- # e.g., "services/api/auth/login.py" → "services/api/auth"
356
- # "commands/review-pr.md" → "commands"
357
- # Exclude findings where the domain contains "billing" or "security"
409
+ For each finding, extract the exact affected-file path under `## Affected Files` (primary grouping key) and its leaf directory — `dirname` of that path — as a broader fallback key:
358
410
 
359
- # Group findings by domain, track oldest creation timestamp per group
360
- declare -A DOMAIN_ISSUES
361
- declare -A DOMAIN_OLDEST
411
+ ```bash
412
+ # Surface area = the exact affected file path listed first under "## Affected
413
+ # Files" (primary key). Leaf directory = dirname of that file (fallback key).
414
+ # e.g., "commands/orchestrate/phase-1-resolve.md" → file "commands/orchestrate/phase-1-resolve.md", leaf-dir "commands/orchestrate"
415
+ # "commands/review-pr.md" → file "commands/review-pr.md", leaf-dir "commands"
416
+ # (security/billing/anti-bot/auth paths are already excluded in Step 4B.1)
417
+
418
+ # Group findings by file (primary) and by leaf directory (fallback), tracking
419
+ # oldest creation timestamp per group
420
+ declare -A FILE_ISSUES
421
+ declare -A FILE_OLDEST
422
+ declare -A DIR_ISSUES
423
+ declare -A DIR_OLDEST
362
424
 
363
425
  echo "$UNBATCHED_P3" | jq -c '.[]' | while read -r issue; do
364
426
  NUM=$(echo "$issue" | jq -r '.number')
365
427
  CREATED=$(echo "$issue" | jq -r '.createdAt')
366
428
  BODY=$(echo "$issue" | jq -r '.body')
367
429
 
368
- # Extract first affected file path from body
369
- FIRST_FILE=$(echo "$BODY" | sed -n '/^## Affected Files/,/^## /p' | grep -oP '`[^`]+`' | head -1 | tr -d '`')
430
+ # Extract first affected file path from body. Uses POSIX ERE (`grep -oE`),
431
+ # NOT PCRE lookbehind (`grep -oP '(?<=...)'`) — the prior fallback line here
432
+ # used `-P`, which BSD grep on macOS does not support (forge#1767). This
433
+ # pattern matches the portable one already used in phase-4-execution.md's
434
+ # same-run surface-area batching. <!-- Changed: forge#1828 -->
435
+ FIRST_FILE=$(echo "$BODY" | sed -n '/^## Affected Files/,/^## /p' | grep -oE '`[^`]+`' | head -1 | tr -d '`')
370
436
  if [ -z "$FIRST_FILE" ]; then
371
- FIRST_FILE=$(echo "$BODY" | grep -oP '(?<=\`)[^`]+\.(?:py|ts|tsx|js|go|rs|java|sh|md)(?=\`)' | head -1 || echo "unknown")
437
+ FIRST_FILE=$(echo "$BODY" | grep -oE '`[^`]+\.(py|tsx?|jsx?|sql|json|ya?ml|sh|md)`' | head -1 | tr -d '`')
372
438
  fi
373
- DOMAIN=$(echo "$FIRST_FILE" | cut -d/ -f1,2,3 | sed 's|/[^/]*$||')
374
- [ -z "$DOMAIN" ] && DOMAIN="general"
439
+ [ -z "$FIRST_FILE" ] && FIRST_FILE="unknown"
440
+ LEAF_DIR=$(dirname "$FIRST_FILE")
375
441
 
376
- echo "${NUM}:${DOMAIN}:${CREATED}"
442
+ echo "${NUM}:${FIRST_FILE}:${LEAF_DIR}:${CREATED}"
377
443
  done
378
444
  ```
379
445
 
380
- ### Step 4B.3: Apply batching threshold
446
+ ### Step 4B.3: Apply two-tier batching threshold
447
+
448
+ <!-- Changed: forge#1828 — added lower same-file tier, matching phase-1-resolve.md's two-tier threshold -->
381
449
 
382
- For each domain group:
383
- - **Trigger**: 5+ unbatched batchable P3 findings in the domain, **OR** the oldest finding in the domain is > 72 hours old
384
- - **No trigger**: fewer than 5 findings AND none older than 72 hours → skip (no batch created)
450
+ - **Same-file cluster** (primary, low threshold): **2+** unbatched batchable P3 findings share the exact same affected file → create a batch issue for that file cluster.
451
+ - **Leaf-directory cluster** (broader fallback, existing threshold preserved): among findings NOT already claimed by a same-file cluster above, **5+** share the same leaf directory, **OR** the oldest finding in that leaf directory is > 72 hours old → create a batch issue for that leaf-directory cluster.
452
+ - Form same-file clusters first; only findings left ungrouped after that pass are evaluated for leaf-directory clustering. A finding is claimed by at most one batch.
385
453
 
386
454
  ```bash
387
455
  NOW_EPOCH=$(date +%s)
388
456
  HOURS_72=$((72 * 3600))
457
+ MAX_MEMBERS=8 # Changed: forge#1828 — was 10, matching phase-1-resolve.md's cap
458
+
459
+ # Pass 1 — same-file clusters (2+ members). Claim member issue numbers into
460
+ # CLAIMED_BY_FILE so Pass 2 (leaf-directory) skips them.
461
+ CLAIMED_BY_FILE=""
462
+ echo "$FILE_ISSUES" | while IFS=: read -r file issue_list; do
463
+ COUNT=$(echo "$issue_list" | tr ',' '\n' | grep -c .)
464
+ if [ "$COUNT" -ge 2 ]; then
465
+ echo "Batching $COUNT P3 findings — same-file cluster: ${file}"
466
+ # Create batch issue(s) in chunks of <= MAX_MEMBERS members (see Batch
467
+ # creation below). Record consumed issue numbers in CLAIMED_BY_FILE.
468
+ fi
469
+ done
389
470
 
390
- # For each domain group that meets the threshold, create a batch issue
391
- # (max 10 members per batch — if more, create multiple batches of ≤ 10 each)
392
-
393
- # Exclude domains with "security" or "billing" in the domain path
394
- echo "$DOMAIN_ISSUES" | while IFS=: read -r domain issue_list; do
395
- echo "$domain" | grep -qiE 'security|billing' && continue
471
+ # Pass 2 — leaf-directory clusters (5+ members, or oldest > 72h), excluding
472
+ # any issue already claimed by a same-file cluster in Pass 1
473
+ echo "$DIR_ISSUES" | while IFS=: read -r dir issue_list; do
474
+ REMAINING=$(echo "$issue_list" | tr ',' '\n' | grep -vxF -f <(printf '%s\n' "$CLAIMED_BY_FILE" | tr ',' '\n') || true)
475
+ COUNT=$(echo "$REMAINING" | grep -c . || echo 0)
476
+ [ "$COUNT" -eq 0 ] && continue
396
477
 
397
- COUNT=$(echo "$issue_list" | tr ',' '\n' | wc -l)
398
- OLDEST_EPOCH=$(echo "$DOMAIN_OLDEST[$domain]" | date -d "$..." +%s 2>/dev/null || echo "$NOW_EPOCH")
478
+ OLDEST_EPOCH=$(echo "${DIR_OLDEST[$dir]}" | date -d "$..." +%s 2>/dev/null || echo "$NOW_EPOCH")
399
479
  AGE_SECS=$((NOW_EPOCH - OLDEST_EPOCH))
400
480
 
401
481
  if [ "$COUNT" -ge 5 ] || [ "$AGE_SECS" -ge "$HOURS_72" ]; then
402
- echo "Batching $COUNT P3 findings in domain: ${domain}"
403
- # Create batch issues (in chunks of ≤ 10)
404
- # See orchestrate.md Phase 1 for the batch creation template
482
+ echo "Batching $COUNT P3 findings — leaf-directory cluster: ${dir}"
483
+ # Create batch issue(s) in chunks of <= MAX_MEMBERS members (see Batch
484
+ # creation below).
405
485
  fi
406
486
  done
407
487
  ```
408
488
 
409
- **Batch creation** uses the same template as `orchestrate.md Phase 1 → P3 Review-Finding Batching`. After creating the batch issue, add the `batch` label to each member issue to prevent re-batching on the next sweep.
489
+ **Batch creation** uses the same template as `orchestrate.md Phase 1 → P3 Review-Finding Batching`, including its surface-area path sanitization (`tr -cd 'A-Za-z0-9._/-'` on the file/directory value before interpolating it into the batch issue's `--title`/`--body`). After creating the batch issue, add the `batch` label to each member issue to prevent re-batching on the next sweep.
410
490
 
411
491
  ### Step 4B.4: Report
412
492
 
413
493
  ```
414
494
  ## P3 Batch Sweep
415
495
 
416
- | Domain | Unbatched P3s | Threshold Met | Action |
417
- |--------|--------------|---------------|--------|
418
- | {domain} | {N} | ≥5 issues | Created batch #{NUM} ({M} members) |
419
- | {domain} | {N} | Oldest > 72h | Created batch #{NUM} ({M} members) |
420
- | {domain} | {N} | Below threshold | No action |
496
+ | Surface Area | Unbatched P3s | Threshold Met | Action |
497
+ |---------------|--------------|---------------|--------|
498
+ | {file} | {N} | ≥2 same-file | Created batch #{NUM} ({M} members) |
499
+ | {leaf-dir} | {N} | ≥5 issues | Created batch #{NUM} ({M} members) |
500
+ | {leaf-dir} | {N} | Oldest > 72h | Created batch #{NUM} ({M} members) |
501
+ | {leaf-dir} | {N} | Below threshold | No action |
421
502
 
422
503
  Total batch issues created: {N}
423
504
  Total P3 findings batched: {N}
@@ -465,7 +546,9 @@ fi
465
546
  | workflow:in-review → workflow:merged | {N} |
466
547
  | workflow:building → workflow:merged | {N} |
467
548
  | workflow:investigating → cleaned | {N} |
468
- | needs-validation removed | {N} |
549
+ | needs-validation → validated (backfill H1) | {N} |
550
+ | needs-validation → false-positive (backfill H1) | {N} |
551
+ | needs-validation stripped (no verdict) | {N} |
469
552
 
470
553
  ### Stale-Open Intermediate Issues (Phase 1D)
471
554
  | Issue | Label | Last Update | Action |
@@ -501,8 +584,9 @@ fi
501
584
  {N} closed issues have no workflow label (closed outside pipeline — no action needed)
502
585
 
503
586
  ### P3 Batch Sweep Results
504
- | Domain | Unbatched P3s | Action |
505
- |--------|--------------|--------|
506
- | {domain} | {N} | Created batch #{NUM} with {M} members |
507
- | {domain} | {N} | Below threshold — no action (< 5 findings, none > 72h) |
587
+ | Surface Area | Unbatched P3s | Action |
588
+ |---------------|--------------|--------|
589
+ | {file} | {N} | Created batch #{NUM} with {M} members (same-file, ≥2) |
590
+ | {leaf-dir} | {N} | Created batch #{NUM} with {M} members (leaf-directory, ≥5 or >72h) |
591
+ | {leaf-dir} | {N} | Below threshold — no action (< 5 findings, none > 72h, no same-file pair) |
508
592
  ```
@@ -10,7 +10,7 @@ install: extras
10
10
 
11
11
  You are the ForgeDock compatibility auditor. Produce a point-in-time advisory report that shows whether the user's installed Claude Code version is current and which ForgeDock features may behave differently on their runtime. This command is **non-blocking** — it never aborts the session. All output is advisory.
12
12
 
13
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
13
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
14
14
  **NEVER use plan mode (EnterPlanMode).**
15
15
 
16
16
  ---
@@ -16,7 +16,7 @@ install: extras
16
16
 
17
17
  You are the pipeline's deploy awareness layer. Before the user merges staging → main (triggering CI/CD deployment), this command shows exactly what's going out: which PRs, which issues, what changed, and what risks exist.
18
18
 
19
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
19
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
20
20
 
21
21
  **NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
22
22
 
@@ -13,7 +13,7 @@ allowed-tools: Bash, Read, Grep, Glob, Skill
13
13
  **NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
14
14
  **NEVER use the Agent tool** — deploy-pr dispatches sub-skills via `Skill(...)` only. The Agent tool bypasses the allowed-tools constraint and produces opaque output that cannot be structured into the deploy result.
15
15
 
16
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`.
16
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`.
17
17
 
18
18
  <!-- FORGE:SPEC_LOADED — deploy-pr.md loaded and active. Agent is bound by this spec. -->
19
19
 
@@ -10,7 +10,7 @@ install: extras
10
10
 
11
11
  **NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
12
12
 
13
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
13
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
14
14
 
15
15
  You are the pipeline's diagnostic layer. Given a single GitHub issue number, you reconstruct the full FORGE annotation chain for that pipeline run, identify the exact failure point, and suggest specific remediation steps. This command is **read-only** — it never mutates GitHub state.
16
16
 
@@ -10,7 +10,7 @@ install: extras
10
10
 
11
11
  You translate the FORGE pipeline's machine-readable annotations on a GitHub issue or PR into a clear, plain-English narrative that anyone can understand — no pipeline knowledge required. This is a read-only command: it never writes to GitHub, never changes labels, and never modifies any state.
12
12
 
13
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
13
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
14
14
  **NEVER use plan mode (EnterPlanMode).**
15
15
 
16
16
  ---
@@ -13,7 +13,7 @@ argument-hint: <PR number or URL> [--max-attempts N] [--repo owner/repo]
13
13
 
14
14
  **Output**: Structured result — `{ pr, status, attempts, fixes_applied }` — printed to stdout and available to the calling skill.
15
15
 
16
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
16
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
17
17
  **NEVER use plan mode (EnterPlanMode).**
18
18
  **NEVER force-push or amend commits** — always create new commits.
19
19
 
@@ -11,7 +11,7 @@ argument-hint: [--preserve | --interactive | --section <name>]
11
11
 
12
12
  You complete the `forge.yaml` configuration that `npx forgedock init` started. The CLI generates required sections (project, paths, branches) from auto-detection. Your job is to scan the codebase, query GitHub APIs, and produce a complete `forge.yaml` with every applicable optional section filled — using detected values directly, without asking for confirmation.
13
13
 
14
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
14
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
15
15
  **NEVER use plan mode (EnterPlanMode).**
16
16
 
17
17
  ---
@@ -16,7 +16,7 @@ install: extras
16
16
 
17
17
  You are the pipeline's incident response coordinator. When production goes down or a critical bug surfaces, this command orchestrates the response: validates the hotfix before deploy, reconstructs the incident timeline, and produces a post-incident analysis.
18
18
 
19
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
19
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
20
20
 
21
21
  **NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
22
22
 
@@ -109,7 +109,7 @@ gh api repos/{owner}/{repo}/issues/{NUMBER}/comments \
109
109
  Spawn a validation agent to check the proposed fix:
110
110
 
111
111
  ```
112
- Agent(model="sonnet", prompt="
112
+ Agent(model="{SUBAGENT_MODEL}", prompt="
113
113
  You are validating a hotfix for a P0 production incident.
114
114
 
115
115
  Issue: #{NUMBER} — {TITLE}
@@ -242,7 +242,7 @@ gh pr list --state merged --base main --json number,title,mergedAt \
242
242
  Spawn an investigation agent:
243
243
 
244
244
  ```
245
- Agent(model="sonnet", prompt="
245
+ Agent(model="{SUBAGENT_MODEL}", prompt="
246
246
  You are conducting a post-incident root cause analysis.
247
247
 
248
248
  Incident: #{NUMBER} — {TITLE}
package/commands/issue.md CHANGED
@@ -11,7 +11,7 @@ argument-hint: [description of the problem or feature]
11
11
 
12
12
  You create GitHub issues with the exact structure the `/work-on` pipeline expects. Every issue you create must give the investigation agent enough context to find the right files on the first pass — no vague descriptions, no missing domains, no ambiguous scope.
13
13
 
14
- **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
14
+ **Agent model policy**: `model: "{DEFAULT_MODEL}"` — resolved from forge.yaml `agents.default_model`, else "sonnet" (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
15
15
  **NEVER use plan mode (EnterPlanMode).**
16
16
 
17
17
  ---