forgedock 1.26.0 → 1.28.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 (137) hide show
  1. package/README.md +4 -1
  2. package/bin/bootstrap.mjs +55 -0
  3. package/bin/engine/outcome-codes.mjs +13 -0
  4. package/bin/engine/phases.mjs +38 -2
  5. package/bin/engine/telemetry-seam.mjs +41 -6
  6. package/bin/engine/telemetry-seam.test.mjs +35 -0
  7. package/bin/engine-cli.mjs +616 -54
  8. package/bin/engine.mjs +13 -5
  9. package/bin/forgedock.mjs +169 -39
  10. package/bin/gc.mjs +580 -0
  11. package/bin/init-detect.mjs +68 -3
  12. package/bin/init-enrich.mjs +3 -0
  13. package/bin/init-repo.mjs +213 -0
  14. package/bin/journey.mjs +278 -43
  15. package/bin/lean/admission.mjs +33 -3
  16. package/bin/lean/batch.mjs +24 -5
  17. package/bin/lean/batch.test.mjs +54 -4
  18. package/bin/lean/checks.mjs +199 -0
  19. package/bin/lean/checks.test.mjs +183 -0
  20. package/bin/lean/conformance.mjs +235 -37
  21. package/bin/lean/conformance.test.mjs +143 -5
  22. package/bin/lean/credentials.mjs +29 -0
  23. package/bin/lean/depcache.mjs +312 -0
  24. package/bin/lean/depcache.test.mjs +298 -0
  25. package/bin/lean/drift.mjs +198 -0
  26. package/bin/lean/drift.test.mjs +223 -0
  27. package/bin/lean/effects.mjs +178 -16
  28. package/bin/lean/execution.e2e.test.mjs +81 -0
  29. package/bin/lean/execution.mjs +431 -0
  30. package/bin/lean/execution.test.mjs +583 -0
  31. package/bin/lean/heavy.mjs +289 -0
  32. package/bin/lean/heavy.test.mjs +207 -0
  33. package/bin/lean/lean.mjs +877 -151
  34. package/bin/lean/lean.test.mjs +1798 -28
  35. package/bin/lean/ledger.mjs +92 -0
  36. package/bin/lean/ledger.test.mjs +100 -0
  37. package/bin/lean/limits.mjs +58 -0
  38. package/bin/lean/limits.test.mjs +150 -0
  39. package/bin/lean/orchestrate.mjs +321 -19
  40. package/bin/lean/prompts.mjs +125 -17
  41. package/bin/lean/refactor.mjs +276 -0
  42. package/bin/lean/refactor.test.mjs +359 -0
  43. package/bin/lean/render.mjs +25 -6
  44. package/bin/lean/review-trigger.mjs +134 -0
  45. package/bin/lean/review-trigger.test.mjs +104 -0
  46. package/bin/lean/sandbox-provider.contract.test.mjs +257 -0
  47. package/bin/lean/sandbox-remote.mjs +142 -0
  48. package/bin/lean/sched.mjs +221 -0
  49. package/bin/lean/sched.test.mjs +310 -0
  50. package/bin/lean/schedd.mjs +247 -0
  51. package/bin/lean/schedd.test.mjs +180 -0
  52. package/bin/lean/toolchain.mjs +228 -0
  53. package/bin/lean/toolchain.test.mjs +154 -0
  54. package/bin/lean/verify.mjs +124 -0
  55. package/bin/lean/verify.test.mjs +118 -0
  56. package/bin/lean/worktree.mjs +69 -0
  57. package/bin/lean/worktree.test.mjs +141 -0
  58. package/bin/runner.mjs +53 -10
  59. package/bin/stats/github-source.mjs +1 -1
  60. package/bin/stats/index.mjs +14 -2
  61. package/bin/stats/query.mjs +72 -0
  62. package/bin/stats/stats.test.mjs +54 -0
  63. package/commands/adopt.md +3 -0
  64. package/commands/audit.md +5 -8
  65. package/commands/autopilot.md +2 -0
  66. package/commands/bootstrap.md +35 -0
  67. package/commands/cleanup.md +7 -22
  68. package/commands/forge-stats.md +2 -1
  69. package/commands/forgedock-init.md +125 -6
  70. package/commands/issue.md +89 -830
  71. package/commands/milestone.md +55 -6
  72. package/commands/orchestrate.md +10 -2
  73. package/commands/review-pr.md +2 -2
  74. package/commands/upgrade-deps.md +3 -3
  75. package/commands/work-on/build/validate.md +4 -0
  76. package/commands/work-on/close.md +2 -0
  77. package/commands/work-on/decompose.md +24 -6
  78. package/docs/CI.md +3 -3
  79. package/docs/CONFIG.md +227 -3
  80. package/docs/OUTCOME-CODES.md +11 -0
  81. package/docs/TELEMETRY.md +1 -1
  82. package/docs/site/command-reference.md +22 -0
  83. package/docs/site/getting-started.md +2 -0
  84. package/docs/site/start-a-new-project.md +327 -0
  85. package/package.json +1 -1
  86. package/scripts/affected-files-check.sh +91 -0
  87. package/scripts/affected-files-check.test.sh +80 -0
  88. package/scripts/bootstrap-create.mjs +393 -0
  89. package/scripts/bootstrap-validate.mjs +321 -0
  90. package/scripts/calibration.mjs +116 -0
  91. package/scripts/check-devdocs-freshness.sh +125 -0
  92. package/scripts/check-devdocs-freshness.test.sh +130 -0
  93. package/scripts/check-spec-markers.sh +3 -0
  94. package/scripts/check-spec-size.sh +29 -11
  95. package/scripts/check-spec-size.test.sh +27 -4
  96. package/scripts/ci-local/lib.mjs +8 -1
  97. package/scripts/ci-local/parallelism.test.mjs +27 -0
  98. package/scripts/ci-local/run.mjs +16 -5
  99. package/scripts/ci-local/scoping.mjs +82 -0
  100. package/scripts/ci-local.test.sh +14 -2
  101. package/scripts/fixtures/symbol-index/other/sample.rb +3 -0
  102. package/scripts/fixtures/symbol-index/py/pkg/__init__.py +1 -0
  103. package/scripts/fixtures/symbol-index/py/pkg/a.py +5 -0
  104. package/scripts/fixtures/symbol-index/py/pkg/b.py +14 -0
  105. package/scripts/fixtures/symbol-index/py/pkg/c.py +1 -0
  106. package/scripts/fixtures/symbol-index/py/sample.py +41 -0
  107. package/scripts/fixtures/symbol-index/ts/sample.ts +13 -0
  108. package/scripts/fixtures/symbol-move/gen_big.py +201 -0
  109. package/scripts/fixtures/symbol-move-ts/gen_big.mjs +56 -0
  110. package/scripts/forge-root.test.sh +1 -1
  111. package/scripts/hotspots.sh +100 -0
  112. package/scripts/hotspots.test.sh +101 -0
  113. package/scripts/issue-create.sh +197 -0
  114. package/scripts/issue-create.test.sh +166 -0
  115. package/scripts/maintenance-scan.sh +185 -0
  116. package/scripts/maintenance-scan.test.sh +89 -0
  117. package/scripts/milestone-drift.sh +206 -0
  118. package/scripts/milestone-drift.test.sh +120 -0
  119. package/scripts/northstar-docs.test.sh +68 -0
  120. package/scripts/refactor-verify.sh +368 -0
  121. package/scripts/refactor-verify.test.sh +202 -0
  122. package/scripts/review-prepare.sh +11 -9
  123. package/scripts/review-prepare.test.sh +14 -0
  124. package/scripts/structure-delta.sh +155 -0
  125. package/scripts/structure-delta.test.sh +136 -0
  126. package/scripts/symbol-index.mjs +253 -0
  127. package/scripts/symbol-index.py +354 -0
  128. package/scripts/symbol-index.test.sh +119 -0
  129. package/scripts/symbol-move-ts.test.sh +161 -0
  130. package/scripts/symbol-move.mjs +379 -0
  131. package/scripts/symbol-move.py +383 -0
  132. package/scripts/symbol-move.test.sh +168 -0
  133. package/templates/bootstrap/interview.md +34 -0
  134. package/templates/bootstrap/issue-body.md +42 -0
  135. package/templates/bootstrap/needs-human-issue.md +45 -0
  136. package/templates/bootstrap/stack-profiles.md +34 -0
  137. package/templates/bootstrap/technical-spike.md +75 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: Create, manage, and ship milestones — the top-level planning layer for feature development
3
- argument-hint: "[create <name> | status | ship <slug> | sync <slug>]"
3
+ argument-hint: "[create <name> [--plan <file>] [--greenfield] [--spike <path>] | status | ship <slug> | sync <slug>]"
4
4
  install: extras
5
5
  ---
6
6
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
@@ -17,7 +17,7 @@ Milestones are the top-level planning unit. They group related issues into a shi
17
17
  **Hierarchy**: Milestone → Issues → Sub-issues (optional decomposition)
18
18
 
19
19
  **NEVER use plan mode (EnterPlanMode).**
20
- **NEVER use the Agent tool for issue work** — milestone dispatches issue work via `Skill(skill="{FORGE_SKILL_PREFIX}work-on", ...)` only. The Agent tool bypasses the Skill pipeline's label state machine, FORGE annotations, and structured review. The Agent tool may be used only for coordination tasks within milestone planning itself (e.g., parallel issue list queries) — never for /work-on sub-dispatch.
20
+ **NEVER use the Agent tool for issue work** — milestone dispatches issue work via `Skill(skill="{FORGE_SKILL_PREFIX}work-on", ...)` only. Agent bypasses the Skill pipeline's label state machine, FORGE annotations and structured review; use it only to coordinate milestone planning (e.g. parallel issue-list queries), never for /work-on sub-dispatch.
21
21
 
22
22
  <!-- FORGE:SPEC_LOADED — milestone.md loaded and active. Agent is bound by this spec. -->
23
23
 
@@ -88,10 +88,12 @@ Parse `$ARGUMENTS` to determine which action to take:
88
88
  | Input Pattern | Action |
89
89
  |--------------|--------|
90
90
  | `create <name>` or just a descriptive phrase | → **Create Milestone** |
91
+ | `create <name> --plan <file>` / `--greenfield` / `--spike <path>` | → **Create Milestone**, greenfield mode (Step 4G) |
91
92
  | `status` (no args, or "status") | → **Show All Milestones** |
92
93
  | `status <slug>` | → **Show Milestone Detail** |
93
94
  | `ship <slug>` | → **Ship Milestone** |
94
95
  | `sync <slug>` | → **Sync Milestone Branch** |
96
+ | `drift <slug>` | → **Milestone Drift** |
95
97
  | `close <slug>` | → **Close Milestone** |
96
98
 
97
99
  ---
@@ -105,6 +107,25 @@ The user provides a name or description. Extract:
105
107
  - **Slug**: Lowercase, hyphenated, max 40 chars (e.g., `llm-extraction-platform`)
106
108
  - **Description**: What this milestone delivers, extracted from user's input
107
109
 
110
+ Optional greenfield flags (strip them from the name before deriving title and slug): <!-- Added: forge#4175 -->
111
+
112
+ | Flag | Meaning |
113
+ |------|---------|
114
+ | `--plan <file>` | Precomputed plan from `/bootstrap`: per issue a title, type, priority, size, dependencies, planned Affected Files and acceptance criteria. Implies greenfield mode. |
115
+ | `--greenfield` | Greenfield mode: the milestone builds code that does not exist yet, so Step 4 code-read gating is replaced by Step 4G. |
116
+ | `--spike <path>` | Spike doc declaring the `Repo layout` (implies greenfield mode). Default `docs/engineering/technical-spike.md`, repo-relative, same semantics as `commands/issue.md` `--spike`. |
117
+
118
+ ```bash
119
+ PLAN_FILE=""; GREENFIELD=false; SPIKE_DOC="docs/engineering/technical-spike.md"
120
+ # set from the flags above: --plan => PLAN_FILE + GREENFIELD=true; --greenfield => GREENFIELD=true; --spike => SPIKE_DOC + GREENFIELD=true
121
+ # A plan with a spike doc declaring a Repo layout is greenfield even without the flag (same predicate as /issue Phase 3E).
122
+ [ -n "$PLAN_FILE" ] && [ -f "$SPIKE_DOC" ] && grep -qiE '^#+[[:space:]].*repo layout' "$SPIKE_DOC" && GREENFIELD=true
123
+ # Passed to every default-repo /issue call in Step 6; empty in brownfield mode.
124
+ GREENFIELD_ARGS=""; [ "$GREENFIELD" = "true" ] && GREENFIELD_ARGS=" --greenfield --spike \"$SPIKE_DOC\""
125
+ ```
126
+
127
+ Without any of these flags `GREENFIELD=false`, `GREENFIELD_ARGS` is empty and every step below behaves as in brownfield mode.
128
+
108
129
  ### Step 2: Create the GitHub milestone
109
130
 
110
131
  ```bash
@@ -127,7 +148,9 @@ git push origin milestone/{slug}
127
148
 
128
149
  ### Step 4: Scope decomposition
129
150
 
130
- **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 -->
151
+ **Greenfield mode** (`GREENFIELD=true`): skip 4A–4C and run **Step 4G** instead. Steps 2, 3 (the `milestone/{slug}` branch), 5, 6 and 6B run unchanged in both modes. Brownfield (`GREENFIELD=false`): Step 4G does not apply and 4A–4C below are mandatory. <!-- Added: forge#4175 -->
152
+
153
+ **Investigation-gated decomposition** — do NOT skip code reads: one shallow pass with no per-issue code reads yields under-specified issues that ship incomplete, so investigate each proposed issue before writing its body. See `work-on/decompose.md` Phase D0 for the reference pattern that enforces this gate. <!-- Added: forge#293 -->
131
154
 
132
155
  **4A: First pass — enumerate proposed issues (no code reads yet)**
133
156
 
@@ -153,7 +176,21 @@ For each proposed issue, after completing the per-issue investigation in Step 4B
153
176
  - **Size**: S (1-2 files), M (3-5 files), L (6+ files)
154
177
  - **Dependencies**: Which other issues must be done first (by number)
155
178
  - **Affected files**: Full list of files enumerated during the Step 4B code read (not estimated)
156
- - **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.
179
+ - **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: a reviewer must be able to verify coverage without reading the implementation.
180
+
181
+ **4G: Greenfield / precomputed plan (only when `GREENFIELD=true`)** <!-- Added: forge#4175 -->
182
+
183
+ There is no code to read yet, so the per-issue code reads of 4B are skipped. The spike doc's `Repo layout` replaces them as the source of truth for paths:
184
+
185
+ 1. **Issue list**: with `--plan`, take the issues from `PLAN_FILE` as-is (title, type, priority, size, dependencies, planned Affected Files, acceptance criteria). Without a plan, enumerate them as in 4A and write each per 4C, reading the spike doc (layout, data model, route/API map) instead of code.
186
+ 2. **Validate planned paths**: write each issue body (Step 6 template) to an entity-scoped `mktemp` file and run the same deterministic check `/issue` Phase 3E uses. Exit 1 (`MISSING <path>`) or 2 (spike doc missing or no layout) → fix the plan or the spike layout; do not create that issue.
187
+ ```bash
188
+ type forge_resolve >/dev/null 2>&1 || { setopt nonomatch 2>/dev/null || true; FORGE_SOURCED_ROOT='${CLAUDE_PLUGIN_ROOT}'; for _r in "$(cygpath -u "${FORGEDOCK_HOME:-}" 2>/dev/null || printf %s "${FORGEDOCK_HOME:-}")" '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGE_HOME:-}" "$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null | sed 's|/commands/work-on\.md$||')" "$(head -n 1 "${CODEX_HOME:-$HOME/.codex}/forge-home" 2>/dev/null)" "$HOME/.claude/plugins/marketplaces/forgedock" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/plugins/cache/forgedock/forgedock/* "$HOME/.claude"/plugins/cache/forgedock/forgedock/*; do case "$_r" in /*) [ -f "$_r/scripts/lib/forge-resolve.sh" ] && { . "$_r/scripts/lib/forge-resolve.sh"; break; };; esac; done; type forge_resolve >/dev/null 2>&1 || forge_resolve() { :; }; }
189
+ AF_CHECK_SCRIPT=$(forge_resolve affected-files-check.sh)
190
+ [ -n "$AF_CHECK_SCRIPT" ] && bash "$AF_CHECK_SCRIPT" "$PLAN_ISSUE_BODY_FILE" --spike "$SPIKE_DOC"
191
+ ```
192
+ If the helper does not resolve, leave the check to `/issue` Phase 3E, which runs it again on every Step 6 call because `GREENFIELD_ARGS` carries `--greenfield --spike`.
193
+ 3. Acceptance criteria stay exhaustive and falsifiable (4B rule 3); the plan's `Depends on` order is kept.
157
194
 
158
195
  **Project ecosystem context** (from `forge.yaml`):
159
196
  - **Primary repo** (`{GH_REPO}`): The main project codebase at `{REPO_PATH}`
@@ -168,7 +205,7 @@ Present the proposed issues to the user. Ask:
168
205
 
169
206
  ### Step 6: Create issues and assign to milestone
170
207
 
171
- **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 -->
208
+ **Issue body standard**: Use the **Pipeline Issue Template** from `issue.md` Phase 3D as the body structure for every issue created here (it is the single canonical standard; no bespoke inline template). The body content (Problem, Root Cause, Affected Files, Acceptance Criteria, Context, Dependencies) comes from the per-issue investigation in Step 4B. <!-- Added: forge#293 --> In greenfield mode it comes from the Step 4G plan instead, and Affected Files are planned paths inside the spike's `Repo layout`. <!-- Added: forge#4175 -->
172
209
 
173
210
  **Route creation through `/issue`'s programmatic invocation contract** (`commands/issue.md`, added #2085) instead of calling `gh issue create` directly — this gets dedup (Phase 2D) and mandatory-section body validation (Phase 3F) for free. <!-- Changed: forge#2086 — route through /issue create-hook -->
174
211
 
@@ -213,7 +250,7 @@ BODY_EOF
213
250
  ```
214
251
 
215
252
  ```
216
- ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"{fix|feat|refactor}: {issue_title}\" --body-file \"${MILESTONE_ISSUE_BODY_FILE}\" --label \"{type}\" --label \"{priority}\" --milestone \"{TITLE}\""))
253
+ ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"{fix|feat|refactor}: {issue_title}\" --body-file \"${MILESTONE_ISSUE_BODY_FILE}\" --label \"{type}\" --label \"{priority}\" --milestone \"{TITLE}\"${GREENFIELD_ARGS}"))
217
254
  ```
218
255
 
219
256
  ```bash
@@ -381,6 +418,18 @@ Show detailed progress with per-issue status.
381
418
 
382
419
  ---
383
420
 
421
+ ## Action: Milestone Drift (drift <slug>)
422
+
423
+ Print verbatim (forge#4161); no LLM, no issue filing.
424
+
425
+ ```bash
426
+ git fetch origin "{STAGING_BRANCH}" "milestone/{slug}"
427
+ type forge_resolve >/dev/null 2>&1 || { setopt nonomatch 2>/dev/null || true; FORGE_SOURCED_ROOT='${CLAUDE_PLUGIN_ROOT}'; for _r in "$(cygpath -u "${FORGEDOCK_HOME:-}" 2>/dev/null || printf %s "${FORGEDOCK_HOME:-}")" '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGE_HOME:-}" "$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null | sed 's|/commands/work-on\.md$||')" "$(head -n 1 "${CODEX_HOME:-$HOME/.codex}/forge-home" 2>/dev/null)" "$HOME/.claude/plugins/marketplaces/forgedock" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/plugins/cache/forgedock/forgedock/* "$HOME/.claude"/plugins/cache/forgedock/forgedock/*; do case "$_r" in /*) [ -f "$_r/scripts/lib/forge-resolve.sh" ] && { . "$_r/scripts/lib/forge-resolve.sh"; break; };; esac; done; type forge_resolve >/dev/null 2>&1 || forge_resolve() { :; }; }
428
+ bash "$(forge_resolve milestone-drift.sh)" "{slug}" --format text
429
+ ```
430
+
431
+ ---
432
+
384
433
  ## Action: Ship Milestone
385
434
 
386
435
  **This is the human-gated deployment step.** When the user says "ship {slug}", they've decided this feature set is ready for production.
@@ -81,6 +81,11 @@ issue right away (priority label first, then the smaller size). Never hold a rea
81
81
  lanes finish. To reduce simultaneous base-syncs, don't start two lanes that name the same function within the
82
82
  same minute; start another ready issue first instead of waiting.
83
83
 
84
+ **Maintenance budget** (off unless forge.yaml `maintenance.enabled: true`). The runner pulls open issues carrying a
85
+ `maintenance.labels` label into the batch and admits in this order: P0/P1 features > maintenance, while maintenance
86
+ starts stay within `maintenance.share` of all starts (0.2 = 1 lane in 5) > P2+ features. With `when_idle` it drains the
87
+ remaining maintenance once no feature is pending; otherwise the rest ends `held (maintenance share)` for a later batch.
88
+
84
89
  - **Batch runner (default, when `forgedock` is on PATH).** Launch the whole set as one durable batch in a single
85
90
  `Bash` call with `run_in_background: true`. Do NOT write a scheduler, wrapper or queue script, and do not launch one
86
91
  `run-issue` per issue yourself: the runner schedules from the issues' `Depends on` / `Blocked by` lines.
@@ -93,9 +98,12 @@ same minute; start another ready issue first instead of waiting.
93
98
  own session, so the batch survives this session: if the runner dies, run `forgedock orchestrate --resume <id>`
94
99
  (live lanes are re-adopted, nothing is launched twice). Change K of a running batch with
95
100
  `forgedock orchestrate --set-parallel <id> K`. The runner requeues `deferred` lanes by code (host busy after its
96
- retry-in, a usage limit holds all admissions until the reset, transient merge/push/idle failures back off; non-host
101
+ retry-in, a usage limit holds admissions for that account (backend identity) until the reset, across every batch on
102
+ the host; `forgedock limits show|clear <id>|--all` lists or drops holds; transient merge/push/idle failures back off; non-host
97
103
  deferrals stop after `orchestration.max_deferrals`), relaunches an `engine-error` once, and stops admitting after
98
- `orchestration.breaker` engine-errors (`errors` within `window_min`; held issues continue on `--resume`). Logical
104
+ `orchestration.breaker` engine-errors (`errors` within `window_min`; held issues continue on `--resume`). Lanes are
105
+ capped host-wide (`orchestration.global_parallel`, `forgedock sched cap N|show`), highest priority first; within a
106
+ priority, slots are shared across repos by weight (`forgedock sched weight <owner/repo> N`, default equal). Logical
99
107
  edges you found in step 2.4 must be written to the dependent issue as a `Depends on #X` line before launching. The
100
108
  last lines are the report table.
101
109
  - **Agent lane (only when the `forgedock` CLI is unavailable).** Spawn one background agent per issue.
@@ -294,7 +294,7 @@ Substitute each printed value into later fences as `{NAME}` (`{CONTEXT_FILE}`, `
294
294
  The 2D env-var, 2E secrets and 2F SQL scans ran in Phase 1 (`PHASE2_FINDINGS`): **a secrets hit is CRITICAL and BLOCKING**; an env var missing from `.env.example` and a migration hazard (`FOR UPDATE` with an aggregate, `DROP` without `IF EXISTS`, `NOT NULL` without `DEFAULT`) are findings. Execution (format, lint, typecheck, build, tests) stays here: it is long-running and needs a throwaway checkout (HARD RULE 7). Commands come from the main checkout's `forge.yaml → verification.commands.*` and run for the changed languages only; a missing command prints `SKIPPED`, never a silent pass, and exit codes are printed so the verdict is not inferred from truncated output.
295
295
 
296
296
  ```bash
297
- PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"; REQUIRES_FULL_BUILD="{REQUIRES_FULL_BUILD}"
297
+ PR_NUMBER="{PR_NUMBER}"; REPO="{GH_REPO}"; REVIEW_SHA="{REVIEW_SHA}"; REQUIRES_FULL_BUILD="{REQUIRES_FULL_BUILD}"; CTX="{CONTEXT_FILE}"
298
298
  type forge_resolve >/dev/null 2>&1 || { setopt nonomatch 2>/dev/null || true; FORGE_SOURCED_ROOT='${CLAUDE_PLUGIN_ROOT}'; for _r in "$(cygpath -u "${FORGEDOCK_HOME:-}" 2>/dev/null || printf %s "${FORGEDOCK_HOME:-}")" '${CLAUDE_PLUGIN_ROOT}' "${FORGE_ROOT:-}" "${FORGE_HOME:-}" "$(readlink -f "$HOME/.claude/commands/work-on.md" 2>/dev/null | sed 's|/commands/work-on\.md$||')" "$(head -n 1 "${CODEX_HOME:-$HOME/.codex}/forge-home" 2>/dev/null)" "$HOME/.claude/plugins/marketplaces/forgedock" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/plugins/cache/forgedock/forgedock/* "$HOME/.claude"/plugins/cache/forgedock/forgedock/*; do case "$_r" in /*) [ -f "$_r/scripts/lib/forge-resolve.sh" ] && { . "$_r/scripts/lib/forge-resolve.sh"; break; };; esac; done; type forge_resolve >/dev/null 2>&1 || forge_resolve() { :; }; }
299
299
  MAIN_ROOT="$(cd "$(git rev-parse --path-format=absolute --git-common-dir)/.." && pwd)" # main checkout even when run from a linked worktree
300
300
  REVIEW_WT="${TMPDIR:-/tmp}/forge-review-wt-${PR_NUMBER}-$$"
@@ -304,7 +304,7 @@ git worktree add --detach "$REVIEW_WT" "$REVIEW_SHA" 2>/dev/null
304
304
  { cd "$REVIEW_WT" && [ "$(git rev-parse HEAD)" = "$(git -C "$MAIN_ROOT" rev-parse "$REVIEW_SHA")" ]; } || { echo "SKIPPED — could not create per-PR worktree at $REVIEW_SHA"; exit 0; }
305
305
  CFG="${FORGE_CONFIG:-$MAIN_ROOT/forge.yaml}"
306
306
  vcmd() { yq ".verification.commands.${1}.${2} // \"\"" "$CFG" 2>/dev/null || echo ''; }
307
- CHANGED=$(gh pr diff "$PR_NUMBER" -R "$REPO" --name-only)
307
+ CHANGED=$(jq -r '.full_files[]?' "$CTX" 2>/dev/null); [ -n "$CHANGED" ] || CHANGED=$(gh pr diff "$PR_NUMBER" -R "$REPO" --name-only) # Phase 1 list survives an API 406 (forge#4154)
308
308
  [ -d "$MAIN_ROOT/node_modules" ] && [ ! -e node_modules ] && ln -s "$MAIN_ROOT/node_modules" node_modules
309
309
  run_step() { # run_step <lang> <step> <tail-lines>: prints the exit code; empty command = SKIPPED
310
310
  local c; c=$(vcmd "${1}" "${2}")
@@ -257,7 +257,7 @@ Major upgrades detected (not auto-processed — require human review):
257
257
 
258
258
  ## Phase 3: Issue Creation
259
259
 
260
- For each eligible package (or batch), create a GitHub issue:
260
+ For each eligible package (or batch), create a GitHub issue. Each carries the `maintenance` label, so `forgedock orchestrate` dispatches it within the maintenance budget (forge.yaml `maintenance.share`, #4197):
261
261
 
262
262
  ### 3A: Per-package issue (patch or minor, non-batched)
263
263
 
@@ -269,7 +269,7 @@ if [ "$DRY_RUN" = "true" ]; then
269
269
  else
270
270
  gh issue create $GH_FLAG \
271
271
  --title "feat(deps): upgrade {PACKAGE} from {CURRENT} to {LATEST} ({SEMVER_LEVEL})" \
272
- --label "enhancement" \
272
+ --label "enhancement" --label "maintenance" \
273
273
  --body "$(cat <<'ISSUE_EOF'
274
274
  ## Problem
275
275
 
@@ -327,7 +327,7 @@ if [ "$DRY_RUN" = "true" ]; then
327
327
  else
328
328
  gh issue create $GH_FLAG \
329
329
  --title "feat(deps): batch patch upgrades — {COUNT} packages ({DATE})" \
330
- --label "enhancement" \
330
+ --label "enhancement" --label "maintenance" \
331
331
  --body "$(cat <<'BATCH_ISSUE_EOF'
332
332
  ## Problem
333
333
 
@@ -357,8 +357,12 @@ else
357
357
  fi
358
358
  [ "$TEST_FAILED" -eq 1 ] && LEARNED_FAILED=1
359
359
  echo "TEST_FAILED=${TEST_FAILED}"
360
+ # #4183: reclaim owned, expired test containers/volumes and scratch (never unowned ones); non-fatal.
361
+ npx forgedock gc --apply --docker --tmp >/dev/null 2>&1 || true
360
362
  ```
361
363
 
364
+ Test services you start yourself (`docker run`, compose) carry the `forgedock.*` labels (#4181), use `--rm`, and are removed in a `trap` on exit.
365
+
362
366
  The configured `verification.commands.<lang>.test` always runs here, whether or not V4.8's `ci-local.sh` resolves (forge#4040): the local CI gate never runs that command, so a failing configured suite must fail V2 on its own. Only a wall-clock timeout (rc 124) is DEGRADED. Test command failures (`TEST_FAILED=1`, also reported as `LEARNED_FAILED=1`) are BLOCKING (same as `verification.commands` failures): fix and re-run. A command matched and skipped by the known-slow gate is never executed and never counted as a failure, but it is DEGRADED, not verified. Each `DEGRADED_CHECKS+=` line printed here is a carry-forward line: record it in your notes, V5 Step 1 and Step 4 rebuild `DEGRADED_CHECKS` from them (a fresh shell does not keep the variable).
363
367
 
364
368
  ---
@@ -1830,6 +1830,8 @@ if [ -n "{WORKTREE_PATH}" ] && [ -d "{WORKTREE_PATH}" ]; then
1830
1830
  else
1831
1831
  echo "Worktree cleanup skipped: path not provided or does not exist"
1832
1832
  fi
1833
+ # #4183: reap ForgeDock-owned, stale leftovers (labelled docker, tmp scratch, orphans); core lanes tear down from the ledger.
1834
+ npx forgedock gc --apply --docker --tmp --processes >/dev/null 2>&1 || true
1833
1835
  ```
1834
1836
 
1835
1837
  Set `{CLEANUP_STATUS}` based on outcome:
@@ -135,11 +135,24 @@ Re-read current state before doing anything:
135
135
  ```bash
136
136
  gh issue view {NUMBER} {GH_FLAG} --json number,title,body,labels,state,milestone
137
137
 
138
- # Read investigation report (required — contains decomposition plan)
138
+ # Read investigation report (contains the decomposition plan; see the /bootstrap plan below when absent)
139
139
  gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
140
140
  --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR")) | .body'
141
141
  ```
142
142
 
143
+ **Bootstrap plan** <!-- Added: forge#4175 -->: a greenfield spike issue filed by `/bootstrap` has no code to investigate, so it carries its plan in a `<!-- FORGE:BOOTSTRAP_PLAN -->` comment instead: a `### Decomposition Assessment` listing the sub-issues (titles, dependencies, `ESTIMATED_DIFF_LINES`) and an optional own-line `Spike: <path>` naming the spike doc. Only a trusted, marker-anchored comment counts (`scripts/trusted-comments.sh`, the same predicate as the resume check in Phase D1). Paste the Script resolution block first (fresh shell per Bash call). An unresolved helper or a failed read means "no bootstrap plan" (fail closed); an untrusted body never becomes a plan.
144
+
145
+ ```bash
146
+ TRUSTED_SCRIPT=$(forge_resolve trusted-comments.sh) # unresolved => empty => fail closed
147
+ BOOTSTRAP_PLAN=""
148
+ if [ -n "$TRUSTED_SCRIPT" ] && COMMENTS_JSON=$(gh api --paginate repos/{GH_REPO}/issues/{NUMBER}/comments); then
149
+ BOOTSTRAP_PLAN=$(printf '%s' "$COMMENTS_JSON" | bash "$TRUSTED_SCRIPT" bodies '^<!-- FORGE:BOOTSTRAP_PLAN -->' | tail -n 1 | jq -r '.' 2>/dev/null || true)
150
+ fi
151
+ PLAN_SPIKE=$(printf '%s\n' "$BOOTSTRAP_PLAN" | sed -nE 's/^Spike: *`?([^` ]+)`? *$/\1/p' | head -1)
152
+ ```
153
+
154
+ **Plan source**: when a FORGE:INVESTIGATOR report exists it is the plan source (`PLAN_SOURCE=investigate`) and the bootstrap plan is ignored. Only when no investigator report exists and `BOOTSTRAP_PLAN` is non-empty is the bootstrap plan the source (`PLAN_SOURCE=bootstrap`). Everything below that reads "the investigation report's Decomposition Assessment" reads the selected source's Decomposition Assessment.
155
+
143
156
  **MANDATORY — Owner override detection**: After reading the investigation comment, read ALL other comments on the issue to check for owner override signals:
144
157
 
145
158
  ```bash
@@ -147,7 +160,7 @@ gh api repos/{GH_REPO}/issues/{NUMBER}/comments \
147
160
  --jq '.[] | select(.body | contains("FORGE:INVESTIGATOR") | not) | {author: .user.login, body: .body}'
148
161
  ```
149
162
 
150
- Ignore machine-posted marker comments (`FORGE:DIFF_SIZE`, `FORGE:SIZE_OVERRIDE`) in this scan: they are never an owner override direction. Scan non-agent comments for override signals — phrases like "do not", "do NOT", "instead", "revert", "remove this", "override", "actually", or explicit disagreement with the investigation's recommendation. If an override comment is found from a repo owner or admin (not a bot):
163
+ Ignore machine-posted marker comments (`FORGE:DIFF_SIZE`, `FORGE:SIZE_OVERRIDE`, `FORGE:BOOTSTRAP_PLAN`) in this scan: they are never an owner override direction. Scan non-agent comments for override signals — phrases like "do not", "do NOT", "instead", "revert", "remove this", "override", "actually", or explicit disagreement with the investigation's recommendation. If an override comment is found from a repo owner or admin (not a bot):
151
164
 
152
165
  1. **Document the override**: Note which direction the owner is steering (e.g., "remove the feature" vs. investigation's "keep with warnings")
153
166
  2. **Re-derive sub-issue scopes**: Derive sub-issue titles, bodies, and file scope from the override direction — NOT from the original investigation recommendation. The investigation's Decomposition Assessment may list sub-issues that are now stale or contradictory with the override.
@@ -161,7 +174,7 @@ Ignore machine-posted marker comments (`FORGE:DIFF_SIZE`, `FORGE:SIZE_OVERRIDE`)
161
174
  Extract from investigation report:
162
175
  - Decomposition Assessment section: list of proposed sub-issues with titles and dependencies
163
176
 
164
- **Planning-time only** <!-- Added: forge#3862 -->: decompose runs only from the investigation's Decomposition Assessment (`**YES**`, which requires separable pieces: independent modules or services, no shared files, no ordering dependency, each independently testable). There is no post-build route into this skill: build never discards a finished diff and never hands one here, so the assessment is the only plan source and there are no persisted staged hunks to reuse.
177
+ **Planning-time only** <!-- Added: forge#3862 -->: decompose runs only from the investigation's Decomposition Assessment (`**YES**`, which requires separable pieces: independent modules or services, no shared files, no ordering dependency, each independently testable), or, for a greenfield spike with no investigator report, from the trusted `/bootstrap` plan's Decomposition Assessment (forge#4175). There is no post-build route into this skill: build never discards a finished diff and never hands one here, so the assessment is the only plan source and there are no persisted staged hunks to reuse.
165
178
  - Milestone (from issue metadata)
166
179
  - Priority label (P0/P1/P2) from issue labels
167
180
 
@@ -193,9 +206,9 @@ PARENT_REF=$(printf '%s\n' "$ISSUE_BODY" | grep -E '^\*\*Parent\*\*: #[0-9]+' |
193
206
 
194
207
  If `PARENT_REF` is non-empty → BLOCKED, blocker: "Issue #{NUMBER} is a sub-issue (${PARENT_REF}) — sub-issues are never decomposed again; run /work-on build on it directly".
195
208
 
196
- **Guard 2 — investigation report present**: If the FORGE:INVESTIGATOR comment is absent → BLOCKED, blocker: "No investigation report found — run investigate first".
209
+ **Guard 2 — investigation report present**: If the FORGE:INVESTIGATOR comment is absent and there is no trusted `/bootstrap` plan (`BOOTSTRAP_PLAN` empty, Phase D0) → BLOCKED, blocker: "No investigation report found — run investigate first". A trusted bootstrap plan satisfies this guard only for an issue with no investigator report. <!-- Changed: forge#4175 -->
197
210
 
198
- **Guard 3 — decomposition plan present**: If the investigation report has no Decomposition Assessment section, OR the assessment does not list any sub-issues → BLOCKED, blocker: "Investigation report has no decomposition plan — re-run investigate with explicit decomposition scope".
211
+ **Guard 3 — decomposition plan present**: If the investigation report has no Decomposition Assessment section, OR the assessment does not list any sub-issues → BLOCKED, blocker: "Investigation report has no decomposition plan — re-run investigate with explicit decomposition scope". When `PLAN_SOURCE=bootstrap` the same check applies to the bootstrap plan's Decomposition Assessment; on failure the blocker is "Bootstrap plan has no decomposition plan — re-run /bootstrap for this spike".
199
212
 
200
213
  **Resume check** <!-- Added: forge#3881 -->: the marker comment drives parent edits and the D6.5 close, so only a trusted, marker-anchored comment counts (`scripts/trusted-comments.sh`, the same predicate as the review gates). Paste the Script resolution block first (fresh shell per Bash call). An unresolved helper or a failed read is treated as "no trusted comment" and the normal path continues (the D2.5 equivalent-set check still guards duplicates); an untrusted body never reaches `SUB_LIST`.
201
214
 
@@ -373,12 +386,15 @@ SUB_BODY_EOF
373
386
  ```bash
374
387
  MILESTONE_ARG=""
375
388
  [ -n "$MILESTONE_TITLE" ] && MILESTONE_ARG=" --milestone \"${MILESTONE_TITLE}\""
389
+ # Greenfield (forge#4175): a bootstrap plan naming a spike doc lets sub-issues list planned Affected Files.
390
+ SPIKE_ARG=""
391
+ [ "$PLAN_SOURCE" = "bootstrap" ] && [ -n "$PLAN_SPIKE" ] && SPIKE_ARG=" --greenfield --spike \"${PLAN_SPIKE}\""
376
392
  ```
377
393
 
378
394
  **Invoke `/issue` in programmatic mode**:
379
395
 
380
396
  ```
381
- ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"${SUB_TITLE}\" --body-file \"${SUB_BODY_FILE}\" --label \"{PRIORITY_LABEL}\"${MILESTONE_ARG}"))
397
+ ISSUE_SKILL_OUTPUT=$(Skill(skill="{FORGE_SKILL_PREFIX}issue", args="--title \"${SUB_TITLE}\" --body-file \"${SUB_BODY_FILE}\" --label \"{PRIORITY_LABEL}\"${MILESTONE_ARG}${SPIKE_ARG}"))
382
398
  ```
383
399
 
384
400
  If the skill is not found → BLOCKED, blocker: "skill not found: issue".
@@ -488,6 +504,8 @@ COMMENT_URL=$(gh issue comment {NUMBER} {GH_FLAG} --body "<!-- FORGE:DECOMPOSED
488
504
 
489
505
  This issue has been broken into sub-issues. Each sub-issue runs through its own /work-on pipeline independently.
490
506
 
507
+ **Plan source**: ${PLAN_SOURCE}
508
+
491
509
  ### Sub-Issues Created
492
510
 
493
511
  {for each sub-issue, in dependency order:}
package/docs/CI.md CHANGED
@@ -137,10 +137,10 @@ Separately, `npx forgedock run <command>` (the standalone, non-Claude-Code comma
137
137
  | Backend | When used | Credential needed |
138
138
  |---------|-----------|--------------------|
139
139
  | `cli` (via `--backend cli` or `FORGEDOCK_BACKEND=cli`) | Explicit, or auto-selected when a working `claude` CLI is detected on PATH | None — reuses the CLI's own authentication (Pro/Max OAuth or a CLI-managed key) |
140
- | `api` (via `--backend api` or `FORGEDOCK_BACKEND=api`) | Explicit, or auto-selected fallback when `claude` is not detected | `ANTHROPIC_API_KEY` |
141
- | `auto` (default) | No `--backend` / `FORGEDOCK_BACKEND` given | Prefers `cli`, falls back to `api` |
140
+ | `api` (via `--backend api` or `FORGEDOCK_BACKEND=api`) | Explicit only, or the `auto` fallback when the owner opted in (forge.yaml `execution.agent_backend: api` plus `FORGEDOCK_ALLOW_API_BACKEND=1`) | `ANTHROPIC_API_KEY` |
141
+ | `auto` (default) | No `--backend` / `FORGEDOCK_BACKEND` given | Uses `cli`; never falls back to `api` without the owner opt-in. With no CLI it fails with `CLI_BACKEND_UNAVAILABLE` |
142
142
 
143
- **When CI still needs `ANTHROPIC_API_KEY` for `forgedock run`**: CI runners generally do not have an interactively-authenticated `claude` CLI (no browser/OAuth flow available in a headless job), so `auto` detection will typically fall back to `api` there anyway. If your CI runner does install and log in the `claude` CLI (e.g. via a headless API-key-based login), you can force `--backend cli` to skip the SDK/API path entirely. Otherwise, keep `ANTHROPIC_API_KEY` configured as a repository secret (see Setup above) and either let `auto` fall back naturally or set `--backend api` / `FORGEDOCK_BACKEND=api` explicitly for a deterministic CI backend regardless of what happens to be on the runner's PATH.
143
+ **When CI still needs `ANTHROPIC_API_KEY` for `forgedock run`**: CI runners generally do not have an interactively-authenticated `claude` CLI (no browser/OAuth flow available in a headless job), so `auto` there fails with `CLI_BACKEND_UNAVAILABLE`: the API backend is never the default, because it bills an API key (#4257). If your CI runner does install and log in the `claude` CLI (e.g. via a headless API-key-based login), you can force `--backend cli` to skip the SDK/API path entirely. Otherwise, keep `ANTHROPIC_API_KEY` configured as a repository secret (see Setup above) and set `--backend api` / `FORGEDOCK_BACKEND=api` explicitly, or opt in to the `auto` fallback with forge.yaml `execution.agent_backend: api` plus `FORGEDOCK_ALLOW_API_BACKEND=1`.
144
144
 
145
145
  `npx forgedock run <command> --dry-run` always reports which backend it resolved to, without making any network call or requiring a key either way.
146
146