@opensip-cli/yagni 0.1.11

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 (152) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +33 -0
  4. package/dist/__tests__/apply-advisory-exit.test.d.ts +2 -0
  5. package/dist/__tests__/apply-advisory-exit.test.d.ts.map +1 -0
  6. package/dist/__tests__/apply-advisory-exit.test.js +31 -0
  7. package/dist/__tests__/apply-advisory-exit.test.js.map +1 -0
  8. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/app-config.d.ts +6 -0
  9. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/app-config.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/app-config.js +5 -0
  11. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/app-config.js.map +1 -0
  12. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/consumer.d.ts +3 -0
  13. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/consumer.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/consumer.js +4 -0
  15. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/consumer.js.map +1 -0
  16. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/index.d.ts +3 -0
  17. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/index.d.ts.map +1 -0
  18. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/index.js +2 -0
  19. package/dist/__tests__/fixtures/unused-config-surface/pkg/src/index.js.map +1 -0
  20. package/dist/__tests__/graph-evidence.test.d.ts +2 -0
  21. package/dist/__tests__/graph-evidence.test.d.ts.map +1 -0
  22. package/dist/__tests__/graph-evidence.test.js +159 -0
  23. package/dist/__tests__/graph-evidence.test.js.map +1 -0
  24. package/dist/__tests__/walk-typescript-files.test.d.ts +2 -0
  25. package/dist/__tests__/walk-typescript-files.test.d.ts.map +1 -0
  26. package/dist/__tests__/walk-typescript-files.test.js +47 -0
  27. package/dist/__tests__/walk-typescript-files.test.js.map +1 -0
  28. package/dist/__tests__/yagni-coverage.test.d.ts +2 -0
  29. package/dist/__tests__/yagni-coverage.test.d.ts.map +1 -0
  30. package/dist/__tests__/yagni-coverage.test.js +542 -0
  31. package/dist/__tests__/yagni-coverage.test.js.map +1 -0
  32. package/dist/__tests__/yagni-golden.test.d.ts +5 -0
  33. package/dist/__tests__/yagni-golden.test.d.ts.map +1 -0
  34. package/dist/__tests__/yagni-golden.test.js +135 -0
  35. package/dist/__tests__/yagni-golden.test.js.map +1 -0
  36. package/dist/__tests__/yagni-presentation.test.d.ts +2 -0
  37. package/dist/__tests__/yagni-presentation.test.d.ts.map +1 -0
  38. package/dist/__tests__/yagni-presentation.test.js +30 -0
  39. package/dist/__tests__/yagni-presentation.test.js.map +1 -0
  40. package/dist/baseline-strategy.d.ts +9 -0
  41. package/dist/baseline-strategy.d.ts.map +1 -0
  42. package/dist/baseline-strategy.js +30 -0
  43. package/dist/baseline-strategy.js.map +1 -0
  44. package/dist/cli/__tests__/yagni-runner.test.d.ts +5 -0
  45. package/dist/cli/__tests__/yagni-runner.test.d.ts.map +1 -0
  46. package/dist/cli/__tests__/yagni-runner.test.js +126 -0
  47. package/dist/cli/__tests__/yagni-runner.test.js.map +1 -0
  48. package/dist/cli/execute-yagni.d.ts +32 -0
  49. package/dist/cli/execute-yagni.d.ts.map +1 -0
  50. package/dist/cli/execute-yagni.js +127 -0
  51. package/dist/cli/execute-yagni.js.map +1 -0
  52. package/dist/cli/report-data.d.ts +23 -0
  53. package/dist/cli/report-data.d.ts.map +1 -0
  54. package/dist/cli/report-data.js +23 -0
  55. package/dist/cli/report-data.js.map +1 -0
  56. package/dist/cli/yagni-command-spec.d.ts +6 -0
  57. package/dist/cli/yagni-command-spec.d.ts.map +1 -0
  58. package/dist/cli/yagni-command-spec.js +152 -0
  59. package/dist/cli/yagni-command-spec.js.map +1 -0
  60. package/dist/cli/yagni-config-schema.d.ts +28 -0
  61. package/dist/cli/yagni-config-schema.d.ts.map +1 -0
  62. package/dist/cli/yagni-config-schema.js +39 -0
  63. package/dist/cli/yagni-config-schema.js.map +1 -0
  64. package/dist/cli/yagni-config.d.ts +27 -0
  65. package/dist/cli/yagni-config.d.ts.map +1 -0
  66. package/dist/cli/yagni-config.js +72 -0
  67. package/dist/cli/yagni-config.js.map +1 -0
  68. package/dist/cli/yagni-presentation.d.ts +15 -0
  69. package/dist/cli/yagni-presentation.d.ts.map +1 -0
  70. package/dist/cli/yagni-presentation.js +92 -0
  71. package/dist/cli/yagni-presentation.js.map +1 -0
  72. package/dist/cli/yagni-runner.d.ts +20 -0
  73. package/dist/cli/yagni-runner.d.ts.map +1 -0
  74. package/dist/cli/yagni-runner.js +95 -0
  75. package/dist/cli/yagni-runner.js.map +1 -0
  76. package/dist/detectors/create-yagni-signal.d.ts +8 -0
  77. package/dist/detectors/create-yagni-signal.d.ts.map +1 -0
  78. package/dist/detectors/create-yagni-signal.js +10 -0
  79. package/dist/detectors/create-yagni-signal.js.map +1 -0
  80. package/dist/detectors/duplicate-body-candidate.d.ts +7 -0
  81. package/dist/detectors/duplicate-body-candidate.d.ts.map +1 -0
  82. package/dist/detectors/duplicate-body-candidate.js +137 -0
  83. package/dist/detectors/duplicate-body-candidate.js.map +1 -0
  84. package/dist/detectors/registry.d.ts +4 -0
  85. package/dist/detectors/registry.d.ts.map +1 -0
  86. package/dist/detectors/registry.js +8 -0
  87. package/dist/detectors/registry.js.map +1 -0
  88. package/dist/detectors/types.d.ts +28 -0
  89. package/dist/detectors/types.d.ts.map +1 -0
  90. package/dist/detectors/types.js +2 -0
  91. package/dist/detectors/types.js.map +1 -0
  92. package/dist/detectors/unused-config-surface.d.ts +10 -0
  93. package/dist/detectors/unused-config-surface.d.ts.map +1 -0
  94. package/dist/detectors/unused-config-surface.js +199 -0
  95. package/dist/detectors/unused-config-surface.js.map +1 -0
  96. package/dist/evidence/graph-evidence.d.ts +15 -0
  97. package/dist/evidence/graph-evidence.d.ts.map +1 -0
  98. package/dist/evidence/graph-evidence.js +89 -0
  99. package/dist/evidence/graph-evidence.js.map +1 -0
  100. package/dist/evidence/load-graph-adapters.d.ts +13 -0
  101. package/dist/evidence/load-graph-adapters.d.ts.map +1 -0
  102. package/dist/evidence/load-graph-adapters.js +67 -0
  103. package/dist/evidence/load-graph-adapters.js.map +1 -0
  104. package/dist/identity.d.ts +7 -0
  105. package/dist/identity.d.ts.map +1 -0
  106. package/dist/identity.js +7 -0
  107. package/dist/identity.js.map +1 -0
  108. package/dist/index.d.ts +8 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +6 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/lib/apply-advisory-exit.d.ts +8 -0
  113. package/dist/lib/apply-advisory-exit.d.ts.map +1 -0
  114. package/dist/lib/apply-advisory-exit.js +16 -0
  115. package/dist/lib/apply-advisory-exit.js.map +1 -0
  116. package/dist/lib/isolate-exit-code.d.ts +8 -0
  117. package/dist/lib/isolate-exit-code.d.ts.map +1 -0
  118. package/dist/lib/isolate-exit-code.js +27 -0
  119. package/dist/lib/isolate-exit-code.js.map +1 -0
  120. package/dist/lib/resolve-positional-paths.d.ts +6 -0
  121. package/dist/lib/resolve-positional-paths.d.ts.map +1 -0
  122. package/dist/lib/resolve-positional-paths.js +33 -0
  123. package/dist/lib/resolve-positional-paths.js.map +1 -0
  124. package/dist/lib/walk-typescript-files.d.ts +2 -0
  125. package/dist/lib/walk-typescript-files.d.ts.map +1 -0
  126. package/dist/lib/walk-typescript-files.js +50 -0
  127. package/dist/lib/walk-typescript-files.js.map +1 -0
  128. package/dist/persistence/session-payload.d.ts +54 -0
  129. package/dist/persistence/session-payload.d.ts.map +1 -0
  130. package/dist/persistence/session-payload.js +48 -0
  131. package/dist/persistence/session-payload.js.map +1 -0
  132. package/dist/scoring/__tests__/confidence.test.d.ts +2 -0
  133. package/dist/scoring/__tests__/confidence.test.d.ts.map +1 -0
  134. package/dist/scoring/__tests__/confidence.test.js +123 -0
  135. package/dist/scoring/__tests__/confidence.test.js.map +1 -0
  136. package/dist/scoring/confidence.d.ts +12 -0
  137. package/dist/scoring/confidence.d.ts.map +1 -0
  138. package/dist/scoring/confidence.js +106 -0
  139. package/dist/scoring/confidence.js.map +1 -0
  140. package/dist/tool.d.ts +11 -0
  141. package/dist/tool.d.ts.map +1 -0
  142. package/dist/tool.js +38 -0
  143. package/dist/tool.js.map +1 -0
  144. package/dist/types/yagni-config.d.ts +18 -0
  145. package/dist/types/yagni-config.d.ts.map +1 -0
  146. package/dist/types/yagni-config.js +11 -0
  147. package/dist/types/yagni-config.js.map +1 -0
  148. package/dist/types/yagni-metadata.d.ts +51 -0
  149. package/dist/types/yagni-metadata.d.ts.map +1 -0
  150. package/dist/types/yagni-metadata.js +5 -0
  151. package/dist/types/yagni-metadata.js.map +1 -0
  152. package/package.json +77 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-body-candidate.d.ts","sourceRoot":"","sources":["../../src/detectors/duplicate-body-candidate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,aAAa,EAA6C,MAAM,YAAY,CAAC;AAuI3F,eAAO,MAAM,8BAA8B,EAAE,aAM5C,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * duplicate-body-candidate — surfaces cross-occurrence duplicate function bodies
3
+ * from a graph catalog bodyHash grouping as consolidation candidates.
4
+ */
5
+ import { relative } from 'node:path';
6
+ import { severityForConfidence } from '../scoring/confidence.js';
7
+ import { createYagniSignal } from './create-yagni-signal.js';
8
+ const DETECTOR_ID = 'duplicate-body-candidate';
9
+ const SLUG = 'yagni:duplicate-body-candidate';
10
+ const DEFAULT_MIN_OCCURRENCES = 2;
11
+ const DEFAULT_MIN_BODY_LINES = 5;
12
+ function groupByBodyHash(catalog) {
13
+ const buckets = new Map();
14
+ for (const occurrences of Object.values(catalog.functions)) {
15
+ for (const occ of occurrences) {
16
+ const bucket = buckets.get(occ.bodyHash);
17
+ if (bucket)
18
+ bucket.push(occ);
19
+ else
20
+ buckets.set(occ.bodyHash, [occ]);
21
+ }
22
+ }
23
+ return buckets;
24
+ }
25
+ function primaryOccurrence(occs) {
26
+ return [...occs].sort((a, b) => a.qualifiedName.localeCompare(b.qualifiedName))[0];
27
+ }
28
+ function relPath(cwd, filePath) {
29
+ return relative(cwd, filePath).split('\\').join('/');
30
+ }
31
+ function displayFunctionName(occ) {
32
+ const simple = occ.simpleName.trim();
33
+ if (simple.startsWith('<arrow:'))
34
+ return 'arrow function';
35
+ if (simple !== '' && !simple.includes('/') && simple.length <= 60)
36
+ return simple;
37
+ if (occ.qualifiedName.includes('<arrow:'))
38
+ return 'arrow function';
39
+ if (occ.qualifiedName.includes('/'))
40
+ return 'function';
41
+ const qualifiedTail = occ.qualifiedName.split('.').at(-1)?.trim() ?? '';
42
+ if (qualifiedTail !== '' && !qualifiedTail.includes('/') && qualifiedTail.length <= 60) {
43
+ return qualifiedTail;
44
+ }
45
+ return 'function';
46
+ }
47
+ function displayOccurrence(cwd, occ) {
48
+ return `${relPath(cwd, occ.filePath)}:${String(occ.line)} (${displayFunctionName(occ)})`;
49
+ }
50
+ function runDuplicateBodyCandidate(ctx) {
51
+ const started = Date.now();
52
+ const settings = ctx.config.detectorSettings?.[DETECTOR_ID] ?? {};
53
+ const minOccurrences = typeof settings.minOccurrences === 'number' ? settings.minOccurrences : DEFAULT_MIN_OCCURRENCES;
54
+ const minBodyLines = typeof settings.minBodyLines === 'number' ? settings.minBodyLines : DEFAULT_MIN_BODY_LINES;
55
+ if (ctx.graphCatalog === null) {
56
+ return Promise.resolve({ signals: [], durationMs: Date.now() - started });
57
+ }
58
+ const signals = [];
59
+ for (const [bodyHash, occs] of groupByBodyHash(ctx.graphCatalog)) {
60
+ if (occs.length < minOccurrences)
61
+ continue;
62
+ const anchor = primaryOccurrence(occs);
63
+ const bodyLines = anchor.endLine - anchor.line + 1;
64
+ if (bodyLines < minBodyLines)
65
+ continue;
66
+ const packages = [
67
+ ...new Set(occs.map((o) => o.package ?? o.filePath.split('/')[0] ?? 'unknown')),
68
+ ].sort();
69
+ const confidence = 'medium';
70
+ const netEstimate = bodyLines * (occs.length - 1);
71
+ const anchorRel = relPath(ctx.cwd, anchor.filePath);
72
+ const peer = occs.find((o) => o.qualifiedName !== anchor.qualifiedName) ?? anchor;
73
+ const peerDisplay = displayOccurrence(ctx.cwd, peer);
74
+ signals.push(createYagniSignal({
75
+ source: SLUG,
76
+ ruleId: SLUG,
77
+ severity: severityForConfidence(confidence),
78
+ category: 'architecture',
79
+ message: `Duplicate body candidate (${String(occs.length)} occurrences) — consolidate shared logic (${confidence} confidence)`,
80
+ suggestion: 'Extract the shared body into one module and import it from each copy site.',
81
+ code: {
82
+ file: anchor.filePath,
83
+ line: anchor.line,
84
+ column: anchor.column,
85
+ },
86
+ yagni: {
87
+ detector: DETECTOR_ID,
88
+ reductionCategory: 'dedupe',
89
+ confidence,
90
+ locDelta: {
91
+ remove: netEstimate,
92
+ add: Math.max(1, Math.floor(bodyLines / 2)),
93
+ netEstimate,
94
+ estimateKind: 'heuristic',
95
+ },
96
+ preservationArgument: 'Occurrences share an identical bodyHash from the graph catalog; behavior should match after hoisting.',
97
+ suggestedAction: `Consolidate with ${peerDisplay}.`,
98
+ validationRequired: [
99
+ 'Confirm neither occurrence is published public API.',
100
+ 'Run tests covering each duplicate site after extraction.',
101
+ ],
102
+ riskTags: ['cross-package', 'behavior-preservation'],
103
+ evidence: [
104
+ {
105
+ id: `body-hash:${bodyHash}`,
106
+ kind: 'body-hash-group',
107
+ summary: `${String(occs.length)} functions share bodyHash ${bodyHash.slice(0, 8)}.`,
108
+ data: {
109
+ bodyHash,
110
+ occurrenceCount: occs.length,
111
+ packages,
112
+ anchor: {
113
+ qualifiedName: anchor.qualifiedName,
114
+ filePath: anchorRel,
115
+ line: anchor.line,
116
+ },
117
+ peer: {
118
+ qualifiedName: peer.qualifiedName,
119
+ filePath: relPath(ctx.cwd, peer.filePath),
120
+ line: peer.line,
121
+ },
122
+ },
123
+ },
124
+ ],
125
+ },
126
+ }));
127
+ }
128
+ return Promise.resolve({ signals, durationMs: Date.now() - started });
129
+ }
130
+ export const duplicateBodyCandidateDetector = {
131
+ id: DETECTOR_ID,
132
+ slug: SLUG,
133
+ description: 'Duplicate function-body candidates from graph bodyHash groups',
134
+ requiresGraph: true,
135
+ run: runDuplicateBodyCandidate,
136
+ };
137
+ //# sourceMappingURL=duplicate-body-candidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-body-candidate.js","sourceRoot":"","sources":["../../src/detectors/duplicate-body-candidate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAK7D,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAC/C,MAAM,IAAI,GAAG,gCAAgC,CAAC;AAC9C,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAClC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAEjC,SAAS,eAAe,CACtB,OAA0D;IAE1D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAqC,CAAC;IAC7D,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3D,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACzC,IAAI,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;gBACxB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAwC;IACjE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,QAAgB;IAC5C,OAAO,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,mBAAmB,CAAC,GAA4B;IACvD,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC1D,IAAI,MAAM,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,MAAM,CAAC;IAEjF,IAAI,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,gBAAgB,CAAC;IACnE,IAAI,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,UAAU,CAAC;IACvD,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxE,IAAI,aAAa,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACvF,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAW,EAAE,GAA4B;IAClE,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC;AAC3F,CAAC;AAED,SAAS,yBAAyB,CAAC,GAAyB;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IAClE,MAAM,cAAc,GAClB,OAAO,QAAQ,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,uBAAuB,CAAC;IAClG,MAAM,YAAY,GAChB,OAAO,QAAQ,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAE7F,IAAI,GAAG,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC;IACnB,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACjE,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc;YAAE,SAAS;QAC3C,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QACnD,IAAI,SAAS,GAAG,YAAY;YAAE,SAAS;QACvC,MAAM,QAAQ,GAAG;YACf,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;SAChF,CAAC,IAAI,EAAE,CAAC;QACT,MAAM,UAAU,GAAG,QAAiB,CAAC;QACrC,MAAM,WAAW,GAAG,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,MAAM,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC;QAClF,MAAM,WAAW,GAAG,iBAAiB,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,CACV,iBAAiB,CAAC;YAChB,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC;YAC3C,QAAQ,EAAE,cAAc;YACxB,OAAO,EAAE,6BAA6B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,6CAA6C,UAAU,cAAc;YAC9H,UAAU,EAAE,4EAA4E;YACxF,IAAI,EAAE;gBACJ,IAAI,EAAE,MAAM,CAAC,QAAQ;gBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,MAAM;aACtB;YACD,KAAK,EAAE;gBACL,QAAQ,EAAE,WAAW;gBACrB,iBAAiB,EAAE,QAAQ;gBAC3B,UAAU;gBACV,QAAQ,EAAE;oBACR,MAAM,EAAE,WAAW;oBACnB,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;oBAC3C,WAAW;oBACX,YAAY,EAAE,WAAW;iBAC1B;gBACD,oBAAoB,EAClB,uGAAuG;gBACzG,eAAe,EAAE,oBAAoB,WAAW,GAAG;gBACnD,kBAAkB,EAAE;oBAClB,qDAAqD;oBACrD,0DAA0D;iBAC3D;gBACD,QAAQ,EAAE,CAAC,eAAe,EAAE,uBAAuB,CAAC;gBACpD,QAAQ,EAAE;oBACR;wBACE,EAAE,EAAE,aAAa,QAAQ,EAAE;wBAC3B,IAAI,EAAE,iBAAiB;wBACvB,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,6BAA6B,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;wBACnF,IAAI,EAAE;4BACJ,QAAQ;4BACR,eAAe,EAAE,IAAI,CAAC,MAAM;4BAC5B,QAAQ;4BACR,MAAM,EAAE;gCACN,aAAa,EAAE,MAAM,CAAC,aAAa;gCACnC,QAAQ,EAAE,SAAS;gCACnB,IAAI,EAAE,MAAM,CAAC,IAAI;6BAClB;4BACD,IAAI,EAAE;gCACJ,aAAa,EAAE,IAAI,CAAC,aAAa;gCACjC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC;gCACzC,IAAI,EAAE,IAAI,CAAC,IAAI;6BAChB;yBACF;qBACF;iBACF;aACF;SACF,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAkB;IAC3D,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,IAAI;IACV,WAAW,EAAE,+DAA+D;IAC5E,aAAa,EAAE,IAAI;IACnB,GAAG,EAAE,yBAAyB;CAC/B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { YagniDetector } from './types.js';
2
+ /** Built-in detector registry in stable registration order. */
3
+ export declare const YAGNI_DETECTORS: readonly YagniDetector[];
4
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/detectors/registry.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,+DAA+D;AAC/D,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAGnD,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { duplicateBodyCandidateDetector } from './duplicate-body-candidate.js';
2
+ import { unusedConfigSurfaceDetector } from './unused-config-surface.js';
3
+ /** Built-in detector registry in stable registration order. */
4
+ export const YAGNI_DETECTORS = [
5
+ unusedConfigSurfaceDetector,
6
+ duplicateBodyCandidateDetector,
7
+ ];
8
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/detectors/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAIzE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAA6B;IACvD,2BAA2B;IAC3B,8BAA8B;CAC/B,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { YagniConfig } from '../types/yagni-config.js';
2
+ import type { GraphCatalog } from '@opensip-cli/contracts';
3
+ import type { Signal } from '@opensip-cli/core';
4
+ export interface YagniDetectorContext {
5
+ readonly cwd: string;
6
+ readonly config: YagniConfig;
7
+ readonly graphCatalog: GraphCatalog | null;
8
+ readonly includeTests: boolean;
9
+ readonly pathRoots?: readonly string[];
10
+ }
11
+ export interface YagniDetectorResult {
12
+ readonly signals: readonly Signal[];
13
+ readonly durationMs: number;
14
+ }
15
+ export interface YagniDetector {
16
+ readonly id: string;
17
+ readonly slug: string;
18
+ readonly description: string;
19
+ readonly requiresGraph: boolean;
20
+ run(ctx: YagniDetectorContext): Promise<YagniDetectorResult>;
21
+ }
22
+ export interface SkippedDetector {
23
+ readonly id: string;
24
+ readonly slug: string;
25
+ readonly reason: 'disabled' | 'graph-required' | 'graph-unavailable';
26
+ readonly detail?: string;
27
+ }
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/detectors/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,GAAG,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/detectors/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * unused-config-surface — flags required config properties on the public API
3
+ * surface that are never read anywhere in the project.
4
+ *
5
+ * Subsumes the fitness `unused-config-options` check by scoping definitions to
6
+ * files reachable from `package.json#exports` via re-export chains.
7
+ */
8
+ import type { YagniDetector } from './types.js';
9
+ export declare const unusedConfigSurfaceDetector: YagniDetector;
10
+ //# sourceMappingURL=unused-config-surface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unused-config-surface.d.ts","sourceRoot":"","sources":["../../src/detectors/unused-config-surface.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAcH,OAAO,KAAK,EAAE,aAAa,EAA6C,MAAM,YAAY,CAAC;AA2M3F,eAAO,MAAM,2BAA2B,EAAE,aAMzC,CAAC"}
@@ -0,0 +1,199 @@
1
+ // @fitness-ignore-file batch-operation-limits -- walks a bounded, already-discovered source file list synchronously; no DB, network, async fanout, or unbounded external batch.
2
+ /**
3
+ * unused-config-surface — flags required config properties on the public API
4
+ * surface that are never read anywhere in the project.
5
+ *
6
+ * Subsumes the fitness `unused-config-options` check by scoping definitions to
7
+ * files reachable from `package.json#exports` via re-export chains.
8
+ */
9
+ import { readFileSync, statSync } from 'node:fs';
10
+ import { relative } from 'node:path';
11
+ import { isInPublicApiSurface, withSpan } from '@opensip-cli/core';
12
+ import { getSharedSourceFile } from '@opensip-cli/lang-typescript';
13
+ import * as ts from 'typescript';
14
+ import { walkTypeScriptFiles } from '../lib/walk-typescript-files.js';
15
+ import { severityForConfidence } from '../scoring/confidence.js';
16
+ import { createYagniSignal } from './create-yagni-signal.js';
17
+ const DETECTOR_ID = 'unused-config-surface';
18
+ const SLUG = 'yagni:unused-config-surface';
19
+ const MAX_SOURCE_FILE_BYTES = 1_000_000;
20
+ const COMMON_PROPERTY_NAMES = new Set([
21
+ 'enabled',
22
+ 'disabled',
23
+ 'timeout',
24
+ 'retries',
25
+ 'debug',
26
+ 'verbose',
27
+ 'name',
28
+ 'type',
29
+ 'id',
30
+ 'key',
31
+ 'value',
32
+ 'data',
33
+ 'options',
34
+ 'config',
35
+ 'settings',
36
+ 'port',
37
+ 'host',
38
+ 'url',
39
+ 'path',
40
+ 'level',
41
+ 'mode',
42
+ ]);
43
+ function isPublicConfigSurfaceInterface(interfaceName) {
44
+ return /\b(?:\w*Config|\w*Options)\b/.test(interfaceName);
45
+ }
46
+ function mayContainConfigSurface(filePath) {
47
+ return filePath.toLowerCase().includes('config');
48
+ }
49
+ function readBoundedSourceFile(filePath) {
50
+ try {
51
+ const stats = statSync(filePath);
52
+ if (!stats.isFile() || stats.size > MAX_SOURCE_FILE_BYTES)
53
+ return undefined;
54
+ return readFileSync(filePath, 'utf8');
55
+ }
56
+ catch {
57
+ // @swallow-ok unreadable files are skipped; callers treat undefined as "no source available".
58
+ return undefined;
59
+ }
60
+ }
61
+ function extractPropertyFromMember(member, sourceFile, interfaceName, filePath) {
62
+ if (!ts.isPropertySignature(member) || !ts.isIdentifier(member.name))
63
+ return null;
64
+ const propName = member.name.text;
65
+ if (COMMON_PROPERTY_NAMES.has(propName))
66
+ return null;
67
+ const { line } = sourceFile.getLineAndCharacterOfPosition(member.getStart());
68
+ return {
69
+ name: propName,
70
+ interfaceName,
71
+ filePath,
72
+ line: line + 1,
73
+ isOptional: member.questionToken !== undefined,
74
+ };
75
+ }
76
+ function extractInterfaceProperties(node, sourceFile, filePath) {
77
+ const properties = [];
78
+ for (const member of node.members) {
79
+ const prop = extractPropertyFromMember(member, sourceFile, node.name.text, filePath);
80
+ if (prop)
81
+ properties.push(prop);
82
+ }
83
+ return properties;
84
+ }
85
+ function collectConfigProperties(filePaths) {
86
+ const properties = [];
87
+ for (const filePath of filePaths) {
88
+ if (!mayContainConfigSurface(filePath) || !isInPublicApiSurface(filePath))
89
+ continue;
90
+ const content = readBoundedSourceFile(filePath);
91
+ if (content === undefined)
92
+ continue;
93
+ const sourceFile = getSharedSourceFile(filePath, content);
94
+ if (!sourceFile)
95
+ continue;
96
+ const visit = (node) => {
97
+ ts.forEachChild(node, visit);
98
+ if (!ts.isInterfaceDeclaration(node) || !isPublicConfigSurfaceInterface(node.name.text))
99
+ return;
100
+ properties.push(...extractInterfaceProperties(node, sourceFile, filePath));
101
+ };
102
+ visit(sourceFile);
103
+ }
104
+ return properties;
105
+ }
106
+ function countPropertyAccesses(filePaths) {
107
+ const accessCounts = new Map();
108
+ for (const filePath of filePaths) {
109
+ const content = readBoundedSourceFile(filePath);
110
+ if (content === undefined)
111
+ continue;
112
+ const sourceFile = getSharedSourceFile(filePath, content);
113
+ if (!sourceFile)
114
+ continue;
115
+ // eslint-disable-next-line unicorn/consistent-function-scoping -- closes over per-file sourceFile; hoisting would thread extra params through every callsite
116
+ const visit = (node) => {
117
+ if (ts.isPropertyAccessExpression(node)) {
118
+ const propertyName = node.name.text;
119
+ accessCounts.set(propertyName, (accessCounts.get(propertyName) ?? 0) + 1);
120
+ }
121
+ if (ts.isBindingElement(node) && ts.isIdentifier(node.name)) {
122
+ const propertyName = node.name.text;
123
+ accessCounts.set(propertyName, (accessCounts.get(propertyName) ?? 0) + 1);
124
+ }
125
+ ts.forEachChild(node, visit);
126
+ };
127
+ visit(sourceFile);
128
+ }
129
+ return accessCounts;
130
+ }
131
+ function runUnusedConfigSurface(ctx) {
132
+ const result = withSpan('opensip-cli-yagni', 'yagni.unused_config_surface', () => {
133
+ const started = Date.now();
134
+ const filePaths = walkTypeScriptFiles(ctx.cwd, ctx.includeTests, ctx.pathRoots);
135
+ const configProperties = collectConfigProperties(filePaths);
136
+ const accessCounts = countPropertyAccesses(filePaths);
137
+ const signals = [];
138
+ for (const prop of configProperties) {
139
+ if (prop.isOptional)
140
+ continue;
141
+ if ((accessCounts.get(prop.name) ?? 0) > 0)
142
+ continue;
143
+ const relPath = relative(ctx.cwd, prop.filePath).split('\\').join('/');
144
+ const confidence = 'high';
145
+ const evidenceId = `unused-config:${relPath}:${String(prop.line)}:${prop.name}`;
146
+ signals.push(createYagniSignal({
147
+ source: SLUG,
148
+ ruleId: SLUG,
149
+ severity: severityForConfidence(confidence),
150
+ category: 'quality',
151
+ message: `Unused public config key '${prop.name}' in ${prop.interfaceName} (${confidence} confidence)`,
152
+ suggestion: `Remove '${prop.name}' from ${prop.interfaceName} or wire it into runtime behavior`,
153
+ code: { file: prop.filePath, line: prop.line, column: 0 },
154
+ yagni: {
155
+ detector: DETECTOR_ID,
156
+ reductionCategory: 'config',
157
+ confidence,
158
+ locDelta: {
159
+ remove: 1,
160
+ add: 0,
161
+ netEstimate: 1,
162
+ estimateKind: 'exact',
163
+ },
164
+ preservationArgument: 'The property is declared on a public config interface but has zero read sites in the project.',
165
+ suggestedAction: `Remove unused key \`${prop.name}\` from ${prop.interfaceName}.`,
166
+ validationRequired: [
167
+ 'Confirm no dynamic property access reads this key.',
168
+ 'Run the package test suite after removal.',
169
+ ],
170
+ riskTags: ['public-api-surface'],
171
+ evidence: [
172
+ {
173
+ id: evidenceId,
174
+ kind: 'unused-config-property',
175
+ summary: `Required property '${prop.name}' on ${prop.interfaceName} has no read sites.`,
176
+ data: {
177
+ property: prop.name,
178
+ interfaceName: prop.interfaceName,
179
+ filePath: relPath,
180
+ line: prop.line,
181
+ publicApiSurface: true,
182
+ },
183
+ },
184
+ ],
185
+ },
186
+ }));
187
+ }
188
+ return { signals, durationMs: Date.now() - started };
189
+ }, { 'yagni.detector': DETECTOR_ID });
190
+ return Promise.resolve(result);
191
+ }
192
+ export const unusedConfigSurfaceDetector = {
193
+ id: DETECTOR_ID,
194
+ slug: SLUG,
195
+ description: 'Unused required config properties on the public API surface',
196
+ requiresGraph: false,
197
+ run: runUnusedConfigSurface,
198
+ };
199
+ //# sourceMappingURL=unused-config-surface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unused-config-surface.js","sourceRoot":"","sources":["../../src/detectors/unused-config-surface.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Graph evidence seam — the ONLY production module that may import
3
+ * `@opensip-cli/graph/internal` (dependency-cruiser carve-out).
4
+ */
5
+ import type { YagniGraphMode } from '../types/yagni-config.js';
6
+ import type { GraphCatalog } from '@opensip-cli/contracts';
7
+ import type { ToolCliContext } from '@opensip-cli/core';
8
+ export interface GraphEvidenceResult {
9
+ readonly catalog: GraphCatalog | null;
10
+ readonly mode: YagniGraphMode;
11
+ readonly built: boolean;
12
+ readonly detail?: string;
13
+ }
14
+ export declare function resolveGraphEvidence(cwd: string, mode: YagniGraphMode, cli: ToolCliContext): Promise<GraphEvidenceResult>;
15
+ //# sourceMappingURL=graph-evidence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-evidence.d.ts","sourceRoot":"","sources":["../../src/evidence/graph-evidence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA8D9B"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Graph evidence seam — the ONLY production module that may import
3
+ * `@opensip-cli/graph/internal` (dependency-cruiser carve-out).
4
+ */
5
+ import { CatalogRepo, runGraph } from '@opensip-cli/graph/internal';
6
+ import { withPreservedExitCode } from '../lib/isolate-exit-code.js';
7
+ import { ensureGraphAdaptersLoaded } from './load-graph-adapters.js';
8
+ export async function resolveGraphEvidence(cwd, mode, cli) {
9
+ if (mode === 'off') {
10
+ return { catalog: null, mode, built: false, detail: 'graph disabled' };
11
+ }
12
+ const datastore = cli.scope.datastore();
13
+ if (datastore === undefined) {
14
+ return {
15
+ catalog: null,
16
+ mode,
17
+ built: false,
18
+ detail: 'datastore unavailable',
19
+ };
20
+ }
21
+ if (mode === 'reuse') {
22
+ const repo = new CatalogRepo(datastore);
23
+ const catalog = repo.loadCatalogContract();
24
+ return {
25
+ catalog,
26
+ mode,
27
+ built: false,
28
+ detail: catalog === null ? 'no cached catalog' : 'reused cached catalog',
29
+ };
30
+ }
31
+ if (mode === 'build') {
32
+ await ensureGraphAdaptersLoaded(cwd);
33
+ const built = await buildGraphCatalog(cwd, cli, { force: true });
34
+ return {
35
+ catalog: built,
36
+ mode,
37
+ built: built !== null,
38
+ detail: built === null ? 'graph build produced no catalog' : 'built fresh catalog',
39
+ };
40
+ }
41
+ // auto: best-effort enrichment — load graph adapters (yagni owns the command,
42
+ // so the host did not), then delegate to graph's cache-invalidation path.
43
+ // When adapters still cannot be resolved, degrade to a cached catalog only.
44
+ await ensureGraphAdaptersLoaded(cwd);
45
+ const graphScope = cli.scope;
46
+ if ((graphScope.graph?.adapters?.size ?? 0) === 0) {
47
+ const catalog = new CatalogRepo(datastore).loadCatalogContract();
48
+ return {
49
+ catalog,
50
+ mode,
51
+ built: false,
52
+ detail: catalog === null
53
+ ? 'no graph adapter; no cached catalog'
54
+ : 'reused cached catalog (no adapter to rebuild)',
55
+ };
56
+ }
57
+ const catalog = await buildGraphCatalog(cwd, cli, { force: false });
58
+ return {
59
+ catalog,
60
+ mode,
61
+ built: false,
62
+ detail: catalog === null ? 'auto build produced no catalog' : 'auto resolved graph catalog',
63
+ };
64
+ }
65
+ async function buildGraphCatalog(cwd, cli, opts) {
66
+ const graphScope = cli.scope;
67
+ if ((graphScope.graph?.adapters?.size ?? 0) === 0)
68
+ return null;
69
+ const datastore = cli.scope.datastore();
70
+ if (datastore === undefined)
71
+ return null;
72
+ // Evidence-only in-process build: `executeGraph` fans out sharded workers that
73
+ // re-bootstrap under yagni (no graph-adapter domain), so fragments never land.
74
+ // `runGraph` reuses the adapters yagni loaded above without emitting a graph
75
+ // CLI envelope onto stdout. Persist explicitly afterward: the orchestrator
76
+ // skips `replaceAll` when `noCache: true` (build mode), but yagni needs the
77
+ // catalog row for duplicate-body-candidate regardless.
78
+ const result = await withPreservedExitCode(cli, () => runGraph({
79
+ cwd,
80
+ noCache: opts.force,
81
+ rules: [],
82
+ datastore,
83
+ }));
84
+ if (result.catalog !== undefined && result.catalog !== null) {
85
+ new CatalogRepo(datastore).replaceAll(result.catalog);
86
+ }
87
+ return new CatalogRepo(datastore).loadCatalogContract();
88
+ }
89
+ //# sourceMappingURL=graph-evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-evidence.js","sourceRoot":"","sources":["../../src/evidence/graph-evidence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lazy-load graph language adapters for YAGNI graph evidence.
3
+ *
4
+ * The CLI host only drives capability discovery for the *owning* tool's domains
5
+ * (`loadOwningToolCapabilities`). A plain `opensip yagni` run therefore does not
6
+ * register graph adapters even though `scope.graph` exists (graph's
7
+ * `contributeScope` runs for every bundled tool). Before building or refreshing
8
+ * graph evidence, YAGNI must drive the `graph-adapter` domain itself — the same
9
+ * pattern simulation uses for `sim-pack` in `ensureScenariosLoaded`.
10
+ */
11
+ /** Register graph language adapters when this yagni run needs graph evidence. */
12
+ export declare function ensureGraphAdaptersLoaded(projectDir: string): Promise<void>;
13
+ //# sourceMappingURL=load-graph-adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-graph-adapters.d.ts","sourceRoot":"","sources":["../../src/evidence/load-graph-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA+CH,iFAAiF;AACjF,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBjF"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Lazy-load graph language adapters for YAGNI graph evidence.
3
+ *
4
+ * The CLI host only drives capability discovery for the *owning* tool's domains
5
+ * (`loadOwningToolCapabilities`). A plain `opensip yagni` run therefore does not
6
+ * register graph adapters even though `scope.graph` exists (graph's
7
+ * `contributeScope` runs for every bundled tool). Before building or refreshing
8
+ * graph evidence, YAGNI must drive the `graph-adapter` domain itself — the same
9
+ * pattern simulation uses for `sim-pack` in `ensureScenariosLoaded`.
10
+ */
11
+ import { dirname } from 'node:path';
12
+ import { resolveCapabilityPreferences } from '@opensip-cli/config';
13
+ import { currentScope, loadCapabilityDomain, } from '@opensip-cli/core';
14
+ /** Built-in graph adapters bundled with the CLI (mirrors bundled-tools.manifest.json). */
15
+ const BUNDLED_GRAPH_ADAPTERS = [
16
+ '@opensip-cli/graph-typescript',
17
+ '@opensip-cli/graph-python',
18
+ '@opensip-cli/graph-rust',
19
+ '@opensip-cli/graph-go',
20
+ '@opensip-cli/graph-java',
21
+ ];
22
+ function graphAdapterCount() {
23
+ const scope = currentScope();
24
+ return scope?.graph?.adapters?.size ?? 0;
25
+ }
26
+ /**
27
+ * Resolve the CLI package install root from the process entry script
28
+ * (`.../dist/index.js` → two levels up). Used so built-in `@opensip-cli/graph-*`
29
+ * adapters resolve from the CLI's own dependency tree when the consumer project
30
+ * does not carry them.
31
+ */
32
+ function resolveCliInstallDir() {
33
+ const entry = process.argv[1];
34
+ if (entry === undefined || entry.length === 0)
35
+ return undefined;
36
+ return dirname(dirname(entry));
37
+ }
38
+ function augmentBundledGraphAdapterPreferences(descriptor, preferences) {
39
+ if (preferences.packages !== undefined)
40
+ return preferences;
41
+ if (descriptor?.discovery.mode !== 'marker')
42
+ return preferences;
43
+ return { ...preferences, packages: [...BUNDLED_GRAPH_ADAPTERS] };
44
+ }
45
+ /** Register graph language adapters when this yagni run needs graph evidence. */
46
+ export async function ensureGraphAdaptersLoaded(projectDir) {
47
+ if (graphAdapterCount() > 0)
48
+ return;
49
+ const scope = currentScope();
50
+ const registry = scope?.capabilities;
51
+ if (!registry?.hasDomain('graph-adapter'))
52
+ return;
53
+ if (registry.isDomainLoaded('graph-adapter', projectDir))
54
+ return;
55
+ const descriptor = registry.getDomain('graph-adapter')?.discovery;
56
+ const preferences = augmentBundledGraphAdapterPreferences(descriptor, descriptor === undefined
57
+ ? {}
58
+ : resolveCapabilityPreferences(descriptor, scope?.configDocument?.plugins ?? {}));
59
+ await loadCapabilityDomain({
60
+ registry,
61
+ domainId: 'graph-adapter',
62
+ projectDir,
63
+ cliDir: resolveCliInstallDir(),
64
+ preferences,
65
+ });
66
+ }
67
+ //# sourceMappingURL=load-graph-adapters.js.map