mandrel 2.30.0 → 2.32.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 +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -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/ci-remediation.md +44 -1
- 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 +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- 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 +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- 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 +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- 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 +0 -1
- 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/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -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/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/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-entry-sync.js +469 -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 +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- 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/push.js +17 -2
- 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 +65 -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-sizing.js +1 -1
- 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/story-body.js +12 -59
- 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/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/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 +0 -2
- 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/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -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 +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- 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 +49 -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 +12 -3
- 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
|
@@ -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
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// .agents/scripts/lib/baselines/orphan-pruner.js
|
|
2
|
+
//
|
|
3
|
+
// Story #5012 — the measurement-free remedy that makes hard-failing on a stale
|
|
4
|
+
// baseline row defensible.
|
|
5
|
+
//
|
|
6
|
+
// The scope gate (`check-baseline-scope.js`) blocks a PR that leaves a row
|
|
7
|
+
// pointing at a file it deleted. A hard gate is only fair while its remedy is
|
|
8
|
+
// cheap, and the existing remedy — re-run the scorer, re-derive the baseline —
|
|
9
|
+
// costs a full coverage run or a full-tree MI re-score for what is arithmetic
|
|
10
|
+
// on a row set. This pruner is that arithmetic.
|
|
11
|
+
//
|
|
12
|
+
// ## Measurement-free by contract
|
|
13
|
+
//
|
|
14
|
+
// Three prohibitions, each guarding a way the file could start lying:
|
|
15
|
+
//
|
|
16
|
+
// 1. **Never add a row.** Adding one means claiming a measurement nobody
|
|
17
|
+
// took. Producing rows stays the producers' exclusive job.
|
|
18
|
+
// 2. **Never restamp `generatedAt`.** A fresh stamp over rows nobody
|
|
19
|
+
// re-measured is precisely the failure an age check exists to catch — the
|
|
20
|
+
// envelope would claim to describe today's tree on the strength of a
|
|
21
|
+
// deletion. The pruner carries the original stamp through untouched.
|
|
22
|
+
// 3. **Never delete a row it cannot prove inert.** Only two classes qualify:
|
|
23
|
+
// the file is absent from disk, or the file is no longer matched by the
|
|
24
|
+
// gate's own `targetDirs` / `ignoreGlobs`. Everything else survives.
|
|
25
|
+
//
|
|
26
|
+
// ## Degrade, never guess
|
|
27
|
+
//
|
|
28
|
+
// A degraded inventory (`files: null` — unreadable `.c8rc.cjs`, absent
|
|
29
|
+
// `targetDirs`) means scope is unknown, not empty. Treating it as empty would
|
|
30
|
+
// make every row look out-of-scope and hand the pruner the whole baseline. So
|
|
31
|
+
// an unreadable scope config falls back to **orphan-only** pruning: the
|
|
32
|
+
// absent-from-disk class still resolves off the filesystem and is still safe,
|
|
33
|
+
// while the out-of-scope class is suspended until scope can be read again.
|
|
34
|
+
|
|
35
|
+
import fs from 'node:fs';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
|
|
38
|
+
import { getKindModule } from './kernel.js';
|
|
39
|
+
import { _internals as readerInternals } from './reader.js';
|
|
40
|
+
import { EXTRA_REASONS } from './scope-assert.js';
|
|
41
|
+
import {
|
|
42
|
+
buildScopeInventory,
|
|
43
|
+
isFileKeyed,
|
|
44
|
+
SCOPE_KINDS,
|
|
45
|
+
} from './scope-inventory.js';
|
|
46
|
+
import { writeFile } from './writer.js';
|
|
47
|
+
|
|
48
|
+
/** Every kind whose rows key on a repo-relative file path. */
|
|
49
|
+
export const PRUNABLE_KINDS = Object.freeze(SCOPE_KINDS.filter(isFileKeyed));
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Partition a row set into the rows to keep and the rows to drop.
|
|
53
|
+
*
|
|
54
|
+
* Pure given its injected `existsOnDisk` predicate. `inScope` of `null` is the
|
|
55
|
+
* degraded case: only the absent-from-disk class is prunable.
|
|
56
|
+
*
|
|
57
|
+
* @param {{
|
|
58
|
+
* rows: Array<Record<string, unknown>>,
|
|
59
|
+
* inScope: Set<string> | null,
|
|
60
|
+
* existsOnDisk: (relPath: string) => boolean,
|
|
61
|
+
* }} params
|
|
62
|
+
* @returns {{ keep: Array<object>, removed: Array<{ path: string, reason: string }> }}
|
|
63
|
+
*/
|
|
64
|
+
export function planPrune({ rows, inScope, existsOnDisk } = {}) {
|
|
65
|
+
const keep = [];
|
|
66
|
+
const removed = [];
|
|
67
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
68
|
+
const key = row?.path;
|
|
69
|
+
if (typeof key !== 'string' || key.length === 0) {
|
|
70
|
+
keep.push(row);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!existsOnDisk(key)) {
|
|
74
|
+
removed.push({ path: key, reason: EXTRA_REASONS.ABSENT });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (inScope !== null && !inScope.has(key)) {
|
|
78
|
+
removed.push({ path: key, reason: EXTRA_REASONS.OUT_OF_SCOPE });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
keep.push(row);
|
|
82
|
+
}
|
|
83
|
+
return { keep, removed };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Prune one parsed envelope and recompute its `rollup` through the kind's own
|
|
88
|
+
* arithmetic (`kinds/<kind>.js#rollup`), so the pruned file stays internally
|
|
89
|
+
* consistent and still validates against its schema. Recomputing by a private
|
|
90
|
+
* formula here would let the rollup and the rows describe different trees.
|
|
91
|
+
*
|
|
92
|
+
* A rollup carrying component buckets beyond `*` is refused rather than
|
|
93
|
+
* recomputed: the component globs that produced those buckets live in gate
|
|
94
|
+
* config this module is not handed, and emitting a `*`-only rollup would
|
|
95
|
+
* silently delete them.
|
|
96
|
+
*
|
|
97
|
+
* @param {{
|
|
98
|
+
* kind: string,
|
|
99
|
+
* envelope: object,
|
|
100
|
+
* inventory: { files: string[] | null },
|
|
101
|
+
* existsOnDisk?: (relPath: string) => boolean,
|
|
102
|
+
* }} params
|
|
103
|
+
* @returns {{
|
|
104
|
+
* envelope: object | null,
|
|
105
|
+
* removed: Array<{ path: string, reason: string }>,
|
|
106
|
+
* skipped: boolean,
|
|
107
|
+
* reason: string | null,
|
|
108
|
+
* }}
|
|
109
|
+
*/
|
|
110
|
+
export function pruneEnvelope({
|
|
111
|
+
kind,
|
|
112
|
+
envelope,
|
|
113
|
+
inventory,
|
|
114
|
+
existsOnDisk,
|
|
115
|
+
} = {}) {
|
|
116
|
+
const rollupKeys = Object.keys(envelope?.rollup ?? {});
|
|
117
|
+
if (rollupKeys.some((key) => key !== '*')) {
|
|
118
|
+
return {
|
|
119
|
+
envelope: null,
|
|
120
|
+
removed: [],
|
|
121
|
+
skipped: true,
|
|
122
|
+
reason: `rollup carries component buckets (${rollupKeys.join(', ')}); prune them with the producer`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const inScope = inventory?.files === null ? null : new Set(inventory.files);
|
|
126
|
+
const { keep, removed } = planPrune({
|
|
127
|
+
rows: envelope?.rows ?? [],
|
|
128
|
+
inScope,
|
|
129
|
+
existsOnDisk: existsOnDisk ?? (() => true),
|
|
130
|
+
});
|
|
131
|
+
if (removed.length === 0) {
|
|
132
|
+
return { envelope: null, removed: [], skipped: false, reason: null };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
envelope: {
|
|
136
|
+
...envelope,
|
|
137
|
+
// `generatedAt` is carried by the spread, deliberately unmodified.
|
|
138
|
+
rollup: getKindModule(kind).rollup(keep, []),
|
|
139
|
+
rows: keep,
|
|
140
|
+
},
|
|
141
|
+
removed,
|
|
142
|
+
skipped: false,
|
|
143
|
+
reason: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Read and JSON-parse a baseline file. Returns `null` when the file is absent
|
|
149
|
+
* (a kind this repository does not use) and throws only on malformed JSON,
|
|
150
|
+
* which is a real defect the caller should surface rather than swallow.
|
|
151
|
+
*
|
|
152
|
+
* @param {string} absPath
|
|
153
|
+
* @param {typeof fs} fsImpl
|
|
154
|
+
* @returns {object | null}
|
|
155
|
+
*/
|
|
156
|
+
function readEnvelope(absPath, fsImpl) {
|
|
157
|
+
let raw;
|
|
158
|
+
try {
|
|
159
|
+
raw = fsImpl.readFileSync(absPath, 'utf8');
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return JSON.parse(raw);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Prune one kind end to end: resolve its baseline path, read it, build the
|
|
168
|
+
* scope inventory, plan, and (unless `check`) write.
|
|
169
|
+
*
|
|
170
|
+
* @param {object} params
|
|
171
|
+
* @returns {object} One entry of the report's `kinds` array.
|
|
172
|
+
*/
|
|
173
|
+
function pruneKind({ kind, cwd, quality, check, fsImpl, requireFn }) {
|
|
174
|
+
const absPath = readerInternals.resolveBaselinePath(kind, { cwd });
|
|
175
|
+
const relPath = path.relative(cwd, absPath).split(path.sep).join('/');
|
|
176
|
+
const envelope = readEnvelope(absPath, fsImpl);
|
|
177
|
+
if (envelope === null) {
|
|
178
|
+
return { kind, path: relPath, present: false, removed: [], written: false };
|
|
179
|
+
}
|
|
180
|
+
const inventory = buildScopeInventory({ kind, cwd, quality, requireFn });
|
|
181
|
+
const result = pruneEnvelope({
|
|
182
|
+
kind,
|
|
183
|
+
envelope,
|
|
184
|
+
inventory,
|
|
185
|
+
existsOnDisk: (rel) => fsImpl.existsSync(path.resolve(cwd, rel)),
|
|
186
|
+
});
|
|
187
|
+
const base = {
|
|
188
|
+
kind,
|
|
189
|
+
path: relPath,
|
|
190
|
+
present: true,
|
|
191
|
+
degraded: inventory.degraded,
|
|
192
|
+
degradedReason: inventory.degraded ? inventory.reason : null,
|
|
193
|
+
skipped: result.skipped,
|
|
194
|
+
skipReason: result.reason,
|
|
195
|
+
removed: result.removed,
|
|
196
|
+
written: false,
|
|
197
|
+
};
|
|
198
|
+
if (result.envelope === null || check) return base;
|
|
199
|
+
writeFile(absPath, result.envelope, { fsImpl });
|
|
200
|
+
return { ...base, written: true };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Prune every file-keyed baseline under `cwd`.
|
|
205
|
+
*
|
|
206
|
+
* @param {{
|
|
207
|
+
* cwd?: string,
|
|
208
|
+
* kinds?: string[],
|
|
209
|
+
* check?: boolean,
|
|
210
|
+
* quality?: object,
|
|
211
|
+
* fsImpl?: typeof fs,
|
|
212
|
+
* requireFn?: (id: string) => object,
|
|
213
|
+
* }} params
|
|
214
|
+
* @returns {{ kinds: Array<object>, removedCount: number, writtenCount: number, check: boolean }}
|
|
215
|
+
*/
|
|
216
|
+
export function runPrune({
|
|
217
|
+
cwd = process.cwd(),
|
|
218
|
+
kinds = PRUNABLE_KINDS,
|
|
219
|
+
check = false,
|
|
220
|
+
quality,
|
|
221
|
+
fsImpl = fs,
|
|
222
|
+
requireFn,
|
|
223
|
+
} = {}) {
|
|
224
|
+
const results = kinds.map((kind) =>
|
|
225
|
+
pruneKind({ kind, cwd, quality, check, fsImpl, requireFn }),
|
|
226
|
+
);
|
|
227
|
+
return {
|
|
228
|
+
check,
|
|
229
|
+
kinds: results,
|
|
230
|
+
removedCount: results.reduce((sum, r) => sum + r.removed.length, 0),
|
|
231
|
+
writtenCount: results.filter((r) => r.written).length,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -76,11 +76,10 @@
|
|
|
76
76
|
* @module .agents/scripts/lib/baselines/refresh-service
|
|
77
77
|
*/
|
|
78
78
|
|
|
79
|
-
import { execFile as nodeExecFile } from 'node:child_process';
|
|
80
79
|
import nodeFs from 'node:fs';
|
|
81
80
|
import { createRequire } from 'node:module';
|
|
82
81
|
import path from 'node:path';
|
|
83
|
-
import {
|
|
82
|
+
import { execFileCaptureAsync } from '../child-exec.js';
|
|
84
83
|
import { getQuality, resolveConfig } from '../config-resolver.js';
|
|
85
84
|
import {
|
|
86
85
|
buildScopePredicate,
|
|
@@ -107,8 +106,6 @@ import {
|
|
|
107
106
|
|
|
108
107
|
const nodeRequire = createRequire(import.meta.url);
|
|
109
108
|
|
|
110
|
-
const execFileAsync = promisify(nodeExecFile);
|
|
111
|
-
|
|
112
109
|
/**
|
|
113
110
|
* Kinds the refresh service knows how to dispatch. Stays in lockstep with
|
|
114
111
|
* the per-kind modules under `.agents/scripts/lib/baselines/kinds/`.
|
|
@@ -574,8 +571,9 @@ function assertRequiredScopeRows({
|
|
|
574
571
|
}
|
|
575
572
|
|
|
576
573
|
/**
|
|
577
|
-
* Default git-diff derivation for the diff-scope path.
|
|
578
|
-
*
|
|
574
|
+
* Default git-diff derivation for the diff-scope path. Runs through the shared
|
|
575
|
+
* child-process surface ([`child-exec.js`](../child-exec.js)) — `execFile`,
|
|
576
|
+
* never a shell — and only the canonical two-dot range `baseRef..headRef`:
|
|
579
577
|
* triple-dot is intentionally avoided so the result reflects exactly the
|
|
580
578
|
* files that differ between the two refs at the time of the call.
|
|
581
579
|
*
|
|
@@ -585,10 +583,10 @@ function assertRequiredScopeRows({
|
|
|
585
583
|
async function defaultGitDiff({ baseRef, headRef, cwd }) {
|
|
586
584
|
const range = `${baseRef}..${headRef}`;
|
|
587
585
|
try {
|
|
588
|
-
const { stdout } = await
|
|
586
|
+
const { stdout } = await execFileCaptureAsync(
|
|
589
587
|
'git',
|
|
590
588
|
['diff', '--name-only', range],
|
|
591
|
-
{ cwd
|
|
589
|
+
{ cwd },
|
|
592
590
|
);
|
|
593
591
|
return stdout
|
|
594
592
|
.split(/\r?\n/)
|