mandrel 2.31.0 → 2.33.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 +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +12 -9
- package/.agents/docs/agentrc-reference.json +63 -58
- package/.agents/docs/configuration.md +275 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +2 -2
- package/.agents/schemas/agentrc.schema.json +1955 -1410
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +16 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/audit-to-stories.js +160 -41
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +159 -0
- package/.agents/scripts/check-lifecycle-lint.js +108 -124
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +81 -35
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/runners.js +38 -16
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/dependency-parser.js +20 -7
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/findings/provenance-field.js +135 -0
- package/.agents/scripts/lib/findings/route-finding.js +57 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-config-resolver.js +181 -0
- package/.agents/scripts/lib/knip-entry-sync.js +508 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/footer-block.js +97 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -81
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +21 -7
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/stories-wave-tick.js +192 -9
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +28 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +30 -2
- package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
- package/.agents/workflows/helpers/plan-reference.md +76 -0
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +58 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +14 -5
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -40,46 +40,26 @@
|
|
|
40
40
|
* @module lib/baselines/envelope
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
-
import
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
import addFormats from 'ajv-formats';
|
|
48
|
-
|
|
49
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
43
|
+
import {
|
|
44
|
+
BASELINE_KIND_SCHEMA_FILES,
|
|
45
|
+
buildBaselineSchemaAjv,
|
|
46
|
+
} from '../baseline-schema-registry.js';
|
|
50
47
|
|
|
51
48
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
49
|
+
* Canonical list of kinds the shared envelope supports, **derived** from the
|
|
50
|
+
* schema registry rather than restated (Story #5002).
|
|
51
|
+
*
|
|
52
|
+
* It used to be a hand-kept literal that happened to mirror
|
|
53
|
+
* `BASELINE_KIND_SCHEMA_FILES` name for name. Deriving it makes
|
|
54
|
+
* "`kind` is known" and "`kind`'s schema is registered" the same fact, so
|
|
55
|
+
* `getValidator` below cannot be handed a kind the AJV instance never
|
|
56
|
+
* compiled — the not-registered branch is gone because the state is
|
|
57
|
+
* unreachable, not because it was hidden.
|
|
56
58
|
*/
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
'..',
|
|
60
|
-
'..',
|
|
61
|
-
'..',
|
|
62
|
-
'schemas',
|
|
63
|
-
'baselines',
|
|
59
|
+
export const KNOWN_KINDS = Object.freeze(
|
|
60
|
+
BASELINE_KIND_SCHEMA_FILES.map((file) => file.replace(/\.schema\.json$/, '')),
|
|
64
61
|
);
|
|
65
62
|
|
|
66
|
-
/**
|
|
67
|
-
* Canonical list of kinds the shared envelope supports. The writer's
|
|
68
|
-
* per-kind module list lives in `kinds/index.js`; this constant is the
|
|
69
|
-
* envelope's view of the same set, kept here to break the import cycle
|
|
70
|
-
* (`kinds/<kind>` imports envelope; envelope only needs the names).
|
|
71
|
-
*/
|
|
72
|
-
export const KNOWN_KINDS = Object.freeze([
|
|
73
|
-
'lint',
|
|
74
|
-
'coverage',
|
|
75
|
-
'crap',
|
|
76
|
-
'maintainability',
|
|
77
|
-
'mutation',
|
|
78
|
-
'lighthouse',
|
|
79
|
-
'bundle-size',
|
|
80
|
-
'duplication',
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
63
|
function schemaRefFor(kind) {
|
|
84
64
|
return `.agents/schemas/baselines/${kind}.schema.json`;
|
|
85
65
|
}
|
|
@@ -193,36 +173,37 @@ export function buildEnvelope({
|
|
|
193
173
|
}
|
|
194
174
|
|
|
195
175
|
/**
|
|
196
|
-
* Lazy AJV instance
|
|
197
|
-
*
|
|
176
|
+
* Lazy AJV instance, built by the canonical
|
|
177
|
+
* [`baseline-schema-registry`](../baseline-schema-registry.js) — the ONE
|
|
178
|
+
* place a baselines-directory schema is read off disk and compiled
|
|
179
|
+
* (Story #5002). This module used to keep its own `SCHEMAS_DIR` + AJV +
|
|
180
|
+
* per-kind compile cache alongside the registry's, so the writer and
|
|
181
|
+
* `baselines/reader.js` validated the same envelope through two independently
|
|
182
|
+
* configured instances; a registration added to one was invisible to the
|
|
183
|
+
* other. One builder means the writer and the reader cannot disagree about
|
|
184
|
+
* what a valid envelope is.
|
|
185
|
+
*
|
|
186
|
+
* Memoised: building it reads eleven schema files, and a single baseline
|
|
187
|
+
* regeneration calls `assertEnvelope` once per write plus once per re-read.
|
|
198
188
|
*/
|
|
199
189
|
let _ajv = null;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
addFormats(ajv);
|
|
206
|
-
const envelopeSchema = JSON.parse(
|
|
207
|
-
fs.readFileSync(
|
|
208
|
-
path.join(SCHEMAS_DIR, 'baseline-envelope.schema.json'),
|
|
209
|
-
'utf8',
|
|
210
|
-
),
|
|
211
|
-
);
|
|
212
|
-
ajv.addSchema(envelopeSchema, 'baseline-envelope.schema.json');
|
|
213
|
-
_ajv = ajv;
|
|
214
|
-
return ajv;
|
|
190
|
+
function ajv() {
|
|
191
|
+
if (_ajv === null) {
|
|
192
|
+
_ajv = buildBaselineSchemaAjv();
|
|
193
|
+
}
|
|
194
|
+
return _ajv;
|
|
215
195
|
}
|
|
216
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Look up the pre-registered validator for a per-kind schema. Total over
|
|
199
|
+
* `KNOWN_KINDS` — that list is derived from the registry's own filenames, so
|
|
200
|
+
* every kind reaching here has a compiled schema.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} kind
|
|
203
|
+
* @returns {import('ajv').ValidateFunction}
|
|
204
|
+
*/
|
|
217
205
|
function getValidator(kind) {
|
|
218
|
-
|
|
219
|
-
const ajv = getAjv();
|
|
220
|
-
const schema = JSON.parse(
|
|
221
|
-
fs.readFileSync(path.join(SCHEMAS_DIR, `${kind}.schema.json`), 'utf8'),
|
|
222
|
-
);
|
|
223
|
-
const validate = ajv.compile(schema);
|
|
224
|
-
_validators.set(kind, validate);
|
|
225
|
-
return validate;
|
|
206
|
+
return ajv().getSchema(`${kind}.schema.json`);
|
|
226
207
|
}
|
|
227
208
|
|
|
228
209
|
/**
|
|
@@ -22,6 +22,13 @@
|
|
|
22
22
|
// `ref` and `file` are passed as separate argv tokens, never
|
|
23
23
|
// interpolated into a shell command.
|
|
24
24
|
//
|
|
25
|
+
// Story #5009 moved the spawn itself, the stdout ceiling and the
|
|
26
|
+
// failure-message shape out to `lib/child-exec.js` — the one child-process
|
|
27
|
+
// surface — so this module now owns only what is genuinely its own: the
|
|
28
|
+
// `(ref, file)` LRU, the exit-code semantics below, and the test seam. The
|
|
29
|
+
// `__setSpawnRunner` seam still injects a mock runner; it is threaded through
|
|
30
|
+
// the shared wrapper as `run` rather than calling `spawnSync` directly.
|
|
31
|
+
//
|
|
25
32
|
// Exit semantics are intentionally narrow:
|
|
26
33
|
//
|
|
27
34
|
// - Exit 0: stdout is the file contents, returned as a UTF-8 string.
|
|
@@ -39,7 +46,7 @@
|
|
|
39
46
|
// means "path absent at ref". Callers may therefore treat a throw as a
|
|
40
47
|
// hard failure without having to re-diagnose it.
|
|
41
48
|
|
|
42
|
-
import {
|
|
49
|
+
import { formatChildFailure, spawnChild } from '../child-exec.js';
|
|
43
50
|
|
|
44
51
|
// ---------------------------------------------------------------------------
|
|
45
52
|
// LRU cache. Small footprint — even a fan-out across all seven baseline
|
|
@@ -53,21 +60,13 @@ import { spawnSync } from 'node:child_process';
|
|
|
53
60
|
const DEFAULT_MAX_ENTRIES = 64;
|
|
54
61
|
|
|
55
62
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `
|
|
59
|
-
* baseline legitimately grows past that (a real consumer's crap baseline
|
|
60
|
-
* measured 1,178,910 bytes), at which point the child is killed —
|
|
61
|
-
* `status: null`, `signal: 'SIGTERM'`, `error.code: 'ENOBUFS'` — and the
|
|
62
|
-
* read fails for a reason that has nothing to do with the repository.
|
|
63
|
+
* Injected spawn runner, or `undefined` to use the shared surface's real one.
|
|
64
|
+
* Story #4914's explicit 64 MB ceiling still applies to both git reads below —
|
|
65
|
+
* it is now `child-exec.js`'s `MAX_BUFFER_BYTES`, applied by `spawnChild`.
|
|
63
66
|
*
|
|
64
|
-
*
|
|
65
|
-
* `run-test-profile.js:87`, `audit-baselines/trend.js:61` and
|
|
66
|
-
* `audit-baselines/weights.js:65`. This module was the outlier.
|
|
67
|
+
* @type {Function | undefined}
|
|
67
68
|
*/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let _spawnSync = spawnSync;
|
|
69
|
+
let _spawnRunner;
|
|
71
70
|
let _cache = new Map();
|
|
72
71
|
let _maxEntries = DEFAULT_MAX_ENTRIES;
|
|
73
72
|
|
|
@@ -80,12 +79,12 @@ let _maxEntries = DEFAULT_MAX_ENTRIES;
|
|
|
80
79
|
* the contract.
|
|
81
80
|
*
|
|
82
81
|
* @param {object} [opts]
|
|
83
|
-
* @param {
|
|
84
|
-
* @param {number} [opts.maxEntries]
|
|
82
|
+
* @param {Function} [opts.spawn] - Mock spawnSync.
|
|
83
|
+
* @param {number} [opts.maxEntries] - Override LRU capacity.
|
|
85
84
|
*/
|
|
86
85
|
export function __setSpawnRunner(opts = {}) {
|
|
87
86
|
if (opts.spawn) {
|
|
88
|
-
|
|
87
|
+
_spawnRunner = opts.spawn;
|
|
89
88
|
}
|
|
90
89
|
if (typeof opts.maxEntries === 'number') {
|
|
91
90
|
_maxEntries = opts.maxEntries;
|
|
@@ -98,7 +97,7 @@ export function __setSpawnRunner(opts = {}) {
|
|
|
98
97
|
* to keep state from leaking across files.
|
|
99
98
|
*/
|
|
100
99
|
export function __resetForTests() {
|
|
101
|
-
|
|
100
|
+
_spawnRunner = undefined;
|
|
102
101
|
_maxEntries = DEFAULT_MAX_ENTRIES;
|
|
103
102
|
_cache = new Map();
|
|
104
103
|
}
|
|
@@ -179,13 +178,10 @@ export function readBaseFromGit(ref, file, opts = {}) {
|
|
|
179
178
|
|
|
180
179
|
const cwd = opts.cwd ?? process.cwd();
|
|
181
180
|
const spec = `${ref}:${file}`;
|
|
182
|
-
const result =
|
|
181
|
+
const result = spawnChild('git', ['show', spec], {
|
|
182
|
+
run: _spawnRunner,
|
|
183
183
|
cwd,
|
|
184
|
-
stdio: 'pipe',
|
|
185
|
-
encoding: 'utf-8',
|
|
186
|
-
shell: false,
|
|
187
184
|
env: cleanGitEnv(),
|
|
188
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
189
185
|
});
|
|
190
186
|
|
|
191
187
|
// `child_process.spawnSync` returns `status: null` when the child died
|
|
@@ -206,9 +202,12 @@ export function readBaseFromGit(ref, file, opts = {}) {
|
|
|
206
202
|
return null;
|
|
207
203
|
}
|
|
208
204
|
|
|
209
|
-
const stderr = (result.stderr || '').toString().trim();
|
|
210
205
|
throw new Error(
|
|
211
|
-
|
|
206
|
+
formatChildFailure({
|
|
207
|
+
label: `readBaseFromGit: git show ${spec}`,
|
|
208
|
+
status,
|
|
209
|
+
stderr: result.stderr,
|
|
210
|
+
}),
|
|
212
211
|
);
|
|
213
212
|
}
|
|
214
213
|
|
|
@@ -222,9 +221,10 @@ export function readBaseFromGit(ref, file, opts = {}) {
|
|
|
222
221
|
* Restricting the log to `-- <file>` means the returned subjects already
|
|
223
222
|
* satisfy the "commit whose diff touches the baseline file" half of the
|
|
224
223
|
* predicate; the caller only has to match the tag substring against each
|
|
225
|
-
* subject. Runs through the same `
|
|
226
|
-
* it inherits the identical env scrubbing
|
|
227
|
-
*
|
|
224
|
+
* subject. Runs through the same shared `spawnChild` surface and injected
|
|
225
|
+
* runner as `readBaseFromGit`, so it inherits the identical env scrubbing
|
|
226
|
+
* (drop inherited `GIT_*`), the same stdout ceiling, and the spawn-not-exec
|
|
227
|
+
* security posture (argv tokens, `shell: false`).
|
|
228
228
|
*
|
|
229
229
|
* Returns an empty array whenever the range cannot be walked (missing base
|
|
230
230
|
* ref, git failure, empty range) — the acknowledgment path treats "no
|
|
@@ -244,17 +244,10 @@ export function readRangeSubjectsTouchingFile(baseRef, file, opts = {}) {
|
|
|
244
244
|
const cwd = opts.cwd ?? process.cwd();
|
|
245
245
|
let result;
|
|
246
246
|
try {
|
|
247
|
-
result =
|
|
247
|
+
result = spawnChild(
|
|
248
248
|
'git',
|
|
249
249
|
['log', `${baseRef}..HEAD`, '--format=%s', '--', file],
|
|
250
|
-
{
|
|
251
|
-
cwd,
|
|
252
|
-
stdio: 'pipe',
|
|
253
|
-
encoding: 'utf-8',
|
|
254
|
-
shell: false,
|
|
255
|
-
env: cleanGitEnv(),
|
|
256
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
257
|
-
},
|
|
250
|
+
{ run: _spawnRunner, cwd, env: cleanGitEnv() },
|
|
258
251
|
);
|
|
259
252
|
} catch {
|
|
260
253
|
return [];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _crap-new-method-gate.js — the policy `kinds/crap.js#compareCrap` applies to
|
|
3
|
+
* a method that has no baseline row (Story #5002).
|
|
4
|
+
*
|
|
5
|
+
* Underscore-prefixed like `_shared-metric.js`: this is a helper for the
|
|
6
|
+
* per-kind modules in this directory, not a kind of its own.
|
|
7
|
+
*
|
|
8
|
+
* A leaf — it imports nothing, so `kinds/crap.js` can consume it without
|
|
9
|
+
* adding an edge to anything the comparator does not already reach.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The set of scanned files in which **every** measured method resolved 0%
|
|
14
|
+
* coverage — the observable signature of a source no test loads at all.
|
|
15
|
+
*
|
|
16
|
+
* Deliberately derived from the scan rows rather than plumbed down from the
|
|
17
|
+
* coverage artifact: the rows are the only thing both the preview gate and the
|
|
18
|
+
* close-validation projection hand the comparator, and "the whole file is at
|
|
19
|
+
* zero" is exactly what an all-zero istanbul entry produces here.
|
|
20
|
+
*
|
|
21
|
+
* A row carrying `coverage: null` is skipped, not counted as a zero — an
|
|
22
|
+
* absent observation is not a measured one, and letting it vote would hand the
|
|
23
|
+
* relief below to a file nothing measured at all.
|
|
24
|
+
*
|
|
25
|
+
* @param {Array<{file: string, coverage?: number|null}>} currentRows
|
|
26
|
+
* @returns {Set<string>}
|
|
27
|
+
*/
|
|
28
|
+
export function deriveUncoveredFiles(currentRows) {
|
|
29
|
+
const allZeroByFile = new Map();
|
|
30
|
+
for (const row of currentRows ?? []) {
|
|
31
|
+
if (typeof row?.coverage !== 'number') continue;
|
|
32
|
+
const soFar = allZeroByFile.get(row.file);
|
|
33
|
+
allZeroByFile.set(row.file, (soFar ?? true) && row.coverage === 0);
|
|
34
|
+
}
|
|
35
|
+
const uncovered = new Set();
|
|
36
|
+
for (const [file, allZero] of allZeroByFile) {
|
|
37
|
+
if (allZero) uncovered.add(file);
|
|
38
|
+
}
|
|
39
|
+
return uncovered;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The score a **new** method is judged against `newMethodCeiling` with.
|
|
44
|
+
*
|
|
45
|
+
* Normally the measured CRAP. In a file whose every method scored 0%, the
|
|
46
|
+
* complexity term `c` alone.
|
|
47
|
+
*
|
|
48
|
+
* **Why.** `crap = c²·(1 − cov)³ + c` collapses to `c² + c` at zero coverage,
|
|
49
|
+
* so the default ceiling of 30 caps a *brand-new* method in an untested file
|
|
50
|
+
* at `c ≈ 5`. For the code that is untestable by construction — argv parsing,
|
|
51
|
+
* `spawn` wiring, a CLI's top-level `main()` — that is not a quality signal
|
|
52
|
+
* but a fragmentation tax: the only way past it is to shatter cohesive wiring
|
|
53
|
+
* into sub-five-branch fragments, which raises the file's method count and its
|
|
54
|
+
* reader's cost while measuring nothing. The coverage term still governs every
|
|
55
|
+
* method in a file the tests *do* reach, and a genuinely sprawling function is
|
|
56
|
+
* still refused — `c` alone must clear the ceiling.
|
|
57
|
+
*
|
|
58
|
+
* This is a **gate** decision, not a scoring one: the persisted baseline row
|
|
59
|
+
* keeps its measured `c² + c`, so the regression arm on the next run compares
|
|
60
|
+
* like with like.
|
|
61
|
+
*
|
|
62
|
+
* @param {{file: string, crap: number, cyclomatic?: number}} row
|
|
63
|
+
* @param {Set<string>} uncoveredFiles
|
|
64
|
+
* @returns {number}
|
|
65
|
+
*/
|
|
66
|
+
export function newMethodGateScore(row, uncoveredFiles) {
|
|
67
|
+
if (!uncoveredFiles.has(row.file)) return row.crap;
|
|
68
|
+
return Number.isFinite(row.cyclomatic) ? row.cyclomatic : row.crap;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Assemble a new-method violation.
|
|
73
|
+
*
|
|
74
|
+
* `gateScore` is written **only** when the ceiling was judged against
|
|
75
|
+
* something other than the measured `crap` — the same write-only-when-
|
|
76
|
+
* non-default idiom `projectRow` uses for its row markers — so the printer can
|
|
77
|
+
* name the number that actually failed instead of one nothing tested.
|
|
78
|
+
*
|
|
79
|
+
* @param {object} row
|
|
80
|
+
* @param {number} ceiling
|
|
81
|
+
* @param {number} gateScore
|
|
82
|
+
* @returns {object}
|
|
83
|
+
*/
|
|
84
|
+
export function buildNewViolation(row, ceiling, gateScore) {
|
|
85
|
+
return {
|
|
86
|
+
...row,
|
|
87
|
+
kind: 'new',
|
|
88
|
+
baseline: null,
|
|
89
|
+
ceiling,
|
|
90
|
+
...(gateScore === row.crap ? {} : { gateScore }),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Render the measured-vs-gated half of a new-method violation line.
|
|
96
|
+
*
|
|
97
|
+
* @param {{crap: number, gateScore?: number}} v
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
export function formatNewViolationMeasure(v) {
|
|
101
|
+
if (v.gateScore === undefined) return `crap=${v.crap.toFixed(2)}`;
|
|
102
|
+
return `complexity=${v.gateScore} (file has no coverage; crap=${v.crap.toFixed(2)})`;
|
|
103
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _crap-read.js — the CRAP baseline read path (Story #5002).
|
|
3
|
+
*
|
|
4
|
+
* Underscore-prefixed like `_shared-metric.js`: a helper for the per-kind
|
|
5
|
+
* modules in this directory, not a kind of its own. `kinds/crap.js` re-exports
|
|
6
|
+
* `loadCrapBaseline` so existing importers keep one door.
|
|
7
|
+
*
|
|
8
|
+
* **This module is the whole read path, and there is only one.** `crap-utils.js`
|
|
9
|
+
* used to carry a second one — `projectCrapEnvelopeToLegacy` plus its
|
|
10
|
+
* `COMPAT_STAMP_*` allow-lists — so `check-baselines` (through
|
|
11
|
+
* `baselines/reader.js`) and the `quality-preview` pre-commit arm (through that
|
|
12
|
+
* projection) fed the same compat axes from two hand-maintained field lists. A
|
|
13
|
+
* stamp added to one and not the other yielded two opposite verdicts on one
|
|
14
|
+
* envelope, three times over: Story #4866 (`scoringSemantics`,
|
|
15
|
+
* `tsTranspilerVersion`), #4969 (`rows[].anonymous`), #4986
|
|
16
|
+
* (`provenanceStamped`). Every one of those axes keys on a POSITIVE marker, so
|
|
17
|
+
* a dropped field read `undefined` and failed the baseline closed with a remedy
|
|
18
|
+
* that could not work — re-deriving it wrote the stamp the read path then
|
|
19
|
+
* discarded. With one reader that drift class is structurally impossible, so no
|
|
20
|
+
* allow-list needs maintaining.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { readBaselineAtRef } from '../../baseline-loader.js';
|
|
25
|
+
import { resolveEscomplexVersion } from '../../crap-utils.js';
|
|
26
|
+
import { loadBaseline } from '../../gates/baseline-store.js';
|
|
27
|
+
import {
|
|
28
|
+
loadFile as loadBaselineFile,
|
|
29
|
+
load as loadBaselineKind,
|
|
30
|
+
} from '../reader.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Read the committed CRAP baseline off the working tree through
|
|
34
|
+
* `baselines/reader.js` and project it onto the `file`-keyed shape the
|
|
35
|
+
* comparator and the compat axes consume.
|
|
36
|
+
*
|
|
37
|
+
* `baselinePath` selects the explicit-path reader variant (the worktree /
|
|
38
|
+
* epic-ref callers always know their own path); without one the reader
|
|
39
|
+
* resolves the configured location for the `crap` kind itself.
|
|
40
|
+
*
|
|
41
|
+
* `escomplexVersion` is back-filled from the running scorer exactly as the
|
|
42
|
+
* deleted projection stamped it — the v2 envelope does not carry the field, and
|
|
43
|
+
* `escomplex-mismatch` is a fatal axis, so omitting it would fail every
|
|
44
|
+
* baseline closed on a value that was never on disk.
|
|
45
|
+
*
|
|
46
|
+
* Returns `null` on any read/parse/schema failure; the preview gate maps that
|
|
47
|
+
* to "no baseline" and fails open, as it always did.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately module-local: `loadCrapBaseline`'s `readFromTree` default is the
|
|
50
|
+
* single production door to it, and tests inject their own loader.
|
|
51
|
+
*
|
|
52
|
+
* @param {{baselinePath?: string, projectRoot?: string}} [opts]
|
|
53
|
+
* @returns {object|null}
|
|
54
|
+
*/
|
|
55
|
+
function readCrapBaselineFromTree({ baselinePath, projectRoot } = {}) {
|
|
56
|
+
const cwd = projectRoot ?? process.cwd();
|
|
57
|
+
let envelope;
|
|
58
|
+
try {
|
|
59
|
+
envelope = baselinePath
|
|
60
|
+
? loadBaselineFile(
|
|
61
|
+
path.isAbsolute(baselinePath)
|
|
62
|
+
? baselinePath
|
|
63
|
+
: path.resolve(cwd, baselinePath),
|
|
64
|
+
{ kind: 'crap' },
|
|
65
|
+
)
|
|
66
|
+
: loadBaselineKind('crap', { cwd });
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
kernelVersion: envelope.kernelVersion,
|
|
72
|
+
escomplexVersion: resolveEscomplexVersion(),
|
|
73
|
+
scoringSemantics: envelope.scoringSemantics ?? null,
|
|
74
|
+
tsTranspilerVersion:
|
|
75
|
+
typeof envelope.tsTranspilerVersion === 'string'
|
|
76
|
+
? envelope.tsTranspilerVersion
|
|
77
|
+
: null,
|
|
78
|
+
provenanceStamped: envelope.provenanceStamped,
|
|
79
|
+
rows: (envelope.rows ?? []).map(projectBaselineRow),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Re-key one on-disk row (`path`) onto the `file` field `compareCrap` matches
|
|
85
|
+
* on, carrying the two write-only-when-non-default row markers verbatim.
|
|
86
|
+
*
|
|
87
|
+
* Both markers are BASELINE facts. Dropping `anonymous` would leave every
|
|
88
|
+
* re-keyed row looking like an unmarked anonymous one — precisely the shape the
|
|
89
|
+
* `anon-identity-unstamped` axis fails closed (Story #4969) — and dropping
|
|
90
|
+
* `coordinateSystem` would let the comparator drift-resolve across two
|
|
91
|
+
* coordinate systems (Story #4866).
|
|
92
|
+
*
|
|
93
|
+
* @param {{path: string, method: string, startLine: number, crap: number,
|
|
94
|
+
* coordinateSystem?: string, anonymous?: boolean}} row
|
|
95
|
+
* @returns {object}
|
|
96
|
+
*/
|
|
97
|
+
function projectBaselineRow(row) {
|
|
98
|
+
return {
|
|
99
|
+
crap: row.crap,
|
|
100
|
+
file: row.path,
|
|
101
|
+
method: row.method,
|
|
102
|
+
startLine: row.startLine,
|
|
103
|
+
...(row.coordinateSystem === undefined
|
|
104
|
+
? {}
|
|
105
|
+
: { coordinateSystem: row.coordinateSystem }),
|
|
106
|
+
...(row.anonymous === undefined ? {} : { anonymous: row.anonymous }),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Pure helper: resolve the CRAP baseline either from the working tree (via
|
|
112
|
+
* `readCrapBaselineFromTree`) or, when `epicRef` is supplied, from
|
|
113
|
+
* `git show <epicRef>:<baselinePath>` via `readBaselineAtRef`.
|
|
114
|
+
*
|
|
115
|
+
* Story #1120 threads `epic/<id>` into close-validation so the comparison runs
|
|
116
|
+
* against the Epic-branch HEAD's committed baseline. This helper delegates the
|
|
117
|
+
* read to baseline-store and applies the CRAP shape-check +
|
|
118
|
+
* `tsTranspilerVersion` back-fill on top.
|
|
119
|
+
*/
|
|
120
|
+
export function loadCrapBaseline({
|
|
121
|
+
baselinePath,
|
|
122
|
+
epicRef,
|
|
123
|
+
readAtRef = readBaselineAtRef,
|
|
124
|
+
readFromTree = readCrapBaselineFromTree,
|
|
125
|
+
logger = console,
|
|
126
|
+
}) {
|
|
127
|
+
const parsed = loadBaseline({
|
|
128
|
+
baselinePath,
|
|
129
|
+
epicRef,
|
|
130
|
+
readAtRef,
|
|
131
|
+
readFromTree,
|
|
132
|
+
logger,
|
|
133
|
+
label: 'CRAP',
|
|
134
|
+
});
|
|
135
|
+
// No-epicRef path delegates to readFromTree which already applies the
|
|
136
|
+
// shape-check + tsTranspilerVersion back-fill, so a tree read returns either
|
|
137
|
+
// a valid envelope or null. Epic-ref path bypasses that helper — shape-check
|
|
138
|
+
// + back-fill happens here.
|
|
139
|
+
if (!epicRef) return parsed;
|
|
140
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
if (typeof parsed.kernelVersion !== 'string') return null;
|
|
144
|
+
if (typeof parsed.escomplexVersion !== 'string') return null;
|
|
145
|
+
if (!Array.isArray(parsed.rows)) return null;
|
|
146
|
+
if (typeof parsed.tsTranspilerVersion !== 'string') {
|
|
147
|
+
parsed.tsTranspilerVersion = '0.0.0';
|
|
148
|
+
}
|
|
149
|
+
return parsed;
|
|
150
|
+
}
|
|
@@ -13,15 +13,12 @@
|
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { readBaselineAtRef } from '../../baseline-loader.js';
|
|
17
16
|
import {
|
|
18
17
|
COORDINATE_ORIGINAL,
|
|
19
18
|
COORDINATE_TRANSPILED,
|
|
20
19
|
deriveFixGuidance,
|
|
21
20
|
} from '../../crap-engine.js';
|
|
22
21
|
import { isAnonymousMethodLabel } from '../../crap-method-identity.js';
|
|
23
|
-
import { getCrapBaseline } from '../../crap-utils.js';
|
|
24
|
-
import { loadBaseline } from '../../gates/baseline-store.js';
|
|
25
22
|
import { Logger } from '../../Logger.js';
|
|
26
23
|
import { resolveTsTranspilerVersion } from '../../transpile.js';
|
|
27
24
|
import {
|
|
@@ -31,6 +28,12 @@ import {
|
|
|
31
28
|
} from '../envelope.js';
|
|
32
29
|
import { canonicalise } from '../path-canon.js';
|
|
33
30
|
import { mergeRowsByScope } from '../scope.js';
|
|
31
|
+
import {
|
|
32
|
+
buildNewViolation,
|
|
33
|
+
deriveUncoveredFiles,
|
|
34
|
+
formatNewViolationMeasure,
|
|
35
|
+
newMethodGateScore,
|
|
36
|
+
} from './_crap-new-method-gate.js';
|
|
34
37
|
import {
|
|
35
38
|
makeAggregate,
|
|
36
39
|
makeCompare,
|
|
@@ -42,6 +45,11 @@ import {
|
|
|
42
45
|
export const name = 'crap';
|
|
43
46
|
export const keyField = 'path';
|
|
44
47
|
|
|
48
|
+
// The CRAP baseline read path (Story #5002) lives in `_crap-read.js` — the ONE
|
|
49
|
+
// door, re-exported here so `preview-gates.js` and every existing importer keep
|
|
50
|
+
// reaching it through this module.
|
|
51
|
+
export { loadCrapBaseline } from './_crap-read.js';
|
|
52
|
+
|
|
45
53
|
const __filename = fileURLToPath(import.meta.url);
|
|
46
54
|
|
|
47
55
|
/**
|
|
@@ -245,18 +253,12 @@ function crapRowKey(row) {
|
|
|
245
253
|
return `${row.path}::${row.method}@${row.startLine}`;
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
// `methodIdentityKey` / `indexBaselineRowsByFile` (Story #4981)
|
|
249
|
-
// crap-baseline-
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
// existing importers of this module keep a single door to the identity key
|
|
255
|
-
// `crapRowKey` composes with the file path.
|
|
256
|
-
export {
|
|
257
|
-
indexBaselineRowsByFile,
|
|
258
|
-
methodIdentityKey,
|
|
259
|
-
} from '../../crap-baseline-index.js';
|
|
256
|
+
// `methodIdentityKey` / `indexBaselineRowsByFile` (Story #4981) are now
|
|
257
|
+
// module-local to `crap-baseline-join.js` (Story #5002): after that module
|
|
258
|
+
// absorbed the per-file queue wiring, both callers are inside it, so the
|
|
259
|
+
// exports — and the re-export that used to live here — were reachable from
|
|
260
|
+
// tests alone. `resolveIncrementalContext` is the production door to the
|
|
261
|
+
// index; `crapRowKey` above is the composite key it halves.
|
|
260
262
|
|
|
261
263
|
/**
|
|
262
264
|
* Pure stabilizer for s-stability-epsilon (Story #1964). CRAP rows match
|
|
@@ -395,6 +397,9 @@ export function checkCrapRegression(row, baseline, tolerance, kind) {
|
|
|
395
397
|
* (`crap: null` / `coverage: null`, or an explicit `unscorable: true`) carries
|
|
396
398
|
* no measurement to compare. It is bucketed and counted, and it is excluded
|
|
397
399
|
* from `comparable` so it cannot dilute any ratio derived from this result.
|
|
400
|
+
*
|
|
401
|
+
* **A new method in a wholly-uncovered file is gated on complexity alone**
|
|
402
|
+
* (Story #5002) — see `newMethodGateScore`.
|
|
398
403
|
*/
|
|
399
404
|
export function compareCrap({
|
|
400
405
|
currentRows,
|
|
@@ -402,6 +407,7 @@ export function compareCrap({
|
|
|
402
407
|
newMethodCeiling,
|
|
403
408
|
tolerance,
|
|
404
409
|
}) {
|
|
410
|
+
const uncoveredFiles = deriveUncoveredFiles(currentRows);
|
|
405
411
|
const exactIndex = new Map();
|
|
406
412
|
const methodIndex = new Map();
|
|
407
413
|
for (const b of baselineRows ?? []) {
|
|
@@ -475,14 +481,10 @@ export function compareCrap({
|
|
|
475
481
|
continue;
|
|
476
482
|
}
|
|
477
483
|
|
|
478
|
-
|
|
484
|
+
const gateScore = newMethodGateScore(row, uncoveredFiles);
|
|
485
|
+
if (gateScore > newMethodCeiling + tolerance) {
|
|
479
486
|
newViolations += 1;
|
|
480
|
-
violations.push(
|
|
481
|
-
...row,
|
|
482
|
-
kind: 'new',
|
|
483
|
-
baseline: null,
|
|
484
|
-
ceiling: newMethodCeiling,
|
|
485
|
-
});
|
|
487
|
+
violations.push(buildNewViolation(row, newMethodCeiling, gateScore));
|
|
486
488
|
}
|
|
487
489
|
}
|
|
488
490
|
|
|
@@ -892,48 +894,6 @@ export function assertBaselineCompatible(baseline, ctx = {}) {
|
|
|
892
894
|
return null;
|
|
893
895
|
}
|
|
894
896
|
|
|
895
|
-
/**
|
|
896
|
-
* Pure helper: resolve the CRAP baseline either from the working tree
|
|
897
|
-
* (via `getCrapBaseline`) or, when `epicRef` is supplied, from
|
|
898
|
-
* `git show <epicRef>:<baselinePath>` via `readBaselineAtRef`.
|
|
899
|
-
*
|
|
900
|
-
* Story #1120 threads `epic/<id>` into close-validation so the
|
|
901
|
-
* comparison runs against the Epic-branch HEAD's committed baseline.
|
|
902
|
-
* This helper delegates the read to baseline-store and applies the CRAP
|
|
903
|
-
* shape-check + `tsTranspilerVersion` back-fill on top.
|
|
904
|
-
*/
|
|
905
|
-
export function loadCrapBaseline({
|
|
906
|
-
baselinePath,
|
|
907
|
-
epicRef,
|
|
908
|
-
readAtRef = readBaselineAtRef,
|
|
909
|
-
readFromTree = getCrapBaseline,
|
|
910
|
-
logger = console,
|
|
911
|
-
}) {
|
|
912
|
-
const parsed = loadBaseline({
|
|
913
|
-
baselinePath,
|
|
914
|
-
epicRef,
|
|
915
|
-
readAtRef,
|
|
916
|
-
readFromTree,
|
|
917
|
-
logger,
|
|
918
|
-
label: 'CRAP',
|
|
919
|
-
});
|
|
920
|
-
// No-epicRef path delegates to readFromTree which already applies the
|
|
921
|
-
// shape-check + tsTranspilerVersion back-fill, so a tree read returns
|
|
922
|
-
// either a valid envelope or null. Epic-ref path bypasses that helper
|
|
923
|
-
// — shape-check + back-fill happens here.
|
|
924
|
-
if (!epicRef) return parsed;
|
|
925
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
926
|
-
return null;
|
|
927
|
-
}
|
|
928
|
-
if (typeof parsed.kernelVersion !== 'string') return null;
|
|
929
|
-
if (typeof parsed.escomplexVersion !== 'string') return null;
|
|
930
|
-
if (!Array.isArray(parsed.rows)) return null;
|
|
931
|
-
if (typeof parsed.tsTranspilerVersion !== 'string') {
|
|
932
|
-
parsed.tsTranspilerVersion = '0.0.0';
|
|
933
|
-
}
|
|
934
|
-
return parsed;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
897
|
/**
|
|
938
898
|
* Build the structured `--json` report envelope.
|
|
939
899
|
*
|
|
@@ -1061,7 +1021,7 @@ export function printViolation(v) {
|
|
|
1061
1021
|
`[CRAP] ❌ NEW-METHOD over ceiling: ${v.file}::${v.method} (line ${v.startLine})`,
|
|
1062
1022
|
);
|
|
1063
1023
|
Logger.error(
|
|
1064
|
-
`
|
|
1024
|
+
` ${formatNewViolationMeasure(v)} > ceiling=${v.ceiling} (c=${v.cyclomatic}, cov=${v.coverage.toFixed(2)})`,
|
|
1065
1025
|
);
|
|
1066
1026
|
return;
|
|
1067
1027
|
}
|