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.
- package/README.md +4 -1
- package/bin/bootstrap.mjs +55 -0
- package/bin/engine/outcome-codes.mjs +13 -0
- package/bin/engine/phases.mjs +38 -2
- package/bin/engine/telemetry-seam.mjs +41 -6
- package/bin/engine/telemetry-seam.test.mjs +35 -0
- package/bin/engine-cli.mjs +616 -54
- package/bin/engine.mjs +13 -5
- package/bin/forgedock.mjs +169 -39
- package/bin/gc.mjs +580 -0
- package/bin/init-detect.mjs +68 -3
- package/bin/init-enrich.mjs +3 -0
- package/bin/init-repo.mjs +213 -0
- package/bin/journey.mjs +278 -43
- package/bin/lean/admission.mjs +33 -3
- package/bin/lean/batch.mjs +24 -5
- package/bin/lean/batch.test.mjs +54 -4
- package/bin/lean/checks.mjs +199 -0
- package/bin/lean/checks.test.mjs +183 -0
- package/bin/lean/conformance.mjs +235 -37
- package/bin/lean/conformance.test.mjs +143 -5
- package/bin/lean/credentials.mjs +29 -0
- package/bin/lean/depcache.mjs +312 -0
- package/bin/lean/depcache.test.mjs +298 -0
- package/bin/lean/drift.mjs +198 -0
- package/bin/lean/drift.test.mjs +223 -0
- package/bin/lean/effects.mjs +178 -16
- package/bin/lean/execution.e2e.test.mjs +81 -0
- package/bin/lean/execution.mjs +431 -0
- package/bin/lean/execution.test.mjs +583 -0
- package/bin/lean/heavy.mjs +289 -0
- package/bin/lean/heavy.test.mjs +207 -0
- package/bin/lean/lean.mjs +877 -151
- package/bin/lean/lean.test.mjs +1798 -28
- package/bin/lean/ledger.mjs +92 -0
- package/bin/lean/ledger.test.mjs +100 -0
- package/bin/lean/limits.mjs +58 -0
- package/bin/lean/limits.test.mjs +150 -0
- package/bin/lean/orchestrate.mjs +321 -19
- package/bin/lean/prompts.mjs +125 -17
- package/bin/lean/refactor.mjs +276 -0
- package/bin/lean/refactor.test.mjs +359 -0
- package/bin/lean/render.mjs +25 -6
- package/bin/lean/review-trigger.mjs +134 -0
- package/bin/lean/review-trigger.test.mjs +104 -0
- package/bin/lean/sandbox-provider.contract.test.mjs +257 -0
- package/bin/lean/sandbox-remote.mjs +142 -0
- package/bin/lean/sched.mjs +221 -0
- package/bin/lean/sched.test.mjs +310 -0
- package/bin/lean/schedd.mjs +247 -0
- package/bin/lean/schedd.test.mjs +180 -0
- package/bin/lean/toolchain.mjs +228 -0
- package/bin/lean/toolchain.test.mjs +154 -0
- package/bin/lean/verify.mjs +124 -0
- package/bin/lean/verify.test.mjs +118 -0
- package/bin/lean/worktree.mjs +69 -0
- package/bin/lean/worktree.test.mjs +141 -0
- package/bin/runner.mjs +53 -10
- package/bin/stats/github-source.mjs +1 -1
- package/bin/stats/index.mjs +14 -2
- package/bin/stats/query.mjs +72 -0
- package/bin/stats/stats.test.mjs +54 -0
- package/commands/adopt.md +3 -0
- package/commands/audit.md +5 -8
- package/commands/autopilot.md +2 -0
- package/commands/bootstrap.md +35 -0
- package/commands/cleanup.md +7 -22
- package/commands/forge-stats.md +2 -1
- package/commands/forgedock-init.md +125 -6
- package/commands/issue.md +89 -830
- package/commands/milestone.md +55 -6
- package/commands/orchestrate.md +10 -2
- package/commands/review-pr.md +2 -2
- package/commands/upgrade-deps.md +3 -3
- package/commands/work-on/build/validate.md +4 -0
- package/commands/work-on/close.md +2 -0
- package/commands/work-on/decompose.md +24 -6
- package/docs/CI.md +3 -3
- package/docs/CONFIG.md +227 -3
- package/docs/OUTCOME-CODES.md +11 -0
- package/docs/TELEMETRY.md +1 -1
- package/docs/site/command-reference.md +22 -0
- package/docs/site/getting-started.md +2 -0
- package/docs/site/start-a-new-project.md +327 -0
- package/package.json +1 -1
- package/scripts/affected-files-check.sh +91 -0
- package/scripts/affected-files-check.test.sh +80 -0
- package/scripts/bootstrap-create.mjs +393 -0
- package/scripts/bootstrap-validate.mjs +321 -0
- package/scripts/calibration.mjs +116 -0
- package/scripts/check-devdocs-freshness.sh +125 -0
- package/scripts/check-devdocs-freshness.test.sh +130 -0
- package/scripts/check-spec-markers.sh +3 -0
- package/scripts/check-spec-size.sh +29 -11
- package/scripts/check-spec-size.test.sh +27 -4
- package/scripts/ci-local/lib.mjs +8 -1
- package/scripts/ci-local/parallelism.test.mjs +27 -0
- package/scripts/ci-local/run.mjs +16 -5
- package/scripts/ci-local/scoping.mjs +82 -0
- package/scripts/ci-local.test.sh +14 -2
- package/scripts/fixtures/symbol-index/other/sample.rb +3 -0
- package/scripts/fixtures/symbol-index/py/pkg/__init__.py +1 -0
- package/scripts/fixtures/symbol-index/py/pkg/a.py +5 -0
- package/scripts/fixtures/symbol-index/py/pkg/b.py +14 -0
- package/scripts/fixtures/symbol-index/py/pkg/c.py +1 -0
- package/scripts/fixtures/symbol-index/py/sample.py +41 -0
- package/scripts/fixtures/symbol-index/ts/sample.ts +13 -0
- package/scripts/fixtures/symbol-move/gen_big.py +201 -0
- package/scripts/fixtures/symbol-move-ts/gen_big.mjs +56 -0
- package/scripts/forge-root.test.sh +1 -1
- package/scripts/hotspots.sh +100 -0
- package/scripts/hotspots.test.sh +101 -0
- package/scripts/issue-create.sh +197 -0
- package/scripts/issue-create.test.sh +166 -0
- package/scripts/maintenance-scan.sh +185 -0
- package/scripts/maintenance-scan.test.sh +89 -0
- package/scripts/milestone-drift.sh +206 -0
- package/scripts/milestone-drift.test.sh +120 -0
- package/scripts/northstar-docs.test.sh +68 -0
- package/scripts/refactor-verify.sh +368 -0
- package/scripts/refactor-verify.test.sh +202 -0
- package/scripts/review-prepare.sh +11 -9
- package/scripts/review-prepare.test.sh +14 -0
- package/scripts/structure-delta.sh +155 -0
- package/scripts/structure-delta.test.sh +136 -0
- package/scripts/symbol-index.mjs +253 -0
- package/scripts/symbol-index.py +354 -0
- package/scripts/symbol-index.test.sh +119 -0
- package/scripts/symbol-move-ts.test.sh +161 -0
- package/scripts/symbol-move.mjs +379 -0
- package/scripts/symbol-move.py +383 -0
- package/scripts/symbol-move.test.sh +168 -0
- package/templates/bootstrap/interview.md +34 -0
- package/templates/bootstrap/issue-body.md +42 -0
- package/templates/bootstrap/needs-human-issue.md +45 -0
- package/templates/bootstrap/stack-profiles.md +34 -0
- package/templates/bootstrap/technical-spike.md +75 -0
package/commands/milestone.md
CHANGED
|
@@ -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.
|
|
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
|
-
**
|
|
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
|
|
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
|
|
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.
|
package/commands/orchestrate.md
CHANGED
|
@@ -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
|
|
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`).
|
|
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.
|
package/commands/review-pr.md
CHANGED
|
@@ -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}")
|
package/commands/upgrade-deps.md
CHANGED
|
@@ -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 (
|
|
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
|
|
141
|
-
| `auto` (default) | No `--backend` / `FORGEDOCK_BACKEND` given |
|
|
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`
|
|
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
|
|