forgedock 1.26.0 → 1.27.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 (42) hide show
  1. package/bin/engine/outcome-codes.mjs +3 -0
  2. package/bin/engine/phases.mjs +38 -2
  3. package/bin/engine/telemetry-seam.mjs +6 -0
  4. package/bin/engine/telemetry-seam.test.mjs +4 -0
  5. package/bin/engine-cli.mjs +78 -20
  6. package/bin/engine.mjs +5 -1
  7. package/bin/forgedock.mjs +94 -30
  8. package/bin/init-detect.mjs +68 -3
  9. package/bin/init-enrich.mjs +3 -0
  10. package/bin/init-repo.mjs +213 -0
  11. package/bin/journey.mjs +278 -43
  12. package/bin/lean/batch.mjs +2 -1
  13. package/bin/lean/batch.test.mjs +49 -1
  14. package/bin/lean/conformance.mjs +76 -10
  15. package/bin/lean/conformance.test.mjs +32 -3
  16. package/bin/lean/effects.mjs +96 -11
  17. package/bin/lean/lean.mjs +326 -33
  18. package/bin/lean/lean.test.mjs +618 -10
  19. package/bin/lean/ledger.mjs +67 -0
  20. package/bin/lean/ledger.test.mjs +65 -0
  21. package/bin/lean/orchestrate.mjs +9 -0
  22. package/bin/lean/prompts.mjs +42 -8
  23. package/bin/lean/render.mjs +1 -1
  24. package/bin/lean/verify.mjs +113 -0
  25. package/bin/lean/verify.test.mjs +95 -0
  26. package/commands/adopt.md +3 -0
  27. package/commands/audit.md +3 -8
  28. package/commands/forgedock-init.md +118 -6
  29. package/commands/review-pr.md +2 -2
  30. package/docs/CONFIG.md +52 -3
  31. package/docs/OUTCOME-CODES.md +3 -0
  32. package/package.json +1 -1
  33. package/scripts/bootstrap-validate.mjs +287 -0
  34. package/scripts/check-devdocs-freshness.sh +120 -0
  35. package/scripts/check-devdocs-freshness.test.sh +120 -0
  36. package/scripts/ci-local/run.mjs +13 -2
  37. package/scripts/ci-local.test.sh +13 -1
  38. package/scripts/northstar-docs.test.sh +68 -0
  39. package/scripts/review-prepare.sh +11 -9
  40. package/scripts/review-prepare.test.sh +14 -0
  41. package/scripts/structure-delta.sh +150 -0
  42. package/scripts/structure-delta.test.sh +135 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: AI-powered forge.yaml config generator — scans codebase, queries GitHub, auto-fills all optional sections from detection
3
- argument-hint: "[--preserve | --interactive | --section <name>]"
3
+ argument-hint: "[--preserve | --interactive | --section <name> | --domain-labels <a,b,c>]"
4
4
  ---
5
5
  <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
6
6
  <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
@@ -23,13 +23,15 @@ You complete the `forge.yaml` configuration that `npx forgedock init` started. T
23
23
  | (none) | Fill all optional sections — overwrite existing values with newly detected ones, skip sections with nothing detected |
24
24
  | `--preserve` | Keep existing active section values unchanged — only fill sections that are not yet configured |
25
25
  | `--interactive` | Ask confirmation for every detected value and present menus for optional features |
26
- | `--section <name>` | Fill only one section: `repos`, `project_board`, `services`, `review`, or `verification` |
26
+ | `--section <name>` | Fill only one section: `repos`, `project_board`, `services`, `review`, `verification`, or `labels` |
27
+ | `--domain-labels <a,b,c>` | Domain labels to bootstrap on the repo (e.g. `frontend,backend,infra,design,legal-gated`) in addition to `bin/labels.json`. Overrides the list Phase 5E derives from the spec. |
27
28
 
28
29
  Parse `$ARGUMENTS` and set:
29
30
  ```
30
31
  PRESERVE = true if --preserve present
31
32
  INTERACTIVE = true if --interactive present (restores full questionnaire behavior for all sections)
32
33
  TARGET_SECTION = value from --section <name>, or "all"
34
+ DOMAIN_LABELS = value from --domain-labels <a,b,c>, or "" (derived in Phase 5E)
33
35
  ```
34
36
 
35
37
  If both `--preserve` and `--interactive` are present, `--interactive` takes precedence (`PRESERVE = false`).
@@ -37,7 +39,7 @@ If both `--preserve` and `--interactive` are present, `--interactive` takes prec
37
39
  If `--section` was provided, validate the value immediately:
38
40
 
39
41
  ```bash
40
- ALLOWED_SECTIONS="repos project_board services review verification"
42
+ ALLOWED_SECTIONS="repos project_board services review verification labels"
41
43
  if [ "$TARGET_SECTION" != "all" ]; then
42
44
  VALID=false
43
45
  for s in $ALLOWED_SECTIONS; do
@@ -46,7 +48,7 @@ if [ "$TARGET_SECTION" != "all" ]; then
46
48
  if [ "$VALID" = "false" ]; then
47
49
  echo "Error: '$TARGET_SECTION' is not a valid section name."
48
50
  echo ""
49
- echo "Valid sections: repos, project_board, services, review, verification"
51
+ echo "Valid sections: repos, project_board, services, review, verification, labels"
50
52
  echo ""
51
53
  echo "Usage: /forgedock-init --section <name>"
52
54
  exit 1
@@ -248,6 +250,17 @@ Build `DETECTED_ANALYTICS` list from findings:
248
250
  - `DETECTED_GA4=true` if GA4 references found
249
251
  - If none found, `DETECTED_ANALYTICS_EMPTY=true`
250
252
 
253
+ ### 2G: Boundary config check
254
+
255
+ Core work-on blocks a new package.json dependency between workspace packages that its contract does not name, and only reports import growth along existing ones (`scripts/structure-delta.sh`). An allow-all boundary rule makes that report the only signal, so suggest tightening it once.
256
+
257
+ ```bash
258
+ # eslint-plugin-boundaries, dependency-cruiser, Nx depConstraints (repo root only)
259
+ ls "$REPO_ROOT"/.eslintrc* "$REPO_ROOT"/eslint.config.* "$REPO_ROOT"/.dependency-cruiser.* "$REPO_ROOT"/nx.json 2>/dev/null | head -10
260
+ ```
261
+
262
+ Read each file found. Set `BOUNDARY_ALLOW_ALL=true` when a rule allows everything: an eslint-plugin-boundaries rule `{ from: [X], allow: ['*'] }` (or an `allow` list naming every element type), a dependency-cruiser config with no `forbidden` rules, or Nx `onlyDependOnLibsWithTags: ['*']`. Record the file and the rule as `BOUNDARY_ALLOW_ALL_AT`. Suggestion only: never edit the config. If no config is found, or the rules are scoped, leave it unset.
263
+
251
264
  ---
252
265
 
253
266
  ## Phase 3: GitHub Queries
@@ -262,6 +275,28 @@ gh auth status 2>&1
262
275
 
263
276
  If not authenticated, skip all GitHub queries and note which sections will need manual completion.
264
277
 
278
+ **Projects v2 scope check.** Board discovery and creation need the `read:project` (read) or `project` (read + write) OAuth scope, which `gh auth login` does not grant by default. Without it, `gh project list` fails — and that failure must NOT be reported as "zero boards":
279
+
280
+ ```bash
281
+ BOARD_SCOPE_MISSING=false
282
+ SCOPES=$(gh auth status 2>&1 | grep -i "Token scopes:" || true)
283
+ # Classic OAuth tokens list their scopes; a token with neither project scope cannot touch boards.
284
+ if [ -n "$SCOPES" ] && ! echo "$SCOPES" | grep -qE "'(read:)?project'"; then
285
+ BOARD_SCOPE_MISSING=true
286
+ fi
287
+ # Fine-grained / App tokens list no scopes — probe instead and match the missing-scope error.
288
+ if [ "$BOARD_SCOPE_MISSING" = "false" ] && gh project list --owner "$OWNER" --limit 1 2>&1 >/dev/null | grep -qiE "required scopes|read:project|INSUFFICIENT_SCOPES"; then
289
+ BOARD_SCOPE_MISSING=true
290
+ fi
291
+ if [ "$BOARD_SCOPE_MISSING" = "true" ]; then
292
+ echo "Your GitHub token lacks the read:project/project scope needed for Projects v2 boards."
293
+ echo "Grant it, then re-run /forgedock-init --section project_board:"
294
+ echo " gh auth refresh -h github.com -s read:project,project"
295
+ fi
296
+ ```
297
+
298
+ When `BOARD_SCOPE_MISSING=true`, skip Phases 3B, 3C, 4B and 5F; the Phase 5D summary shows `✗ project_board — token lacks read:project/project scope (run: gh auth refresh -h github.com -s read:project,project)`. Label bootstrap (Phase 5E) does not need this scope and still runs.
299
+
265
300
  ### 3B: Project board discovery
266
301
 
267
302
  ```bash
@@ -312,6 +347,7 @@ Store results in `SIBLING_REPOS`. Assess satellite signals for each repo — str
312
347
  - **Auto-detected value** → use it directly. No confirmation prompt.
313
348
  - **Genuine ambiguity** (e.g., multiple project boards, multiple candidates for the same field) → ask once with the detected candidates listed.
314
349
  - **Nothing detected** → skip the section silently. Do NOT offer a menu or ask "do you want X?".
350
+ Exception: `project_board` — with no board found, Phase 4B offers to create one (a new project has none to detect).
315
351
  - **`--interactive` flag** → override the above and ask confirmation/selection for every section, even when detection found a clear answer.
316
352
 
317
353
  ### 4A: repos section
@@ -346,8 +382,16 @@ Store results in `SIBLING_REPOS`. Assess satellite signals for each repo — str
346
382
 
347
383
  Which board should ForgeDock use? Enter number, or press Enter to skip:
348
384
  ```
349
- - Zero project boards found AND `INTERACTIVE=false` → skip this section silently.
350
- - `INTERACTIVE=true` → present all boards and ask which one to use.
385
+ - Zero project boards found → offer to create one (Phase 5F provisions it after `forge.yaml` is written):
386
+ ```
387
+ No Projects v2 board found under {OWNER}.
388
+ Create "{REPO} ForgeDock" with the ForgeDock fields (Status, Lane, Priority, Workflow, Component) and link it to {OWNER}/{REPO}? [Y/n]
389
+ ```
390
+ Set `BOARD_ACTION=create` on yes, else skip the section.
391
+ - Board(s) found but you are linking, not auto-using one (multiple boards, or `INTERACTIVE=true`) → the menu above also offers `[n] Create a new "{REPO} ForgeDock" board`. Picking an existing board sets `BOARD_ACTION=link`; picking new sets `BOARD_ACTION=create`. The single auto-used board is `BOARD_ACTION=link`.
392
+ - `BOARD_SCOPE_MISSING=true` (Phase 3A) → skip; the scope hint was already printed.
393
+
394
+ Whichever path is taken, Phase 5F makes sure the board has every ForgeDock field before the `project_board` IDs are written.
351
395
 
352
396
  ### 4C: services section
353
397
 
@@ -453,6 +497,7 @@ project_board:
453
497
  lane:
454
498
  fast: "{FAST_OPTION_ID}"
455
499
  feature: "{FEATURE_OPTION_ID}"
500
+ sync: "{SYNC_OPTION_ID}"
456
501
  priority:
457
502
  p0: "{P0_OPTION_ID}"
458
503
  p1: "{P1_OPTION_ID}"
@@ -562,6 +607,69 @@ forge.yaml updated:
562
607
 
563
608
  If nothing changed for an overwritten section (detected values match existing values), show `(no change)` instead of a diff.
564
609
 
610
+ Add one line for each provisioning step below, e.g. `✓ project_board — created "Acme ForgeDock" (#3), linked to acme/app, 4 fields added` and `✓ labels — 36 ForgeDock labels + domain labels: frontend, backend`.
611
+
612
+ ### 5E: Bootstrap labels (after forge.yaml is written)
613
+
614
+ Runs on every invocation unless `--section` targets some other section. With `--preserve` it still runs: label creation is idempotent.
615
+
616
+ **Domain labels.** If `DOMAIN_LABELS` was supplied (`--domain-labels`), use it verbatim. Otherwise derive it from the spec — the Phase 2 scan, `CLAUDE.md` and `README.md`:
617
+ - web/UI framework (React, Next.js, Vue, Svelte, Angular, `app/`/`frontend/` dirs) → `frontend`
618
+ - server/API code (Express, FastAPI, Django, Rails, Go/Rust services, `api/`/`backend/`/`server/` dirs) → `backend`
619
+ - `Dockerfile`, `docker-compose*`, Terraform, Kubernetes/Helm manifests, deployment workflows → `infra`
620
+ - a design system, Figma links, Storybook, or `design/` dir → `design`
621
+ - legal, compliance, licensing or regulatory gating described in the spec → `legal-gated`
622
+
623
+ Ask once — listing the derived names — only when the spec is ambiguous (or `INTERACTIVE=true`); otherwise use the derived list directly. An empty list is fine.
624
+
625
+ ```bash
626
+ if [ -n "$DOMAIN_LABELS" ]; then
627
+ npx forgedock labels setup --repo "${OWNER}/${REPO}" --domain "$DOMAIN_LABELS"
628
+ else
629
+ npx forgedock labels setup --repo "${OWNER}/${REPO}"
630
+ fi
631
+ ```
632
+
633
+ This creates or updates every label in `bin/labels.json` plus one `Domain: <name>` label per domain name (known domains get fixed colors). Failures are reported, not fatal.
634
+
635
+ ### 5F: Create or link the Projects v2 board (after forge.yaml is written)
636
+
637
+ Run only when Phase 4B set `BOARD_ACTION` (and `BOARD_SCOPE_MISSING=false`).
638
+
639
+ **Create** (`BOARD_ACTION=create`):
640
+ ```bash
641
+ CREATED=$(gh project create --owner "$OWNER" --title "${REPO} ForgeDock" --format json)
642
+ PROJECT_NUMBER=$(echo "$CREATED" | jq -r '.number')
643
+ PROJECT_ID=$(echo "$CREATED" | jq -r '.id')
644
+ ```
645
+
646
+ **Link** to the repo (both actions; idempotent — an "already linked" error is fine):
647
+ ```bash
648
+ gh project link "$PROJECT_NUMBER" --owner "$OWNER" --repo "${OWNER}/${REPO}" 2>/dev/null || true
649
+ ```
650
+
651
+ **Ensure ForgeDock fields.** `Status` is built into every Projects v2 board (Todo / In Progress / Done) — map it, never create it. Create each other field that is missing (case-insensitive name match):
652
+
653
+ ```bash
654
+ EXISTING=$(gh project field-list "$PROJECT_NUMBER" --owner "$OWNER" --format json | jq -r '.fields[].name | ascii_downcase')
655
+ COMPONENT_OPTIONS="${DOMAIN_LABELS:-core}"
656
+ # One "field name|comma-separated options" row per ForgeDock field
657
+ while IFS='|' read -r FIELD_NAME FIELD_OPTIONS; do
658
+ echo "$EXISTING" | grep -qx "$(echo "$FIELD_NAME" | tr '[:upper:]' '[:lower:]')" && continue
659
+ gh project field-create "$PROJECT_NUMBER" --owner "$OWNER" --name "$FIELD_NAME" \
660
+ --data-type SINGLE_SELECT --single-select-options "$FIELD_OPTIONS"
661
+ done <<EOF
662
+ Lane|Fast,Feature,Sync
663
+ Priority|P0,P1,P2,P3
664
+ Workflow|Investigating,Building,In Review,Merged
665
+ Component|${COMPONENT_OPTIONS}
666
+ EOF
667
+ ```
668
+
669
+ Component options come from the Phase 5E domain-label list (fallback `core`), so board components and domain labels line up.
670
+
671
+ **Write the IDs.** Re-run the Phase 3C field/option queries against `PROJECT_NUMBER`, then write the `project_board` section (Phase 5B schema) into `forge.yaml`, replacing any previous `project_board` block, and keep `forge.yaml.bak` from 5D. If any `gh project` call fails with a missing-scope error, print `gh auth refresh -h github.com -s read:project,project` and stop this step.
672
+
565
673
  ---
566
674
 
567
675
  ## Phase 6: Validation
@@ -626,6 +734,9 @@ forge.yaml is ready.
626
734
  Validation results:
627
735
  {validation output from 6A–6D}
628
736
 
737
+ Board: {created "{TITLE}" (#{N}) | linked "{TITLE}" (#{N}) | skipped — token lacks read:project/project scope, run: gh auth refresh -h github.com -s read:project,project}
738
+ Labels: {N} ForgeDock labels + domain labels: {DOMAIN_LABELS or "none"}
739
+
629
740
  Next steps:
630
741
  1. If forge.yaml contains sensitive paths (credentials file, local paths):
631
742
  echo "forge.yaml" >> .gitignore
@@ -633,6 +744,7 @@ Next steps:
633
744
  /work-on next
634
745
  3. If any WARNING appeared above, edit forge.yaml to fix the flagged values.
635
746
  4. Backup file: forge.yaml.bak (delete when satisfied)
747
+ {If BOUNDARY_ALLOW_ALL (2G): "5. Your boundary config allows every package to import every package ({BOUNDARY_ALLOW_ALL_AT}). Consider tightening it per package once its design settles: core work-on reports import growth but only blocks undeclared package.json edges."}
636
748
 
637
749
  ForgeDock commands now use your project's config from forge.yaml.
638
750
  ```
@@ -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/docs/CONFIG.md CHANGED
@@ -55,6 +55,7 @@ If you have both a local `claude` CLI and `ANTHROPIC_API_KEY` set and want to pi
55
55
  | [`review`](#review-optional) | No | Context injected into review agents |
56
56
  | [`devdocs`](#devdocs-optional) | No | Devdocs knowledge tree path |
57
57
  | [`verification`](#verification-optional) | No | Health-check patterns |
58
+ | [`verify`](#verify-optional) | No | Core work-on: run lane verification on CI runners instead of the host (`mode: local\|ci\|hybrid`) |
58
59
  | [`billing`](#billing-optional) | No | Enable financial integrity audit phase |
59
60
  | [`adaptive_scripts`](#adaptive_scripts-optional) | No | Per-repo script override configuration |
60
61
 
@@ -227,6 +228,12 @@ repos:
227
228
 
228
229
  GitHub Projects v2 integration. When configured, ForgeDock automatically adds issues to the project board and updates Status, Lane, Component, Priority, and Workflow fields as issues progress through the pipeline.
229
230
 
231
+ `/forgedock-init` fills this section for you: it links an existing board or creates a new one, links it to the repo, adds any missing Lane/Priority/Workflow/Component fields (Status is built in), and writes the IDs below. Projects v2 needs the `read:project`/`project` token scope, which `gh auth login` does not grant by default. If the scope is missing, `/forgedock-init` prints the fix:
232
+
233
+ ```bash
234
+ gh auth refresh -h github.com -s read:project,project
235
+ ```
236
+
230
237
  ```yaml
231
238
  project_board:
232
239
  owner: "acme-org"
@@ -399,7 +406,7 @@ orchestration:
399
406
  |-------|------|----------|-------------|
400
407
  | `max_concurrent` | integer | No | Max concurrent top-level `/work-on` dispatches. It does not bound cascade growth; under `policy: all` it raises drain and spawn throughput together. Default: 12. |
401
408
  | `breaker.errors` / `breaker.window_min` | integer | No | `forgedock orchestrate` circuit breaker (#4121): after `errors` engine-errors within `window_min` minutes the batch stops admitting lanes, lets running lanes finish, and keeps the rest pending for `--resume`. `errors: 0` turns it off. Default: 3 in 30 |
402
- | `max_deferrals` | integer | No | `forgedock orchestrate`: requeues of one issue after a non-host `deferred` (usage limit, merge/push transient, CLI idle, lease held) before its row is final. `host.busy` deferrals are not capped. Default: 3 |
409
+ | `max_deferrals` | integer | No | `forgedock orchestrate`: requeues of one issue after a non-host `deferred` (usage limit, merge/push transient, CLI idle, lease held, gh unavailable) before its row is final. `host.busy` deferrals are not capped. Default: 3 |
403
410
  | `cascade.policy` | string | No | `all` \| `balanced` \| `conservative`. Default: `balanced` |
404
411
  | `cascade.max_generation` | integer or `"unlimited"` | No | Max cascade generation admitted at Phase 1 resolve time. Default: 1 |
405
412
  | `cascade.batch_max_generation` | integer | No | Finite maximum member generation that an automated P3 batch may aggregate. Default: 2 |
@@ -484,12 +491,12 @@ engine:
484
491
  | `<key>_max_turns` | integer | No | Positive integer passed as `--max-turns`. Invalid values are ignored. |
485
492
 
486
493
  | `path` | string | No | `core` (default), `full`, `lean` or `auto`. `core` is the lean work-on (`bin/lean/`): a planning call that writes a frozen contract, an implement call in the same session, one read-only conformance reviewer, GitHub as the register (see `conformance`). `lean` and `auto` are aliases of `core`; `full` runs the legacy multi-phase pipeline and is required for `--retry` and `--wait-lease`. The `--path` flag overrides. `lean_reviewer_model` sets the reviewer model (default `claude-sonnet-5-5`); the builder uses `--model` or the CLI default. `forgedock orchestrate <issues...> [--parallel K] [--lane L]` runs the core per issue (K default 3, lane default `staging`) and honours `**Depends on**` ordering among the given issues. |
487
- | `conformance` | string | No | Core work-on conformance gate. The builder first writes a contract (requirements quoted verbatim from the issue and maintainers' comments, deliverables, preserve constraints, validate commands), which the engine freezes and posts as `FORGE:CONTRACT`; changes go only through typed amendments (`FORGE:AMENDMENT`). A deterministic precheck (scope, validation, deferrals) and a row-based conformance review follow, and the engine computes the verdict. `shadow` (default): the verdict is posted in `FORGE:REVIEW` and logged (`CONFORMANCE_SHADOW` event) but only what the previous reviewer blocked on still blocks (a requirement found not implemented, a regression); deferrals are not filed. `enforce`: every failure blocks (one same-session builder resume, then `needs-human` with a `conformance.*`/`review.*` code, see docs/OUTCOME-CODES.md); deferrals the reviewer accepts are filed in one batched follow-up issue (`scripts/file-deferred-followups.sh`) and linked in the PR body and a `FORGE:TRACKED` comment before merge. |
494
+ | `conformance` | string | No | Core work-on conformance gate. The builder first writes a contract (requirements quoted verbatim from the issue and maintainers' comments, deliverables, preserve constraints, validate commands), which the engine freezes and posts as `FORGE:CONTRACT`; changes go only through typed amendments (`FORGE:AMENDMENT`). A deterministic precheck (scope, validation, deferrals) and a row-based conformance review follow, and the engine computes the verdict. `shadow` (default): the verdict is posted in `FORGE:REVIEW` and logged (`CONFORMANCE_SHADOW` event) but only what the previous reviewer blocked on still blocks (a requirement found not implemented, a regression), plus the precheck's equivalent: a validate command that ran and failed, or a deliver requirement the builder reported as not implemented (#4160). These block before any reviewer run. A validate step with no recorded result, scope creep and amendment problems are recorded only. Deferrals are not filed. `enforce`: every failure blocks (one same-session builder resume, then `needs-human` with a `conformance.*`/`review.*` code, see docs/OUTCOME-CODES.md); deferrals the reviewer accepts are filed in one batched follow-up issue (`scripts/file-deferred-followups.sh`) and linked in the PR body and a `FORGE:TRACKED` comment before merge. |
488
495
  | `cli_idle_minutes` | number | No | Core builder idle timeout (#4112): the builder CLI runs with `--output-format stream-json` and is killed (code `cli.idle`) when it shows no stream activity for this many minutes. The run log records the last tool call and the live child processes. Default 15. |
489
496
  | `cli_max_minutes` | number | No | Absolute ceiling for a core builder session (code `cli.timeout`), replacing the fixed 60-minute kill. A session that keeps making progress is never idle-killed. Default 180. On either kill, uncommitted work is committed as `wip(forge): checkpoint after <reason>` and the lane branch is pushed (with the push retry), the builder retries once from the checkpoint, and a second kill ends `deferred`; a rerun resumes from the branch. |
490
497
  | `lane_limits` | map | No | Cap each core lane's whole process tree (#4115), under `pipeline:` or `engine:`: `memory_max` (for example `3G`) and `cpu_quota` (for example `150%`). When set and `systemd-run --user --scope` works (probed once), the builder CLI is spawned as `systemd-run --user --scope -q -p MemoryMax=... -p CPUQuota=... -- claude ...`; elsewhere the caps are ignored. Write it nested or flat (`lane_limits_memory_max`, `lane_limits_cpu_quota`). While a builder runs the engine polls host load and memory every 15 s against the `admission` thresholds: over them it SIGSTOPs the lane's process group, under them SIGCONT, and never pauses longer than 10 minutes. |
491
498
  | `admission` | map | No | Host-resource admission for the core work-on (#4105), checked before the lease, worktree or any GitHub call. When a check fails the run ends `deferred` (code `host.busy`, exit 75) with a last line such as `issue #N → deferred (host busy: mem 1.2G < 2G; retry in 60s)`; no lease is taken and nothing is posted. Keys: `enabled` (default `true`; `false` disables admission), `min_free_mem_mb` (default 2048; `MemAvailable` from `/proc/meminfo`, else `os.freemem()`), `min_free_tmp_mb` (default 1024; free space of the run TMPDIR filesystem, `~/.forge/tmp`, and of `/tmp`, via `statfs`), `max_load_per_core` (default 4; 1-minute load average divided by cores). Write it nested (`engine:` then `admission:` with the keys indented) or flat as `admission_enabled`, `admission_min_free_mem_mb`, `admission_min_free_tmp_mb`, `admission_max_load_per_core`; the parser reads one nested level into `engine`. `/orchestrate` requeues a host-busy lane and lowers K by 1 for 5 minutes. |
492
- | `merge_wait` | string | No | Core work-on merge gate. `required` (default): wait only for the status checks the base branch requires under branch protection (`gh api repos/{o}/{r}/branches/{base}/protection/required_status_checks`, contexts and checks); with no protection or no required checks (404 or empty) merge right after review PASS. A failing required check is `needs-human`; a pending one is polled with backoff and never `needs-human`. `all`: wait for every check (`scripts/wait-ci-green.sh`). `none`: merge right after review. Always `--match-head-commit` at the reviewed head. The policy used is recorded in the `FORGE:WORK-ON` comment. |
499
+ | `merge_wait` | string | No | Core work-on merge gate. `required` (default): wait only for the status checks the base branch requires: the union of classic branch protection (`gh api repos/{o}/{r}/branches/{base}/protection/required_status_checks`, contexts and checks) and repository rulesets (`gh api repos/{o}/{r}/rules/branches/{base}`, `required_status_checks` rules); with neither (404 or empty) merge right after review PASS. A merge rejected by base branch policy (PR `MERGEABLE` but `BLOCKED`) is waited on and retried within the CI budget, then left `ci-pending`; it is never base-synced and never `base.conflict`. A failing required check is `needs-human`; a pending one is polled with backoff and never `needs-human`. `all`: wait for every check (`scripts/wait-ci-green.sh`). `none`: merge right after review. Always `--match-head-commit` at the reviewed head. The policy used is recorded in the `FORGE:WORK-ON` comment. |
493
500
  | `ci_pending_max_minutes` | integer | No | Minutes the engine waits for CI when the review phase ends BLOCKED with "checks still pending", or the remediate phase holds at its M7 re-gate on "CI pending" (the merge gate's wait window ended while CI was still running). Default: 90. Invalid values are ignored. This key is flat like the others but is not part of the `<key>_model` / `<key>_max_turns` pattern. |
494
501
 
495
502
  **CI still pending**: the engine polls `scripts/wait-ci-green.sh` (no model calls) one read per poll on a 60 s, 2 min, 4 min, 5 min (cap) backoff with +/-20% jitter, while the lease keeps renewing, then re-dispatches the phase that was waiting, review or remediate (idempotent at an unchanged head: review re-runs the merge gate and merges; remediate resumes at its M7 re-gate). Before each poll it reads `gh api rate_limit` (not itself rate-limited) and, below 10% of the REST budget, sleeps until the window resets instead of polling; a rate-limited or transient (5xx, network) poll is retried and never re-dispatches the phase. Red CI takes the normal ci-gate path. When the budget is exhausted the run ends `needs-human` with outcome code `review.ci_pending_timeout`; `--retry` resumes. Each wait emits a `ci_pending_wait` telemetry event (`docs/TELEMETRY.md`) with the minutes waited, polls made and rate-limit sleeps.
@@ -508,6 +515,43 @@ engine:
508
515
 
509
516
  ---
510
517
 
518
+ ## `verify` (OPTIONAL)
519
+
520
+ Verify-by-CI for the core work-on path (forge#4167). It moves a lane's heavy verification (type-checks, test suites, docker test containers) off the host and onto CI runners, and it catches CI-class failures (lock files, suite wiring, hangs, formatting) before the lane merges into staging. Write it as a block-style section: `parseForgeYaml` does not read inline `{ ... }` maps.
521
+
522
+ ```yaml
523
+ verify:
524
+ mode: ci # local (default) | ci | hybrid
525
+ workflow: forgedock-verify.yml # optional; needs an `on: workflow_dispatch` trigger
526
+ paths_input: changed_paths # optional workflow_dispatch input that receives the changed paths (newline-separated)
527
+ skip_paths: "*.md,docs/**" # optional, comma-separated; default *.md,*.mdx,*.rst,*.txt,docs/**,devdocs/**
528
+ max_rounds: 2 # optional CI fix rounds before needs-human (default 2)
529
+ ```
530
+
531
+ | Key | Type | Required | Description |
532
+ |-----|------|----------|-------------|
533
+ | `mode` | string | No | `local` (default): today's behaviour; the builder runs every validate command on the host and no CI is started before review. `ci`: the builder runs only formatters and linters on the files it changed (no type-checks, test suites or test containers) and records validate ids it left to CI as `{"id","command","ci":true}`; the engine verifies on CI. `hybrid`: the builder runs the targeted validate commands too, and CI also verifies. |
534
+ | `workflow` | string | No | Workflow file name or id dispatched on the lane branch (`gh workflow run <workflow> --ref <branch>`). The engine waits for the `workflow_dispatch` run of the lane's head commit. Without it, the engine waits on the head commit's push-triggered check runs. |
535
+ | `paths_input` | string | No | Name of a `workflow_dispatch` input that receives the changed paths, so the workflow can scope its jobs to the change. |
536
+ | `skip_paths` | string | No | Comma-separated globs. A glob without `/` matches the basename anywhere; `**` spans directories. When every changed file matches, the lane skips CI verification (a docs-only lane runs no CI matrix). |
537
+ | `max_rounds` | integer | No | Builder fix rounds for red CI (0–10, default 2). |
538
+
539
+ **Flow** (`ci`/`hybrid`, `bin/lean/verify.mjs` `runCiVerify`):
540
+
541
+ 1. After the build and before every conformance review, the engine pushes the lane branch (a plain push, never forced) and runs CI on the head. A base-sync that moves the head is verified again.
542
+ 2. Green (or skipped by `skip_paths`): the review runs. The precheck accepts validate ids left to CI for that head, but a recorded non-zero exit still fails.
543
+ 3. Red: the failed jobs' logs (`gh run view --log-failed`, tails capped at about 12 KB) go back to the builder session. The builder fixes and commits, and the engine pushes and verifies again. Still red after `max_rounds`, or a fix round with no new commit, ends as `needs-human` (`ci.red`) and nothing merges.
544
+ 4. Slow CI: once the core CI wait budget (360 min, the same budget the merge wait uses) is used up, the lane ends `ci-pending` and can be resumed. Slow CI is never `needs-human`.
545
+ 5. CI unavailable (dispatch refused, or no run or check for the head within 5 minutes): one local fallback. The builder runs every validate command on the host and records real exit codes.
546
+
547
+ Each verification emits a `CI_VERIFY` run-log/telemetry event (`mode`, `round`, `result`, `skipped`, `run_url`) and adds a **Verify** row to the trajectory.
548
+
549
+ **Cost:** `ci`/`hybrid` spend GitHub Actions minutes before review, so they are opt-in per repo. On the green path they add no LLM calls; each red CI round adds one builder resume. The merge gate (`engine.merge_wait`) is unchanged.
550
+
551
+ **Commands that use this section**: core work-on (`forgedock run-issue`, `forgedock orchestrate`; `bin/engine-cli.mjs` `readVerify`)
552
+
553
+ ---
554
+
511
555
  ## `build` (OPTIONAL)
512
556
 
513
557
  Tuning for the `/work-on` build phase. Today it holds the **scope check** (forge#3862, replacing the diff-size gate): after the implement phase stages its changes and before validation commits them, the build compares the staged diff with the architect plan (the `PLANNED_FILES:` list and `ESTIMATED_DIFF_LINES` in the plan's `### Scope Baseline`), measures a deterministic `review_weight` with `scripts/diff-size.sh`, and records the outcome in `FORGE:DIFF_SIZE`. **Size never blocks, a finished build is never discarded, and a completed build is never routed to decompose.**
@@ -977,8 +1021,13 @@ npx forgedock labels setup
977
1021
 
978
1022
  # Or target a specific repo explicitly:
979
1023
  npx forgedock labels setup --repo owner/repo
1024
+
1025
+ # Also create per-repo domain labels (comma-separated):
1026
+ npx forgedock labels setup --domain frontend,backend,infra,design,legal-gated
980
1027
  ```
981
1028
 
1029
+ Domain labels are optional and per-repo. They are not part of `bin/labels.json`, so `forgedock doctor` never requires them. Each one gets the description `Domain: <name>. Managed by ForgeDock.`. `frontend`, `backend`, `infra`, `design` and `legal-gated` have fixed colors; any other name is grey (`#EDEDED`). A name that is already in the manifest is not created twice. `/forgedock-init` derives the list from your spec or takes it from `--domain-labels`.
1030
+
982
1031
  Running this command is idempotent — it creates labels that don't exist and updates the color/description of labels that do. Safe to re-run at any time.
983
1032
 
984
1033
  **When to run it**: Once after `npx forgedock install`, or whenever a pipeline command fails with "label not found". The command bootstraps every label the pipeline relies on.
@@ -37,7 +37,9 @@ An absent or unknown code is recorded as `unclassified`, with the raw reason kep
37
37
  | `merge.transient` | `gh pr merge` kept failing with a transient error (`Base branch was modified`, GraphQL or 5xx) after the retry budget; the run is `deferred` and a rerun resumes at the merge | lean | `bin/lean/lean.mjs` |
38
38
  | `push.transient` | `git push` kept failing with a transient error (`remote unpack failed`, `index-pack failed`, `RPC failed`, 5xx, reset, timeout) after 4 attempts; `deferred`, rerun resumes | lean | `bin/lean/lean.mjs` |
39
39
  | `cli.idle` | The builder CLI showed no stream activity for `engine.cli_idle_minutes` and was killed; uncommitted work was checkpointed to the lane branch; `deferred` after one automatic retry | lean | `bin/runner.mjs` |
40
+ | `run.interrupted` | A core run got SIGTERM/SIGINT; the lane tore down from its resource ledger (unpushed work checkpointed to the lane branch, worktree removed, lease released) and ended `deferred`, so a batch requeues it | lean | `bin/engine-cli.mjs` |
40
41
  | `host.busy` | Core run deferred at admission: free memory, free tmp space or load per core outside `engine.admission` limits; no lease taken, nothing posted | lean | `bin/lean/admission.mjs` |
42
+ | `gh.unavailable` | `run-issue --repo` could not verify the cwd repo because `gh repo view` kept failing after 3 attempts with backoff (typically a timeout under host load); the core or full run is `deferred` (exit 75, retry in 60s) before any lease, nothing posted | engine CLI | `bin/engine-cli.mjs` |
41
43
  | `rate_limited` | Session or usage limit outlasted the pause budget | engine | `err.usageLimit` / `err.resetAtEpochMs` |
42
44
  | `engine.error` | Engine or tool failure (missing key/SDK, CLI crash, max turns, unresolved args, runner threw every attempt) | engine | `err.code` |
43
45
  | `infra.disk_quota` | Disk full or quota exhausted (EDQUOT/ENOSPC, errno -28/-122); retryable infra failure, never `needs-human` | engine | Runner error text |
@@ -70,6 +72,7 @@ An absent or unknown code is recorded as `unclassified`, with the raw reason kep
70
72
  | `conformance.unjustified_deferral` | Core work-on: the reviewer judged a deferred or post-deploy requirement unjustified (it belongs in this change), after one builder resume; nothing is filed or merged | lean | Review requirement row `deferral_ok: false` |
71
73
  | `conformance.preserve_violated` | Core work-on: a preserve constraint is violated or unverified, after one builder resume | lean | Review `preserve` row not `intact` |
72
74
  | `conformance.regression` | Core work-on: the reviewer named a concrete regression, after one builder resume | lean | Review `regressions` row |
75
+ | `structure.undeclared_edge` | Core work-on: the change adds a package.json dependency between workspace packages that the frozen contract does not name, still present after one builder resume | lean | `unnamedEdges` over `scripts/structure-delta.sh` `package_edges_added` |
73
76
  | `followup.unavailable` | Core work-on: filing or verifying the tracking issue for a deferral failed; the run is deferred with nothing merged, never needs-human | lean | `file-deferred-followups.sh` exit 3/4 or an unreadable tracking issue |
74
77
  | `merge.head_mismatch` | Core work-on: CI passed for a head other than the reviewed head; merge refused | lean | Gate head differs from the reviewed head |
75
78
  | `completed` | Non-failure terminal (merged, phase-complete, investigation) | engine | Terminal reason |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
4
4
  "description": "Autonomous development pipeline for coding agents. GitHub as institutional memory, with native Claude Code and OpenCode entrypoints.",
5
5
  "bin": {
6
6
  "forgedock": "bin/forgedock.mjs"