@opensip-cli/yagni 0.1.11 → 0.1.12

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 (109) hide show
  1. package/README.md +2 -2
  2. package/dist/__tests__/architecture-invariants.test.d.ts +6 -0
  3. package/dist/__tests__/architecture-invariants.test.d.ts.map +1 -0
  4. package/dist/__tests__/architecture-invariants.test.js +37 -0
  5. package/dist/__tests__/architecture-invariants.test.js.map +1 -0
  6. package/dist/__tests__/detector-progress.test.d.ts +2 -0
  7. package/dist/__tests__/detector-progress.test.d.ts.map +1 -0
  8. package/dist/__tests__/detector-progress.test.js +73 -0
  9. package/dist/__tests__/detector-progress.test.js.map +1 -0
  10. package/dist/__tests__/duplicate-parity.test.d.ts +15 -0
  11. package/dist/__tests__/duplicate-parity.test.d.ts.map +1 -0
  12. package/dist/__tests__/duplicate-parity.test.js +304 -0
  13. package/dist/__tests__/duplicate-parity.test.js.map +1 -0
  14. package/dist/__tests__/hardening.test.d.ts +2 -0
  15. package/dist/__tests__/hardening.test.d.ts.map +1 -0
  16. package/dist/__tests__/hardening.test.js +101 -0
  17. package/dist/__tests__/hardening.test.js.map +1 -0
  18. package/dist/__tests__/session-payload.test.d.ts +2 -0
  19. package/dist/__tests__/session-payload.test.d.ts.map +1 -0
  20. package/dist/__tests__/session-payload.test.js +57 -0
  21. package/dist/__tests__/session-payload.test.js.map +1 -0
  22. package/dist/__tests__/walk-typescript-files.test.js +15 -1
  23. package/dist/__tests__/walk-typescript-files.test.js.map +1 -1
  24. package/dist/__tests__/yagni-coverage.test.js +24 -199
  25. package/dist/__tests__/yagni-coverage.test.js.map +1 -1
  26. package/dist/__tests__/yagni-golden.test.js +7 -24
  27. package/dist/__tests__/yagni-golden.test.js.map +1 -1
  28. package/dist/__tests__/yagni-presentation.test.js +4 -5
  29. package/dist/__tests__/yagni-presentation.test.js.map +1 -1
  30. package/dist/cli/__tests__/yagni-runner.test.js +4 -6
  31. package/dist/cli/__tests__/yagni-runner.test.js.map +1 -1
  32. package/dist/cli/execute-yagni.d.ts +15 -4
  33. package/dist/cli/execute-yagni.d.ts.map +1 -1
  34. package/dist/cli/execute-yagni.js +68 -30
  35. package/dist/cli/execute-yagni.js.map +1 -1
  36. package/dist/cli/report-data.d.ts +0 -2
  37. package/dist/cli/report-data.d.ts.map +1 -1
  38. package/dist/cli/report-data.js +0 -2
  39. package/dist/cli/report-data.js.map +1 -1
  40. package/dist/cli/yagni-command-spec.d.ts.map +1 -1
  41. package/dist/cli/yagni-command-spec.js +4 -19
  42. package/dist/cli/yagni-command-spec.js.map +1 -1
  43. package/dist/cli/yagni-config-schema.d.ts +1 -7
  44. package/dist/cli/yagni-config-schema.d.ts.map +1 -1
  45. package/dist/cli/yagni-config-schema.js +4 -6
  46. package/dist/cli/yagni-config-schema.js.map +1 -1
  47. package/dist/cli/yagni-config.d.ts.map +1 -1
  48. package/dist/cli/yagni-config.js +3 -7
  49. package/dist/cli/yagni-config.js.map +1 -1
  50. package/dist/cli/yagni-presentation.d.ts +1 -2
  51. package/dist/cli/yagni-presentation.d.ts.map +1 -1
  52. package/dist/cli/yagni-presentation.js +4 -4
  53. package/dist/cli/yagni-presentation.js.map +1 -1
  54. package/dist/cli/yagni-runner.d.ts +0 -2
  55. package/dist/cli/yagni-runner.d.ts.map +1 -1
  56. package/dist/cli/yagni-runner.js +40 -8
  57. package/dist/cli/yagni-runner.js.map +1 -1
  58. package/dist/detectors/duplicate-body-candidate.d.ts +9 -2
  59. package/dist/detectors/duplicate-body-candidate.d.ts.map +1 -1
  60. package/dist/detectors/duplicate-body-candidate.js +123 -120
  61. package/dist/detectors/duplicate-body-candidate.js.map +1 -1
  62. package/dist/detectors/registry.d.ts +9 -1
  63. package/dist/detectors/registry.d.ts.map +1 -1
  64. package/dist/detectors/registry.js +9 -1
  65. package/dist/detectors/registry.js.map +1 -1
  66. package/dist/detectors/types.d.ts +1 -2
  67. package/dist/detectors/types.d.ts.map +1 -1
  68. package/dist/detectors/unused-config-surface.d.ts.map +1 -1
  69. package/dist/detectors/unused-config-surface.js +0 -2
  70. package/dist/detectors/unused-config-surface.js.map +1 -1
  71. package/dist/index.d.ts +1 -1
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/lib/build-ts-inventory.d.ts +33 -0
  74. package/dist/lib/build-ts-inventory.d.ts.map +1 -0
  75. package/dist/lib/build-ts-inventory.js +252 -0
  76. package/dist/lib/build-ts-inventory.js.map +1 -0
  77. package/dist/persistence/session-payload.d.ts +7 -10
  78. package/dist/persistence/session-payload.d.ts.map +1 -1
  79. package/dist/persistence/session-payload.js +64 -6
  80. package/dist/persistence/session-payload.js.map +1 -1
  81. package/dist/scoring/__tests__/confidence.test.js +3 -4
  82. package/dist/scoring/__tests__/confidence.test.js.map +1 -1
  83. package/dist/scoring/confidence.d.ts +1 -1
  84. package/dist/scoring/confidence.d.ts.map +1 -1
  85. package/dist/scoring/confidence.js +1 -2
  86. package/dist/scoring/confidence.js.map +1 -1
  87. package/dist/types/yagni-config.d.ts +1 -4
  88. package/dist/types/yagni-config.d.ts.map +1 -1
  89. package/dist/types/yagni-config.js +0 -1
  90. package/dist/types/yagni-config.js.map +1 -1
  91. package/dist/types/yagni-metadata.d.ts +0 -1
  92. package/dist/types/yagni-metadata.d.ts.map +1 -1
  93. package/package.json +10 -10
  94. package/dist/__tests__/graph-evidence.test.d.ts +0 -2
  95. package/dist/__tests__/graph-evidence.test.d.ts.map +0 -1
  96. package/dist/__tests__/graph-evidence.test.js +0 -159
  97. package/dist/__tests__/graph-evidence.test.js.map +0 -1
  98. package/dist/evidence/graph-evidence.d.ts +0 -15
  99. package/dist/evidence/graph-evidence.d.ts.map +0 -1
  100. package/dist/evidence/graph-evidence.js +0 -89
  101. package/dist/evidence/graph-evidence.js.map +0 -1
  102. package/dist/evidence/load-graph-adapters.d.ts +0 -13
  103. package/dist/evidence/load-graph-adapters.d.ts.map +0 -1
  104. package/dist/evidence/load-graph-adapters.js +0 -67
  105. package/dist/evidence/load-graph-adapters.js.map +0 -1
  106. package/dist/lib/isolate-exit-code.d.ts +0 -8
  107. package/dist/lib/isolate-exit-code.d.ts.map +0 -1
  108. package/dist/lib/isolate-exit-code.js +0 -27
  109. package/dist/lib/isolate-exit-code.js.map +0 -1
package/README.md CHANGED
@@ -25,8 +25,8 @@ This package is published for the CLI and advanced plugin authors; most users sh
25
25
  ## Documentation
26
26
 
27
27
  - 📚 Project docs: https://opensip.ai/docs/opensip-cli/
28
- - 🧭 Package catalog (what every package does): https://github.com/opensip-ai/opensip-cli/blob/v0.1.11/docs/public/70-reference/02-package-catalog.md
29
- - 📦 Source: https://github.com/opensip-ai/opensip-cli/tree/v0.1.11/packages/yagni/engine
28
+ - 🧭 Package catalog (what every package does): https://github.com/opensip-ai/opensip-cli/blob/v0.1.12/docs/public/70-reference/02-package-catalog.md
29
+ - 📦 Source: https://github.com/opensip-ai/opensip-cli/tree/v0.1.12/packages/yagni/engine
30
30
 
31
31
  ## License
32
32
 
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Architectural invariant probes (ADR-0064): yagni must never import graph;
3
+ * @opensip-cli/clone-detection must stay a leaf.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=architecture-invariants.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"architecture-invariants.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/architecture-invariants.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Architectural invariant probes (ADR-0064): yagni must never import graph;
3
+ * @opensip-cli/clone-detection must stay a leaf.
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { describe, expect, it } from 'vitest';
8
+ const REPO_ROOT = fileURLToPath(new URL('../../../../..', import.meta.url));
9
+ describe('ADR-0064 architectural invariants', () => {
10
+ it('yagni package.json declares no @opensip-cli/graph dependency', () => {
11
+ const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'));
12
+ expect(Object.keys(pkg.dependencies ?? {})).not.toContain('@opensip-cli/graph');
13
+ });
14
+ it('clone-detection package.json declares no workspace dependencies', () => {
15
+ const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../../../../clone-detection/package.json', import.meta.url)), 'utf8'));
16
+ const all = [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
17
+ expect(all.filter((n) => n.startsWith('@opensip-cli/'))).toEqual([]);
18
+ expect(all).not.toContain('opensipTools');
19
+ });
20
+ it('dependency-cruiser wires clone-detection-imports-nothing and yagni-no-graph rules', () => {
21
+ const config = readFileSync(`${REPO_ROOT}/.config/dependency-cruiser.cjs`, 'utf8');
22
+ expect(config).toContain('clone-detection-imports-nothing');
23
+ expect(config).toContain('yagni-no-graph-engine');
24
+ expect(config).toContain('yagni-no-graph-adapter-packs');
25
+ });
26
+ it('YagniDetector contract has no requiresGraph field (S5)', () => {
27
+ const types = readFileSync(fileURLToPath(new URL('../detectors/types.ts', import.meta.url)), 'utf8');
28
+ expect(types).not.toContain('requiresGraph');
29
+ expect(types).not.toContain('graph-unavailable');
30
+ });
31
+ it('planDetectors has no graph-gated branch', () => {
32
+ const execute = readFileSync(fileURLToPath(new URL('../cli/execute-yagni.ts', import.meta.url)), 'utf8');
33
+ expect(execute).not.toContain('requiresGraph');
34
+ expect(execute).not.toContain('graph-unavailable');
35
+ });
36
+ });
37
+ //# sourceMappingURL=architecture-invariants.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"architecture-invariants.test.js","sourceRoot":"","sources":["../../src/__tests__/architecture-invariants.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=detector-progress.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector-progress.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/detector-progress.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { executeYagni } from '../cli/execute-yagni.js';
3
+ import { unusedConfigSurfaceDetector } from '../detectors/unused-config-surface.js';
4
+ const FIXTURE_ROOT = new URL('fixtures/unused-config-surface/pkg', import.meta.url).pathname;
5
+ function stubCli() {
6
+ return {
7
+ scope: { datastore: () => undefined },
8
+ deliverSignals: vi.fn(() => Promise.resolve({ delivered: false })),
9
+ };
10
+ }
11
+ const disabledStub = {
12
+ id: 'disabled-stub',
13
+ slug: 'yagni:disabled-stub',
14
+ description: 'stub detector disabled via config (test only)',
15
+ run: () => Promise.resolve({ signals: [], durationMs: 0 }),
16
+ };
17
+ const throwingStub = {
18
+ id: 'throwing-stub',
19
+ slug: 'yagni:throwing-stub',
20
+ description: 'stub detector that throws (test only)',
21
+ run: () => Promise.reject(new Error('detector exploded')),
22
+ };
23
+ describe('executeYagni detector progress callbacks (phases live view)', () => {
24
+ it('reports start/done per detector that runs, and skips disabled detectors', async () => {
25
+ const started = [];
26
+ const done = [];
27
+ const skippedBatches = [];
28
+ await executeYagni({
29
+ cwd: FIXTURE_ROOT,
30
+ config: { defaultMinConfidence: 'low', disabledDetectors: ['yagni:disabled-stub'] },
31
+ includeTests: true,
32
+ onDetectorStart: (slug) => started.push(slug),
33
+ onDetectorDone: (slug, durationMs) => done.push({ slug, durationMs }),
34
+ onDetectorsSkipped: (slugs) => skippedBatches.push([...slugs]),
35
+ }, stubCli(), [unusedConfigSurfaceDetector, disabledStub]);
36
+ expect(started).toEqual(['yagni:unused-config-surface']);
37
+ expect(done.map((d) => d.slug)).toEqual(['yagni:unused-config-surface']);
38
+ expect(typeof done[0]?.durationMs).toBe('number');
39
+ expect(skippedBatches).toEqual([['yagni:disabled-stub']]);
40
+ });
41
+ it('start precedes done for the same detector', async () => {
42
+ const events = [];
43
+ await executeYagni({
44
+ cwd: FIXTURE_ROOT,
45
+ config: { defaultMinConfidence: 'low' },
46
+ includeTests: true,
47
+ onDetectorStart: (slug) => events.push(`start:${slug}`),
48
+ onDetectorDone: (slug) => events.push(`done:${slug}`),
49
+ }, stubCli(), [unusedConfigSurfaceDetector]);
50
+ expect(events).toEqual([
51
+ 'start:yagni:unused-config-surface',
52
+ 'done:yagni:unused-config-surface',
53
+ ]);
54
+ });
55
+ it('records a failed unit when a detector throws', async () => {
56
+ const events = [];
57
+ const outcome = await executeYagni({
58
+ cwd: FIXTURE_ROOT,
59
+ config: { defaultMinConfidence: 'low' },
60
+ includeTests: true,
61
+ onDetectorStart: (slug) => events.push(`start:${slug}`),
62
+ onDetectorDone: (slug) => events.push(`done:${slug}`),
63
+ }, stubCli(), [throwingStub]);
64
+ expect(events).toEqual(['start:yagni:throwing-stub', 'done:yagni:throwing-stub']);
65
+ expect(outcome.envelope.units).toHaveLength(1);
66
+ expect(outcome.envelope.units[0]).toMatchObject({
67
+ slug: 'yagni:throwing-stub',
68
+ passed: false,
69
+ error: 'detector exploded',
70
+ });
71
+ });
72
+ });
73
+ //# sourceMappingURL=detector-progress.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector-progress.test.js","sourceRoot":"","sources":["../../src/__tests__/detector-progress.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Duplicate-detection parity + policy guard (ADR-0064).
3
+ *
4
+ * yagni and graph share ONE implementation: the detection algorithm + curation policy
5
+ * live in `@opensip-cli/clone-detection` (`findDuplicateBodies`), and the body hash is
6
+ * computed by the same `digestCanonicalBody(normalizeWhitespace(stripComments(slice)))`
7
+ * pipeline both tools use (yagni: `build-ts-inventory.ts`; graph:
8
+ * `graph-typescript/inventory-helpers/hash-body.ts`). This test pins yagni's extraction +
9
+ * the shared policy on a fixture corpus that exercises every branch — the standing guard
10
+ * against the 430-vs-0 (filter-divergence) regression class. The full both-binaries
11
+ * cross-tool run on a real repo is Phase 5 validation (Task 5.2), where graph has the heap
12
+ * to build a complete catalog.
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=duplicate-parity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-parity.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/duplicate-parity.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Duplicate-detection parity + policy guard (ADR-0064).
3
+ *
4
+ * yagni and graph share ONE implementation: the detection algorithm + curation policy
5
+ * live in `@opensip-cli/clone-detection` (`findDuplicateBodies`), and the body hash is
6
+ * computed by the same `digestCanonicalBody(normalizeWhitespace(stripComments(slice)))`
7
+ * pipeline both tools use (yagni: `build-ts-inventory.ts`; graph:
8
+ * `graph-typescript/inventory-helpers/hash-body.ts`). This test pins yagni's extraction +
9
+ * the shared policy on a fixture corpus that exercises every branch — the standing guard
10
+ * against the 430-vs-0 (filter-divergence) regression class. The full both-binaries
11
+ * cross-tool run on a real repo is Phase 5 validation (Task 5.2), where graph has the heap
12
+ * to build a complete catalog.
13
+ */
14
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { dirname, join } from 'node:path';
17
+ import { digestCanonicalBody, findDuplicateBodies, isEligibleKind, normalizeWhitespace, } from '@opensip-cli/clone-detection';
18
+ import { RunScope, runWithScope } from '@opensip-cli/core';
19
+ import { typescriptGraphAdapter } from '@opensip-cli/graph-typescript';
20
+ import { stripComments } from '@opensip-cli/lang-typescript';
21
+ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
22
+ import { duplicateBodyCandidateDetector } from '../detectors/duplicate-body-candidate.js';
23
+ import { buildTsInventory } from '../lib/build-ts-inventory.js';
24
+ import { readYagniMetadata } from '../scoring/confidence.js';
25
+ // A function whose normalized body is >= 200 chars (passes the per-instance floor).
26
+ const BIG = `export function summarize(items: readonly number[]): { total: number; max: number; count: number } {
27
+ let total = 0;
28
+ let max = Number.NEGATIVE_INFINITY;
29
+ for (const item of items) {
30
+ total += item;
31
+ if (item > max) { max = item; }
32
+ }
33
+ return { total, max, count: items.length };
34
+ }`;
35
+ // A function whose normalized body is between 80 and 200 chars (passes the cross-package
36
+ // floor of 80, fails the per-instance floor of 200).
37
+ const MEDIUM = `export function normalizeName(raw: string): string {
38
+ const trimmed = raw.trim().toLowerCase();
39
+ return trimmed.replace(/[^a-z0-9]+/g, '-');
40
+ }`;
41
+ // A function whose normalized body is < 80 chars (fails the cross-package floor).
42
+ const THIN = `export function noop(): void {
43
+ return;
44
+ }`;
45
+ // An arrow function — excluded by kind on both tools (yagni emits only eligible kinds).
46
+ const ARROW = `export const handler = (x: number): number => {
47
+ const doubled = x * 2;
48
+ return doubled + 1;
49
+ };`;
50
+ const STATIC_BLOCK = ` static {
51
+ const values = [11, 13, 17, 19, 23, 29, 31, 37];
52
+ const total = values.reduce((sum, value) => sum + value, 0);
53
+ const label = values.map((value) => String(value * total)).join(':');
54
+ globalThis.__opensipStaticParity = { label, total, count: values.length };
55
+ }`;
56
+ const LOOSE_UNIQUE = `export function looseOnly(value: number): number {
57
+ const scaled = value * 3;
58
+ return scaled + 7;
59
+ }`;
60
+ const NAMELESS_CHILD_UNIQUE = `export function namelessChildOnly(value: number): number {
61
+ const shifted = value + 101;
62
+ return shifted * 2;
63
+ }`;
64
+ const EMPTY_NAME_CHILD_UNIQUE = `export function emptyNameChildOnly(value: number): number {
65
+ const shifted = value + 211;
66
+ return shifted * 3;
67
+ }`;
68
+ let root;
69
+ beforeAll(() => {
70
+ root = mkdtempSync(join(tmpdir(), 'yagni-parity-'));
71
+ writeFileSync(join(root, 'tsconfig.json'), JSON.stringify({
72
+ compilerOptions: {
73
+ target: 'ES2022',
74
+ module: 'Node16',
75
+ moduleResolution: 'Node16',
76
+ skipLibCheck: true,
77
+ },
78
+ include: ['**/*.ts', '**/*.tsx'],
79
+ }));
80
+ const pkg = (name) => {
81
+ const dir = join(root, name);
82
+ mkdirSync(join(dir, 'src'), { recursive: true });
83
+ writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: `@p/${name}` }));
84
+ return dir;
85
+ };
86
+ const a = pkg('a');
87
+ const b = pkg('b');
88
+ const c = pkg('c');
89
+ mkdirSync(join(root, 'loose'), { recursive: true });
90
+ writeFileSync(join(root, 'loose', 'unique.ts'), LOOSE_UNIQUE);
91
+ mkdirSync(join(root, 'parent', 'nameless'), { recursive: true });
92
+ writeFileSync(join(root, 'parent', 'package.json'), JSON.stringify({ name: '@p/parent' }));
93
+ writeFileSync(join(root, 'parent', 'nameless', 'package.json'), JSON.stringify({}));
94
+ writeFileSync(join(root, 'parent', 'nameless', 'unique.ts'), NAMELESS_CHILD_UNIQUE);
95
+ mkdirSync(join(root, 'parent', 'empty-name'), { recursive: true });
96
+ writeFileSync(join(root, 'parent', 'empty-name', 'package.json'), JSON.stringify({ name: '' }));
97
+ writeFileSync(join(root, 'parent', 'empty-name', 'unique.ts'), EMPTY_NAME_CHILD_UNIQUE);
98
+ // MEDIUM duplicated across 3 packages → cross-package aggregate (3 occurrences).
99
+ writeFileSync(join(a, 'src', 'u.ts'), MEDIUM);
100
+ writeFileSync(join(b, 'src', 'u.ts'), MEDIUM);
101
+ writeFileSync(join(c, 'src', 'u.ts'), MEDIUM);
102
+ // …plus a copy in a TEST file → excluded by inTestFile (must NOT raise the count to 4).
103
+ writeFileSync(join(a, 'src', 'u.test.ts'), MEDIUM);
104
+ // BIG duplicated twice within ONE package → per-instance group (2 members).
105
+ writeFileSync(join(a, 'src', 'big1.ts'), BIG);
106
+ writeFileSync(join(a, 'src', 'big2.ts'), BIG);
107
+ // Class static blocks are graph-emitted as '<static-init>' function-declarations and
108
+ // must also be emitted by yagni. Duplicated twice within one package → per-instance group.
109
+ writeFileSync(join(a, 'src', 'static1.ts'), `export class StaticOne {\n${STATIC_BLOCK}\n}\n`);
110
+ writeFileSync(join(a, 'src', 'static2.ts'), `export class StaticTwo {\n${STATIC_BLOCK}\n}\n`);
111
+ // THIN duplicated across 3 packages → excluded (below the 80-char cross-package floor).
112
+ writeFileSync(join(a, 'src', 'thin.ts'), THIN);
113
+ writeFileSync(join(b, 'src', 'thin.ts'), THIN);
114
+ writeFileSync(join(c, 'src', 'thin.ts'), THIN);
115
+ // ARROW duplicated across packages → excluded by kind (never emitted by yagni).
116
+ writeFileSync(join(a, 'src', 'arr.ts'), ARROW);
117
+ writeFileSync(join(b, 'src', 'arr.ts'), ARROW);
118
+ // Graph's TS discovery excludes declarations and module-format TS files today; yagni's
119
+ // walker must keep the same duplicate-detection source scope.
120
+ writeFileSync(join(a, 'src', 'ambient.d.ts'), `declare class Ambient { constructor(); }\n`);
121
+ writeFileSync(join(a, 'src', 'module.mts'), MEDIUM);
122
+ writeFileSync(join(a, 'src', 'common.cts'), MEDIUM);
123
+ // A class + default-export + an overload signature — exercises the method / constructor /
124
+ // getter / setter / default-export classify branches and the body-less skip. Single
125
+ // package, not duplicated, so it adds no group.
126
+ writeFileSync(join(a, 'src', 'cls.ts'), `export class Widget {
127
+ constructor(private readonly base: number) {}
128
+ get value(): number { return this.base; }
129
+ set value(v: number) { /* readonly demo */ void v; }
130
+ compute(): number { return this.base * 2 + 1; }
131
+ }
132
+ export default function (): number { return 42; }
133
+ export function overload(x: number): number;
134
+ export function overload(x: string): string;
135
+ export function overload(x: unknown): unknown { return x; }
136
+ `);
137
+ });
138
+ afterAll(() => {
139
+ rmSync(root, { recursive: true, force: true });
140
+ });
141
+ describe('yagni duplicate detection — extraction + shared policy parity', () => {
142
+ it('matches graph-typescript eligible CloneCandidates and exact-duplicate findings', () => {
143
+ const yagniCandidates = buildTsInventory(root).filter(isEligibleKind);
144
+ const graphCandidates = graphEligibleCandidates(root);
145
+ expect(candidateSignatures(yagniCandidates)).toEqual(candidateSignatures(graphCandidates));
146
+ expect(duplicateFindingsSignature(yagniCandidates)).toEqual(duplicateFindingsSignature(graphCandidates));
147
+ });
148
+ it('computes byte-identical bodyHash via the canonical pipeline graph uses', () => {
149
+ const candidates = buildTsInventory(root);
150
+ const medium = candidates.find((c) => c.simpleName === 'normalizeName');
151
+ expect(medium).toBeDefined();
152
+ // The exact pipeline graph-typescript's digestFunctionBody uses — same shared fns.
153
+ const expected = digestCanonicalBody(normalizeWhitespace(stripComments(MEDIUM)));
154
+ expect(medium?.bodyHash).toBe(expected.hash);
155
+ expect(medium?.bodySize).toBe(expected.size);
156
+ // All copies share one hash (so they group).
157
+ const mediumHashes = new Set(candidates.filter((c) => c.simpleName === 'normalizeName').map((c) => c.bodyHash));
158
+ expect(mediumHashes.size).toBe(1);
159
+ });
160
+ it('applies the shared curation policy exactly (cross-package, test-exclusion, floors, kind)', () => {
161
+ const candidates = buildTsInventory(root);
162
+ const { aggregates, groups } = findDuplicateBodies(candidates);
163
+ // Exactly ONE cross-package aggregate: MEDIUM across @p/a, @p/b, @p/c.
164
+ expect(aggregates).toHaveLength(1);
165
+ const agg = aggregates[0];
166
+ expect(agg?.packages).toEqual(['@p/a', '@p/b', '@p/c']);
167
+ // 3 occurrences — the test-file copy in @p/a is EXCLUDED (not 4).
168
+ expect(agg?.occurrenceCount).toBe(3);
169
+ // Exactly TWO per-instance groups: BIG twice and STATIC_BLOCK twice in @p/a.
170
+ expect(groups).toHaveLength(2);
171
+ const groupNames = groups.map((g) => g.members.map((m) => m.simpleName).sort()).sort();
172
+ expect(groupNames).toEqual([
173
+ ['<static-init>', '<static-init>'],
174
+ ['summarize', 'summarize'],
175
+ ]);
176
+ // THIN (below floor) and ARROW (excluded kind) produce no finding.
177
+ const allHashes = [...aggregates.map((a) => a.bodyHash), ...groups.map((g) => g.bodyHash)];
178
+ const thinHash = digestCanonicalBody(normalizeWhitespace(stripComments(THIN))).hash;
179
+ expect(allHashes).not.toContain(thinHash);
180
+ expect(candidates.some((c) => c.kind === 'arrow')).toBe(false);
181
+ });
182
+ it('classifies class members + default export, and skips body-less overloads', () => {
183
+ const candidates = buildTsInventory(root);
184
+ const kinds = new Set(candidates.filter((c) => c.filePath.endsWith('cls.ts')).map((c) => c.kind));
185
+ expect(kinds).toEqual(new Set(['method', 'constructor', 'getter', 'setter', 'function-declaration']));
186
+ // The default-export function records the '<default>' name; the overload SIGNATURE
187
+ // (body-less) is skipped — only the implementation is a candidate.
188
+ const names = candidates.filter((c) => c.filePath.endsWith('cls.ts')).map((c) => c.simpleName);
189
+ expect(names).toContain('<default>');
190
+ expect(names).toContain('Widget');
191
+ expect(names.filter((n) => n === 'overload')).toHaveLength(1);
192
+ });
193
+ it('the detector emits reduction findings with reduction metadata (aggregate + per-instance)', async () => {
194
+ const result = await runWithScope(new RunScope(), () => duplicateBodyCandidateDetector.run({
195
+ cwd: root,
196
+ config: {},
197
+ graphCatalog: null,
198
+ includeTests: false,
199
+ }));
200
+ // 1 cross-package aggregate + 2 per-instance groups = 3 findings.
201
+ expect(result.signals).toHaveLength(3);
202
+ const metas = result.signals.map((s) => readYagniMetadata(s));
203
+ expect(metas.every((m) => m?.reductionCategory === 'dedupe')).toBe(true);
204
+ expect(metas.every((m) => m?.confidence === 'medium')).toBe(true);
205
+ expect(metas.every((m) => (m?.locDelta?.netEstimate ?? -1) >= 0)).toBe(true);
206
+ // The cross-package finding carries the 'cross-package' risk tag; the per-instance one does not.
207
+ expect(metas.some((m) => m?.riskTags.includes('cross-package'))).toBe(true);
208
+ expect(metas.some((m) => m?.riskTags.length === 0)).toBe(true);
209
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
210
+ });
211
+ });
212
+ function graphEligibleCandidates(projectRoot) {
213
+ const discovery = typescriptGraphAdapter.discoverFiles({ cwd: projectRoot });
214
+ const parsed = typescriptGraphAdapter.parseProject({
215
+ projectDirAbs: discovery.projectDirAbs,
216
+ files: discovery.files,
217
+ configPathAbs: discovery.configPathAbs,
218
+ compilerOptions: discovery.compilerOptions,
219
+ resolutionMode: 'exact',
220
+ });
221
+ expect(parsed.parseErrors).toEqual([]);
222
+ const walked = typescriptGraphAdapter.walkProject({
223
+ project: parsed.project,
224
+ projectDirAbs: discovery.projectDirAbs,
225
+ files: discovery.files,
226
+ });
227
+ expect(walked.parseErrors).toEqual([]);
228
+ return Object.values(walked.occurrences)
229
+ .flatMap((occs) => occs.map((occ) => graphOccurrenceToCandidate(projectRoot, occ)))
230
+ .filter(isEligibleKind);
231
+ }
232
+ function graphOccurrenceToCandidate(projectRoot, occ) {
233
+ return {
234
+ bodyHash: occ.bodyHash,
235
+ kind: occ.kind,
236
+ inTestFile: occ.inTestFile,
237
+ filePath: occ.filePath,
238
+ line: occ.line,
239
+ column: occ.column,
240
+ endLine: occ.endLine,
241
+ simpleName: occ.simpleName,
242
+ qualifiedName: occ.qualifiedName,
243
+ ...(occ.bodySize === undefined ? {} : { bodySize: occ.bodySize }),
244
+ package: packageFor(projectRoot, occ.filePath),
245
+ };
246
+ }
247
+ function packageFor(projectRoot, projectRel) {
248
+ let dir = dirname(join(projectRoot, projectRel));
249
+ while (dir.startsWith(projectRoot)) {
250
+ try {
251
+ const raw = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
252
+ if (typeof raw.name === 'string' && raw.name.length > 0)
253
+ return raw.name;
254
+ }
255
+ catch {
256
+ // Keep walking toward the project root.
257
+ }
258
+ const parent = dirname(dir);
259
+ if (parent === dir)
260
+ break;
261
+ dir = parent;
262
+ }
263
+ const segment = projectRel.split('/')[0];
264
+ return segment && segment !== projectRel ? segment : '<unknown>';
265
+ }
266
+ function candidateSignatures(candidates) {
267
+ return candidates
268
+ .map((c) => [
269
+ c.bodyHash,
270
+ String(c.bodySize ?? ''),
271
+ c.kind,
272
+ String(c.inTestFile),
273
+ c.filePath,
274
+ String(c.line),
275
+ String(c.column),
276
+ String(c.endLine),
277
+ c.simpleName,
278
+ c.qualifiedName,
279
+ c.package ?? '',
280
+ ].join('|'))
281
+ .sort();
282
+ }
283
+ function duplicateFindingsSignature(candidates) {
284
+ const findings = findDuplicateBodies(candidates);
285
+ return {
286
+ aggregates: findings.aggregates
287
+ .map((a) => `${a.bodyHash}|${a.packages.join(',')}|${String(a.occurrenceCount)}|${memberId(a.anchor)}`)
288
+ .sort(),
289
+ groups: findings.groups
290
+ .map((g) => `${g.bodyHash}|${g.members.map(memberId).sort().join(',')}`)
291
+ .sort(),
292
+ };
293
+ }
294
+ function memberId(candidate) {
295
+ return [
296
+ candidate.qualifiedName,
297
+ candidate.filePath,
298
+ String(candidate.line),
299
+ String(candidate.column),
300
+ candidate.simpleName,
301
+ candidate.package ?? '',
302
+ ].join('@');
303
+ }
304
+ //# sourceMappingURL=duplicate-parity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-parity.test.js","sourceRoot":"","sources":["../../src/__tests__/duplicate-parity.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hardening.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hardening.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hardening.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { RunScope, createSignal, runWithScope, runWithScopeSync } from '@opensip-cli/core';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import { executeYagni } from '../cli/execute-yagni.js';
7
+ import { YagniConfigSchema } from '../cli/yagni-config-schema.js';
8
+ import { duplicateBodyCandidateDetector } from '../detectors/duplicate-body-candidate.js';
9
+ import { buildTsInventory } from '../lib/build-ts-inventory.js';
10
+ function stubCli() {
11
+ return {
12
+ scope: { datastore: () => undefined },
13
+ deliverSignals: vi.fn(() => Promise.resolve({ delivered: false })),
14
+ };
15
+ }
16
+ describe('yagni hardening (H1–H4)', () => {
17
+ it('rejects unknown keys in the strict yagni config block (H1)', () => {
18
+ expect(YagniConfigSchema.safeParse({ graphMode: 'build' }).success).toBe(false);
19
+ });
20
+ it('emits project-relative paths under the project root (H2)', () => {
21
+ const dir = mkdtempSync(join(tmpdir(), 'yagni-hardening-'));
22
+ try {
23
+ mkdirSync(join(dir, 'src'));
24
+ writeFileSync(join(dir, 'src', 'sample.ts'), `export function secretFn() { return 'SECRET_TOKEN_abc123'; }\n`);
25
+ const scope = new RunScope();
26
+ const candidates = runWithScopeSync(scope, () => buildTsInventory(dir));
27
+ for (const c of candidates) {
28
+ expect(c.filePath.startsWith('../')).toBe(false);
29
+ expect(c.filePath.includes('..')).toBe(false);
30
+ }
31
+ }
32
+ finally {
33
+ rmSync(dir, { recursive: true, force: true });
34
+ }
35
+ });
36
+ it('never surfaces raw body source in logger events or signals (H4)', async () => {
37
+ const dir = mkdtempSync(join(tmpdir(), 'yagni-secret-'));
38
+ const secret = 'SECRET_TOKEN_abc123';
39
+ const info = vi.fn();
40
+ try {
41
+ mkdirSync(join(dir, 'src'));
42
+ writeFileSync(join(dir, 'src', 'secret.ts'), `export function leak() { return '${secret}'; }\n`);
43
+ const scope = new RunScope({
44
+ logger: { debug: vi.fn(), info, warn: vi.fn(), error: vi.fn() },
45
+ });
46
+ const result = await runWithScope(scope, () => duplicateBodyCandidateDetector.run({
47
+ cwd: dir,
48
+ config: {},
49
+ graphCatalog: null,
50
+ includeTests: true,
51
+ }));
52
+ const logText = JSON.stringify(info.mock.calls);
53
+ const signalText = JSON.stringify(result.signals);
54
+ expect(logText).not.toContain(secret);
55
+ expect(signalText).not.toContain(secret);
56
+ }
57
+ finally {
58
+ rmSync(dir, { recursive: true, force: true });
59
+ }
60
+ });
61
+ it('honors documented @yagni-ignore-next-line directives', async () => {
62
+ const dir = mkdtempSync(join(tmpdir(), 'yagni-ignore-'));
63
+ try {
64
+ mkdirSync(join(dir, 'src'));
65
+ writeFileSync(join(dir, 'src', 'sample.ts'), [
66
+ '// @yagni-ignore-next-line duplicate-body-candidate -- fixture documents an intentional duplicate shape',
67
+ 'export function mirrored(): number { return 1; }',
68
+ ].join('\n'));
69
+ const detector = {
70
+ id: 'duplicate-body-candidate',
71
+ slug: 'yagni:duplicate-body-candidate',
72
+ description: 'test detector',
73
+ run: () => Promise.resolve({
74
+ durationMs: 0,
75
+ signals: [
76
+ createSignal({
77
+ source: 'yagni:duplicate-body-candidate',
78
+ provider: 'yagni',
79
+ ruleId: 'yagni:duplicate-body-candidate',
80
+ severity: 'medium',
81
+ category: 'quality',
82
+ message: 'duplicate',
83
+ code: { file: 'src/sample.ts', line: 2, column: 0 },
84
+ }),
85
+ ],
86
+ }),
87
+ };
88
+ const outcome = await executeYagni({ cwd: dir, config: { defaultMinConfidence: 'low' } }, stubCli(), [detector]);
89
+ expect(outcome.envelope.signals).toHaveLength(0);
90
+ expect(outcome.envelope.units[0]).toMatchObject({
91
+ slug: 'yagni:duplicate-body-candidate',
92
+ violationCount: 0,
93
+ ignoredCount: 1,
94
+ });
95
+ }
96
+ finally {
97
+ rmSync(dir, { recursive: true, force: true });
98
+ }
99
+ });
100
+ });
101
+ //# sourceMappingURL=hardening.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hardening.test.js","sourceRoot":"","sources":["../../src/__tests__/hardening.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-payload.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-payload.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session-payload.test.ts"],"names":[],"mappings":""}