project-graph-mcp 2.3.2 → 2.4.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 (279) hide show
  1. package/package.json +3 -2
  2. package/src/analysis/analysis-cache.ctx +9 -0
  3. package/src/analysis/analysis-cache.js +1 -1
  4. package/src/analysis/complexity.ctx +6 -0
  5. package/src/analysis/complexity.js +1 -1
  6. package/src/analysis/custom-rules.ctx +14 -0
  7. package/src/analysis/custom-rules.js +1 -1
  8. package/src/analysis/db-analysis.ctx +7 -0
  9. package/src/analysis/db-analysis.js +1 -1
  10. package/src/analysis/dead-code.ctx +6 -0
  11. package/src/analysis/dead-code.js +1 -1
  12. package/src/analysis/full-analysis.ctx +9 -0
  13. package/src/analysis/full-analysis.js +1 -1
  14. package/src/analysis/jsdoc-checker.ctx +10 -0
  15. package/src/analysis/jsdoc-checker.js +1 -1
  16. package/src/analysis/jsdoc-generator.ctx +9 -0
  17. package/src/analysis/jsdoc-generator.js +1 -1
  18. package/src/analysis/large-files.ctx +6 -0
  19. package/src/analysis/large-files.js +1 -1
  20. package/src/analysis/outdated-patterns.ctx +7 -0
  21. package/src/analysis/outdated-patterns.js +1 -1
  22. package/src/analysis/similar-functions.ctx +6 -0
  23. package/src/analysis/similar-functions.js +1 -1
  24. package/src/analysis/test-annotations.ctx +11 -0
  25. package/src/analysis/test-annotations.js +1 -1
  26. package/src/analysis/type-checker.ctx +6 -0
  27. package/src/analysis/type-checker.js +1 -1
  28. package/src/analysis/undocumented.ctx +8 -0
  29. package/src/analysis/undocumented.js +1 -1
  30. package/src/cli/cli-handlers.ctx +7 -0
  31. package/src/cli/cli-handlers.js +1 -1
  32. package/src/cli/cli.ctx +6 -0
  33. package/src/cli/cli.js +1 -1
  34. package/src/compact/ai-context.ctx +6 -0
  35. package/src/compact/ai-context.js +1 -1
  36. package/src/compact/compact-migrate.ctx +8 -0
  37. package/src/compact/compact-migrate.js +1 -1
  38. package/src/compact/compact.ctx +11 -0
  39. package/src/compact/compact.js +1 -1
  40. package/src/compact/compress.ctx +7 -0
  41. package/src/compact/compress.js +1 -1
  42. package/src/compact/ctx-resolver.ctx +2 -0
  43. package/src/compact/ctx-resolver.js +1 -1
  44. package/src/compact/ctx-to-jsdoc.ctx +11 -0
  45. package/src/compact/ctx-to-jsdoc.js +1 -1
  46. package/src/compact/doc-dialect.ctx +11 -0
  47. package/src/compact/doc-dialect.js +2 -2
  48. package/src/compact/expand.ctx +14 -0
  49. package/src/compact/expand.js +1 -1
  50. package/src/compact/framework-references.ctx +7 -0
  51. package/src/compact/framework-references.js +1 -1
  52. package/src/compact/instructions.ctx +6 -0
  53. package/src/compact/instructions.js +1 -1
  54. package/src/compact/jsdoc-builder.ctx +4 -0
  55. package/src/compact/jsdoc-builder.js +1 -1
  56. package/src/compact/mode-config.ctx +8 -0
  57. package/src/compact/mode-config.js +1 -1
  58. package/src/compact/split-declarations.ctx +6 -0
  59. package/src/compact/split-declarations.js +1 -1
  60. package/src/compact/validate-pipeline.ctx +12 -0
  61. package/src/compact/validate-pipeline.js +1 -1
  62. package/src/core/event-bus.ctx +9 -0
  63. package/src/core/event-bus.js +1 -1
  64. package/src/core/file-walker.ctx +1 -0
  65. package/src/core/file-walker.js +1 -1
  66. package/src/core/filters.ctx +12 -0
  67. package/src/core/filters.js +1 -1
  68. package/src/core/graph-builder.ctx +7 -0
  69. package/src/core/graph-builder.js +1 -1
  70. package/src/core/parser.ctx +12 -0
  71. package/src/core/parser.js +1 -1
  72. package/src/core/utils.ctx +1 -0
  73. package/src/core/utils.js +1 -1
  74. package/src/core/workspace.ctx +7 -0
  75. package/src/core/workspace.js +1 -1
  76. package/src/lang/lang-go.ctx +8 -0
  77. package/src/lang/lang-go.js +1 -1
  78. package/src/lang/lang-python.ctx +5 -0
  79. package/src/lang/lang-python.js +1 -1
  80. package/src/lang/lang-sql.ctx +10 -0
  81. package/src/lang/lang-sql.js +1 -1
  82. package/src/lang/lang-typescript.ctx +6 -0
  83. package/src/lang/lang-typescript.js +1 -1
  84. package/src/lang/lang-utils.ctx +5 -0
  85. package/src/lang/lang-utils.js +1 -1
  86. package/src/mcp/mcp-server.ctx +6 -0
  87. package/src/mcp/mcp-server.js +1 -1
  88. package/src/mcp/tool-defs.ctx +2 -0
  89. package/src/mcp/tool-defs.js +1 -1
  90. package/src/mcp/tools.ctx +13 -0
  91. package/src/mcp/tools.js +1 -1
  92. package/src/network/backend-lifecycle.ctx +10 -0
  93. package/src/network/backend-lifecycle.js +1 -1
  94. package/src/network/backend.ctx +5 -0
  95. package/src/network/backend.js +1 -1
  96. package/src/network/local-gateway.ctx +9 -0
  97. package/src/network/local-gateway.js +1 -1
  98. package/src/network/mdns.ctx +6 -0
  99. package/src/network/mdns.js +1 -1
  100. package/src/network/server.ctx +2 -0
  101. package/src/network/server.js +2 -2
  102. package/src/network/web-server.ctx +17 -0
  103. package/src/network/web-server.js +2 -2
  104. package/web/follow-controller.js +94 -25
  105. package/web/panels/dep-graph.js +207 -21
  106. package/project-graph-mcp-2.3.0.tgz +0 -0
  107. package/vendor/symbiote-node/CHANGELOG.md +0 -31
  108. package/vendor/symbiote-node/LICENSE +0 -21
  109. package/vendor/symbiote-node/README.md +0 -206
  110. package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
  111. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
  112. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
  113. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
  114. package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
  115. package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
  116. package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
  117. package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
  118. package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
  119. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
  120. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
  121. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
  122. package/vendor/symbiote-node/canvas/LODManager.js +0 -88
  123. package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
  124. package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
  125. package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
  126. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
  127. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
  128. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
  129. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
  130. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
  131. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
  132. package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
  133. package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
  134. package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
  135. package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
  136. package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
  137. package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
  138. package/vendor/symbiote-node/core/Connection.js +0 -45
  139. package/vendor/symbiote-node/core/Editor.js +0 -451
  140. package/vendor/symbiote-node/core/Frame.js +0 -31
  141. package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
  142. package/vendor/symbiote-node/core/GraphText.js +0 -210
  143. package/vendor/symbiote-node/core/Node.js +0 -143
  144. package/vendor/symbiote-node/core/Portal.js +0 -104
  145. package/vendor/symbiote-node/core/Socket.js +0 -185
  146. package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
  147. package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
  148. package/vendor/symbiote-node/engine/Executor.js +0 -371
  149. package/vendor/symbiote-node/engine/Graph.js +0 -314
  150. package/vendor/symbiote-node/engine/GraphServer.js +0 -353
  151. package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
  152. package/vendor/symbiote-node/engine/History.js +0 -83
  153. package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
  154. package/vendor/symbiote-node/engine/Persistence.js +0 -84
  155. package/vendor/symbiote-node/engine/Registry.js +0 -264
  156. package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
  157. package/vendor/symbiote-node/engine/cli.js +0 -404
  158. package/vendor/symbiote-node/engine/index.js +0 -56
  159. package/vendor/symbiote-node/engine/nanoid.js +0 -28
  160. package/vendor/symbiote-node/engine/package.json +0 -26
  161. package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
  162. package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
  163. package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
  164. package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
  165. package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
  166. package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
  167. package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
  168. package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
  169. package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
  170. package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
  171. package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
  172. package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
  173. package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
  174. package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
  175. package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
  176. package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
  177. package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
  178. package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
  179. package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
  180. package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
  181. package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
  182. package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
  183. package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
  184. package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
  185. package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
  186. package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
  187. package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
  188. package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
  189. package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
  190. package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
  191. package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
  192. package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
  193. package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
  194. package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
  195. package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
  196. package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
  197. package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
  198. package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
  199. package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
  200. package/vendor/symbiote-node/index.js +0 -103
  201. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
  202. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
  203. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
  204. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
  205. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
  206. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
  207. package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
  208. package/vendor/symbiote-node/interactions/Drag.js +0 -102
  209. package/vendor/symbiote-node/interactions/Selector.js +0 -132
  210. package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
  211. package/vendor/symbiote-node/interactions/Zoom.js +0 -140
  212. package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
  213. package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
  214. package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
  215. package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
  216. package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
  217. package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
  218. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
  219. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
  220. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
  221. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
  222. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
  223. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
  224. package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
  225. package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
  226. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
  227. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
  228. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
  229. package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
  230. package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
  231. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
  232. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
  233. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
  234. package/vendor/symbiote-node/layout/index.js +0 -16
  235. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
  236. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
  237. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
  238. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
  239. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
  240. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
  241. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
  242. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
  243. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
  244. package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
  245. package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
  246. package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
  247. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
  248. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
  249. package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
  250. package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
  251. package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
  252. package/vendor/symbiote-node/package.json +0 -59
  253. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
  254. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
  255. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
  256. package/vendor/symbiote-node/plugins/History.js +0 -384
  257. package/vendor/symbiote-node/plugins/Readonly.js +0 -59
  258. package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
  259. package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
  260. package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
  261. package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
  262. package/vendor/symbiote-node/shapes/PillShape.js +0 -91
  263. package/vendor/symbiote-node/shapes/RectShape.js +0 -72
  264. package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
  265. package/vendor/symbiote-node/shapes/index.js +0 -53
  266. package/vendor/symbiote-node/themes/Palette.js +0 -32
  267. package/vendor/symbiote-node/themes/Skin.js +0 -113
  268. package/vendor/symbiote-node/themes/Theme.js +0 -84
  269. package/vendor/symbiote-node/themes/carbon.js +0 -137
  270. package/vendor/symbiote-node/themes/dark.js +0 -137
  271. package/vendor/symbiote-node/themes/ebook.js +0 -138
  272. package/vendor/symbiote-node/themes/grey.js +0 -137
  273. package/vendor/symbiote-node/themes/light.js +0 -137
  274. package/vendor/symbiote-node/themes/neon.js +0 -138
  275. package/vendor/symbiote-node/themes/pcb.js +0 -273
  276. package/vendor/symbiote-node/themes/synthwave.js +0 -137
  277. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
  278. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
  279. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-graph-mcp",
3
- "version": "2.3.2",
3
+ "version": "2.4.1",
4
4
  "type": "module",
5
5
  "description": "MCP server for AI agents — project graph, code quality analysis, visual web explorer. JS, TS, Python, Go.",
6
6
  "main": "src/network/server.js",
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "scripts": {
14
14
  "start": "node src/network/server.js",
15
- "test": "node --test tests/*.test.js"
15
+ "test": "node --test tests/*.test.js",
16
+ "test:consumer": "node scripts/consumer-test.mjs"
16
17
  },
17
18
  "keywords": [
18
19
  "mcp",
@@ -0,0 +1,9 @@
1
+ --- src/analysis/analysis-cache.js ---
2
+ @sig 3e2247a9
3
+ export computeContentHash(content:string)→i|compute full source code hash|for body-dependent metrics
4
+ export getCachePath(rootDir:string,key:string)→e.replace,c|get cache file path for source file|.context/.cache/path.json
5
+ export readCache(rootDir:string,key:string)→getCachePath,r,t,JSON.parse|read cached analysis file|returns CacheEntry or null
6
+ export writeCache(rootDir:string,key:string,data:Object)→getCachePath,a,n,JSON.stringify,e|write analysis cache entry|includes timestamp
7
+ export isCacheValid(rootDir:string,key:string,sourceFile:string,maxAge:number=)|check if cache is valid|supports interface or content hash level
8
+ PATTERNS: dual-hashing (sig vs content)|persistent file-based cache
9
+ EDGE_CASES: ignores read/write cache errors|handles missing directories
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/analysis-cache.ctx
1
+ // @ctx analysis-cache.ctx
2
2
  import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as r}from"fs";
3
3
  import{join as c,dirname as a}from"path";
4
4
  import{createHash as i}from"crypto";
@@ -0,0 +1,6 @@
1
+ --- src/analysis/complexity.js ---
2
+ @sig 9df5918d
3
+ export analyzeComplexityFile(filePath:string,code:string)→a,u,h,o.push,l.simple|analyze complexity for all functions in a single file
4
+ export getComplexity(path:string,options:Object=)→i,p,f,l.push,l.filter,l.sort|scan directory, filter and sort complexity results
5
+ PATTERNS: AST tree walking via Acorn|cyclomatic complexity counting
6
+ EDGE_CASES: parses arrow functions with blocks|ignores unparseable files
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/complexity.ctx
1
+ // @ctx complexity.ctx
2
2
  import{readFileSync as t,readdirSync as e,statSync as o}from"fs";import{join as n,relative as r,resolve as i}from"path";import{parse as a}from"../../vendor/acorn.mjs";
3
3
  import*as l from"../../vendor/walk.mjs";
4
4
  import{shouldExcludeDir as s,shouldExcludeFile as c,parseGitignore as m}from"../core/filters.js";
@@ -0,0 +1,14 @@
1
+ --- src/analysis/custom-rules.js ---
2
+ @sig 683d58c0
3
+ b(t,s,n)→s.replace,e.endsWith,e,c,e.exec,j|(internal utility)
4
+ export getCustomRules()→x,Object.entries,rules.map|load and return summary of all custom rules
5
+ export setCustomRule(ruleSet:string,rule:Object)→x,rules.findIndex,rules.push,v|add or update a single rule in a ruleset
6
+ export deleteCustomRule(ruleSet:string,ruleId:string)→x,rules.findIndex,rules.splice,v|remove a rule from a ruleset by ID
7
+ export detectProjectRuleSets(path:string)→x,o,n,e,JSON.parse,Object.keys|auto-detect applicable rulesets from package.json and imports
8
+ export checkCustomRules(path:string,options:Object=)→l,x,detectProjectRuleSets,r.push,Object.entries,detected.includes|run relevant custom rules against project directory
9
+ PATTERNS: auto-detection via dependencies and imports|regex code scanning
10
+ EDGE_CASES: state machine ignores matches inside strings/comments
11
+ y(a)|parseGraphignore — internal helper
12
+ g(a)|isGraphignored — internal helper
13
+ $(a,b,c)|findFiles — internal helper
14
+ S(a,b,c)|isInStringOrComment — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/custom-rules.ctx
1
+ // @ctx custom-rules.ctx
2
2
  import{readFileSync as e,writeFileSync as t,readdirSync as s,existsSync as n,statSync as r}from"fs";import{join as o,relative as c,dirname as i,resolve as l}from"path";import{fileURLToPath as u}from"url";import{shouldExcludeDir as f,shouldExcludeFile as a,parseGitignore as d}from"../core/filters.js";
3
3
  const p=i(u(import.meta.url)),h=o(p,"..","..","rules");
4
4
  let m=[];
@@ -0,0 +1,7 @@
1
+ --- src/analysis/db-analysis.js ---
2
+ @sig 5ff5551d
3
+ export getDBSchema(path:string)→e,a.map,a.reduce|read parsed project to list all defined SQL tables and columns
4
+ export getTableUsage(path:string,table:string)→e,t,readers.some,readers.push,writers.some,writers.push|find readers and writers for tables using parsed graph edges
5
+ export getDBDeadTables(path:string)→e,t,r.add,r.has,o.filter,t.add|find schema tables not referenced by any read/write in code
6
+ PATTERNS: dependency graph analysis|heuristic DB column extraction
7
+ EDGE_CASES: hardcoded standard columns prevent false positive dead tables
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/db-analysis.ctx
1
+ // @ctx db-analysis.ctx
2
2
  import{parseProject as e}from"../core/parser.js";import{buildGraph as t}from"../core/graph-builder.js";
3
3
  export async function getDBSchema(t){const a=(await e(t)).tables||[];return{tables:a.map(e=>({name:e.name,columns:e.columns,file:e.file,line:e.line})),totalTables:a.length,totalColumns:a.reduce((e,t)=>e+t.columns.length,0)}}
4
4
  export async function getTableUsage(a,s){const n=await e(a),o=t(n),r={};for(const[e,t,a]of o.edges){if("R→"!==t&&"W→"!==t)continue;const n=a;if(s&&n!==s)continue;r[n]||(r[n]={readers:[],writers:[]});const l=o.reverseLegend[e]||e,d=o.nodes[e],i={name:l,file:d?.f||"?"};"R→"===t?r[n].readers.some(e=>e.name===l)||r[n].readers.push(i):r[n].writers.some(e=>e.name===l)||r[n].writers.push(i)}const l=Object.entries(r).map(([e,t])=>({table:e,readers:t.readers,writers:t.writers,totalReaders:t.readers.length,totalWriters:t.writers.length})).sort((e,t)=>t.totalReaders+t.totalWriters-(e.totalReaders+e.totalWriters));return{tables:l,totalTables:l.length,totalQueries:l.reduce((e,t)=>e+t.totalReaders+t.totalWriters,0)}}
@@ -0,0 +1,6 @@
1
+ --- src/analysis/dead-code.js ---
2
+ @sig 36cbbeca
3
+ export getDeadCode(path:string)→i,m,u,e,a,h|scans dir for unused fns, classes, exports, vars, imports
4
+ PATTERNS: AST traversal|cross-file dep tracking|heuristic usage regex
5
+ EDGE_CASES: syntax err→skip file|test/css/tpl files→ignored
6
+ y(a)|analyzeFileLocals — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/dead-code.ctx
1
+ // @ctx dead-code.ctx
2
2
  import{readFileSync as e,readdirSync as t,statSync as n,existsSync as s}from"fs";import{join as o,relative as a,resolve as i,dirname as r}from"path";import{parse as c}from"../../vendor/acorn.mjs";
3
3
  import*as l from"../../vendor/walk.mjs";
4
4
  import{shouldExcludeDir as d,shouldExcludeFile as f,parseGitignore as p}from"../core/filters.js";
@@ -0,0 +1,9 @@
1
+ --- src/analysis/full-analysis.js ---
2
+ @sig df2774af
3
+ M(t)→t.filter,t.slice|(internal utility)
4
+ export getFullAnalysis(path:string,options:Object=)→n,C,a,S,D,M|run full code health analysis|combines cached and dynamic cross-file metrics
5
+ export getAnalysisSummaryOnly(path:string)→C,a,S,D,M,P|get partial fast health score skipping cross-file analyses
6
+ PATTERNS: incremental caching for per-file metrics|score aggregation with clamping
7
+ EDGE_CASES: cross-file metrics always dynamic|handles missing cache gracefully
8
+ w(a)|calculateHealthScore — internal helper
9
+ F(a,b)|findJSFiles — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/full-analysis.ctx
1
+ // @ctx full-analysis.ctx
2
2
  import{readFileSync as t,readdirSync as e,statSync as s}from"fs";import{join as a,relative as o,resolve as n}from"path";import{getDeadCode as i}from"./dead-code.js";import{checkUndocumentedFile as r}from"./undocumented.js";import{getSimilarFunctions as l}from"./similar-functions.js";import{analyzeComplexityFile as c}from"./complexity.js";import{getLargeFiles as d}from"./large-files.js";import{getOutdatedPatterns as u}from"./outdated-patterns.js";import{getTableUsage as m}from"./db-analysis.js";import{checkJSDocFile as h}from"./jsdoc-checker.js";import{readCache as p,writeCache as y,computeContentHash as g,isCacheValid as f}from"./analysis-cache.js";import{shouldExcludeDir as j,shouldExcludeFile as x,parseGitignore as b}from"../core/filters.js";import{getWorkspaceRoot as C}from"../core/workspace.js";
3
3
  function w(t){let e=100;const s=[];e-=Math.min(2*t.deadCode.total,20),t.deadCode.total>0&&s.push(`${t.deadCode.total} unused functions/classes`),e-=Math.min(.5*t.undocumented.total,15),t.undocumented.total>10&&s.push(`${t.undocumented.total} undocumented items`),e-=Math.min(3*t.similar.total,15),t.similar.total>0&&s.push(`${t.similar.total} similar function pairs`);const a=t.complexity.stats?.critical||0,o=t.complexity.stats?.high||0;e-=Math.min(5*a+2*o,20),a>0&&s.push(`${a} critical complexity functions`);const n=t.largeFiles.stats?.critical||0,i=t.largeFiles.stats?.warning||0;e-=Math.min(4*n+1*i,10),n>0&&s.push(`${n} files need splitting`);const r=t.outdated.stats?.bySeverity?.error||0,l=t.outdated.stats?.bySeverity?.warning||0;if(e-=Math.min(3*r+1*l,10),t.outdated.redundantDeps?.length>0&&s.push(`${t.outdated.redundantDeps.length} redundant npm dependencies`),t.jsdocConsistency){const a=t.jsdocConsistency.errors||0,o=t.jsdocConsistency.warnings||0;e-=Math.min(2*a+1*o,15),a>0&&s.push(`${a} JSDoc consistency errors`)}let c;return e=Math.max(0,Math.min(100,Math.round(e))),c=e>=90?"excellent":e>=70?"good":e>=50?"warning":"critical",{score:e,rating:c,topIssues:s.slice(0,5)}}
4
4
  function F(t,n=t){t===n&&b(n);const i=[];try{for(const r of e(t)){const e=a(t,r),l=o(n,e);s(e).isDirectory()?j(r,l)||i.push(...F(e,n)):!r.endsWith(".js")||r.endsWith(".css.js")||r.endsWith(".tpl.js")||x(r,l)||i.push(e)}}catch(t){}return i}
@@ -0,0 +1,10 @@
1
+ --- src/analysis/jsdoc-checker.js ---
2
+ @sig f91e4d5d
3
+ export checkJSDocFile(filePath:string,code:string)→o,n.exec,e.slice,o.exec,n.slice,o.startsWith|check JSDoc consistency in single file
4
+ export checkJSDocConsistency(path:string)→i,f,e,r,checkJSDocFile,o.push|check JSDoc consistency in directory|aggregates errors and warnings
5
+ PATTERNS: AST parsing|regex for initial JSDoc extraction|AST type inference
6
+ EDGE_CASES: handles optional param [] syntax|handles union types in JSDoc
7
+ m(a,b)|extractJSDocComments — internal helper
8
+ p(a)|findJSDocBefore — internal helper
9
+ h(a)|extractParamName — internal helper
10
+ y(a)|inferTypeFromDefault — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/jsdoc-checker.ctx
1
+ // @ctx jsdoc-checker.ctx
2
2
  import{readFileSync as e,readdirSync as t,statSync as n}from"fs";import{join as s,relative as r,resolve as i}from"path";import{parse as o}from"../../vendor/acorn.mjs";
3
3
  import*as a from"../../vendor/walk.mjs";
4
4
  import{shouldExcludeDir as c,shouldExcludeFile as l,parseGitignore as u}from"../core/filters.js";
@@ -0,0 +1,9 @@
1
+ --- src/analysis/jsdoc-generator.js ---
2
+ @sig 1f2fddef
3
+ export generateJSDoc(filePath:string,options:Object=)→t,a,e,n,f.split,Math.max|(internal utility)
4
+ s(t)|(internal utility)
5
+ export generateJSDocFor(filePath:string,functionName:string,options:Object=)→generateJSDoc,name.endsWith|null→generateJSDoc,name.endsWith,results.find|null→generateJSDoc,name.endsWith,results.find|null→generateJSDoc,name.endsWith,results.find|generate JSDoc for a specific named function
6
+ PATTERNS: AST tree walking via Acorn|reverse line scanning for existing JSDoc
7
+ EDGE_CASES: skips getters/setters/constructors|infers types from AST defaults
8
+ i(a)|buildJSDoc — internal helper
9
+ o|buildJSDoc — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/jsdoc-generator.ctx
1
+ // @ctx jsdoc-generator.ctx
2
2
  import{readFileSync as t}from"fs";import{relative as e}from"path";import{parse as n}from"../../vendor/acorn.mjs";
3
3
  import*as r from"../../vendor/walk.mjs";
4
4
  import{getWorkspaceRoot as a}from"../core/workspace.js";
@@ -0,0 +1,6 @@
1
+ --- src/analysis/large-files.js ---
2
+ @sig 8675b153
3
+ analyzeFile(e,t)→s,i,n.split,o,l.simple,h.push|parse file, count structures, and assign size rating
4
+ export getLargeFiles(path:string,options:Object=)→r,findJSFiles,analyzeFile,i.map,o.filter,o.sort|analyze directory to find overly large and complex files
5
+ PATTERNS: AST visitor via Acorn|threshold-based scoring
6
+ EDGE_CASES: ignores parse errors gracefully
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/large-files.ctx
1
+ // @ctx large-files.ctx
2
2
  import{readFileSync as s,readdirSync as e,statSync as t}from"fs";import{join as n,relative as i,resolve as r}from"path";import{parse as o}from"../../vendor/acorn.mjs";import*as l from"../../vendor/walk.mjs";import{shouldExcludeDir as a,shouldExcludeFile as c,parseGitignore as u}from"../core/filters.js";function findJSFiles(s,r=s){s===r&&u(r);
3
3
  const o=[];try{for(const l of e(s)){const e=n(s,l),u=i(r,e);t(e).isDirectory()?a(l,u)||o.push(...findJSFiles(e,r)):!l.endsWith(".js")||l.endsWith(".css.js")||l.endsWith(".tpl.js")||c(l,u)||o.push(e)}}catch(s){}return o}
4
4
  function analyzeFile(e,t){const n=s(e,"utf-8"),r=i(t,e),a=n.split("\n").length;
@@ -0,0 +1,7 @@
1
+ --- src/analysis/outdated-patterns.js ---
2
+ @sig db82899a
3
+ analyzeFilePatterns(r,t)→e,o,c,a.simple,r.check,i.push|scan AST against code patterns array, track async context
4
+ analyzePackageJson(r)→s,n,e,JSON.parse,Object.keys,o.push|check package.json dependencies against redundant deps list
5
+ export getOutdatedPatterns(path:string,options:Object=)→i,findJSFiles,analyzeFilePatterns,o.push,o.sort,analyzePackageJson|aggregate outdated code patterns and redundant package deps
6
+ PATTERNS: AST ancestor traversal|declarative pattern rules list
7
+ EDGE_CASES: tracks async context to detect sync operations in async functions
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/outdated-patterns.ctx
1
+ // @ctx outdated-patterns.ctx
2
2
  import{readFileSync as e,readdirSync as r,statSync as t,existsSync as n}from"fs";import{join as s,relative as o,resolve as i}from"path";import{parse as c}from"../../vendor/acorn.mjs";import*as a from"../../vendor/walk.mjs";import{shouldExcludeDir as l,shouldExcludeFile as p,parseGitignore as d}from"../core/filters.js";
3
3
  const f={"node-fetch":{replacement:"fetch()",since:"Node 18"},"cross-fetch":{replacement:"fetch()",since:"Node 18"},"isomorphic-fetch":{replacement:"fetch()",since:"Node 18"},uuid:{replacement:"crypto.randomUUID()",since:"Node 19"},"deep-clone":{replacement:"structuredClone()",since:"Node 17"},"lodash.clonedeep":{replacement:"structuredClone()",since:"Node 17"},"abort-controller":{replacement:"AbortController (global)",since:"Node 15"},"form-data":{replacement:"FormData (global)",since:"Node 18"},"web-streams-polyfill":{replacement:"ReadableStream (global)",since:"Node 18"},"url-parse":{replacement:"URL (global)",since:"Node 10"},querystring:{replacement:"URLSearchParams",since:"Node 10"},rimraf:{replacement:"fs.rm({ recursive: true })",since:"Node 14"},mkdirp:{replacement:"fs.mkdir({ recursive: true })",since:"Node 10"},"recursive-readdir":{replacement:"fs.readdir({ recursive: true })",since:"Node 20"},glob:{replacement:"fs.glob()",since:"Node 22"}},m=[{name:"var-usage",description:"Use const/let instead of var",check:e=>"VariableDeclaration"===e.type&&"var"===e.kind,severity:"warning",replacement:"const/let"},{name:"require-usage",description:"Use ESM import instead of require()",check:e=>"CallExpression"===e.type&&"Identifier"===e.callee.type&&"require"===e.callee.name,severity:"info",replacement:"import ... from"},{name:"module-exports",description:"Use ESM export instead of module.exports",check:e=>"AssignmentExpression"===e.type&&"MemberExpression"===e.left.type&&"Identifier"===e.left.object.type&&"module"===e.left.object.name&&"Identifier"===e.left.property.type&&"exports"===e.left.property.name,severity:"info",replacement:"export default/export"},{name:"buffer-constructor",description:"new Buffer() is deprecated",check:e=>"NewExpression"===e.type&&"Identifier"===e.callee.type&&"Buffer"===e.callee.name,severity:"error",replacement:"Buffer.from() / Buffer.alloc()"},{name:"arguments-usage",description:"Use rest parameters instead of arguments",check:e=>"Identifier"===e.type&&"arguments"===e.name,severity:"warning",replacement:"...args"},{name:"promisify-usage",description:"Use fs/promises instead of util.promisify",check:e=>"CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&"util"===e.callee.object.name&&"Identifier"===e.callee.property.type&&"promisify"===e.callee.property.name,severity:"info",replacement:"fs/promises module"},{name:"sync-in-async",description:"Avoid sync methods in async context (readFileSync, etc.)",check:(e,r)=>{if("CallExpression"!==e.type)return!1;
4
4
  const t=e.callee;return"MemberExpression"===t.type&&"Identifier"===t.property.type&&t.property.name.endsWith("Sync")&&r.inAsync},severity:"warning",replacement:"async fs/promises methods"}];function findJSFiles(e,n=e){e===n&&d(n);
@@ -0,0 +1,6 @@
1
+ --- src/analysis/similar-functions.js ---
2
+ @sig 73623fb2
3
+ export getSimilarFunctions(path:string,options:Object=)→r,m,p,l.push,f,o.push|extract and compare signatures across directory for similar pairs
4
+ PATTERNS: structural hashing of control flow|pair-wise comparison scoring
5
+ EDGE_CASES: skips very small functions|ignores overloads (same name/file)
6
+ u(a,b,c)|buildSignature — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/similar-functions.ctx
1
+ // @ctx similar-functions.ctx
2
2
  import{readFileSync as t,readdirSync as e,statSync as n}from"fs";import{join as s,relative as a,resolve as r}from"path";import{parse as l}from"../../vendor/acorn.mjs";
3
3
  import*as o from"../../vendor/walk.mjs";
4
4
  import{shouldExcludeDir as i,shouldExcludeFile as h,parseGitignore as c}from"../core/filters.js";
@@ -0,0 +1,11 @@
1
+ --- src/analysis/test-annotations.js ---
2
+ @sig b2010e5d
3
+ export parseAnnotations(content:string,filePath:string)→t.split,t.startsWith,i,n.push,t.match,f.split|parse checklist lines into test feature objects
4
+ export getAllFeatures(dir:string)→c,o,a,t,parseAnnotations,n.push|read and parse all test features from context dir
5
+ export getPendingTests(dir:string)→c,getAllFeatures,r,n.push|return list of pending tests with file paths
6
+ export markTestPassed(testId:string)→t.split,f|mark specific test id as passed [x]
7
+ export markTestFailed(testId:string,reason:string)→t.split,f|mark specific test id as failed [!] with reason
8
+ export getTestSummary(dir:string)→getAllFeatures,c.push,Math.round|compute total, passed, failed, pending counts
9
+ export resetTestState()→process.cwd,o,a,t,e.replace,n|reset all test states to pending [ ]
10
+ PATTERNS: regex markdown checklist parsing|file rewriting for state
11
+ EDGE_CASES: handles unreadable files gracefully|handles missing failure reasons
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/test-annotations.ctx
1
+ // @ctx test-annotations.ctx
2
2
  import{readFileSync as t,readdirSync as s,statSync as e,writeFileSync as n}from"fs";
3
3
  import{join as o,relative as r,resolve as c}from"path";
4
4
  function a(t){const n=[];try{for(const r of s(t)){const s=o(t,r);e(s).isDirectory()&&!r.startsWith(".")?n.push(...a(s)):r.endsWith(".ctx.md")&&n.push(s)}}catch(t){}return n}
@@ -0,0 +1,6 @@
1
+ --- src/analysis/type-checker.js ---
2
+ @sig 5e85e31c
3
+ export checkTypes(path:string,options:Object=)→n,o,l,path.includes,e,stdout.on|null, diagnostics: TypeDiagnostic[], summary: Object, hint: string|null →resolve,detectTsc,buildArgs,path.includes,spawn,stdout.on|null, diagnostics: TypeDiagnostic[], summary: Object, hint: string|null →resolve,detectTsc,buildArgs,path.includes,spawn,stdout.on|null, diagnostics: TypeDiagnostic[], summary: Object, hint: string|null →resolve,detectTsc,buildArgs,path.includes,spawn,stdout.on|spawn tsc process, collect and parse type diagnostics
4
+ PATTERNS: subprocess spawning|regex CLI output parsing
5
+ EDGE_CASES: falls back to npx if global tsc missing|auto-generates config args
6
+ i(a,b)|parseDiagnosticLine — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/type-checker.ctx
1
+ // @ctx type-checker.ctx
2
2
  import{execSync as t,spawn as e}from"child_process";import{existsSync as s}from"fs";import{resolve as n,join as r}from"path";
3
3
  function o(){try{return{available:!0,version:t("tsc --version",{encoding:"utf-8",timeout:5e3}).trim(),path:t("which tsc",{encoding:"utf-8",timeout:5e3}).trim()}}catch(e){try{return{available:!0,version:t("npx tsc --version",{encoding:"utf-8",timeout:15e3}).trim(),path:"npx tsc"}}catch(t){return{available:!1,version:null,path:null}}}}
4
4
  function i(t,e){const s=t.match(/^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/);return s?{file:s[1],line:parseInt(s[2]),column:parseInt(s[3]),severity:s[4],message:s[6],code:s[5]}:null}
@@ -0,0 +1,8 @@
1
+ --- src/analysis/undocumented.js ---
2
+ @sig 898e8f30
3
+ export checkUndocumentedFile(filePath:string,code:string,level:string)→c,n.exec,t.slice,e.push,f,s.push|analyze single file for undocumented classes, functions, methods
4
+ export getUndocumented(path:string,options:Object=)→r,d,t,o,checkUndocumentedFile,i.push|scan directory for all undocumented items
5
+ export getUndocumentedSummary(path:string,options:Object=)→getUndocumented,n.filter,n.slice|scan directory and return aggregated undocumented summary
6
+ PATTERNS: AST traversal|regex JSDoc extraction
7
+ EDGE_CASES: skips lifecycle methods|skips private prefixed methods
8
+ p(a,b)|findJSDocBefore — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/analysis/undocumented.ctx
1
+ // @ctx undocumented.ctx
2
2
  import{readFileSync as t,readdirSync as e,statSync as n}from"fs";import{join as s,relative as o,resolve as r}from"path";import{parse as c}from"../../vendor/acorn.mjs";
3
3
  import*as i from"../../vendor/walk.mjs";
4
4
  import{shouldExcludeDir as l,shouldExcludeFile as a,parseGitignore as u}from"../core/filters.js";
@@ -0,0 +1,7 @@
1
+ --- src/cli/cli-handlers.js ---
2
+ @sig 0737c704
3
+ R(r)→r.startsWith,r.find,q|(internal utility)
4
+ PATTERNS: command definition map|lazy execution
5
+ EDGE_CASES: falls back to cwd if no path given|validates required args
6
+ O(a,b)|getArg — internal helper
7
+ export CLI_HANDLERS|map of CLI command handlers
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/cli/cli-handlers.ctx
1
+ // @ctx cli-handlers.ctx
2
2
  import{getSkeleton as r,expand as e,deps as s,usages as a}from"../mcp/tools.js";import{getPendingTests as t,getTestSummary as n}from"../analysis/test-annotations.js";import{getFilters as o}from"../core/filters.js";import{getInstructions as c}from"../compact/instructions.js";import{getUndocumentedSummary as i}from"../analysis/undocumented.js";import{getDeadCode as d}from"../analysis/dead-code.js";import{generateJSDoc as l}from"../analysis/jsdoc-generator.js";import{getSimilarFunctions as m}from"../analysis/similar-functions.js";import{getComplexity as u}from"../analysis/complexity.js";import{getLargeFiles as p}from"../analysis/large-files.js";import{getOutdatedPatterns as g}from"../analysis/outdated-patterns.js";import{getFullAnalysis as y}from"../analysis/full-analysis.js";import{compressFile as h}from"../compact/compress.js";import{getProjectDocs as f,generateContextFiles as j}from"../compact/doc-dialect.js";import{getGraph as b}from"../mcp/tools.js";import{parseProject as x}from"../core/parser.js";import{resolvePath as q}from"../core/workspace.js";import{checkJSDocConsistency as A}from"../analysis/jsdoc-checker.js";import{checkTypes as w}from"../analysis/type-checker.js";import{compactProject as E,expandProject as S}from"../compact/compact.js";import{injectJSDoc as U,stripJSDoc as P,validateCtxContracts as k}from"../compact/ctx-to-jsdoc.js";import{getConfig as v,setConfig as C,getModeDescription as D,getModeWorkflow as I}from"../compact/mode-config.js";import{compactMigrate as F}from"../compact/compact-migrate.js";
3
3
  function O(r,e){const s=r.find(r=>r.startsWith(`--${e}=`));return s?s.split("=")[1]:void 0}
4
4
  function R(r){const e=r.find(r=>!r.startsWith("--"))||".";return q(e)}
@@ -0,0 +1,6 @@
1
+ --- src/cli/cli.js ---
2
+ @sig daa9547d
3
+ export printHelp()→console.log|log usage instructions and available commands to stdout
4
+ export runCLI(command:string,args:string[])→printHelp,console.error,process.exit,o.handler,console.log,JSON.stringify|parse args, validate, and route to corresponding cli handler
5
+ PATTERNS: generic command router|table help text
6
+ EDGE_CASES: handles unknown commands gracefully|validates required args
package/src/cli/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/cli/cli.ctx
1
+ // @ctx cli.ctx
2
2
  import{CLI_HANDLERS as e}from"./cli-handlers.js";
3
3
  export function printHelp(){console.log("\nproject-graph-mcp - MCP server for AI agents\n\nUsage:\n npx project-graph-mcp Start MCP stdio server\n npx project-graph-mcp <command> [args] Run CLI command\n\nCommands:\n config Generate MCP config with correct paths\n skeleton <path> Get compact project overview\n expand <symbol> Expand minified symbol (e.g., SN, SN.togglePin)\n deps <symbol> Get dependency tree\n usages <symbol> Find all usages\n pending <path> List pending .ctx.md test checklists\n summary <path> Get test progress summary\n undocumented <path> Find missing JSDoc (--level=tests|params|all)\n deadcode <path> Find unused functions/classes\n jsdoc <file> Generate JSDoc for file\n similar <path> Find similar functions (--threshold=60)\n complexity <path> Analyze cyclomatic complexity (--min=1)\n largefiles <path> Find files needing split (--problematic)\n outdated <path> Find legacy patterns & redundant deps\n analyze <path> Run ALL checks with Health Score\n jsdoc-check <path> Validate JSDoc vs function signatures\n types <path> Run tsc type checking (--max=50)\n compress <file> Compress JS file for AI (--no-beautify, --no-legend)\n compact <path> Compact all JS files (--dry-run)\n beautify <path> Beautify/expand all JS files (--dry-run)\n inject-jsdoc <path> Generate JSDoc from .ctx files and inject into source\n strip-jsdoc <path> Strip all JSDoc blocks from source files\n docs <path> Get project docs in doc-dialect format (--file=<name>)\n generate-ctx <path> Generate .context/ docs (--overwrite --scope=focus)\n validate-ctx <path> Validate .ctx contracts against source AST (--strict)\n mode <path> Show current compact code mode and workflow\n compact-migrate <path> Migrate formatted source to compact mode (git must be clean)\n set-mode <path> <1|2> Set mode (1=compact*, 2=full)\n serve <path> Start web dashboard (--port=N)\n filters Show current filter configuration\n instructions Show agent guidelines (JSDoc, Arch)\n help Show this help\n\nQuick Start:\n npx project-graph-mcp config # Get MCP config for your IDE\n\nWeb Dashboard:\n npx project-graph-mcp serve . # Start at auto-assigned port\n npx project-graph-mcp serve . --port 3000\n # Then open http://localhost:{port}/ or http://project-graph.local/{name}/\n\nExamples:\n npx project-graph-mcp skeleton src/components\n npx project-graph-mcp expand SN\n npx project-graph-mcp compact src/ --dry-run\n")}
4
4
  export async function runCLI(n,t){if(!n||"help"===n||"--help"===n||"-h"===n)return void printHelp();
@@ -0,0 +1,6 @@
1
+ --- src/compact/ai-context.js ---
2
+ @sig 8aed2da2
3
+ estimateTokens(e)→JSON.stringify,Math.ceil|(internal utility)
4
+ export getAiContext(path:string,options:Object=)→e,s,estimateTokens,o,n,r|get project context including skeleton, docs, and optionally compressed files
5
+ PATTERNS: token estimation heuristic|parallel generation
6
+ EDGE_CASES: gracefully skips unreadable files|handles missing files
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/ai-context.ctx
1
+ // @ctx ai-context.ctx
2
2
  import{estimateTokens}from"../core/utils.js";import{resolve as e,extname as t,relative as _rel,join as _join,basename as _base}from"path";import{getSkeleton as s,getGraph as o}from"../mcp/tools.js";import{getProjectDocs as n}from"./doc-dialect.js";import{compressFile as i}from"./compress.js";import{findJSFiles as r}from"../core/parser.js";import{readFileSync as _rf,existsSync as _ex,writeFileSync as _wf}from"fs";
3
3
  const c=new Set([".js",".mjs",".ts",".tsx"]);
4
4
  const IGNORE_FILE=".contextignore";
@@ -0,0 +1,8 @@
1
+ --- src/compact/compact-migrate.js ---
2
+ @sig b7ae75d3
3
+ export compactMigrate(path:string,options:Object=)→m,message.includes,h,r,y,e|(internal utility)
4
+ PATTERNS: (internal utility)
5
+ EDGE_CASES: (internal utility)
6
+ w(a,b)|extractNames — internal helper
7
+ S(a,b,c)|buildNamesDirective — internal helper
8
+ x(a)|updateCtxNames — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/compact-migrate.ctx
1
+ // @ctx compact-migrate.ctx
2
2
  import{walkJSFiles}from"../core/file-walker.js";import{readFileSync as e,writeFileSync as t,existsSync as s}from"fs";import{join as o,extname as i,relative as r,basename as c,dirname as l}from"path";import{execSync as m}from"child_process";import{compactProject as f}from"./compact.js";import{validatePipeline as d}from"./validate-pipeline.js";import{setConfig as u}from"./mode-config.js";
3
3
  const p=new Set([".js",".mjs"]),g=new Set(["node_modules",".git","vendor",".context","dev-docs",".agent",".agents",".expanded"]);
4
4
 
@@ -0,0 +1,11 @@
1
+ --- src/compact/compact.js ---
2
+ @sig 596f30f0
3
+ walkJSFiles(e,t=)→n,a.startsWith,r,o,f.has,walkJSFiles|(internal utility)
4
+ addTopLevelNewlines(e)→e.replace|insert newlines after minified semicolons to format top-level statements
5
+ resolveCtxPath(e,t)→a,c,i,l,r,s|(internal utility)
6
+ compactFile(n,o)→e,s.trim,u,addTopLevelNewlines,resolveCtxPath,a.startsWith|minify single JS file, prepend @ctx header if .ctx exists
7
+ beautifyFile(n)→e,o.trim,u,t|format single JS file with proper indentation
8
+ export compactProject(path:string,options:Object=)→walkJSFiles,a,e,u,addTopLevelNewlines,compactFile|compact all JS files in dir, per-file error handling, ret stats+savings%
9
+ PATTERNS: Terser mangle:false preserves all names|per-file try/catch collects errors array|dryRun mode estimates without writing|skips node_modules,vendor,.git,.context
10
+ EDGE_CASES: empty files return 0/0|syntax errors collected in errors[] not thrown|files with only comments become empty (correct behavior)
11
+ expandProject(t,n=)|expandProject — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/compact.ctx
1
+ // @ctx compact.ctx
2
2
  import{resolveCtxRelPath as resolveCtxPath}from"./ctx-resolver.js";import{walkJSFiles}from"../core/file-walker.js";import{readFileSync as e,writeFileSync as t,existsSync as s}from"fs";import{join as r,extname as c,relative as a,basename as i,dirname as l}from"path";import{minify as u}from"../../vendor/terser.mjs";
3
3
 
4
4
  function addTopLevelNewlines(e){return e.replace(/;(import )/g,";\n$1").replace(/;(export )/g,";\n$1").replace(/\}(export )/g,"}\n$1").replace(/\}(function )/g,"}\n$1").replace(/\}(async function )/g,"}\n$1").replace(/\}(class )/g,"}\n$1").replace(/;(const |let |var )/g,";\n$1")}
@@ -0,0 +1,7 @@
1
+ --- src/compact/compress.js ---
2
+ @sig 094fad9d
3
+ export compressFile(filePath:string,options:Object=)→n,o.has,e,i,d.trim,a|minifies JS/TS via Terser, prepends JSDoc legend
4
+ export editCompressed(rootPath:string,filePath:string,symbolName:string,newCode:string=)→e,s,l,m.slice,a|find symbol in file and replace its code safely
5
+ PATTERNS: AST traversal|regex JSDoc extraction|Terser minification
6
+ EDGE_CASES: validates syntax before writing|handles empty files
7
+ c(a,b)|extractLegend — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/compress.ctx
1
+ // @ctx compress.ctx
2
2
  import{estimateTokens as i}from"../core/utils.js";import{readFileSync as e}from"fs";import{basename as t,extname as n}from"path";import{minify as a}from"../../vendor/terser.mjs";import{parse as s}from"../../vendor/acorn.mjs";import{simple as r}from"../../vendor/walk.mjs";
3
3
  const o=new Set([".js",".mjs",".ts",".tsx"]);
4
4
 
@@ -0,0 +1,2 @@
1
+ export resolveCtxPath(rootDir:string,filePath:string)|find .ctx file for a source file|checks .context/ then co-located
2
+ export readCtxFile(rootDir:string,filePath:string)|read .ctx content for a source file|returns null if not found
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/ctx-resolver.ctx
1
+ // @ctx ctx-resolver.ctx
2
2
  import{readFileSync as t,existsSync as s}from"fs";import{join as i,basename as a,extname as c,dirname as p,relative as l}from"path";
3
3
  export function resolveCtxPath(e,n){const o=a(n,c(n))+".ctx",r=p(n),d=i(e,".context",r,o);if(s(d))return d;const f=i(e,r,o);return s(f)?f:null}
4
4
  export function resolveCtxRelPath(e,n){const o=l(n,e),r=a(o,c(o))+".ctx",u=p(o),d=i(n,".context",u,r);if(s(d))return".context/"+u+"/"+r;const f=i(n,u,r);return s(f)?u+"/"+r:null}
@@ -0,0 +1,11 @@
1
+ --- src/compact/ctx-to-jsdoc.js ---
2
+ @sig 174ef437
3
+ m(t,e)→t.replace,r,o|(internal utility)
4
+ F(t)|(internal utility)
5
+ export parseCtxFile(ctxPath:string)→t.split,t.match,r.split,i.trim,functions.push|null, functions: Array<{name: string, params: string, exported: boolean, description: string→ctxContent.split,line.match,arrowParts.split,desc.trim,functions.push|null, functions: Array<{name: string, params: string, exported: boolean, description: string→ctxContent.split,line.match,arrowParts.split,desc.trim,functions.push|null, functions: Array<{name: string, params: string, exported: boolean, description: string→ctxContent.split,line.match,arrowParts.split,desc.trim,functions.push|parse .ctx file into structured fn signatures with params+types
6
+ export injectJSDoc(filePath:string,options:Object=)→d,a,m,t,parseCtxFile,c|inject JSDoc from .ctx into source, skip if JSDoc exists, AST-located
7
+ export stripJSDoc(filePath:string,options:Object=)→d,t,c,value.startsWith,f.filter,u.slice|strip all JSDoc via AST comments (safe—ignores strings), regex fallback
8
+ export validateCtxContracts(rootPath:string,options:Object=)→d,a,m,t,parseCtxFile,c|validate if .ctx signatures match source AST signatures
9
+ PATTERNS: AST-based comment detection via Acorn onComment array|reverse-order insertions preserve line numbers|export keyword detection for duplicate prevention
10
+ EDGE_CASES: strings containing /** preserved by AST parser|blank lines between JSDoc and fn handled by trimEnd|unparseable files fall back to regex strip
11
+ h(a)|splitTopLevelParams — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/ctx-to-jsdoc.ctx
1
+ // @ctx ctx-to-jsdoc.ctx
2
2
  import{walkJSFiles}from"../core/file-walker.js";import{resolveCtxPath}from"./ctx-resolver.js";import{buildJSDocFromRaw as u}from"./jsdoc-builder.js";import{readFileSync as t,writeFileSync as e,readdirSync as n,statSync as s,existsSync as o}from"fs";
3
3
  import{join as r,extname as i,relative as a}from"path";
4
4
  import{parse as c}from"../../vendor/acorn.mjs";
@@ -0,0 +1,11 @@
1
+ --- src/compact/doc-dialect.js ---
2
+ @sig bccef7f4
3
+ export generateDocDialect(graph:Object,rootPath:string)→r,s.push,c.push,c.join,edges.filter,h.push|generate compact doc-dialect string from graph|includes project/file/function levels
4
+ export readContextDocs(rootPath:string)→i,x,t,n.set,relPath.startsWith,n.keys|read manual docs (mirror + colocated)|colocated overrides mirror
5
+ export getProjectDocs(rootPath:string,parsed:Object,options:Object=)→readContextDocs,j,t,generateDocDialect,t.indexOf,t.slice|get project docs|merges manual .ctx and auto-generated dialect
6
+ export checkStaleness(rootPath:string,parsed:Object)→i,x,t,e.match,E,o.push|check .ctx files staleness vs current AST
7
+ export generateContextFiles(graph:Object,rootPath:string,parsed:Object,options:Object=)→i,s,n,r,c.push,c.join|generate .context/ templates|supports focus scope and overwrite merge
8
+ PATTERNS: two-tier doc resolution (colocated > mirror)|AST signature hashing
9
+ EDGE_CASES: preserves existing placeholders on overwrite|handles git diff for focus scope
10
+ S(a,b)|resolveCtxMdPath — internal helper
11
+ y(a,b,c,d,e,f,g)|buildFileTemplate — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/doc-dialect.ctx
1
+ // @ctx doc-dialect.ctx
2
2
  import{readFileSync as t,readdirSync as e,existsSync as s,mkdirSync as n,writeFileSync as o,statSync as c}from"fs";
3
3
  import{join as i,basename as r,extname as a,dirname as l,relative as h}from"path";
4
4
  import{execSync as f}from"child_process";
@@ -16,4 +16,4 @@ export function getProjectDocs(e,s,n={}){const{file:o}=n,c=readContextDocs(s);if
16
16
  function E(t,e){const s=[];for(const n of(e.functions||[]).filter(e=>e.file===t))s.push(`F:${n.exported?"e":""}:${n.name}(${n.params?.join(",")||""})`);for(const n of(e.classes||[]).filter(e=>e.file===t)){const t=n.methods?.sort().join(",")||"";s.push(`C:${n.name}:${n.extends||""}:${t}`)}return s.sort(),u("md5").update(s.join("|")).digest("hex").slice(0,8)}
17
17
  export function checkStaleness(e,s){const n=i(e,".context"),o=[];let c=0,r=0;for(const{relPath:e,absPath:i}of x(n,n))try{const e=t(i,"utf-8"),n=e.match(/@sig\s+(\w+)/),a=e.match(/^--- (.+) ---/m);if(!n||!a){r++;continue}E(a[1],s)!==n[1]?o.push(a[1]):c++}catch{}for(const{absPath:n}of x(e,e).filter(t=>!t.relPath.startsWith(".context")))try{const e=t(n,"utf-8"),i=e.match(/@sig\s+(\w+)/),a=e.match(/^--- (.+) ---/m);if(!i||!a){r++;continue}E(a[1],s)!==i[1]?o.push(a[1]):c++}catch{}return{stale:o,fresh:c,unknown:r}}
18
18
  export async function generateContextFiles(t,e,c,a={}){const{overwrite:l=!1,scope:h="all"}=a,u=i(e,".context"),p=[],m=[],d={};s(u)||n(u,{recursive:!0});const $=i(u,"project.ctx");if(!s($)||l){const s=r(e),{stats:n}=t,c=[];n.files>0&&c.push(`${n.files} files`),n.classes>0&&c.push(`${n.classes} classes`),n.functions>0&&c.push(`${n.functions} functions`);let i=[`=== PROJECT: ${s} ===`,"ARCH: {DESCRIBE}","FLOW: {DESCRIBE}",`STATS: ${c.join("|")}`].join("\n")+"\n";o($,i,"utf-8"),p.push("project.ctx"),d["project.ctx"]=i}else m.push("project.ctx");const g={};for(const[e,s]of Object.entries(t.nodes)){const t=s.f;t&&(g[t]||(g[t]=[]),g[t].push({shortName:e,...s}))}let x=null;if("focus"===h)try{const t=f("git diff --name-only HEAD~5",{cwd:e,encoding:"utf-8"});x=new Set(t.split("\n").filter(t=>t.endsWith(".js")||t.endsWith(".mjs")||t.endsWith(".ts")).map(t=>t.trim()).filter(Boolean))}catch{x=null}else Array.isArray(h)&&(x=new Set(h));const j=Object.entries(g).filter(([t])=>!x||x.has(t));for(let s=0;s<j.length;s+=5){const n=j.slice(s,s+5),o=await Promise.allSettled(n.map(([s,n])=>y(s,n,t,c,u,e,l)));for(const t of o)if("fulfilled"===t.status&&t.value){const{action:e,path:s,template:n}=t.value;"created"===e?(p.push(s),d[s]=n):m.push(s)}}const S={created:p,skipped:m};return p.length>0&&(S.templates=d),S}
19
- async function y(e,c,h,f,u,x,j){const S=r(e,a(e))+".ctx",y=l(e),D=i(u,y),C=i(D,S),b=i(y,S),R=i(x,y,S);if((s(C)||s(R))&&!j)return{action:"skipped",path:b};let W;s(D)||n(D,{recursive:!0});const P=s(R)?R:s(C)?C:null;if(P&&j)try{W=function(t){const e=new Map;for(const s of t.split("\n")){const t=s.trim();if(!t||t.startsWith("---")||t.startsWith("@sig")||t.startsWith("@enrich")||t.startsWith("Rules:")||t.startsWith("Save this")||t.startsWith("CALLS→")||t.startsWith("R→")||t.startsWith("W→"))continue;const n=t.match(/^(PATTERNS|EDGE_CASES):\s*(.+)/);if(n&&"{DESCRIBE}"!==n[2]){e.set(n[1],n[2]);continue}const o=t.match(/^(?:export\s+)?(?:class\s+)?\.?([\w]+)\([^)]*\)(?:[^|]*?)\|(.+)/);if(o&&"{DESCRIBE}"!==o[2]){e.set(o[1],o[2]);continue}const c=t.match(/^class\s+([\w]+)[^|]*\|[^|]*\|(.+)/);c&&"{DESCRIBE}"!==c[2]&&e.set(c[1],c[2])}return e}(t(P,"utf-8"))}catch{}let w=function(t,e,s,n,o){const c=[`--- ${t} ---`,`@sig ${E(t,n)}`],i=o||new Map,r={};for(const e of n.functions||[])e.file===t&&(r[e.name]=e);const a={};for(const e of n.classes||[])e.file===t&&(a[e.name]=e);for(const t of e){const e=s.reverseLegend[t.shortName]||t.shortName;if("C"===t.t){const n=a[e]||{},o=t.x?` extends ${t.x}`:"",r=t.m?.length||0,l=t.$?.length||0,h=[];r>0&&h.push(`${r}m`),l>0&&h.push(`${l}$`);const f=i.get(e)||"{DESCRIBE}";if(c.push(`class ${e}${o}|${h.join(",")}|${f}`),t.m)for(const e of t.m){const t=s.reverseLegend[e]||e,n=i.get(t)||"{DESCRIBE}";c.push(` .${t}()|${n}`)}n.calls?.length>0&&c.push(` CALLS→${n.calls.slice(0,8).join(",")}`),n.dbReads?.length>0&&c.push(` R→${n.dbReads.join(",")}`),n.dbWrites?.length>0&&c.push(` W→${n.dbWrites.join(",")}`)}else if("F"===t.t){const s=r[e]||{},n=t.e?"export ":"",o=s.params?.length>0?s.params.join(","):"",a=s.returns?`→${s.returns}`:"",l=s.calls?.slice(0,6)||[],h=l.length>0?`→${l.join(",")}`:"",f=i.get(e)||"{DESCRIBE}";c.push(`${n}${e}(${o})${a}${h}|${f}`),s.dbReads?.length>0&&c.push(` R→${s.dbReads.join(",")}`),s.dbWrites?.length>0&&c.push(` W→${s.dbWrites.join(",")}`)}}const l=i.get("PATTERNS")||"{DESCRIBE}",h=i.get("EDGE_CASES")||"{DESCRIBE}";return c.push(`PATTERNS: ${l}`),c.push(`EDGE_CASES: ${h}`),c.join("\n").includes("{DESCRIBE}")&&c.splice(2,0,`@enrich: Replace each {DESCRIBE} below. Read ${t} for context.`," Rules: max 80ch, pipe|separated, abbrev (fn/ret/cfg/init/auth/db/msg)."," Save this file after filling all markers. Remove @enrich lines when done."),c.join("\n")}(e,c,h,f,W);o(C,w+"\n","utf-8");try{const n=i(x,e);if(s(n)&&e.endsWith(".js")){const s=t(n,"utf-8"),o=m(s),c=d(s,e),i=$(s,e,"tests"),r=g(s,e);p(u,e,{sig:o,contentHash:o,complexity:c,undocumented:i,jsdocIssues:r})}}catch{}const A=r(e,a(e))+".ctx.md",L=i(D,A);if(!s(L)){const t=`# ${r(e)}\n\n## Notes\n\n## TODO\n\n## Decisions\n`;o(L,t,"utf-8")}return{action:"created",path:b,template:w}}
19
+ async function y(e,c,h,f,u,x,j){const S=r(e,a(e))+".ctx",y=l(e),D=i(u,y),C=i(D,S),b=i(y,S),R=i(x,y,S);if((s(R)||s(C))&&!j)return{action:"skipped",path:b};let W;s(D)||n(D,{recursive:!0});const P=s(R)?R:s(C)?C:null;if(P&&j)try{W=function(t){const e=new Map;for(const s of t.split("\n")){const t=s.trim();if(!t||t.startsWith("---")||t.startsWith("@sig")||t.startsWith("@enrich")||t.startsWith("Rules:")||t.startsWith("Save this")||t.startsWith("CALLS→")||t.startsWith("R→")||t.startsWith("W→"))continue;const n=t.match(/^(PATTERNS|EDGE_CASES):\s*(.+)/);if(n&&"{DESCRIBE}"!==n[2]){e.set(n[1],n[2]);continue}const o=t.match(/^(?:export\s+)?(?:class\s+)?\.?([\w]+)\([^)]*\)(?:[^|]*?)\|(.+)/);if(o&&"{DESCRIBE}"!==o[2]){e.set(o[1],o[2]);continue}const c=t.match(/^class\s+([\w]+)[^|]*\|[^|]*\|(.+)/);c&&"{DESCRIBE}"!==c[2]&&e.set(c[1],c[2])}return e}(t(P,"utf-8"))}catch{}let w=function(t,e,s,n,o){const c=[`--- ${t} ---`,`@sig ${E(t,n)}`],i=o||new Map,r={};for(const e of n.functions||[])e.file===t&&(r[e.name]=e);const a={};for(const e of n.classes||[])e.file===t&&(a[e.name]=e);for(const t of e){const e=s.reverseLegend[t.shortName]||t.shortName;if("C"===t.t){const n=a[e]||{},o=t.x?` extends ${t.x}`:"",r=t.m?.length||0,l=t.$?.length||0,h=[];r>0&&h.push(`${r}m`),l>0&&h.push(`${l}$`);const f=i.get(e)||"{DESCRIBE}";if(c.push(`class ${e}${o}|${h.join(",")}|${f}`),t.m)for(const e of t.m){const t=s.reverseLegend[e]||e,n=i.get(t)||"{DESCRIBE}";c.push(` .${t}()|${n}`)}n.calls?.length>0&&c.push(` CALLS→${n.calls.slice(0,8).join(",")}`),n.dbReads?.length>0&&c.push(` R→${n.dbReads.join(",")}`),n.dbWrites?.length>0&&c.push(` W→${n.dbWrites.join(",")}`)}else if("F"===t.t){const s=r[e]||{},n=t.e?"export ":"",o=s.params?.length>0?s.params.join(","):"",a=s.returns?`→${s.returns}`:"",l=s.calls?.slice(0,6)||[],h=l.length>0?`→${l.join(",")}`:"",f=i.get(e)||"{DESCRIBE}";c.push(`${n}${e}(${o})${a}${h}|${f}`),s.dbReads?.length>0&&c.push(` R→${s.dbReads.join(",")}`),s.dbWrites?.length>0&&c.push(` W→${s.dbWrites.join(",")}`)}}const l=i.get("PATTERNS")||"{DESCRIBE}",h=i.get("EDGE_CASES")||"{DESCRIBE}";return c.push(`PATTERNS: ${l}`),c.push(`EDGE_CASES: ${h}`),c.join("\n").includes("{DESCRIBE}")&&c.splice(2,0,`@enrich: Replace each {DESCRIBE} below. Read ${t} for context.`," Rules: max 80ch, pipe|separated, abbrev (fn/ret/cfg/init/auth/db/msg)."," Save this file after filling all markers. Remove @enrich lines when done."),c.join("\n")}(e,c,h,f,W);const srcDir=i(x,y);s(srcDir)||n(srcDir,{recursive:!0});o(R,w+"\n","utf-8");try{const n=i(x,e);if(s(n)&&e.endsWith(".js")){const s=t(n,"utf-8"),o=m(s),c=d(s,e),i=$(s,e,"tests"),r=g(s,e);p(u,e,{sig:o,contentHash:o,complexity:c,undocumented:i,jsdocIssues:r})}}catch{}const A=r(e,a(e))+".ctx.md",L=i(D,A);if(!s(L)){const t=`# ${r(e)}\n\n## Notes\n\n## TODO\n\n## Decisions\n`;o(L,t,"utf-8")}return{action:"created",path:b,template:w}}
@@ -0,0 +1,14 @@
1
+ --- src/compact/expand.js ---
2
+ @sig a9cd25fe
3
+ @enrich: Replace each (internal utility) below. Read src/compact/expand.js for context.
4
+ Rules: max 80ch, pipe|separated, abbrev (fn/ret/cfg/init/auth/db/msg).
5
+ Save this file after filling all markers. Remove @enrich lines when done.
6
+ $(e,n)→c,a,p,i,s,t|(internal utility)
7
+ export expandProject(rootPath:string,options:Object=)→i,s,S,l,$,expandFile|expand all src/ → .expanded/ with JSDoc injection
8
+ export expandFile(filePath:string,ctxContent:string,options:Object=)→t,r.trim,m,i.split,s.trim,s.startsWith|beautify compact JS + inject JSDoc from .ctx|core hybrid mode expand
9
+ PATTERNS: Terser beautify (no compress/mangle)|AST walk for injection points|reverse-order insertion|name-based symbol matching
10
+ EDGE_CASES: */ in descriptions→sanitized|empty files→skip|parse errors→fallback to beautified|unmapped lines→null
11
+ h(a)|parseCtxSignatures — internal helper
12
+ y(a)|parseCtxParams — internal helper
13
+ g(a)|extractReturnType — internal helper
14
+ w(a,b,c,d,e)|sanitizeJSDocText — internal helper
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/expand.ctx
1
+ // @ctx expand.ctx
2
2
  import{parseCtxParams as h,buildJSDocBlock as g}from"./jsdoc-builder.js";import{readCtxFile as $}from"./ctx-resolver.js";import{walkJSFiles}from"../core/file-walker.js";import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as s}from"fs";import{join as i,basename as a,extname as c,dirname as p,relative as l}from"path";import{minify as m}from"../../vendor/terser.mjs";import{parse as d}from"../../vendor/acorn.mjs";import{simple as f,ancestor as u}from"../../vendor/walk.mjs";
3
3
 
4
4
  function y(t){if(!t)return null;const e=t.match(/^→([A-Z][\w<>\[\]|]*)/);return e?e[1]:null}function w(t,e,n,s,o){const r=new Map;let i=[];for(const t of e.body)if("ImportDeclaration"===t.type)for(const e of t.specifiers)if("ImportSpecifier"===e.type&&e.imported.name!==e.local.name)r.set(e.local.name,e.imported.name),i.push({s:e.start,e:e.end,n:e.imported.name,k:e.local.name});else if("ImportDefaultSpecifier"===e.type){const n=t.source.value.replace(/^node:/,"").split("/").pop().replace(/\.\\w+$/,"").replace(/-(\w)/g,(t,e)=>e.toUpperCase());n&&/^[a-zA-Z_$][\w$]*$/.test(n)&&n!==e.local.name&&(r.set(e.local.name,n),i.push({s:e.start,e:e.end,n:n,k:e.local.name}))}else if("ImportNamespaceSpecifier"===e.type&&e.local.name.length<=2){const n=t.source.value.replace(/^node:/,"").split("/").pop().replace(/\.\\w+$/,"").replace(/-(\w)/g,(t,e)=>e.toUpperCase());n&&/^[a-zA-Z_$][\w$]*$/.test(n)&&n!==e.local.name&&(r.set(e.local.name,n),i.push({s:e.start,e:e.end,n:"* as "+n,k:e.local.name}))}for(const[t,e]of s)r.set(t,e);if(o.has("__top__"))for(const[t,e]of o.get("__top__"))r.set(t,e);const a=new Set(r.values());for(const[t]of[...r])a.has(t)&&r.delete(t);{const _u=new Set;for(const[k,v]of[...r])_u.has(v)?(r.delete(k),i=i.filter(x=>x.k!==k)):_u.add(v)}const c=[],p=[],l=[];const m=t=>{const e=t.params.map(t=>"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&"Identifier"===t.left?.type?t.left.name:"RestElement"===t.type&&"Identifier"===t.argument?.type?t.argument.name:null).filter(Boolean),s=new Map;if(t.id?.name){const r=n.get(t.id.name);if(r?.params)for(let t=0;t<Math.min(e.length,r.params.length);t++)e[t]!==r.params[t].name&&s.set(e[t],r.params[t].name);const i=o.get(t.id.name);if(i)for(const[t,e]of i)s.has(t)||s.set(t,e);if(s.size>0)for(const e of t.params){const t="Identifier"===e.type?e:"AssignmentPattern"===e.type&&"Identifier"===e.left?.type?e.left:null;t&&s.has(t.name)&&p.push({s:t.start,e:t.end,n:s.get(t.name)})}}const r=function(t){const e=new Set;return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.add(t.id.name)},CatchClause(t){t.param&&"Identifier"===t.param.type&&e.add(t.param.name)}}),e}(t.body);for(const t of e)r.add(t);const _tgts=Array.from(s.values());for(const t of _tgts){if(r.has(t)&&!s.has(t)){let n=t+"_";while(r.has(n)||_tgts.includes(n))n+="_";s.set(t,n)}}const i=t.id?.name&&o.get(t.id.name);for(const e of function(t){const e=[];return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.push(t.id)},CatchClause(t){t.param&&"Identifier"===t.param.type&&e.push(t.param)}}),e}(t.body)){const t=s.has(e.name)?s.get(e.name):i?.get(e.name);t&&l.push({s:e.start,e:e.end,n:t})}c.push({s:t.params.length>0?t.params[0].start:t.body.start,e:t.body.end,p:r,r:s})};f(e,{FunctionDeclaration:m,FunctionExpression:m,ArrowFunctionExpression:m});for(const t of e.body)if("VariableDeclaration"===t.type)for(const e of t.declarations)e.id&&"Identifier"===e.id.type&&r.has(e.id.name)&&l.push({s:e.id.start,e:e.id.end,n:r.get(e.id.name)});const d=[...i,...p,...l];u(e,{Identifier(t,e,n){const s=n[n.length-2];if("MemberExpression"===s?.type&&s.property===t&&!s.computed)return;if("Property"===s?.type&&s.key===t&&!s.computed&&s.value!==t)return;if("ExportSpecifier"===s?.type)return;const o=t.name;const scopes=c.filter(e=>t.start>=e.s&&t.end<=e.e).sort((a,b)=>(a.e-a.s)-(b.e-b.s));let mapped=!1,isLocal=!1;for(const e of scopes){if(e.r.has(o)){d.push({s:t.start,e:t.end,n:e.r.get(o)});mapped=!0;break}if(e.p.has(o)){isLocal=!0;break}}if(!mapped&&!isLocal&&r.has(o)){d.push({s:t.start,e:t.end,n:r.get(o)})}}});d.sort((t,e)=>e.s-t.s);let h=t;for(const t of d)h=h.slice(0,t.s)+t.n+h.slice(t.e);return h}
@@ -0,0 +1,7 @@
1
+ --- src/compact/framework-references.js ---
2
+ @sig 05360f4a
3
+ fetchReference(t)→o,u.get,Date.now,AbortSignal.timeout,fetch,e.text|fetch remote markdown reference with caching and local fallback
4
+ listAvailable()→Object.keys,r,t,r.endsWith,a,e.add|list available framework references from cache and local dir
5
+ export getFrameworkReference(options:Object=)→listAvailable,t.includes,fetchReference,r.split,i,o.includes|get full reference docs for a framework or auto-detect
6
+ PATTERNS: in-memory caching with TTL|fetch with AbortSignal timeout
7
+ EDGE_CASES: gracefully handles fetch timeouts and write failures
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/framework-references.ctx
1
+ // @ctx framework-references.ctx
2
2
  import{readFileSync as e,readdirSync as t,existsSync as r,writeFileSync as n}from"fs";import{join as o,basename as a,dirname as s}from"path";import{fileURLToPath as c}from"url";import{detectProjectRuleSets as i}from"../analysis/custom-rules.js";
3
3
  const f=s(c(import.meta.url)),m=o(f,"..","..","docs","references"),l={"symbiote-3x":"https://raw.githubusercontent.com/symbiotejs/symbiote.js/main/AI_REFERENCE.md"},u=new Map;async function fetchReference(t){const a=l[t],s=o(m,`${t}.md`),c=u.get(t);if(c&&Date.now()-c.fetchedAt<36e5)return{content:c.content,source:"cache"};if(a)try{const e=await fetch(a,{signal:AbortSignal.timeout(5e3)});if(e.ok){const r=await e.text();u.set(t,{content:r,fetchedAt:Date.now()});try{n(s,r,"utf-8")}catch(e){}return{content:r,source:`github (${a})`}}}catch(e){}if(r(s)){const r=e(s,"utf-8");return u.set(t,{content:r,fetchedAt:Date.now()}),{content:r,source:"local"}}return{content:"",source:"not_found"}}const d={"symbiote-3x":"symbiote-3x","symbiote-2x":"symbiote-3x"};function listAvailable(){const e=new Set(Object.keys(l));if(r(m))for(const r of t(m))r.endsWith(".md")&&e.add(a(r,".md"));return[...e]}
4
4
  export async function getFrameworkReference(e={}){const t=listAvailable();if(e.framework){if(!t.includes(e.framework))return{error:`Framework reference '${e.framework}' not found`,available:t};const{content:r,source:n}=await fetchReference(e.framework);return r?{framework:e.framework,source:n,lines:r.split("\n").length,content:r}:{error:`Failed to load reference '${e.framework}'`,available:t}}if(e.path){const{detected:r,reasons:n}=i(e.path),o=[];for(const e of r){const r=d[e];r&&t.includes(r)&&!o.includes(r)&&o.push(r)}if(0===o.length)return{error:"No framework references found for this project",detected:r,reasons:n,available:t};
@@ -0,0 +1,6 @@
1
+ --- src/compact/instructions.js ---
2
+ @sig 7e055438
3
+ export getInstructions()|return constant string containing AI agent guidelines
4
+ PATTERNS: constant export
5
+ EDGE_CASES: none
6
+ export AGENT_INSTRUCTIONS|compact mode agent instructions text
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/instructions.ctx
1
+ // @ctx instructions.ctx
2
2
  import{readFileSync}from"fs";import{join,dirname}from"path";import{fileURLToPath}from"url";
3
3
  const __dir=dirname(fileURLToPath(import.meta.url));
4
4
  const _mdPath=join(__dir,"..","..","docs","AGENT_INSTRUCTIONS.md");
@@ -0,0 +1,4 @@
1
+ export parseCtxParams(paramStr:string)|parse ctx param string into structured array|supports name:type, optional=, rest...
2
+ export buildJSDocBlock(entry:Object)|generate JSDoc from parsed entry|skips @param for all-minified helpers
3
+ export buildJSDocFromRaw(entry:Object)|generate JSDoc from raw ctx entry|params as unparsed string
4
+ sanitize(text:string)|escape */ in JSDoc text
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/jsdoc-builder.ctx
1
+ // @ctx jsdoc-builder.ctx
2
2
  export function parseCtxParams(t){return t&&t.trim()?t.split(",").map(t=>{const e=t.trim();if(!e)return null;const n=e.match(/^(\w+)(\?)?(?::(\w[\w<>\[\]|.]*))?(\=)?$/);if(n)return{name:n[1],type:n[3]||null,optional:!(!n[2]&&!n[4])};const s=e.match(/^(\w+)(=)?$/);return s?{name:s[1],type:null,optional:!!s[2]}:"..."===e?{name:"args",type:null,rest:!0}:{name:e.replace(/[=?:].*/g,""),type:null}}).filter(Boolean):[]}
3
3
  function sanitize(t){return t.replace(/\*\//g,"*\\/")}
4
4
  export function buildJSDocBlock(t){const e=["/**"];if(t.description&&"{DESCRIBE}"!==t.description&&e.push(` * ${sanitize(t.description)}`),t.params&&t.params.length>0&&!t.params.every(p=>!p.type&&p.name.length<=1))for(const n of t.params){const t=n.type||"*",s=n.optional?`[${n.name}]`:n.name;e.push(` * @param {${t}} ${s}`)}return t.returnType&&e.push(` * @returns {${t.returnType}}`),e.push(" */"),e.join("\n")}
@@ -0,0 +1,8 @@
1
+ --- src/compact/mode-config.js ---
2
+ @sig 84ca6758
3
+ export getConfig(rootPath:string)→i,r,e,JSON.parse|read .context/config.json, merge with defaults
4
+ export setConfig(rootPath:string,config:Object)→i,c,r,o,getConfig,JSON.stringify|save mode config|validates mode 1-4
5
+ export getModeDescription(mode:number)|return human-readable desc for mode 1-4
6
+ export getModeWorkflow(mode:number)|return read/edit/docs/validate workflow per mode
7
+ PATTERNS: config merge with DEFAULTS|.context/config.json storage
8
+ EDGE_CASES: missing config→defaults|invalid mode→throw error|missing dir→mkdirSync
@@ -1,4 +1,4 @@
1
- // @ctx .context/src/compact/mode-config.ctx
1
+ // @ctx mode-config.ctx
2
2
  import{readFileSync as e,writeFileSync as t,existsSync as r,mkdirSync as o}from"fs";
3
3
  import{join as i,dirname as c}from"path";
4
4
  const n=".context/config.json",a={mode:2,beautify:!0,autoValidate:!1,stripJSDoc:!1};
@@ -0,0 +1,6 @@
1
+ --- src/compact/split-declarations.js ---
2
+ @sig 66570947
3
+ export splitDeclarations(code:string)→t,n.push,n.sort,r.slice,o.push,o.filter|split minified blob into one-declaration-per-line using AST
4
+ export isSingleLineBlob(code:string)→r.split,t.startsWith,t|check if code has >3 declarations on ≤2 lines
5
+ PATTERNS: Acorn AST node.start boundaries for splitting
6
+ EDGE_CASES: (internal utility)