mandrel 2.36.0 → 2.38.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/.agents/README.md +16 -16
- package/.agents/agents/plan-critic.md +1 -1
- package/.agents/docs/SDLC.md +40 -40
- package/.agents/docs/configuration.md +41 -16
- package/.agents/docs/execution-reference.md +3 -3
- package/.agents/docs/quality-gates.md +2 -2
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +5 -5
- package/.agents/rules/changelog-style.md +3 -3
- package/.agents/rules/ci-remediation.md +1 -1
- package/.agents/rules/git-conventions-reference.md +4 -4
- package/.agents/rules/testing-standards.md +56 -0
- package/.agents/schemas/agentrc.schema.json +9 -9
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/apply-quality-bootstrap.js +1 -1
- package/.agents/scripts/audit-to-stories.js +2 -2
- package/.agents/scripts/boot-sweep.js +7 -1
- package/.agents/scripts/check-context-budget.js +195 -103
- package/.agents/scripts/check-cyclomatic.js +112 -42
- package/.agents/scripts/check-doc-links.js +32 -4
- package/.agents/scripts/check-generated-validator.js +202 -0
- package/.agents/scripts/check-lifecycle-lint.js +2 -68
- package/.agents/scripts/check-schema-references.js +2 -28
- package/.agents/scripts/check-test-temp-hygiene.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +1 -1
- package/.agents/scripts/deliver-light.js +10 -10
- package/.agents/scripts/deliver-recover.js +1 -1
- package/.agents/scripts/drain-pending-cleanup.js +19 -2
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +1 -1
- package/.agents/scripts/lib/baselines/components.js +32 -2
- package/.agents/scripts/lib/baselines/env-overrides.js +1 -1
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-budget.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bdd-step-index.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -2
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/install-ledger.js +1 -1
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/prompt.js +1 -1
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +2 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +1 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +1 -1
- package/.agents/scripts/lib/cli-args.js +1 -3
- package/.agents/scripts/lib/close-validation/gates.js +1 -1
- package/.agents/scripts/lib/close-validation/process.js +1 -1
- package/.agents/scripts/lib/command-header.js +1 -1
- package/.agents/scripts/lib/config/delivery-routing.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +3 -3
- package/.agents/scripts/lib/config-settings-schema-quality.js +6 -6
- package/.agents/scripts/lib/config-settings-schema.js +49 -9
- package/.agents/scripts/lib/coverage-baseline.js +2 -2
- package/.agents/scripts/lib/cpu-pool.js +90 -10
- package/.agents/scripts/lib/crap-utils.js +6 -2
- package/.agents/scripts/lib/cyclomatic-ceiling.js +28 -6
- package/.agents/scripts/lib/cyclomatic-scope.js +144 -0
- package/.agents/scripts/lib/dependency-version.js +86 -0
- package/.agents/scripts/lib/duplicate-search.js +2 -2
- package/.agents/scripts/lib/errors/index.js +1 -1
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +2 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +5 -5
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +2 -2
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +12 -12
- package/.agents/scripts/lib/findings/route-finding.js +2 -2
- package/.agents/scripts/lib/generated/agentrc-validator.js +16 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/install-cmd-parser.js +1 -1
- package/.agents/scripts/lib/knip-entry-sync.js +1 -57
- package/.agents/scripts/lib/label-constants.js +2 -2
- package/.agents/scripts/lib/label-taxonomy.js +1 -1
- package/.agents/scripts/lib/maintainability-utils.js +108 -10
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/onboard/init-tail.js +4 -4
- package/.agents/scripts/lib/onboard/scaffold-docs.js +2 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +4 -4
- package/.agents/scripts/lib/orchestration/complexity-gate.js +5 -5
- package/.agents/scripts/lib/orchestration/deliver-recover.js +3 -3
- package/.agents/scripts/lib/orchestration/docs-digest.js +3 -3
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +2 -2
- package/.agents/scripts/lib/orchestration/light-backstop.js +1 -1
- package/.agents/scripts/lib/orchestration/light-escalation.js +6 -6
- package/.agents/scripts/lib/orchestration/light-suitability.js +19 -19
- package/.agents/scripts/lib/orchestration/plan-context.js +4 -4
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-metrics.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +6 -6
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +86 -25
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +6 -6
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +7 -7
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -1
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +3 -3
- package/.agents/scripts/lib/orchestration/pr-watch.js +1 -30
- package/.agents/scripts/lib/orchestration/reassert-status-column.js +2 -2
- package/.agents/scripts/lib/orchestration/remote-verifier.js +2 -2
- package/.agents/scripts/lib/orchestration/resolve-stories.js +4 -4
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +3 -3
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/native.js +9 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -3
- package/.agents/scripts/lib/orchestration/run-epilogue.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +5 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +52 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +3 -3
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +2 -5
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +14 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +34 -9
- package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +6 -4
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +3 -3
- package/.agents/scripts/lib/qa/qa-session.js +1 -1
- package/.agents/scripts/lib/runtime-deps/manifest.js +1 -7
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +3 -58
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +284 -25
- package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
- package/.agents/scripts/lib/source-text/strip-js-comments.js +115 -0
- package/.agents/scripts/lib/story-adjacency.js +2 -2
- package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
- package/.agents/scripts/lib/test-isolate/runner.js +3 -3
- package/.agents/scripts/lib/test-runner-contract.js +14 -6
- package/.agents/scripts/lib/test-tiers.js +135 -18
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/transpile.js +16 -3
- package/.agents/scripts/lib/util/concurrent-map.js +1 -1
- package/.agents/scripts/lib/util/parse-id-list.js +1 -1
- package/.agents/scripts/lib/wave-runner/live-probe.js +4 -4
- package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
- package/.agents/scripts/lib/workflow-closure.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +170 -32
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +2 -5
- package/.agents/scripts/lint-issue-body.js +3 -3
- package/.agents/scripts/plan-context.js +4 -4
- package/.agents/scripts/plan-critics.js +2 -2
- package/.agents/scripts/plan-persist.js +2 -2
- package/.agents/scripts/plan-run-epilogue.js +2 -2
- package/.agents/scripts/pr-watch-with-update.js +305 -137
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/tickets.js +114 -21
- package/.agents/scripts/quality-preview.js +162 -70
- package/.agents/scripts/resolve-stories.js +3 -3
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/run-lint.js +1 -1
- package/.agents/scripts/run-test-profile.js +8 -5
- package/.agents/scripts/run-tests.js +79 -14
- package/.agents/scripts/single-story-close.js +2 -4
- package/.agents/scripts/single-story-init.js +10 -6
- package/.agents/scripts/stories-wave-tick.js +8 -8
- package/.agents/skills/core/idea-refinement/SKILL.md +5 -5
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/skills/skills.index.json +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -1
- package/.agents/workflows/audit-data-model.md +1 -1
- package/.agents/workflows/audit-documentation.md +2 -2
- package/.agents/workflows/audit-to-stories.md +11 -11
- package/.agents/workflows/git-deliver.md +4 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/audit-lens-core.md +1 -1
- package/.agents/workflows/helpers/code-review.md +4 -4
- package/.agents/workflows/helpers/deliver-light.md +26 -26
- package/.agents/workflows/helpers/deliver-reference.md +15 -7
- package/.agents/workflows/helpers/deliver-story-reference.md +9 -4
- package/.agents/workflows/helpers/deliver-story.md +6 -7
- package/.agents/workflows/helpers/diagnose.md +2 -2
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/plan-reference.md +9 -9
- package/.agents/workflows/helpers/qa-core.md +8 -8
- package/.agents/workflows/helpers/worktree-lifecycle.md +6 -6
- package/.agents/workflows/{deliver.md → mandrel-deliver.md} +15 -19
- package/.agents/workflows/{plan.md → mandrel-plan.md} +9 -9
- package/.agents/workflows/memory-consolidate.md +4 -4
- package/.agents/workflows/prototype.md +3 -3
- package/.agents/workflows/qa-assist.md +12 -12
- package/.agents/workflows/qa-explore.md +6 -6
- package/.agents/workflows/qa-run.md +1 -1
- package/README.md +7 -7
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/doctor.js +1 -1
- package/lib/cli/init.js +1 -1
- package/lib/cli/migrate.js +100 -94
- package/lib/migrations/helpers/retire-agentrc-key.js +200 -0
- package/lib/migrations/index.js +32 -33
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +21 -80
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +15 -87
- package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +13 -71
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +1 -1
- package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +13 -101
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +13 -100
- package/package.json +7 -3
- package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +0 -32
- package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +0 -37
- package/.agents/scripts/lib/cli/parse-numeric.js +0 -60
- package/.agents/scripts/lib/close-validation/telemetry.js +0 -79
- package/.agents/scripts/lib/orchestration/label-transitions.js +0 -44
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +0 -147
- package/.agents/scripts/lib/orchestration/phase-runner.js +0 -88
- package/.agents/scripts/lib/orchestration/recut.js +0 -56
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This rule governs the shape of per-release entries in the project CHANGELOG
|
|
4
4
|
(typically `docs/CHANGELOG.md` or `CHANGELOG.md`). It applies whenever a
|
|
5
5
|
release entry is authored or edited — most commonly inside Story #N's
|
|
6
|
-
docs sweep before `/deliver` opens the release PR.
|
|
6
|
+
docs sweep before `/mandrel-deliver` opens the release PR.
|
|
7
7
|
|
|
8
8
|
The contract is **guidance-tier**: no automated gate fails a close when an
|
|
9
9
|
entry drifts off-template. It still binds every author.
|
|
@@ -152,12 +152,12 @@ worktree cleanup.
|
|
|
152
152
|
- **Shared-store worktrees.** Per-story worktrees link a shared
|
|
153
153
|
`node_modules` store, so parallel waves no longer duplicate installs
|
|
154
154
|
or leave residue that blocks reap.
|
|
155
|
-
- **`/deliver` auto-invokes pre-merge gates** (code review, retro)
|
|
155
|
+
- **`/mandrel-deliver` auto-invokes pre-merge gates** (code review, retro)
|
|
156
156
|
inline. `--skip-code-review` is available as an override.
|
|
157
157
|
- **Closure sweep covers Epic Health tickets** in addition to PRD and
|
|
158
158
|
Tech Spec tickets.
|
|
159
159
|
- **Stale-lock sweep** on the shared `.git/` directory runs at
|
|
160
|
-
`/deliver` start, clearing lock files left behind by interrupted
|
|
160
|
+
`/mandrel-deliver` start, clearing lock files left behind by interrupted
|
|
161
161
|
operations.
|
|
162
162
|
```
|
|
163
163
|
|
|
@@ -31,7 +31,7 @@ exactly one of two ways, and no others:
|
|
|
31
31
|
delivery's scope — a pre-existing flaky test, a runner/infra weakness, a
|
|
32
32
|
framework-level environment gap. Open the issue with the `meta::framework-gap`
|
|
33
33
|
label (see [`git-conventions.md`](git-conventions.md)) carrying **the run
|
|
34
|
-
link and the failure signature** so a later `/plan` Phase 0 sweep can act on
|
|
34
|
+
link and the failure signature** so a later `/mandrel-plan` Phase 0 sweep can act on
|
|
35
35
|
it. Remediate this delivery only if the pre-existing defect is genuinely
|
|
36
36
|
blocking it.
|
|
37
37
|
|
|
@@ -95,7 +95,7 @@ signature is worth naming:
|
|
|
95
95
|
**Invariant (stated in the core): the delivering flow owns tidying the local
|
|
96
96
|
checkout — reaping its own merged refs and fast-forwarding the base branch.
|
|
97
97
|
`/git-cleanup` is a recovery tool, not a routine chore.** The outcome every
|
|
98
|
-
delivering flow (`/deliver`, `/git-deliver`) guarantees, with the mechanics
|
|
98
|
+
delivering flow (`/mandrel-deliver`, `/git-deliver`) guarantees, with the mechanics
|
|
99
99
|
owned by `boot-sweep.js` / `git-cleanup.js`:
|
|
100
100
|
|
|
101
101
|
- **`main` is fast-forwarded** by the flow itself in its cleanup phase, so the
|
|
@@ -104,7 +104,7 @@ owned by `boot-sweep.js` / `git-cleanup.js`:
|
|
|
104
104
|
- **Merged local refs are reaped** at the next workflow boot's protected sweep
|
|
105
105
|
(`boot-sweep.js`) — every local branch whose PR is already merged, skipping
|
|
106
106
|
any candidate with unpushed work, a dirty worktree, or a still-open parent
|
|
107
|
-
ticket. `/plan` and `/git-deliver` widen the sweep's `--include` scope beyond
|
|
107
|
+
ticket. `/mandrel-plan` and `/git-deliver` widen the sweep's `--include` scope beyond
|
|
108
108
|
the default `story-*` to their own branch namespaces at their boot call site.
|
|
109
109
|
- **Content-merged branches are report-only.** A branch detected only via the
|
|
110
110
|
weaker content-equivalence signal (`detectedBy: 'content-merged'` — content
|
|
@@ -149,13 +149,13 @@ different hazards:
|
|
|
149
149
|
- **Recognition signature**: close aborts naming stray files in the main
|
|
150
150
|
checkout that intersect the Story's diff. **Resolution**: relocate those
|
|
151
151
|
edits under the worktree, restore the main checkout
|
|
152
|
-
(`git -C <main-repo> checkout -- <files>`), then re-run `/deliver <storyId>`.
|
|
152
|
+
(`git -C <main-repo> checkout -- <files>`), then re-run `/mandrel-deliver <storyId>`.
|
|
153
153
|
Never `git reset --hard` or `git checkout --force` to clear the way.
|
|
154
154
|
|
|
155
155
|
## Meta Labels (Retrospective Signal Routing)
|
|
156
156
|
|
|
157
157
|
Two `meta::*` labels route retrospective signals into durable substrates so
|
|
158
|
-
the `/plan` Phase 0 fetcher (see
|
|
158
|
+
the `/mandrel-plan` Phase 0 fetcher (see
|
|
159
159
|
[`prior-feedback-fetcher.js`](../scripts/lib/feedback-loop/prior-feedback-fetcher.js))
|
|
160
160
|
can surface open feedback issues to the planner. Both labels live in
|
|
161
161
|
[`label-constants.js`](../scripts/lib/label-constants.js) under the
|
|
@@ -79,6 +79,13 @@ of the pyramid.
|
|
|
79
79
|
- **Location.** `tests/features/**/*.feature` with step definitions in
|
|
80
80
|
`tests/steps/**` (or the project's equivalent). The companion skill is
|
|
81
81
|
[`stack/qa/playwright-bdd`](../skills/stack/qa/playwright-bdd/SKILL.md).
|
|
82
|
+
- **Run tier.** This tier MUST NOT ride inside the default suite. It is slow
|
|
83
|
+
by construction — real installs, real browsers, real stacks — and the
|
|
84
|
+
default suite is what a pre-push hook and every local iteration pay for.
|
|
85
|
+
Give it its own runner tier and its own CI job, so its cost is charged to
|
|
86
|
+
the surface whose signal it is. In this repository that is `tests/e2e/**`,
|
|
87
|
+
the `e2e` tier (`npm run test:e2e`), and the per-PR `e2e` job; the coverage
|
|
88
|
+
run still measures those files, so nothing leaves the measured surface.
|
|
82
89
|
|
|
83
90
|
## Assertion Placement Rule {#assertion-placement}
|
|
84
91
|
|
|
@@ -236,3 +243,52 @@ Hypothesis keeps a failure DB, proptest writes `proptest-regressions/` — pin o
|
|
|
236
243
|
commit whichever the stack provides. Once shrinking surfaces a minimal
|
|
237
244
|
counterexample, **add it as an example-based regression test** alongside the
|
|
238
245
|
property: the property guards the domain, the pinned example guards the bug.
|
|
246
|
+
|
|
247
|
+
## The suite's child-process budget
|
|
248
|
+
|
|
249
|
+
`npm test` forks one process per test file, and the spawns those leaves make
|
|
250
|
+
dominate the suite's system time. Two instruments and one rule keep that
|
|
251
|
+
budget visible and honest (Story #5121).
|
|
252
|
+
|
|
253
|
+
### Measure it with `npm run test:census`
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm run test:census # writes temp/census.json
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`tests/fixtures/spawn-census.cjs` is a `--require` preload that counts every
|
|
260
|
+
`child_process` call per binary, aggregates across all ~700 processes, and
|
|
261
|
+
reports `nodeInSuite` (node children spawned *by test files*, excluding the
|
|
262
|
+
runner's own fan-out), `git`, `gh`, `npm`, and any standalone
|
|
263
|
+
`git config user.*` spawns. Read the numbers from the census rather than
|
|
264
|
+
re-deriving them; two audits hand-rolled this measurement and lost it both
|
|
265
|
+
times with the gitignored temp tree.
|
|
266
|
+
|
|
267
|
+
The script interpolates `$PWD` deliberately. A **relative** `--require` path is
|
|
268
|
+
inherited by children that run with a different `cwd`, where it fails to
|
|
269
|
+
resolve and kills the child before it runs a line — measured as 15 spurious
|
|
270
|
+
failures in one file.
|
|
271
|
+
|
|
272
|
+
### Build a fixture repo once, then copy it
|
|
273
|
+
|
|
274
|
+
A multi-commit fixture repo rebuilt in `beforeEach` is the costly shape. Build
|
|
275
|
+
it once in `before()` and hand each test an `fs` copy via
|
|
276
|
+
`copyGitRepo(pristine)` from `tests/fixtures/git-fixture.js`: each test still
|
|
277
|
+
gets a private directory it may freely mutate, for **no subprocess at all**.
|
|
278
|
+
One file went from 92 `git` spawns to 27 this way.
|
|
279
|
+
|
|
280
|
+
`copyGitRepo` is safe only for a locally-`git init`ed repo, whose
|
|
281
|
+
`.git/config` holds no absolute paths. Do **not** copy a **clone** (its
|
|
282
|
+
`remote.origin.url` is absolute, so the copy would fetch from the original) or a
|
|
283
|
+
linked worktree (its `gitdir:` / `commondir` pointers would dangle).
|
|
284
|
+
|
|
285
|
+
### Never trade coverage for a spawn count
|
|
286
|
+
|
|
287
|
+
Most of the suite's remaining `git` spawns are integration tests exercising
|
|
288
|
+
real git against git-manipulating production code — the spawn **is** the
|
|
289
|
+
subject under test, and so is a CLI's exit code in an exit-code contract test.
|
|
290
|
+
Those are not fixture waste and must not be converted to in-process calls or
|
|
291
|
+
mocks to make a number smaller. Hoist shared setup; leave the assertions alone.
|
|
292
|
+
A spawn census also records argv, not what the binary resolved to: a
|
|
293
|
+
`gh pr view 4890` line may well be a fake `gh` the test put on `PATH`, so
|
|
294
|
+
verify resolution before calling a spawn a network call.
|
|
@@ -298,7 +298,7 @@
|
|
|
298
298
|
},
|
|
299
299
|
"planning": {
|
|
300
300
|
"type": "object",
|
|
301
|
-
"description": "Inputs to `/plan`: risk escalation heuristics, ceremony-lite routing, and the cross-Story conflict-finding severity gates.",
|
|
301
|
+
"description": "Inputs to `/mandrel-plan`: risk escalation heuristics, ceremony-lite routing, and the cross-Story conflict-finding severity gates.",
|
|
302
302
|
"properties": {
|
|
303
303
|
"riskHeuristics": {
|
|
304
304
|
"oneOf": [
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
},
|
|
441
441
|
"delivery": {
|
|
442
442
|
"type": "object",
|
|
443
|
-
"description": "Everything `/deliver` and `single-story-close` consume: execution timeouts, worktree isolation, runner concurrency, docs freshness, signals, quality gates, merge/CI watch, review ceremony, and the feedback loop.",
|
|
443
|
+
"description": "Everything `/mandrel-deliver` and `single-story-close` consume: execution timeouts, worktree isolation, runner concurrency, docs freshness, signals, quality gates, merge/CI watch, review ceremony, and the feedback loop.",
|
|
444
444
|
"properties": {
|
|
445
445
|
"execution": {
|
|
446
446
|
"type": "object",
|
|
@@ -521,12 +521,12 @@
|
|
|
521
521
|
},
|
|
522
522
|
"deliverRunner": {
|
|
523
523
|
"type": "object",
|
|
524
|
-
"description": "Bounded-concurrency knob for the /deliver fan-out.",
|
|
524
|
+
"description": "Bounded-concurrency knob for the /mandrel-deliver fan-out.",
|
|
525
525
|
"properties": {
|
|
526
526
|
"concurrencyCap": {
|
|
527
527
|
"type": "integer",
|
|
528
528
|
"minimum": 1,
|
|
529
|
-
"description": "Maximum ready Stories dispatched by /deliver at once. Default 3. Moderate by design — keeps host-quota consumption predictable while allowing a small ready-set fan-out. Set 1 for strictly sequential delivery; raise further on hosts with adequate parallel-agent quota. See deliver.md for the sequencing model and throughput tradeoff.",
|
|
529
|
+
"description": "Maximum ready Stories dispatched by /mandrel-deliver at once. Default 3. Moderate by design — keeps host-quota consumption predictable while allowing a small ready-set fan-out. Set 1 for strictly sequential delivery; raise further on hosts with adequate parallel-agent quota. See deliver.md for the sequencing model and throughput tradeoff.",
|
|
530
530
|
"default": 3
|
|
531
531
|
},
|
|
532
532
|
"footprintGuard": {
|
|
@@ -1628,7 +1628,7 @@
|
|
|
1628
1628
|
},
|
|
1629
1629
|
"navigability": {
|
|
1630
1630
|
"type": "object",
|
|
1631
|
-
"description": "Navigability lens + journey-suite config (Epic #4131, F2/F3/F1/F4). Read by audit-suite/selector.js (route globs) and /deliver's per-Story ceremony (journey suite). Opt-in: absent or empty routeGlobs degrades to a silent no-op.",
|
|
1631
|
+
"description": "Navigability lens + journey-suite config (Epic #4131, F2/F3/F1/F4). Read by audit-suite/selector.js (route globs) and /mandrel-deliver's per-Story ceremony (journey suite). Opt-in: absent or empty routeGlobs degrades to a silent no-op.",
|
|
1632
1632
|
"properties": {
|
|
1633
1633
|
"routeGlobs": {
|
|
1634
1634
|
"type": "array",
|
|
@@ -1648,7 +1648,7 @@
|
|
|
1648
1648
|
},
|
|
1649
1649
|
"journeySuite": {
|
|
1650
1650
|
"type": "string",
|
|
1651
|
-
"description": "Path or command for the per-persona journey suite /deliver's per-Story ceremony runs."
|
|
1651
|
+
"description": "Path or command for the per-persona journey suite /mandrel-deliver's per-Story ceremony runs."
|
|
1652
1652
|
}
|
|
1653
1653
|
},
|
|
1654
1654
|
"additionalProperties": false
|
|
@@ -1692,7 +1692,7 @@
|
|
|
1692
1692
|
},
|
|
1693
1693
|
"codeReview": {
|
|
1694
1694
|
"type": "object",
|
|
1695
|
-
"description": "Review-provider chain plus bounded-retry knobs for the /deliver code-review ceremony.",
|
|
1695
|
+
"description": "Review-provider chain plus bounded-retry knobs for the /mandrel-deliver code-review ceremony.",
|
|
1696
1696
|
"properties": {
|
|
1697
1697
|
"providers": {
|
|
1698
1698
|
"type": "array",
|
|
@@ -1780,7 +1780,7 @@
|
|
|
1780
1780
|
"maxFixAttempts": {
|
|
1781
1781
|
"type": "integer",
|
|
1782
1782
|
"minimum": 0,
|
|
1783
|
-
"description": "Maximum auto-fix retry attempts per finding in /deliver Phase 5 (code-review). 0 disables auto-fix. Default 3.",
|
|
1783
|
+
"description": "Maximum auto-fix retry attempts per finding in /mandrel-deliver Phase 5 (code-review). 0 disables auto-fix. Default 3.",
|
|
1784
1784
|
"default": 3
|
|
1785
1785
|
},
|
|
1786
1786
|
"maxFixScopeFiles": {
|
|
@@ -1792,7 +1792,7 @@
|
|
|
1792
1792
|
"autoFixSeverity": {
|
|
1793
1793
|
"type": "string",
|
|
1794
1794
|
"enum": ["high", "medium"],
|
|
1795
|
-
"description": "Severity threshold for on-branch remediation in /deliver Phase 5 (code-review). `medium` (default) routes 🔴/🟠/🟡 findings into the host-LLM focused-fix routing (Mediums batched per lens: one commit per lens, a single validation + rescan at the end) while 🟢 suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover — no back-compat flag.",
|
|
1795
|
+
"description": "Severity threshold for on-branch remediation in /mandrel-deliver Phase 5 (code-review). `medium` (default) routes 🔴/🟠/🟡 findings into the host-LLM focused-fix routing (Mediums batched per lens: one commit per lens, a single validation + rescan at the end) while 🟢 suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover — no back-compat flag.",
|
|
1796
1796
|
"default": "medium"
|
|
1797
1797
|
}
|
|
1798
1798
|
},
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"status": {
|
|
23
23
|
"type": "string",
|
|
24
|
-
"description": "landed — the PR merged, the Story is agent::done, and the post-land tail was attempted. pending — a bounded wait expired with the PR still in flight; NO label was mutated and no merge.unlanded event was emitted, so the run is resumable via nextCommand. blocked — a classified hard block; the Story carries agent::blocked and blocked.blockClass names the class. failed — a phase crashed; phase names which one. escalated — the /deliver-light suitability gate refused an over-scope prompt under --yes; nothing was created and the session ENDS here, nextCommand naming the /plan invocation that owns the work instead.",
|
|
24
|
+
"description": "landed — the PR merged, the Story is agent::done, and the post-land tail was attempted. pending — a bounded wait expired with the PR still in flight; NO label was mutated and no merge.unlanded event was emitted, so the run is resumable via nextCommand. blocked — a classified hard block; the Story carries agent::blocked and blocked.blockClass names the class. failed — a phase crashed; phase names which one. escalated — the /deliver-light suitability gate refused an over-scope prompt under --yes; nothing was created and the session ENDS here, nextCommand naming the /mandrel-plan invocation that owns the work instead.",
|
|
25
25
|
"enum": ["landed", "pending", "blocked", "failed", "escalated"]
|
|
26
26
|
},
|
|
27
27
|
"phase": {
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
},
|
|
174
174
|
"nextCommand": {
|
|
175
175
|
"type": ["string", "null"],
|
|
176
|
-
"description": "The single command that advances this work from where it stopped, or null when status === \"landed\" and nothing remains. Shares its vocabulary with deliver-recover.js so recovery and normal resumption speak one language. For status escalated it is the /plan invocation the operator runs in a FRESH session — the one case where the command is a slash command rather than a script, because the work needs planning, not resumption.",
|
|
176
|
+
"description": "The single command that advances this work from where it stopped, or null when status === \"landed\" and nothing remains. Shares its vocabulary with deliver-recover.js so recovery and normal resumption speak one language. For status escalated it is the /mandrel-plan invocation the operator runs in a FRESH session — the one case where the command is a slash command rather than a script, because the work needs planning, not resumption.",
|
|
177
177
|
"minLength": 1
|
|
178
178
|
},
|
|
179
179
|
"elapsedSeconds": { "type": "number", "minimum": 0 },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "validation-evidence",
|
|
4
4
|
"title": "Validation Evidence",
|
|
5
|
-
"description": "Per-scope record of which validation gates have passed against which commit SHA. Written by lib/validation-evidence.js under the per-Epic temp tree at temp/run-<id>/validation-evidence.json (Epic-scoped) or temp/run-<id>/stories/story-<storyId>/validation-evidence.json (Story-scoped). Standalone Stories (no parent Epic) use a storyId-anchored keyspace at temp/standalone/stories/story-<storyId>/validation-evidence.json. All paths are gitignored via temp/. Consumed by close-validation, epic-code-review, the acceptance self-eval critic, and /deliver Phase 3 (close-validation) to skip identical re-runs against an already-validated tree. The `storyId` field carries the scope id and equals the epic id for Epic-scoped records.",
|
|
5
|
+
"description": "Per-scope record of which validation gates have passed against which commit SHA. Written by lib/validation-evidence.js under the per-Epic temp tree at temp/run-<id>/validation-evidence.json (Epic-scoped) or temp/run-<id>/stories/story-<storyId>/validation-evidence.json (Story-scoped). Standalone Stories (no parent Epic) use a storyId-anchored keyspace at temp/standalone/stories/story-<storyId>/validation-evidence.json. All paths are gitignored via temp/. Consumed by close-validation, epic-code-review, the acceptance self-eval critic, and /mandrel-deliver Phase 3 (close-validation) to skip identical re-runs against an already-validated tree. The `storyId` field carries the scope id and equals the epic id for Epic-scoped records.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": ["storyId", "schemaVersion", "records"],
|
|
8
8
|
"properties": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* and the resolved, undisableable round cap
|
|
21
21
|
* (`delivery.acceptanceEval.maxRounds`, clamped to `[1, ceiling]`).
|
|
22
22
|
* 3. Emit one per-criterion `acceptance-eval` signal into the retro /
|
|
23
|
-
* feedback substrate so the retro and `/plan` Phase 0 feedback
|
|
23
|
+
* feedback substrate so the retro and `/mandrel-plan` Phase 0 feedback
|
|
24
24
|
* fetch can see which acceptance items needed rework and the round
|
|
25
25
|
* count.
|
|
26
26
|
* 4. Print a single JSON envelope and exit:
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Story #5007 retired the second step. `migrateBaselinesLayout` relocated
|
|
22
22
|
* per-Epic ratchet snapshots into `temp/epic/<id>/baselines/` on the contract
|
|
23
|
-
* that `/deliver` reaps that namespace on merge — a mechanism the Story-only
|
|
23
|
+
* that `/mandrel-deliver` reaps that namespace on merge — a mechanism the Story-only
|
|
24
24
|
* v2 model deleted, so the migration moved dead data into a namespace no code
|
|
25
25
|
* path writes, reads, or reaps. Its one residual hygiene value (getting the
|
|
26
26
|
* committed `baselines/epic/` tree out of version control) survives as
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* stdout when --json is set).
|
|
14
14
|
*
|
|
15
15
|
* --emit-plan-seed --plan <plan.json> --out <path>
|
|
16
|
-
* Read the plan envelope from disk, render the `/plan --seed`
|
|
16
|
+
* Read the plan envelope from disk, render the `/mandrel-plan --seed`
|
|
17
17
|
* seed markdown, persist to --out.
|
|
18
18
|
*
|
|
19
19
|
* --emit-stories --plan <plan.json>
|
|
@@ -844,7 +844,7 @@ runAsCli(import.meta.url, main, {
|
|
|
844
844
|
flags: [
|
|
845
845
|
['--scan', 'Print the grouped, deduplicated plan as JSON.'],
|
|
846
846
|
['--auto', 'Run the full scan → file pipeline and print the summary.'],
|
|
847
|
-
['--emit-plan-seed', 'Emit a /plan --seed-file document.'],
|
|
847
|
+
['--emit-plan-seed', 'Emit a /mandrel-plan --seed-file document.'],
|
|
848
848
|
['--emit-stories', 'Emit the Story drafts as JSON.'],
|
|
849
849
|
[
|
|
850
850
|
'--wire-edges',
|
|
@@ -45,6 +45,7 @@ import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
|
|
|
45
45
|
import { Logger } from './lib/Logger.js';
|
|
46
46
|
import { createProvider } from './lib/provider-factory.js';
|
|
47
47
|
import { buildProtectionCtx } from './lib/single-story-sweep/protection-ctx.js';
|
|
48
|
+
import { resolveSweepLockPath } from './lib/single-story-sweep/sweep-lock.js';
|
|
48
49
|
import { sweepMergedBranches } from './lib/single-story-sweep.js';
|
|
49
50
|
import { sweepTempRetention } from './lib/temp-retention.js';
|
|
50
51
|
|
|
@@ -141,8 +142,13 @@ export async function runBootSweep({
|
|
|
141
142
|
excludeGlobs.push(current);
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
// Story #5112 — one critical section, one lock. `single-story-init.js`
|
|
146
|
+
// reaps the same merged `story-*` branches through the same engine; when
|
|
147
|
+
// the two surfaces held differently named lockfiles they could run
|
|
148
|
+
// concurrently, each deleting branches the other had already planned.
|
|
149
|
+
// Both now resolve the path through `resolveSweepLockPath`.
|
|
144
150
|
const tempRoot = config?.project?.paths?.tempRoot ?? 'temp';
|
|
145
|
-
const lockPath =
|
|
151
|
+
const lockPath = resolveSweepLockPath({ cwd: root, tempRoot });
|
|
146
152
|
const lockTimeoutMs =
|
|
147
153
|
config.delivery?.worktreeIsolation?.sweepLockMs ?? 60_000;
|
|
148
154
|
|
|
@@ -481,6 +481,179 @@ export function renderReachable(tierMap, baseline) {
|
|
|
481
481
|
* @returns {Promise<number>} 0 = clean / within tolerance / shrink-only / no-op;
|
|
482
482
|
* 1 = a gated tier grew beyond tolerance
|
|
483
483
|
*/
|
|
484
|
+
/**
|
|
485
|
+
* Write a fresh budget, preserving the recorded tolerance so `--update` never
|
|
486
|
+
* silently widens the gate it is refreshing.
|
|
487
|
+
*
|
|
488
|
+
* @param {object} params
|
|
489
|
+
* @returns {0}
|
|
490
|
+
*/
|
|
491
|
+
function writeUpdatedBaseline({ tierMap, resolvedBaselinePath, json, stdout }) {
|
|
492
|
+
const existing = loadBaseline(resolvedBaselinePath);
|
|
493
|
+
const tolerance = Number.isFinite(existing?.toleranceBytes)
|
|
494
|
+
? existing.toleranceBytes
|
|
495
|
+
: DEFAULT_TOLERANCE_BYTES;
|
|
496
|
+
const envelope = buildBaseline(tierMap, tolerance);
|
|
497
|
+
fs.mkdirSync(path.dirname(resolvedBaselinePath), { recursive: true });
|
|
498
|
+
fs.writeFileSync(
|
|
499
|
+
resolvedBaselinePath,
|
|
500
|
+
`${JSON.stringify(envelope, null, 2)}\n`,
|
|
501
|
+
);
|
|
502
|
+
if (!json) {
|
|
503
|
+
stdout.write(
|
|
504
|
+
`[context-budget] wrote baseline ${resolvedBaselinePath} (tolerance ${tolerance} bytes)\n`,
|
|
505
|
+
);
|
|
506
|
+
} else {
|
|
507
|
+
stdout.write(
|
|
508
|
+
`${JSON.stringify({ kind: 'context-budget-update', baselinePath: resolvedBaselinePath, envelope }, null, 2)}\n`,
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
return 0;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* An absent budget is a no-op, not a failure: a consumer that has never
|
|
516
|
+
* recorded one has nothing to regress against.
|
|
517
|
+
*
|
|
518
|
+
* @param {object} params
|
|
519
|
+
* @returns {0}
|
|
520
|
+
*/
|
|
521
|
+
function reportMissingBaseline({
|
|
522
|
+
tierMap,
|
|
523
|
+
resolvedBaselinePath,
|
|
524
|
+
json,
|
|
525
|
+
stdout,
|
|
526
|
+
stderr,
|
|
527
|
+
}) {
|
|
528
|
+
if (json) {
|
|
529
|
+
stdout.write(
|
|
530
|
+
`${JSON.stringify({ kind: 'context-budget-report', baselinePath: resolvedBaselinePath, tiers: tierMap.tiers, grown: [], shrunk: [], absent: [], skipped: GATED_TIERS, exitCode: 0, noBaseline: true }, null, 2)}\n`,
|
|
531
|
+
);
|
|
532
|
+
} else {
|
|
533
|
+
stderr.write(
|
|
534
|
+
`[context-budget] ⚠ budget not found at ${resolvedBaselinePath} — skipping (no-op)\n`,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Score the tree against the recorded budget. Pure — every verdict the two
|
|
542
|
+
* renderers below present is decided here, so they cannot disagree about what
|
|
543
|
+
* failed or drift apart in which fields they surface.
|
|
544
|
+
*
|
|
545
|
+
* @param {{ tierMap: object, baseline: object }} params
|
|
546
|
+
* @returns {{ diff: object, ceiling: number, bootOverflow: object[], bootDrift: object[], permissiveDrift: object[], exitCode: 0 | 1 }}
|
|
547
|
+
*/
|
|
548
|
+
function evaluateBudget({ tierMap, baseline }) {
|
|
549
|
+
const diff = diffBudget(tierMap, baseline);
|
|
550
|
+
const ceiling = Number.isFinite(baseline?.agentBoot?.ceilingBytes)
|
|
551
|
+
? baseline.agentBoot.ceilingBytes
|
|
552
|
+
: AGENT_BOOT_CEILING_BYTES;
|
|
553
|
+
const bootOverflow = agentBootOverflow(tierMap, ceiling);
|
|
554
|
+
const bootDrift = agentBootDrift(tierMap, baseline, ceiling);
|
|
555
|
+
const permissiveDrift = bootDrift.filter((d) => d.direction === 'permissive');
|
|
556
|
+
const exitCode =
|
|
557
|
+
budgetFailureCount(diff) > 0 ||
|
|
558
|
+
bootOverflow.length > 0 ||
|
|
559
|
+
permissiveDrift.length > 0
|
|
560
|
+
? 1
|
|
561
|
+
: 0;
|
|
562
|
+
return { diff, ceiling, bootOverflow, bootDrift, permissiveDrift, exitCode };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* @param {object} params
|
|
567
|
+
* @returns {void}
|
|
568
|
+
*/
|
|
569
|
+
function renderJsonReport({
|
|
570
|
+
tierMap,
|
|
571
|
+
baseline,
|
|
572
|
+
resolvedBaselinePath,
|
|
573
|
+
report,
|
|
574
|
+
stdout,
|
|
575
|
+
}) {
|
|
576
|
+
const { diff, ceiling, bootOverflow, bootDrift, exitCode } = report;
|
|
577
|
+
const envelope = {
|
|
578
|
+
kind: 'context-budget-report',
|
|
579
|
+
baselinePath: resolvedBaselinePath,
|
|
580
|
+
toleranceBytes: Number.isFinite(baseline.toleranceBytes)
|
|
581
|
+
? baseline.toleranceBytes
|
|
582
|
+
: 0,
|
|
583
|
+
current: Object.fromEntries(
|
|
584
|
+
GATED_TIERS.map((t) => [t, tierTotalBytes(tierMap.tiers[t] ?? [])]),
|
|
585
|
+
),
|
|
586
|
+
grown: diff.grown,
|
|
587
|
+
shrunk: diff.shrunk,
|
|
588
|
+
absent: diff.absent,
|
|
589
|
+
skipped: diff.skipped,
|
|
590
|
+
agentBootCeilingBytes: ceiling,
|
|
591
|
+
agentBootOverflow: bootOverflow,
|
|
592
|
+
agentBootDrift: bootDrift,
|
|
593
|
+
workflowReachableBytes: tierMap.workflowClosure?.reachableTotalBytes ?? 0,
|
|
594
|
+
exitCode,
|
|
595
|
+
};
|
|
596
|
+
stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Each failing condition gets its own remediation line: they are fixed
|
|
601
|
+
* differently (trim a role def vs refresh the budget), so a single generic
|
|
602
|
+
* message would leave the author guessing which applies.
|
|
603
|
+
*
|
|
604
|
+
* @param {object} params
|
|
605
|
+
* @returns {void}
|
|
606
|
+
*/
|
|
607
|
+
function renderFailureDiagnostics({ report, stderr }) {
|
|
608
|
+
const { diff, ceiling, bootOverflow, permissiveDrift } = report;
|
|
609
|
+
if (permissiveDrift.length > 0) {
|
|
610
|
+
stderr.write(
|
|
611
|
+
`[context-budget] ❌ a recorded agentBoot row understates the file it describes, so it overstates the headroom an author would size an edit against — refresh it with \`node .agents/scripts/check-context-budget.js --update\` (the ceiling is unchanged)\n`,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (bootOverflow.length > 0) {
|
|
615
|
+
stderr.write(
|
|
616
|
+
`[context-budget] ❌ a role-agent boot context exceeds the ${ceiling}-byte per-agent ceiling — trim the role def (the ceiling is a hard cap, not a starve target)\n`,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
if (diff.grown.length > 0) {
|
|
620
|
+
stderr.write(
|
|
621
|
+
`[context-budget] ❌ a documentation tier grew beyond tolerance — refresh the budget consciously with \`node .agents/scripts/check-context-budget.js --update\` once the growth is intentional\n`,
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
if (diff.shrunk.length > 0) {
|
|
625
|
+
stderr.write(
|
|
626
|
+
`[context-budget] ❌ a documentation tier came in under its recorded total — the ratchet is holding slack the tree no longer spends, so the next growth would be absorbed silently. Lock the gain in with \`node .agents/scripts/check-context-budget.js --update\`\n`,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
if (diff.absent.length > 0) {
|
|
630
|
+
stderr.write(
|
|
631
|
+
`[context-budget] ❌ a recorded row names a path the measured tier no longer contains — its bytes inflate the recorded total against nothing. Refresh with \`node .agents/scripts/check-context-budget.js --update\`\n`,
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* @param {object} params
|
|
638
|
+
* @returns {void}
|
|
639
|
+
*/
|
|
640
|
+
function renderTextReport({ tierMap, baseline, report, stdout, stderr }) {
|
|
641
|
+
const { diff, bootOverflow, bootDrift, exitCode } = report;
|
|
642
|
+
stdout.write(`\n--- context-budget preview ---\n`);
|
|
643
|
+
stdout.write(`${renderDiff(diff)}\n`);
|
|
644
|
+
const reachable = renderReachable(tierMap, baseline);
|
|
645
|
+
if (reachable) stdout.write(`${reachable}\n`);
|
|
646
|
+
for (const o of bootOverflow) {
|
|
647
|
+
stdout.write(
|
|
648
|
+
`+ agentBoot: ${o.path} is ${o.bytes} bytes, over the ${o.ceiling}-byte per-agent ceiling\n`,
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
for (const line of renderBootDrift(bootDrift)) {
|
|
652
|
+
stdout.write(`${line}\n`);
|
|
653
|
+
}
|
|
654
|
+
if (exitCode === 1) renderFailureDiagnostics({ report, stderr });
|
|
655
|
+
}
|
|
656
|
+
|
|
484
657
|
export async function runCli({
|
|
485
658
|
argv = process.argv.slice(2),
|
|
486
659
|
cwd = process.cwd(),
|
|
@@ -498,120 +671,39 @@ export async function runCli({
|
|
|
498
671
|
const tierMap = resolveDocTiers(resolvedConfig, { root });
|
|
499
672
|
|
|
500
673
|
if (update) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
? existing.toleranceBytes
|
|
504
|
-
: DEFAULT_TOLERANCE_BYTES;
|
|
505
|
-
const envelope = buildBaseline(tierMap, tolerance);
|
|
506
|
-
fs.mkdirSync(path.dirname(resolvedBaselinePath), { recursive: true });
|
|
507
|
-
fs.writeFileSync(
|
|
674
|
+
return writeUpdatedBaseline({
|
|
675
|
+
tierMap,
|
|
508
676
|
resolvedBaselinePath,
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
stdout.write(
|
|
513
|
-
`[context-budget] wrote baseline ${resolvedBaselinePath} (tolerance ${tolerance} bytes)\n`,
|
|
514
|
-
);
|
|
515
|
-
} else {
|
|
516
|
-
stdout.write(
|
|
517
|
-
`${JSON.stringify({ kind: 'context-budget-update', baselinePath: resolvedBaselinePath, envelope }, null, 2)}\n`,
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
return 0;
|
|
677
|
+
json,
|
|
678
|
+
stdout,
|
|
679
|
+
});
|
|
521
680
|
}
|
|
522
681
|
|
|
523
682
|
const baseline = loadBaseline(resolvedBaselinePath);
|
|
524
683
|
if (!baseline) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
stderr
|
|
531
|
-
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
return 0;
|
|
684
|
+
return reportMissingBaseline({
|
|
685
|
+
tierMap,
|
|
686
|
+
resolvedBaselinePath,
|
|
687
|
+
json,
|
|
688
|
+
stdout,
|
|
689
|
+
stderr,
|
|
690
|
+
});
|
|
535
691
|
}
|
|
536
692
|
|
|
537
|
-
const
|
|
538
|
-
const ceiling = Number.isFinite(baseline?.agentBoot?.ceilingBytes)
|
|
539
|
-
? baseline.agentBoot.ceilingBytes
|
|
540
|
-
: AGENT_BOOT_CEILING_BYTES;
|
|
541
|
-
const bootOverflow = agentBootOverflow(tierMap, ceiling);
|
|
542
|
-
const bootDrift = agentBootDrift(tierMap, baseline, ceiling);
|
|
543
|
-
const permissiveDrift = bootDrift.filter((d) => d.direction === 'permissive');
|
|
544
|
-
const exitCode =
|
|
545
|
-
budgetFailureCount(diff) > 0 ||
|
|
546
|
-
bootOverflow.length > 0 ||
|
|
547
|
-
permissiveDrift.length > 0
|
|
548
|
-
? 1
|
|
549
|
-
: 0;
|
|
550
|
-
|
|
693
|
+
const report = evaluateBudget({ tierMap, baseline });
|
|
551
694
|
if (json) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
GATED_TIERS.map((t) => [t, tierTotalBytes(tierMap.tiers[t] ?? [])]),
|
|
560
|
-
),
|
|
561
|
-
grown: diff.grown,
|
|
562
|
-
shrunk: diff.shrunk,
|
|
563
|
-
absent: diff.absent,
|
|
564
|
-
skipped: diff.skipped,
|
|
565
|
-
agentBootCeilingBytes: ceiling,
|
|
566
|
-
agentBootOverflow: bootOverflow,
|
|
567
|
-
agentBootDrift: bootDrift,
|
|
568
|
-
workflowReachableBytes: tierMap.workflowClosure?.reachableTotalBytes ?? 0,
|
|
569
|
-
exitCode,
|
|
570
|
-
};
|
|
571
|
-
stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
695
|
+
renderJsonReport({
|
|
696
|
+
tierMap,
|
|
697
|
+
baseline,
|
|
698
|
+
resolvedBaselinePath,
|
|
699
|
+
report,
|
|
700
|
+
stdout,
|
|
701
|
+
});
|
|
572
702
|
} else {
|
|
573
|
-
|
|
574
|
-
stdout.write(`${renderDiff(diff)}\n`);
|
|
575
|
-
const reachable = renderReachable(tierMap, baseline);
|
|
576
|
-
if (reachable) stdout.write(`${reachable}\n`);
|
|
577
|
-
for (const o of bootOverflow) {
|
|
578
|
-
stdout.write(
|
|
579
|
-
`+ agentBoot: ${o.path} is ${o.bytes} bytes, over the ${o.ceiling}-byte per-agent ceiling\n`,
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
for (const line of renderBootDrift(bootDrift)) {
|
|
583
|
-
stdout.write(`${line}\n`);
|
|
584
|
-
}
|
|
585
|
-
if (exitCode === 1) {
|
|
586
|
-
if (permissiveDrift.length > 0) {
|
|
587
|
-
stderr.write(
|
|
588
|
-
`[context-budget] ❌ a recorded agentBoot row understates the file it describes, so it overstates the headroom an author would size an edit against — refresh it with \`node .agents/scripts/check-context-budget.js --update\` (the ceiling is unchanged)\n`,
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
if (bootOverflow.length > 0) {
|
|
592
|
-
stderr.write(
|
|
593
|
-
`[context-budget] ❌ a role-agent boot context exceeds the ${ceiling}-byte per-agent ceiling — trim the role def (the ceiling is a hard cap, not a starve target)\n`,
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
if (diff.grown.length > 0) {
|
|
597
|
-
stderr.write(
|
|
598
|
-
`[context-budget] ❌ a documentation tier grew beyond tolerance — refresh the budget consciously with \`node .agents/scripts/check-context-budget.js --update\` once the growth is intentional\n`,
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
if (diff.shrunk.length > 0) {
|
|
602
|
-
stderr.write(
|
|
603
|
-
`[context-budget] ❌ a documentation tier came in under its recorded total — the ratchet is holding slack the tree no longer spends, so the next growth would be absorbed silently. Lock the gain in with \`node .agents/scripts/check-context-budget.js --update\`\n`,
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
if (diff.absent.length > 0) {
|
|
607
|
-
stderr.write(
|
|
608
|
-
`[context-budget] ❌ a recorded row names a path the measured tier no longer contains — its bytes inflate the recorded total against nothing. Refresh with \`node .agents/scripts/check-context-budget.js --update\`\n`,
|
|
609
|
-
);
|
|
610
|
-
}
|
|
611
|
-
}
|
|
703
|
+
renderTextReport({ tierMap, baseline, report, stdout, stderr });
|
|
612
704
|
}
|
|
613
705
|
|
|
614
|
-
return exitCode;
|
|
706
|
+
return report.exitCode;
|
|
615
707
|
}
|
|
616
708
|
|
|
617
709
|
async function main() {
|