mandrel 2.24.0 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/.agents/agents/acceptance-critic.md +19 -21
  2. package/.agents/agents/auditor.md +16 -19
  3. package/.agents/agents/story-worker.md +41 -54
  4. package/.agents/audit-checklists/architecture.md +5 -1
  5. package/.agents/audit-checklists/baselines.md +21 -0
  6. package/.agents/audit-checklists/quality.md +3 -0
  7. package/.agents/docs/agentrc-reference.json +2 -1
  8. package/.agents/docs/configuration.md +2 -1
  9. package/.agents/docs/quality-gates.md +80 -18
  10. package/.agents/docs/workflows.md +4 -2
  11. package/.agents/instructions.md +1 -1
  12. package/.agents/rules/ci-remediation.md +68 -3
  13. package/.agents/schemas/agentrc.schema.json +6 -1
  14. package/.agents/schemas/audit-rules.json +15 -0
  15. package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
  16. package/.agents/schemas/baselines/baseline-envelope.schema.json +8 -0
  17. package/.agents/schemas/baselines/crap.schema.json +13 -1
  18. package/.agents/schemas/crap-report.schema.json +37 -0
  19. package/.agents/schemas/model-attribution.schema.json +4 -0
  20. package/.agents/scripts/acceptance-eval.js +124 -15
  21. package/.agents/scripts/audit-baselines.js +136 -0
  22. package/.agents/scripts/audit-labels-bootstrap.js +16 -95
  23. package/.agents/scripts/audit-to-stories.js +33 -3
  24. package/.agents/scripts/check-arch-cycles.js +12 -93
  25. package/.agents/scripts/check-baseline-drift.js +16 -3
  26. package/.agents/scripts/check-baselines.js +19 -3
  27. package/.agents/scripts/check-context-budget.js +95 -14
  28. package/.agents/scripts/check-cyclomatic.js +214 -0
  29. package/.agents/scripts/check-schema-references.js +392 -0
  30. package/.agents/scripts/check-test-temp-hygiene.js +121 -1
  31. package/.agents/scripts/check-workflow-timeouts.js +291 -0
  32. package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
  33. package/.agents/scripts/deliver-light.js +3 -4
  34. package/.agents/scripts/deliver-recover.js +13 -0
  35. package/.agents/scripts/diagnose-friction.js +85 -19
  36. package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
  37. package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
  38. package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
  39. package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
  40. package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
  41. package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
  42. package/.agents/scripts/lib/audit-baselines/read.js +87 -0
  43. package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
  44. package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
  45. package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
  46. package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
  47. package/.agents/scripts/lib/audit-suite/index.js +0 -5
  48. package/.agents/scripts/lib/audit-suite/selector.js +9 -62
  49. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
  50. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -0
  51. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
  52. package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
  53. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
  54. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
  55. package/.agents/scripts/lib/baseline-loader.js +0 -0
  56. package/.agents/scripts/lib/baseline-schema-registry.js +13 -1
  57. package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
  58. package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
  59. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  60. package/.agents/scripts/lib/baselines/kinds/crap.js +499 -57
  61. package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
  62. package/.agents/scripts/lib/baselines/reader.js +10 -0
  63. package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
  64. package/.agents/scripts/lib/baselines/scope.js +39 -90
  65. package/.agents/scripts/lib/baselines/writer.js +16 -7
  66. package/.agents/scripts/lib/changed-files.js +8 -1
  67. package/.agents/scripts/lib/cli-args.js +115 -1
  68. package/.agents/scripts/lib/close-validation/runner.js +70 -25
  69. package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
  70. package/.agents/scripts/lib/crap-engine.js +124 -27
  71. package/.agents/scripts/lib/crap-method-identity.js +153 -0
  72. package/.agents/scripts/lib/crap-utils.js +86 -13
  73. package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
  74. package/.agents/scripts/lib/env-loader.js +46 -16
  75. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
  76. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
  77. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
  78. package/.agents/scripts/lib/findings/route-finding.js +123 -17
  79. package/.agents/scripts/lib/findings/severity.js +80 -2
  80. package/.agents/scripts/lib/git/cached-fetch.js +0 -0
  81. package/.agents/scripts/lib/git-utils.js +136 -80
  82. package/.agents/scripts/lib/import-graph.js +156 -0
  83. package/.agents/scripts/lib/label-constants.js +17 -0
  84. package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
  85. package/.agents/scripts/lib/observability/source-classifier.js +175 -2
  86. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
  87. package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
  88. package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
  89. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
  90. package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
  91. package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
  92. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
  93. package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
  94. package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +10 -5
  95. package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
  96. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
  97. package/.agents/scripts/lib/orchestration/light-backstop.js +20 -3
  98. package/.agents/scripts/lib/orchestration/light-escalation.js +82 -3
  99. package/.agents/scripts/lib/orchestration/light-suitability.js +103 -5
  100. package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
  101. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
  102. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +216 -109
  103. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +61 -13
  104. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
  105. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
  106. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
  107. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
  108. package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
  109. package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
  110. package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -0
  111. package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
  112. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +125 -37
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
  118. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +267 -125
  119. package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
  120. package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
  121. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
  122. package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
  123. package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
  124. package/.agents/scripts/lib/reserved-test-ids.js +77 -0
  125. package/.agents/scripts/lib/story-adjacency.js +3 -3
  126. package/.agents/scripts/lib/story-plan.js +137 -42
  127. package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
  128. package/.agents/scripts/lib/test-runner-contract.js +134 -0
  129. package/.agents/scripts/lib/test-tiers.js +11 -2
  130. package/.agents/scripts/lib/ticket-body-sections.js +1 -1
  131. package/.agents/scripts/lib/util/concurrent-map.js +17 -0
  132. package/.agents/scripts/lib/util/parse-id-list.js +103 -0
  133. package/.agents/scripts/lib/util/poll-loop.js +18 -2
  134. package/.agents/scripts/lib/wave-runner/live-probe.js +29 -13
  135. package/.agents/scripts/lib/wave-runner/ready-set.js +254 -43
  136. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +10 -13
  137. package/.agents/scripts/lib/workers/crap-worker.js +8 -13
  138. package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
  139. package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
  140. package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
  141. package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
  142. package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
  143. package/.agents/scripts/lib/worktree-manager.js +14 -0
  144. package/.agents/scripts/plan-run-epilogue.js +17 -5
  145. package/.agents/scripts/pr-watch-with-update.js +568 -213
  146. package/.agents/scripts/providers/github/tickets.js +33 -10
  147. package/.agents/scripts/provision-git-hooks.js +85 -0
  148. package/.agents/scripts/quality-preview.js +137 -28
  149. package/.agents/scripts/resolve-stories.js +4 -1
  150. package/.agents/scripts/run-coverage.js +86 -35
  151. package/.agents/scripts/run-lint.js +20 -0
  152. package/.agents/scripts/run-tests.js +36 -36
  153. package/.agents/scripts/single-story-close.js +34 -2
  154. package/.agents/scripts/single-story-confirm-merge.js +22 -6
  155. package/.agents/scripts/single-story-init.js +7 -0
  156. package/.agents/scripts/stories-wave-tick.js +308 -47
  157. package/.agents/scripts/story-plan.js +65 -9
  158. package/.agents/scripts/update-coverage-baseline.js +34 -4
  159. package/.agents/scripts/update-crap-baseline.js +42 -4
  160. package/.agents/scripts/update-duplication-baseline.js +209 -83
  161. package/.agents/scripts/update-maintainability-baseline.js +31 -4
  162. package/.agents/scripts/validate-docs-freshness.js +1 -0
  163. package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
  164. package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
  165. package/.agents/skills/skills.index.json +2 -2
  166. package/.agents/templates/single-story-body.md +16 -8
  167. package/.agents/workflows/audit-architecture.md +69 -13
  168. package/.agents/workflows/audit-baselines.md +289 -0
  169. package/.agents/workflows/audit-navigability.md +5 -4
  170. package/.agents/workflows/audit-quality.md +26 -0
  171. package/.agents/workflows/audit-to-stories.md +30 -4
  172. package/.agents/workflows/deliver.md +92 -97
  173. package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
  174. package/.agents/workflows/helpers/audit-lens-core.md +25 -4
  175. package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
  176. package/.agents/workflows/helpers/deliver-digest.md +41 -21
  177. package/.agents/workflows/helpers/deliver-reference.md +95 -4
  178. package/.agents/workflows/helpers/deliver-story-reference.md +174 -52
  179. package/.agents/workflows/helpers/deliver-story.md +104 -119
  180. package/.agents/workflows/helpers/plan-reference.md +49 -5
  181. package/.agents/workflows/memory-consolidate.md +116 -0
  182. package/.agents/workflows/plan.md +62 -75
  183. package/README.md +13 -6
  184. package/docs/CHANGELOG.md +93 -0
  185. package/lib/cli/update.js +14 -11
  186. package/lib/cli/version-check.js +9 -1
  187. package/package.json +9 -4
  188. package/.agents/schemas/friction-event.schema.json +0 -56
  189. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
@@ -45,10 +45,22 @@ export const BASELINE_KIND_SCHEMA_FILES = Object.freeze([
45
45
  'duplication.schema.json',
46
46
  ]);
47
47
 
48
- /** Every baseline schema filename (envelope + per-kind) in registration order. */
48
+ /**
49
+ * Report schemas that live under `.agents/schemas/baselines/` but describe
50
+ * an *engine's output about* the baselines rather than a committed baseline
51
+ * (Story #4902). They are registered here for two reasons: the AJV instance
52
+ * below is the one place a caller can compile a baselines-directory schema
53
+ * without re-registering the envelope, and the mirror-drift test compares
54
+ * this registry against the on-disk listing — an unregistered file there is
55
+ * drift regardless of which category it belongs to.
56
+ */
57
+ const BASELINE_REPORT_SCHEMA_FILES = ['audit-baselines-envelope.schema.json'];
58
+
59
+ /** Every baseline schema filename (envelope + per-kind + report) in registration order. */
49
60
  export const BASELINE_SCHEMA_FILES = Object.freeze([
50
61
  BASELINE_ENVELOPE_FILE,
51
62
  ...BASELINE_KIND_SCHEMA_FILES,
63
+ ...BASELINE_REPORT_SCHEMA_FILES,
52
64
  ]);
53
65
 
54
66
  /**
@@ -3,26 +3,32 @@
3
3
  * the manual baseline-update CLIs (Story #1974 / Task #1986, Epic #1943).
4
4
  *
5
5
  * `update-coverage-baseline.js`, `update-crap-baseline.js`,
6
- * `update-maintainability-baseline.js`, and `update-mutation-baseline.js`
7
- * all accept an opt-in `--diff-scope <ref>` flag. When supplied, the
8
- * baseline write narrows to files changed since `<ref>` (resolved via
9
- * `git diff --name-only <ref>...HEAD`). Out-of-scope rows are preserved
10
- * verbatim from the prior on-disk baseline via the per-kind `mergeRows`.
6
+ * `update-maintainability-baseline.js`, and
7
+ * `update-duplication-baseline.js` all accept an opt-in `--diff-scope <ref>`
8
+ * flag. When supplied, the baseline write narrows to files changed since
9
+ * `<ref>`; out-of-scope rows are preserved verbatim from the prior on-disk
10
+ * baseline.
11
11
  *
12
- * When the flag is absent, the CLIs behave exactly as they did before
13
- * #1974 full regenerate + write preserving operator workflows that
14
- * intentionally rewrite the whole baseline.
12
+ * The helper is shared to keep the flag's contract identical across the four
13
+ * scripts: one argv parser, one spelling, one error message.
15
14
  *
16
- * The helper is shared to keep the flag's contract identical across the
17
- * four scripts: same argv parser, same git invocation, same forward-slash
18
- * path normalisation. The four CLIs differ only in how they pipe the
19
- * resolved scope through to their writer.
15
+ * **Scope of this module, post-Story #4944.** It parses the flag and nothing
16
+ * else. Everything downstream of the parse resolving the changed-file set,
17
+ * reading the prior envelope, and assembling the writer call used to live
18
+ * here too, in `buildWriterScopeArgs` and its helpers. Those were the
19
+ * pre-service path; `lib/baselines/refresh-service.js` owns all of it now
20
+ * (`resolveScope`, `readPriorEnvelope`, and the `writer.write()` handoff),
21
+ * and `refreshBaseline()` derives its own diff via `execFile` rather than
22
+ * `spawnSync`. `update-duplication-baseline.js` was the last caller of the
23
+ * legacy path; when it migrated, the whole write-side half of this module
24
+ * became unreachable and was removed rather than left shipped-but-uncalled.
25
+ *
26
+ * Note for anyone tracing the per-kind prior-row reader that used to live
27
+ * here: it is gone, not relocated. The service reads the prior envelope
28
+ * kind-agnostically (any `{ rows: [], rollup: {} }` document), so the
29
+ * per-kind row-shape filter it needed no longer has a job to do.
20
30
  */
21
31
 
22
- import { spawnSync } from 'node:child_process';
23
- import fs from 'node:fs';
24
- import { parseNameOnlyStdout } from '../changed-files.js';
25
-
26
32
  /**
27
33
  * Parse `--diff-scope <ref>` (and the legacy `--diff-scope=<ref>` form)
28
34
  * from an argv slice. Returns `null` when the flag is absent. Throws a
@@ -57,147 +63,3 @@ export function parseDiffScopeFlag(argv = []) {
57
63
  }
58
64
  return null;
59
65
  }
60
-
61
- /**
62
- * Resolve the file footprint of `git diff --name-only <ref>...HEAD`.
63
- * Returns a `Set<string>` of repo-relative paths with forward-slash
64
- * normalisation. Returns an empty Set when the diff is empty or git
65
- * exits non-zero (best-effort; a missing-ref or corrupt repo is the
66
- * operator's signal to inspect the working tree).
67
- *
68
- * The `spawnImpl` seam exists for unit tests — production callers omit it.
69
- *
70
- * @param {{ ref: string, cwd?: string, spawnImpl?: typeof spawnSync }} args
71
- * @returns {Set<string>}
72
- */
73
- export function resolveDiffScopeFiles({
74
- ref,
75
- cwd = process.cwd(),
76
- spawnImpl = spawnSync,
77
- } = {}) {
78
- if (typeof ref !== 'string' || ref.length === 0) return new Set();
79
- const res = spawnImpl('git', ['diff', '--name-only', `${ref}...HEAD`], {
80
- cwd,
81
- encoding: 'utf8',
82
- });
83
- if (!res || res.status !== 0) return new Set();
84
- return new Set(parseNameOnlyStdout(res.stdout));
85
- }
86
-
87
- /**
88
- * Convenience: parse `--diff-scope` and resolve files in one call.
89
- * Returns `null` when the flag is absent (so the caller can branch on
90
- * "scope was opted in?"); otherwise returns
91
- * `{ ref, files: Set<string>, scope: { mode: 'diff', files } }` ready to
92
- * pass into `writer.write({ scope })`.
93
- *
94
- * @param {{ argv: string[], cwd?: string, spawnImpl?: typeof spawnSync }} args
95
- * @returns {{ ref: string, files: Set<string>, scope: {mode: 'diff', files: Set<string>} } | null}
96
- */
97
- export function resolveDiffScope({ argv, cwd, spawnImpl } = {}) {
98
- const ref = parseDiffScopeFlag(argv);
99
- if (ref === null) return null;
100
- const files = resolveDiffScopeFiles({ ref, cwd, spawnImpl });
101
- return { ref, files, scope: { mode: 'diff', files } };
102
- }
103
-
104
- /**
105
- * Read + parse the prior baseline at `absBaselinePath` and return the
106
- * canonical `rows[]` array (per-kind row shape with `path:` keys, as
107
- * expected by the per-kind `mergeRows` / `applyEpsilon` helpers from
108
- * Story #1974). Returns `null` when the file is absent, malformed, or
109
- * missing a `rows[]` envelope; the caller treats `null` as "skip the
110
- * merge" (regression-fail-safe — equivalent to a fresh write).
111
- *
112
- * Pure-by-design (file I/O through the injected `fsImpl` seam).
113
- *
114
- * @param {{ kind: 'maintainability' | 'crap', absBaselinePath: string, fsImpl?: typeof fs }} args
115
- * @returns {Array<object> | null}
116
- */
117
- // Read + JSON-parse a baseline file. Returns `null` on any I/O or parse
118
- // failure (the caller treats "no prior" the same as "unreadable prior").
119
- function readBaselineJson(absBaselinePath, fsImpl) {
120
- let raw;
121
- try {
122
- raw = fsImpl.readFileSync(absBaselinePath, 'utf8');
123
- } catch {
124
- return null;
125
- }
126
- try {
127
- const parsed = JSON.parse(raw);
128
- return parsed && typeof parsed === 'object' ? parsed : null;
129
- } catch {
130
- return null;
131
- }
132
- }
133
-
134
- // CRAP path: envelope `rows[]` only; rows already carry canonical `path:`.
135
- function readCrapPriorRows(parsed) {
136
- if (!Array.isArray(parsed.rows)) return null;
137
- return parsed.rows;
138
- }
139
-
140
- // Maintainability path: envelope `rows[]` only; rows already carry
141
- // canonical `path:` + `mi:`.
142
- function readMaintainabilityPriorRows(parsed) {
143
- if (!Array.isArray(parsed.rows)) return null;
144
- return parsed.rows.filter(
145
- (r) => r && typeof r.path === 'string' && typeof r.mi === 'number',
146
- );
147
- }
148
-
149
- export function readPriorBaselineRows({ kind, absBaselinePath, fsImpl = fs }) {
150
- const parsed = readBaselineJson(absBaselinePath, fsImpl);
151
- if (!parsed) return null;
152
- if (kind === 'crap') return readCrapPriorRows(parsed);
153
- return readMaintainabilityPriorRows(parsed);
154
- }
155
-
156
- /**
157
- * Compose the full Story #1974 write-side payload for a manual baseline
158
- * CLI: read prior rows, resolve `--diff-scope`, log the scope decision,
159
- * and return the four params (`prior`, `epsilon`, `scope`, plus the
160
- * resolved `diffScope` for caller-side logging) that the CLI feeds into
161
- * `writer.write({ ..., prior, epsilon, scope })`.
162
- *
163
- * Returns a flat record so each CLI can spread it into the writer call.
164
- *
165
- * @param {{
166
- * kind: 'maintainability' | 'crap',
167
- * absBaselinePath: string,
168
- * epsilon: number,
169
- * argv?: string[],
170
- * cwd?: string,
171
- * logger?: { info?: (msg: string) => void },
172
- * logTag: string,
173
- * }} args
174
- * @returns {{
175
- * prior: Array<object> | undefined,
176
- * epsilon: number | undefined,
177
- * scope: {mode: 'diff', files: Set<string>} | undefined,
178
- * diffScope: {ref: string, files: Set<string>, scope: object} | null,
179
- * }}
180
- */
181
- export function buildWriterScopeArgs({
182
- kind,
183
- absBaselinePath,
184
- epsilon,
185
- argv = process.argv.slice(2),
186
- cwd,
187
- logger,
188
- logTag,
189
- }) {
190
- const prior = readPriorBaselineRows({ kind, absBaselinePath });
191
- const diffScope = resolveDiffScope({ argv, cwd });
192
- if (diffScope && logger?.info) {
193
- logger.info(
194
- `${logTag} --diff-scope ${diffScope.ref}: ${diffScope.files.size} file(s) in scope; out-of-scope rows preserved verbatim.`,
195
- );
196
- }
197
- return {
198
- prior: prior ?? undefined,
199
- epsilon: prior ? epsilon : undefined,
200
- scope: diffScope?.scope,
201
- diffScope,
202
- };
203
- }
@@ -26,11 +26,38 @@
26
26
  */
27
27
 
28
28
  import { readFileSync } from 'node:fs';
29
+ import { createRequire } from 'node:module';
29
30
  import path from 'node:path';
30
31
 
31
32
  const DEFAULT_MIN_TOKENS = 50;
32
33
  const DEFAULT_FORMATS = Object.freeze(['javascript']);
33
34
 
35
+ const require = createRequire(import.meta.url);
36
+
37
+ /**
38
+ * Resolve jscpd's `detectClones` lazily. The jscpd ESM entrypoint has a
39
+ * broken transitive `colors/safe` specifier under Node's strict ESM
40
+ * resolver, so we load the CJS build via `createRequire`. Isolated behind a
41
+ * function so the rest of the module stays import-pure and testable — no
42
+ * importer of this file pays the jscpd load unless it actually scans.
43
+ *
44
+ * Lives here rather than in `update-duplication-baseline.js` since Story
45
+ * #4944: the refresh service's default duplication scorer needs the same
46
+ * seam, and a second copy in the CLI would be the classic "two
47
+ * implementations of one probe" divergence.
48
+ *
49
+ * @returns {(opts: object) => Promise<Array<object>>}
50
+ */
51
+ export function resolveDetectClones() {
52
+ const jscpd = require('jscpd');
53
+ if (typeof jscpd.detectClones !== 'function') {
54
+ throw new Error(
55
+ "[Duplication] jscpd.detectClones is not available — run 'npm install'",
56
+ );
57
+ }
58
+ return jscpd.detectClones;
59
+ }
60
+
34
61
  /**
35
62
  * Normalise a jscpd `sourceId` (or any path) to a canonical POSIX
36
63
  * repo-relative path. jscpd already emits cwd-relative paths, but a future