mandrel 2.24.0 → 2.26.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/agents/acceptance-critic.md +19 -21
- package/.agents/agents/auditor.md +16 -19
- package/.agents/agents/story-worker.md +41 -54
- package/.agents/audit-checklists/architecture.md +5 -1
- package/.agents/audit-checklists/baselines.md +21 -0
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +2 -1
- package/.agents/docs/configuration.md +2 -1
- package/.agents/docs/quality-gates.md +80 -18
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +1 -1
- package/.agents/rules/ci-remediation.md +68 -3
- package/.agents/schemas/agentrc.schema.json +6 -1
- package/.agents/schemas/audit-rules.json +15 -0
- package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
- package/.agents/schemas/baselines/baseline-envelope.schema.json +8 -0
- package/.agents/schemas/baselines/crap.schema.json +13 -1
- package/.agents/schemas/crap-report.schema.json +37 -0
- package/.agents/schemas/model-attribution.schema.json +4 -0
- package/.agents/scripts/acceptance-eval.js +124 -15
- package/.agents/scripts/audit-baselines.js +136 -0
- package/.agents/scripts/audit-labels-bootstrap.js +16 -95
- package/.agents/scripts/audit-to-stories.js +33 -3
- package/.agents/scripts/check-arch-cycles.js +12 -93
- package/.agents/scripts/check-baseline-drift.js +16 -3
- package/.agents/scripts/check-baselines.js +19 -3
- package/.agents/scripts/check-context-budget.js +95 -14
- package/.agents/scripts/check-cyclomatic.js +214 -0
- package/.agents/scripts/check-schema-references.js +392 -0
- package/.agents/scripts/check-test-temp-hygiene.js +121 -1
- package/.agents/scripts/check-workflow-timeouts.js +291 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
- package/.agents/scripts/deliver-light.js +3 -4
- package/.agents/scripts/deliver-recover.js +13 -0
- package/.agents/scripts/diagnose-friction.js +85 -19
- package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
- package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
- package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
- package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
- package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
- package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
- package/.agents/scripts/lib/audit-baselines/read.js +87 -0
- package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
- package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
- package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
- package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
- package/.agents/scripts/lib/audit-suite/index.js +0 -5
- package/.agents/scripts/lib/audit-suite/selector.js +9 -62
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
- package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
- package/.agents/scripts/lib/baseline-loader.js +0 -0
- package/.agents/scripts/lib/baseline-schema-registry.js +13 -1
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
- package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +499 -57
- package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
- package/.agents/scripts/lib/baselines/reader.js +10 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
- package/.agents/scripts/lib/baselines/scope.js +39 -90
- package/.agents/scripts/lib/baselines/writer.js +16 -7
- package/.agents/scripts/lib/changed-files.js +8 -1
- package/.agents/scripts/lib/cli-args.js +115 -1
- package/.agents/scripts/lib/close-validation/runner.js +70 -25
- package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
- package/.agents/scripts/lib/crap-engine.js +124 -27
- package/.agents/scripts/lib/crap-method-identity.js +153 -0
- package/.agents/scripts/lib/crap-utils.js +86 -13
- package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
- package/.agents/scripts/lib/env-loader.js +46 -16
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
- package/.agents/scripts/lib/findings/route-finding.js +123 -17
- package/.agents/scripts/lib/findings/severity.js +80 -2
- package/.agents/scripts/lib/git/cached-fetch.js +0 -0
- package/.agents/scripts/lib/git-utils.js +136 -80
- package/.agents/scripts/lib/import-graph.js +156 -0
- package/.agents/scripts/lib/label-constants.js +17 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
- package/.agents/scripts/lib/observability/source-classifier.js +175 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
- package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +10 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
- package/.agents/scripts/lib/orchestration/light-backstop.js +20 -3
- package/.agents/scripts/lib/orchestration/light-escalation.js +82 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +103 -5
- package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +216 -109
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +61 -13
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
- package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
- package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -0
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +125 -37
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +267 -125
- package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
- package/.agents/scripts/lib/reserved-test-ids.js +77 -0
- package/.agents/scripts/lib/story-adjacency.js +3 -3
- package/.agents/scripts/lib/story-plan.js +137 -42
- package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
- package/.agents/scripts/lib/test-runner-contract.js +134 -0
- package/.agents/scripts/lib/test-tiers.js +11 -2
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/util/concurrent-map.js +17 -0
- package/.agents/scripts/lib/util/parse-id-list.js +103 -0
- package/.agents/scripts/lib/util/poll-loop.js +18 -2
- package/.agents/scripts/lib/wave-runner/live-probe.js +29 -13
- package/.agents/scripts/lib/wave-runner/ready-set.js +254 -43
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +10 -13
- package/.agents/scripts/lib/workers/crap-worker.js +8 -13
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
- package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
- package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
- package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
- package/.agents/scripts/lib/worktree-manager.js +14 -0
- package/.agents/scripts/plan-run-epilogue.js +17 -5
- package/.agents/scripts/pr-watch-with-update.js +568 -213
- package/.agents/scripts/providers/github/tickets.js +33 -10
- package/.agents/scripts/provision-git-hooks.js +85 -0
- package/.agents/scripts/quality-preview.js +137 -28
- package/.agents/scripts/resolve-stories.js +4 -1
- package/.agents/scripts/run-coverage.js +86 -35
- package/.agents/scripts/run-lint.js +20 -0
- package/.agents/scripts/run-tests.js +36 -36
- package/.agents/scripts/single-story-close.js +34 -2
- package/.agents/scripts/single-story-confirm-merge.js +22 -6
- package/.agents/scripts/single-story-init.js +7 -0
- package/.agents/scripts/stories-wave-tick.js +308 -47
- package/.agents/scripts/story-plan.js +65 -9
- package/.agents/scripts/update-coverage-baseline.js +34 -4
- package/.agents/scripts/update-crap-baseline.js +42 -4
- package/.agents/scripts/update-duplication-baseline.js +209 -83
- package/.agents/scripts/update-maintainability-baseline.js +31 -4
- package/.agents/scripts/validate-docs-freshness.js +1 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/templates/single-story-body.md +16 -8
- package/.agents/workflows/audit-architecture.md +69 -13
- package/.agents/workflows/audit-baselines.md +289 -0
- package/.agents/workflows/audit-navigability.md +5 -4
- package/.agents/workflows/audit-quality.md +26 -0
- package/.agents/workflows/audit-to-stories.md +30 -4
- package/.agents/workflows/deliver.md +92 -97
- package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
- package/.agents/workflows/helpers/audit-lens-core.md +25 -4
- package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
- package/.agents/workflows/helpers/deliver-digest.md +41 -21
- package/.agents/workflows/helpers/deliver-reference.md +95 -4
- package/.agents/workflows/helpers/deliver-story-reference.md +174 -52
- package/.agents/workflows/helpers/deliver-story.md +104 -119
- package/.agents/workflows/helpers/plan-reference.md +49 -5
- package/.agents/workflows/memory-consolidate.md +116 -0
- package/.agents/workflows/plan.md +62 -75
- package/README.md +13 -6
- package/docs/CHANGELOG.md +93 -0
- package/lib/cli/update.js +14 -11
- package/lib/cli/version-check.js +9 -1
- package/package.json +9 -4
- package/.agents/schemas/friction-event.schema.json +0 -56
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
|
@@ -19,8 +19,10 @@ exactly one of two ways, and no others:
|
|
|
19
19
|
caused by the diff under review — **verify the same check against an
|
|
20
20
|
unmodified `main` checkout**; if it also fails on `main` the defect is
|
|
21
21
|
pre-existing and belongs in a separate change — then fix it at source,
|
|
22
|
-
commit on `story-<storyId>`, push, and re-run the watcher. Auto-merge
|
|
23
|
-
|
|
22
|
+
commit on `story-<storyId>`, push, and re-run the watcher. Auto-merge is
|
|
23
|
+
**disarmed on the first red** and re-armed only by a green on a **new head
|
|
24
|
+
SHA**, so the fix must be a new commit. Route deterministic per-check
|
|
25
|
+
failures (lint/format,
|
|
24
26
|
maintainability/CRAP baseline drift, test failure, coverage threshold)
|
|
25
27
|
through the fix table in
|
|
26
28
|
[`deliver-story-reference.md` § Step 4](../workflows/helpers/deliver-story-reference.md#step-4--ci-watch--fix-recovery);
|
|
@@ -39,6 +41,51 @@ through the same two options; bisect environment (runner OS, Node version,
|
|
|
39
41
|
concurrency, a platform-conditional branch, an external service) vs. code (an
|
|
40
42
|
order-dependent test, a race, a shared-state assumption) to decide which.
|
|
41
43
|
|
|
44
|
+
## Verdicts
|
|
45
|
+
|
|
46
|
+
Every red check reaches exactly one verdict, and each verdict routes to one of
|
|
47
|
+
the two options above. Name the verdict you reached in the `friction` comment.
|
|
48
|
+
|
|
49
|
+
| Verdict | Evidence | Routes to |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| **defect-in-diff** | The failure reproduces on the branch and not on an unmodified `main` | Option 1 — fix at source |
|
|
52
|
+
| **pre-existing** | The same check fails on an unmodified `main` too | Option 2 — file `meta::framework-gap`; remediate here only if it blocks this delivery |
|
|
53
|
+
| **capacity** | Proven exhaustion of a runner resource, not a property of the diff (see below) | Option 2 — file `meta::framework-gap` **and** escalate to the operator |
|
|
54
|
+
|
|
55
|
+
### The `capacity` verdict
|
|
56
|
+
|
|
57
|
+
A job can fail because the runner ran out of something, not because the code is
|
|
58
|
+
wrong: no runner could be provisioned, the disk or memory ceiling was hit, a
|
|
59
|
+
process/PTY/file-descriptor limit was exhausted, the job wall-clock timed out
|
|
60
|
+
with no progress, or a self-hosted pool was saturated. Nothing on the branch
|
|
61
|
+
causes it and nothing on the branch can fix it.
|
|
62
|
+
|
|
63
|
+
This verdict exists because the rule previously offered no landing for that
|
|
64
|
+
case. The honest reading of "a red check is a defect until proven otherwise" is
|
|
65
|
+
that capacity failures are the *otherwise* — but with no verdict for them the
|
|
66
|
+
only shapes on offer were "fix the diff" (impossible) and "it's flaky, re-run
|
|
67
|
+
it" (forbidden), so the rule got broken rather than followed. Naming the verdict
|
|
68
|
+
removes the incentive to launder a capacity failure as a rerun.
|
|
69
|
+
|
|
70
|
+
**Capacity must be proven, not inferred.** A green on re-run is the single
|
|
71
|
+
weakest form of evidence for it and never establishes it — that is precisely the
|
|
72
|
+
observation a flaky test produces. Cite the resource and the reading: the log
|
|
73
|
+
line naming the exhausted limit (an OOM kill, `ENOSPC`, `EMFILE`,
|
|
74
|
+
`forkpty/sudo: Device not configured`, a provisioning error, a no-output
|
|
75
|
+
timeout), plus the fact that the failure is not specific to this diff. Absent
|
|
76
|
+
that reading the verdict is **flaky, not capacity**, and it routes to Option 1.
|
|
77
|
+
|
|
78
|
+
On a `capacity` verdict: file the `meta::framework-gap` issue with the run link,
|
|
79
|
+
the failure signature, and the resource reading; flip the Story to
|
|
80
|
+
`agent::blocked` with a `friction` comment naming the verdict; and hand back to
|
|
81
|
+
the operator, who owns the runner pool. Do not sit in a retry loop waiting for
|
|
82
|
+
capacity to return.
|
|
83
|
+
|
|
84
|
+
**Rerunning a failed job to reach green stays forbidden under every verdict,
|
|
85
|
+
`capacity` included.** The verdict changes who owns the fix and where it is
|
|
86
|
+
filed; it never licenses a re-run, and it is not a route to a green bar. A
|
|
87
|
+
capacity-blocked delivery ends `agent::blocked` — not merged.
|
|
88
|
+
|
|
42
89
|
## Verifier
|
|
43
90
|
|
|
44
91
|
The check is resolved only when it is **green with zero reruns of the failed
|
|
@@ -48,6 +95,23 @@ re-run a failed job to "see if it goes green," and you may **not** skip,
|
|
|
48
95
|
`.only`, or quarantine a flaky test to get a green bar. Both mask the defect
|
|
49
96
|
and are prohibited by this rule.
|
|
50
97
|
|
|
98
|
+
**The enforcement point is
|
|
99
|
+
[`pr-watch-with-update.js`](../scripts/pr-watch-with-update.js)**, and it acts
|
|
100
|
+
on the **first red** — GitHub's native auto-merge fires server-side, so it
|
|
101
|
+
races any attempt to detect a rerun-green and block it after the fact. On the
|
|
102
|
+
first red the watcher **disarms native auto-merge** (a disarm failure is a
|
|
103
|
+
blocker, not a warning) and records the PR **head SHA** in the digest
|
|
104
|
+
alongside the failing check-run identity. On green it adjudicates:
|
|
105
|
+
|
|
106
|
+
- **Same head SHA** → the green came from re-running the failed job. The
|
|
107
|
+
watcher exits non-zero, flips the Story to `agent::blocked` with a
|
|
108
|
+
`friction` comment, and requires the `meta::framework-gap` issue (run link +
|
|
109
|
+
failure signature, both already in the digest) before the delivery proceeds.
|
|
110
|
+
- **New head SHA** → fix at source. The digest is retired, auto-merge is
|
|
111
|
+
re-armed, and the delivery continues unobstructed.
|
|
112
|
+
|
|
113
|
+
A delivery that never went red has no digest and is untouched.
|
|
114
|
+
|
|
51
115
|
## Escalation
|
|
52
116
|
|
|
53
117
|
Flip the ticket to `agent::blocked`, post a `friction` comment (naming the
|
|
@@ -65,4 +129,5 @@ operator under **any** of:
|
|
|
65
129
|
registry/network outage, a branch-protection or CI misconfiguration, an
|
|
66
130
|
expired credential) — file the `meta::framework-gap` issue (with run link +
|
|
67
131
|
signature) and escalate on the first encounter rather than burning iterations
|
|
68
|
-
trying to code around it.
|
|
132
|
+
trying to code around it. A proven-capacity failure is this case: reach the
|
|
133
|
+
`capacity` verdict above and escalate on the first encounter.
|
|
@@ -1367,7 +1367,7 @@
|
|
|
1367
1367
|
"properties": {
|
|
1368
1368
|
"watch": {
|
|
1369
1369
|
"type": "object",
|
|
1370
|
-
"description": "Story #4356 (Epic #4355). Poll-loop tuning for the merge/CI watch. pollIntervalMs is the cadence between check probes; maxPolls caps total probes before the watcher gives up; maxResumes caps how many times the watcher may resume after a transient stall.",
|
|
1370
|
+
"description": "Story #4356 (Epic #4355). Poll-loop tuning for the merge/CI watch. pollIntervalMs is the cadence between check probes; maxPolls caps total probes before the watcher gives up; maxResumes caps how many times the watcher may resume after a transient stall; attachWindowMs bounds the wait for a required context to attach at all.",
|
|
1371
1371
|
"properties": {
|
|
1372
1372
|
"pollIntervalMs": {
|
|
1373
1373
|
"type": "integer",
|
|
@@ -1380,6 +1380,11 @@
|
|
|
1380
1380
|
"maxResumes": {
|
|
1381
1381
|
"type": "integer",
|
|
1382
1382
|
"minimum": 0
|
|
1383
|
+
},
|
|
1384
|
+
"attachWindowMs": {
|
|
1385
|
+
"type": "integer",
|
|
1386
|
+
"minimum": 1,
|
|
1387
|
+
"description": "Story #4890. How long (ms) the watch keeps re-resolving an EMPTY `gh pr checks --required` set before it stops waiting for a required context to attach. A ruleset attaches its contexts asynchronously and the arrival latency is set by the slowest one, so a required context that is an aggregator job gated on every other tier is the last to appear — measured at 16m52s on this repository. Default 1200000 (20 minutes). Raise it for a repository whose contexts arrive later still; exhausting the window is never reported as a red check (the watch exits 2, not-yet-started)."
|
|
1383
1388
|
}
|
|
1384
1389
|
},
|
|
1385
1390
|
"additionalProperties": false
|
|
@@ -167,6 +167,21 @@
|
|
|
167
167
|
"scope": "cumulative",
|
|
168
168
|
"substitutionKeys": []
|
|
169
169
|
},
|
|
170
|
+
"audit-baselines": {
|
|
171
|
+
"triggers": {
|
|
172
|
+
"gates": ["gate3"],
|
|
173
|
+
"keywords": [
|
|
174
|
+
"baseline",
|
|
175
|
+
"ratchet",
|
|
176
|
+
"floor",
|
|
177
|
+
"headroom",
|
|
178
|
+
"quality gate"
|
|
179
|
+
],
|
|
180
|
+
"filePatterns": ["baselines/**", ".agentrc.json"]
|
|
181
|
+
},
|
|
182
|
+
"scope": "cumulative",
|
|
183
|
+
"substitutionKeys": []
|
|
184
|
+
},
|
|
170
185
|
"audit-documentation": {
|
|
171
186
|
"triggers": {
|
|
172
187
|
"gates": ["gate1", "gate3"],
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://mandrel/.agents/schemas/baselines/audit-baselines-envelope.schema.json",
|
|
4
|
+
"title": "AuditBaselinesEnvelope",
|
|
5
|
+
"description": "Evidence envelope emitted by `.agents/scripts/audit-baselines.js` (Story #4902). Unlike its siblings in this directory it does NOT describe a committed baseline — it describes an engine's read-only report ABOUT the baseline surface, so it deliberately does not extend baseline-envelope.schema.json. Four sections: gateSurface (instrument health per kind), hotspots (ranked per-file clusters joining top-N outliers across gates), trend (rollup deltas from each baseline file's git history), and headroom (configured floor vs measured rollup).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"kind",
|
|
9
|
+
"schemaVersion",
|
|
10
|
+
"generatedAt",
|
|
11
|
+
"cwd",
|
|
12
|
+
"topN",
|
|
13
|
+
"configError",
|
|
14
|
+
"degradations",
|
|
15
|
+
"gateSurface",
|
|
16
|
+
"hotspots",
|
|
17
|
+
"trend",
|
|
18
|
+
"headroom"
|
|
19
|
+
],
|
|
20
|
+
"additionalProperties": false,
|
|
21
|
+
"properties": {
|
|
22
|
+
"kind": { "const": "audit-baselines-envelope" },
|
|
23
|
+
"schemaVersion": { "type": "string", "minLength": 1 },
|
|
24
|
+
"generatedAt": { "type": "string", "format": "date-time" },
|
|
25
|
+
"cwd": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"minLength": 1,
|
|
28
|
+
"description": "Repository root the engine ran against."
|
|
29
|
+
},
|
|
30
|
+
"topN": {
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"minimum": 0,
|
|
33
|
+
"description": "Per-gate bound on extracted outlier rows. The envelope never embeds a full baseline; crap.json alone is ~650KB of per-method rows."
|
|
34
|
+
},
|
|
35
|
+
"configError": {
|
|
36
|
+
"type": ["string", "null"],
|
|
37
|
+
"description": "Message from a failed config resolution. Non-null means floors, targetDirs, and ignoreGlobs were unavailable and the engine fell back to default baseline paths."
|
|
38
|
+
},
|
|
39
|
+
"degradations": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"description": "Which optional inputs were unavailable. Each degradation collapses its rank multiplier to exactly 1.0 and never changes the exit code.",
|
|
42
|
+
"required": ["gitHistory", "importGraph", "frictionLedger"],
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"properties": {
|
|
45
|
+
"gitHistory": { "type": "boolean" },
|
|
46
|
+
"importGraph": { "type": "boolean" },
|
|
47
|
+
"frictionLedger": { "type": "boolean" }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"gateSurface": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"description": "One entry per kind across BOTH halves of the surface: the closed delivery.quality.gates kinds and the out-of-band ratchet baselines the CI baselines job owns.",
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"required": [
|
|
56
|
+
"kind",
|
|
57
|
+
"surface",
|
|
58
|
+
"baselinePath",
|
|
59
|
+
"configured",
|
|
60
|
+
"baselineExists",
|
|
61
|
+
"stub",
|
|
62
|
+
"rowCount",
|
|
63
|
+
"measured",
|
|
64
|
+
"generatedAt",
|
|
65
|
+
"staleDays",
|
|
66
|
+
"staleCommits",
|
|
67
|
+
"surfaceStale",
|
|
68
|
+
"deadIgnoreGlobs",
|
|
69
|
+
"parseError"
|
|
70
|
+
],
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"properties": {
|
|
73
|
+
"kind": { "type": "string", "minLength": 1 },
|
|
74
|
+
"surface": { "enum": ["gate", "ratchet"] },
|
|
75
|
+
"baselinePath": { "type": "string", "minLength": 1 },
|
|
76
|
+
"configured": {
|
|
77
|
+
"type": "boolean",
|
|
78
|
+
"description": "A gate block for this kind is present in the resolved config."
|
|
79
|
+
},
|
|
80
|
+
"baselineExists": { "type": "boolean" },
|
|
81
|
+
"stub": {
|
|
82
|
+
"type": "boolean",
|
|
83
|
+
"description": "Zero rows AND an all-zero rollup — an instrument that passes every run vacuously. Both halves are required, so a ratchet baseline with nothing to report (e.g. zero import cycles) is never mistaken for a dead instrument."
|
|
84
|
+
},
|
|
85
|
+
"rowCount": {
|
|
86
|
+
"type": "integer",
|
|
87
|
+
"minimum": 0,
|
|
88
|
+
"description": "Rows AFTER the kind's per-file aggregation, so it is a file count for kinds whose on-disk grain is finer (dead exports are one row per {file, symbol}). Read `measured` for the quantity the instrument reports."
|
|
89
|
+
},
|
|
90
|
+
"measured": {
|
|
91
|
+
"type": ["object", "null"],
|
|
92
|
+
"description": "The whole-repo quantity in the unit the instrument measures — symbols for dead exports, bytes for context budget. Null when the baseline is absent or unreadable. Sits beside rowCount because the two legitimately disagree.",
|
|
93
|
+
"required": ["unit", "value"],
|
|
94
|
+
"additionalProperties": false,
|
|
95
|
+
"properties": {
|
|
96
|
+
"unit": { "type": "string", "minLength": 1 },
|
|
97
|
+
"value": { "type": "number" }
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"generatedAt": { "type": ["string", "null"] },
|
|
101
|
+
"staleDays": {
|
|
102
|
+
"type": ["integer", "null"],
|
|
103
|
+
"description": "Whole days since generatedAt. Null when the stamp is absent or unparseable — never a fabricated 0. Wall time only: a baseline can read 0 here and still predate the surface it scores, which is what staleCommits catches."
|
|
104
|
+
},
|
|
105
|
+
"staleCommits": {
|
|
106
|
+
"type": ["integer", "null"],
|
|
107
|
+
"description": "Commits touching the measured surface (the gate's targetDirs, else the baseline's own row paths) since the baseline was last committed. Null when git cannot answer or the surface is not path-keyed — an unknown count, never a reassuring 0.",
|
|
108
|
+
"minimum": 0
|
|
109
|
+
},
|
|
110
|
+
"surfaceStale": {
|
|
111
|
+
"type": ["boolean", "null"],
|
|
112
|
+
"description": "staleCommits > 0 — the baseline is older than the surface it scores, independent of wall-clock age. Null when staleCommits is unknown."
|
|
113
|
+
},
|
|
114
|
+
"deadIgnoreGlobs": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"description": "Configured ignoreGlobs matching zero files on disk.",
|
|
117
|
+
"items": { "type": "string" }
|
|
118
|
+
},
|
|
119
|
+
"parseError": { "type": ["string", "null"] }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"hotspots": {
|
|
124
|
+
"type": "array",
|
|
125
|
+
"description": "Per-file clusters, highest rank first. Severity sums across gate memberships, so a file that is an outlier in two gates outranks a single-gate outlier of equal per-gate severity.",
|
|
126
|
+
"items": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"required": [
|
|
129
|
+
"path",
|
|
130
|
+
"gates",
|
|
131
|
+
"gateKinds",
|
|
132
|
+
"gateCount",
|
|
133
|
+
"severityWeight",
|
|
134
|
+
"churnWeight",
|
|
135
|
+
"centralityWeight",
|
|
136
|
+
"frictionWeight",
|
|
137
|
+
"rank"
|
|
138
|
+
],
|
|
139
|
+
"additionalProperties": false,
|
|
140
|
+
"properties": {
|
|
141
|
+
"path": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"minLength": 1,
|
|
144
|
+
"description": "Cluster key. A repository file path for every kind except lighthouse (route) and bundle-size (bundle name)."
|
|
145
|
+
},
|
|
146
|
+
"gates": {
|
|
147
|
+
"type": "array",
|
|
148
|
+
"minItems": 1,
|
|
149
|
+
"items": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"required": [
|
|
152
|
+
"kind",
|
|
153
|
+
"metric",
|
|
154
|
+
"value",
|
|
155
|
+
"rowCount",
|
|
156
|
+
"severityWeight"
|
|
157
|
+
],
|
|
158
|
+
"additionalProperties": false,
|
|
159
|
+
"properties": {
|
|
160
|
+
"kind": { "type": "string", "minLength": 1 },
|
|
161
|
+
"metric": { "type": "string", "minLength": 1 },
|
|
162
|
+
"value": { "type": "number" },
|
|
163
|
+
"rowCount": { "type": "integer", "minimum": 1 },
|
|
164
|
+
"severityWeight": { "type": "number", "minimum": 0 }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"gateKinds": { "type": "array", "items": { "type": "string" } },
|
|
169
|
+
"gateCount": { "type": "integer", "minimum": 1 },
|
|
170
|
+
"severityWeight": { "type": "number", "minimum": 0 },
|
|
171
|
+
"churnWeight": { "type": "number", "minimum": 1 },
|
|
172
|
+
"centralityWeight": { "type": "number", "minimum": 1 },
|
|
173
|
+
"frictionWeight": { "type": "number", "minimum": 1 },
|
|
174
|
+
"rank": { "type": "number", "minimum": 0 }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"trend": {
|
|
179
|
+
"type": "array",
|
|
180
|
+
"description": "Newest-vs-previous rollup deltas parsed from each baseline file's git history. Empty when no history is readable.",
|
|
181
|
+
"items": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"required": [
|
|
184
|
+
"kind",
|
|
185
|
+
"baselinePath",
|
|
186
|
+
"sampleCount",
|
|
187
|
+
"from",
|
|
188
|
+
"to",
|
|
189
|
+
"deltas"
|
|
190
|
+
],
|
|
191
|
+
"additionalProperties": false,
|
|
192
|
+
"properties": {
|
|
193
|
+
"kind": { "type": "string", "minLength": 1 },
|
|
194
|
+
"baselinePath": { "type": "string", "minLength": 1 },
|
|
195
|
+
"sampleCount": { "type": "integer", "minimum": 2 },
|
|
196
|
+
"from": { "$ref": "#/definitions/commitRef" },
|
|
197
|
+
"to": { "$ref": "#/definitions/commitRef" },
|
|
198
|
+
"deltas": {
|
|
199
|
+
"type": "object",
|
|
200
|
+
"description": "Axis-by-axis `to - from`. Each key NAMES THE UNIT the delta is denominated in — `symbols` for dead exports, `bytes` for context budget, `filesTracked` where the metric is a non-additive score with no honest whole-repo sum. A kind carrying its own rollup keeps that rollup's axes.",
|
|
201
|
+
"additionalProperties": { "type": "number" }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"headroom": {
|
|
207
|
+
"type": "array",
|
|
208
|
+
"description": "Configured floor vs measured rollup per axis. Positive headroom is slack the floor could be tightened into; negative means the floor is already breached.",
|
|
209
|
+
"items": {
|
|
210
|
+
"type": "object",
|
|
211
|
+
"required": [
|
|
212
|
+
"kind",
|
|
213
|
+
"axis",
|
|
214
|
+
"floor",
|
|
215
|
+
"measured",
|
|
216
|
+
"direction",
|
|
217
|
+
"headroom"
|
|
218
|
+
],
|
|
219
|
+
"additionalProperties": false,
|
|
220
|
+
"properties": {
|
|
221
|
+
"kind": { "type": "string", "minLength": 1 },
|
|
222
|
+
"axis": { "type": "string", "minLength": 1 },
|
|
223
|
+
"floor": { "type": "number" },
|
|
224
|
+
"measured": { "type": ["number", "null"] },
|
|
225
|
+
"direction": { "enum": ["gte", "lte"] },
|
|
226
|
+
"headroom": { "type": ["number", "null"] }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
"definitions": {
|
|
232
|
+
"commitRef": {
|
|
233
|
+
"type": "object",
|
|
234
|
+
"required": ["sha", "committedAt"],
|
|
235
|
+
"additionalProperties": false,
|
|
236
|
+
"properties": {
|
|
237
|
+
"sha": { "type": "string", "minLength": 7 },
|
|
238
|
+
"committedAt": { "type": "string", "minLength": 1 }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"type": "string",
|
|
26
26
|
"description": "Optional per-kind stamp identifying the SCORING SEMANTICS that produced these rows — the dimension kernelVersion cannot express, because a kind's own scoring can change while the upstream package it stamps does not. A baseline whose stamp differs from the running scorer's is incomparable and the gate fails closed with re-baseline guidance rather than comparing across the boundary."
|
|
27
27
|
},
|
|
28
|
+
"tsTranspilerVersion": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Optional per-kind stamp identifying the TypeScript transpiler that produced the sourcemap these rows' line coordinates were resolved through (Story #4866). Only meaningful for kinds whose rows carry a line coordinate derived from transpiled sources; a transpiler change can move every such coordinate, and for kinds that key rows on a line that coordinate is half the row identity."
|
|
31
|
+
},
|
|
32
|
+
"provenanceStamped": {
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"description": "Optional per-kind POSITIVE assertion that the writer which produced these rows recorded each row's coordinate provenance (Story #4901). Only meaningful for kinds whose rows carry a line coordinate that may be transpiled. Absence is the only evidence a baseline written before provenance existed leaves behind — such a baseline claims by omission that every row is an original-source coordinate — so the kind's compat axis fails it closed with re-seed guidance rather than letting the comparator key rows against a coordinate space they are not in."
|
|
35
|
+
},
|
|
28
36
|
"rollup": {
|
|
29
37
|
"type": "object",
|
|
30
38
|
"description": "Aggregate metrics keyed by component. The '*' key is reserved for the whole-repo rollup and is REQUIRED; additional keys MAY be present, one per declared component.",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"type": "string",
|
|
11
11
|
"description": "Identifier for the per-method coverage-join semantics that produced these rows (Story #4775). Deliberately NOT constrained to the current value here: an old-semantics baseline must reach the gate's compat axis, which fails it closed with an explicit re-baseline command, rather than being rejected with a raw schema error."
|
|
12
12
|
},
|
|
13
|
+
"provenanceStamped": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"description": "Positive assertion that the writer which produced these rows recorded coordinate provenance per row (Story #4901). Absence is the ONLY evidence a pre-Story-#4866 baseline leaves — such a baseline claims by omission that every row is an original-source coordinate while some are transpiled. Deliberately optional and unconstrained here for the same reason as scoringSemantics: an unstamped baseline must reach the `provenance-unstamped` compat axis, which fails it closed with the re-seed command when it also carries transpiled-source rows, rather than dying on a raw schema error. A pure-JavaScript baseline never needs it."
|
|
16
|
+
},
|
|
13
17
|
"rollup": {
|
|
14
18
|
"type": "object",
|
|
15
19
|
"required": ["*"],
|
|
@@ -48,7 +52,15 @@
|
|
|
48
52
|
"path": { "type": "string" },
|
|
49
53
|
"method": { "type": "string" },
|
|
50
54
|
"startLine": { "type": "integer", "minimum": 1 },
|
|
51
|
-
"crap": { "type": "number", "minimum": 0 }
|
|
55
|
+
"crap": { "type": "number", "minimum": 0 },
|
|
56
|
+
"anonymous": {
|
|
57
|
+
"enum": [true],
|
|
58
|
+
"description": "Marks `method` as a DERIVED identity rather than a name the source carries (Story #4969). escomplex labels an unnamed function `<anon method-N>`, where N is a per-module counter, so inserting one anonymous function renumbers every later one and re-keys 34.6% of this baseline's rows; the derived identity is the method's enclosing-scope path plus its parameter list, which is invariant under edits elsewhere in the file. Written ONLY when true, mirroring `coordinateSystem`: a named row keeps the exact four-key shape it always had. Absence on a row whose `method` still looks anonymous is the signature of a half-migrated baseline — rows preserved verbatim by a diff-scoped refresh under an envelope stamped with the current semantics — which the `anon-identity-unstamped` compat axis fails closed."
|
|
59
|
+
},
|
|
60
|
+
"coordinateSystem": {
|
|
61
|
+
"enum": ["transpiled"],
|
|
62
|
+
"description": "Coordinate provenance of startLine (Story #4866). Written ONLY when the row kept transpiled coordinates because its sourcemap lookup did not resolve — the absent-means-'original' default keeps a pure-JavaScript baseline byte-identical, since JavaScript coordinates already ARE original-source coordinates. A row carrying this key is not comparable to one without it: the compare refuses to resolve the two through its nearest-line drift heuristic rather than manufacturing a regression from the gap between two coordinate systems."
|
|
63
|
+
}
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
}
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"regressions",
|
|
24
24
|
"newViolations",
|
|
25
25
|
"drifted",
|
|
26
|
+
"incomparable",
|
|
27
|
+
"provenanceMismatched",
|
|
28
|
+
"unscorable",
|
|
26
29
|
"removed",
|
|
27
30
|
"skippedNoCoverage",
|
|
28
31
|
"scope",
|
|
@@ -34,6 +37,21 @@
|
|
|
34
37
|
"regressions": { "type": "integer", "minimum": 0 },
|
|
35
38
|
"newViolations": { "type": "integer", "minimum": 0 },
|
|
36
39
|
"drifted": { "type": "integer", "minimum": 0 },
|
|
40
|
+
"incomparable": {
|
|
41
|
+
"type": "integer",
|
|
42
|
+
"minimum": 0,
|
|
43
|
+
"description": "Story #4866: scanned methods the compare REFUSED to resolve because their line coordinate provenance differs from the matching baseline rows'. Reported, never scored — resolving them through the nearest-line drift heuristic is what manufactures a regression no change can satisfy."
|
|
44
|
+
},
|
|
45
|
+
"provenanceMismatched": {
|
|
46
|
+
"type": "integer",
|
|
47
|
+
"minimum": 0,
|
|
48
|
+
"description": "Story #4871: scanned methods whose baseline candidates included at least one row expressed in a DIFFERENT line coordinate system, counted BEFORE the compare's provenance filter discards that evidence. This — not the drifted-row count, which by construction only ever counts rows whose coordinates agreed — is the quantity the unsound-basis backstop compares against its threshold."
|
|
49
|
+
},
|
|
50
|
+
"unscorable": {
|
|
51
|
+
"type": "integer",
|
|
52
|
+
"minimum": 0,
|
|
53
|
+
"description": "Story #4871: methods the scan could not score because no coverage artifact covered them. Reported rather than computed from an assumed zero percent coverage (which would drive CRAP maximal on untouched files), and excluded from the denominator of the unsound-basis ratio."
|
|
54
|
+
},
|
|
37
55
|
"removed": { "type": "integer", "minimum": 0 },
|
|
38
56
|
"skippedNoCoverage": { "type": "integer", "minimum": 0 },
|
|
39
57
|
"scope": {
|
|
@@ -97,6 +115,25 @@
|
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
}
|
|
118
|
+
},
|
|
119
|
+
"diagnostics": {
|
|
120
|
+
"type": "array",
|
|
121
|
+
"description": "Story #4866: named, gate-level findings emitted INSTEAD of per-method verdicts when the gate has established that no verdict it could produce would be meaningful — an incomparable baseline, or a comparison basis whose drifted-row ratio proves the two sides disagree on line coordinates. Present only in that case; `violations` is then empty and the preview gate exits 0.",
|
|
122
|
+
"items": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"required": ["name", "message"],
|
|
125
|
+
"additionalProperties": false,
|
|
126
|
+
"properties": {
|
|
127
|
+
"name": {
|
|
128
|
+
"type": "string",
|
|
129
|
+
"enum": [
|
|
130
|
+
"crap-unsound-comparison-basis",
|
|
131
|
+
"crap-baseline-incompatible"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"message": { "type": "string" }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
100
137
|
}
|
|
101
138
|
}
|
|
102
139
|
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
"$id": "https://github.com/dsj1984/mandrel/blob/main/.agents/schemas/model-attribution.schema.json",
|
|
4
4
|
"title": "ModelAttribution",
|
|
5
5
|
"description": "Payload of the <!-- structured:model-attribution --> comment upserted onto a Task ticket at the moment it transitions to agent::executing (Story #2813). One entry per Task. Story- and Epic-level breakdowns are derived at query time from the child Tasks' attribution comments — there is no Story/Epic-scope emission.",
|
|
6
|
+
"x-mandrel-uncompiled": {
|
|
7
|
+
"reason": "Deliberate: this document is the SSOT for the shape, but no AJV instance compiles it. The framework does not pull AJV into the structured-comment path — that path uses hand-rolled shape guards — so the runtime gate below mirrors this document by hand. Keep the two in step: an edit here is only real once the validator enforces it. Declared in-file per Story #4938 so a reader is never left inferring authority from the file's mere existence.",
|
|
8
|
+
"runtimeGate": ".agents/scripts/lib/orchestration/model-attribution.js#validateModelAttributionPayload"
|
|
9
|
+
},
|
|
6
10
|
"type": "object",
|
|
7
11
|
"additionalProperties": false,
|
|
8
12
|
"required": ["kind", "ticketId", "model", "source", "recordedAt"],
|