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.
- package/bin/engine/outcome-codes.mjs +3 -0
- package/bin/engine/phases.mjs +38 -2
- package/bin/engine/telemetry-seam.mjs +6 -0
- package/bin/engine/telemetry-seam.test.mjs +4 -0
- package/bin/engine-cli.mjs +78 -20
- package/bin/engine.mjs +5 -1
- package/bin/forgedock.mjs +94 -30
- 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/batch.mjs +2 -1
- package/bin/lean/batch.test.mjs +49 -1
- package/bin/lean/conformance.mjs +76 -10
- package/bin/lean/conformance.test.mjs +32 -3
- package/bin/lean/effects.mjs +96 -11
- package/bin/lean/lean.mjs +326 -33
- package/bin/lean/lean.test.mjs +618 -10
- package/bin/lean/ledger.mjs +67 -0
- package/bin/lean/ledger.test.mjs +65 -0
- package/bin/lean/orchestrate.mjs +9 -0
- package/bin/lean/prompts.mjs +42 -8
- package/bin/lean/render.mjs +1 -1
- package/bin/lean/verify.mjs +113 -0
- package/bin/lean/verify.test.mjs +95 -0
- package/commands/adopt.md +3 -0
- package/commands/audit.md +3 -8
- package/commands/forgedock-init.md +118 -6
- package/commands/review-pr.md +2 -2
- package/docs/CONFIG.md +52 -3
- package/docs/OUTCOME-CODES.md +3 -0
- package/package.json +1 -1
- package/scripts/bootstrap-validate.mjs +287 -0
- package/scripts/check-devdocs-freshness.sh +120 -0
- package/scripts/check-devdocs-freshness.test.sh +120 -0
- package/scripts/ci-local/run.mjs +13 -2
- package/scripts/ci-local.test.sh +13 -1
- package/scripts/northstar-docs.test.sh +68 -0
- package/scripts/review-prepare.sh +11 -9
- package/scripts/review-prepare.test.sh +14 -0
- package/scripts/structure-delta.sh +150 -0
- 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 `
|
|
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
|
|
350
|
-
|
|
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
|
```
|
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/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)
|
|
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
|
|
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.
|
package/docs/OUTCOME-CODES.md
CHANGED
|
@@ -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.
|
|
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"
|