mandrel 2.31.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/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/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-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 +1 -11
- 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 +42 -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,362 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// .agents/scripts/check-baseline-scope.js — Story #5012.
|
|
4
|
+
//
|
|
5
|
+
// Assert that every committed baseline's ROW SET still describes the tree.
|
|
6
|
+
//
|
|
7
|
+
// `check-baselines.js` answers "did a measured value regress?" and answers it
|
|
8
|
+
// well. Nothing answered the prior question: does this baseline still measure
|
|
9
|
+
// the right files? A row can point at a file deleted months ago, and an
|
|
10
|
+
// in-scope file can carry no row at all, while every gate stays green — a
|
|
11
|
+
// ratchet is perfectly capable of being green over almost nothing.
|
|
12
|
+
//
|
|
13
|
+
// This gate is measurement-free by construction. It runs no scorer, needs no
|
|
14
|
+
// coverage artifact and no test run: it walks the directories the gate's own
|
|
15
|
+
// config names (`lib/baselines/scope-inventory.js`) and compares that set
|
|
16
|
+
// against the committed rows in both directions
|
|
17
|
+
// (`lib/baselines/scope-assert.js`). That is what makes it cheap enough to be
|
|
18
|
+
// a required check, and what makes its companion —
|
|
19
|
+
// `prune-baseline-orphans.js` — a one-command remedy rather than a full
|
|
20
|
+
// re-score.
|
|
21
|
+
//
|
|
22
|
+
// Exit codes:
|
|
23
|
+
// 0 no fatal divergence (inherited divergence may still be warned about)
|
|
24
|
+
// 1 fatal divergence — attributable to this change set, or strict mode
|
|
25
|
+
// 2 the check could not run (unreadable config, unusable repository)
|
|
26
|
+
|
|
27
|
+
// Fail-fast if the framework's runtime deps are not installed — must be the
|
|
28
|
+
// first import so the check runs before any third-party-importing sibling
|
|
29
|
+
// module is evaluated (Story #3432).
|
|
30
|
+
import './lib/runtime-deps/ensure-installed.js';
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
import { _internals as readerInternals } from './lib/baselines/reader.js';
|
|
34
|
+
import {
|
|
35
|
+
assertScope,
|
|
36
|
+
attributeDivergence,
|
|
37
|
+
resolveStrictness,
|
|
38
|
+
} from './lib/baselines/scope-assert.js';
|
|
39
|
+
import {
|
|
40
|
+
buildScopeInventory,
|
|
41
|
+
SCOPE_KINDS,
|
|
42
|
+
} from './lib/baselines/scope-inventory.js';
|
|
43
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
44
|
+
import { getQuality } from './lib/config/quality.js';
|
|
45
|
+
import { resolveConfig } from './lib/config-resolver.js';
|
|
46
|
+
import { gitSpawn } from './lib/git-utils.js';
|
|
47
|
+
|
|
48
|
+
const EXIT_PASS = 0;
|
|
49
|
+
const EXIT_DIVERGED = 1;
|
|
50
|
+
const EXIT_CANNOT_RUN = 2;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Files whose edit makes merge-base attribution untrustworthy: once a branch
|
|
54
|
+
* has rewritten the scope rules themselves, "which side of the merge-base
|
|
55
|
+
* introduced this row" is no longer a question the diff can answer.
|
|
56
|
+
*/
|
|
57
|
+
const SCOPE_CONFIG_PATHS = Object.freeze([
|
|
58
|
+
'.c8rc.cjs',
|
|
59
|
+
'.agentrc.json',
|
|
60
|
+
'.agentrc.local.json',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const HELP = {
|
|
64
|
+
invocation:
|
|
65
|
+
'node .agents/scripts/check-baseline-scope.js [--kind <kind>] [--base <ref>] [--strict] [--json]',
|
|
66
|
+
summary:
|
|
67
|
+
"Assert each committed baseline's row set still describes the tree — in-scope files with no row, and rows whose file is gone or out of scope.",
|
|
68
|
+
flags: [
|
|
69
|
+
['--kind <kind>', 'Check one kind only (repeatable). Default: all.'],
|
|
70
|
+
['--base <ref>', 'Attribution base ref. Default: origin/<baseBranch>.'],
|
|
71
|
+
['--strict', 'Treat every divergence as fatal, skipping attribution.'],
|
|
72
|
+
['--json', 'Emit the report as JSON instead of text.'],
|
|
73
|
+
['--cwd <dir>', 'Repository root to check. Default: process.cwd().'],
|
|
74
|
+
],
|
|
75
|
+
notes: [
|
|
76
|
+
'Exit codes:\n 0 no fatal divergence\n 1 fatal divergence\n 2 the check could not run',
|
|
77
|
+
'Remedy for a stale row: node .agents/scripts/prune-baseline-orphans.js',
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse argv into an options bag. Unknown flags are a config error rather than
|
|
83
|
+
* a silent no-op — a typo'd `--kinds` must not read as "check everything".
|
|
84
|
+
*
|
|
85
|
+
* @param {string[]} argv
|
|
86
|
+
* @returns {{ kinds: string[], base: string | null, strict: boolean, json: boolean, cwd: string }}
|
|
87
|
+
*/
|
|
88
|
+
export function parseArgs(argv = []) {
|
|
89
|
+
const out = { kinds: [], base: null, strict: false, json: false, cwd: null };
|
|
90
|
+
let i = 0;
|
|
91
|
+
while (i < argv.length) {
|
|
92
|
+
const arg = argv[i];
|
|
93
|
+
const value = argv[i + 1];
|
|
94
|
+
i += 1;
|
|
95
|
+
if (arg === '--strict') out.strict = true;
|
|
96
|
+
else if (arg === '--json') out.json = true;
|
|
97
|
+
else if (arg === '--kind') {
|
|
98
|
+
out.kinds.push(value);
|
|
99
|
+
i += 1;
|
|
100
|
+
} else if (arg === '--base') {
|
|
101
|
+
out.base = value;
|
|
102
|
+
i += 1;
|
|
103
|
+
} else if (arg === '--cwd') {
|
|
104
|
+
out.cwd = value;
|
|
105
|
+
i += 1;
|
|
106
|
+
} else throw new Error(`unknown flag "${arg}" (try --help)`);
|
|
107
|
+
}
|
|
108
|
+
const unknown = out.kinds.filter((k) => !SCOPE_KINDS.includes(k));
|
|
109
|
+
if (unknown.length > 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`unknown --kind ${unknown.join(', ')}; expected one of ${SCOPE_KINDS.join(', ')}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (out.kinds.length === 0) out.kinds = [...SCOPE_KINDS];
|
|
115
|
+
out.cwd = out.cwd ?? process.cwd();
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the merge base between HEAD and the requested base ref, preferring
|
|
121
|
+
* the remote-tracking ref so a stale local `main` cannot widen attribution.
|
|
122
|
+
*
|
|
123
|
+
* Returns `{ base: null }` when no candidate resolves — the strictness
|
|
124
|
+
* resolver treats that as a reason to fail towards strict, never as a licence
|
|
125
|
+
* to skip.
|
|
126
|
+
*
|
|
127
|
+
* @param {{ cwd: string, baseRef: string }} params
|
|
128
|
+
* @returns {{ base: string | null, aheadOfBase: boolean }}
|
|
129
|
+
*/
|
|
130
|
+
function resolveMergeBase({ cwd, baseRef }) {
|
|
131
|
+
for (const candidate of [`origin/${baseRef}`, baseRef]) {
|
|
132
|
+
const merged = gitSpawn(cwd, 'merge-base', candidate, 'HEAD');
|
|
133
|
+
if (merged.status !== 0 || merged.stdout.length === 0) continue;
|
|
134
|
+
const head = gitSpawn(cwd, 'rev-parse', 'HEAD');
|
|
135
|
+
return {
|
|
136
|
+
base: merged.stdout,
|
|
137
|
+
aheadOfBase: head.status === 0 && head.stdout !== merged.stdout,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return { base: null, aheadOfBase: false };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Enumerate what `<base>..HEAD` did to the tree, split into the two sets
|
|
145
|
+
* attribution needs. A rename contributes its old path to `removed` and its
|
|
146
|
+
* new path to `added`, which is exactly how a rename strands a row.
|
|
147
|
+
*
|
|
148
|
+
* @param {{ cwd: string, base: string | null }} params
|
|
149
|
+
* @returns {{ added: string[], removed: string[], changed: string[] }}
|
|
150
|
+
*/
|
|
151
|
+
function readChangeSet({ cwd, base }) {
|
|
152
|
+
const empty = { added: [], removed: [], changed: [] };
|
|
153
|
+
if (base === null) return empty;
|
|
154
|
+
const diff = gitSpawn(cwd, 'diff', '--name-status', '-M', `${base}..HEAD`);
|
|
155
|
+
if (diff.status !== 0) return empty;
|
|
156
|
+
const added = [];
|
|
157
|
+
const removed = [];
|
|
158
|
+
const changed = [];
|
|
159
|
+
for (const line of diff.stdout.split('\n').filter(Boolean)) {
|
|
160
|
+
const [code, first, second] = line.split('\t');
|
|
161
|
+
const letter = code[0];
|
|
162
|
+
if (letter === 'R' || letter === 'C') {
|
|
163
|
+
removed.push(first);
|
|
164
|
+
added.push(second);
|
|
165
|
+
changed.push(first, second);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (letter === 'A') added.push(first);
|
|
169
|
+
if (letter === 'D') removed.push(first);
|
|
170
|
+
changed.push(first);
|
|
171
|
+
}
|
|
172
|
+
return { added, removed, changed };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Repo-relative paths of every baseline file in play — the "did this change
|
|
177
|
+
* set edit a baseline?" input to the strictness resolver.
|
|
178
|
+
*
|
|
179
|
+
* @param {{ cwd: string, kinds: string[] }} params
|
|
180
|
+
* @returns {string[]}
|
|
181
|
+
*/
|
|
182
|
+
function baselinePathsFor({ cwd, kinds }) {
|
|
183
|
+
return kinds.map((kind) =>
|
|
184
|
+
path
|
|
185
|
+
.relative(cwd, readerInternals.resolveBaselinePath(kind, { cwd }))
|
|
186
|
+
.split(path.sep)
|
|
187
|
+
.join('/'),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Read a baseline's rows, or `null` when the kind ships no baseline in this
|
|
193
|
+
* repository. Deliberately does NOT go through `reader.load`: an envelope too
|
|
194
|
+
* stale to satisfy its schema is exactly the state this gate exists to report,
|
|
195
|
+
* and refusing to read it would turn the report into a crash.
|
|
196
|
+
*
|
|
197
|
+
* @param {{ cwd: string, kind: string }} params
|
|
198
|
+
* @returns {Array<object> | null}
|
|
199
|
+
*/
|
|
200
|
+
function readRows({ cwd, kind }) {
|
|
201
|
+
const abs = readerInternals.resolveBaselinePath(kind, { cwd });
|
|
202
|
+
try {
|
|
203
|
+
const parsed = JSON.parse(fs.readFileSync(abs, 'utf8'));
|
|
204
|
+
return Array.isArray(parsed?.rows) ? parsed.rows : [];
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Assess one kind: inventory → both-directions assertion → attribution.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} params
|
|
214
|
+
* @returns {object} One entry of the report's `kinds` array.
|
|
215
|
+
*/
|
|
216
|
+
function assessKind({ kind, cwd, quality, strict, changeSet }) {
|
|
217
|
+
const rows = readRows({ cwd, kind });
|
|
218
|
+
if (rows === null) {
|
|
219
|
+
return { kind, present: false, skipped: true, reason: 'no baseline file' };
|
|
220
|
+
}
|
|
221
|
+
const inventory = buildScopeInventory({ kind, cwd, quality });
|
|
222
|
+
const found = assertScope({
|
|
223
|
+
inventory,
|
|
224
|
+
rows,
|
|
225
|
+
existsOnDisk: (rel) => fs.existsSync(path.resolve(cwd, rel)),
|
|
226
|
+
});
|
|
227
|
+
if (found.skipped) return { kind, present: true, ...found };
|
|
228
|
+
const attributed = attributeDivergence({
|
|
229
|
+
missing: found.missing,
|
|
230
|
+
extra: found.extra,
|
|
231
|
+
added: changeSet.added,
|
|
232
|
+
removed: changeSet.removed,
|
|
233
|
+
strict,
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
kind,
|
|
237
|
+
present: true,
|
|
238
|
+
skipped: false,
|
|
239
|
+
reason: null,
|
|
240
|
+
inScopeCount: inventory.files.length,
|
|
241
|
+
rowCount: rows.length,
|
|
242
|
+
...attributed,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Run the whole assessment and return a report plus its exit code.
|
|
248
|
+
*
|
|
249
|
+
* @param {{ argv?: string[] }} [params]
|
|
250
|
+
* @returns {{ report: object, exitCode: number }}
|
|
251
|
+
*/
|
|
252
|
+
export function runScopeCheck({ argv = [] } = {}) {
|
|
253
|
+
const opts = parseArgs(argv);
|
|
254
|
+
const cwd = opts.cwd;
|
|
255
|
+
const config = resolveConfig({ cwd });
|
|
256
|
+
const quality = getQuality(config) ?? { gates: {} };
|
|
257
|
+
const baseRef = opts.base ?? config?.project?.baseBranch ?? 'main';
|
|
258
|
+
const { base, aheadOfBase } = resolveMergeBase({ cwd, baseRef });
|
|
259
|
+
const changeSet = readChangeSet({ cwd, base });
|
|
260
|
+
const strictness = opts.strict
|
|
261
|
+
? { strict: true, reason: 'operator requested --strict' }
|
|
262
|
+
: resolveStrictness({
|
|
263
|
+
base,
|
|
264
|
+
aheadOfBase,
|
|
265
|
+
changedFiles: changeSet.changed,
|
|
266
|
+
baselinePaths: baselinePathsFor({ cwd, kinds: opts.kinds }),
|
|
267
|
+
scopeConfigPaths: SCOPE_CONFIG_PATHS,
|
|
268
|
+
});
|
|
269
|
+
const kinds = opts.kinds.map((kind) =>
|
|
270
|
+
assessKind({ kind, cwd, quality, strict: strictness.strict, changeSet }),
|
|
271
|
+
);
|
|
272
|
+
const fatalCount = kinds.reduce((sum, k) => sum + (k.fatalCount ?? 0), 0);
|
|
273
|
+
const report = {
|
|
274
|
+
schemaVersion: '1',
|
|
275
|
+
base,
|
|
276
|
+
strict: strictness.strict,
|
|
277
|
+
strictReason: strictness.reason,
|
|
278
|
+
fatalCount,
|
|
279
|
+
warningCount: kinds.reduce((sum, k) => sum + (k.warningCount ?? 0), 0),
|
|
280
|
+
kinds,
|
|
281
|
+
};
|
|
282
|
+
return { report, exitCode: fatalCount > 0 ? EXIT_DIVERGED : EXIT_PASS };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Render one kind's findings as indented text lines.
|
|
287
|
+
*
|
|
288
|
+
* @param {object} entry
|
|
289
|
+
* @returns {string[]}
|
|
290
|
+
*/
|
|
291
|
+
function renderKind(entry) {
|
|
292
|
+
if (entry.skipped) return [` - ${entry.kind}: skipped (${entry.reason})`];
|
|
293
|
+
const head =
|
|
294
|
+
` - ${entry.kind}: ${entry.rowCount} row(s) over ${entry.inScopeCount} ` +
|
|
295
|
+
`in-scope file(s) — ${entry.fatalCount} fatal, ${entry.warningCount} inherited`;
|
|
296
|
+
const lines = [head];
|
|
297
|
+
for (const file of entry.fatal.missing) {
|
|
298
|
+
lines.push(` ✗ missing row: ${file}`);
|
|
299
|
+
}
|
|
300
|
+
for (const row of entry.fatal.extra) {
|
|
301
|
+
lines.push(` ✗ stale row (${row.reason}): ${row.path}`);
|
|
302
|
+
}
|
|
303
|
+
return lines;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Render the whole report as text.
|
|
308
|
+
*
|
|
309
|
+
* @param {object} report
|
|
310
|
+
* @returns {string}
|
|
311
|
+
*/
|
|
312
|
+
export function formatReport(report) {
|
|
313
|
+
const mode = report.strict ? 'strict' : 'attributed';
|
|
314
|
+
const lines = [
|
|
315
|
+
`[check-baseline-scope] ${report.fatalCount} fatal, ${report.warningCount} inherited ` +
|
|
316
|
+
`(${mode}: ${report.strictReason})`,
|
|
317
|
+
...report.kinds.flatMap(renderKind),
|
|
318
|
+
];
|
|
319
|
+
if (report.fatalCount > 0) {
|
|
320
|
+
lines.push(
|
|
321
|
+
'',
|
|
322
|
+
'A baseline no longer describes the tree. Prune provably-inert rows with:',
|
|
323
|
+
' node .agents/scripts/prune-baseline-orphans.js',
|
|
324
|
+
'A missing row means a file was added without being measured — run that',
|
|
325
|
+
"kind's producer (npm run coverage:update / maintainability:update).",
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return lines.join('\n');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* CLI entry point. Returns its exit code rather than calling `process.exit()`,
|
|
333
|
+
* so `runAsCli`'s `propagateExitCode` path settles it through `flushStdio` and
|
|
334
|
+
* an unbounded report is not truncated at a pipe boundary (Story #4783).
|
|
335
|
+
*
|
|
336
|
+
* @returns {Promise<number>}
|
|
337
|
+
*/
|
|
338
|
+
async function main() {
|
|
339
|
+
let result;
|
|
340
|
+
try {
|
|
341
|
+
result = runScopeCheck({ argv: process.argv.slice(2) });
|
|
342
|
+
} catch (err) {
|
|
343
|
+
const message = err?.message ?? String(err);
|
|
344
|
+
process.stdout.write(
|
|
345
|
+
`${JSON.stringify({ schemaVersion: '1', error: message }, null, 2)}\n`,
|
|
346
|
+
);
|
|
347
|
+
return EXIT_CANNOT_RUN;
|
|
348
|
+
}
|
|
349
|
+
const opts = { json: process.argv.includes('--json') };
|
|
350
|
+
process.stdout.write(
|
|
351
|
+
opts.json
|
|
352
|
+
? `${JSON.stringify(result.report, null, 2)}\n`
|
|
353
|
+
: `${formatReport(result.report)}\n`,
|
|
354
|
+
);
|
|
355
|
+
return result.exitCode;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
runAsCli(import.meta.url, main, {
|
|
359
|
+
source: 'check-baseline-scope',
|
|
360
|
+
usage: HELP,
|
|
361
|
+
propagateExitCode: true,
|
|
362
|
+
});
|
|
@@ -14,13 +14,21 @@
|
|
|
14
14
|
* production-dead code hides behind its own tests. See
|
|
15
15
|
* `lib/dead-exports-mode.js` for why the two passes carry separate baselines.
|
|
16
16
|
*
|
|
17
|
+
* Story #5001 widens what the ratchet can see: knip's `files` category (a
|
|
18
|
+
* module nothing imports) now maps to a `{ file, symbol: '*' }` row alongside
|
|
19
|
+
* the per-export rows, and `knip.json` lists `.agents/scripts/*.js` entry
|
|
20
|
+
* points explicitly instead of by blanket glob — so a top-level CLI that
|
|
21
|
+
* nothing invokes surfaces as dead instead of being declared live by the glob.
|
|
22
|
+
*
|
|
17
23
|
* Contract:
|
|
18
24
|
* - Reads the committed baseline at `baselines/dead-exports.json` — or
|
|
19
25
|
* `baselines/dead-exports-production.json` under `--production`
|
|
20
26
|
* (override either with `--baseline <path>`). Envelope shape:
|
|
21
27
|
* { $schema, kernelVersion, generatedAt, rows: [{ file, symbol }] }
|
|
22
28
|
* - Spawns `npx knip --reporter json --no-progress` (plus `--production`),
|
|
23
|
-
* parses stdout, extracts `{ file, symbol }` rows from `issues[].exports[]
|
|
29
|
+
* parses stdout, extracts `{ file, symbol }` rows from `issues[].exports[]`
|
|
30
|
+
* **and** `issues[].files[]` — the latter as one `{ file, symbol: '*' }`
|
|
31
|
+
* whole-file row per module nothing imports (Story #5001).
|
|
24
32
|
* - Diffs current vs. baseline by `(file, symbol)` identity.
|
|
25
33
|
* - Prints `+ <file>: <symbol>` for each added dead export and
|
|
26
34
|
* `- <file>: <symbol>` for each removed one, then a summary line.
|