@reactgraph/cli 0.1.1

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 (178) hide show
  1. package/README.md +319 -0
  2. package/bun.lock +527 -0
  3. package/dist/cli/components/IndexProgress.d.ts +18 -0
  4. package/dist/cli/components/IndexProgress.d.ts.map +1 -0
  5. package/dist/cli/components/IndexProgress.js +26 -0
  6. package/dist/cli/components/IndexProgress.js.map +1 -0
  7. package/dist/cli/components/InitResult.d.ts +7 -0
  8. package/dist/cli/components/InitResult.d.ts.map +1 -0
  9. package/dist/cli/components/InitResult.js +6 -0
  10. package/dist/cli/components/InitResult.js.map +1 -0
  11. package/dist/cli/index-cmd.d.ts +7 -0
  12. package/dist/cli/index-cmd.d.ts.map +1 -0
  13. package/dist/cli/index-cmd.js +28 -0
  14. package/dist/cli/index-cmd.js.map +1 -0
  15. package/dist/cli/index.d.ts +3 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +81 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/init.d.ts +8 -0
  20. package/dist/cli/init.d.ts.map +1 -0
  21. package/dist/cli/init.js +77 -0
  22. package/dist/cli/init.js.map +1 -0
  23. package/dist/cli/serve.d.ts +2 -0
  24. package/dist/cli/serve.d.ts.map +1 -0
  25. package/dist/cli/serve.js +28 -0
  26. package/dist/cli/serve.js.map +1 -0
  27. package/dist/cli/unused.d.ts +2 -0
  28. package/dist/cli/unused.d.ts.map +1 -0
  29. package/dist/cli/unused.js +56 -0
  30. package/dist/cli/unused.js.map +1 -0
  31. package/dist/graph/graph.d.ts +30 -0
  32. package/dist/graph/graph.d.ts.map +1 -0
  33. package/dist/graph/graph.js +166 -0
  34. package/dist/graph/graph.js.map +1 -0
  35. package/dist/graph/index.d.ts +5 -0
  36. package/dist/graph/index.d.ts.map +1 -0
  37. package/dist/graph/index.js +5 -0
  38. package/dist/graph/index.js.map +1 -0
  39. package/dist/graph/schema.d.ts +33 -0
  40. package/dist/graph/schema.d.ts.map +1 -0
  41. package/dist/graph/schema.js +3 -0
  42. package/dist/graph/schema.js.map +1 -0
  43. package/dist/graph/serialize.d.ts +7 -0
  44. package/dist/graph/serialize.d.ts.map +1 -0
  45. package/dist/graph/serialize.js +39 -0
  46. package/dist/graph/serialize.js.map +1 -0
  47. package/dist/graph/traverse.d.ts +14 -0
  48. package/dist/graph/traverse.d.ts.map +1 -0
  49. package/dist/graph/traverse.js +50 -0
  50. package/dist/graph/traverse.js.map +1 -0
  51. package/dist/mcp/formatter.d.ts +26 -0
  52. package/dist/mcp/formatter.d.ts.map +1 -0
  53. package/dist/mcp/formatter.js +691 -0
  54. package/dist/mcp/formatter.js.map +1 -0
  55. package/dist/mcp/server.d.ts +2 -0
  56. package/dist/mcp/server.d.ts.map +1 -0
  57. package/dist/mcp/server.js +45 -0
  58. package/dist/mcp/server.js.map +1 -0
  59. package/dist/mcp/tools.d.ts +9 -0
  60. package/dist/mcp/tools.d.ts.map +1 -0
  61. package/dist/mcp/tools.js +136 -0
  62. package/dist/mcp/tools.js.map +1 -0
  63. package/dist/output/ai-context.d.ts +7 -0
  64. package/dist/output/ai-context.d.ts.map +1 -0
  65. package/dist/output/ai-context.js +26 -0
  66. package/dist/output/ai-context.js.map +1 -0
  67. package/dist/parser/extractors/api-calls.d.ts +15 -0
  68. package/dist/parser/extractors/api-calls.d.ts.map +1 -0
  69. package/dist/parser/extractors/api-calls.js +168 -0
  70. package/dist/parser/extractors/api-calls.js.map +1 -0
  71. package/dist/parser/extractors/components.d.ts +5 -0
  72. package/dist/parser/extractors/components.d.ts.map +1 -0
  73. package/dist/parser/extractors/components.js +236 -0
  74. package/dist/parser/extractors/components.js.map +1 -0
  75. package/dist/parser/extractors/context.d.ts +14 -0
  76. package/dist/parser/extractors/context.d.ts.map +1 -0
  77. package/dist/parser/extractors/context.js +196 -0
  78. package/dist/parser/extractors/context.js.map +1 -0
  79. package/dist/parser/extractors/effects.d.ts +14 -0
  80. package/dist/parser/extractors/effects.d.ts.map +1 -0
  81. package/dist/parser/extractors/effects.js +175 -0
  82. package/dist/parser/extractors/effects.js.map +1 -0
  83. package/dist/parser/extractors/hooks.d.ts +5 -0
  84. package/dist/parser/extractors/hooks.d.ts.map +1 -0
  85. package/dist/parser/extractors/hooks.js +242 -0
  86. package/dist/parser/extractors/hooks.js.map +1 -0
  87. package/dist/parser/extractors/imports.d.ts +6 -0
  88. package/dist/parser/extractors/imports.d.ts.map +1 -0
  89. package/dist/parser/extractors/imports.js +148 -0
  90. package/dist/parser/extractors/imports.js.map +1 -0
  91. package/dist/parser/extractors/index.d.ts +12 -0
  92. package/dist/parser/extractors/index.d.ts.map +1 -0
  93. package/dist/parser/extractors/index.js +11 -0
  94. package/dist/parser/extractors/index.js.map +1 -0
  95. package/dist/parser/extractors/jsx-tree.d.ts +5 -0
  96. package/dist/parser/extractors/jsx-tree.d.ts.map +1 -0
  97. package/dist/parser/extractors/jsx-tree.js +226 -0
  98. package/dist/parser/extractors/jsx-tree.js.map +1 -0
  99. package/dist/parser/extractors/routes.d.ts +13 -0
  100. package/dist/parser/extractors/routes.d.ts.map +1 -0
  101. package/dist/parser/extractors/routes.js +275 -0
  102. package/dist/parser/extractors/routes.js.map +1 -0
  103. package/dist/parser/extractors/state.d.ts +14 -0
  104. package/dist/parser/extractors/state.d.ts.map +1 -0
  105. package/dist/parser/extractors/state.js +368 -0
  106. package/dist/parser/extractors/state.js.map +1 -0
  107. package/dist/parser/extractors/types.d.ts +22 -0
  108. package/dist/parser/extractors/types.d.ts.map +1 -0
  109. package/dist/parser/extractors/types.js +51 -0
  110. package/dist/parser/extractors/types.js.map +1 -0
  111. package/dist/parser/indexer.d.ts +14 -0
  112. package/dist/parser/indexer.d.ts.map +1 -0
  113. package/dist/parser/indexer.js +167 -0
  114. package/dist/parser/indexer.js.map +1 -0
  115. package/dist/parser/pipeline.d.ts +16 -0
  116. package/dist/parser/pipeline.d.ts.map +1 -0
  117. package/dist/parser/pipeline.js +63 -0
  118. package/dist/parser/pipeline.js.map +1 -0
  119. package/dist/parser/setup.d.ts +4 -0
  120. package/dist/parser/setup.d.ts.map +1 -0
  121. package/dist/parser/setup.js +29 -0
  122. package/dist/parser/setup.js.map +1 -0
  123. package/dist/parser/walker.d.ts +6 -0
  124. package/dist/parser/walker.d.ts.map +1 -0
  125. package/dist/parser/walker.js +45 -0
  126. package/dist/parser/walker.js.map +1 -0
  127. package/dist/watcher.d.ts +12 -0
  128. package/dist/watcher.d.ts.map +1 -0
  129. package/dist/watcher.js +72 -0
  130. package/dist/watcher.js.map +1 -0
  131. package/package.json +51 -0
  132. package/src/cli/components/IndexProgress.tsx +79 -0
  133. package/src/cli/components/InitResult.tsx +28 -0
  134. package/src/cli/index-cmd.ts +41 -0
  135. package/src/cli/index.ts +92 -0
  136. package/src/cli/init.ts +97 -0
  137. package/src/cli/serve.ts +29 -0
  138. package/src/cli/unused.ts +88 -0
  139. package/src/graph/graph.ts +179 -0
  140. package/src/graph/index.ts +4 -0
  141. package/src/graph/schema.ts +68 -0
  142. package/src/graph/serialize.ts +40 -0
  143. package/src/graph/traverse.ts +66 -0
  144. package/src/mcp/formatter.ts +757 -0
  145. package/src/mcp/server.ts +59 -0
  146. package/src/mcp/tools.ts +154 -0
  147. package/src/output/ai-context.ts +29 -0
  148. package/src/parser/extractors/api-calls.ts +192 -0
  149. package/src/parser/extractors/components.ts +273 -0
  150. package/src/parser/extractors/context.ts +216 -0
  151. package/src/parser/extractors/effects.ts +205 -0
  152. package/src/parser/extractors/hooks.ts +268 -0
  153. package/src/parser/extractors/imports.ts +192 -0
  154. package/src/parser/extractors/index.ts +11 -0
  155. package/src/parser/extractors/jsx-tree.ts +271 -0
  156. package/src/parser/extractors/routes.ts +331 -0
  157. package/src/parser/extractors/state.ts +392 -0
  158. package/src/parser/extractors/types.ts +71 -0
  159. package/src/parser/indexer.ts +197 -0
  160. package/src/parser/pipeline.ts +89 -0
  161. package/src/parser/setup.ts +33 -0
  162. package/src/parser/walker.ts +61 -0
  163. package/src/watcher.ts +91 -0
  164. package/templates/CLAUDE.md +7 -0
  165. package/tests/extractors.test.ts +164 -0
  166. package/tests/fixtures/basic/src/App.tsx +12 -0
  167. package/tests/fixtures/basic/src/components/Dashboard.tsx +24 -0
  168. package/tests/fixtures/basic/src/components/MetricsCard.tsx +15 -0
  169. package/tests/fixtures/basic/src/components/Sidebar.tsx +20 -0
  170. package/tests/fixtures/basic/src/contexts/ThemeContext.tsx +16 -0
  171. package/tests/fixtures/basic/src/hooks/useAuth.ts +25 -0
  172. package/tests/fixtures/basic/src/stores/authStore.ts +15 -0
  173. package/tests/fixtures/basic/src/utils.ts +7 -0
  174. package/tests/graph.test.ts +91 -0
  175. package/tests/phase2.test.ts +309 -0
  176. package/tests/smoke.test.ts +77 -0
  177. package/tsconfig.json +20 -0
  178. package/vitest.config.ts +8 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexProgress.d.ts","sourceRoot":"","sources":["../../../src/cli/components/IndexProgress.tsx"],"names":[],"mappings":"AAgBA,UAAU,kBAAkB;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,wBAAgB,aAAa,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CA8CzG"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
5
+ function useSpinner() {
6
+ const [frame, setFrame] = useState(0);
7
+ useEffect(() => {
8
+ const timer = setInterval(() => {
9
+ setFrame(f => (f + 1) % SPINNER_FRAMES.length);
10
+ }, 80);
11
+ return () => clearInterval(timer);
12
+ }, []);
13
+ return SPINNER_FRAMES[frame];
14
+ }
15
+ export function IndexProgress({ currentFile, current, total, done, stats, graphStats }) {
16
+ const spinner = useSpinner();
17
+ if (done && stats && graphStats) {
18
+ return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 Indexing complete" }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["Files: ", stats.totalFiles, " found, ", stats.parsedFiles, " parsed, ", stats.skippedFiles, " cached"] }), _jsxs(Text, { dimColor: true, children: ["Time: ", (stats.durationMs / 1000).toFixed(2), "s"] }), _jsxs(Text, { dimColor: true, children: ["Nodes: ", stats.nodes] }), _jsxs(Text, { dimColor: true, children: ["Edges: ", stats.edges] })] }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: [graphStats.Component !== undefined && _jsxs(Text, { children: [" Components: ", graphStats.Component] }), graphStats.Hook !== undefined && _jsxs(Text, { children: [" Hooks: ", graphStats.Hook] }), graphStats.Module !== undefined && _jsxs(Text, { children: [" Modules: ", graphStats.Module] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: "\u2192 .reactgraph/ai-context.md generated" }) })] }));
19
+ }
20
+ const pct = total > 0 ? Math.round((current / total) * 100) : 0;
21
+ const barWidth = 30;
22
+ const filled = Math.round((pct / 100) * barWidth);
23
+ const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
24
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: spinner }), _jsx(Text, { children: " Indexing... " }), _jsxs(Text, { dimColor: true, children: [current, "/", total] })] }), _jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: "cyan", children: bar }), _jsxs(Text, { children: [" ", pct, "%"] })] }), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: currentFile }) })] }));
25
+ }
26
+ //# sourceMappingURL=IndexProgress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IndexProgress.js","sourceRoot":"","sources":["../../../src/cli/components/IndexProgress.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE1E,SAAS,UAAU;IACjB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC,EAAE,EAAE,CAAC,CAAC;IACP,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAkBD,MAAM,UAAU,aAAa,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAsB;IACxG,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAE7B,IAAI,IAAI,IAAI,KAAK,IAAI,UAAU,EAAE,CAAC;QAChC,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAE,CAAC,aACvC,KAAC,IAAI,IAAC,KAAK,EAAC,OAAO,EAAC,IAAI,+CAA2B,EACnD,MAAC,GAAG,IAAC,WAAW,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,CAAC,aACtD,MAAC,IAAI,IAAC,QAAQ,+BAAU,KAAK,CAAC,UAAU,cAAU,KAAK,CAAC,WAAW,eAAW,KAAK,CAAC,YAAY,eAAe,EAC/G,MAAC,IAAI,IAAC,QAAQ,+BAAU,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EACrE,MAAC,IAAI,IAAC,QAAQ,+BAAU,KAAK,CAAC,KAAK,IAAQ,EAC3C,MAAC,IAAI,IAAC,QAAQ,+BAAU,KAAK,CAAC,KAAK,IAAQ,IACvC,EACN,MAAC,GAAG,IAAC,WAAW,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,CAAC,aACrD,UAAU,CAAC,SAAS,KAAK,SAAS,IAAI,MAAC,IAAI,iCAAgB,UAAU,CAAC,SAAS,IAAQ,EACvF,UAAU,CAAC,IAAI,KAAK,SAAS,IAAI,MAAC,IAAI,iCAAgB,UAAU,CAAC,IAAI,IAAQ,EAC7E,UAAU,CAAC,MAAM,KAAK,SAAS,IAAI,MAAC,IAAI,iCAAgB,UAAU,CAAC,MAAM,IAAQ,IAC9E,EACN,KAAC,GAAG,IAAC,SAAS,EAAE,CAAC,YACf,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,2DAA6C,GAC3D,IACF,CACP,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,CAAC;IAE/D,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,GAAG,eACF,KAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,YAAE,OAAO,GAAQ,EACrC,KAAC,IAAI,gCAAqB,EAC1B,MAAC,IAAI,IAAC,QAAQ,mBAAE,OAAO,OAAG,KAAK,IAAQ,IACnC,EACN,MAAC,GAAG,IAAC,WAAW,EAAE,CAAC,aACjB,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,YAAE,GAAG,GAAQ,EAC/B,MAAC,IAAI,oBAAG,GAAG,SAAS,IAChB,EACN,KAAC,GAAG,IAAC,WAAW,EAAE,CAAC,YACjB,KAAC,IAAI,IAAC,QAAQ,kBAAE,WAAW,GAAQ,GAC/B,IACF,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { InitResult as InitResultType } from '../init.js';
2
+ interface InitResultProps {
3
+ result: InitResultType;
4
+ }
5
+ export declare function InitResultView({ result }: InitResultProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=InitResult.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InitResult.d.ts","sourceRoot":"","sources":["../../../src/cli/components/InitResult.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,YAAY,CAAC;AAE/D,UAAU,eAAe;IACvB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,eAAe,2CAmBzD"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function InitResultView({ result }) {
4
+ return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsx(Text, { color: "green", bold: true, children: "\u2713 ReactGraph initialized" }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: ["Framework: ", _jsx(Text, { color: "cyan", children: result.framework })] }), _jsxs(Text, { children: ["Source dirs: ", _jsx(Text, { color: "cyan", children: result.srcDirs.join(', ') })] }), _jsxs(Text, { children: ["Config: ", _jsx(Text, { dimColor: true, children: ".reactgraph/config.json" })] }), result.gitignoreUpdated && (_jsx(Text, { dimColor: true, children: "Added .reactgraph/ to .gitignore" }))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "Next steps:" }), _jsxs(Text, { children: [" 1. Run ", _jsx(Text, { color: "cyan", bold: true, children: "reactgraph index" }), " to build the graph"] }), _jsxs(Text, { children: [" 2. Add to CLAUDE.md: ", _jsx(Text, { dimColor: true, children: "\"Read .reactgraph/ai-context.md at session start\"" })] })] })] }));
5
+ }
6
+ //# sourceMappingURL=InitResult.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InitResult.js","sourceRoot":"","sources":["../../../src/cli/components/InitResult.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAOhC,MAAM,UAAU,cAAc,CAAC,EAAE,MAAM,EAAmB;IACxD,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAE,CAAC,aACvC,KAAC,IAAI,IAAC,KAAK,EAAC,OAAO,EAAC,IAAI,oDAAgC,EACxD,MAAC,GAAG,IAAC,WAAW,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,EAAC,SAAS,EAAE,CAAC,aACtD,MAAC,IAAI,8BAAY,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,YAAE,MAAM,CAAC,SAAS,GAAQ,IAAO,EACpE,MAAC,IAAI,gCAAc,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,YAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAQ,IAAO,EAC/E,MAAC,IAAI,2BAAS,KAAC,IAAI,IAAC,QAAQ,8CAA+B,IAAO,EACjE,MAAM,CAAC,gBAAgB,IAAI,CAC1B,KAAC,IAAI,IAAC,QAAQ,uDAAwC,CACvD,IACG,EACN,MAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,aACvC,KAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,4BAAmB,EACvC,MAAC,IAAI,4BAAU,KAAC,IAAI,IAAC,KAAK,EAAC,MAAM,EAAC,IAAI,uCAAwB,2BAA0B,EACxF,MAAC,IAAI,0CAAwB,KAAC,IAAI,IAAC,QAAQ,0EAAyD,IAAO,IACvG,IACF,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type IndexStats } from '../parser/indexer.js';
2
+ export interface IndexResult {
3
+ stats: IndexStats;
4
+ graphStats: Record<string, number>;
5
+ }
6
+ export declare function runIndex(projectDir: string, onProgress?: (file: string, current: number, total: number) => void): Promise<IndexResult>;
7
+ //# sourceMappingURL=index-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-cmd.d.ts","sourceRoot":"","sources":["../../src/cli/index-cmd.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGrE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAClE,OAAO,CAAC,WAAW,CAAC,CActB"}
@@ -0,0 +1,28 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { indexProject } from '../parser/indexer.js';
4
+ import { generateAIContext } from '../output/ai-context.js';
5
+ export async function runIndex(projectDir, onProgress) {
6
+ // Load config
7
+ const config = loadConfig(projectDir);
8
+ // Run indexer
9
+ const { graph, stats } = await indexProject(projectDir, config.exclude, onProgress);
10
+ // Always generate ai-context.md
11
+ await generateAIContext(graph, projectDir);
12
+ return {
13
+ stats,
14
+ graphStats: graph.stats(),
15
+ };
16
+ }
17
+ function loadConfig(projectDir) {
18
+ const configPath = join(projectDir, '.reactgraph', 'config.json');
19
+ if (existsSync(configPath)) {
20
+ return JSON.parse(readFileSync(configPath, 'utf-8'));
21
+ }
22
+ return {
23
+ srcDirs: ['src'],
24
+ framework: 'unknown',
25
+ exclude: ['**/*.test.*', '**/*.spec.*'],
26
+ };
27
+ }
28
+ //# sourceMappingURL=index-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-cmd.js","sourceRoot":"","sources":["../../src/cli/index-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAmB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAO5D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,UAAkB,EAClB,UAAmE;IAEnE,cAAc;IACd,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAEtC,cAAc;IACd,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEpF,gCAAgC;IAChC,MAAM,iBAAiB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAE3C,OAAO;QACL,KAAK;QACL,UAAU,EAAE,KAAK,CAAC,KAAK,EAAE;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,UAAkB;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IAClE,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO;QACL,OAAO,EAAE,CAAC,KAAK,CAAC;QAChB,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { render } from 'ink';
4
+ import React from 'react';
5
+ import { runInit } from './init.js';
6
+ import { runIndex } from './index-cmd.js';
7
+ import { runServe } from './serve.js';
8
+ import { runUnused } from './unused.js';
9
+ import { InitResultView } from './components/InitResult.js';
10
+ import { IndexProgress } from './components/IndexProgress.js';
11
+ const program = new Command();
12
+ program
13
+ .name('reactgraph')
14
+ .description('Pre-computed React project graph for AI-assisted development')
15
+ .version('0.1.0');
16
+ program
17
+ .command('init')
18
+ .description('Initialize ReactGraph in the current project')
19
+ .action(() => {
20
+ const projectDir = process.cwd();
21
+ const result = runInit(projectDir);
22
+ const { unmount } = render(React.createElement(InitResultView, { result }));
23
+ setTimeout(() => unmount(), 100);
24
+ });
25
+ program
26
+ .command('index')
27
+ .description('Build the project graph')
28
+ .action(async () => {
29
+ const projectDir = process.cwd();
30
+ let currentFile = '';
31
+ let current = 0;
32
+ let total = 0;
33
+ let done = false;
34
+ let stats = null;
35
+ let graphStats = null;
36
+ const App = () => React.createElement(IndexProgress, {
37
+ currentFile,
38
+ current,
39
+ total,
40
+ done,
41
+ stats,
42
+ graphStats,
43
+ });
44
+ const { rerender, unmount } = render(React.createElement(App));
45
+ const onProgress = (file, cur, tot) => {
46
+ currentFile = file;
47
+ current = cur;
48
+ total = tot;
49
+ rerender(React.createElement(App));
50
+ };
51
+ try {
52
+ const result = await runIndex(projectDir, onProgress);
53
+ done = true;
54
+ stats = result.stats;
55
+ graphStats = result.graphStats;
56
+ rerender(React.createElement(App));
57
+ setTimeout(() => unmount(), 200);
58
+ }
59
+ catch (err) {
60
+ unmount();
61
+ console.error('Index failed:', err);
62
+ process.exit(1);
63
+ }
64
+ });
65
+ program
66
+ .command('serve')
67
+ .description('Start the MCP server')
68
+ .option('--watch', 'Watch for file changes and update graph incrementally')
69
+ .action(async (opts) => {
70
+ const projectDir = process.cwd();
71
+ await runServe(projectDir, opts.watch ?? false);
72
+ });
73
+ program
74
+ .command('unused')
75
+ .description('Find orphan components, unused hooks, and dead exports')
76
+ .action(async () => {
77
+ const projectDir = process.cwd();
78
+ await runUnused(projectDir);
79
+ });
80
+ program.parse();
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,8DAA8D,CAAC;KAC3E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,8CAA8C,CAAC;KAC3D,MAAM,CAAC,GAAG,EAAE;IACX,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5E,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAEjC,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,KAAK,GAAQ,IAAI,CAAC;IACtB,IAAI,UAAU,GAAQ,IAAI,CAAC;IAE3B,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,aAAa,EAAE;QACnD,WAAW;QACX,OAAO;QACP,KAAK;QACL,IAAI;QACJ,KAAK;QACL,UAAU;KACX,CAAC,CAAC;IAEH,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;IAE/D,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,GAAW,EAAE,EAAE;QAC5D,WAAW,GAAG,IAAI,CAAC;QACnB,OAAO,GAAG,GAAG,CAAC;QACd,KAAK,GAAG,GAAG,CAAC;QACZ,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACtD,IAAI,GAAG,IAAI,CAAC;QACZ,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QACrB,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAC/B,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;QACnC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,sBAAsB,CAAC;KACnC,MAAM,CAAC,SAAS,EAAE,uDAAuD,CAAC;KAC1E,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;IACrB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACjC,MAAM,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;AAClD,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,wDAAwD,CAAC;KACrE,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACjC,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface InitResult {
2
+ framework: string;
3
+ srcDirs: string[];
4
+ configCreated: boolean;
5
+ gitignoreUpdated: boolean;
6
+ }
7
+ export declare function runInit(projectDir: string): InitResult;
8
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAyBtD"}
@@ -0,0 +1,77 @@
1
+ import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export function runInit(projectDir) {
4
+ const framework = detectFramework(projectDir);
5
+ const srcDirs = detectSrcDirs(projectDir);
6
+ // Create .reactgraph/ directory
7
+ const rgDir = join(projectDir, '.reactgraph');
8
+ mkdirSync(rgDir, { recursive: true });
9
+ // Write config
10
+ const config = {
11
+ srcDirs,
12
+ framework,
13
+ exclude: ['**/*.test.*', '**/*.spec.*', '**/*.stories.*'],
14
+ };
15
+ writeFileSync(join(rgDir, 'config.json'), JSON.stringify(config, null, 2));
16
+ // Update .gitignore
17
+ const gitignoreUpdated = ensureGitignore(projectDir);
18
+ return {
19
+ framework,
20
+ srcDirs,
21
+ configCreated: true,
22
+ gitignoreUpdated,
23
+ };
24
+ }
25
+ function detectFramework(projectDir) {
26
+ // Next.js
27
+ if (existsSync(join(projectDir, 'next.config.js')) ||
28
+ existsSync(join(projectDir, 'next.config.mjs')) ||
29
+ existsSync(join(projectDir, 'next.config.ts'))) {
30
+ return 'next';
31
+ }
32
+ // Vite
33
+ if (existsSync(join(projectDir, 'vite.config.ts')) ||
34
+ existsSync(join(projectDir, 'vite.config.js'))) {
35
+ return 'vite';
36
+ }
37
+ // Remix
38
+ if (existsSync(join(projectDir, 'remix.config.js'))) {
39
+ return 'remix';
40
+ }
41
+ // CRA (react-scripts in package.json)
42
+ try {
43
+ const pkg = JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf-8'));
44
+ if (pkg.dependencies?.['react-scripts'] || pkg.devDependencies?.['react-scripts']) {
45
+ return 'cra';
46
+ }
47
+ }
48
+ catch {
49
+ // no package.json
50
+ }
51
+ return 'unknown';
52
+ }
53
+ function detectSrcDirs(projectDir) {
54
+ const candidates = ['src', 'app', 'pages', 'lib'];
55
+ const found = candidates.filter(d => existsSync(join(projectDir, d)));
56
+ return found.length > 0 ? found : ['.'];
57
+ }
58
+ function ensureGitignore(projectDir) {
59
+ const gitignorePath = join(projectDir, '.gitignore');
60
+ const entry = '.reactgraph/';
61
+ try {
62
+ if (existsSync(gitignorePath)) {
63
+ const content = readFileSync(gitignorePath, 'utf-8');
64
+ if (content.includes(entry))
65
+ return false;
66
+ appendFileSync(gitignorePath, `\n# ReactGraph cache\n${entry}\n`);
67
+ }
68
+ else {
69
+ writeFileSync(gitignorePath, `# ReactGraph cache\n${entry}\n`);
70
+ }
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function runServe(projectDir: string, watchMode?: boolean): Promise<void>;
2
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"AAIA,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB5F"}
@@ -0,0 +1,28 @@
1
+ import { loadGraph } from '../graph/serialize.js';
2
+ import { startServer } from '../mcp/server.js';
3
+ import { startWatcher } from '../watcher.js';
4
+ export async function runServe(projectDir, watchMode = false) {
5
+ if (watchMode) {
6
+ const graph = await loadGraph(projectDir);
7
+ if (!graph) {
8
+ console.error('No graph found. Run "reactgraph index" first.');
9
+ process.exit(1);
10
+ }
11
+ startWatcher({
12
+ projectDir,
13
+ graph,
14
+ onUpdate: (file, stats) => {
15
+ process.stderr.write(`[reactgraph] Updated: ${file} (${stats.nodes} nodes, ${stats.edges} edges)\n`);
16
+ },
17
+ onError: (file, err) => {
18
+ process.stderr.write(`[reactgraph] Error parsing ${file}: ${err.message}\n`);
19
+ },
20
+ });
21
+ // Start MCP server with the live graph
22
+ await startServer(projectDir, graph);
23
+ }
24
+ else {
25
+ await startServer(projectDir);
26
+ }
27
+ }
28
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB,EAAE,YAAqB,KAAK;IAC3E,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,YAAY,CAAC;YACX,UAAU;YACV,KAAK;YACL,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,IAAI,KAAK,KAAK,CAAC,KAAK,WAAW,KAAK,CAAC,KAAK,WAAW,CAAC,CAAC;YACvG,CAAC;YACD,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,IAAI,KAAK,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/E,CAAC;SACF,CAAC,CAAC;QAEH,uCAAuC;QACvC,MAAM,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function runUnused(projectDir: string): Promise<void>;
2
+ //# sourceMappingURL=unused.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unused.d.ts","sourceRoot":"","sources":["../../src/cli/unused.ts"],"names":[],"mappings":"AAOA,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDjE"}
@@ -0,0 +1,56 @@
1
+ import { render } from 'ink';
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { loadGraph } from '../graph/serialize.js';
5
+ export async function runUnused(projectDir) {
6
+ const graph = await loadGraph(projectDir);
7
+ if (!graph) {
8
+ console.error('No graph found. Run "reactgraph index" first.');
9
+ process.exit(1);
10
+ }
11
+ const orphanComponents = findOrphanComponents(graph);
12
+ const unusedHooks = findUnusedHooks(graph);
13
+ const unusedExports = findUnusedExports(graph);
14
+ const App = () => React.createElement(Box, { flexDirection: 'column', paddingTop: 1 }, React.createElement(Text, { bold: true }, 'ReactGraph — Unused Analysis'), React.createElement(Text, null, ''),
15
+ // Orphan components
16
+ orphanComponents.length > 0
17
+ ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: 'yellow' }, `Orphan components (${orphanComponents.length}) — rendered by nothing:`), ...orphanComponents.map(c => React.createElement(Text, { key: c.id, dimColor: true }, ` ${c.name} [${c.file}:${c.line}]`)))
18
+ : React.createElement(Text, { color: 'green' }, '✓ No orphan components'), React.createElement(Text, null, ''),
19
+ // Unused hooks
20
+ unusedHooks.length > 0
21
+ ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: 'yellow' }, `Unused hooks (${unusedHooks.length}) — never called:`), ...unusedHooks.map(h => React.createElement(Text, { key: h.id, dimColor: true }, ` ${h.name} [${h.file}:${h.line}]`)))
22
+ : React.createElement(Text, { color: 'green' }, '✓ No unused hooks'), React.createElement(Text, null, ''),
23
+ // Unused exports
24
+ unusedExports.length > 0
25
+ ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { color: 'yellow' }, `Unused exports (${unusedExports.length}) — never imported:`), ...unusedExports.map(e => React.createElement(Text, { key: e.id, dimColor: true }, ` ${e.name} [${e.file}:${e.line}]`)))
26
+ : React.createElement(Text, { color: 'green' }, '✓ No unused exports'));
27
+ const { unmount } = render(React.createElement(App));
28
+ setTimeout(() => unmount(), 100);
29
+ }
30
+ function findOrphanComponents(graph) {
31
+ return graph.getNodesByKind('Component').filter(c => {
32
+ // A component is orphan if nothing renders it and it's not a route target
33
+ const renderedBy = graph.getEdgesTo(c.id, ['renders']);
34
+ return renderedBy.length === 0;
35
+ });
36
+ }
37
+ function findUnusedHooks(graph) {
38
+ return graph.getNodesByKind('Hook').filter(h => {
39
+ const usedBy = graph.getEdgesTo(h.id, ['uses_hook']);
40
+ return usedBy.length === 0;
41
+ });
42
+ }
43
+ function findUnusedExports(graph) {
44
+ // Find exported nodes that are never imported by anything
45
+ const allNodes = graph.getAllNodes();
46
+ return allNodes.filter(n => {
47
+ if (n.exportType === 'none')
48
+ return false;
49
+ if (n.kind === 'Module')
50
+ return false; // Modules themselves aren't "used"
51
+ // Check if any edge points to this node
52
+ const incoming = graph.getEdgesTo(n.id);
53
+ return incoming.length === 0;
54
+ });
55
+ }
56
+ //# sourceMappingURL=unused.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unused.js","sourceRoot":"","sources":["../../src/cli/unused.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { GraphNode, GraphEdge, NodeKind, EdgeKind } from './schema.js';
2
+ export declare class ReactGraph {
3
+ private nodes;
4
+ private edges;
5
+ private byKind;
6
+ private byFile;
7
+ private byName;
8
+ private edgesBySource;
9
+ private edgesByTarget;
10
+ private edgesByKind;
11
+ addNode(node: GraphNode): void;
12
+ addEdge(edge: GraphEdge): void;
13
+ getNode(id: string): GraphNode | undefined;
14
+ getAllNodes(): GraphNode[];
15
+ getAllEdges(): GraphEdge[];
16
+ getNodesByKind(kind: NodeKind): GraphNode[];
17
+ getNodesByFile(file: string): GraphNode[];
18
+ getNodesByName(name: string): GraphNode[];
19
+ getEdgesFrom(nodeId: string, kinds?: EdgeKind[]): GraphEdge[];
20
+ getEdgesTo(nodeId: string, kinds?: EdgeKind[]): GraphEdge[];
21
+ getEdgesByKind(kind: EdgeKind): GraphEdge[];
22
+ removeNodesByFile(file: string): void;
23
+ clear(): void;
24
+ /** Find a node by name with fuzzy matching: exact → case-insensitive → partial */
25
+ findNode(name: string): GraphNode | undefined;
26
+ /** Count of in+out edges for a node */
27
+ connectivity(nodeId: string): number;
28
+ stats(): Record<string, number>;
29
+ }
30
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/graph/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5E,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,KAAK,CAAmB;IAGhC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,WAAW,CAAoC;IAEvD,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAgB9B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAa9B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAI1C,WAAW,IAAI,SAAS,EAAE;IAI1B,WAAW,IAAI,SAAS,EAAE;IAI1B,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE;IAM3C,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE;IAMzC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE;IAMzC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE;IAM7D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE;IAM3D,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE;IAI3C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAyCrC,KAAK,IAAI,IAAI;IAWb,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAmB7C,uCAAuC;IACvC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAKpC,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAWhC"}
@@ -0,0 +1,166 @@
1
+ export class ReactGraph {
2
+ nodes = new Map();
3
+ edges = [];
4
+ // Secondary indexes
5
+ byKind = new Map();
6
+ byFile = new Map();
7
+ byName = new Map();
8
+ edgesBySource = new Map();
9
+ edgesByTarget = new Map();
10
+ edgesByKind = new Map();
11
+ addNode(node) {
12
+ this.nodes.set(node.id, node);
13
+ // Index by kind
14
+ if (!this.byKind.has(node.kind))
15
+ this.byKind.set(node.kind, new Set());
16
+ this.byKind.get(node.kind).add(node.id);
17
+ // Index by file
18
+ if (!this.byFile.has(node.file))
19
+ this.byFile.set(node.file, new Set());
20
+ this.byFile.get(node.file).add(node.id);
21
+ // Index by name
22
+ if (!this.byName.has(node.name))
23
+ this.byName.set(node.name, []);
24
+ this.byName.get(node.name).push(node.id);
25
+ }
26
+ addEdge(edge) {
27
+ this.edges.push(edge);
28
+ if (!this.edgesBySource.has(edge.source))
29
+ this.edgesBySource.set(edge.source, []);
30
+ this.edgesBySource.get(edge.source).push(edge);
31
+ if (!this.edgesByTarget.has(edge.target))
32
+ this.edgesByTarget.set(edge.target, []);
33
+ this.edgesByTarget.get(edge.target).push(edge);
34
+ if (!this.edgesByKind.has(edge.kind))
35
+ this.edgesByKind.set(edge.kind, []);
36
+ this.edgesByKind.get(edge.kind).push(edge);
37
+ }
38
+ getNode(id) {
39
+ return this.nodes.get(id);
40
+ }
41
+ getAllNodes() {
42
+ return Array.from(this.nodes.values());
43
+ }
44
+ getAllEdges() {
45
+ return this.edges;
46
+ }
47
+ getNodesByKind(kind) {
48
+ const ids = this.byKind.get(kind);
49
+ if (!ids)
50
+ return [];
51
+ return Array.from(ids).map(id => this.nodes.get(id));
52
+ }
53
+ getNodesByFile(file) {
54
+ const ids = this.byFile.get(file);
55
+ if (!ids)
56
+ return [];
57
+ return Array.from(ids).map(id => this.nodes.get(id));
58
+ }
59
+ getNodesByName(name) {
60
+ const ids = this.byName.get(name);
61
+ if (!ids)
62
+ return [];
63
+ return ids.map(id => this.nodes.get(id));
64
+ }
65
+ getEdgesFrom(nodeId, kinds) {
66
+ const edges = this.edgesBySource.get(nodeId) ?? [];
67
+ if (!kinds)
68
+ return edges;
69
+ return edges.filter(e => kinds.includes(e.kind));
70
+ }
71
+ getEdgesTo(nodeId, kinds) {
72
+ const edges = this.edgesByTarget.get(nodeId) ?? [];
73
+ if (!kinds)
74
+ return edges;
75
+ return edges.filter(e => kinds.includes(e.kind));
76
+ }
77
+ getEdgesByKind(kind) {
78
+ return this.edgesByKind.get(kind) ?? [];
79
+ }
80
+ removeNodesByFile(file) {
81
+ const ids = this.byFile.get(file);
82
+ if (!ids)
83
+ return;
84
+ // Remove edges referencing these nodes
85
+ const idSet = new Set(ids);
86
+ this.edges = this.edges.filter(e => !idSet.has(e.source) && !idSet.has(e.target));
87
+ // Rebuild edge indexes
88
+ this.edgesBySource.clear();
89
+ this.edgesByTarget.clear();
90
+ this.edgesByKind.clear();
91
+ for (const edge of this.edges) {
92
+ if (!this.edgesBySource.has(edge.source))
93
+ this.edgesBySource.set(edge.source, []);
94
+ this.edgesBySource.get(edge.source).push(edge);
95
+ if (!this.edgesByTarget.has(edge.target))
96
+ this.edgesByTarget.set(edge.target, []);
97
+ this.edgesByTarget.get(edge.target).push(edge);
98
+ if (!this.edgesByKind.has(edge.kind))
99
+ this.edgesByKind.set(edge.kind, []);
100
+ this.edgesByKind.get(edge.kind).push(edge);
101
+ }
102
+ // Remove nodes
103
+ for (const id of ids) {
104
+ const node = this.nodes.get(id);
105
+ this.nodes.delete(id);
106
+ // Clean kind index
107
+ this.byKind.get(node.kind)?.delete(id);
108
+ // Clean name index
109
+ const nameIds = this.byName.get(node.name);
110
+ if (nameIds) {
111
+ const idx = nameIds.indexOf(id);
112
+ if (idx >= 0)
113
+ nameIds.splice(idx, 1);
114
+ if (nameIds.length === 0)
115
+ this.byName.delete(node.name);
116
+ }
117
+ }
118
+ this.byFile.delete(file);
119
+ }
120
+ clear() {
121
+ this.nodes.clear();
122
+ this.edges = [];
123
+ this.byKind.clear();
124
+ this.byFile.clear();
125
+ this.byName.clear();
126
+ this.edgesBySource.clear();
127
+ this.edgesByTarget.clear();
128
+ this.edgesByKind.clear();
129
+ }
130
+ /** Find a node by name with fuzzy matching: exact → case-insensitive → partial */
131
+ findNode(name) {
132
+ // Exact match
133
+ const exact = this.byName.get(name);
134
+ if (exact?.length)
135
+ return this.nodes.get(exact[0]);
136
+ // Case-insensitive
137
+ const lower = name.toLowerCase();
138
+ for (const [key, ids] of this.byName) {
139
+ if (key.toLowerCase() === lower && ids.length)
140
+ return this.nodes.get(ids[0]);
141
+ }
142
+ // Partial match
143
+ for (const [key, ids] of this.byName) {
144
+ if (key.toLowerCase().includes(lower) && ids.length)
145
+ return this.nodes.get(ids[0]);
146
+ }
147
+ return undefined;
148
+ }
149
+ /** Count of in+out edges for a node */
150
+ connectivity(nodeId) {
151
+ return (this.edgesBySource.get(nodeId)?.length ?? 0) +
152
+ (this.edgesByTarget.get(nodeId)?.length ?? 0);
153
+ }
154
+ stats() {
155
+ const s = {
156
+ totalNodes: this.nodes.size,
157
+ totalEdges: this.edges.length,
158
+ files: this.byFile.size,
159
+ };
160
+ for (const [kind, ids] of this.byKind) {
161
+ s[kind] = ids.size;
162
+ }
163
+ return s;
164
+ }
165
+ }
166
+ //# sourceMappingURL=graph.js.map