@opensip-cli/yagni 0.1.11 → 0.1.13
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/README.md +3 -3
- package/dist/__tests__/apply-advisory-exit.test.js +2 -1
- package/dist/__tests__/apply-advisory-exit.test.js.map +1 -1
- package/dist/__tests__/architecture-invariants.test.d.ts +6 -0
- package/dist/__tests__/architecture-invariants.test.d.ts.map +1 -0
- package/dist/__tests__/architecture-invariants.test.js +37 -0
- package/dist/__tests__/architecture-invariants.test.js.map +1 -0
- package/dist/__tests__/detector-progress.test.d.ts +2 -0
- package/dist/__tests__/detector-progress.test.d.ts.map +1 -0
- package/dist/__tests__/detector-progress.test.js +77 -0
- package/dist/__tests__/detector-progress.test.js.map +1 -0
- package/dist/__tests__/duplicate-parity.test.d.ts +15 -0
- package/dist/__tests__/duplicate-parity.test.d.ts.map +1 -0
- package/dist/__tests__/duplicate-parity.test.js +304 -0
- package/dist/__tests__/duplicate-parity.test.js.map +1 -0
- package/dist/__tests__/hardening.test.d.ts +2 -0
- package/dist/__tests__/hardening.test.d.ts.map +1 -0
- package/dist/__tests__/hardening.test.js +102 -0
- package/dist/__tests__/hardening.test.js.map +1 -0
- package/dist/__tests__/session-payload.test.d.ts +2 -0
- package/dist/__tests__/session-payload.test.d.ts.map +1 -0
- package/dist/__tests__/session-payload.test.js +61 -0
- package/dist/__tests__/session-payload.test.js.map +1 -0
- package/dist/__tests__/walk-typescript-files.test.js +15 -1
- package/dist/__tests__/walk-typescript-files.test.js.map +1 -1
- package/dist/__tests__/yagni-coverage.test.js +49 -202
- package/dist/__tests__/yagni-coverage.test.js.map +1 -1
- package/dist/__tests__/yagni-golden.test.js +8 -24
- package/dist/__tests__/yagni-golden.test.js.map +1 -1
- package/dist/__tests__/yagni-presentation.test.js +5 -5
- package/dist/__tests__/yagni-presentation.test.js.map +1 -1
- package/dist/baseline-strategy.d.ts +1 -2
- package/dist/baseline-strategy.d.ts.map +1 -1
- package/dist/baseline-strategy.js +21 -16
- package/dist/baseline-strategy.js.map +1 -1
- package/dist/cli/__tests__/yagni-runner.test.js +10 -7
- package/dist/cli/__tests__/yagni-runner.test.js.map +1 -1
- package/dist/cli/execute-yagni.d.ts +15 -4
- package/dist/cli/execute-yagni.d.ts.map +1 -1
- package/dist/cli/execute-yagni.js +68 -30
- package/dist/cli/execute-yagni.js.map +1 -1
- package/dist/cli/report-data.d.ts +0 -2
- package/dist/cli/report-data.d.ts.map +1 -1
- package/dist/cli/report-data.js +0 -2
- package/dist/cli/report-data.js.map +1 -1
- package/dist/cli/yagni-command-spec.d.ts.map +1 -1
- package/dist/cli/yagni-command-spec.js +4 -19
- package/dist/cli/yagni-command-spec.js.map +1 -1
- package/dist/cli/yagni-config-schema.d.ts +1 -7
- package/dist/cli/yagni-config-schema.d.ts.map +1 -1
- package/dist/cli/yagni-config-schema.js +4 -6
- package/dist/cli/yagni-config-schema.js.map +1 -1
- package/dist/cli/yagni-config.d.ts.map +1 -1
- package/dist/cli/yagni-config.js +3 -7
- package/dist/cli/yagni-config.js.map +1 -1
- package/dist/cli/yagni-presentation.d.ts +1 -2
- package/dist/cli/yagni-presentation.d.ts.map +1 -1
- package/dist/cli/yagni-presentation.js +4 -4
- package/dist/cli/yagni-presentation.js.map +1 -1
- package/dist/cli/yagni-runner.d.ts +0 -2
- package/dist/cli/yagni-runner.d.ts.map +1 -1
- package/dist/cli/yagni-runner.js +40 -8
- package/dist/cli/yagni-runner.js.map +1 -1
- package/dist/detectors/duplicate-body-candidate.d.ts +9 -2
- package/dist/detectors/duplicate-body-candidate.d.ts.map +1 -1
- package/dist/detectors/duplicate-body-candidate.js +123 -120
- package/dist/detectors/duplicate-body-candidate.js.map +1 -1
- package/dist/detectors/registry.d.ts +9 -1
- package/dist/detectors/registry.d.ts.map +1 -1
- package/dist/detectors/registry.js +9 -1
- package/dist/detectors/registry.js.map +1 -1
- package/dist/detectors/types.d.ts +1 -2
- package/dist/detectors/types.d.ts.map +1 -1
- package/dist/detectors/unused-config-surface.d.ts.map +1 -1
- package/dist/detectors/unused-config-surface.js +0 -2
- package/dist/detectors/unused-config-surface.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/build-ts-inventory.d.ts +33 -0
- package/dist/lib/build-ts-inventory.d.ts.map +1 -0
- package/dist/lib/build-ts-inventory.js +252 -0
- package/dist/lib/build-ts-inventory.js.map +1 -0
- package/dist/persistence/session-payload.d.ts +7 -10
- package/dist/persistence/session-payload.d.ts.map +1 -1
- package/dist/persistence/session-payload.js +64 -6
- package/dist/persistence/session-payload.js.map +1 -1
- package/dist/scoring/__tests__/confidence.test.js +3 -4
- package/dist/scoring/__tests__/confidence.test.js.map +1 -1
- package/dist/scoring/confidence.d.ts +1 -1
- package/dist/scoring/confidence.d.ts.map +1 -1
- package/dist/scoring/confidence.js +1 -2
- package/dist/scoring/confidence.js.map +1 -1
- package/dist/tool.d.ts.map +1 -1
- package/dist/tool.js +3 -1
- package/dist/tool.js.map +1 -1
- package/dist/types/yagni-config.d.ts +1 -4
- package/dist/types/yagni-config.d.ts.map +1 -1
- package/dist/types/yagni-config.js +0 -1
- package/dist/types/yagni-config.js.map +1 -1
- package/dist/types/yagni-metadata.d.ts +0 -1
- package/dist/types/yagni-metadata.d.ts.map +1 -1
- package/package.json +10 -10
- package/dist/__tests__/graph-evidence.test.d.ts +0 -2
- package/dist/__tests__/graph-evidence.test.d.ts.map +0 -1
- package/dist/__tests__/graph-evidence.test.js +0 -159
- package/dist/__tests__/graph-evidence.test.js.map +0 -1
- package/dist/evidence/graph-evidence.d.ts +0 -15
- package/dist/evidence/graph-evidence.d.ts.map +0 -1
- package/dist/evidence/graph-evidence.js +0 -89
- package/dist/evidence/graph-evidence.js.map +0 -1
- package/dist/evidence/load-graph-adapters.d.ts +0 -13
- package/dist/evidence/load-graph-adapters.d.ts.map +0 -1
- package/dist/evidence/load-graph-adapters.js +0 -67
- package/dist/evidence/load-graph-adapters.js.map +0 -1
- package/dist/lib/isolate-exit-code.d.ts +0 -8
- package/dist/lib/isolate-exit-code.d.ts.map +0 -1
- package/dist/lib/isolate-exit-code.js +0 -27
- package/dist/lib/isolate-exit-code.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
reportFailure: vi.fn(() => Promise.resolve()),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
describe('yagni hardening (H1–H4)', () => {
|
|
18
|
+
it('rejects unknown keys in the strict yagni config block (H1)', () => {
|
|
19
|
+
expect(YagniConfigSchema.safeParse({ graphMode: 'build' }).success).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it('emits project-relative paths under the project root (H2)', () => {
|
|
22
|
+
const dir = mkdtempSync(join(tmpdir(), 'yagni-hardening-'));
|
|
23
|
+
try {
|
|
24
|
+
mkdirSync(join(dir, 'src'));
|
|
25
|
+
writeFileSync(join(dir, 'src', 'sample.ts'), `export function secretFn() { return 'SECRET_TOKEN_abc123'; }\n`);
|
|
26
|
+
const scope = new RunScope();
|
|
27
|
+
const candidates = runWithScopeSync(scope, () => buildTsInventory(dir));
|
|
28
|
+
for (const c of candidates) {
|
|
29
|
+
expect(c.filePath.startsWith('../')).toBe(false);
|
|
30
|
+
expect(c.filePath.includes('..')).toBe(false);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
rmSync(dir, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
it('never surfaces raw body source in logger events or signals (H4)', async () => {
|
|
38
|
+
const dir = mkdtempSync(join(tmpdir(), 'yagni-secret-'));
|
|
39
|
+
const secret = 'SECRET_TOKEN_abc123';
|
|
40
|
+
const info = vi.fn();
|
|
41
|
+
try {
|
|
42
|
+
mkdirSync(join(dir, 'src'));
|
|
43
|
+
writeFileSync(join(dir, 'src', 'secret.ts'), `export function leak() { return '${secret}'; }\n`);
|
|
44
|
+
const scope = new RunScope({
|
|
45
|
+
logger: { debug: vi.fn(), info, warn: vi.fn(), error: vi.fn() },
|
|
46
|
+
});
|
|
47
|
+
const result = await runWithScope(scope, () => duplicateBodyCandidateDetector.run({
|
|
48
|
+
cwd: dir,
|
|
49
|
+
config: {},
|
|
50
|
+
graphCatalog: null,
|
|
51
|
+
includeTests: true,
|
|
52
|
+
}));
|
|
53
|
+
const logText = JSON.stringify(info.mock.calls);
|
|
54
|
+
const signalText = JSON.stringify(result.signals);
|
|
55
|
+
expect(logText).not.toContain(secret);
|
|
56
|
+
expect(signalText).not.toContain(secret);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
rmSync(dir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
it('honors documented @yagni-ignore-next-line directives', async () => {
|
|
63
|
+
const dir = mkdtempSync(join(tmpdir(), 'yagni-ignore-'));
|
|
64
|
+
try {
|
|
65
|
+
mkdirSync(join(dir, 'src'));
|
|
66
|
+
writeFileSync(join(dir, 'src', 'sample.ts'), [
|
|
67
|
+
'// @yagni-ignore-next-line duplicate-body-candidate -- fixture documents an intentional duplicate shape',
|
|
68
|
+
'export function mirrored(): number { return 1; }',
|
|
69
|
+
].join('\n'));
|
|
70
|
+
const detector = {
|
|
71
|
+
id: 'duplicate-body-candidate',
|
|
72
|
+
slug: 'yagni:duplicate-body-candidate',
|
|
73
|
+
description: 'test detector',
|
|
74
|
+
run: () => Promise.resolve({
|
|
75
|
+
durationMs: 0,
|
|
76
|
+
signals: [
|
|
77
|
+
createSignal({
|
|
78
|
+
source: 'yagni:duplicate-body-candidate',
|
|
79
|
+
provider: 'yagni',
|
|
80
|
+
ruleId: 'yagni:duplicate-body-candidate',
|
|
81
|
+
severity: 'medium',
|
|
82
|
+
category: 'quality',
|
|
83
|
+
message: 'duplicate',
|
|
84
|
+
code: { file: 'src/sample.ts', line: 2, column: 0 },
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
const outcome = await executeYagni({ cwd: dir, config: { defaultMinConfidence: 'low' } }, stubCli(), [detector]);
|
|
90
|
+
expect(outcome.envelope.signals).toHaveLength(0);
|
|
91
|
+
expect(outcome.envelope.units[0]).toMatchObject({
|
|
92
|
+
slug: 'yagni:duplicate-body-candidate',
|
|
93
|
+
violationCount: 0,
|
|
94
|
+
ignoredCount: 1,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
rmSync(dir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# 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 @@
|
|
|
1
|
+
{"version":3,"file":"session-payload.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/session-payload.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildYagniSessionPayload, readYagniSessionPayload, } from '../persistence/session-payload.js';
|
|
3
|
+
import { buildYagniRunSummary } from '../scoring/confidence.js';
|
|
4
|
+
function minimalEnvelope() {
|
|
5
|
+
return {
|
|
6
|
+
schemaVersion: 2,
|
|
7
|
+
tool: 'yagni',
|
|
8
|
+
runId: 'run-1',
|
|
9
|
+
createdAt: '2026-06-25T00:00:00.000Z',
|
|
10
|
+
verdict: {
|
|
11
|
+
score: 100,
|
|
12
|
+
passed: true,
|
|
13
|
+
summary: { total: 0, passed: 0, failed: 0, errors: 0, warnings: 0 },
|
|
14
|
+
},
|
|
15
|
+
units: [],
|
|
16
|
+
signals: [],
|
|
17
|
+
baselineIdentity: {
|
|
18
|
+
fingerprintStrategyId: 'yagni.sha256-detector-locations',
|
|
19
|
+
fingerprintStrategyVersion: 1,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
describe('YagniSessionPayload', () => {
|
|
24
|
+
it('round-trips a fresh payload without graph fields', () => {
|
|
25
|
+
const summary = buildYagniRunSummary([], []);
|
|
26
|
+
const payload = buildYagniSessionPayload(minimalEnvelope(), [], summary);
|
|
27
|
+
expect(payload.summary).not.toHaveProperty('graphMode');
|
|
28
|
+
expect(payload.summary).not.toHaveProperty('graphBuilt');
|
|
29
|
+
expect(payload.summary).not.toHaveProperty('graphDetail');
|
|
30
|
+
expect(readYagniSessionPayload(payload)).toEqual(payload);
|
|
31
|
+
});
|
|
32
|
+
it('forward-compat loads pre-feature rows carrying removed graph fields', () => {
|
|
33
|
+
const legacy = {
|
|
34
|
+
__version: 1,
|
|
35
|
+
summary: {
|
|
36
|
+
total: 1,
|
|
37
|
+
passed: 1,
|
|
38
|
+
failed: 0,
|
|
39
|
+
errors: 0,
|
|
40
|
+
warnings: 0,
|
|
41
|
+
skippedDetectors: [],
|
|
42
|
+
graphMode: 'off',
|
|
43
|
+
graphBuilt: false,
|
|
44
|
+
graphDetail: 'legacy detail',
|
|
45
|
+
yagni: {
|
|
46
|
+
totalCandidates: 0,
|
|
47
|
+
byConfidence: { high: 0, medium: 0, low: 0 },
|
|
48
|
+
estimatedTotalLocReduction: 0,
|
|
49
|
+
graphMode: 'off',
|
|
50
|
+
skippedDetectors: [],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
checks: [],
|
|
54
|
+
};
|
|
55
|
+
const loaded = readYagniSessionPayload(legacy);
|
|
56
|
+
expect(loaded).toBeDefined();
|
|
57
|
+
expect(loaded?.summary).not.toHaveProperty('graphMode');
|
|
58
|
+
expect(loaded?.summary.yagni).not.toHaveProperty('graphMode');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=session-payload.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-payload.test.js","sourceRoot":"","sources":["../../src/__tests__/session-payload.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EACL,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAIhE,SAAS,eAAe;IACtB,OAAO;QACL,aAAa,EAAE,CAAC;QAChB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,SAAS,EAAE,0BAA0B;QACrC,OAAO,EAAE;YACP,KAAK,EAAE,GAAG;YACV,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE;SACpE;QACD,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAE;QACX,gBAAgB,EAAE;YAChB,qBAAqB,EAAE,iCAAiC;YACxD,0BAA0B,EAAE,CAAC;SAC9B;KACF,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,OAAO,GAAG,oBAAoB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,wBAAwB,CAAC,eAAe,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QACzE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACxD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;QACzD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC1D,MAAM,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,MAAM,GAAG;YACb,SAAS,EAAE,CAAC;YACZ,OAAO,EAAE;gBACP,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,CAAC;gBACT,QAAQ,EAAE,CAAC;gBACX,gBAAgB,EAAE,EAAE;gBACpB,SAAS,EAAE,KAAK;gBAChB,UAAU,EAAE,KAAK;gBACjB,WAAW,EAAE,eAAe;gBAC5B,KAAK,EAAE;oBACL,eAAe,EAAE,CAAC;oBAClB,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;oBAC5C,0BAA0B,EAAE,CAAC;oBAC7B,SAAS,EAAE,KAAK;oBAChB,gBAAgB,EAAE,EAAE;iBACrB;aACF;YACD,MAAM,EAAE,EAAE;SACX,CAAC;QACF,MAAM,MAAM,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -17,6 +17,10 @@ function makeFixtureTree() {
|
|
|
17
17
|
}
|
|
18
18
|
for (const file of [
|
|
19
19
|
'src/index.ts',
|
|
20
|
+
'src/view.tsx',
|
|
21
|
+
'src/types.d.ts',
|
|
22
|
+
'src/module.mts',
|
|
23
|
+
'src/common.cts',
|
|
20
24
|
'src/__tests__/unit.test.ts',
|
|
21
25
|
'src/__tests__/fixtures/sample.ts',
|
|
22
26
|
'src/__tests__/__fixtures__/golden.ts',
|
|
@@ -35,12 +39,22 @@ describe('walkTypeScriptFiles', () => {
|
|
|
35
39
|
});
|
|
36
40
|
it('excludes tests and fixtures unless includeTests is enabled', () => {
|
|
37
41
|
const root = makeFixtureTree();
|
|
38
|
-
expect(rel(root, walkTypeScriptFiles(root, false))).toEqual([
|
|
42
|
+
expect(rel(root, walkTypeScriptFiles(root, false))).toEqual([
|
|
43
|
+
'src/common.cts',
|
|
44
|
+
'src/index.ts',
|
|
45
|
+
'src/module.mts',
|
|
46
|
+
'src/types.d.ts',
|
|
47
|
+
'src/view.tsx',
|
|
48
|
+
]);
|
|
39
49
|
expect(rel(root, walkTypeScriptFiles(root, true))).toEqual([
|
|
40
50
|
'src/__tests__/__fixtures__/golden.ts',
|
|
41
51
|
'src/__tests__/fixtures/sample.ts',
|
|
42
52
|
'src/__tests__/unit.test.ts',
|
|
53
|
+
'src/common.cts',
|
|
43
54
|
'src/index.ts',
|
|
55
|
+
'src/module.mts',
|
|
56
|
+
'src/types.d.ts',
|
|
57
|
+
'src/view.tsx',
|
|
44
58
|
]);
|
|
45
59
|
});
|
|
46
60
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walk-typescript-files.test.js","sourceRoot":"","sources":["../../src/__tests__/walk-typescript-files.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,KAAK,GAAa,EAAE,CAAC;AAE3B,SAAS,eAAe;IACtB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,KAAK,MAAM,GAAG,IAAI;QAChB,KAAK;QACL,eAAe;QACf,wBAAwB;QACxB,4BAA4B;KAC7B,EAAE,CAAC;QACF,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,KAAK,MAAM,IAAI,IAAI;QACjB,cAAc;QACd,4BAA4B;QAC5B,kCAAkC;QAClC,sCAAsC;KACvC,EAAE,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,GAAG,CAAC,IAAY,EAAE,KAAwB;IACjD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;QAE/B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"walk-typescript-files.test.js","sourceRoot":"","sources":["../../src/__tests__/walk-typescript-files.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,KAAK,GAAa,EAAE,CAAC;AAE3B,SAAS,eAAe;IACtB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,KAAK,MAAM,GAAG,IAAI;QAChB,KAAK;QACL,eAAe;QACf,wBAAwB;QACxB,4BAA4B;KAC7B,EAAE,CAAC;QACF,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,KAAK,MAAM,IAAI,IAAI;QACjB,cAAc;QACd,cAAc;QACd,gBAAgB;QAChB,gBAAgB;QAChB,gBAAgB;QAChB,4BAA4B;QAC5B,kCAAkC;QAClC,sCAAsC;KACvC,EAAE,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,GAAG,CAAC,IAAY,EAAE,KAAwB;IACjD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAChF,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;QAE/B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAC1D,gBAAgB;YAChB,cAAc;YACd,gBAAgB;YAChB,gBAAgB;YAChB,cAAc;SACf,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACzD,sCAAsC;YACtC,kCAAkC;YAClC,4BAA4B;YAC5B,gBAAgB;YAChB,cAAc;YACd,gBAAgB;YAChB,gBAAgB;YAChB,cAAc;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -11,7 +11,6 @@ import { YagniConfigSchema, yagniConfigDeclaration } from '../cli/yagni-config-s
|
|
|
11
11
|
import { loadYagniConfig } from '../cli/yagni-config.js';
|
|
12
12
|
import { buildYagniRunPresentation, buildYagniPresentationLines, } from '../cli/yagni-presentation.js';
|
|
13
13
|
import { createYagniSignal } from '../detectors/create-yagni-signal.js';
|
|
14
|
-
import { duplicateBodyCandidateDetector } from '../detectors/duplicate-body-candidate.js';
|
|
15
14
|
import { unusedConfigSurfaceDetector } from '../detectors/unused-config-surface.js';
|
|
16
15
|
import { resolveYagniPositionalPaths } from '../lib/resolve-positional-paths.js';
|
|
17
16
|
import { buildYagniSessionPayload } from '../persistence/session-payload.js';
|
|
@@ -43,6 +42,7 @@ function makeCli() {
|
|
|
43
42
|
writeSarif: vi.fn(() => Promise.resolve()),
|
|
44
43
|
maybeOpenReport: vi.fn(() => Promise.resolve()),
|
|
45
44
|
_state: state,
|
|
45
|
+
reportFailure: vi.fn(() => Promise.resolve()),
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
function signal(id, confidence, netEstimate, estimateKind, category = 'config') {
|
|
@@ -72,20 +72,6 @@ function signal(id, confidence, netEstimate, estimateKind, category = 'config')
|
|
|
72
72
|
},
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
-
function graphOccurrence(overrides) {
|
|
76
|
-
return {
|
|
77
|
-
kind: 'function-declaration',
|
|
78
|
-
params: [],
|
|
79
|
-
returnType: null,
|
|
80
|
-
enclosingClass: null,
|
|
81
|
-
decorators: [],
|
|
82
|
-
visibility: 'exported',
|
|
83
|
-
inTestFile: false,
|
|
84
|
-
definedInGenerated: false,
|
|
85
|
-
calls: [],
|
|
86
|
-
...overrides,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
75
|
function envelope(input) {
|
|
90
76
|
const summary = input.summary ?? {
|
|
91
77
|
total: input.units?.length ?? 0,
|
|
@@ -102,6 +88,10 @@ function envelope(input) {
|
|
|
102
88
|
verdict: { score: 1, passed: summary.errors === 0, summary },
|
|
103
89
|
units: input.units ?? [],
|
|
104
90
|
signals: input.signals,
|
|
91
|
+
baselineIdentity: {
|
|
92
|
+
fingerprintStrategyId: 'yagni.sha256-detector-locations',
|
|
93
|
+
fingerprintStrategyVersion: 1,
|
|
94
|
+
},
|
|
105
95
|
};
|
|
106
96
|
}
|
|
107
97
|
async function runCommandInScope(scope, rawOpts, cli) {
|
|
@@ -115,7 +105,6 @@ describe('yagni config, tool metadata, and command handler', () => {
|
|
|
115
105
|
Object.assign(scoped, {
|
|
116
106
|
toolConfig: {
|
|
117
107
|
yagni: {
|
|
118
|
-
graphMode: 'off',
|
|
119
108
|
defaultMinConfidence: 'high',
|
|
120
109
|
includeTests: true,
|
|
121
110
|
disabledDetectors: ['x'],
|
|
@@ -124,14 +113,12 @@ describe('yagni config, tool metadata, and command handler', () => {
|
|
|
124
113
|
});
|
|
125
114
|
const scopedConfig = runWithScopeSync(scoped, () => loadYagniConfig('/unused'));
|
|
126
115
|
expect(scopedConfig).toMatchObject({
|
|
127
|
-
graphMode: 'off',
|
|
128
116
|
defaultMinConfidence: 'high',
|
|
129
117
|
includeTests: true,
|
|
130
118
|
disabledDetectors: ['x'],
|
|
131
119
|
});
|
|
132
120
|
const emptyScope = new RunScope();
|
|
133
121
|
expect(runWithScopeSync(emptyScope, () => loadYagniConfig('/unused'))).toMatchObject({
|
|
134
|
-
graphMode: 'auto',
|
|
135
122
|
defaultMinConfidence: 'medium',
|
|
136
123
|
includeTests: false,
|
|
137
124
|
});
|
|
@@ -139,42 +126,43 @@ describe('yagni config, tool metadata, and command handler', () => {
|
|
|
139
126
|
writeFileSync(join(dir, 'opensip-cli.config.yml'), [
|
|
140
127
|
'schemaVersion: 1',
|
|
141
128
|
'yagni:',
|
|
142
|
-
' graphMode: reuse',
|
|
143
129
|
' defaultMinConfidence: low',
|
|
144
130
|
' failOnWarnings: 2',
|
|
145
131
|
' detectorSettings:',
|
|
146
|
-
'
|
|
147
|
-
'
|
|
132
|
+
' unused-config-surface:',
|
|
133
|
+
' someKnob: 3',
|
|
148
134
|
].join('\n'));
|
|
149
135
|
expect(loadYagniConfig(dir)).toMatchObject({
|
|
150
|
-
graphMode: 'reuse',
|
|
151
136
|
defaultMinConfidence: 'low',
|
|
152
137
|
failOnWarnings: 2,
|
|
153
|
-
detectorSettings: { '
|
|
138
|
+
detectorSettings: { 'unused-config-surface': { someKnob: 3 } },
|
|
154
139
|
});
|
|
155
140
|
const invalidDir = tempDir();
|
|
156
141
|
writeFileSync(join(invalidDir, 'opensip-cli.config.yml'), 'schemaVersion: 1\nyagni: nope\n');
|
|
157
|
-
expect(loadYagniConfig(invalidDir)).toMatchObject({
|
|
142
|
+
expect(loadYagniConfig(invalidDir)).toMatchObject({
|
|
143
|
+
defaultMinConfidence: 'medium',
|
|
144
|
+
});
|
|
158
145
|
});
|
|
159
146
|
it('exports config schema, report data, and tool metadata', () => {
|
|
160
147
|
expect(YagniConfigSchema.parse({
|
|
161
148
|
failOnErrors: 1,
|
|
162
149
|
failOnWarnings: 0,
|
|
163
150
|
defaultMinConfidence: 'medium',
|
|
164
|
-
graphMode: 'build',
|
|
165
151
|
includeTests: false,
|
|
166
|
-
disabledDetectors: ['
|
|
167
|
-
detectorSettings: { '
|
|
168
|
-
})).toMatchObject({
|
|
169
|
-
expect(YagniConfigSchema.safeParse({ graphMode: '
|
|
170
|
-
expect(yagniConfigDeclaration.env?.map((entry) => entry.envVar)).toContain('
|
|
152
|
+
disabledDetectors: ['unused-config-surface'],
|
|
153
|
+
detectorSettings: { 'unused-config-surface': { someKnob: 8 } },
|
|
154
|
+
})).toMatchObject({ defaultMinConfidence: 'medium' });
|
|
155
|
+
expect(YagniConfigSchema.safeParse({ graphMode: 'build' }).success).toBe(false);
|
|
156
|
+
expect(yagniConfigDeclaration.env?.map((entry) => entry.envVar)).toContain('OPENSIP_YAGNI_MIN_CONFIDENCE');
|
|
171
157
|
const reportData = collectYagniReportData({});
|
|
172
158
|
expect(reportData.yagniSummary).toMatchObject({
|
|
173
159
|
detectorCount: 2,
|
|
174
|
-
graphBackedCount: 1,
|
|
175
160
|
contractVersion: YAGNI_CONTRACT_VERSION,
|
|
176
161
|
});
|
|
177
|
-
expect(reportData.yagniCatalog).toEqual(expect.arrayContaining([
|
|
162
|
+
expect(reportData.yagniCatalog).toEqual(expect.arrayContaining([
|
|
163
|
+
expect.objectContaining({ slug: 'yagni:unused-config-surface' }),
|
|
164
|
+
expect.objectContaining({ slug: 'yagni:duplicate-body-candidate' }),
|
|
165
|
+
]));
|
|
178
166
|
expect(yagniTool.metadata).toMatchObject({
|
|
179
167
|
id: YAGNI_STABLE_ID,
|
|
180
168
|
name: 'yagni',
|
|
@@ -187,12 +175,11 @@ describe('yagni config, tool metadata, and command handler', () => {
|
|
|
187
175
|
const jsonCli = makeCli();
|
|
188
176
|
const scope = new RunScope();
|
|
189
177
|
Object.assign(scope, {
|
|
190
|
-
toolConfig: { yagni: {
|
|
178
|
+
toolConfig: { yagni: { defaultMinConfidence: 'low' } },
|
|
191
179
|
});
|
|
192
180
|
await runCommandInScope(scope, {
|
|
193
181
|
cwd: FIXTURE_ROOT,
|
|
194
182
|
json: true,
|
|
195
|
-
graph: 'off',
|
|
196
183
|
minConfidence: 'low',
|
|
197
184
|
includeTests: true,
|
|
198
185
|
detector: ['unused-config-surface'],
|
|
@@ -210,7 +197,6 @@ describe('yagni config, tool metadata, and command handler', () => {
|
|
|
210
197
|
await runCommandInScope(scope, {
|
|
211
198
|
cwd: FIXTURE_ROOT,
|
|
212
199
|
verbose: true,
|
|
213
|
-
graph: 'invalid',
|
|
214
200
|
minConfidence: 'invalid',
|
|
215
201
|
category: 'config',
|
|
216
202
|
includeTests: true,
|
|
@@ -224,145 +210,6 @@ describe('yagni config, tool metadata, and command handler', () => {
|
|
|
224
210
|
});
|
|
225
211
|
});
|
|
226
212
|
describe('yagni detectors and scoring helpers', () => {
|
|
227
|
-
it('emits duplicate-body candidates from graph body hashes', async () => {
|
|
228
|
-
const catalog = {
|
|
229
|
-
version: '1',
|
|
230
|
-
tool: 'graph',
|
|
231
|
-
language: 'typescript',
|
|
232
|
-
builtAt: '2026-06-22T00:00:00.000Z',
|
|
233
|
-
functions: {
|
|
234
|
-
a: [
|
|
235
|
-
graphOccurrence({
|
|
236
|
-
qualifiedName: 'pkgA.alpha',
|
|
237
|
-
simpleName: 'alpha',
|
|
238
|
-
filePath: join(FIXTURE_ROOT, 'src', 'a.ts'),
|
|
239
|
-
line: 10,
|
|
240
|
-
column: 2,
|
|
241
|
-
endLine: 18,
|
|
242
|
-
bodyHash: 'same-body-hash',
|
|
243
|
-
package: '@opensip-cli/a',
|
|
244
|
-
}),
|
|
245
|
-
],
|
|
246
|
-
b: [
|
|
247
|
-
graphOccurrence({
|
|
248
|
-
qualifiedName: 'pkgB.strip.test.<arrow:packages/languages/lang-python/src/__tests__/strip.test.ts:69:29>',
|
|
249
|
-
simpleName: '<arrow:packages/languages/lang-python/src/__tests__/strip.test.ts:69:29>',
|
|
250
|
-
filePath: join(FIXTURE_ROOT, 'src', 'b.ts'),
|
|
251
|
-
line: 20,
|
|
252
|
-
column: 4,
|
|
253
|
-
endLine: 28,
|
|
254
|
-
bodyHash: 'same-body-hash',
|
|
255
|
-
package: '@opensip-cli/b',
|
|
256
|
-
}),
|
|
257
|
-
graphOccurrence({
|
|
258
|
-
qualifiedName: 'pkgB.short',
|
|
259
|
-
simpleName: 'short',
|
|
260
|
-
filePath: join(FIXTURE_ROOT, 'src', 'short.ts'),
|
|
261
|
-
line: 1,
|
|
262
|
-
column: 1,
|
|
263
|
-
endLine: 2,
|
|
264
|
-
bodyHash: 'short-body-hash',
|
|
265
|
-
}),
|
|
266
|
-
],
|
|
267
|
-
},
|
|
268
|
-
};
|
|
269
|
-
const result = await duplicateBodyCandidateDetector.run({
|
|
270
|
-
cwd: FIXTURE_ROOT,
|
|
271
|
-
config: { detectorSettings: { 'duplicate-body-candidate': { minBodyLines: 3 } } },
|
|
272
|
-
graphCatalog: catalog,
|
|
273
|
-
includeTests: false,
|
|
274
|
-
});
|
|
275
|
-
expect(result.signals).toHaveLength(1);
|
|
276
|
-
const metadata = result.signals[0] === undefined ? undefined : readYagniMetadata(result.signals[0]);
|
|
277
|
-
const suggestedAction = metadata?.suggestedAction ?? '';
|
|
278
|
-
expect(suggestedAction).toBe('Consolidate with src/b.ts:20 (arrow function).');
|
|
279
|
-
expect(suggestedAction).not.toContain('<arrow:');
|
|
280
|
-
expect(metadata).toMatchObject({
|
|
281
|
-
detector: 'duplicate-body-candidate',
|
|
282
|
-
reductionCategory: 'dedupe',
|
|
283
|
-
confidence: 'medium',
|
|
284
|
-
evidence: [
|
|
285
|
-
expect.objectContaining({
|
|
286
|
-
data: expect.objectContaining({
|
|
287
|
-
occurrenceCount: 2,
|
|
288
|
-
packages: ['@opensip-cli/a', '@opensip-cli/b'],
|
|
289
|
-
peer: expect.objectContaining({
|
|
290
|
-
qualifiedName: 'pkgB.strip.test.<arrow:packages/languages/lang-python/src/__tests__/strip.test.ts:69:29>',
|
|
291
|
-
}),
|
|
292
|
-
}),
|
|
293
|
-
}),
|
|
294
|
-
],
|
|
295
|
-
});
|
|
296
|
-
const noGraph = await duplicateBodyCandidateDetector.run({
|
|
297
|
-
cwd: FIXTURE_ROOT,
|
|
298
|
-
config: {},
|
|
299
|
-
graphCatalog: null,
|
|
300
|
-
includeTests: false,
|
|
301
|
-
});
|
|
302
|
-
expect(noGraph.signals).toEqual([]);
|
|
303
|
-
});
|
|
304
|
-
it('formats duplicate-body peer names for human CLI output', async () => {
|
|
305
|
-
function pair(bodyHash, peer) {
|
|
306
|
-
return [
|
|
307
|
-
graphOccurrence({
|
|
308
|
-
qualifiedName: `${bodyHash}.anchor`,
|
|
309
|
-
simpleName: 'anchor',
|
|
310
|
-
filePath: join(FIXTURE_ROOT, 'src', `${bodyHash}-anchor.ts`),
|
|
311
|
-
line: 10,
|
|
312
|
-
column: 1,
|
|
313
|
-
endLine: 16,
|
|
314
|
-
bodyHash,
|
|
315
|
-
}),
|
|
316
|
-
graphOccurrence({
|
|
317
|
-
qualifiedName: `zz.${bodyHash}`,
|
|
318
|
-
simpleName: 'peer',
|
|
319
|
-
filePath: join(FIXTURE_ROOT, 'src', `${bodyHash}-peer.ts`),
|
|
320
|
-
line: 30,
|
|
321
|
-
column: 1,
|
|
322
|
-
endLine: 36,
|
|
323
|
-
bodyHash,
|
|
324
|
-
...peer,
|
|
325
|
-
}),
|
|
326
|
-
];
|
|
327
|
-
}
|
|
328
|
-
const catalog = {
|
|
329
|
-
version: '1',
|
|
330
|
-
tool: 'graph',
|
|
331
|
-
language: 'typescript',
|
|
332
|
-
builtAt: '2026-06-22T00:00:00.000Z',
|
|
333
|
-
functions: {
|
|
334
|
-
normal: pair('normal', { simpleName: 'namedHelper' }),
|
|
335
|
-
qualifiedArrow: pair('qualified-arrow', {
|
|
336
|
-
simpleName: 'packages/example/src/file.ts',
|
|
337
|
-
qualifiedName: 'zz.qualified.<arrow:packages/example/src/file.ts:5:9>',
|
|
338
|
-
}),
|
|
339
|
-
qualifiedTail: pair('qualified-tail', {
|
|
340
|
-
simpleName: 'packages/example/src/file.ts',
|
|
341
|
-
qualifiedName: 'zz.qualified.namedTail',
|
|
342
|
-
}),
|
|
343
|
-
fallback: pair('fallback', {
|
|
344
|
-
simpleName: 'packages/example/src/file.ts',
|
|
345
|
-
qualifiedName: 'zz.qualified.packages/example/src/file.ts',
|
|
346
|
-
}),
|
|
347
|
-
},
|
|
348
|
-
};
|
|
349
|
-
const result = await duplicateBodyCandidateDetector.run({
|
|
350
|
-
cwd: FIXTURE_ROOT,
|
|
351
|
-
config: { detectorSettings: { 'duplicate-body-candidate': { minBodyLines: 3 } } },
|
|
352
|
-
graphCatalog: catalog,
|
|
353
|
-
includeTests: false,
|
|
354
|
-
});
|
|
355
|
-
const actions = result.signals
|
|
356
|
-
.map((signal) => readYagniMetadata(signal)?.suggestedAction)
|
|
357
|
-
.filter((action) => action !== undefined);
|
|
358
|
-
expect(actions).toEqual(expect.arrayContaining([
|
|
359
|
-
'Consolidate with src/normal-peer.ts:30 (namedHelper).',
|
|
360
|
-
'Consolidate with src/qualified-arrow-peer.ts:30 (arrow function).',
|
|
361
|
-
'Consolidate with src/qualified-tail-peer.ts:30 (namedTail).',
|
|
362
|
-
'Consolidate with src/fallback-peer.ts:30 (function).',
|
|
363
|
-
]));
|
|
364
|
-
expect(actions.join('\n')).not.toContain('<arrow:');
|
|
365
|
-
});
|
|
366
213
|
it('sorts, filters, summarizes, and persists YAGNI signal metadata', () => {
|
|
367
214
|
const high = signal('high', 'high', 5, 'exact', 'config');
|
|
368
215
|
const medium = signal('medium', 'medium', 8, 'heuristic', 'dedupe');
|
|
@@ -402,35 +249,38 @@ describe('yagni detectors and scoring helpers', () => {
|
|
|
402
249
|
'yagni:alpha',
|
|
403
250
|
'yagni:beta',
|
|
404
251
|
]);
|
|
405
|
-
const summary = buildYagniRunSummary([low, medium, high, plain], '
|
|
406
|
-
{ id: 'skipped', slug: 'yagni:skipped', reason: 'disabled' },
|
|
407
|
-
{ id: 'graph', slug: 'yagni:graph', reason: 'graph-required', detail: 'missing' },
|
|
408
|
-
]);
|
|
252
|
+
const summary = buildYagniRunSummary([low, medium, high, plain], [{ id: 'skipped', slug: 'yagni:skipped', reason: 'disabled' }]);
|
|
409
253
|
expect(summary).toMatchObject({
|
|
410
254
|
totalCandidates: 4,
|
|
411
255
|
byConfidence: { high: 1, medium: 1, low: 1 },
|
|
412
256
|
estimatedTotalLocReduction: 33,
|
|
413
|
-
graphMode: 'reuse',
|
|
414
257
|
});
|
|
415
|
-
expect(summary.skippedDetectors).toEqual([
|
|
416
|
-
{ slug: 'yagni:skipped', reason: 'disabled' },
|
|
417
|
-
{ slug: 'yagni:graph', reason: 'graph-required', detail: 'missing' },
|
|
418
|
-
]);
|
|
258
|
+
expect(summary.skippedDetectors).toEqual([{ slug: 'yagni:skipped', reason: 'disabled' }]);
|
|
419
259
|
const payload = buildYagniSessionPayload(envelope({
|
|
420
260
|
signals: [high],
|
|
421
|
-
units: [
|
|
261
|
+
units: [
|
|
262
|
+
{
|
|
263
|
+
slug: high.source,
|
|
264
|
+
passed: false,
|
|
265
|
+
violationCount: 1,
|
|
266
|
+
durationMs: 7,
|
|
267
|
+
},
|
|
268
|
+
],
|
|
422
269
|
summary: { total: 1, passed: 0, failed: 1, errors: 0, warnings: 1 },
|
|
423
|
-
}), [],
|
|
424
|
-
graphMode: 'reuse',
|
|
425
|
-
graphBuilt: false,
|
|
426
|
-
yagniSummary: summary,
|
|
427
|
-
});
|
|
270
|
+
}), [], summary);
|
|
428
271
|
expect(payload.checks[0]).toMatchObject({
|
|
429
272
|
checkSlug: high.source,
|
|
430
273
|
violationCount: 1,
|
|
431
|
-
findings: [
|
|
274
|
+
findings: [
|
|
275
|
+
expect.objectContaining({
|
|
276
|
+
severity: 'warning',
|
|
277
|
+
metadata: high.metadata,
|
|
278
|
+
}),
|
|
279
|
+
],
|
|
432
280
|
});
|
|
433
|
-
expect(payload.summary
|
|
281
|
+
expect(payload.summary).not.toHaveProperty('graphMode');
|
|
282
|
+
expect(payload.summary).not.toHaveProperty('graphBuilt');
|
|
283
|
+
expect(payload.summary).not.toHaveProperty('graphDetail');
|
|
434
284
|
const errorFinding = {
|
|
435
285
|
...high,
|
|
436
286
|
severity: 'high',
|
|
@@ -443,12 +293,7 @@ describe('yagni detectors and scoring helpers', () => {
|
|
|
443
293
|
signals: [errorFinding, duplicateSource],
|
|
444
294
|
units: [{ slug: high.source, passed: false, durationMs: 9 }],
|
|
445
295
|
summary: { total: 1, passed: 0, failed: 1, errors: 1, warnings: 1 },
|
|
446
|
-
}), [],
|
|
447
|
-
graphMode: 'build',
|
|
448
|
-
graphBuilt: true,
|
|
449
|
-
graphDetail: 'built fresh catalog',
|
|
450
|
-
yagniSummary: summary,
|
|
451
|
-
});
|
|
296
|
+
}), [], summary);
|
|
452
297
|
expect(detailedPayload.checks[0]).toMatchObject({
|
|
453
298
|
violationCount: 2,
|
|
454
299
|
findings: [
|
|
@@ -458,7 +303,7 @@ describe('yagni detectors and scoring helpers', () => {
|
|
|
458
303
|
});
|
|
459
304
|
expect(detailedPayload.checks[0]?.findings[0]).not.toHaveProperty('line');
|
|
460
305
|
expect(detailedPayload.checks[0]?.findings[0]).not.toHaveProperty('suggestion');
|
|
461
|
-
expect(detailedPayload.summary
|
|
306
|
+
expect(detailedPayload.summary).not.toHaveProperty('graphDetail');
|
|
462
307
|
});
|
|
463
308
|
it('covers presentation variants and unreadable/oversized source skips', async () => {
|
|
464
309
|
const outcome = await unusedConfigSurfaceDetector.run({
|
|
@@ -484,7 +329,7 @@ describe('yagni detectors and scoring helpers', () => {
|
|
|
484
329
|
units: [{ slug: 'yagni:unused-config-surface', passed: true, durationMs: 1 }],
|
|
485
330
|
signals: outcome.signals,
|
|
486
331
|
summary: { total: 1, passed: 0, failed: 1, errors: 0, warnings: 1 },
|
|
487
|
-
}), FIXTURE_ROOT,
|
|
332
|
+
}), FIXTURE_ROOT, [{ id: 'x', slug: 'yagni:x', reason: 'disabled', detail: 'configured' }], true);
|
|
488
333
|
expect(verboseLines.join('\n')).toContain('Skipped detectors');
|
|
489
334
|
const presentation = buildYagniRunPresentation({
|
|
490
335
|
envelope: envelope({
|
|
@@ -492,12 +337,14 @@ describe('yagni detectors and scoring helpers', () => {
|
|
|
492
337
|
summary: { total: 0, passed: 1, failed: 0, errors: 0, warnings: 0 },
|
|
493
338
|
}),
|
|
494
339
|
cwd: FIXTURE_ROOT,
|
|
495
|
-
graphMode: 'off',
|
|
496
340
|
skippedDetectors: [],
|
|
497
341
|
verbose: false,
|
|
498
342
|
durationMs: 12,
|
|
499
343
|
});
|
|
500
|
-
expect(presentation.envelope.verdict.summary).toMatchObject({
|
|
344
|
+
expect(presentation.envelope.verdict.summary).toMatchObject({
|
|
345
|
+
total: 0,
|
|
346
|
+
warnings: 0,
|
|
347
|
+
});
|
|
501
348
|
const highNoDetails = signal('no-details', 'high', 0, 'exact');
|
|
502
349
|
const metadata = readYagniMetadata(highNoDetails);
|
|
503
350
|
const sparse = {
|
|
@@ -520,7 +367,7 @@ describe('yagni detectors and scoring helpers', () => {
|
|
|
520
367
|
const sparseLines = buildYagniPresentationLines(envelope({
|
|
521
368
|
signals: [noMetadata, sparse],
|
|
522
369
|
units: [{ slug: 'yagni:sparse', passed: false, durationMs: 1 }],
|
|
523
|
-
}), FIXTURE_ROOT,
|
|
370
|
+
}), FIXTURE_ROOT, [{ id: 'plain', slug: 'yagni:plain', reason: 'disabled' }], true);
|
|
524
371
|
const sparseText = sparseLines.join('\n');
|
|
525
372
|
expect(sparseText).toContain('<unknown>');
|
|
526
373
|
expect(sparseText).toContain('yagni:plain: disabled');
|