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,223 @@
|
|
|
1
|
+
// .agents/scripts/lib/baselines/scope-assert.js
|
|
2
|
+
//
|
|
3
|
+
// Story #5012 — the pure both-directions row-set assertion, plus the
|
|
4
|
+
// merge-base attribution that decides which half of a divergence is this
|
|
5
|
+
// change set's fault.
|
|
6
|
+
//
|
|
7
|
+
// `check-baselines.js` answers "did a measured value regress?". Nothing
|
|
8
|
+
// answered "does this baseline still describe the tree?" — so a row could
|
|
9
|
+
// point at a file deleted six months ago, or an in-scope file could carry no
|
|
10
|
+
// row at all, and every gate stayed green. Both directions are checked here:
|
|
11
|
+
//
|
|
12
|
+
// - **missing** — an in-scope file with no row. Only asserted for kinds
|
|
13
|
+
// whose producer emits one row per in-scope file
|
|
14
|
+
// (`scope-inventory.js#KIND_SCOPE_POLICY`).
|
|
15
|
+
// - **extra** — a row whose file is gone from disk (`absent`) or no longer
|
|
16
|
+
// matched by the gate's own `targetDirs` / `ignoreGlobs` (`out-of-scope`).
|
|
17
|
+
//
|
|
18
|
+
// ## Why attribution exists
|
|
19
|
+
//
|
|
20
|
+
// Whole-tree equality on the PR path is unusable: the moment anyone lands an
|
|
21
|
+
// in-scope file, every open PR reds on divergence its author did not create
|
|
22
|
+
// and cannot fix from their branch. So the gate blocks only on divergence
|
|
23
|
+
// attributable to `merge-base(base, HEAD)..HEAD`, and reports the inherited
|
|
24
|
+
// remainder as a warning that costs nothing to carry.
|
|
25
|
+
//
|
|
26
|
+
// Attribution can be wrong in one safe direction only, so the resolution
|
|
27
|
+
// **fails towards strict**: no resolvable base, a HEAD not ahead of its base,
|
|
28
|
+
// or a change set that edits a baseline or the config defining its scope all
|
|
29
|
+
// promote every finding to fatal. The last case is the load-bearing one — once
|
|
30
|
+
// a branch has rewritten the scope rules themselves, "which side of the
|
|
31
|
+
// merge-base introduced this row" is no longer a question the diff can answer.
|
|
32
|
+
//
|
|
33
|
+
// Pure module: no filesystem, no git, no config resolution. Every input is
|
|
34
|
+
// handed in already resolved, which is what lets the whole attribution matrix
|
|
35
|
+
// be unit-tested without a fixture repository.
|
|
36
|
+
|
|
37
|
+
/** A row whose keyed file no longer exists on disk. */
|
|
38
|
+
const REASON_ABSENT = 'absent';
|
|
39
|
+
/** A row whose file exists but is no longer inside the gate's scope. */
|
|
40
|
+
const REASON_OUT_OF_SCOPE = 'out-of-scope';
|
|
41
|
+
|
|
42
|
+
export const EXTRA_REASONS = Object.freeze({
|
|
43
|
+
ABSENT: REASON_ABSENT,
|
|
44
|
+
OUT_OF_SCOPE: REASON_OUT_OF_SCOPE,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Strictness reasons, in resolution order. Exported so the CLI can render the
|
|
49
|
+
* cause without restating the strings.
|
|
50
|
+
*/
|
|
51
|
+
export const STRICT_REASONS = Object.freeze({
|
|
52
|
+
NO_BASE: 'no-resolvable-base',
|
|
53
|
+
NOT_AHEAD: 'head-not-ahead-of-base',
|
|
54
|
+
BASELINE_EDITED: 'change-set-edits-a-baseline',
|
|
55
|
+
SCOPE_CONFIG_EDITED: 'change-set-edits-a-scope-config',
|
|
56
|
+
ATTRIBUTABLE: 'attributable-to-merge-base-range',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Collect the distinct key values a row set carries, skipping malformed rows.
|
|
61
|
+
*
|
|
62
|
+
* @param {Array<Record<string, unknown>>} rows
|
|
63
|
+
* @param {string} keyField
|
|
64
|
+
* @returns {Set<string>}
|
|
65
|
+
*/
|
|
66
|
+
function rowKeys(rows, keyField) {
|
|
67
|
+
const keys = new Set();
|
|
68
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
69
|
+
const value = row?.[keyField];
|
|
70
|
+
if (typeof value === 'string' && value.length > 0) keys.add(value);
|
|
71
|
+
}
|
|
72
|
+
return keys;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compare a baseline's row set against its in-scope inventory.
|
|
77
|
+
*
|
|
78
|
+
* Direction gating is the inventory's call, not this function's: a kind whose
|
|
79
|
+
* policy omits `missing` produces no missing findings even when its row set is
|
|
80
|
+
* far sparser than the tree, because for that kind sparseness is correct.
|
|
81
|
+
*
|
|
82
|
+
* @param {{
|
|
83
|
+
* inventory: { kind: string, keyField: string, directions: readonly string[],
|
|
84
|
+
* files: string[] | null, degraded: boolean, reason: string | null },
|
|
85
|
+
* rows: Array<Record<string, unknown>>,
|
|
86
|
+
* existsOnDisk?: (relPath: string) => boolean,
|
|
87
|
+
* }} params
|
|
88
|
+
* @returns {{
|
|
89
|
+
* kind: string,
|
|
90
|
+
* skipped: boolean,
|
|
91
|
+
* reason: string | null,
|
|
92
|
+
* missing: string[],
|
|
93
|
+
* extra: Array<{ path: string, reason: string }>,
|
|
94
|
+
* }}
|
|
95
|
+
*/
|
|
96
|
+
export function assertScope({ inventory, rows, existsOnDisk } = {}) {
|
|
97
|
+
const kind = inventory?.kind ?? 'unknown';
|
|
98
|
+
const directions = inventory?.directions ?? [];
|
|
99
|
+
if (directions.length === 0 || inventory?.files === null) {
|
|
100
|
+
return {
|
|
101
|
+
kind,
|
|
102
|
+
skipped: true,
|
|
103
|
+
reason: inventory?.reason ?? `${kind} declares no assertable direction`,
|
|
104
|
+
missing: [],
|
|
105
|
+
extra: [],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const keyField = inventory.keyField;
|
|
110
|
+
const inScope = new Set(inventory.files);
|
|
111
|
+
const keyed = rowKeys(rows, keyField);
|
|
112
|
+
const onDisk = typeof existsOnDisk === 'function' ? existsOnDisk : () => true;
|
|
113
|
+
|
|
114
|
+
const missing = directions.includes('missing')
|
|
115
|
+
? inventory.files.filter((file) => !keyed.has(file)).sort()
|
|
116
|
+
: [];
|
|
117
|
+
|
|
118
|
+
const extra = [];
|
|
119
|
+
if (directions.includes('extra')) {
|
|
120
|
+
for (const key of [...keyed].sort()) {
|
|
121
|
+
if (inScope.has(key)) continue;
|
|
122
|
+
extra.push({
|
|
123
|
+
path: key,
|
|
124
|
+
reason: onDisk(key) ? REASON_OUT_OF_SCOPE : REASON_ABSENT,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { kind, skipped: false, reason: null, missing, extra };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Decide whether this run blocks on every finding or only on the attributable
|
|
134
|
+
* ones. Fails towards strict — each early return below is a case where the
|
|
135
|
+
* merge-base range cannot be trusted to explain the divergence.
|
|
136
|
+
*
|
|
137
|
+
* @param {{
|
|
138
|
+
* base?: string | null,
|
|
139
|
+
* aheadOfBase?: boolean,
|
|
140
|
+
* changedFiles?: string[],
|
|
141
|
+
* baselinePaths?: string[],
|
|
142
|
+
* scopeConfigPaths?: string[],
|
|
143
|
+
* }} params
|
|
144
|
+
* @returns {{ strict: boolean, reason: string }}
|
|
145
|
+
*/
|
|
146
|
+
export function resolveStrictness({
|
|
147
|
+
base,
|
|
148
|
+
aheadOfBase,
|
|
149
|
+
changedFiles = [],
|
|
150
|
+
baselinePaths = [],
|
|
151
|
+
scopeConfigPaths = [],
|
|
152
|
+
} = {}) {
|
|
153
|
+
if (typeof base !== 'string' || base.length === 0) {
|
|
154
|
+
return { strict: true, reason: STRICT_REASONS.NO_BASE };
|
|
155
|
+
}
|
|
156
|
+
if (aheadOfBase !== true) {
|
|
157
|
+
return { strict: true, reason: STRICT_REASONS.NOT_AHEAD };
|
|
158
|
+
}
|
|
159
|
+
const changed = new Set(changedFiles);
|
|
160
|
+
if (baselinePaths.some((file) => changed.has(file))) {
|
|
161
|
+
return { strict: true, reason: STRICT_REASONS.BASELINE_EDITED };
|
|
162
|
+
}
|
|
163
|
+
if (scopeConfigPaths.some((file) => changed.has(file))) {
|
|
164
|
+
return { strict: true, reason: STRICT_REASONS.SCOPE_CONFIG_EDITED };
|
|
165
|
+
}
|
|
166
|
+
return { strict: false, reason: STRICT_REASONS.ATTRIBUTABLE };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Split a kind's findings into the fatal set and the warning set.
|
|
171
|
+
*
|
|
172
|
+
* Under `strict`, everything is fatal. Otherwise a finding is fatal only when
|
|
173
|
+
* this change set created it:
|
|
174
|
+
*
|
|
175
|
+
* - a **missing** row for a file the range ADDED — the branch introduced an
|
|
176
|
+
* in-scope file and left it unmeasured;
|
|
177
|
+
* - an **extra** row for a file the range DELETED or renamed away — the
|
|
178
|
+
* branch removed the file and left its row behind.
|
|
179
|
+
*
|
|
180
|
+
* Everything else is inherited: real, worth reporting, and not this author's
|
|
181
|
+
* to fix from this branch.
|
|
182
|
+
*
|
|
183
|
+
* @param {{
|
|
184
|
+
* missing?: string[],
|
|
185
|
+
* extra?: Array<{ path: string, reason: string }>,
|
|
186
|
+
* added?: string[],
|
|
187
|
+
* removed?: string[],
|
|
188
|
+
* strict?: boolean,
|
|
189
|
+
* }} params
|
|
190
|
+
* @returns {{
|
|
191
|
+
* fatal: { missing: string[], extra: Array<{ path: string, reason: string }> },
|
|
192
|
+
* warning: { missing: string[], extra: Array<{ path: string, reason: string }> },
|
|
193
|
+
* fatalCount: number,
|
|
194
|
+
* warningCount: number,
|
|
195
|
+
* }}
|
|
196
|
+
*/
|
|
197
|
+
export function attributeDivergence({
|
|
198
|
+
missing = [],
|
|
199
|
+
extra = [],
|
|
200
|
+
added = [],
|
|
201
|
+
removed = [],
|
|
202
|
+
strict = false,
|
|
203
|
+
} = {}) {
|
|
204
|
+
const addedSet = new Set(added);
|
|
205
|
+
const removedSet = new Set(removed);
|
|
206
|
+
const isFatalMissing = (file) => strict || addedSet.has(file);
|
|
207
|
+
const isFatalExtra = (finding) => strict || removedSet.has(finding.path);
|
|
208
|
+
|
|
209
|
+
const fatal = {
|
|
210
|
+
missing: missing.filter(isFatalMissing),
|
|
211
|
+
extra: extra.filter(isFatalExtra),
|
|
212
|
+
};
|
|
213
|
+
const warning = {
|
|
214
|
+
missing: missing.filter((file) => !isFatalMissing(file)),
|
|
215
|
+
extra: extra.filter((finding) => !isFatalExtra(finding)),
|
|
216
|
+
};
|
|
217
|
+
return {
|
|
218
|
+
fatal,
|
|
219
|
+
warning,
|
|
220
|
+
fatalCount: fatal.missing.length + fatal.extra.length,
|
|
221
|
+
warningCount: warning.missing.length + warning.extra.length,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// .agents/scripts/lib/baselines/scope-inventory.js
|
|
2
|
+
//
|
|
3
|
+
// Story #5012 — the ONE in-scope file inventory the honesty surface reads.
|
|
4
|
+
//
|
|
5
|
+
// A ratchet gate can be green while measuring almost nothing: nothing checks
|
|
6
|
+
// that a committed baseline's row set still describes the tree it claims to
|
|
7
|
+
// describe. Both instruments that now do — `check-baseline-scope.js` (assert)
|
|
8
|
+
// and `prune-baseline-orphans.js` (remedy) — resolve "which files is this kind
|
|
9
|
+
// supposed to have rows for?" through this module, so the gate and the pruner
|
|
10
|
+
// cannot disagree about scope and start fighting each other.
|
|
11
|
+
//
|
|
12
|
+
// ## Recompute from the gate's own config, never from a second walker
|
|
13
|
+
//
|
|
14
|
+
// The inventory is derived from **the same configuration the kind's refresh
|
|
15
|
+
// scorer reads**, through the same helpers:
|
|
16
|
+
//
|
|
17
|
+
// - `coverage` — `.c8rc.cjs` `include` / `exclude`, applied with
|
|
18
|
+
// `coverage-baseline.js#buildScopePredicate`, exactly as
|
|
19
|
+
// `refresh-service.js#buildDefaultCoverageScorer` does.
|
|
20
|
+
// - every other file-keyed kind — `delivery.quality.gates.<kind>.targetDirs`
|
|
21
|
+
// / `.ignoreGlobs`, walked with `maintainability-utils.js#scanDirectory`,
|
|
22
|
+
// exactly as `refresh-service.js#buildDefaultMaintainabilityScorer` does.
|
|
23
|
+
//
|
|
24
|
+
// A second walker written here would report the two implementations
|
|
25
|
+
// disagreeing as divergence — the same failure mode
|
|
26
|
+
// `check-baseline-drift.js` avoids by re-scoring through
|
|
27
|
+
// `resolveDefaultScorer` rather than a private scorer.
|
|
28
|
+
//
|
|
29
|
+
// ## Measurement-free by contract
|
|
30
|
+
//
|
|
31
|
+
// Nothing in this module runs a scorer, reads `coverage/coverage-final.json`,
|
|
32
|
+
// or needs a test run to have happened. It walks directories and matches
|
|
33
|
+
// globs. That is what makes the gate runnable on any checkout, which is in
|
|
34
|
+
// turn what makes hard-failing on a stale row defensible.
|
|
35
|
+
|
|
36
|
+
import { createRequire } from 'node:module';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
|
|
39
|
+
import { buildScopePredicate } from '../coverage-baseline.js';
|
|
40
|
+
import { scanDirectory } from '../maintainability-utils.js';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Per-kind classification of which divergence directions may be asserted.
|
|
44
|
+
*
|
|
45
|
+
* **Only a kind whose producer emits one row per in-scope file may assert the
|
|
46
|
+
* MISSING direction.** This is not a conservatism knob — it is arithmetic.
|
|
47
|
+
* Measured against this repository on 2026-08-05, asserting MISSING densely
|
|
48
|
+
* yields 128 phantom rows for `crap` (rows are per-method and coverage-gated,
|
|
49
|
+
* so a file with no scorable method legitimately has none) and 510 for
|
|
50
|
+
* `duplication` (a row exists only where a clone was found). A gate that
|
|
51
|
+
* reports 638 phantom findings on a healthy tree is a gate nobody reads.
|
|
52
|
+
*
|
|
53
|
+
* `lint` and `mutation` are sparse for the same reason — a clean file has no
|
|
54
|
+
* lint row, an untested file has no mutation row. `lighthouse` (keyed on
|
|
55
|
+
* `route`) and `bundle-size` (keyed on `bundle`) are not file-keyed at all, so
|
|
56
|
+
* neither direction is meaningful: their keys name artefacts, not paths on
|
|
57
|
+
* disk, and a directory walk can say nothing about them.
|
|
58
|
+
*
|
|
59
|
+
* @type {Readonly<Record<string, { keyField: string, directions: readonly string[] }>>}
|
|
60
|
+
*/
|
|
61
|
+
export const KIND_SCOPE_POLICY = Object.freeze({
|
|
62
|
+
coverage: Object.freeze({
|
|
63
|
+
keyField: 'path',
|
|
64
|
+
directions: Object.freeze(['missing', 'extra']),
|
|
65
|
+
}),
|
|
66
|
+
maintainability: Object.freeze({
|
|
67
|
+
keyField: 'path',
|
|
68
|
+
directions: Object.freeze(['missing', 'extra']),
|
|
69
|
+
}),
|
|
70
|
+
crap: Object.freeze({
|
|
71
|
+
keyField: 'path',
|
|
72
|
+
directions: Object.freeze(['extra']),
|
|
73
|
+
}),
|
|
74
|
+
duplication: Object.freeze({
|
|
75
|
+
keyField: 'path',
|
|
76
|
+
directions: Object.freeze(['extra']),
|
|
77
|
+
}),
|
|
78
|
+
lint: Object.freeze({
|
|
79
|
+
keyField: 'path',
|
|
80
|
+
directions: Object.freeze(['extra']),
|
|
81
|
+
}),
|
|
82
|
+
mutation: Object.freeze({
|
|
83
|
+
keyField: 'path',
|
|
84
|
+
directions: Object.freeze(['extra']),
|
|
85
|
+
}),
|
|
86
|
+
lighthouse: Object.freeze({
|
|
87
|
+
keyField: 'route',
|
|
88
|
+
directions: Object.freeze([]),
|
|
89
|
+
}),
|
|
90
|
+
'bundle-size': Object.freeze({
|
|
91
|
+
keyField: 'bundle',
|
|
92
|
+
directions: Object.freeze([]),
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/** Every kind the honesty surface knows about, in report order. */
|
|
97
|
+
export const SCOPE_KINDS = Object.freeze(Object.keys(KIND_SCOPE_POLICY));
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The directions `kind` may assert. Unknown kinds assert nothing — a kind this
|
|
101
|
+
* module has never heard of has no scope model, and inventing one would be the
|
|
102
|
+
* phantom-row failure above with extra steps.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} kind
|
|
105
|
+
* @returns {readonly string[]}
|
|
106
|
+
*/
|
|
107
|
+
export function directionsFor(kind) {
|
|
108
|
+
return KIND_SCOPE_POLICY[kind]?.directions ?? [];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The row field `kind` keys on — `path` for the file-keyed kinds, `route` for
|
|
113
|
+
* lighthouse, `bundle` for bundle-size. Mirrors `kinds/<kind>.js#keyField`;
|
|
114
|
+
* kept here so a caller can classify without importing the kernel.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} kind
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
export function keyFieldFor(kind) {
|
|
120
|
+
return KIND_SCOPE_POLICY[kind]?.keyField ?? 'path';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* True when `kind` keys its rows on a repo-relative file path, so a directory
|
|
125
|
+
* walk can be compared against its row set at all.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} kind
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
export function isFileKeyed(kind) {
|
|
131
|
+
return keyFieldFor(kind) === 'path' && Object.hasOwn(KIND_SCOPE_POLICY, kind);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Glob metacharacters that end a pattern's literal prefix. */
|
|
135
|
+
const GLOB_META = /[*?[\]{}!+@()]/;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Derive directory roots to walk from a list of include globs, by taking each
|
|
139
|
+
* pattern's literal leading path segments.
|
|
140
|
+
*
|
|
141
|
+
* The coverage gate's schema is closed and declares no `targetDirs` — its
|
|
142
|
+
* scope lives entirely in `.c8rc.cjs`. Without this derivation the coverage
|
|
143
|
+
* walk has no root to start from, so there is nothing to compare the row set
|
|
144
|
+
* against and the whole MISSING direction goes silently inert for the one kind
|
|
145
|
+
* whose producer is densest.
|
|
146
|
+
*
|
|
147
|
+
* `'.agents/scripts/**'` yields `.agents/scripts`; a pattern that is glob from
|
|
148
|
+
* its first segment (`'**/*.js'`) yields `.`, the repo root.
|
|
149
|
+
*
|
|
150
|
+
* @param {string[]} globs
|
|
151
|
+
* @returns {string[]} De-duplicated roots, repo-relative POSIX.
|
|
152
|
+
*/
|
|
153
|
+
export function deriveWalkRoots(globs) {
|
|
154
|
+
const roots = new Set();
|
|
155
|
+
for (const glob of Array.isArray(globs) ? globs : []) {
|
|
156
|
+
if (typeof glob !== 'string' || glob.length === 0) continue;
|
|
157
|
+
const literal = [];
|
|
158
|
+
for (const segment of glob.split('/')) {
|
|
159
|
+
if (GLOB_META.test(segment)) break;
|
|
160
|
+
literal.push(segment);
|
|
161
|
+
}
|
|
162
|
+
roots.add(literal.join('/') || '.');
|
|
163
|
+
}
|
|
164
|
+
return [...roots];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Walk `roots` under `cwd` and return canonical repo-relative POSIX paths.
|
|
169
|
+
*
|
|
170
|
+
* Delegates to `scanDirectory`, which already owns the supported-extension set
|
|
171
|
+
* and the ignored-directory set (`node_modules`, `.git`, `temp`, `.worktrees`,
|
|
172
|
+
* …) that every scorer walks with. Passing `ignoreGlobs` straight through
|
|
173
|
+
* means an ignored file is dropped by the identical matcher the scorer uses.
|
|
174
|
+
*
|
|
175
|
+
* @param {{ cwd: string, roots: string[], ignoreGlobs?: string[] }} params
|
|
176
|
+
* @returns {string[]}
|
|
177
|
+
*/
|
|
178
|
+
function walkRoots({ cwd, roots, ignoreGlobs = [] }) {
|
|
179
|
+
const absolute = [];
|
|
180
|
+
for (const root of roots) {
|
|
181
|
+
const abs = path.isAbsolute(root) ? root : path.resolve(cwd, root);
|
|
182
|
+
scanDirectory(abs, absolute, { cwd, ignoreGlobs });
|
|
183
|
+
}
|
|
184
|
+
return absolute.map((abs) =>
|
|
185
|
+
path.relative(cwd, abs).split(path.sep).join('/'),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Build the coverage inventory from `.c8rc.cjs`.
|
|
191
|
+
*
|
|
192
|
+
* Roots come from the literal prefixes of `include`; the walked set is then
|
|
193
|
+
* filtered through the same `buildScopePredicate` the coverage scorer applies,
|
|
194
|
+
* so `exclude` is honoured by one implementation rather than two.
|
|
195
|
+
*
|
|
196
|
+
* @param {{ cwd: string, requireFn?: (id: string) => object }} params
|
|
197
|
+
* @returns {{ files: string[] | null, roots: string[], degraded: boolean, reason: string | null }}
|
|
198
|
+
*/
|
|
199
|
+
function coverageInventory({ cwd, requireFn }) {
|
|
200
|
+
const load = requireFn ?? createRequire(path.join(cwd, 'noop.cjs'));
|
|
201
|
+
let config;
|
|
202
|
+
try {
|
|
203
|
+
config = load(path.resolve(cwd, '.c8rc.cjs'));
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
files: null,
|
|
207
|
+
roots: [],
|
|
208
|
+
degraded: true,
|
|
209
|
+
reason: `.c8rc.cjs unreadable: ${err?.message ?? err}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const include = Array.isArray(config?.include) ? config.include : [];
|
|
213
|
+
const exclude = Array.isArray(config?.exclude) ? config.exclude : [];
|
|
214
|
+
if (include.length === 0) {
|
|
215
|
+
return {
|
|
216
|
+
files: null,
|
|
217
|
+
roots: [],
|
|
218
|
+
degraded: true,
|
|
219
|
+
reason: '.c8rc.cjs declares no include globs',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const roots = deriveWalkRoots(include);
|
|
223
|
+
const inScope = buildScopePredicate({ include, exclude });
|
|
224
|
+
return {
|
|
225
|
+
files: walkRoots({ cwd, roots }).filter(inScope),
|
|
226
|
+
roots,
|
|
227
|
+
degraded: false,
|
|
228
|
+
reason: null,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Build the inventory for a `targetDirs`-configured kind (everything except
|
|
234
|
+
* coverage). Reads the gate's own `targetDirs` / `ignoreGlobs` off the
|
|
235
|
+
* normalized quality block — the same two keys its refresh scorer reads.
|
|
236
|
+
*
|
|
237
|
+
* @param {{ kind: string, cwd: string, gate: object }} params
|
|
238
|
+
* @returns {{ files: string[] | null, roots: string[], degraded: boolean, reason: string | null }}
|
|
239
|
+
*/
|
|
240
|
+
function targetDirsInventory({ kind, cwd, gate }) {
|
|
241
|
+
const roots = Array.isArray(gate?.targetDirs) ? gate.targetDirs : [];
|
|
242
|
+
const ignoreGlobs = Array.isArray(gate?.ignoreGlobs) ? gate.ignoreGlobs : [];
|
|
243
|
+
if (roots.length === 0) {
|
|
244
|
+
return {
|
|
245
|
+
files: null,
|
|
246
|
+
roots: [],
|
|
247
|
+
degraded: true,
|
|
248
|
+
reason: `delivery.quality.gates.${kind}.targetDirs is empty or unset`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
files: walkRoots({ cwd, roots, ignoreGlobs }),
|
|
253
|
+
roots,
|
|
254
|
+
degraded: false,
|
|
255
|
+
reason: null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Resolve the set of files `kind` is configured to have rows for.
|
|
261
|
+
*
|
|
262
|
+
* Never throws: an unreadable or absent scope config degrades to
|
|
263
|
+
* `{ files: null, degraded: true, reason }`. Callers treat a degraded
|
|
264
|
+
* inventory as "scope unknown" — the gate reports it and skips that kind's
|
|
265
|
+
* scope directions, the pruner falls back to orphan-only pruning. Neither may
|
|
266
|
+
* infer emptiness from it: a `files: []` inventory would make every committed
|
|
267
|
+
* row look out-of-scope and hand the pruner a licence to delete the baseline.
|
|
268
|
+
*
|
|
269
|
+
* @param {{
|
|
270
|
+
* kind: string,
|
|
271
|
+
* cwd?: string,
|
|
272
|
+
* quality?: object,
|
|
273
|
+
* requireFn?: (id: string) => object,
|
|
274
|
+
* }} params
|
|
275
|
+
* @returns {{
|
|
276
|
+
* kind: string,
|
|
277
|
+
* keyField: string,
|
|
278
|
+
* directions: readonly string[],
|
|
279
|
+
* files: string[] | null,
|
|
280
|
+
* roots: string[],
|
|
281
|
+
* degraded: boolean,
|
|
282
|
+
* reason: string | null,
|
|
283
|
+
* }}
|
|
284
|
+
*/
|
|
285
|
+
export function buildScopeInventory({
|
|
286
|
+
kind,
|
|
287
|
+
cwd = process.cwd(),
|
|
288
|
+
quality,
|
|
289
|
+
requireFn,
|
|
290
|
+
} = {}) {
|
|
291
|
+
const base = {
|
|
292
|
+
kind,
|
|
293
|
+
keyField: keyFieldFor(kind),
|
|
294
|
+
directions: directionsFor(kind),
|
|
295
|
+
};
|
|
296
|
+
if (!isFileKeyed(kind)) {
|
|
297
|
+
return {
|
|
298
|
+
...base,
|
|
299
|
+
files: null,
|
|
300
|
+
roots: [],
|
|
301
|
+
degraded: false,
|
|
302
|
+
reason: `${kind} is not file-keyed (rows key on "${base.keyField}")`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const resolved =
|
|
306
|
+
kind === 'coverage'
|
|
307
|
+
? coverageInventory({ cwd, requireFn })
|
|
308
|
+
: targetDirsInventory({
|
|
309
|
+
kind,
|
|
310
|
+
cwd,
|
|
311
|
+
gate: quality?.gates?.[kind] ?? {},
|
|
312
|
+
});
|
|
313
|
+
return { ...base, ...resolved };
|
|
314
|
+
}
|