forgedock 1.17.0 → 1.19.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 +37 -2
- package/bin/engine/phases.mjs +207 -106
- package/bin/engine/runlog.mjs +17 -2
- package/bin/engine/telemetry-seam.mjs +3 -0
- package/bin/engine/telemetry.test.mjs +3 -2
- package/bin/engine-cli.mjs +37 -2
- package/bin/engine.mjs +221 -15
- package/bin/forgedock.mjs +1 -1
- package/bin/hooks/pre-tool-use.mjs +65 -32
- package/bin/runner.mjs +125 -6
- package/bin/stats/aggregate.mjs +7 -3
- package/bin/stats/cache.mjs +7 -3
- package/bin/stats/github-source.mjs +203 -24
- package/bin/stats/index.mjs +46 -9
- package/bin/stats/render.mjs +1 -1
- package/bin/stats/stats.test.mjs +264 -15
- package/commands/cleanup.md +10 -3
- package/commands/deploy-pr.md +64 -0
- package/commands/fix-ci.md +20 -6
- package/commands/milestone.md +34 -17
- package/commands/orchestrate/phase-4-execution.md +11 -4
- package/commands/review-pr-staging.md +145 -522
- package/commands/review-pr.md +496 -3124
- package/commands/test-gate.md +25 -10
- package/commands/work-on/build/architect.md +66 -1
- package/commands/work-on/build/implement.md +21 -1
- package/commands/work-on/build/validate.md +322 -28
- package/commands/work-on/build.md +173 -95
- package/commands/work-on/close.md +50 -15
- package/commands/work-on/decompose.md +72 -34
- package/commands/work-on/investigate.md +16 -12
- package/commands/work-on/remediate.md +58 -25
- package/commands/work-on/review.md +36 -21
- package/commands/work-on.md +1 -2
- package/docs/CONFIG.md +132 -26
- package/docs/OUTCOME-CODES.md +12 -3
- package/docs/RELEASE-NOTES.md +12 -0
- package/docs/STATS.md +13 -5
- package/docs/TELEMETRY.md +7 -4
- package/docs/WORK-ON-RUNTIME.md +24 -0
- package/docs/spec/forge-protocol-v1.md +2 -2
- package/package.json +2 -2
- package/packages/protocol/src/phases.js +5 -5
- package/packages/protocol/src/types.js +1 -1
- package/scripts/acceptance-red-green.sh +90 -0
- package/scripts/acceptance-red-green.test.sh +74 -0
- package/scripts/cap-findings.sh +1 -1
- package/scripts/cap-findings.test.sh +3 -1
- package/scripts/check-contract-scope.test.sh +2 -0
- package/scripts/check-edge-cases.sh +143 -0
- package/scripts/check-edge-cases.test.sh +175 -0
- package/scripts/check-spec-bash.sh +5 -1
- package/scripts/check-spec-bash.test.sh +20 -0
- package/scripts/check-spec-markers.sh +2 -0
- package/scripts/check-test-tmp-cleanup.sh +56 -0
- package/scripts/check-test-tmp-cleanup.test.sh +69 -0
- package/scripts/ci-local/adapters/azure-pipelines.mjs +113 -0
- package/scripts/ci-local/adapters/bitbucket.mjs +64 -0
- package/scripts/ci-local/adapters/buildkite.mjs +58 -0
- package/scripts/ci-local/adapters/circleci.mjs +101 -0
- package/scripts/ci-local/adapters/common.mjs +115 -0
- package/scripts/ci-local/adapters/explicit.mjs +21 -0
- package/scripts/ci-local/adapters/github-actions.mjs +349 -0
- package/scripts/ci-local/adapters/gitlab-ci.mjs +184 -0
- package/scripts/ci-local/adapters/portability.mjs +104 -0
- package/scripts/ci-local/adapters/task-runners.mjs +67 -0
- package/scripts/ci-local/cli.mjs +50 -0
- package/scripts/ci-local/lib.mjs +371 -0
- package/scripts/ci-local/plan.mjs +186 -0
- package/scripts/ci-local/run.mjs +345 -0
- package/scripts/ci-local/scoping.mjs +508 -0
- package/scripts/ci-local.sh +97 -0
- package/scripts/ci-local.test.sh +919 -0
- package/scripts/classify-finding.sh +32 -6
- package/scripts/classify-finding.test.sh +23 -0
- package/scripts/classify-test-run.sh +69 -0
- package/scripts/classify-test-run.test.sh +90 -0
- package/scripts/close-parent-c3.test.sh +71 -0
- package/scripts/consumer-sweep.sh +569 -0
- package/scripts/consumer-sweep.test.sh +357 -0
- package/scripts/detect-append-registries.sh +106 -0
- package/scripts/detect-append-registries.test.sh +64 -0
- package/scripts/diff-size.sh +93 -4
- package/scripts/diff-size.test.sh +59 -2
- package/scripts/fixtures/ci-local/azure/azure-pipelines.yml +11 -0
- package/scripts/fixtures/ci-local/bitbucket/bitbucket-pipelines.yml +12 -0
- package/scripts/fixtures/ci-local/buildkite/.buildkite/pipeline.yml +10 -0
- package/scripts/fixtures/ci-local/circleci/.circleci/config.yml +29 -0
- package/scripts/fixtures/ci-local/gha-basic/.github/workflows/ci.yml +59 -0
- package/scripts/fixtures/ci-local/gha-basic/.github/workflows/docs.yml +10 -0
- package/scripts/fixtures/ci-local/gha-basic/.github/workflows/nightly.yml +10 -0
- package/scripts/fixtures/ci-local/gha-basic/docs/index.md +1 -0
- package/scripts/fixtures/ci-local/gha-basic/src/greet.sh +2 -0
- package/scripts/fixtures/ci-local/gha-basic/tests/greet.test.sh +3 -0
- package/scripts/fixtures/ci-local/gha-dorny/.github/workflows/ci.yml +51 -0
- package/scripts/fixtures/ci-local/gha-dorny/backend/app.py +1 -0
- package/scripts/fixtures/ci-local/gha-dorny/docs/a.md +1 -0
- package/scripts/fixtures/ci-local/gha-dorny/frontend/app.js +1 -0
- package/scripts/fixtures/ci-local/gha-run/.github/workflows/ci.yml +39 -0
- package/scripts/fixtures/ci-local/gha-run/src/x.txt +1 -0
- package/scripts/fixtures/ci-local/gitlab/.gitlab-ci.yml +43 -0
- package/scripts/fixtures/ci-local/gitlab/docs/d.md +1 -0
- package/scripts/fixtures/ci-local/gitlab/src/a.txt +1 -0
- package/scripts/fixtures/ci-local/monorepo/.github/workflows/ci.yml +13 -0
- package/scripts/fixtures/ci-local/monorepo/package-lock.json +1 -0
- package/scripts/fixtures/ci-local/monorepo/packages/a/src/a.sh +2 -0
- package/scripts/fixtures/ci-local/monorepo/packages/a/tests/a.test.sh +3 -0
- package/scripts/fixtures/ci-local/monorepo/packages/b/src/b.sh +2 -0
- package/scripts/fixtures/ci-local/monorepo/packages/b/tests/b.test.sh +3 -0
- package/scripts/fixtures/ci-local/no-ci/Makefile +6 -0
- package/scripts/fixtures/ci-local/no-ci/package.json +1 -0
- package/scripts/fixtures/ci-local/no-ci/src/a.js +1 -0
- package/scripts/fixtures/ci-local/no-ci/src/b.js +1 -0
- package/scripts/fixtures/ci-local/no-ci/tests/a.test.js +2 -0
- package/scripts/fixtures/ci-local/no-ci/tests/b.test.js +2 -0
- package/scripts/forge-root.test.sh +14 -12
- package/scripts/lib/telemetry.sh +1 -0
- package/scripts/publish-staging-sync.test.sh +48 -0
- package/scripts/remediate-approved-head.test.sh +2 -0
- package/scripts/remediate-basesync.test.sh +1 -1
- package/scripts/resolve-deploy-gate.sh +32 -6
- package/scripts/resolve-deploy-gate.test.sh +42 -1
- package/scripts/review-acceptance-gate.sh +2 -2
- package/scripts/review-acceptance-gate.test.sh +20 -0
- package/scripts/review-delta.sh +8 -1
- package/scripts/review-delta.test.sh +19 -0
- package/scripts/review-finalize.sh +1579 -0
- package/scripts/review-finalize.test.sh +1040 -0
- package/scripts/review-pr-delta-wiring.test.sh +32 -72
- package/scripts/review-pr-domain-map.test.sh +18 -13
- package/scripts/review-pr-gate2.test.sh +9 -65
- package/scripts/review-pr-staging-scope.test.sh +14 -98
- package/scripts/review-prepare.sh +53 -15
- package/scripts/review-prepare.test.sh +37 -3
- package/scripts/review-roster.sh +16 -2
- package/scripts/review-roster.test.sh +22 -0
- package/scripts/run-shell-tests.sh +5 -0
- package/scripts/telemetry.test.sh +2 -0
- package/scripts/trusted-comments.test.sh +11 -3
- package/scripts/verify-phase-trail.test.sh +8 -0
- package/scripts/wait-ci-green.sh +61 -9
- package/scripts/wait-ci-green.test.sh +44 -6
- package/scripts/with-run-tmpdir.sh +20 -0
package/docs/CONFIG.md
CHANGED
|
@@ -50,7 +50,7 @@ If you have both a local `claude` CLI and `ANTHROPIC_API_KEY` set and want to pi
|
|
|
50
50
|
| [`project_board`](#project_board-optional) | No | GitHub Projects v2 integration |
|
|
51
51
|
| [`orchestration`](#orchestration-optional) | No | `/orchestrate` concurrency and cascade admission policy |
|
|
52
52
|
| [`pipeline`](#pipeline-optional) | No | `/orchestrate` batch-engine tuning (stall detection, token budget, narration) |
|
|
53
|
-
| [`build`](#build-optional) | No | `/work-on` build-phase
|
|
53
|
+
| [`build`](#build-optional) | No | `/work-on` build-phase scope check (`build.scope.*`) and review depth (`review.depth.*`) |
|
|
54
54
|
| [`services`](#services-optional) | No | External service URLs and IDs |
|
|
55
55
|
| [`review`](#review-optional) | No | Context injected into review agents |
|
|
56
56
|
| [`devdocs`](#devdocs-optional) | No | Devdocs knowledge tree path |
|
|
@@ -481,58 +481,79 @@ engine:
|
|
|
481
481
|
| `<key>_model` | string | No | Model alias (`sonnet`, `opus`, `haiku`) or full model id passed as `--model`. Invalid values are ignored. |
|
|
482
482
|
| `<key>_max_turns` | integer | No | Positive integer passed as `--max-turns`. Invalid values are ignored. |
|
|
483
483
|
|
|
484
|
+
| `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. |
|
|
485
|
+
|
|
486
|
+
**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.
|
|
487
|
+
|
|
484
488
|
**Precedence** (model): `engine.<key>_model`, then the run-level model the caller passed explicitly, then omitted. `max_turns` comes only from this section. No defaults ship.
|
|
485
489
|
|
|
486
490
|
**Version gate**: the flags are added only when the installed `claude --version` supports them (`--max-turns` is hidden from `claude --help`, so help text is never scraped); an unparseable version omits both flags.
|
|
487
491
|
|
|
488
492
|
**Turn exhaustion**: when `--max-turns` is exhausted the run ends `engine-error` (detail `max-turns`) and the phase is never committed, so `forgedock run-issue --retry` re-enters the same phase. Raise `<key>_max_turns` before retrying. Phase runlog events record `model` and `turns` next to `usage` so a limit can be sized from measured turn counts.
|
|
489
493
|
|
|
494
|
+
**Per-run scratch dir** (`tmp_root`): every engine child runs with `TMPDIR` and `CLAUDE_CODE_TMPDIR` set to `<tmp_root>/<repo-key>/<issue>` so venvs and Claude session data stay off a size-limited `/tmp`. `engine.tmp_root` is an absolute or `~`-prefixed path; default `~/.forge/tmp`. The `<repo-key>` is the checkout's directory name plus 8 hex characters of a hash of its real path, so the same issue number in two repos never shares a dir; the dir is created `0700` and an existing symlink or dir owned by another user is not used (the child keeps the inherited tmp). Old `<tmp_root>/<issue>` dirs from earlier versions are not migrated. When `tmp_root` is unset and the operator already exported `TMPDIR` or `CLAUDE_CODE_TMPDIR`, that choice is respected and never cleaned. The dir is removed on a terminal outcome and kept on `engine-error` so `--retry` resumes. A disk-full/quota failure (EDQUOT, ENOSPC, errno -28/-122) ends a retryable `engine-error` (outcome code `infra.disk_quota`), never `needs-human`.
|
|
495
|
+
|
|
490
496
|
**Commands that use this section**: CLI backend (`runCommand` in `bin/runner.mjs`)
|
|
491
497
|
|
|
492
498
|
---
|
|
493
499
|
|
|
494
500
|
## `build` (OPTIONAL)
|
|
495
501
|
|
|
496
|
-
Tuning for the `/work-on` build phase. Today it holds the diff-size gate: after the implement phase stages its changes and before validation commits them, the build
|
|
502
|
+
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.**
|
|
497
503
|
|
|
498
504
|
```yaml
|
|
499
505
|
build:
|
|
500
|
-
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
#
|
|
505
|
-
#
|
|
506
|
-
estimate_factor: 1.2
|
|
507
|
-
|
|
508
|
-
# Pre-build estimate (investigate): a non-batch issue with at least this many
|
|
509
|
-
# independent seams and an estimate over half the threshold is decomposed into
|
|
510
|
-
# packed sub-issues (not one per seam). Review-finding batches are exempt.
|
|
511
|
-
# Default: 3.
|
|
512
|
-
batch_seam_min: 3
|
|
513
|
-
|
|
506
|
+
scope:
|
|
507
|
+
test_weight: 50 # percent weight of test lines in review_weight (default 50)
|
|
508
|
+
module_depth: 2 # path segments that define a module (default 2)
|
|
509
|
+
overrun_pct: 200 # whitespace-insensitive lines over the plan estimate, in percent, that count as scope creep (default 200)
|
|
510
|
+
trim_out_of_plan: false # move out-of-plan hunks into the batched follow-up checklist (default false: flag only)
|
|
511
|
+
trim_max_files: 3 # trim only when at most this many files are out of plan (default 3)
|
|
514
512
|
# Extra exclusion globs, ADDITIVE to the built-in defaults below.
|
|
515
513
|
exclude_globs:
|
|
516
514
|
- "docs/api/*"
|
|
517
515
|
- "*.pb.go"
|
|
516
|
+
|
|
517
|
+
review:
|
|
518
|
+
depth:
|
|
519
|
+
extended_weight: 1500 # review_weight at or above which review is EXTENDED (default 1500)
|
|
520
|
+
extended_modules: 6 # modules touched at or above which review is EXTENDED (default 6)
|
|
521
|
+
slice_files: 12 # changed files per slice in extended review (default 12)
|
|
518
522
|
```
|
|
519
523
|
|
|
520
524
|
| Field | Type | Required | Description |
|
|
521
525
|
|-------|------|----------|-------------|
|
|
522
|
-
| `
|
|
523
|
-
| `
|
|
524
|
-
| `
|
|
525
|
-
| `
|
|
526
|
+
| `build.scope.test_weight` | integer | No | Percent of a test file's changed lines counted in `review_weight` (tests are easier to review). Default: 50 |
|
|
527
|
+
| `build.scope.module_depth` | integer | No | Leading path segments of a file's directory that identify a module for the `modules` count. Default: 2 |
|
|
528
|
+
| `build.scope.overrun_pct` | integer | No | The scope is flagged when whitespace-insensitive changed lines exceed the plan's estimate by more than this percent (200 = more than twice the estimate). Default: 200 |
|
|
529
|
+
| `build.scope.trim_out_of_plan` | boolean | No | Opt-in. Moves each out-of-plan file's hunks out of the PR into the batched follow-up issue (forge#3843); the hunks are saved in a patch file next to the git dir, so nothing is lost. Never triggered by an overrun alone. Default: `false` (flag only) |
|
|
530
|
+
| `build.scope.trim_max_files` | integer | No | Trim only when at most this many files are out of plan; more is flagged, not trimmed. Default: 3 |
|
|
531
|
+
| `build.scope.exclude_globs` | list of strings | No | Globs excluded from the measurement, in addition to the built-in defaults. Default: `[]` |
|
|
532
|
+
| `review.depth.extended_weight` | integer | No | `review_weight` at or above which the build records `review_depth: EXTENDED`. Default: 1500 |
|
|
533
|
+
| `review.depth.extended_modules` | integer | No | Modules touched at or above which the build records `review_depth: EXTENDED`. Default: 6 |
|
|
534
|
+
| `review.depth.slice_files` | integer | No | In extended review, each domain agent reviews slices of at most this many changed files. Default: 12 |
|
|
535
|
+
|
|
536
|
+
All thresholds are config values with generous defaults, to be calibrated from telemetry (`diff_size` events carry the plan estimate, the actual, `review_weight`, the scope outcome and the review depth; see `docs/TELEMETRY.md`).
|
|
537
|
+
|
|
538
|
+
**`review_weight`** (computed by `scripts/diff-size.sh`): changed lines with whitespace ignored (`git diff -w`, so a pure re-indent counts about 0), over the counted files only (generated and vendored code excluded), where test files count `test_weight` percent. `modules` counts the distinct directories (first `module_depth` segments) holding at least one non-whitespace change. It drives review depth, never blocking.
|
|
526
539
|
|
|
527
540
|
**Built-in default exclusions** (always applied, defined once in `scripts/diff-size.sh`): lockfiles (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `Cargo.lock`, `poetry.lock`, `uv.lock`, `go.sum`, `Gemfile.lock`, `composer.lock`), `*.min.*`, `*.snap`, and the directories `dist/`, `build/`, `generated/`, `fixtures/`, `vendor/` (anchored to the repo root, so first-party directories such as `commands/work-on/build/` are counted) plus `__generated__/` and `__snapshots__/` (any depth). The diff is measured against the merge base of `HEAD` and `origin/<base>`, so commits that landed on the base after the branch point are not counted. A configured glob ending in `/` matches that directory at any depth; any other glob is matched against the file basename and the full path. Globs are treated as data, never evaluated.
|
|
528
541
|
|
|
529
|
-
**
|
|
542
|
+
**Scope-check outcomes** (`result:` in `FORGE:DIFF_SIZE`): `OK` (in plan, standard depth); `EXTENDED_REVIEW` (in plan, `review_weight` or `modules` at the extended threshold: review adds reviewers and per-file slices); `SCOPE_FLAGGED` (files outside the plan, or an estimate overrun: listed in the comment for review, review extended, optionally trimmed); `OVERRIDDEN` (scope flagged but a justified `FORGE:SIZE_OVERRIDE` is present). An architect skip marker (empty plan) means no plan baseline: the out-of-plan check is skipped, never blocking. A `FORGE:SIZE_OVERRIDE` comment (trusted author, non-empty justification on the line after the marker) suppresses the scope flag and the decompose loop guard. The measured `diff_lines` is still recorded in the `FORGE:TRAJECTORY` comment.
|
|
543
|
+
|
|
544
|
+
**Decomposition is a planning decision**: `work-on:investigate` answers `**YES**` only when the work is separable (independent modules or services, no shared files, no ordering dependency, each piece independently testable), with the reason recorded; a cohesive change stays one unit at any size. It emits `ESTIMATED_DIFF_LINES` (the scope baseline) and `INDEPENDENT_SEAMS` inside `### Decomposition Assessment`. The engine downgrades a `**YES**` to NO unless `INDEPENDENT_SEAMS` is at least 2 with an own-line `DECOMPOSE_JUSTIFICATION:`; it has no line-count rule. A sub-issue, an issue with a trusted `FORGE:DECOMPOSED` comment, or one with a justified `FORGE:SIZE_OVERRIDE` is never decomposed (decompose Guard 1).
|
|
530
545
|
|
|
531
|
-
**
|
|
546
|
+
**Migration from `build.diff_size.*`** (deprecated; read as aliases for one release, then removed):
|
|
532
547
|
|
|
533
|
-
|
|
548
|
+
| Old key | New behaviour |
|
|
549
|
+
|---------|---------------|
|
|
550
|
+
| `build.diff_size.threshold` | No longer a gate. A positive value is read as `review.depth.extended_weight` (extended review instead of a block or split); `0` (gate disabled) now just means the defaults. The build records `deprecated_config` in `FORGE:DIFF_SIZE` when an alias supplied a value |
|
|
551
|
+
| `build.diff_size.exclude_globs` | Read as an alias of `build.scope.exclude_globs` (additive) |
|
|
552
|
+
| `build.diff_size.estimate_factor`, `build.diff_size.batch_seam_min` | Ignored. The investigator's decomposition rule is separability, not a line count |
|
|
534
553
|
|
|
535
|
-
|
|
554
|
+
`BUILD_RESULT: status: NEEDS_DECOMPOSE`, the `forge/wip/{NUMBER}` ref, the cohesive-overage path and the outcome code `build.size_over_gate` are retired. An old run whose build was handed to decompose can be resumed at build with `run-issue --retry` once a trusted `FORGE:DECOMPOSED:REVOKED` comment (newer than the `FORGE:DECOMPOSED` comment) and a justified `FORGE:SIZE_OVERRIDE` are on the issue; untrusted comments never reopen a run.
|
|
555
|
+
|
|
556
|
+
**Commands that use this section**: `work-on` (build phase B5.5, `close`), `review-pr` (Phase 3 review depth)
|
|
536
557
|
|
|
537
558
|
---
|
|
538
559
|
|
|
@@ -550,6 +571,8 @@ decomposition:
|
|
|
550
571
|
|-------|------|----------|-------------|
|
|
551
572
|
| `close_parent` | boolean | No | When `true`, `work-on:decompose` (Phase D6.5) closes the parent after the sub-issues exist, `FORGE:DECOMPOSED:COMPLETE` is posted and `workflow:decomposed` is set, with the comment "Decomposed into #a #b #c; tracked here until all complete". The label is kept. Every child body is also checked for the `**Parent**: #N` reference. Default: `false`. Any value other than `true`, or an unreadable config, leaves the parent open |
|
|
552
573
|
|
|
574
|
+
**Batch members**: when the parent carries a `FORGE:BATCH_MEMBERS` block, `work-on:decompose` (Phase D2.7) reopens and reuses a closed member that matches a planned sub-issue one to one, instead of filing a duplicate. Members with a merged PR or a `false-positive`, `needs-human` or `operator-only` label are never reopened, and any ambiguity falls back to creating a new issue. There is no config key for this.
|
|
575
|
+
|
|
553
576
|
**With `close_parent: true`**: `work-on:close` Phase C3 keeps checking children off the closed parent's checklist. When the last child closes, the parent gets an "All sub-issues complete" comment and `workflow:merged`; it is not closed a second time and never reopened. `/pipeline-status` and `/pipeline-health` exclude `workflow:decomposed` from work counts and report them separately as `trackers (N)`. `/orchestrate` expands an explicitly requested closed decomposed parent to its open children.
|
|
554
577
|
|
|
555
578
|
**With `close_parent: false`** (default): unchanged. The parent stays open and is closed by C3 when the last child closes.
|
|
@@ -637,7 +660,7 @@ review:
|
|
|
637
660
|
| `tech_stack` | string | No | One-line tech stack summary. Injected into all review agent prompts |
|
|
638
661
|
| `context` | string (multiline) | No | Freeform context about architecture, deploy model, and known pitfalls |
|
|
639
662
|
| `domains.*` | string | No | Domain-specific notes keyed by review agent name (`billing`, `auth`, `database`, `frontend`, `api`, `infra`, `security`) |
|
|
640
|
-
| `max_filed_findings` | integer >= 0 | No | Per-review cap on ISSUE dispositions (`scripts/cap-findings.sh`, applied by `review-pr` §6B.5 and `review-pr-staging` §7B.
|
|
663
|
+
| `max_filed_findings` | integer >= 0 | No | Per-review cap on ISSUE dispositions (`scripts/cap-findings.sh`, applied by `review-pr` §6B.5 and `review-pr-staging` §7B (via `review-finalize.sh classify`) after classification). Default `2`; `1` when the reviewed PR is itself a fix for a `review-finding` issue, where every filed HIGH/CRITICAL consumes that budget. ISSUE findings are ranked by severity, then confidence, then safety-domain agent (security, auth, billing, concurrency, database), then input order; an unparseable severity or confidence ranks as MEDIUM/LIKELY. The overflow becomes `NOTE capped`: listed in the PR body and counted as `notes_capped` in `FORGE:NOTE_DISPOSITION`, never filed and never dropped. **HIGH/CRITICAL findings are never capped** and still block; `ISSUE fix-efficacy-*` findings (the linked issue is not fixed, or the PR re-introduces its defect) are never capped either and count against the review-finding budget like HIGH. `0` is valid (cap every MEDIUM/LOW finding); a non-numeric or negative value falls back to the default with a warning. An explicit value overrides both defaults. |
|
|
641
664
|
| `key_paths` | map(string→list of strings) | No | Domain-to-file mapping used by `/work-on` investigation and `/review-pr` agents to quickly locate relevant files. Keys are domain names matching issue labels; values are lists of file path patterns (glob-style, relative to repo root). When present, agents use this table instead of inferring files from labels and issue body. |
|
|
642
665
|
|
|
643
666
|
**Commands that use this section**: `review-pr`, `review-pr-agents` (all domain agents), `work-on` (Phase 1B investigation)
|
|
@@ -717,6 +740,10 @@ verification:
|
|
|
717
740
|
- name: "web"
|
|
718
741
|
container: "acme-web-blue"
|
|
719
742
|
health_url: "http://localhost:3000"
|
|
743
|
+
|
|
744
|
+
consumer_sweep:
|
|
745
|
+
mode: advisory # advisory (default) | blocking
|
|
746
|
+
budget_seconds: 60 # wall-clock budget for the sweep
|
|
720
747
|
```
|
|
721
748
|
|
|
722
749
|
| Field | Type | Required | Description |
|
|
@@ -727,8 +754,87 @@ verification:
|
|
|
727
754
|
| `services[].name` | string | **Yes (per entry)** | Human-readable service name |
|
|
728
755
|
| `services[].container` | string | No | Docker container name for `docker exec` verification |
|
|
729
756
|
| `services[].health_url` | string | No | Local URL for container-level health checks |
|
|
757
|
+
| `ci_local` | object | No | Local CI gate settings (see below) |
|
|
758
|
+
| `consumer_sweep.mode` | string | No | `advisory` (default): the validate consumer sweep (`scripts/consumer-sweep.sh`, V4.6) records unaddressed referencing sites and never fails. `blocking`: unaddressed sites fail validate with `consumer_unaddressed`, build runs one repair round, and sites still unaddressed are recorded as `CONSUMERS: unresolved` and passed to review as known risk (never `needs-human`). Flip to `blocking` only after `bash scripts/consumer-sweep.sh --eval-history 20` shows a mean of at most 2 flagged sites per merge and none above 10 on your repo |
|
|
759
|
+
| `consumer_sweep.budget_seconds` | integer | No | Wall-clock budget for the sweep (default `60`). When it runs out the sweep stops with `BUDGET_EXCEEDED`, reports the partial result, and never fails, in either mode |
|
|
760
|
+
|
|
761
|
+
**Commands that use this section**: `quality-gate`, `validate` (V2 runs `verification.commands`; V4.8 runs the local CI gate; health checks and `consumer_sweep`), `work-on`
|
|
762
|
+
|
|
763
|
+
### `verification.ci_local` (OPTIONAL)
|
|
764
|
+
|
|
765
|
+
Validate Phase V4.8 runs `scripts/ci-local.sh` before review: it derives the repo's pull-request checks from its own CI
|
|
766
|
+
definitions and runs them locally in the build worktree, so deterministic CI failures are fixed in build (at most 2 fix
|
|
767
|
+
iterations; a failure still unfixable after that is DEGRADED by default, see `mode`). It never contacts GitHub and never triggers a remote
|
|
768
|
+
workflow; remote CI still runs once, at merge. Everything works with no configuration.
|
|
769
|
+
|
|
770
|
+
```yaml
|
|
771
|
+
verification:
|
|
772
|
+
ci_local:
|
|
773
|
+
mode: advisory
|
|
774
|
+
exclude_jobs: ["gha:ci:e2e-*"]
|
|
775
|
+
include_jobs: []
|
|
776
|
+
extra_commands:
|
|
777
|
+
- "make lint-docs"
|
|
778
|
+
services: auto
|
|
779
|
+
budget_minutes: 15
|
|
780
|
+
max_parallel: 4
|
|
781
|
+
```
|
|
730
782
|
|
|
731
|
-
|
|
783
|
+
| Field | Type | Default | Description |
|
|
784
|
+
|-------|------|---------|-------------|
|
|
785
|
+
| `mode` | `advisory` \| `blocking` | `advisory` | `advisory`: a runnable job still failing after the 2 fix iterations is DEGRADED (`DEGRADED_CHECKS+=ci-local`), recorded in `FORGE:BUILDER` under `### Local CI unresolved` and surfaced to review; the build is not blocked. `blocking` (opt-in): the build is blocked with `ci-local: <jobs>`. Flip to `blocking` only once telemetry (`ci_local` events) shows a low false-fail rate on your repo |
|
|
786
|
+
| `exclude_jobs` | array of globs | `[]` | Job ids or names to drop from the plan (matched against `gha:<workflow>:<job>`, `gitlab:<job>`, and the job name) |
|
|
787
|
+
| `include_jobs` | array of globs | `[]` | When non-empty, only matching CI jobs run (an allowlist); `extra_commands` and the portability checks (when they apply) always run |
|
|
788
|
+
| `extra_commands` | array of strings or `{name, command}` | `[]` | Extra commands always added to the plan. `verification.commands` is not repeated here: V2 already runs it as the fast pre-step |
|
|
789
|
+
| `services` | `auto` \| `off` | `auto` | `auto` starts a job's service containers with docker (same image, env, ports), tearing them down afterwards; without docker, or with `off`, such jobs are deferred |
|
|
790
|
+
| `budget_minutes` | number | `15` | Overall wall-time budget; jobs not finished or started within it leave the run DEGRADED (never PASS, never FAIL). One invocation is capped at 9 minutes whatever this says (the Bash tool limit is 10): beyond that the run is a partial DEGRADED result and re-running resumes from the result cache (`FORGE_CI_LOCAL_MAX_INVOCATION_SECONDS`, default 540) |
|
|
791
|
+
| `max_parallel` | number | `4` | Jobs run in parallel by one run (also bounded machine-wide, see below) |
|
|
792
|
+
|
|
793
|
+
**Trusted config**: every field above is read from the main checkout's `forge.yaml` (for a plain checkout, the base ref's
|
|
794
|
+
copy), never from the PR's own tree, so a PR cannot exclude jobs from the gate that checks it.
|
|
795
|
+
|
|
796
|
+
**Where checks come from**: GitHub Actions (`.github/workflows`), GitLab CI, CircleCI, Azure Pipelines, Bitbucket Pipelines
|
|
797
|
+
and Buildkite definitions, read from the trusted base ref plus the PR's own edits (a PR can add a check but cannot delete
|
|
798
|
+
one). Only when the repo has NO CI definitions are its task runners used; when CI exists but its path filters leave no job
|
|
799
|
+
applicable the run executes nothing and records `no_applicable_jobs` (a docs-only change never triggers a full suite) (`package.json` scripts, Makefile, justfile,
|
|
800
|
+
Taskfile, tox/nox, pre-commit, pyproject tools, Cargo, Go, Maven, Gradle). Deterministic portability checks (Windows-invalid
|
|
801
|
+
filenames, case collisions, CRLF in scripts, bash 3.2 constructs and GNU-only flags on added lines, plus the repo's
|
|
802
|
+
`scripts/check-shell-portability.sh` when present) run only when the repo's CI definitions target macOS or Windows (a
|
|
803
|
+
`macos-*`/`windows-*` runner, matrix entry or executor); for a Linux-only repo they are skipped with a note, so correct
|
|
804
|
+
Linux-only code is never blocked on bash 3.2 rules.
|
|
805
|
+
|
|
806
|
+
**Deferred to CI** (listed in the `FORGE:BUILDER` comment under `### Deferred to CI`, never guessed): jobs that reference
|
|
807
|
+
`secrets.` or `environment:`, deploy/publish jobs, `pull_request_target`, schedule/dispatch-only workflows, unknown `uses:`
|
|
808
|
+
actions, orbs, plugins and templates, service containers without docker, macOS/Windows variants on another host, matrix
|
|
809
|
+
variants beyond the first host-compatible one, and jobs whose tools are not installed locally. A failed dependency install
|
|
810
|
+
(for example offline) defers the job instead of failing it, and so does a failure of a job whose pinned toolchain differs
|
|
811
|
+
from the host by a major version (`setup-node` 18 vs host 26: version drift, listed with its reason). A job timeout is
|
|
812
|
+
DEGRADED, never FAIL, and its clock starts when the job starts executing, not while it waits for a machine slot.
|
|
813
|
+
|
|
814
|
+
**Exact scoping** (runs only what the change can affect): job-level path filters from the CI definition (including
|
|
815
|
+
`dorny/paths-filter` outputs) gate whole jobs; per-file linters (shellcheck, eslint, prettier, ruff, black, flake8, ...) run
|
|
816
|
+
on the changed files only; tests use the ecosystem selector (`jest --findRelatedTests`, `vitest related`, `go` affected
|
|
817
|
+
packages with reverse dependencies, `cargo test -p` affected crates, Maven `-pl ... -amd`, `nx affected`, `turbo --filter`) or a
|
|
818
|
+
git-grep reference graph for file-list runners (pytest, mocha, `node --test`, bats, rspec, direct `*.test.*` invocations);
|
|
819
|
+
changed test files always run. **Fail-safe widening**: a lockfile, build or lint config, CI definition, unreferenced shared
|
|
820
|
+
fixture, an unknown file type, or an empty test selection for a changed source file widens the step to the full command (the
|
|
821
|
+
reason is reported, never silently narrowed). Short module names (`app/db.py`, `__init__.py`) are matched by their qualified
|
|
822
|
+
import forms. `${{ github.* }}` expressions in `run:` scripts are passed to the script as environment variables, never
|
|
823
|
+
substituted into the script text. CircleCI secondary containers run on a per-job docker network and publish only declared
|
|
824
|
+
(or well-known default) ports on 127.0.0.1, never `--network host`.
|
|
825
|
+
Passing results are cached by input hash under `~/.forge/ci-local/results` and reused across build iterations, lanes and
|
|
826
|
+
issues on the same base.
|
|
827
|
+
|
|
828
|
+
**Environment** (all optional): `FORGE_CI_LOCAL_SLOTS` (machine-wide concurrent jobs across all runs, default
|
|
829
|
+
`max(2, min(8, cpus/2))`; slots live under `~/.forge/ci-local.lock` and stale ones are reclaimed), `FORGE_CI_LOCAL_BUDGET_MINUTES`,
|
|
830
|
+
`FORGE_CI_LOCAL_MAX_PARALLEL`, `FORGE_CI_LOCAL_JOB_TIMEOUT` (seconds, default min(900, budget)), `FORGE_CI_LOCAL_SERVICES=off`,
|
|
831
|
+
`FORGE_CI_LOCAL_MAX_INVOCATION_SECONDS` (default 540), `FORGE_CI_LOCAL_HOME` (default `~/.forge`), `FORGE_CI_LOCAL_NO_CACHE=1`. Jobs run in a runner-like environment: an allowlist of
|
|
832
|
+
system and toolchain variables (PATH, HOME, locale, XDG_*, CARGO_*, GOPATH, JAVA_HOME, proxies, ...) plus `CI=true`; credentials and
|
|
833
|
+
ambient operator state (agent tokens, `FORGE_*`/`CLAUDE_*` settings, git hook variables) never reach them. Extra names can be
|
|
834
|
+
listed in `FORGE_CI_LOCAL_PASSTHROUGH` (comma separated). Requires `node`, `git`, and mikefarah `yq` v4 for YAML CI definitions (without `yq`
|
|
835
|
+
the gate is DEGRADED and reported in `verification_skipped`, never a silent pass).
|
|
836
|
+
|
|
837
|
+
**Commands that use this section**: `work-on` (validate V4.8)
|
|
732
838
|
|
|
733
839
|
---
|
|
734
840
|
|
package/docs/OUTCOME-CODES.md
CHANGED
|
@@ -18,20 +18,29 @@ An absent or unknown code is recorded as `unclassified`, with the raw reason kep
|
|
|
18
18
|
| `review.changes_requested` | Review asked for an in-PR fix and handed off to remediate | review | `REVIEW_RESULT` `status: NEXT`, `next: remediate` |
|
|
19
19
|
| `review.escalated` | Review escalated the PR to a human or remediation | review | PR/issue carries `needs-human` or `workflow:remediating` |
|
|
20
20
|
| `review.rereview_skipped` | Re-review was skipped | reserved | No structured evidence source yet |
|
|
21
|
-
| `
|
|
21
|
+
| `review.blocking_issue` | Review left the PR open at an unchanged head with a filed blocking ISSUE and handed off to remediate | review | Trusted `FORGE:NOTE_DISPOSITION` on the PR with `findings_filed` > 0 and `reviewed_sha` equal to the live head |
|
|
22
|
+
| `review.pr_open` | Review ended with the PR open and unmerged and no handoff signal | review | Live PR open, no `REVIEW_RESULT` remediate handoff, no `needs-human`/`workflow:remediating` label, no blocking disposition |
|
|
23
|
+
| `review.ci_pending_timeout` | CI was still pending at the merge gate after the engine's bounded wait (`engine.ci_pending_max_minutes`) ran out | engine | Review ended `REVIEW_RESULT` `status: BLOCKED` with a blocker containing `checks still pending` (or remediate held at its M7 re-gate on `CI pending`), and `scripts/wait-ci-green.sh` still reported pending when the budget expired |
|
|
24
|
+
| `build.acceptance_failed` | Newest acceptance gate marker is blocked | build | `FORGE:ACCEPTANCE_GATE:FAILED`/`:BLOCKED` or `FORGE:BUILD_BLOCKED` (`:DEGRADED` is a pass-class marker, not blocked) |
|
|
22
25
|
| `build.acceptance_malformed` | Acceptance spec was malformed | reserved | Indistinguishable from `build.acceptance_failed` by sentinel alone |
|
|
23
|
-
| `build.size_over_gate` |
|
|
24
|
-
| `build.cohesive` |
|
|
26
|
+
| `build.size_over_gate` | Retired (forge#3862): the old size gate routed a finished build to decompose; never emitted now, kept so historic runs still parse | reserved | A finished build is never routed to decompose |
|
|
27
|
+
| `build.cohesive` | Retired (forge#3862): the old size gate judged an oversized diff cohesive | reserved | No size gate remains |
|
|
25
28
|
| `remediate.no_marker` | Remediation ended without its completion marker | remediate | No trusted `FORGE:REMEDIATION:COMPLETE` |
|
|
26
29
|
| `remediate.deferred_live_run` | Remediation deferred to a live earlier run | remediate, engine | Trusted `FORGE:REMEDIATION_DEFERRED` record |
|
|
27
30
|
| `remediate.re_escalated` | Remediation re-escalated to a human | remediate | `**Re-gate outcome**: RE-ESCALATED` |
|
|
28
31
|
| `remediate.unfixable` | Remediation judged the findings unfixable | remediate | `**Re-gate outcome**: UNFIXABLE` |
|
|
32
|
+
| `remediate.inpr_requested` | Re-gate approved but requested one further in-PR fix round; the run continues, never `needs-human` | remediate | `**Re-gate outcome**: INPR-FIX-REQUESTED` |
|
|
29
33
|
| `ci.red` | CI failed | reserved | `REVIEW_RESULT` carries no CI field yet |
|
|
30
34
|
| `ci.timeout` | CI timed out | reserved | `REVIEW_RESULT` carries no CI field yet |
|
|
31
35
|
| `base.conflict` | Base-branch conflict blocked the PR | reserved | `REVIEW_RESULT` carries no conflict field yet |
|
|
32
36
|
| `rate_limited` | Session or usage limit outlasted the pause budget | engine | `err.usageLimit` / `err.resetAtEpochMs` |
|
|
33
37
|
| `engine.error` | Engine or tool failure (missing key/SDK, CLI crash, max turns, unresolved args, runner threw every attempt) | engine | `err.code` |
|
|
38
|
+
| `infra.disk_quota` | Disk full or quota exhausted (EDQUOT/ENOSPC, errno -28/-122); retryable infra failure, never `needs-human` | engine | Runner error text |
|
|
39
|
+
| `infra.cli_spawn_transient` | CLI spawn failed transiently before the child ran (ETIMEDOUT, EAGAIN, ENOMEM, EMFILE); retried with backoff on a separate bounded counter that does not consume a phase attempt | engine | `err.code` `CLI_SPAWN_TRANSIENT` |
|
|
40
|
+
| `cli.timeout` | CLI child ran and was killed at the wall-clock limit; a normal attempt (consumes one of `maxAttempts`), usage recorded when the CLI emitted any | engine | `err.code` `CLI_TIMEOUT` |
|
|
34
41
|
| `lease.held` | Another live agent holds the issue lease | engine | Foreign live lease on acquire |
|
|
42
|
+
| `dependency.open` | A trusted `**Depends on**: #N` dependency is still open, so the run was refused before any state write | engine | Trusted issue body `**Depends on**` line + the dependency issue's open state |
|
|
43
|
+
| `dependency.unreadable` | The issue body or a declared dependency could not be read, so the run was refused (fail closed) | engine | Failed or unparsable `gh api` read |
|
|
35
44
|
| `invalid` | Issue is invalid or in a divergent closed state | investigate, engine | `INVESTIGATION:INVALID` marker, or live snapshot |
|
|
36
45
|
| `decomposed` | Issue was decomposed into sub-issues | investigate, decompose | `FORGE:DECOMPOSED` or the decomposition assessment |
|
|
37
46
|
| `completed` | Non-failure terminal (merged, phase-complete, investigation) | engine | Terminal reason |
|
package/docs/RELEASE-NOTES.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Operator-facing notes for behavior changes that need action or awareness. Newest first.
|
|
4
4
|
|
|
5
|
+
## Review specs call the prepare and finalize scripts (#3841)
|
|
6
|
+
|
|
7
|
+
`commands/review-pr.md` is now under 100 KB: Phases 1-3B and 6 run through `scripts/review-prepare.sh` and `scripts/review-finalize.sh`, and `commands/review-pr-staging.md` reuses both (`review-prepare.sh --no-delta` in 0C, then `review-finalize.sh extract|classify|file-prepare|file-verify` with `--scope-files` and `--prior-prs` in Phase 7) instead of carrying its own template resolution, slicing, classifier and cap copies. A staging bundle whose API diff is too large is read from `origin/<base>...origin/<head>`. CI now runs `--fence-funcs` over `commands/review-pr*.md` and `--fence-state` over the staging spec. Engine: `close.detectOutcome` reports `merged` only for a CLOSED issue that also carries `workflow:merged` (a stale label on an open issue no longer counts).
|
|
8
|
+
|
|
9
|
+
Action: none. Staging reviews now stop with `REVIEW_RESULT: status: BLOCKED` when `review-prepare.sh` exits 3 (for example no template source, or an empty trusted `typescript.build` on the full-build lane) instead of continuing.
|
|
10
|
+
|
|
11
|
+
## Post-build scope check replaces the diff-size gate (#3862)
|
|
12
|
+
|
|
13
|
+
The line-count size gate is gone. A finished build is never discarded and never routed to decompose; `BUILD_RESULT: status: NEEDS_DECOMPOSE`, the `forge/wip/{N}` ref, the `COHESIVE` path and the engine's size-based decompose routing (including the investigate estimate net) are removed. Decomposition is a planning decision: `work-on:investigate` says `**YES**` only for separable work (independent modules or services, no shared files, no ordering), and the engine downgrades a YES without `INDEPENDENT_SEAMS >= 2` and a `DECOMPOSE_JUSTIFICATION`. Build B5.5 is now a scope check: it compares the diff with the architect plan (`### Scope Baseline`: `PLANNED_FILES:` and `ESTIMATED_DIFF_LINES:`), flags files outside the plan (opt-in trim into the batched follow-up), and measures `review_weight` (whitespace-insensitive, generated/vendored excluded, tests weighted, modules counted). A high `review_weight` extends review (more reviewers, per-file slices) instead of blocking; the Security agent and the Phase 2 floor stay unconditional. `FORGE:SIZE_OVERRIDE`, the loop guards and the batch exemption keep working. `run-issue --retry` reopens a stale `decomposed` run at build when a trusted `FORGE:DECOMPOSED:REVOKED` comment and a justified `FORGE:SIZE_OVERRIDE` are present. Telemetry `diff_size` events now carry estimate, actual, `review_weight`, scope outcome and review depth.
|
|
14
|
+
|
|
15
|
+
Action: replace `build.diff_size.*` with `build.scope.*` / `review.depth.*` (see docs/CONFIG.md, "Migration"). The old keys are read as deprecated aliases for one release; `threshold` no longer blocks anything. Stacked-PR splitting and default calibration are tracked in #3907.
|
|
16
|
+
|
|
5
17
|
## Gate scripts emit structured telemetry (#3834)
|
|
6
18
|
|
|
7
19
|
`diff-size.sh`, `review-acceptance-gate.sh`, `classify-finding.sh`, `wait-ci-green.sh`, `remediate-liveness.sh` and the build/review spec call sites (B5.5, B6.5, review panel) now append one JSONL record per decision to a trusted `<!-- FORGE:TELEMETRY v1 -->` comment on the issue or PR (shell emitter `scripts/lib/telemetry.sh`, Node twin `bin/engine/telemetry.mjs`). A failed write is buffered under `~/.forge/telemetry-buffer/` and flushed on the next emit. Catalogue and privacy note: `docs/TELEMETRY.md`.
|
package/docs/STATS.md
CHANGED
|
@@ -14,6 +14,10 @@ npx forgedock stats [--since <version|date>] [--repo <owner/repo>] [--json] [--r
|
|
|
14
14
|
versions at or above it; `--since 2026-10-01` keeps activity on or after the date. `--compact` prints one row per
|
|
15
15
|
version (the table embedded in the staging review summary). `--json` is machine-readable.
|
|
16
16
|
|
|
17
|
+
`--since` also bounds the GitHub crawl: only issues and comments updated on or after the window start are read (a version
|
|
18
|
+
is resolved to its tag time minus one day; an unknown tag falls back to a full crawl and says so on stderr). Progress is
|
|
19
|
+
printed to stderr while crawling, so `--json` stdout stays parseable.
|
|
20
|
+
|
|
17
21
|
## Sources
|
|
18
22
|
|
|
19
23
|
1. **GitHub (primary).** Issues, PRs and labels of the target repo, plus trusted `<!-- FORGE:TELEMETRY v1 -->` comments
|
|
@@ -21,8 +25,9 @@ version (the table embedded in the staging review summary). `--json` is machine-
|
|
|
21
25
|
`scripts/trusted-comments.sh`); `part=N` comments are concatenated and records are deduped on `eid`. All `gh` calls
|
|
22
26
|
are argv-based. Needs an authenticated `gh`.
|
|
23
27
|
2. **Local cache (optional).** `~/.forge/stats-cache/<owner>_<repo>/cache.json` (`FORGE_STATS_CACHE_DIR`) holds what was
|
|
24
|
-
fetched
|
|
25
|
-
|
|
28
|
+
fetched, an `updated_at` watermark (the next run only re-reads items updated since) and a `coveredFrom` floor (the
|
|
29
|
+
oldest window start crawled, so a later, wider `--since` crawls the uncovered gap instead of under-reporting). It is
|
|
30
|
+
safe to delete (the cost is one full re-fetch); a corrupt cache is treated as empty. `--refresh` ignores it, `--no-cache` never touches it.
|
|
26
31
|
3. **Local run logs (optional).** `~/.forge/runs/*.jsonl` (`FORGE_RUNS_DIR`) for runs that predate the engine's
|
|
27
32
|
`phase_attempt` / `run_terminal` records. A run already present on GitHub is not counted twice. Only runs whose
|
|
28
33
|
`RUN_START.repo` equals the target repo are used. `--no-local` skips them.
|
|
@@ -47,7 +52,8 @@ Each group shows a delta against the previous version of the same repo and is fl
|
|
|
47
52
|
They match `scripts/cost-report.sh`, so the two reports agree on the same logs: only finished runs count; hydrated runs
|
|
48
53
|
and runs with no usage events are excluded; tokens are cache-read tokens; `PHASE_ATTEMPT_USAGE` supersedes the cumulative
|
|
49
54
|
`PHASE_COMMIT` usage of the same phase. On GitHub a run is the set of `phase_attempt` records sharing a `run` id and it is
|
|
50
|
-
finished once a `run_terminal` record exists.
|
|
55
|
+
finished once a `run_terminal` record exists. On GitHub stats count one run per issue: the latest by start timestamp wins and earlier attempts (for example before a
|
|
56
|
+
`--retry`) are discarded, as in `scripts/cost-report.sh`. Wall time covers the winning engine process only.
|
|
51
57
|
|
|
52
58
|
## Versions and n/a
|
|
53
59
|
|
|
@@ -58,5 +64,7 @@ instead of failing.
|
|
|
58
64
|
|
|
59
65
|
## Limits
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
Comments are read from the repo-wide `issues/comments?since=` endpoint (a few paginated calls); only issues with a trusted
|
|
68
|
+
telemetry comment in the window get their full comment list re-read (6 in parallel, backing off when the API rate limit is
|
|
69
|
+
low). A run with no `--since` and no cache still walks the whole history once. Telemetry comments are edited in place, which
|
|
70
|
+
the comment window catches; if a cached item still looks stale, run with `--refresh`.
|
package/docs/TELEMETRY.md
CHANGED
|
@@ -31,18 +31,21 @@ is dropped. Payload keys are `[A-Za-z0-9_]+` and cannot shadow the shared keys;
|
|
|
31
31
|
|
|
32
32
|
| Event | Emitted by | Payload |
|
|
33
33
|
|-------|-----------|---------|
|
|
34
|
-
| `diff_size` | `scripts/diff-size.sh`; `work-on/build.md` B5.5 (final result) | `lines`, `excluded`, `
|
|
34
|
+
| `diff_size` | `scripts/diff-size.sh`; `work-on/build.md` B5.5 (final result) | `lines`, `excluded`, `ws_lines`, `review_weight`, `modules`, `result`; the script emits the legacy `threshold` and `OK` \| `OVER` \| `COHESIVE`, the B5.5 call site emits the final `result` = `OK` \| `EXTENDED_REVIEW` \| `SCOPE_FLAGGED` \| `OVERRIDDEN` plus `estimate` (plan), `actual` (whitespace-insensitive lines), `scope` = `ok` \| `out_of_plan` \| `no_baseline` and `review_depth` = `STANDARD` \| `EXTENDED` (calibration inputs, forge#3862) |
|
|
35
35
|
| `acceptance_gate` | `work-on/build.md` B6.5 (`phase=build`); `scripts/review-acceptance-gate.sh` (`phase=review`) | `phase`, `verified`, `failed`, `malformed`, `deferred` |
|
|
36
|
-
| `finding_disposition` | `scripts/classify-finding.sh`, one per finding | `severity`, `confidence`, `agent`, `disposition` = `ISSUE` \| `NOTE` \| `INPR_FIX
|
|
36
|
+
| `finding_disposition` | `scripts/classify-finding.sh`, one per finding | `severity`, `confidence`, `agent`, `scope` = `introduced` \| `pre-existing` \| `unknown` (from the diff; `unknown` when not supplied), `disposition` = `ISSUE` \| `NOTE` \| `INPR_FIX` (also MEDIUM LIKELY when `scope=introduced`), `reason` |
|
|
37
37
|
| `review_panel` | `review-pr.md` Phase 4 | `agents_selected`, `agents_completed`, `mode` = `FULL` \| `DELTA` \| `BASE_SYNC_ONLY` |
|
|
38
|
-
| `ci_gate` | `scripts/wait-ci-green.sh` | `result` = `PASS` \| `FAIL` \| `ERROR` \| `TIMEOUT
|
|
38
|
+
| `ci_gate` | `scripts/wait-ci-green.sh` | `result` = `PASS` \| `FAIL` \| `ERROR` \| `TIMEOUT` \| `RATE_LIMITED` (rc 4: GitHub throttled or briefly unavailable for the whole window; retryable, never a verdict), `rc`, `checks` |
|
|
39
|
+
| `ci_pending_wait` | the engine (`bin/engine/telemetry-seam.mjs`, one per finished CI-pending wait at the merge gate; `phase` = `review` \| `remediate`) | `pr`, `minutes_waited`, `result` = `green` \| `red` \| `error` \| `timeout`, `polls` (CI reads made), `rate_limit_sleeps` (polls that were rate-limited plus sleeps until the budget reset) |
|
|
40
|
+
| `ci_local` | `scripts/ci-local.sh run` (validate V4.8), one per run | `result` = `PASS` \| `FAIL` \| `DEGRADED`, `jobs_run`, `jobs_deferred`, `jobs_failed`, `duration_ms`, `fix_iterations` (0 first run, 1 and 2 after fixes), `checks`, `cache_hits`, `narrowed` (checks run on a subset or skipped for no impact), `widened` (checks widened to the full command), `selected` / `total` (summed scope) |
|
|
41
|
+
| `ci_local_check` | `scripts/ci-local.sh run`, one per scoped, widened or executed check (at most 20 per run) | `job`, `selector`, `selected`, `total`, `cache` = `hit` \| `miss` \| `na`, `duration_ms`, `widen` (reason or `none`). A remote-CI failure at merge on a check recorded here as narrowed is a selector bug |
|
|
39
42
|
| `liveness` | `scripts/remediate-liveness.sh` | `decision` = `PROCEED` \| `DEFER` \| `EXHAUSTED`, `why` |
|
|
40
43
|
| `phase_attempt` | the engine (`bin/engine/telemetry-seam.mjs`, one per phase attempt; `phase` = the phase id) | `attempt`, `model`, `turns`, `duration_ms`, `cost_usd`, `input_tokens`, `output_tokens`, `cache_read_tokens`, `cache_creation_tokens` (any value the runner did not report is omitted) |
|
|
41
44
|
| `run_terminal` | the engine (`terminate()`, one per run segment) | `reason` (terminal reason), `code` (see `docs/OUTCOME-CODES.md`), `wall_ms` (this engine process, start to terminal) |
|
|
42
45
|
|
|
43
46
|
The engine records carry `run` = `r_<issue>_<start epoch seconds>` (one per `forgedock run-issue` process) so `forgedock stats`
|
|
44
47
|
(`docs/STATS.md`) can group the attempts of a run and tell a finished run from an unfinished one. They are the GitHub-side
|
|
45
|
-
source for Cost, Time and outcome codes: without them a fresh machine can rebuild only the gate and review sections.
|
|
48
|
+
source for Cost, Time and outcome codes (`run` is per process, but stats count one run per issue, last wins): without them a fresh machine can rebuild only the gate and review sections.
|
|
46
49
|
|
|
47
50
|
The classifier and the scripts emit only when a destination is known: callers export `FORGE_ISSUE` and/or `FORGE_PR`
|
|
48
51
|
(`review-pr.md` exports them before classifying; the other scripts default `FORGE_REPO`/`FORGE_ISSUE`/`FORGE_PR` from
|
package/docs/WORK-ON-RUNTIME.md
CHANGED
|
@@ -51,6 +51,12 @@ Only the router may invoke them, so they always run at router layer + 1 (L2 unde
|
|
|
51
51
|
L1 solo). When another phase needs one, it returns a handoff (`status: NEXT`, `next: <phase>`) and
|
|
52
52
|
the router invokes it. Concretely: review returns `next: remediate` instead of calling remediation.
|
|
53
53
|
|
|
54
|
+
The same rule gives the `PHASE_TRAIL_FAILED` re-dispatch a single owner: `work-on:review` Phase R4 alone
|
|
55
|
+
re-runs the missing phases. `/review-pr` (Phase 8, via `scripts/review-finalize.sh merge-gate`) and
|
|
56
|
+
`work-on:remediate` (Phase M6) only report the status with its `MISSING:` lines and never re-dispatch. When
|
|
57
|
+
`/review-pr` finds neither `Task` nor `Agent` ("neither tool available"), the usual cause is this depth limit
|
|
58
|
+
(F1/F5): the review was invoked from inside a fork instead of by the router, so `Agent` was withheld.
|
|
59
|
+
|
|
54
60
|
**R2 — Non-dispatching forks may nest.** Phases and children that never spawn sub-agents may invoke
|
|
55
61
|
further forks at any depth (F5): build → context/architect/implement/validate → quality-gate,
|
|
56
62
|
review → quality-gate / missing-phase re-dispatch, remediate → close. Forking them keeps each
|
|
@@ -104,6 +110,10 @@ phase hosting `/review-pr` cannot wait on a notification. Therefore:
|
|
|
104
110
|
`/orchestrate` Step 4B item 6.4 dispatches a worker that runs `work-on <pr> --remediate`: worker L1,
|
|
105
111
|
remediate L2, reviewers L3, same as the table.
|
|
106
112
|
|
|
113
|
+
### Pre-handoff base sync and union registries (forge#3969)
|
|
114
|
+
|
|
115
|
+
`work-on:review` merges `origin/<base>` into the branch before the first push, so a PR is never born stale and a conflict is resolved while the build context is warm. Hand-maintained append-only registries (test lists, cron targets) are the usual conflict source; `scripts/detect-append-registries.sh` ranks them from history and proves a `.gitattributes` `merge=union` entry safe (`--check-union`). Union is allowed only for line-independent files, never YAML/JSON maps or ordered lists (see `.gitattributes`). `scripts/review-delta.sh` still treats a hand-resolved merge as not `BASE_SYNC_ONLY`, but accepts a merge whose tree is exactly what a mechanical `git merge-tree` of the same parents produces (a clean or union merge), so union registries take the no-panel fast path.
|
|
116
|
+
|
|
107
117
|
## 5. Router routing
|
|
108
118
|
|
|
109
119
|
| Phase result | Router action |
|
|
@@ -130,3 +140,17 @@ remediate L2, reviewers L3, same as the table.
|
|
|
130
140
|
## 8. Outcome codes
|
|
131
141
|
|
|
132
142
|
Every `PHASE_FAILED`, blocked `PHASE_COMMIT` and `RUN_TERMINAL` run-log event carries a machine-readable `code` from a closed, versioned enum (`bin/engine/outcome-codes.mjs`), and `FORGE:STATE` records the causing code as `outcomeCode`. Codes come from structured evidence only, never from the prose `detail`; an unmapped outcome is `unclassified` with the raw reason kept. See `docs/OUTCOME-CODES.md`.
|
|
143
|
+
|
|
144
|
+
## Remediation re-gate outcomes and spawn retries (forge#4021)
|
|
145
|
+
|
|
146
|
+
The `**Re-gate outcome**` token in a `FORGE:REMEDIATION:COMPLETE` trail is a closed vocabulary the engine parses:
|
|
147
|
+
`AUTO-LANDED`, `HELD-AWAITING-MERGE`, `RE-ESCALATED`, `UNFIXABLE`, `REREVIEW-REQUIRED` and `INPR-FIX-REQUESTED`.
|
|
148
|
+
`INPR-FIX-REQUESTED` (with `**INPR round**: N`) means the re-review approved but posted a `FORGE:INPR_FIX` work
|
|
149
|
+
order. The engine commits `remediate` with `outputs.remediation = "inpr-fix"`, re-arms it once, and re-reviews. The
|
|
150
|
+
`INPR_REMEDIATION` marker count bounds it: a second request escalates to `needs-human`.
|
|
151
|
+
|
|
152
|
+
A CLI spawn failure (`ETIMEDOUT`, `EAGAIN`, `ENOMEM`, `EMFILE`) carries `code = "CLI_SPAWN_TRANSIENT"`. The engine
|
|
153
|
+
retries it after a 10s/30s/60s backoff on a separate counter (max 3) that does not consume a phase attempt; once
|
|
154
|
+
spent, normal attempt accounting applies. `ETIMEDOUT` (a `FORGEDOCK_CLI_TIMEOUT_MS` kill) is in this class by design,
|
|
155
|
+
so a phase that times out every time can run up to `maxAttempts + 3` full-length CLI sessions; lower the timeout or
|
|
156
|
+
max attempts if that cost is unacceptable.
|
|
@@ -535,8 +535,8 @@ needed.
|
|
|
535
535
|
|
|
536
536
|
| Marker | Category | Meaning |
|
|
537
537
|
|--------|----------|---------|
|
|
538
|
-
| `<!-- FORGE:DIFF_SIZE -->` | Lifecycle (no completion sentinel) |
|
|
539
|
-
| `<!-- FORGE:SIZE_OVERRIDE -->` | Control | Posted by a human to
|
|
538
|
+
| `<!-- FORGE:DIFF_SIZE -->` | Lifecycle (no completion sentinel) | Record of the build's post-build scope check (B5.5): `diff_lines`, `excluded_lines`, `ws_lines`, `review_weight`, `modules`, `estimated_diff_lines`, `scope: ok \| out_of_plan \| no_baseline`, `review_depth: STANDARD \| EXTENDED`, `result: OK \| EXTENDED_REVIEW \| SCOPE_FLAGGED \| OVERRIDDEN` plus `out_of_plan:` / `overrun:` / `trimmed:` notes. Never a size gate and never a decompose signal (a finished build is not routed to decompose). Refreshed in place; readers take the latest trusted comment. Consumed by review-pr (review depth) and close (records `diff_lines` in `FORGE:TRAJECTORY`). Older records carrying `threshold` / `result: OVER \| COHESIVE` still parse |
|
|
539
|
+
| `<!-- FORGE:SIZE_OVERRIDE -->` | Control | Posted by a human to suppress the scope-check flag (out-of-plan files or estimate overrun) and the decompose loop guard. Valid only from a trusted author and with a non-empty justification on the line after the marker |
|
|
540
540
|
|
|
541
541
|
Both lookups are anchored at the start of the comment body and trust-filtered
|
|
542
542
|
(`scripts/trusted-comments.sh`); an unreadable lookup fails closed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgedock",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.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"
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"README.md"
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
|
-
"test": "node --test \"bin/tests/**/*.test.mjs\" \"bin/engine/**/*.test.mjs\" \"bin/stats/**/*.test.mjs\" \"packages/protocol/test/**/*.test.mjs\"",
|
|
51
|
+
"test": "bash scripts/with-run-tmpdir.sh node --test \"bin/tests/**/*.test.mjs\" \"bin/engine/**/*.test.mjs\" \"bin/stats/**/*.test.mjs\" \"packages/protocol/test/**/*.test.mjs\"",
|
|
52
52
|
"test:sh": "bash scripts/run-shell-tests.sh",
|
|
53
53
|
"docs:dev": "vitepress dev docs/site",
|
|
54
54
|
"docs:build": "vitepress build docs/site",
|
|
@@ -57,8 +57,8 @@ export const PHASE_IDS = ['investigate', 'decompose', 'build', 'review', 'remedi
|
|
|
57
57
|
* investigate.md emits it as the HTML comment `<!-- DECOMPOSE:YES -->`.
|
|
58
58
|
* @property {string} [notDecomposedMarker] - (investigate only) the explicit not-decomposed
|
|
59
59
|
* verdict, emitted as `<!-- DECOMPOSE:NO -->`.
|
|
60
|
-
* @property {string} [sizeGateMarker] - (build only) bare annotation-opener of the diff
|
|
61
|
-
* @property {string} [sizeOverrideMarker] - (build only) bare annotation-opener of the
|
|
60
|
+
* @property {string} [sizeGateMarker] - (build only) bare annotation-opener of the scope-check record (diff size, review weight, scope outcome).
|
|
61
|
+
* @property {string} [sizeOverrideMarker] - (build only) bare annotation-opener of the scope-check override.
|
|
62
62
|
* @property {string} [investigationMarker] - (build only) bare marker of the durable
|
|
63
63
|
* investigation-deliverables comment, posted as `<!-- FORGE:INVESTIGATION:DELIVERABLES -->`.
|
|
64
64
|
* @property {string} [partialMarker] - sentinel for an interrupted/partial annotation.
|
|
@@ -108,9 +108,9 @@ export const PHASE_MARKERS = {
|
|
|
108
108
|
build: {
|
|
109
109
|
completionMarker: RESERVED_TYPES.BUILDER.completionSentinel, // 'FORGE:BUILDER:COMPLETE'
|
|
110
110
|
header: 'FORGE:BUILDER',
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
111
|
+
// Scope-check (B5.5) records. Neither is a completion sentinel and neither routes anywhere
|
|
112
|
+
// (forge#3862): a finished build is never sent to decompose. DIFF_SIZE is the record of the
|
|
113
|
+
// scope-check outcome; SIZE_OVERRIDE suppresses the scope flag and the decompose loop guard.
|
|
114
114
|
sizeGateMarker: `FORGE:${RESERVED_TYPES.DIFF_SIZE.type}`, // 'FORGE:DIFF_SIZE'
|
|
115
115
|
sizeOverrideMarker: `FORGE:${RESERVED_TYPES.SIZE_OVERRIDE.type}`, // 'FORGE:SIZE_OVERRIDE'
|
|
116
116
|
// Investigation exit (implement.md I2): no code, no PR, so no FORGE:BUILDER:COMPLETE.
|
|
@@ -109,7 +109,7 @@ export const RESERVED_TYPES = {
|
|
|
109
109
|
partialSentinel: null,
|
|
110
110
|
inlineValue: false,
|
|
111
111
|
requiredFields: [],
|
|
112
|
-
reGateOutcomeValues: ['AUTO-LANDED', 'HELD-AWAITING-MERGE', 'RE-ESCALATED', 'UNFIXABLE'],
|
|
112
|
+
reGateOutcomeValues: ['AUTO-LANDED', 'HELD-AWAITING-MERGE', 'RE-ESCALATED', 'UNFIXABLE', 'INPR-FIX-REQUESTED', 'REREVIEW-REQUIRED'],
|
|
113
113
|
},
|
|
114
114
|
TRAJECTORY: {
|
|
115
115
|
type: 'TRAJECTORY',
|