@flaid/devmap 0.3.1 → 0.3.3
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 +0 -3
- package/dist/analyzers/analysis/extractors/fallbackExtractor.d.ts.map +1 -1
- package/dist/analyzers/analysis/extractors/fallbackExtractor.js +11 -1
- package/dist/analyzers/analysis/extractors/fallbackExtractor.js.map +1 -1
- package/dist/analyzers/features/featureCandidates.d.ts +59 -0
- package/dist/analyzers/features/featureCandidates.d.ts.map +1 -0
- package/dist/analyzers/features/featureCandidates.js +238 -0
- package/dist/analyzers/features/featureCandidates.js.map +1 -0
- package/dist/analyzers/features/featureDetector.d.ts +1 -0
- package/dist/analyzers/features/featureDetector.d.ts.map +1 -1
- package/dist/analyzers/features/featureDetector.js +78 -16
- package/dist/analyzers/features/featureDetector.js.map +1 -1
- package/dist/analyzers/features/featureSimilarity.d.ts +2 -0
- package/dist/analyzers/features/featureSimilarity.d.ts.map +1 -1
- package/dist/analyzers/features/featureSimilarity.js +9 -6
- package/dist/analyzers/features/featureSimilarity.js.map +1 -1
- package/dist/analyzers/features/index.d.ts +3 -1
- package/dist/analyzers/features/index.d.ts.map +1 -1
- package/dist/analyzers/features/index.js +2 -1
- package/dist/analyzers/features/index.js.map +1 -1
- package/dist/analyzers/graph/aliasResolver.d.ts +15 -0
- package/dist/analyzers/graph/aliasResolver.d.ts.map +1 -0
- package/dist/analyzers/graph/aliasResolver.js +50 -0
- package/dist/analyzers/graph/aliasResolver.js.map +1 -0
- package/dist/analyzers/graph/dependencyGraph.d.ts +12 -1
- package/dist/analyzers/graph/dependencyGraph.d.ts.map +1 -1
- package/dist/analyzers/graph/dependencyGraph.js +53 -6
- package/dist/analyzers/graph/dependencyGraph.js.map +1 -1
- package/dist/analyzers/graph/index.d.ts +3 -1
- package/dist/analyzers/graph/index.d.ts.map +1 -1
- package/dist/analyzers/graph/index.js +1 -0
- package/dist/analyzers/graph/index.js.map +1 -1
- package/dist/analyzers/pipeline/projectMap.d.ts +3 -0
- package/dist/analyzers/pipeline/projectMap.d.ts.map +1 -1
- package/dist/analyzers/pipeline/projectMap.js +12 -3
- package/dist/analyzers/pipeline/projectMap.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,9 +10,6 @@ maps, `DEVMAP.md`) is what you and any AI agent read afterward,
|
|
|
10
10
|
instead of re-scanning or re-pasting the whole repo. Full repo
|
|
11
11
|
source stays local — nothing gets sent wholesale to the AI model.
|
|
12
12
|
|
|
13
|
-
Version `0.2.0` classifies full workspaces (multiple project types at once),
|
|
14
|
-
centralizes detection vocabulary, and ships `map`, `flow`, and `explain`.
|
|
15
|
-
|
|
16
13
|
## Install
|
|
17
14
|
|
|
18
15
|
```bash
|
|
@@ -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;
|
|
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":"AAGA,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACxC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IACvB,QAAQ,EAAE,UAAU,EAAE,SAAS;IAC/B,MAAM,EAAE,OAAO,EAAE,UAAU;IAC3B,QAAQ,EAAE,MAAM,EAAE,QAAQ;IAC1B,QAAQ,EAAE,QAAQ,EAAE,OAAO;IAC3B,IAAI,EAAE,MAAM;CACb,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,UAAU,EAAE,OAAO,CAAC;IACrB,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,MAAM,EAAE,OAAO,CAAC;IACjB,CAAC,OAAO,EAAE,OAAO,CAAC;CACnB,CAAC,CAAC;AAEH,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,sBAAsB;IACxB,IAAI,GAAG,gBAAgB,CAAC;IAEjC,OAAO,CAAC,MAAmB;QACzB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QAEtC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAC3D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;
|
|
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;
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
326
|
+
candidates.push(toFeatureCandidate("frontend-page", "file-page", feature, routes));
|
|
326
327
|
}
|
|
327
328
|
for (const feature of detectClientRouteFeatures(scopedFiles, fileGraph)) {
|
|
328
|
-
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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")
|