mapra 0.1.0

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/analyzer/blast-radius.d.ts +27 -0
  4. package/dist/analyzer/blast-radius.d.ts.map +1 -0
  5. package/dist/analyzer/blast-radius.js +58 -0
  6. package/dist/analyzer/blast-radius.js.map +1 -0
  7. package/dist/analyzer/churn.d.ts +26 -0
  8. package/dist/analyzer/churn.d.ts.map +1 -0
  9. package/dist/analyzer/churn.js +96 -0
  10. package/dist/analyzer/churn.js.map +1 -0
  11. package/dist/analyzer/co-change.d.ts +60 -0
  12. package/dist/analyzer/co-change.d.ts.map +1 -0
  13. package/dist/analyzer/co-change.js +153 -0
  14. package/dist/analyzer/co-change.js.map +1 -0
  15. package/dist/analyzer/conventions.d.ts +22 -0
  16. package/dist/analyzer/conventions.d.ts.map +1 -0
  17. package/dist/analyzer/conventions.js +81 -0
  18. package/dist/analyzer/conventions.js.map +1 -0
  19. package/dist/analyzer/git-hash.d.ts +11 -0
  20. package/dist/analyzer/git-hash.d.ts.map +1 -0
  21. package/dist/analyzer/git-hash.js +25 -0
  22. package/dist/analyzer/git-hash.js.map +1 -0
  23. package/dist/analyzer/graph-utils.d.ts +46 -0
  24. package/dist/analyzer/graph-utils.d.ts.map +1 -0
  25. package/dist/analyzer/graph-utils.js +145 -0
  26. package/dist/analyzer/graph-utils.js.map +1 -0
  27. package/dist/analyzer/index.d.ts +34 -0
  28. package/dist/analyzer/index.d.ts.map +1 -0
  29. package/dist/analyzer/index.js +63 -0
  30. package/dist/analyzer/index.js.map +1 -0
  31. package/dist/cli/hooks.d.ts +24 -0
  32. package/dist/cli/hooks.d.ts.map +1 -0
  33. package/dist/cli/hooks.js +126 -0
  34. package/dist/cli/hooks.js.map +1 -0
  35. package/dist/cli/index.d.ts +18 -0
  36. package/dist/cli/index.d.ts.map +1 -0
  37. package/dist/cli/index.js +818 -0
  38. package/dist/cli/index.js.map +1 -0
  39. package/dist/cli/plan-parser.d.ts +20 -0
  40. package/dist/cli/plan-parser.d.ts.map +1 -0
  41. package/dist/cli/plan-parser.js +104 -0
  42. package/dist/cli/plan-parser.js.map +1 -0
  43. package/dist/cli/shim.d.ts +3 -0
  44. package/dist/cli/shim.d.ts.map +1 -0
  45. package/dist/cli/shim.js +33 -0
  46. package/dist/cli/shim.js.map +1 -0
  47. package/dist/cli/templates.d.ts +28 -0
  48. package/dist/cli/templates.d.ts.map +1 -0
  49. package/dist/cli/templates.js +91 -0
  50. package/dist/cli/templates.js.map +1 -0
  51. package/dist/encoder/encode.d.ts +17 -0
  52. package/dist/encoder/encode.d.ts.map +1 -0
  53. package/dist/encoder/encode.js +270 -0
  54. package/dist/encoder/encode.js.map +1 -0
  55. package/dist/encoder/layer-infrastructure.d.ts +17 -0
  56. package/dist/encoder/layer-infrastructure.d.ts.map +1 -0
  57. package/dist/encoder/layer-infrastructure.js +232 -0
  58. package/dist/encoder/layer-infrastructure.js.map +1 -0
  59. package/dist/encoder/layer-labels.d.ts +18 -0
  60. package/dist/encoder/layer-labels.d.ts.map +1 -0
  61. package/dist/encoder/layer-labels.js +172 -0
  62. package/dist/encoder/layer-labels.js.map +1 -0
  63. package/dist/encoder/layer-terrain.d.ts +18 -0
  64. package/dist/encoder/layer-terrain.d.ts.map +1 -0
  65. package/dist/encoder/layer-terrain.js +135 -0
  66. package/dist/encoder/layer-terrain.js.map +1 -0
  67. package/dist/encoder/layout.d.ts +53 -0
  68. package/dist/encoder/layout.d.ts.map +1 -0
  69. package/dist/encoder/layout.js +178 -0
  70. package/dist/encoder/layout.js.map +1 -0
  71. package/dist/encoder/parse-strand-header.d.ts +29 -0
  72. package/dist/encoder/parse-strand-header.d.ts.map +1 -0
  73. package/dist/encoder/parse-strand-header.js +59 -0
  74. package/dist/encoder/parse-strand-header.js.map +1 -0
  75. package/dist/encoder/spatial-text-encode.d.ts +22 -0
  76. package/dist/encoder/spatial-text-encode.d.ts.map +1 -0
  77. package/dist/encoder/spatial-text-encode.js +199 -0
  78. package/dist/encoder/spatial-text-encode.js.map +1 -0
  79. package/dist/encoder/strand-format-encode-v1.d.ts +16 -0
  80. package/dist/encoder/strand-format-encode-v1.d.ts.map +1 -0
  81. package/dist/encoder/strand-format-encode-v1.js +296 -0
  82. package/dist/encoder/strand-format-encode-v1.js.map +1 -0
  83. package/dist/encoder/strand-format-encode.d.ts +21 -0
  84. package/dist/encoder/strand-format-encode.d.ts.map +1 -0
  85. package/dist/encoder/strand-format-encode.js +562 -0
  86. package/dist/encoder/strand-format-encode.js.map +1 -0
  87. package/dist/encoder/text-encode.d.ts +13 -0
  88. package/dist/encoder/text-encode.d.ts.map +1 -0
  89. package/dist/encoder/text-encode.js +123 -0
  90. package/dist/encoder/text-encode.js.map +1 -0
  91. package/dist/query/blast-radius.d.ts +14 -0
  92. package/dist/query/blast-radius.d.ts.map +1 -0
  93. package/dist/query/blast-radius.js +81 -0
  94. package/dist/query/blast-radius.js.map +1 -0
  95. package/dist/query/cache.d.ts +29 -0
  96. package/dist/query/cache.d.ts.map +1 -0
  97. package/dist/query/cache.js +138 -0
  98. package/dist/query/cache.js.map +1 -0
  99. package/dist/query/index.d.ts +2 -0
  100. package/dist/query/index.d.ts.map +1 -0
  101. package/dist/query/index.js +46 -0
  102. package/dist/query/index.js.map +1 -0
  103. package/dist/query/resolve.d.ts +7 -0
  104. package/dist/query/resolve.d.ts.map +1 -0
  105. package/dist/query/resolve.js +24 -0
  106. package/dist/query/resolve.js.map +1 -0
  107. package/dist/query/risk-profile.d.ts +30 -0
  108. package/dist/query/risk-profile.d.ts.map +1 -0
  109. package/dist/query/risk-profile.js +94 -0
  110. package/dist/query/risk-profile.js.map +1 -0
  111. package/dist/query/test-map.d.ts +13 -0
  112. package/dist/query/test-map.d.ts.map +1 -0
  113. package/dist/query/test-map.js +43 -0
  114. package/dist/query/test-map.js.map +1 -0
  115. package/dist/scanner/index.d.ts +51 -0
  116. package/dist/scanner/index.d.ts.map +1 -0
  117. package/dist/scanner/index.js +480 -0
  118. package/dist/scanner/index.js.map +1 -0
  119. package/dist/scanner/workspace.d.ts +11 -0
  120. package/dist/scanner/workspace.d.ts.map +1 -0
  121. package/dist/scanner/workspace.js +243 -0
  122. package/dist/scanner/workspace.js.map +1 -0
  123. package/package.json +52 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Convention Detector — identifies import patterns repeated across files of the same type.
3
+ *
4
+ * A "convention" is a dependency imported by >= 60% of files with a given type
5
+ * (e.g., 8/12 API routes import Sentry). Minimum 3 files of that type required.
6
+ */
7
+ const CONVENTION_THRESHOLD = 0.6;
8
+ const VIOLATION_THRESHOLD = 0.7;
9
+ const MIN_TYPE_COUNT = 3;
10
+ /**
11
+ * Detect import conventions from graph data.
12
+ * Returns conventions sorted by coverage descending.
13
+ */
14
+ export function detectConventions(nodes, edges) {
15
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
16
+ // Group non-test, non-config nodes by type
17
+ const byType = new Map();
18
+ for (const node of nodes) {
19
+ if (node.type === "test" || node.type === "config" || node.type === "schema")
20
+ continue;
21
+ const existing = byType.get(node.type) ?? [];
22
+ existing.push(node);
23
+ byType.set(node.type, existing);
24
+ }
25
+ // Build forward adjacency from non-test edges
26
+ const imports = new Map();
27
+ for (const edge of edges) {
28
+ if (edge.type === "tests")
29
+ continue;
30
+ const set = imports.get(edge.from) ?? new Set();
31
+ set.add(edge.to);
32
+ imports.set(edge.from, set);
33
+ }
34
+ const conventions = [];
35
+ for (const [type, typeNodes] of byType) {
36
+ if (typeNodes.length < MIN_TYPE_COUNT)
37
+ continue;
38
+ // Count how many nodes of this type import each dependency
39
+ const depCounts = new Map();
40
+ for (const node of typeNodes) {
41
+ const deps = imports.get(node.id);
42
+ if (!deps)
43
+ continue;
44
+ for (const dep of deps) {
45
+ depCounts.set(dep, (depCounts.get(dep) ?? 0) + 1);
46
+ }
47
+ }
48
+ // Check each dependency against threshold
49
+ for (const [dep, count] of depCounts) {
50
+ const coverage = count / typeNodes.length;
51
+ if (coverage < CONVENTION_THRESHOLD)
52
+ continue;
53
+ // Skip self-type dependencies (api-route importing another api-route isn't a convention)
54
+ const depNode = nodeMap.get(dep);
55
+ if (depNode?.type === type)
56
+ continue;
57
+ // Find violators: files of this type that DON'T import the convention dep
58
+ // Only populate for conventions with >= 70% adoption (strong conventions)
59
+ let violators = [];
60
+ if (coverage >= VIOLATION_THRESHOLD) {
61
+ violators = typeNodes
62
+ .filter((n) => {
63
+ const deps = imports.get(n.id);
64
+ return !deps || !deps.has(dep);
65
+ })
66
+ .map((n) => n.id);
67
+ }
68
+ conventions.push({
69
+ anchorFile: dep,
70
+ anchorExports: depNode?.exports?.filter((e) => e !== "default") ?? [],
71
+ consumerType: type,
72
+ adoption: count,
73
+ total: typeNodes.length,
74
+ coverage,
75
+ violators,
76
+ });
77
+ }
78
+ }
79
+ return conventions.sort((a, b) => b.coverage - a.coverage);
80
+ }
81
+ //# sourceMappingURL=conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../src/analyzer/conventions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Git Hash Utility — resolves the current HEAD short hash.
3
+ *
4
+ * Used to embed commit identity in .mapra headers for staleness detection.
5
+ */
6
+ /**
7
+ * Get the short git hash of the current HEAD commit.
8
+ * Returns null if not in a git repo or git is unavailable.
9
+ */
10
+ export declare function getGitHash(rootDir: string): string | null;
11
+ //# sourceMappingURL=git-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-hash.d.ts","sourceRoot":"","sources":["../../src/analyzer/git-hash.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzD"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Git Hash Utility — resolves the current HEAD short hash.
3
+ *
4
+ * Used to embed commit identity in .mapra headers for staleness detection.
5
+ */
6
+ import { execSync } from "child_process";
7
+ /**
8
+ * Get the short git hash of the current HEAD commit.
9
+ * Returns null if not in a git repo or git is unavailable.
10
+ */
11
+ export function getGitHash(rootDir) {
12
+ try {
13
+ const hash = execSync("git rev-parse --short HEAD", {
14
+ cwd: rootDir,
15
+ encoding: "utf-8",
16
+ timeout: 5000,
17
+ stdio: ["pipe", "pipe", "pipe"],
18
+ }).trim();
19
+ return hash || null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ //# sourceMappingURL=git-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-hash.js","sourceRoot":"","sources":["../../src/analyzer/git-hash.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,4BAA4B,EAAE;YAClD,GAAG,EAAE,OAAO;YACZ,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,IAAI;YACb,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,IAAI,IAAI,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared graph utilities — adjacency builders, BFS, module ID helpers.
3
+ * Used by blast-radius and future analyzers (keystones, dead wood, etc.).
4
+ */
5
+ import type { StrandEdge } from "../scanner/index.js";
6
+ /**
7
+ * Build reverse adjacency: for each node, who imports it.
8
+ * Optionally excludes test edges (type === "tests") and/or
9
+ * edges where the source node is a test file (by ID).
10
+ */
11
+ export declare function buildReverseAdjacency(edges: StrandEdge[], excludeTestEdges?: boolean, testNodeIds?: Set<string>): Map<string, Set<string>>;
12
+ /**
13
+ * Build forward adjacency: for each node, who does it import.
14
+ * Optionally excludes test edges.
15
+ */
16
+ export declare function buildForwardAdjacency(edges: StrandEdge[], excludeTestEdges?: boolean): Map<string, Set<string>>;
17
+ /**
18
+ * Count inbound edges per node.
19
+ * Optionally excludes test edges.
20
+ */
21
+ export declare function countInboundEdges(edges: StrandEdge[], excludeTestEdges?: boolean): Map<string, number>;
22
+ /**
23
+ * BFS from startId through adjacency map.
24
+ * Returns Map<nodeId, depth> of all reachable nodes (excluding start).
25
+ * Handles cycles safely via visited set.
26
+ */
27
+ export declare function bfs(startId: string, adjacency: Map<string, Set<string>>): Map<string, number>;
28
+ /**
29
+ * Extract coarse module ID from a file path.
30
+ * Uses first 2 path segments: "src/lib", "src/app", etc.
31
+ * Root-level files return the first segment.
32
+ */
33
+ export declare function getModuleId(nodePath: string): string;
34
+ /**
35
+ * Count distinct modules among a set of node IDs.
36
+ */
37
+ export declare function countDistinctModules(nodeIds: Iterable<string>): number;
38
+ /**
39
+ * BFS with parent tracking — like bfs() but also records which node
40
+ * discovered each visited node. Used for cascade path reconstruction.
41
+ */
42
+ export declare function bfsWithParents(startId: string, adjacency: Map<string, Set<string>>): {
43
+ depths: Map<string, number>;
44
+ parents: Map<string, string>;
45
+ };
46
+ //# sourceMappingURL=graph-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-utils.d.ts","sourceRoot":"","sources":["../../src/analyzer/graph-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EAAE,EACnB,gBAAgB,UAAQ,EACxB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACxB,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAW1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EAAE,EACnB,gBAAgB,UAAQ,GACvB,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAU1B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EAAE,EACnB,gBAAgB,UAAQ,GACvB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CASrB;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAClC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAiCrB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAMtE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,GAClC;IAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAmC/D"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Shared graph utilities — adjacency builders, BFS, module ID helpers.
3
+ * Used by blast-radius and future analyzers (keystones, dead wood, etc.).
4
+ */
5
+ /**
6
+ * Build reverse adjacency: for each node, who imports it.
7
+ * Optionally excludes test edges (type === "tests") and/or
8
+ * edges where the source node is a test file (by ID).
9
+ */
10
+ export function buildReverseAdjacency(edges, excludeTestEdges = false, testNodeIds) {
11
+ const rev = new Map();
12
+ for (const edge of edges) {
13
+ if (excludeTestEdges && edge.type === "tests")
14
+ continue;
15
+ if (testNodeIds && testNodeIds.has(edge.from))
16
+ continue;
17
+ if (!rev.has(edge.to))
18
+ rev.set(edge.to, new Set());
19
+ rev.get(edge.to).add(edge.from);
20
+ }
21
+ return rev;
22
+ }
23
+ /**
24
+ * Build forward adjacency: for each node, who does it import.
25
+ * Optionally excludes test edges.
26
+ */
27
+ export function buildForwardAdjacency(edges, excludeTestEdges = false) {
28
+ const fwd = new Map();
29
+ for (const edge of edges) {
30
+ if (excludeTestEdges && edge.type === "tests")
31
+ continue;
32
+ if (!fwd.has(edge.from))
33
+ fwd.set(edge.from, new Set());
34
+ fwd.get(edge.from).add(edge.to);
35
+ }
36
+ return fwd;
37
+ }
38
+ /**
39
+ * Count inbound edges per node.
40
+ * Optionally excludes test edges.
41
+ */
42
+ export function countInboundEdges(edges, excludeTestEdges = false) {
43
+ const counts = new Map();
44
+ for (const edge of edges) {
45
+ if (excludeTestEdges && edge.type === "tests")
46
+ continue;
47
+ counts.set(edge.to, (counts.get(edge.to) || 0) + 1);
48
+ }
49
+ return counts;
50
+ }
51
+ /**
52
+ * BFS from startId through adjacency map.
53
+ * Returns Map<nodeId, depth> of all reachable nodes (excluding start).
54
+ * Handles cycles safely via visited set.
55
+ */
56
+ export function bfs(startId, adjacency) {
57
+ const visited = new Set([startId]);
58
+ const depths = new Map();
59
+ const queue = [];
60
+ // Seed queue with direct neighbors
61
+ const neighbors = adjacency.get(startId);
62
+ if (neighbors) {
63
+ for (const n of neighbors) {
64
+ if (!visited.has(n)) {
65
+ visited.add(n);
66
+ depths.set(n, 1);
67
+ queue.push({ id: n, depth: 1 });
68
+ }
69
+ }
70
+ }
71
+ let head = 0;
72
+ while (head < queue.length) {
73
+ const { id, depth } = queue[head++];
74
+ const next = adjacency.get(id);
75
+ if (!next)
76
+ continue;
77
+ for (const n of next) {
78
+ if (!visited.has(n)) {
79
+ visited.add(n);
80
+ depths.set(n, depth + 1);
81
+ queue.push({ id: n, depth: depth + 1 });
82
+ }
83
+ }
84
+ }
85
+ return depths;
86
+ }
87
+ /**
88
+ * Extract coarse module ID from a file path.
89
+ * Uses first 2 path segments: "src/lib", "src/app", etc.
90
+ * Root-level files return the first segment.
91
+ */
92
+ export function getModuleId(nodePath) {
93
+ const parts = nodePath.split("/");
94
+ return parts.length > 2
95
+ ? parts.slice(0, 2).join("/")
96
+ : (parts[0] ?? nodePath);
97
+ }
98
+ /**
99
+ * Count distinct modules among a set of node IDs.
100
+ */
101
+ export function countDistinctModules(nodeIds) {
102
+ const modules = new Set();
103
+ for (const id of nodeIds) {
104
+ modules.add(getModuleId(id));
105
+ }
106
+ return modules.size;
107
+ }
108
+ /**
109
+ * BFS with parent tracking — like bfs() but also records which node
110
+ * discovered each visited node. Used for cascade path reconstruction.
111
+ */
112
+ export function bfsWithParents(startId, adjacency) {
113
+ const visited = new Set([startId]);
114
+ const depths = new Map();
115
+ const parents = new Map();
116
+ const queue = [];
117
+ const neighbors = adjacency.get(startId);
118
+ if (neighbors) {
119
+ for (const n of neighbors) {
120
+ if (!visited.has(n)) {
121
+ visited.add(n);
122
+ depths.set(n, 1);
123
+ parents.set(n, startId);
124
+ queue.push({ id: n, depth: 1 });
125
+ }
126
+ }
127
+ }
128
+ let head = 0;
129
+ while (head < queue.length) {
130
+ const { id, depth } = queue[head++];
131
+ const next = adjacency.get(id);
132
+ if (!next)
133
+ continue;
134
+ for (const n of next) {
135
+ if (!visited.has(n)) {
136
+ visited.add(n);
137
+ depths.set(n, depth + 1);
138
+ parents.set(n, id);
139
+ queue.push({ id: n, depth: depth + 1 });
140
+ }
141
+ }
142
+ }
143
+ return { depths, parents };
144
+ }
145
+ //# sourceMappingURL=graph-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-utils.js","sourceRoot":"","sources":["../../src/analyzer/graph-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Graph Analyzer — entry point for structural analysis.
3
+ *
4
+ * Builds shared data structures once, passes them to individual analyzers.
5
+ * Currently: blast radius. Designed to extend with keystones, dead wood, symbiosis.
6
+ */
7
+ import type { StrandGraph } from "../scanner/index.js";
8
+ import { type BlastResult } from "./blast-radius.js";
9
+ import { type ChurnResult } from "./churn.js";
10
+ import { type Convention } from "./conventions.js";
11
+ import { type CoChangePair } from "./co-change.js";
12
+ /**
13
+ * Returns true for files that are noise in analytical sections:
14
+ * - `.generated.{ts,tsx,js,jsx}` — auto-generated code
15
+ * - `.d.ts` — TypeScript declaration files (ambient types, not business logic)
16
+ */
17
+ export declare function isNoiseFile(filePath: string): boolean;
18
+ export interface GraphAnalysis {
19
+ risk: BlastResult[];
20
+ deadCode: string[];
21
+ churn: Map<string, ChurnResult>;
22
+ conventions: Convention[];
23
+ coChanges: CoChangePair[];
24
+ }
25
+ /**
26
+ * Analyze a strand graph for structural risk patterns.
27
+ * Returns sorted results ready for rendering.
28
+ */
29
+ export declare function analyzeGraph(graph: StrandGraph, rootDir?: string): GraphAnalysis;
30
+ export type { BlastResult } from "./blast-radius.js";
31
+ export type { ChurnResult } from "./churn.js";
32
+ export type { Convention } from "./conventions.js";
33
+ export type { CoChangePair } from "./co-change.js";
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EACL,KAAK,WAAW,EAEjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,KAAK,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAErE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAmBD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,SAAS,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,CAoChF;AAED,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Graph Analyzer — entry point for structural analysis.
3
+ *
4
+ * Builds shared data structures once, passes them to individual analyzers.
5
+ * Currently: blast radius. Designed to extend with keystones, dead wood, symbiosis.
6
+ */
7
+ import { buildReverseAdjacency } from "./graph-utils.js";
8
+ import { computeAllBlastRadii, } from "./blast-radius.js";
9
+ import { computeChurn } from "./churn.js";
10
+ import { detectConventions } from "./conventions.js";
11
+ import { computeCoChanges } from "./co-change.js";
12
+ /**
13
+ * Returns true for files that are noise in analytical sections:
14
+ * - `.generated.{ts,tsx,js,jsx}` — auto-generated code
15
+ * - `.d.ts` — TypeScript declaration files (ambient types, not business logic)
16
+ */
17
+ export function isNoiseFile(filePath) {
18
+ return /\.generated\.(ts|tsx|js|jsx)$|\.d\.ts$/.test(filePath);
19
+ }
20
+ /**
21
+ * Files managed by dependency injection — typed as utility but not dead code.
22
+ * These are loaded by DI containers (NestJS, Angular) at runtime, not via
23
+ * static imports, so they have zero inbound edges in the import graph.
24
+ */
25
+ const DI_ENTRY_PATTERNS = [
26
+ /\.service\.(ts|js)$/,
27
+ /\.repository\.(ts|js)$/,
28
+ /\.resolver\.(ts|js)$/,
29
+ /\.gateway\.(ts|js)$/,
30
+ /\.subscriber\.(ts|js)$/,
31
+ ];
32
+ function isDiEntryPoint(filePath) {
33
+ return DI_ENTRY_PATTERNS.some(re => re.test(filePath));
34
+ }
35
+ /**
36
+ * Analyze a strand graph for structural risk patterns.
37
+ * Returns sorted results ready for rendering.
38
+ */
39
+ export function analyzeGraph(graph, rootDir) {
40
+ // Collect test node IDs for filtering
41
+ const testNodeIds = new Set(graph.nodes.filter(n => n.type === "test").map(n => n.id));
42
+ // Build reverse adjacency once, excluding test edges and test-sourced edges
43
+ const reverseAdj = buildReverseAdjacency(graph.edges, true, testNodeIds);
44
+ // Compute blast radii
45
+ const blastMap = computeAllBlastRadii(reverseAdj);
46
+ // Sort by amplificationRatio descending
47
+ const risk = [...blastMap.values()].sort((a, b) => b.amplificationRatio - a.amplificationRatio);
48
+ // Dead code: files with no inbound edges (not routes, configs, or tests)
49
+ const SKIP_TYPES = new Set([
50
+ "route", "api-route", "config", "test", "layout", "middleware",
51
+ ]);
52
+ const deadCode = graph.nodes
53
+ .filter((n) => !SKIP_TYPES.has(n.type) &&
54
+ !isNoiseFile(n.id) &&
55
+ !isDiEntryPoint(n.id) &&
56
+ !reverseAdj.has(n.id))
57
+ .map((n) => n.id);
58
+ const churn = rootDir ? computeChurn(rootDir) : new Map();
59
+ const conventions = detectConventions(graph.nodes, graph.edges);
60
+ const coChanges = rootDir ? computeCoChanges(rootDir, graph.edges) : [];
61
+ return { risk, deadCode, churn, conventions, coChanges };
62
+ }
63
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analyzer/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAEL,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAoB,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAmB,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAqB,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAErE;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,OAAO,wCAAwC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,iBAAiB,GAAG;IACxB,qBAAqB;IACrB,wBAAwB;IACxB,sBAAsB;IACtB,qBAAqB;IACrB,wBAAwB;CACzB,CAAC;AAEF,SAAS,cAAc,CAAC,QAAgB;IACtC,OAAO,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzD,CAAC;AAUD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAkB,EAAE,OAAgB;IAC/D,sCAAsC;IACtC,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC1D,CAAC;IAEF,4EAA4E;IAC5E,MAAM,UAAU,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;IAEzE,sBAAsB;IACtB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAElD,wCAAwC;IACxC,MAAM,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CACtC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,GAAG,CAAC,CAAC,kBAAkB,CACtD,CAAC;IAEF,yEAAyE;IACzE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAqB;QAC7C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY;KAC/D,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK;SACzB,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QACvB,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAClB,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CACxB;SACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpB,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAuB,CAAC;IAC/E,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAExE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,24 @@
1
+ export declare const MAPRA_HOOK_START = "# --- mapra auto-update (do not edit) ---";
2
+ export declare const MAPRA_HOOK_END = "# --- end mapra ---";
3
+ declare const HOOK_TYPES: readonly ["post-commit", "post-merge", "post-checkout"];
4
+ type HookType = (typeof HOOK_TYPES)[number];
5
+ export declare function generateTrampoline(hookType: HookType): string;
6
+ export declare function installHook(hooksDir: string, hookType: HookType): void;
7
+ export declare function uninstallHook(hooksDir: string, hookType: HookType): void;
8
+ /**
9
+ * Resolve the hooks directory: checks core.hooksPath first, falls back to .git/hooks.
10
+ */
11
+ export declare function getHooksDir(targetPath: string): string | null;
12
+ /**
13
+ * Install all three mapra hook trampolines.
14
+ */
15
+ export declare function installAllHooks(targetPath: string): {
16
+ installed: string[];
17
+ skipped: string | null;
18
+ };
19
+ /**
20
+ * Uninstall all mapra hook trampolines.
21
+ */
22
+ export declare function uninstallAllHooks(targetPath: string): void;
23
+ export {};
24
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,8CAA8C,CAAC;AAC5E,eAAO,MAAM,cAAc,wBAAwB,CAAC;AAEpD,QAAA,MAAM,UAAU,yDAA0D,CAAC;AAC3E,KAAK,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAc7D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAwBtE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAsBxE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+B7D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAgBnG;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAO1D"}
@@ -0,0 +1,126 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { execFileSync } from "child_process";
4
+ export const MAPRA_HOOK_START = "# --- mapra auto-update (do not edit) ---";
5
+ export const MAPRA_HOOK_END = "# --- end mapra ---";
6
+ const HOOK_TYPES = ["post-commit", "post-merge", "post-checkout"];
7
+ export function generateTrampoline(hookType) {
8
+ const lines = ["#!/bin/sh", MAPRA_HOOK_START];
9
+ if (hookType === "post-checkout") {
10
+ // $3=1 means branch switch; $3=0 means file checkout — skip file checkouts
11
+ lines.push('[ "$3" = "1" ] && [ -f .mapra/hook.mjs ] && node .mapra/hook.mjs &');
12
+ }
13
+ else {
14
+ lines.push("[ -f .mapra/hook.mjs ] && node .mapra/hook.mjs &");
15
+ }
16
+ lines.push(MAPRA_HOOK_END, "");
17
+ return lines.join("\n");
18
+ }
19
+ export function installHook(hooksDir, hookType) {
20
+ const hookPath = path.join(hooksDir, hookType);
21
+ const block = generateTrampoline(hookType);
22
+ if (fs.existsSync(hookPath)) {
23
+ const existing = fs.readFileSync(hookPath, "utf-8");
24
+ // Already installed — skip
25
+ if (existing.includes(MAPRA_HOOK_START))
26
+ return;
27
+ // Append to existing hook
28
+ const separator = existing.endsWith("\n") ? "" : "\n";
29
+ const content = existing + separator + block;
30
+ fs.writeFileSync(hookPath, content.replace(/\r\n/g, "\n"));
31
+ }
32
+ else {
33
+ fs.writeFileSync(hookPath, block.replace(/\r\n/g, "\n"));
34
+ }
35
+ // Set executable bit (no-op on Windows, needed on Unix)
36
+ try {
37
+ fs.chmodSync(hookPath, 0o755);
38
+ }
39
+ catch {
40
+ /* ignore on systems that don't support chmod */
41
+ }
42
+ }
43
+ export function uninstallHook(hooksDir, hookType) {
44
+ const hookPath = path.join(hooksDir, hookType);
45
+ if (!fs.existsSync(hookPath))
46
+ return;
47
+ const content = fs.readFileSync(hookPath, "utf-8");
48
+ if (!content.includes(MAPRA_HOOK_START))
49
+ return;
50
+ // Remove the mapra block (including markers)
51
+ const startIdx = content.indexOf(MAPRA_HOOK_START);
52
+ const endIdx = content.indexOf(MAPRA_HOOK_END);
53
+ if (startIdx === -1 || endIdx === -1)
54
+ return;
55
+ const before = content.slice(0, startIdx);
56
+ const after = content.slice(endIdx + MAPRA_HOOK_END.length + 1); // +1 for trailing \n
57
+ const remaining = (before + after).trim();
58
+ if (!remaining || remaining === "#!/bin/sh") {
59
+ fs.unlinkSync(hookPath);
60
+ }
61
+ else {
62
+ fs.writeFileSync(hookPath, remaining + "\n");
63
+ }
64
+ }
65
+ /**
66
+ * Resolve the hooks directory: checks core.hooksPath first, falls back to .git/hooks.
67
+ */
68
+ export function getHooksDir(targetPath) {
69
+ // Check core.hooksPath first
70
+ try {
71
+ const customPath = execFileSync("git", ["config", "core.hooksPath"], {
72
+ cwd: targetPath,
73
+ encoding: "utf-8",
74
+ timeout: 5000,
75
+ }).trim();
76
+ if (customPath) {
77
+ const resolved = path.isAbsolute(customPath)
78
+ ? customPath
79
+ : path.join(targetPath, customPath);
80
+ return resolved;
81
+ }
82
+ }
83
+ catch {
84
+ // core.hooksPath not set — fall through
85
+ }
86
+ // Use git rev-parse to handle worktrees, submodules, and normal repos
87
+ try {
88
+ const gitCommonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
89
+ cwd: targetPath,
90
+ encoding: "utf-8",
91
+ timeout: 5000,
92
+ }).trim();
93
+ return path.join(path.resolve(targetPath, gitCommonDir), "hooks");
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ /**
100
+ * Install all three mapra hook trampolines.
101
+ */
102
+ export function installAllHooks(targetPath) {
103
+ const hooksDir = getHooksDir(targetPath);
104
+ if (!hooksDir) {
105
+ return { installed: [], skipped: "No .git directory found" };
106
+ }
107
+ fs.mkdirSync(hooksDir, { recursive: true });
108
+ const installed = [];
109
+ for (const hookType of HOOK_TYPES) {
110
+ installHook(hooksDir, hookType);
111
+ installed.push(hookType);
112
+ }
113
+ return { installed, skipped: null };
114
+ }
115
+ /**
116
+ * Uninstall all mapra hook trampolines.
117
+ */
118
+ export function uninstallAllHooks(targetPath) {
119
+ const hooksDir = getHooksDir(targetPath);
120
+ if (!hooksDir)
121
+ return;
122
+ for (const hookType of HOOK_TYPES) {
123
+ uninstallHook(hooksDir, hookType);
124
+ }
125
+ }
126
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/cli/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * mapra CLI
4
+ *
5
+ * Commands:
6
+ * mapra setup [path] Generate .mapra and wire CLAUDE.md (first-time setup)
7
+ * mapra generate [path] Scan codebase and write .mapra file
8
+ * mapra update [path] Regenerate .mapra in place (alias for generate in cwd)
9
+ * mapra init [path] Wire .mapra into project's CLAUDE.md
10
+ * mapra status [path] Show current mapra setup state
11
+ * mapra check [path] Check if .mapra is current or stale (git hash comparison)
12
+ * mapra validate-plan <plan.md> [--since YYYY-MM-DD] [--checkpoints] Cross-reference plan against .mapra
13
+ * mapra batch <config.json> [--resume] Run batch experiment from config
14
+ * mapra analyze <results.json> [--advise] [--judge-check] Analyze experiment results
15
+ * mapra query <type> <file> [--json] Query structural data (blast_radius, risk_profile, test_map)
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}