@flaid/devmap 0.3.0 → 0.3.2

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 (47) hide show
  1. package/dist/analyzers/analysis/extractors/fallbackExtractor.d.ts.map +1 -1
  2. package/dist/analyzers/analysis/extractors/fallbackExtractor.js +11 -1
  3. package/dist/analyzers/analysis/extractors/fallbackExtractor.js.map +1 -1
  4. package/dist/analyzers/features/featureCandidates.d.ts +59 -0
  5. package/dist/analyzers/features/featureCandidates.d.ts.map +1 -0
  6. package/dist/analyzers/features/featureCandidates.js +238 -0
  7. package/dist/analyzers/features/featureCandidates.js.map +1 -0
  8. package/dist/analyzers/features/featureDetector.d.ts +1 -0
  9. package/dist/analyzers/features/featureDetector.d.ts.map +1 -1
  10. package/dist/analyzers/features/featureDetector.js +78 -16
  11. package/dist/analyzers/features/featureDetector.js.map +1 -1
  12. package/dist/analyzers/features/featureSimilarity.d.ts +2 -0
  13. package/dist/analyzers/features/featureSimilarity.d.ts.map +1 -1
  14. package/dist/analyzers/features/featureSimilarity.js +9 -6
  15. package/dist/analyzers/features/featureSimilarity.js.map +1 -1
  16. package/dist/analyzers/features/index.d.ts +3 -1
  17. package/dist/analyzers/features/index.d.ts.map +1 -1
  18. package/dist/analyzers/features/index.js +2 -1
  19. package/dist/analyzers/features/index.js.map +1 -1
  20. package/dist/analyzers/graph/aliasResolver.d.ts +15 -0
  21. package/dist/analyzers/graph/aliasResolver.d.ts.map +1 -0
  22. package/dist/analyzers/graph/aliasResolver.js +50 -0
  23. package/dist/analyzers/graph/aliasResolver.js.map +1 -0
  24. package/dist/analyzers/graph/dependencyGraph.d.ts +12 -1
  25. package/dist/analyzers/graph/dependencyGraph.d.ts.map +1 -1
  26. package/dist/analyzers/graph/dependencyGraph.js +53 -6
  27. package/dist/analyzers/graph/dependencyGraph.js.map +1 -1
  28. package/dist/analyzers/graph/entryPoints.d.ts.map +1 -1
  29. package/dist/analyzers/graph/entryPoints.js +16 -1
  30. package/dist/analyzers/graph/entryPoints.js.map +1 -1
  31. package/dist/analyzers/graph/index.d.ts +3 -1
  32. package/dist/analyzers/graph/index.d.ts.map +1 -1
  33. package/dist/analyzers/graph/index.js +1 -0
  34. package/dist/analyzers/graph/index.js.map +1 -1
  35. package/dist/analyzers/pipeline/projectMap.d.ts +3 -0
  36. package/dist/analyzers/pipeline/projectMap.d.ts.map +1 -1
  37. package/dist/analyzers/pipeline/projectMap.js +12 -3
  38. package/dist/analyzers/pipeline/projectMap.js.map +1 -1
  39. package/dist/analyzers/registry/background-jobs.js +1 -1
  40. package/dist/analyzers/registry/background-jobs.js.map +1 -1
  41. package/dist/analyzers/registry/caching.js +1 -1
  42. package/dist/analyzers/registry/caching.js.map +1 -1
  43. package/dist/analyzers/registry/file-upload.js +1 -1
  44. package/dist/analyzers/registry/file-upload.js.map +1 -1
  45. package/dist/analyzers/registry/payments.js +1 -1
  46. package/dist/analyzers/registry/payments.js.map +1 -1
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"fallbackExtractor.d.ts","sourceRoot":"","sources":["../../../../src/analyzers/analysis/extractors/fallbackExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAsCtE;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAuB,YAAW,uBAAuB;IACpE,QAAQ,CAAC,IAAI,oBAAoB;IAEjC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE;CAyB3C;AAMD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4BhD"}
1
+ {"version":3,"file":"fallbackExtractor.d.ts","sourceRoot":"","sources":["../../../../src/analyzers/analysis/extractors/fallbackExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAsCtE;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAuB,YAAW,uBAAuB;IACpE,QAAQ,CAAC,IAAI,oBAAoB;IAEjC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,UAAU,EAAE;CAoC3C;AAMD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4BhD"}
@@ -48,10 +48,19 @@ export class RouteFallbackExtractor {
48
48
  const entityNames = new Set();
49
49
  for (const route of routes.filter((r) => r.kind === "api")) {
50
50
  const segments = route.path.split("/").filter(Boolean);
51
+ let prevWasDynamic = false;
51
52
  for (const segment of segments) {
52
53
  // Skip dynamic segments: [id], [slug], [...catchall]
53
- if (segment.startsWith("["))
54
+ if (segment.startsWith("[")) {
55
+ prevWasDynamic = true;
54
56
  continue;
57
+ }
58
+ // Segments immediately after a dynamic parameter are sub-resources
59
+ // (e.g. /api/workspaces/[id]/members → "members" is a sub-resource of "workspaces")
60
+ if (prevWasDynamic) {
61
+ prevWasDynamic = false;
62
+ continue;
63
+ }
55
64
  // Skip short segments: "v1", "id", dll
56
65
  if (segment.length <= 2)
57
66
  continue;
@@ -59,6 +68,7 @@ export class RouteFallbackExtractor {
59
68
  if (ROUTE_NON_ENTITY_SEGMENTS.has(segment.toLowerCase()))
60
69
  continue;
61
70
  entityNames.add(singularize(segment));
71
+ prevWasDynamic = false;
62
72
  }
63
73
  }
64
74
  return [...entityNames].map((name) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"fallbackExtractor.js","sourceRoot":"","sources":["../../../../src/analyzers/analysis/extractors/fallbackExtractor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fallbackExtractor.js","sourceRoot":"","sources":["../../../../src/analyzers/analysis/extractors/fallbackExtractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { FeatureInfo } from "./featureDetector.js";
2
+ export type FeatureCandidateSource = "registry" | "capability" | "entity" | "frontend-page" | "client-route" | "ai";
3
+ export type ObservationReliability = "high" | "medium" | "low";
4
+ export interface FeatureEvidence {
5
+ ruleId: string;
6
+ source: FeatureCandidateSource;
7
+ files: string[];
8
+ routePaths?: string[];
9
+ entityNames?: string[];
10
+ detail: string;
11
+ reliability: ObservationReliability;
12
+ }
13
+ export interface FeatureCandidate {
14
+ id: string;
15
+ label: string;
16
+ source: FeatureCandidateSource;
17
+ evidence: FeatureEvidence[];
18
+ files: string[];
19
+ routePaths: string[];
20
+ entityNames: string[];
21
+ conclusionConfidence: "high" | "medium" | "low";
22
+ /** Internal compatibility payload used while the public snapshot remains V1. */
23
+ projection?: FeatureInfo;
24
+ }
25
+ export type AnchorType = "entity" | "file" | "route-resource" | "alias";
26
+ export interface MergeDecision {
27
+ candidateIds: [string, string];
28
+ outcome: "merged" | "rejected" | "retained";
29
+ anchors: Array<{
30
+ type: AnchorType;
31
+ value: string;
32
+ }>;
33
+ similarity?: number;
34
+ reason: string;
35
+ }
36
+ export type FeatureCluster = {
37
+ id: string;
38
+ canonicalCandidateId: string;
39
+ canonicalLabel: string;
40
+ aliases: string[];
41
+ candidates: FeatureCandidate[];
42
+ memberIds: string[];
43
+ anchors: string[];
44
+ decisionRationale: string;
45
+ };
46
+ export type FeatureReconciliation = {
47
+ clusters: FeatureCluster[];
48
+ rejectedCandidateIds: string[];
49
+ mergeDecisions?: MergeDecision[];
50
+ };
51
+ /**
52
+ * Reconcile complete candidate sets using hard evidence anchors only. Names
53
+ * only influence the deterministic canonical-label tie break after candidates
54
+ * are already in the same connected component.
55
+ */
56
+ export declare function reconcileFeatureCandidates(candidates: FeatureCandidate[]): FeatureReconciliation;
57
+ /** Project only deterministic clusters to the established public snapshot shape. */
58
+ export declare function projectFeatureCandidates(clusters: FeatureCluster[]): FeatureInfo[];
59
+ //# sourceMappingURL=featureCandidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"featureCandidates.d.ts","sourceRoot":"","sources":["../../../src/analyzers/features/featureCandidates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,MAAM,sBAAsB,GAC9B,UAAU,GACV,YAAY,GACZ,QAAQ,GACR,eAAe,GACf,cAAc,GACd,IAAI,CAAC;AAET,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,sBAAsB,CAAC;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,sBAAsB,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,oBAAoB,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IAChD,gFAAgF;IAChF,UAAU,CAAC,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;IAC5C,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC,CAAC;AAuBF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,EAAE,GAAG,qBAAqB,CAgEhG;AAED,oFAAoF;AACpF,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,WAAW,EAAE,CA4BlF"}
@@ -0,0 +1,238 @@
1
+ const SOURCE_PRIORITY = {
2
+ "frontend-page": 6,
3
+ "client-route": 5,
4
+ entity: 4,
5
+ capability: 3,
6
+ registry: 2,
7
+ ai: 0,
8
+ };
9
+ const RELIABILITY_PRIORITY = {
10
+ high: 3,
11
+ medium: 2,
12
+ low: 1,
13
+ };
14
+ const CONFIDENCE_PRIORITY = {
15
+ high: 3,
16
+ medium: 2,
17
+ low: 1,
18
+ };
19
+ /**
20
+ * Reconcile complete candidate sets using hard evidence anchors only. Names
21
+ * only influence the deterministic canonical-label tie break after candidates
22
+ * are already in the same connected component.
23
+ */
24
+ export function reconcileFeatureCandidates(candidates) {
25
+ const deterministicCandidates = candidates
26
+ .filter((candidate) => candidate.source !== "ai")
27
+ .map(normalizeCandidate)
28
+ .sort((left, right) => left.id.localeCompare(right.id));
29
+ const rejectedCandidateIds = candidates
30
+ .filter((candidate) => candidate.source === "ai")
31
+ .map((candidate) => candidate.id)
32
+ .sort();
33
+ const unionFind = new UnionFind(deterministicCandidates.length);
34
+ const anchorsByPair = new Map();
35
+ const structuredAnchorsByPair = new Map();
36
+ const mergeDecisions = [];
37
+ for (const rejectedId of rejectedCandidateIds) {
38
+ mergeDecisions.push({
39
+ candidateIds: [rejectedId, ""],
40
+ outcome: "rejected",
41
+ anchors: [],
42
+ reason: "AI-sourced candidate rejected from deterministic reconciliation.",
43
+ });
44
+ }
45
+ for (let left = 0; left < deterministicCandidates.length; left += 1) {
46
+ for (let right = left + 1; right < deterministicCandidates.length; right += 1) {
47
+ const leftCandidate = deterministicCandidates[left];
48
+ const rightCandidate = deterministicCandidates[right];
49
+ const structured = findStructuredAnchors(leftCandidate, rightCandidate);
50
+ if (structured.length === 0) {
51
+ mergeDecisions.push({
52
+ candidateIds: [leftCandidate.id, rightCandidate.id],
53
+ outcome: "retained",
54
+ anchors: [],
55
+ reason: "No hard anchor found; candidates remain separate.",
56
+ });
57
+ continue;
58
+ }
59
+ const anchorStrings = structured.map((a) => `${a.type}:${a.value}`);
60
+ unionFind.union(left, right);
61
+ anchorsByPair.set(`${left}:${right}`, anchorStrings);
62
+ structuredAnchorsByPair.set(`${left}:${right}`, structured);
63
+ mergeDecisions.push({
64
+ candidateIds: [leftCandidate.id, rightCandidate.id],
65
+ outcome: "merged",
66
+ anchors: structured,
67
+ reason: `Merged by hard anchors: ${anchorStrings.join(", ")}.`,
68
+ });
69
+ }
70
+ }
71
+ const componentIndexes = new Map();
72
+ deterministicCandidates.forEach((_, index) => {
73
+ const root = unionFind.find(index);
74
+ const indexes = componentIndexes.get(root) ?? [];
75
+ indexes.push(index);
76
+ componentIndexes.set(root, indexes);
77
+ });
78
+ const clusters = [...componentIndexes.values()]
79
+ .map((indexes) => createCluster(indexes, deterministicCandidates, anchorsByPair))
80
+ .sort((left, right) => left.id.localeCompare(right.id));
81
+ return { clusters, rejectedCandidateIds, mergeDecisions };
82
+ }
83
+ /** Project only deterministic clusters to the established public snapshot shape. */
84
+ export function projectFeatureCandidates(clusters) {
85
+ return clusters
86
+ .filter((cluster) => cluster.candidates.some((candidate) => candidate.source !== "ai"))
87
+ .map((cluster) => {
88
+ const projection = projectCompatibilityFeature(cluster);
89
+ if (projection)
90
+ return projection;
91
+ const files = uniqueSorted(cluster.candidates.flatMap((candidate) => candidate.files));
92
+ const searchTerms = uniqueSorted(cluster.candidates.flatMap((candidate) => [
93
+ ...candidate.entityNames,
94
+ ...candidate.routePaths.map(routeResource),
95
+ ]).filter(Boolean));
96
+ const evidence = uniqueSorted(cluster.candidates.flatMap((candidate) => candidate.evidence.flatMap((item) => item.files)));
97
+ return {
98
+ name: cluster.canonicalLabel,
99
+ purpose: `Deterministic ${cluster.canonicalLabel} feature cluster.`,
100
+ files,
101
+ entryPoints: [],
102
+ businessFlow: [],
103
+ searchTerms,
104
+ confidence: clusterConfidence(cluster.candidates),
105
+ evidence,
106
+ };
107
+ })
108
+ .sort((left, right) => left.name.localeCompare(right.name));
109
+ }
110
+ function projectCompatibilityFeature(cluster) {
111
+ const canonical = cluster.candidates.find((candidate) => candidate.id === cluster.canonicalCandidateId)?.projection;
112
+ if (!canonical)
113
+ return undefined;
114
+ const projections = cluster.candidates
115
+ .map((candidate) => candidate.projection)
116
+ .filter((candidate) => Boolean(candidate));
117
+ const files = uniqueSorted(projections.flatMap((feature) => feature.files));
118
+ const evidence = uniqueSorted(projections.flatMap((feature) => feature.evidence));
119
+ const entryPoints = uniqueSorted(projections.flatMap((feature) => feature.entryPoints));
120
+ const searchTerms = uniqueSorted(projections.flatMap((feature) => feature.searchTerms)).slice(0, 8);
121
+ return {
122
+ ...canonical,
123
+ files,
124
+ evidence,
125
+ entryPoints,
126
+ ...(entryPoints[0] ? { entryPoint: entryPoints[0] } : {}),
127
+ searchTerms,
128
+ confidence: clusterConfidence(cluster.candidates),
129
+ };
130
+ }
131
+ function createCluster(indexes, candidates, anchorsByPair) {
132
+ const members = indexes.map((index) => candidates[index]).sort((left, right) => left.id.localeCompare(right.id));
133
+ const canonical = [...members].sort(compareCanonicalCandidates)[0];
134
+ const anchors = uniqueSorted(indexes.flatMap((left) => indexes.flatMap((right) => left < right ? anchorsByPair.get(`${left}:${right}`) ?? [] : [])));
135
+ return {
136
+ id: `cluster:${members.map((candidate) => candidate.id).join("|")}`,
137
+ canonicalCandidateId: canonical.id,
138
+ canonicalLabel: canonical.label,
139
+ aliases: uniqueSorted(members.map((candidate) => candidate.label).filter((label) => label !== canonical.label)),
140
+ candidates: members,
141
+ memberIds: members.map((candidate) => candidate.id),
142
+ anchors,
143
+ decisionRationale: anchors.length === 0
144
+ ? "Single candidate retained because no hard anchor connected it to another candidate."
145
+ : `Merged by hard anchors: ${anchors.join(", ")}. Canonical label selected by source priority, evidence reliability, and candidate ID.`,
146
+ };
147
+ }
148
+ function compareCanonicalCandidates(left, right) {
149
+ const source = SOURCE_PRIORITY[right.source] - SOURCE_PRIORITY[left.source];
150
+ if (source !== 0)
151
+ return source;
152
+ const reliability = highestReliability(right.evidence) - highestReliability(left.evidence);
153
+ if (reliability !== 0)
154
+ return reliability;
155
+ const evidence = right.evidence.length - left.evidence.length;
156
+ if (evidence !== 0)
157
+ return evidence;
158
+ return left.id.localeCompare(right.id);
159
+ }
160
+ function findHardAnchors(left, right) {
161
+ return findStructuredAnchors(left, right).map((a) => `${a.type}:${a.value}`);
162
+ }
163
+ function findStructuredAnchors(left, right) {
164
+ const entityAnchors = intersection(left.entityNames, right.entityNames)
165
+ .map((entity) => ({ type: "entity", value: entity }));
166
+ const fileAnchors = intersection(left.files, right.files)
167
+ .map((file) => ({ type: "file", value: file }));
168
+ const routeAnchors = intersection(left.routePaths.map(routeResource).filter(Boolean), right.routePaths.map(routeResource).filter(Boolean)).map((resource) => ({ type: "route-resource", value: resource }));
169
+ return [...entityAnchors, ...fileAnchors, ...routeAnchors];
170
+ }
171
+ function routeResource(routePath) {
172
+ const parts = routePath
173
+ .split("/")
174
+ .filter(Boolean)
175
+ .filter((part) => part !== "api" && !part.startsWith("[") && !part.startsWith(":"));
176
+ return parts[0]?.toLowerCase() ?? "";
177
+ }
178
+ function normalizeCandidate(candidate) {
179
+ return {
180
+ ...candidate,
181
+ evidence: candidate.evidence.map((item) => ({
182
+ ...item,
183
+ files: uniqueSorted(item.files),
184
+ ...(item.routePaths ? { routePaths: uniqueSorted(item.routePaths) } : {}),
185
+ ...(item.entityNames ? { entityNames: uniqueSorted(item.entityNames) } : {}),
186
+ })).sort((left, right) => left.ruleId.localeCompare(right.ruleId)),
187
+ files: uniqueSorted(candidate.files),
188
+ routePaths: uniqueSorted(candidate.routePaths),
189
+ entityNames: uniqueSorted(candidate.entityNames),
190
+ };
191
+ }
192
+ function clusterConfidence(candidates) {
193
+ const corroboratedSources = new Set(candidates.map((candidate) => candidate.source));
194
+ const hasHighConclusion = candidates.some((candidate) => candidate.conclusionConfidence === "high");
195
+ if (hasHighConclusion && corroboratedSources.size >= 2)
196
+ return "high";
197
+ return candidates
198
+ .map((candidate) => candidate.conclusionConfidence)
199
+ .sort((left, right) => CONFIDENCE_PRIORITY[right] - CONFIDENCE_PRIORITY[left])[0] ?? "low";
200
+ }
201
+ function highestReliability(evidence) {
202
+ return Math.max(0, ...evidence.map((item) => RELIABILITY_PRIORITY[item.reliability]));
203
+ }
204
+ function intersection(left, right) {
205
+ const rightSet = new Set(right.map(normalizeAnchor));
206
+ return left.filter((value) => rightSet.has(normalizeAnchor(value)));
207
+ }
208
+ function normalizeAnchor(value) {
209
+ return value.trim().toLowerCase();
210
+ }
211
+ function uniqueSorted(values) {
212
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
213
+ }
214
+ class UnionFind {
215
+ parent;
216
+ constructor(size) {
217
+ this.parent = Array.from({ length: size }, (_, index) => index);
218
+ }
219
+ find(index) {
220
+ if (this.parent[index] !== index) {
221
+ this.parent[index] = this.find(this.parent[index]);
222
+ }
223
+ return this.parent[index];
224
+ }
225
+ union(left, right) {
226
+ const leftRoot = this.find(left);
227
+ const rightRoot = this.find(right);
228
+ if (leftRoot === rightRoot)
229
+ return;
230
+ if (leftRoot < rightRoot) {
231
+ this.parent[rightRoot] = leftRoot;
232
+ }
233
+ else {
234
+ this.parent[leftRoot] = rightRoot;
235
+ }
236
+ }
237
+ }
238
+ //# sourceMappingURL=featureCandidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"featureCandidates.js","sourceRoot":"","sources":["../../../src/analyzers/features/featureCandidates.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ export type FileTier = "primary" | "supporting" | "reference" | "excluded";
17
17
  export declare function classifyFileTier(path: string): FileTier;
18
18
  export declare function detectFeatures(files: ScannedFile[], analyses: Record<string, FileAnalysis>, routes: RouteInfo[], database?: DatabaseInfo, entityGraph?: EntityGraph, capabilities?: CapabilityInfo[], fileGraph?: FileGraph): FeatureInfo[];
19
19
  export declare function detectAuthenticationSemanticRole(path: string, symbols: string[], imports: string[], content?: string): AuthSemanticRole | null;
20
+ export declare function featureFilePriority(featureName: string, path: string): number;
20
21
  export declare function orderAuthenticationFiles(files: string[]): string[];
21
22
  export declare function authenticationFilePriority(path: string): number;
22
23
  //# sourceMappingURL=featureDetector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"featureDetector.d.ts","sourceRoot":"","sources":["../../../src/analyzers/features/featureDetector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EAEZ,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAK7D,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAuOjF,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;AAE3E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAkBvD;AA6BD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAAE,EACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,MAAM,EAAE,SAAS,EAAE,EACnB,QAAQ,CAAC,EAAE,YAAY,EACvB,WAAW,CAAC,EAAE,WAAW,EACzB,YAAY,CAAC,EAAE,cAAc,EAAE,EAC/B,SAAS,CAAC,EAAE,SAAS,GACpB,WAAW,EAAE,CAkGf;AA8cD,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,SAAK,GACX,gBAAgB,GAAG,IAAI,CAwCzB;AAsBD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKlE;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY/D"}
1
+ {"version":3,"file":"featureDetector.d.ts","sourceRoot":"","sources":["../../../src/analyzers/features/featureDetector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,WAAW,EAEZ,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAU7D,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAuOjF,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;AAE3E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAkBvD;AA6BD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,EAAE,EACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,MAAM,EAAE,SAAS,EAAE,EACnB,QAAQ,CAAC,EAAE,YAAY,EACvB,WAAW,CAAC,EAAE,WAAW,EACzB,YAAY,CAAC,EAAE,cAAc,EAAE,EAC/B,SAAS,CAAC,EAAE,SAAS,GACpB,WAAW,EAAE,CAyGf;AAmhBD,wBAAgB,gCAAgC,CAC9C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,SAAK,GACX,gBAAgB,GAAG,IAAI,CAwCzB;AAKD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAe7E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAKlE;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAY/D"}
@@ -1,7 +1,7 @@
1
1
  import { classifyFileRole, isTechnicalFeatureSource, isDocumentationMeta } from "../analysis/index.js";
2
2
  import { detectFrontendPageFeatures, detectClientRouteFeatures } from "../detectors/index.js";
3
3
  import { isArchitectureSource } from "../graph/index.js";
4
- import { mergeIntoFeatureList } from "./featureMerge.js";
4
+ import { projectFeatureCandidates, reconcileFeatureCandidates, } from "./featureCandidates.js";
5
5
  import { FEATURE_SIGNALS, hasAiProviderUrl, isAiProviderImport } from "../registry/index.js";
6
6
  // ---------------------------------------------------------------------------
7
7
  // ROLE_FEATURES — only user-visible features, not architectural layers.
@@ -266,7 +266,7 @@ function scoreEntryPointRelevance(file, _context) {
266
266
  // Main entry
267
267
  // ---------------------------------------------------------------------------
268
268
  export function detectFeatures(files, analyses, routes, database, entityGraph, capabilities, fileGraph) {
269
- const features = [];
269
+ const candidates = [];
270
270
  const scopedFiles = files.filter((file) => isArchitectureSource(file.path));
271
271
  // --- ROLE_FEATURES ---
272
272
  for (const definition of ROLE_FEATURES) {
@@ -281,7 +281,7 @@ export function detectFeatures(files, analyses, routes, database, entityGraph, c
281
281
  || left.localeCompare(right))
282
282
  .slice(0, 12);
283
283
  if (evidence.length > 0) {
284
- features.push(createFeatureInfo(definition.name, evidence, definition.terms, definition.purpose, analyses));
284
+ candidates.push(toFeatureCandidate("registry", `role-${definition.role}`, createFeatureInfo(definition.name, evidence, definition.terms, definition.purpose, analyses)));
285
285
  }
286
286
  }
287
287
  // --- FEATURE_SIGNALS ---
@@ -304,33 +304,66 @@ export function detectFeatures(files, analyses, routes, database, entityGraph, c
304
304
  continue;
305
305
  }
306
306
  if (evidence.length > 0) {
307
- mergeFeature(features, createFeatureInfo(signal.name, evidence, signal.terms, signal.purpose, analyses));
307
+ candidates.push(toFeatureCandidate("registry", `signal-${signal.name}`, createFeatureInfo(signal.name, evidence, signal.terms, signal.purpose, analyses)));
308
308
  }
309
309
  }
310
310
  // Database and API Routes are architectural concerns, not domain features.
311
311
  // Database info lives in snapshot.database, routes in snapshot.routes.
312
312
  if (capabilities && capabilities.length > 0) {
313
313
  for (const feature of capabilitiesToFeatures(capabilities)) {
314
- if (feature !== null)
315
- mergeFeature(features, feature);
314
+ if (feature !== null) {
315
+ candidates.push(toFeatureCandidate("capability", "route-capability", feature, routes));
316
+ }
316
317
  }
317
318
  }
318
319
  if (entityGraph && entityGraph.entityNames.length > 0) {
319
320
  for (const feature of entityGraphToFeatures(entityGraph, scopedFiles)) {
320
- mergeFeature(features, feature);
321
+ candidates.push(toFeatureCandidate("entity", `entity-${feature.name}`, feature, routes));
321
322
  }
322
323
  }
323
324
  if (fileGraph) {
324
325
  for (const feature of detectFrontendPageFeatures(routes, fileGraph)) {
325
- mergeFeature(features, feature);
326
+ candidates.push(toFeatureCandidate("frontend-page", "file-page", feature, routes));
326
327
  }
327
328
  for (const feature of detectClientRouteFeatures(scopedFiles, fileGraph)) {
328
- mergeFeature(features, feature);
329
+ candidates.push(toFeatureCandidate("client-route", "client-route", feature, routes));
329
330
  }
330
331
  }
332
+ const features = projectFeatureCandidates(reconcileFeatureCandidates(candidates).clusters);
331
333
  return enrichAuthenticationFeature(features, scopedFiles, analyses)
332
334
  .sort((left, right) => left.name.localeCompare(right.name));
333
335
  }
336
+ function toFeatureCandidate(source, ruleId, feature, routes = []) {
337
+ const entityNames = feature.name.endsWith(" Management")
338
+ ? [feature.name.slice(0, -" Management".length)]
339
+ : [];
340
+ const routePaths = routes
341
+ .filter((route) => feature.files.includes(route.file))
342
+ .map((route) => route.path)
343
+ .sort();
344
+ return {
345
+ id: `${source}:${ruleId}:${normalizeCandidateSubject(feature.name)}`,
346
+ label: feature.name,
347
+ source,
348
+ evidence: [{
349
+ ruleId,
350
+ source,
351
+ files: [...feature.evidence].sort(),
352
+ routePaths,
353
+ entityNames,
354
+ detail: feature.purpose,
355
+ reliability: source === "entity" ? "high" : "medium",
356
+ }],
357
+ files: [...feature.files].sort(),
358
+ routePaths,
359
+ entityNames,
360
+ conclusionConfidence: feature.confidence,
361
+ projection: feature,
362
+ };
363
+ }
364
+ function normalizeCandidateSubject(value) {
365
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
366
+ }
334
367
  // ---------------------------------------------------------------------------
335
368
  // capabilitiesToFeatures
336
369
  //
@@ -456,6 +489,8 @@ function findEntityFiles(entityName, files) {
456
489
  const fileStem = segment.replace(/\.[^/.]+$/, "");
457
490
  if (fileStem === lowerName)
458
491
  return true;
492
+ if (fileStem.includes(lowerName) && fileStem.length >= lowerName.length + 1)
493
+ return true;
459
494
  const segParts = splitNameToSegments(fileStem);
460
495
  return nameSegments.some((ns) => segParts.includes(ns));
461
496
  });
@@ -612,9 +647,6 @@ function createFeatureInfo(name, evidence, terms, purpose = `Identifies ${name.t
612
647
  * Alasan tidak hapus function ini: masih dipanggil di beberapa tempat di file ini.
613
648
  * mergeIntoFeatureList menggantikan logika lama yang pakai normalizeFeatureName.
614
649
  */
615
- function mergeFeature(features, addition) {
616
- mergeIntoFeatureList(features, addition);
617
- }
618
650
  // ---------------------------------------------------------------------------
619
651
  // matchesSignal
620
652
  //
@@ -635,10 +667,40 @@ function matchesSignal(file, analysis, terms, importOnly) {
635
667
  if (terms.some((term) => matchesPathTerm(path, term)))
636
668
  return true;
637
669
  if (analysis) {
638
- return terms.some((term) => analysis.imports.some((specifier) => specifier.toLowerCase().includes(term)));
670
+ return terms.some((term) => analysis.imports.some((specifier) => matchesImportTerm(specifier, term)));
639
671
  }
640
672
  return false;
641
673
  }
674
+ /**
675
+ * matchesImportTerm — segment-aware import specifier matching.
676
+ *
677
+ * Prevents false positives like "author" matching the "auth" signal.
678
+ *
679
+ * Rules:
680
+ * - Scoped packages: exact match on package name after @org/
681
+ * e.g. "@auth/core" matches "auth", "@prisma/client" matches "prisma"
682
+ * - Unscoped packages: exact match on first segment
683
+ * e.g. "next-auth" matches "auth" (via segment boundary), "author" does NOT
684
+ * - Local imports: use the same segment-boundary regex as path matching
685
+ * e.g. "./author" does NOT match "auth", "./auth/config" DOES match "auth"
686
+ */
687
+ function matchesImportTerm(specifier, term) {
688
+ const lower = specifier.toLowerCase();
689
+ // Scoped package: @org/name — match against the name segment
690
+ const scopedMatch = lower.match(/^@[^/]+\/(.+)$/);
691
+ if (scopedMatch) {
692
+ const packageName = scopedMatch[1];
693
+ // Exact segment match: "auth" matches "next-auth" (contains "-auth")
694
+ // but "auth" does NOT match "author" (different segment)
695
+ return getOrCompilePattern(term).test(packageName)
696
+ || packageName === term
697
+ || packageName.split("-").includes(term);
698
+ }
699
+ // Unscoped package or local path: use segment-boundary matching
700
+ // This reuses the same regex that protects path matching
701
+ return getOrCompilePattern(term).test(lower)
702
+ || lower.split("/").some((segment) => segment === term);
703
+ }
642
704
  const regexCache = new Map();
643
705
  function getOrCompilePattern(term) {
644
706
  if (regexCache.has(term))
@@ -716,10 +778,10 @@ export function detectAuthenticationSemanticRole(path, symbols, imports, content
716
778
  const hasAuthImport = normalizedImports.some((s) => /(^|[/@-])(auth|next-auth|auth0|clerk|lucia|better-auth|passport|kinde)([/.-]|$)/.test(s)
717
779
  || /supabase.*auth/.test(s)
718
780
  || /firebase\/auth/.test(s));
719
- const hasAuthSymbol = symbols.some((s) => /(auth|session|login|register|signin|signout|jwt|token|credential)/i.test(s));
781
+ const hasAuthSymbol = symbols.some((s) => /\b(auth|session|login|register|signin|signout|jwt|token|credential)\b/i.test(s));
720
782
  const hasAuthPath = /(^|[/._-])(auth|session|login|register|signin|signout)([/._-]|$)/.test(normalizedPath);
721
783
  const hasGuardPath = /(^|[/._-])(guard|middleware|proxy|protected)([/._-]|$)/.test(normalizedPath);
722
- const hasGuardSymbol = symbols.some((s) => /(guard|middleware|proxy|protected)/i.test(s));
784
+ const hasGuardSymbol = symbols.some((s) => /\b(guard|middleware|proxy|protected)\b/i.test(s));
723
785
  if (/(^|\/)src\/auth\.[cm]?[jt]sx?$/.test(normalizedPath)
724
786
  || /(^|\/)auth\.[cm]?[jt]sx?$/.test(normalizedPath)
725
787
  || (hasSymbol(symbols, "auth") && hasSymbol(symbols, "handlers"))
@@ -740,7 +802,7 @@ export function detectAuthenticationSemanticRole(path, symbols, imports, content
740
802
  // ---------------------------------------------------------------------------
741
803
  // File priority helpers
742
804
  // ---------------------------------------------------------------------------
743
- function featureFilePriority(featureName, path) {
805
+ export function featureFilePriority(featureName, path) {
744
806
  const normalized = path.toLowerCase();
745
807
  if (featureName === "Documentation") {
746
808
  if (normalized === "readme.md")