project-graph-mcp 1.3.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +223 -17
  2. package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +87 -30
  3. package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +23 -8
  4. package/package.json +12 -8
  5. package/src/.project-graph-cache.json +1 -0
  6. package/src/analysis/analysis-cache.js +7 -0
  7. package/src/analysis/complexity.js +14 -0
  8. package/src/analysis/custom-rules.js +36 -0
  9. package/src/analysis/db-analysis.js +9 -0
  10. package/src/analysis/dead-code.js +19 -0
  11. package/src/analysis/full-analysis.js +18 -0
  12. package/src/analysis/jsdoc-checker.js +24 -0
  13. package/src/analysis/jsdoc-generator.js +10 -0
  14. package/src/analysis/large-files.js +11 -0
  15. package/src/analysis/outdated-patterns.js +12 -0
  16. package/src/analysis/similar-functions.js +16 -0
  17. package/src/analysis/test-annotations.js +21 -0
  18. package/src/analysis/type-checker.js +8 -0
  19. package/src/analysis/undocumented.js +14 -0
  20. package/src/cli/cli-handlers.js +4 -0
  21. package/src/cli/cli.js +5 -0
  22. package/src/compact/ai-context.js +7 -0
  23. package/src/compact/compact.js +18 -0
  24. package/src/compact/compress.js +13 -0
  25. package/src/compact/ctx-to-jsdoc.js +29 -0
  26. package/src/compact/doc-dialect.js +30 -0
  27. package/src/compact/expand.js +37 -0
  28. package/src/compact/framework-references.js +5 -0
  29. package/src/compact/instructions.js +3 -0
  30. package/src/compact/mode-config.js +8 -0
  31. package/src/compact/validate-pipeline.js +9 -0
  32. package/src/core/event-bus.js +9 -0
  33. package/src/core/filters.js +14 -0
  34. package/src/core/graph-builder.js +12 -0
  35. package/src/core/parser.js +31 -0
  36. package/src/core/workspace.js +8 -0
  37. package/src/lang/lang-go.js +17 -0
  38. package/src/lang/lang-python.js +12 -0
  39. package/src/lang/lang-sql.js +23 -0
  40. package/src/lang/lang-typescript.js +9 -0
  41. package/src/lang/lang-utils.js +4 -0
  42. package/src/mcp/mcp-server.js +17 -0
  43. package/src/mcp/tool-defs.js +3 -0
  44. package/src/mcp/tools.js +25 -0
  45. package/src/network/backend-lifecycle.js +19 -0
  46. package/src/network/backend.js +5 -0
  47. package/src/network/local-gateway.js +23 -0
  48. package/src/network/mdns.js +13 -0
  49. package/src/network/server.js +10 -0
  50. package/src/network/web-server.js +34 -0
  51. package/vendor/terser.mjs +49 -0
  52. package/web/.project-graph-cache.json +1 -0
  53. package/web/app.js +16 -0
  54. package/web/components/code-block.js +3 -0
  55. package/web/components/quick-open.js +5 -0
  56. package/web/dashboard-state.js +3 -0
  57. package/web/dashboard.html +27 -0
  58. package/web/dashboard.js +8 -0
  59. package/web/highlight.js +13 -0
  60. package/web/index.html +35 -0
  61. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  62. package/web/panels/ActionBoard/ActionBoard.js +4 -0
  63. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  64. package/web/panels/EventItem/EventItem.css.js +1 -0
  65. package/web/panels/EventItem/EventItem.js +4 -0
  66. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  67. package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
  68. package/web/panels/ProjectItem/ProjectItem.js +5 -0
  69. package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
  70. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  71. package/web/panels/ProjectList/ProjectList.js +4 -0
  72. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  73. package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
  74. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  75. package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
  76. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  77. package/web/panels/code-viewer.js +5 -0
  78. package/web/panels/ctx-panel.js +4 -0
  79. package/web/panels/dep-graph.js +6 -0
  80. package/web/panels/file-tree.js +188 -0
  81. package/web/panels/health-panel.js +3 -0
  82. package/web/panels/live-monitor.js +3 -0
  83. package/web/state.js +17 -0
  84. package/web/style.css +157 -0
  85. package/references/symbiote-3x.md +0 -834
  86. package/src/cli-handlers.js +0 -140
  87. package/src/cli.js +0 -83
  88. package/src/complexity.js +0 -223
  89. package/src/custom-rules.js +0 -583
  90. package/src/db-analysis.js +0 -194
  91. package/src/dead-code.js +0 -468
  92. package/src/filters.js +0 -227
  93. package/src/framework-references.js +0 -177
  94. package/src/full-analysis.js +0 -174
  95. package/src/graph-builder.js +0 -299
  96. package/src/instructions.js +0 -175
  97. package/src/jsdoc-generator.js +0 -214
  98. package/src/lang-go.js +0 -285
  99. package/src/lang-python.js +0 -197
  100. package/src/lang-sql.js +0 -309
  101. package/src/lang-typescript.js +0 -190
  102. package/src/lang-utils.js +0 -124
  103. package/src/large-files.js +0 -162
  104. package/src/mcp-server.js +0 -468
  105. package/src/outdated-patterns.js +0 -295
  106. package/src/parser.js +0 -452
  107. package/src/server.js +0 -28
  108. package/src/similar-functions.js +0 -278
  109. package/src/test-annotations.js +0 -301
  110. package/src/tool-defs.js +0 -525
  111. package/src/tools.js +0 -470
  112. package/src/undocumented.js +0 -260
  113. package/src/workspace.js +0 -70
@@ -0,0 +1 @@
1
+ {"version":1,"path":"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src","mtimes":{"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/analysis-cache.js":1775932068076.741,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/complexity.js":1775932068098.837,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/custom-rules.js":1775932068138.1648,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/db-analysis.js":1775932068151.1123,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/dead-code.js":1775932068179.2783,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/full-analysis.js":1775932068199.422,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/jsdoc-checker.js":1775932068214.1042,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/jsdoc-generator.js":1775932068226.255,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/large-files.js":1775932068231.2554,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/outdated-patterns.js":1775932068241.3853,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/similar-functions.js":1775932068258.163,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/test-annotations.js":1775932068268.5154,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/type-checker.js":1775932068273.1277,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/undocumented.js":1775932068287.6243,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/cli/cli-handlers.js":1775932068298.3313,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/cli/cli.js":1775932068300.5159,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ai-context.js":1775932068307.1665,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compact.js":1775932562256.5852,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compress.js":1775932068329.118,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ctx-to-jsdoc.js":1775932068348.9448,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/doc-dialect.js":1775932068402.0254,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/expand.js":1775932068432.0977,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/framework-references.js":1775932068436.373,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/instructions.js":1775932068437.6816,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/mode-config.js":1775932068441.0076,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/validate-pipeline.js":1775932068445.2222,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/event-bus.js":1775932068446.521,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/filters.js":1775932068453.1003,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/graph-builder.js":1775932068467.494,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/parser.js":1775932068524.0032,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/workspace.js":1775932068530.74,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/lang/lang-go.js":1775932068546.679,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/lang/lang-python.js":1775932068554.9434,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/lang/lang-sql.js":1775932068573.637,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/lang/lang-typescript.js":1775932068582.2983,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/lang/lang-utils.js":1775932068585.4934,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/mcp-server.js":1775932068611.3706,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/tool-defs.js":1775932068615.0066,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/tools.js":1775932068628.6646,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/backend-lifecycle.js":1775932068638.9226,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/backend.js":1775932249992.6167,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/local-gateway.js":1775932068657.378,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/mdns.js":1775932068663.7195,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/server.js":1775932211325.8235,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/web-server.js":1775937659045.6243},"graph":{"v":1,"legend":{"computeContentHash":"CH","getCachePath":"CP","readCache":"rC","writeCache":"wC","isCacheValid":"CV","findJSFiles":"JSF","calculateComplexity":"cC","getRating":"gR","analyzeComplexityFile":"CF","analyzeFile":"aF","getComplexity":"gC","parseGraphignore":"pG","isGraphignored":"iG","loadRuleSets":"RS","saveRuleSet":"RS1","findFiles":"fF","isExcluded":"iE","isInStringOrComment":"ISO","isWithinContext":"WC","checkFileAgainstRule":"FAR","getCustomRules":"CR","setCustomRule":"CR1","deleteCustomRule":"CR2","detectProjectRuleSets":"PRS","checkCustomRules":"CR3","getDBSchema":"DBS","getTableUsage":"TU","getDBDeadTables":"DBD","collectReferencedColumns":"RC","findProjectRoot":"PR","analyzeFileLocals":"FL","getDeadCode":"DC","calculateHealthScore":"HS","runCacheableAnalyses":"CA","aggregateComplexity":"aC","aggregateUndocumented":"aU","aggregateJSDoc":"JSD","getFullAnalysis":"FA","getAnalysisSummaryOnly":"ASO","extractJSDocComments":"JSD1","findJSDocBefore":"JSD2","extractParamName":"PN","inferTypeFromDefault":"TFD","hasReturnValue":"RV","validateFunction":"vF","checkJSDocFile":"JSD3","checkJSDocConsistency":"JSD4","generateJSDoc":"JSD5","buildJSDoc":"JSD6","inferParamType":"PT","generateJSDocFor":"JSD7","getLargeFiles":"LF","analyzeFilePatterns":"FP","analyzePackageJson":"PJ","getOutdatedPatterns":"OP","extractSignatures":"eS","buildSignature":"bS","hashBodyStructure":"BS","calculateSimilarity":"cS","getSimilarFunctions":"SF","findCtxMdFiles":"CMF","parseAnnotations":"pA","groupByName":"BN","getAllFeatures":"AF","getPendingTests":"PT1","markTestPassed":"TP","markTestFailed":"TF","updateTestState":"TS","getTestSummary":"TS1","resetTestState":"TS2","detectTsc":"dT","parseDiagnosticLine":"DL","buildArgs":"bA","checkTypes":"cT","extractComments":"eC","checkMissing":"cM","checkUndocumentedFile":"UF","getUndocumented":"gU","getUndocumentedSummary":"US","getArg":"gA","getPath":"gP","printHelp":"pH","runCLI":"CLI","estimateTokens":"eT","getAiContext":"AC","walkJSFiles":"JSF1","addTopLevelNewlines":"TLN","resolveCtxPath":"CP1","compactFile":"cF","beautifyFile":"bF","compactProject":"cP","expandProject":"eP","extractLegend":"eL","compressFile":"cF1","editCompressed":"eC1","findSymbolRange":"SR","parseCtxFile":"CF1","buildJSDocBlock":"JSD8","findCtxFile":"CF2","findExportStart":"ES","injectJSDoc":"JSD9","stripJSDoc":"JSD10","splitTopLevelParams":"TLP","validateCtxContracts":"CC","generateDocDialect":"DD","walkCtxFiles":"CF3","resolveCtxMdPath":"CMP","readContextDocs":"CD","getProjectDocs":"PD","computeSignature":"cS1","parseCtxDescriptions":"CD1","checkStaleness":"cS2","buildFileTemplate":"FT","generateContextFiles":"CF4","processFileCtx":"FC","parseCtxSignatures":"CS","parseCtxParams":"CP2","extractReturnType":"RT","sanitizeJSDocText":"JSD11","parseCtxVars":"CV1","parseCtxNames":"CN","collectLocals":"cL","collectLocalDecls":"LD","restoreNames":"rN","expandFile":"eF","resolveCtx":"rC1","fetchReference":"fR","listAvailable":"lA","getFrameworkReference":"FR","getInstructions":"gI","getConfig":"gC1","setConfig":"sC","getModeDescription":"MD","getModeWorkflow":"MW","validatePipeline":"vP","emitToolCall":"TC","emitToolResult":"TR","onToolCall":"TC1","onToolResult":"TR1","removeToolListener":"TL","getFilters":"gF","setFilters":"sF","addExcludes":"aE","removeExcludes":"rE","resetFilters":"rF","parseGitignore":"pG1","shouldExcludeDir":"ED","shouldExcludeFile":"EF","matchWildcard":"mW","matchGitignorePattern":"GP","minifyLegend":"mL","createShortName":"SN","buildGraph":"bG","createSkeleton":"cS3","parseFile":"pF","extractCallsAndSQL":"CAS","getTagName":"TN","getCallMethodName":"CMN","extractStringValue":"SV","templateToString":"TS3","discoverSubProjects":"SP","parseProject":"pP","parseFileByExtension":"FBE","isSourceFile":"SF1","findAllProjectFiles":"APF","buildJSDocTypeMap":"JSD12","findJSDocForNode":"JSD13","enrichParamsWithTypes":"PWT","setRoots":"sR","getWorkspaceRoot":"WR","resolvePath":"rP","parseGo":"pG2","extractImports":"eI","getBody":"gB","extractCalls":"eC2","parsePython":"pP1","isSQLString":"SQL","isValidTableName":"VTN","extractSQLFromString":"SQL1","parseSQL":"SQL2","parseColumns":"pC","splitByTopLevelComma":"BTL","extractSQLFromCode":"SQL3","extractORMFromCode":"ORM","parseTypeScript":"TS4","extractParams":"eP1","stripStringsAndComments":"SAC","createServer":"cS4","startStdioServer":"SS","saveDiskCache":"DC1","loadDiskCache":"DC2","getGraph":"gG","detectChanges":"dC","snapshotMtimes":"sM","getSkeleton":"gS","getFocusZone":"FZ","expand":"ex","deps":"de","usages":"us","extractMethod":"eM","getCallChain":"CC1","invalidateCache":"iC","getPortFilePath":"PFP","readPortFile":"PF","writePortFile":"PF1","removePortFile":"PF2","listBackends":"lB","ensureBackend":"eB","encodeClientFrame":"CF5","decodeFrame":"dF","startStdioProxy":"SP1","cleanup":"cl","readRegistry":"rR","writeRegistry":"wR","registerService":"rS","resolveBackend":"rB","readGatewayPid":"GP1","isGatewayRunning":"GR","getGatewayPort":"GP2","startListening":"sL","ensureGateway":"eG","stopGateway":"sG","registerLocal":"rL","registerDnsSd":"DS","tryAvahi":"tA","registerMcast":"rM","serveStatic":"sS","computeWSAccept":"WSA","encodeWSFrame":"WSF","decodeWSFrame":"WSF1","broadcastRPC":"RPC","patchState":"pS","ensureSkeleton":"eS1","hasActiveClients":"AC1","resetShutdownTimer":"ST","startShutdownTimer":"ST1","touchActivity":"tA1","handleAPI":"API","startWebServer":"WS"},"reverseLegend":{"CH":"computeContentHash","CP":"getCachePath","rC":"readCache","wC":"writeCache","CV":"isCacheValid","JSF":"findJSFiles","cC":"calculateComplexity","gR":"getRating","CF":"analyzeComplexityFile","aF":"analyzeFile","gC":"getComplexity","pG":"parseGraphignore","iG":"isGraphignored","RS":"loadRuleSets","RS1":"saveRuleSet","fF":"findFiles","iE":"isExcluded","ISO":"isInStringOrComment","WC":"isWithinContext","FAR":"checkFileAgainstRule","CR":"getCustomRules","CR1":"setCustomRule","CR2":"deleteCustomRule","PRS":"detectProjectRuleSets","CR3":"checkCustomRules","DBS":"getDBSchema","TU":"getTableUsage","DBD":"getDBDeadTables","RC":"collectReferencedColumns","PR":"findProjectRoot","FL":"analyzeFileLocals","DC":"getDeadCode","HS":"calculateHealthScore","CA":"runCacheableAnalyses","aC":"aggregateComplexity","aU":"aggregateUndocumented","JSD":"aggregateJSDoc","FA":"getFullAnalysis","ASO":"getAnalysisSummaryOnly","JSD1":"extractJSDocComments","JSD2":"findJSDocBefore","PN":"extractParamName","TFD":"inferTypeFromDefault","RV":"hasReturnValue","vF":"validateFunction","JSD3":"checkJSDocFile","JSD4":"checkJSDocConsistency","JSD5":"generateJSDoc","JSD6":"buildJSDoc","PT":"inferParamType","JSD7":"generateJSDocFor","LF":"getLargeFiles","FP":"analyzeFilePatterns","PJ":"analyzePackageJson","OP":"getOutdatedPatterns","eS":"extractSignatures","bS":"buildSignature","BS":"hashBodyStructure","cS":"calculateSimilarity","SF":"getSimilarFunctions","CMF":"findCtxMdFiles","pA":"parseAnnotations","BN":"groupByName","AF":"getAllFeatures","PT1":"getPendingTests","TP":"markTestPassed","TF":"markTestFailed","TS":"updateTestState","TS1":"getTestSummary","TS2":"resetTestState","dT":"detectTsc","DL":"parseDiagnosticLine","bA":"buildArgs","cT":"checkTypes","eC":"extractComments","cM":"checkMissing","UF":"checkUndocumentedFile","gU":"getUndocumented","US":"getUndocumentedSummary","gA":"getArg","gP":"getPath","pH":"printHelp","CLI":"runCLI","eT":"estimateTokens","AC":"getAiContext","JSF1":"walkJSFiles","TLN":"addTopLevelNewlines","CP1":"resolveCtxPath","cF":"compactFile","bF":"beautifyFile","cP":"compactProject","eP":"expandProject","eL":"extractLegend","cF1":"compressFile","eC1":"editCompressed","SR":"findSymbolRange","CF1":"parseCtxFile","JSD8":"buildJSDocBlock","CF2":"findCtxFile","ES":"findExportStart","JSD9":"injectJSDoc","JSD10":"stripJSDoc","TLP":"splitTopLevelParams","CC":"validateCtxContracts","DD":"generateDocDialect","CF3":"walkCtxFiles","CMP":"resolveCtxMdPath","CD":"readContextDocs","PD":"getProjectDocs","cS1":"computeSignature","CD1":"parseCtxDescriptions","cS2":"checkStaleness","FT":"buildFileTemplate","CF4":"generateContextFiles","FC":"processFileCtx","CS":"parseCtxSignatures","CP2":"parseCtxParams","RT":"extractReturnType","JSD11":"sanitizeJSDocText","CV1":"parseCtxVars","CN":"parseCtxNames","cL":"collectLocals","LD":"collectLocalDecls","rN":"restoreNames","eF":"expandFile","rC1":"resolveCtx","fR":"fetchReference","lA":"listAvailable","FR":"getFrameworkReference","gI":"getInstructions","gC1":"getConfig","sC":"setConfig","MD":"getModeDescription","MW":"getModeWorkflow","vP":"validatePipeline","TC":"emitToolCall","TR":"emitToolResult","TC1":"onToolCall","TR1":"onToolResult","TL":"removeToolListener","gF":"getFilters","sF":"setFilters","aE":"addExcludes","rE":"removeExcludes","rF":"resetFilters","pG1":"parseGitignore","ED":"shouldExcludeDir","EF":"shouldExcludeFile","mW":"matchWildcard","GP":"matchGitignorePattern","mL":"minifyLegend","SN":"createShortName","bG":"buildGraph","cS3":"createSkeleton","pF":"parseFile","CAS":"extractCallsAndSQL","TN":"getTagName","CMN":"getCallMethodName","SV":"extractStringValue","TS3":"templateToString","SP":"discoverSubProjects","pP":"parseProject","FBE":"parseFileByExtension","SF1":"isSourceFile","APF":"findAllProjectFiles","JSD12":"buildJSDocTypeMap","JSD13":"findJSDocForNode","PWT":"enrichParamsWithTypes","sR":"setRoots","WR":"getWorkspaceRoot","rP":"resolvePath","pG2":"parseGo","eI":"extractImports","gB":"getBody","eC2":"extractCalls","pP1":"parsePython","SQL":"isSQLString","VTN":"isValidTableName","SQL1":"extractSQLFromString","SQL2":"parseSQL","pC":"parseColumns","BTL":"splitByTopLevelComma","SQL3":"extractSQLFromCode","ORM":"extractORMFromCode","TS4":"parseTypeScript","eP1":"extractParams","SAC":"stripStringsAndComments","cS4":"createServer","SS":"startStdioServer","DC1":"saveDiskCache","DC2":"loadDiskCache","gG":"getGraph","dC":"detectChanges","sM":"snapshotMtimes","gS":"getSkeleton","FZ":"getFocusZone","ex":"expand","de":"deps","us":"usages","eM":"extractMethod","CC1":"getCallChain","iC":"invalidateCache","PFP":"getPortFilePath","PF":"readPortFile","PF1":"writePortFile","PF2":"removePortFile","lB":"listBackends","eB":"ensureBackend","CF5":"encodeClientFrame","dF":"decodeFrame","SP1":"startStdioProxy","cl":"cleanup","rR":"readRegistry","wR":"writeRegistry","rS":"registerService","rB":"resolveBackend","GP1":"readGatewayPid","GR":"isGatewayRunning","GP2":"getGatewayPort","sL":"startListening","eG":"ensureGateway","sG":"stopGateway","rL":"registerLocal","DS":"registerDnsSd","tA":"tryAvahi","rM":"registerMcast","sS":"serveStatic","WSA":"computeWSAccept","WSF":"encodeWSFrame","WSF1":"decodeWSFrame","RPC":"broadcastRPC","pS":"patchState","eS1":"ensureSkeleton","AC1":"hasActiveClients","ST":"resetShutdownTimer","ST1":"startShutdownTimer","tA1":"touchActivity","API":"handleAPI","WS":"startWebServer"},"stats":{"files":45,"classes":0,"functions":260,"tables":0},"nodes":{"CH":{"t":"F","e":true,"f":"analysis/analysis-cache.js"},"CP":{"t":"F","e":true,"f":"analysis/analysis-cache.js"},"rC":{"t":"F","e":true,"f":"analysis/analysis-cache.js"},"wC":{"t":"F","e":true,"f":"analysis/analysis-cache.js"},"CV":{"t":"F","e":true,"f":"analysis/analysis-cache.js"},"JSF":{"t":"F","e":true,"f":"core/parser.js"},"cC":{"t":"F","e":false,"f":"analysis/complexity.js"},"gR":{"t":"F","e":false,"f":"analysis/complexity.js"},"CF":{"t":"F","e":true,"f":"analysis/complexity.js"},"aF":{"t":"F","e":false,"f":"analysis/large-files.js"},"gC":{"t":"F","e":true,"f":"analysis/complexity.js"},"pG":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"iG":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"RS":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"RS1":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"fF":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"iE":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"ISO":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"WC":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"FAR":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"CR":{"t":"F","e":true,"f":"analysis/custom-rules.js"},"CR1":{"t":"F","e":true,"f":"analysis/custom-rules.js"},"CR2":{"t":"F","e":true,"f":"analysis/custom-rules.js"},"PRS":{"t":"F","e":true,"f":"analysis/custom-rules.js"},"CR3":{"t":"F","e":true,"f":"analysis/custom-rules.js"},"DBS":{"t":"F","e":true,"f":"analysis/db-analysis.js"},"TU":{"t":"F","e":true,"f":"analysis/db-analysis.js"},"DBD":{"t":"F","e":true,"f":"analysis/db-analysis.js"},"RC":{"t":"F","e":false,"f":"analysis/db-analysis.js"},"PR":{"t":"F","e":false,"f":"analysis/dead-code.js"},"FL":{"t":"F","e":false,"f":"analysis/dead-code.js"},"DC":{"t":"F","e":true,"f":"analysis/dead-code.js"},"HS":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"CA":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"aC":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"aU":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"JSD":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"FA":{"t":"F","e":true,"f":"analysis/full-analysis.js"},"ASO":{"t":"F","e":true,"f":"analysis/full-analysis.js"},"JSD1":{"t":"F","e":false,"f":"analysis/jsdoc-checker.js"},"JSD2":{"t":"F","e":false,"f":"analysis/undocumented.js"},"PN":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"TFD":{"t":"F","e":false,"f":"analysis/jsdoc-checker.js"},"RV":{"t":"F","e":false,"f":"analysis/jsdoc-checker.js"},"vF":{"t":"F","e":false,"f":"analysis/jsdoc-checker.js"},"JSD3":{"t":"F","e":true,"f":"analysis/jsdoc-checker.js"},"JSD4":{"t":"F","e":true,"f":"analysis/jsdoc-checker.js"},"JSD5":{"t":"F","e":false,"f":"compact/expand.js"},"JSD6":{"t":"F","e":false,"f":"analysis/jsdoc-generator.js"},"PT":{"t":"F","e":false,"f":"analysis/jsdoc-generator.js"},"JSD7":{"t":"F","e":true,"f":"analysis/jsdoc-generator.js"},"LF":{"t":"F","e":true,"f":"analysis/large-files.js"},"FP":{"t":"F","e":false,"f":"analysis/outdated-patterns.js"},"PJ":{"t":"F","e":false,"f":"analysis/outdated-patterns.js"},"OP":{"t":"F","e":true,"f":"analysis/outdated-patterns.js"},"eS":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"bS":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"BS":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"cS":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"SF":{"t":"F","e":true,"f":"analysis/similar-functions.js"},"CMF":{"t":"F","e":false,"f":"analysis/test-annotations.js"},"pA":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"BN":{"t":"F","e":false,"f":"analysis/test-annotations.js"},"AF":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"PT1":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"TP":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"TF":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"TS":{"t":"F","e":false,"f":"analysis/test-annotations.js"},"TS1":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"TS2":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"dT":{"t":"F","e":false,"f":"analysis/type-checker.js"},"DL":{"t":"F","e":false,"f":"analysis/type-checker.js"},"bA":{"t":"F","e":false,"f":"analysis/type-checker.js"},"cT":{"t":"F","e":true,"f":"analysis/type-checker.js"},"eC":{"t":"F","e":false,"f":"analysis/undocumented.js"},"cM":{"t":"F","e":false,"f":"analysis/undocumented.js"},"UF":{"t":"F","e":true,"f":"analysis/undocumented.js"},"gU":{"t":"F","e":true,"f":"analysis/undocumented.js"},"US":{"t":"F","e":true,"f":"analysis/undocumented.js"},"gA":{"t":"F","e":false,"f":"cli/cli-handlers.js"},"gP":{"t":"F","e":false,"f":"cli/cli-handlers.js"},"pH":{"t":"F","e":true,"f":"cli/cli.js"},"CLI":{"t":"F","e":true,"f":"cli/cli.js"},"eT":{"t":"F","e":false,"f":"compact/validate-pipeline.js"},"AC":{"t":"F","e":true,"f":"compact/ai-context.js"},"JSF1":{"t":"F","e":false,"f":"compact/validate-pipeline.js"},"TLN":{"t":"F","e":false,"f":"compact/compact.js"},"CP1":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"cF":{"t":"F","e":false,"f":"compact/compact.js"},"bF":{"t":"F","e":false,"f":"compact/compact.js"},"cP":{"t":"F","e":true,"f":"compact/compact.js"},"eP":{"t":"F","e":true,"f":"compact/expand.js"},"eL":{"t":"F","e":false,"f":"compact/compress.js"},"cF1":{"t":"F","e":true,"f":"compact/compress.js"},"eC1":{"t":"F","e":true,"f":"compact/compress.js"},"SR":{"t":"F","e":false,"f":"compact/compress.js"},"CF1":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"JSD8":{"t":"F","e":false,"f":"compact/ctx-to-jsdoc.js"},"CF2":{"t":"F","e":false,"f":"compact/ctx-to-jsdoc.js"},"ES":{"t":"F","e":false,"f":"compact/ctx-to-jsdoc.js"},"JSD9":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"JSD10":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"TLP":{"t":"F","e":false,"f":"compact/ctx-to-jsdoc.js"},"CC":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"DD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"CF3":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"CMP":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"CD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"PD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"cS1":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"CD1":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"cS2":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"FT":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"CF4":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"FC":{"t":"F","e":false,"f":"compact/doc-dialect.js"},"CS":{"t":"F","e":false,"f":"compact/expand.js"},"CP2":{"t":"F","e":false,"f":"compact/expand.js"},"RT":{"t":"F","e":false,"f":"compact/expand.js"},"JSD11":{"t":"F","e":false,"f":"compact/expand.js"},"CV1":{"t":"F","e":false,"f":"compact/expand.js"},"CN":{"t":"F","e":false,"f":"compact/expand.js"},"cL":{"t":"F","e":false,"f":"compact/expand.js"},"LD":{"t":"F","e":false,"f":"compact/expand.js"},"rN":{"t":"F","e":false,"f":"compact/expand.js"},"eF":{"t":"F","e":true,"f":"compact/expand.js"},"rC1":{"t":"F","e":false,"f":"compact/expand.js"},"fR":{"t":"F","e":false,"f":"compact/framework-references.js"},"lA":{"t":"F","e":false,"f":"compact/framework-references.js"},"FR":{"t":"F","e":true,"f":"compact/framework-references.js"},"gI":{"t":"F","e":true,"f":"compact/instructions.js"},"gC1":{"t":"F","e":true,"f":"compact/mode-config.js"},"sC":{"t":"F","e":true,"f":"compact/mode-config.js"},"MD":{"t":"F","e":true,"f":"compact/mode-config.js"},"MW":{"t":"F","e":true,"f":"compact/mode-config.js"},"vP":{"t":"F","e":true,"f":"compact/validate-pipeline.js"},"TC":{"t":"F","e":true,"f":"core/event-bus.js"},"TR":{"t":"F","e":true,"f":"core/event-bus.js"},"TC1":{"t":"F","e":true,"f":"core/event-bus.js"},"TR1":{"t":"F","e":true,"f":"core/event-bus.js"},"TL":{"t":"F","e":true,"f":"core/event-bus.js"},"gF":{"t":"F","e":true,"f":"core/filters.js"},"sF":{"t":"F","e":true,"f":"core/filters.js"},"aE":{"t":"F","e":true,"f":"core/filters.js"},"rE":{"t":"F","e":true,"f":"core/filters.js"},"rF":{"t":"F","e":true,"f":"core/filters.js"},"pG1":{"t":"F","e":true,"f":"core/filters.js"},"ED":{"t":"F","e":true,"f":"core/filters.js"},"EF":{"t":"F","e":true,"f":"core/filters.js"},"mW":{"t":"F","e":false,"f":"core/filters.js"},"GP":{"t":"F","e":false,"f":"core/filters.js"},"mL":{"t":"F","e":true,"f":"core/graph-builder.js"},"SN":{"t":"F","e":false,"f":"core/graph-builder.js"},"bG":{"t":"F","e":true,"f":"core/graph-builder.js"},"cS3":{"t":"F","e":true,"f":"core/graph-builder.js"},"pF":{"t":"F","e":true,"f":"core/parser.js"},"CAS":{"t":"F","e":false,"f":"core/parser.js"},"TN":{"t":"F","e":false,"f":"core/parser.js"},"CMN":{"t":"F","e":false,"f":"core/parser.js"},"SV":{"t":"F","e":false,"f":"core/parser.js"},"TS3":{"t":"F","e":false,"f":"core/parser.js"},"SP":{"t":"F","e":true,"f":"core/parser.js"},"pP":{"t":"F","e":true,"f":"core/parser.js"},"FBE":{"t":"F","e":false,"f":"core/parser.js"},"SF1":{"t":"F","e":false,"f":"core/parser.js"},"APF":{"t":"F","e":true,"f":"core/parser.js"},"JSD12":{"t":"F","e":false,"f":"core/parser.js"},"JSD13":{"t":"F","e":false,"f":"core/parser.js"},"PWT":{"t":"F","e":false,"f":"core/parser.js"},"sR":{"t":"F","e":true,"f":"core/workspace.js"},"WR":{"t":"F","e":true,"f":"core/workspace.js"},"rP":{"t":"F","e":true,"f":"core/workspace.js"},"pG2":{"t":"F","e":true,"f":"lang/lang-go.js"},"eI":{"t":"F","e":false,"f":"lang/lang-go.js"},"gB":{"t":"F","e":false,"f":"lang/lang-go.js"},"eC2":{"t":"F","e":false,"f":"lang/lang-go.js"},"pP1":{"t":"F","e":true,"f":"lang/lang-python.js"},"SQL":{"t":"F","e":true,"f":"lang/lang-sql.js"},"VTN":{"t":"F","e":false,"f":"lang/lang-sql.js"},"SQL1":{"t":"F","e":true,"f":"lang/lang-sql.js"},"SQL2":{"t":"F","e":true,"f":"lang/lang-sql.js"},"pC":{"t":"F","e":false,"f":"lang/lang-sql.js"},"BTL":{"t":"F","e":false,"f":"lang/lang-sql.js"},"SQL3":{"t":"F","e":true,"f":"lang/lang-sql.js"},"ORM":{"t":"F","e":true,"f":"lang/lang-sql.js"},"TS4":{"t":"F","e":true,"f":"lang/lang-typescript.js"},"eP1":{"t":"F","e":false,"f":"lang/lang-typescript.js"},"SAC":{"t":"F","e":true,"f":"lang/lang-utils.js"},"cS4":{"t":"F","e":true,"f":"mcp/mcp-server.js"},"SS":{"t":"F","e":true,"f":"mcp/mcp-server.js"},"DC1":{"t":"F","e":false,"f":"mcp/tools.js"},"DC2":{"t":"F","e":false,"f":"mcp/tools.js"},"gG":{"t":"F","e":true,"f":"mcp/tools.js"},"dC":{"t":"F","e":false,"f":"mcp/tools.js"},"sM":{"t":"F","e":false,"f":"mcp/tools.js"},"gS":{"t":"F","e":true,"f":"mcp/tools.js"},"FZ":{"t":"F","e":true,"f":"mcp/tools.js"},"ex":{"t":"F","e":true,"f":"mcp/tools.js"},"de":{"t":"F","e":true,"f":"mcp/tools.js"},"us":{"t":"F","e":true,"f":"mcp/tools.js"},"eM":{"t":"F","e":false,"f":"mcp/tools.js"},"CC1":{"t":"F","e":true,"f":"mcp/tools.js"},"iC":{"t":"F","e":true,"f":"mcp/tools.js"},"PFP":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"PF":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"PF1":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"PF2":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"lB":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"eB":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"CF5":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"dF":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"SP1":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"cl":{"t":"F","e":false,"f":"network/backend.js"},"rR":{"t":"F","e":false,"f":"network/local-gateway.js"},"wR":{"t":"F","e":false,"f":"network/local-gateway.js"},"rS":{"t":"F","e":true,"f":"network/local-gateway.js"},"rB":{"t":"F","e":false,"f":"network/local-gateway.js"},"GP1":{"t":"F","e":false,"f":"network/local-gateway.js"},"GR":{"t":"F","e":false,"f":"network/local-gateway.js"},"GP2":{"t":"F","e":true,"f":"network/local-gateway.js"},"sL":{"t":"F","e":false,"f":"network/local-gateway.js"},"eG":{"t":"F","e":false,"f":"network/local-gateway.js"},"sG":{"t":"F","e":false,"f":"network/local-gateway.js"},"rL":{"t":"F","e":true,"f":"network/mdns.js"},"DS":{"t":"F","e":false,"f":"network/mdns.js"},"tA":{"t":"F","e":false,"f":"network/mdns.js"},"rM":{"t":"F","e":false,"f":"network/mdns.js"},"sS":{"t":"F","e":false,"f":"network/web-server.js"},"WSA":{"t":"F","e":false,"f":"network/web-server.js"},"WSF":{"t":"F","e":false,"f":"network/web-server.js"},"WSF1":{"t":"F","e":false,"f":"network/web-server.js"},"RPC":{"t":"F","e":false,"f":"network/web-server.js"},"pS":{"t":"F","e":false,"f":"network/web-server.js"},"eS1":{"t":"F","e":false,"f":"network/web-server.js"},"AC1":{"t":"F","e":false,"f":"network/web-server.js"},"ST":{"t":"F","e":false,"f":"network/web-server.js"},"ST1":{"t":"F","e":false,"f":"network/web-server.js"},"tA1":{"t":"F","e":false,"f":"network/web-server.js"},"API":{"t":"F","e":false,"f":"network/web-server.js"},"WS":{"t":"F","e":true,"f":"network/web-server.js"}},"edges":[],"orphans":["calculateComplexity","getRating","analyzeFile","parseGraphignore","isGraphignored","loadRuleSets","saveRuleSet","findFiles","isExcluded","isInStringOrComment","isWithinContext","checkFileAgainstRule","collectReferencedColumns","findProjectRoot","analyzeFileLocals","calculateHealthScore","runCacheableAnalyses","aggregateComplexity","aggregateUndocumented","aggregateJSDoc","extractJSDocComments","findJSDocBefore","extractParamName","inferTypeFromDefault","hasReturnValue","validateFunction","generateJSDoc","buildJSDoc","inferParamType","analyzeFilePatterns","analyzePackageJson","extractSignatures","buildSignature","hashBodyStructure","calculateSimilarity","findCtxMdFiles","groupByName","updateTestState","detectTsc","parseDiagnosticLine","buildArgs","extractComments","checkMissing","getArg","getPath","estimateTokens","walkJSFiles","addTopLevelNewlines","resolveCtxPath","compactFile","beautifyFile","extractLegend","findSymbolRange","buildJSDocBlock","findCtxFile","findExportStart","splitTopLevelParams","walkCtxFiles","resolveCtxMdPath","computeSignature","parseCtxDescriptions","buildFileTemplate","processFileCtx","parseCtxSignatures","parseCtxParams","extractReturnType","sanitizeJSDocText","parseCtxVars","parseCtxNames","collectLocals","collectLocalDecls","restoreNames","resolveCtx","fetchReference","listAvailable","matchWildcard","matchGitignorePattern","createShortName","extractCallsAndSQL","getTagName","getCallMethodName","extractStringValue","templateToString","parseFileByExtension","isSourceFile","buildJSDocTypeMap","findJSDocForNode","enrichParamsWithTypes","extractImports","getBody","extractCalls","isValidTableName","parseColumns","splitByTopLevelComma","extractParams","saveDiskCache","loadDiskCache","detectChanges","snapshotMtimes","extractMethod","getPortFilePath","readPortFile","encodeClientFrame","decodeFrame","cleanup","readRegistry","writeRegistry","resolveBackend","readGatewayPid","isGatewayRunning","startListening","ensureGateway","stopGateway","registerDnsSd","tryAvahi","registerMcast","serveStatic","computeWSAccept","encodeWSFrame","decodeWSFrame","broadcastRPC","patchState","ensureSkeleton","hasActiveClients","resetShutdownTimer","startShutdownTimer","touchActivity","handleAPI"],"duplicates":{},"files":["analysis/analysis-cache.js","analysis/complexity.js","analysis/custom-rules.js","analysis/db-analysis.js","analysis/dead-code.js","analysis/full-analysis.js","analysis/jsdoc-checker.js","analysis/jsdoc-generator.js","analysis/large-files.js","analysis/outdated-patterns.js","analysis/similar-functions.js","analysis/test-annotations.js","analysis/type-checker.js","analysis/undocumented.js","cli/cli-handlers.js","cli/cli.js","compact/ai-context.js","compact/compact.js","compact/compress.js","compact/ctx-to-jsdoc.js","compact/doc-dialect.js","compact/expand.js","compact/framework-references.js","compact/instructions.js","compact/mode-config.js","compact/validate-pipeline.js","core/event-bus.js","core/filters.js","core/graph-builder.js","core/parser.js","core/workspace.js","lang/lang-go.js","lang/lang-python.js","lang/lang-sql.js","lang/lang-typescript.js","lang/lang-utils.js","mcp/mcp-server.js","mcp/tool-defs.js","mcp/tools.js","network/backend-lifecycle.js","network/backend.js","network/local-gateway.js","network/mdns.js","network/server.js","network/web-server.js"]}}
@@ -0,0 +1,7 @@
1
+ // @ctx .context/src/analysis/analysis-cache.ctx
2
+ import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as r}from"fs";import{join as c,dirname as a}from"path";import{createHash as i}from"crypto";
3
+ export function computeContentHash(t){return i("md5").update(t).digest("hex").slice(0,8)}
4
+ export function getCachePath(t,e){const n=e.replace(/\.[^.]+$/,".json");return c(t,".cache",n)}
5
+ export function readCache(e,n){const c=getCachePath(e,n);try{return r(c)?JSON.parse(t(c,"utf-8")):null}catch(t){return null}}
6
+ export function writeCache(t,r,c){const i=getCachePath(t,r);try{n(a(i),{recursive:!0}),e(i,JSON.stringify({...c,cachedAt:(new Date).toISOString()},null,2))}catch(t){}}
7
+ export function isCacheValid(t,e,n,r="content"){return!(!t||!t.sig||!t.contentHash||("sig"===r?t.sig!==e:t.sig!==e||t.contentHash!==n))}
@@ -0,0 +1,14 @@
1
+ // @ctx .context/src/analysis/complexity.ctx
2
+ import{readFileSync as t,readdirSync as e,statSync as i}from"fs";import{join as n,relative as o,resolve as r}from"path";import{parse as a}from"../../vendor/acorn.mjs";import*as l from"../../vendor/walk.mjs";import{shouldExcludeDir as s,shouldExcludeFile as c,parseGitignore as m}from"../core/filters.js";function findJSFiles(t,r=t){t===r&&m(r);
3
+ const a=[];try{for(const l of e(t)){const e=n(t,l),m=o(r,e);i(e).isDirectory()?s(l,m)||a.push(...findJSFiles(e,r)):!l.endsWith(".js")||l.endsWith(".css.js")||l.endsWith(".tpl.js")||c(l,m)||a.push(e)}}catch(t){}return a}
4
+ function calculateComplexity(t){let e=1;return l.simple(t,{IfStatement(){e++},ConditionalExpression(){e++},ForStatement(){e++},ForOfStatement(){e++},ForInStatement(){e++},WhileStatement(){e++},DoWhileStatement(){e++},SwitchCase(t){t.test&&e++},LogicalExpression(t){"&&"!==t.operator&&"||"!==t.operator||e++},BinaryExpression(t){"??"===t.operator&&e++},CatchClause(){e++}}),e}
5
+ function getRating(t){return t<=5?"low":t<=10?"moderate":t<=20?"high":"critical"}
6
+ export function analyzeComplexityFile(t,e){const i=[];
7
+ let n;try{n=a(t,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(t){return i}return l.simple(n,{FunctionDeclaration(t){if(!t.id)return;
8
+ const n=calculateComplexity(t.body);i.push({name:t.id.name,type:"function",file:e,line:t.loc.start.line,complexity:n,rating:getRating(n)})},ArrowFunctionExpression(t){if("BlockStatement"!==t.body.type)return;
9
+ const n=calculateComplexity(t.body);n>5&&i.push({name:"(arrow)",type:"function",file:e,line:t.loc.start.line,complexity:n,rating:getRating(n)})},MethodDefinition(t){if("method"!==t.kind)return;
10
+ const n=t.key.name||t.key.value,o=calculateComplexity(t.value.body);i.push({name:n,type:"method",file:e,line:t.loc.start.line,complexity:o,rating:getRating(o)})}}),i}
11
+ function analyzeFile(e,i){let n;try{n=t(e,"utf-8")}catch(t){return[]}return analyzeComplexityFile(n,o(i,e))}
12
+ export async function getComplexity(t,e={}){const i=e.minComplexity||1,n=e.onlyProblematic||!1,o=r(t),a=findJSFiles(t);
13
+ let l=[];for(const t of a)l.push(...analyzeFile(t,o));l=l.filter(t=>!(t.complexity<i||n&&("low"===t.rating||"moderate"===t.rating))),l.sort((t,e)=>e.complexity-t.complexity);
14
+ const s={low:l.filter(t=>"low"===t.rating).length,moderate:l.filter(t=>"moderate"===t.rating).length,high:l.filter(t=>"high"===t.rating).length,critical:l.filter(t=>"critical"===t.rating).length,average:l.length>0?Math.round(l.reduce((t,e)=>t+e.complexity,0)/l.length*10)/10:0};return{total:l.length,stats:s,items:l.slice(0,30)}}
@@ -0,0 +1,36 @@
1
+ // @ctx .context/src/analysis/custom-rules.ctx
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 i,dirname as c,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
+ const p=c(u(import.meta.url)),h=o(p,"..","..","rules");
4
+ let m=[];function parseGraphignore(t){m=[];
5
+ let s=t;for(;s!==c(s);){const t=o(s,".graphignore");if(n(t))try{const s=e(t,"utf-8");return void(m=s.split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")))}catch(e){}s=c(s)}}
6
+ function isGraphignored(e){const t=e.split("/").pop();for(const s of m)if(s.endsWith("*")){const n=s.slice(0,-1);if(e.startsWith(n)||t.startsWith(n))return!0}else if(s.startsWith("*")){const n=s.slice(1);if(e.endsWith(n)||t.endsWith(n))return!0}else if(e.includes(s)||t===s)return!0;return!1}
7
+ function loadRuleSets(){const t={};if(!n(h))return t;for(const n of s(h))if(n.endsWith(".json"))try{const s=e(o(h,n),"utf-8"),r=JSON.parse(s);t[r.name]=r}catch(e){}return t}
8
+ function saveRuleSet(e){const s=o(h,`${e.name}.json`);t(s,JSON.stringify(e,null,2))}
9
+ function findFiles(e,t,n=e){e===n&&(d(n),parseGraphignore(n));
10
+ const c=[],l=t.replace("*","");try{for(const u of s(e)){const s=o(e,u),d=i(n,s);r(s).isDirectory()?f(u,d)||c.push(...findFiles(s,t,n)):u.endsWith(l)&&(a(u,d)||isGraphignored(d)||c.push(s))}}catch(e){}return c}
11
+ function isExcluded(e,t=[]){for(const s of t){const t=s.replace("*","");if(e.endsWith(t))return!0}return!1}
12
+ function isInStringOrComment(e,t){const s=e.indexOf("//");if(-1!==s&&t>s)return!0;
13
+ let n=!1,r=null;for(let s=0;s<t;s++){const t=e[s],o=s>0?e[s-1]:"";n||'"'!==t&&"'"!==t&&"`"!==t?n&&t===r&&"\\"!==o&&(n=!1,r=null):(n=!0,r=t)}return n}
14
+ function isWithinContext(e,t,s){const n=s,r=`</${n.replace(/[<>]/g,"")}>`;
15
+ let o=0;for(let s=0;s<=t;s++){const t=e[s];
16
+ let i=0;for(;i<t.length;){const e=t.indexOf(n,i),s=t.indexOf(r,i);if(-1===e&&-1===s)break;-1!==e&&(-1===s||e<s)?(o++,i=e+n.length):(o--,i=s+r.length)}}return o>0}
17
+ function checkFileAgainstRule(t,s,n){if(isExcluded(t,s.exclude))return[];
18
+ const r=[],o=e(t,"utf-8").split("\n"),c=i(n,t);for(let e=0;e<o.length;e++){const t=o[e];
19
+ let n=!1,i="";if("regex"===s.patternType)try{const e=new RegExp(s.pattern,"g");
20
+ let r;for(;null!==(r=e.exec(t));)if(!isInStringOrComment(t,r.index)){n=!0,i=r[0];break}}catch(e){}else{const e=t.indexOf(s.pattern);-1===e||isInStringOrComment(t,e)||(n=!0,i=s.pattern)}n&&s.contextRequired&&!isWithinContext(o,e,s.contextRequired)||n&&r.push({ruleId:s.id,ruleName:s.name,severity:s.severity,file:c,line:e+1,match:i,replacement:s.replacement})}return r}
21
+ export async function getCustomRules(){const e=loadRuleSets();
22
+ let t=0;
23
+ const s={};for(const[n,r]of Object.entries(e))s[n]={description:r.description,ruleCount:r.rules.length,rules:r.rules.map(e=>({id:e.id,name:e.name,severity:e.severity}))},t+=r.rules.length;return{ruleSets:s,totalRules:t}}
24
+ export async function setCustomRule(e,t){const s=loadRuleSets();s[e]||(s[e]={name:e,description:`Custom rules for ${e}`,rules:[]});
25
+ const n=s[e],r=n.rules.findIndex(e=>e.id===t.id);return r>=0?n.rules[r]=t:n.rules.push(t),saveRuleSet(n),{success:!0,message:r>=0?`Updated rule "${t.id}" in ${e}`:`Added rule "${t.id}" to ${e}`}}
26
+ export async function deleteCustomRule(e,t){const s=loadRuleSets();if(!s[e])return{success:!1,message:`Ruleset "${e}" not found`};
27
+ const n=s[e],r=n.rules.findIndex(e=>e.id===t);return r<0?{success:!1,message:`Rule "${t}" not found`}:(n.rules.splice(r,1),saveRuleSet(n),{success:!0,message:`Deleted rule "${t}" from ${e}`})}
28
+ export function detectProjectRuleSets(t){const s=loadRuleSets(),r=[],c={};
29
+ let l=[];try{const s=o(t,"package.json");if(n(s)){const t=JSON.parse(e(s,"utf-8"));l=[...Object.keys(t.dependencies||{}),...Object.keys(t.devDependencies||{})]}}catch(e){}for(const[n,o]of Object.entries(s)){if(!o.detect)continue;
30
+ const s=o.detect;if(s.packageJson)for(const e of s.packageJson)if(l.includes(e)){r.push(n),c[n]=`Found "${e}" in package.json`;break}if(!r.includes(n)&&(s.imports||s.patterns)){const o=findFiles(t,"*.js");e:for(const l of o.slice(0,50))try{const o=e(l,"utf-8");if(s.imports)for(const e of s.imports)if(o.includes(e)){r.push(n),c[n]=`Found "${e}" in ${i(t,l)}`;break e}if(s.patterns)for(const e of s.patterns)if(o.includes(e)){r.push(n),c[n]=`Found "${e}" in ${i(t,l)}`;break e}}catch(e){}}}return{detected:r,reasons:c}}
31
+ export async function checkCustomRules(e,t={}){const s=l(e),n=loadRuleSets();
32
+ let r=[],o=null;if(t.ruleSet)n[t.ruleSet]&&(r=n[t.ruleSet].rules);else if(!1!==t.autoDetect){if(o=detectProjectRuleSets(e),o.detected.length>0)for(const e of o.detected)n[e]&&r.push(...n[e].rules);for(const[e,t]of Object.entries(n))t.alwaysApply&&!o.detected.includes(e)&&r.push(...t.rules)}else for(const e of Object.values(n))r.push(...e.rules);
33
+ const i={};for(const e of r){const t=e.filePattern||"*.js";i[t]||(i[t]=[]),i[t].push(e)}const c=[];for(const[t,n]of Object.entries(i)){const r=findFiles(e,t);for(const e of r)for(const t of n){const n=checkFileAgainstRule(e,t,s);c.push(...n)}}const u=new Set,f=c.filter(e=>{const t=`${e.file}:${e.line}:${e.match}`;return!u.has(t)&&(u.add(t),!0)});
34
+ let a=f;t.severity&&(a=f.filter(e=>e.severity===t.severity));
35
+ const d={error:0,warning:1,info:2};a.sort((e,t)=>{const s=d[e.severity]-d[t.severity];return 0!==s?s:e.file.localeCompare(t.file)});
36
+ const p={error:a.filter(e=>"error"===e.severity).length,warning:a.filter(e=>"warning"===e.severity).length,info:a.filter(e=>"info"===e.severity).length},h={};for(const e of a)h[e.ruleId]=(h[e.ruleId]||0)+1;return{basePath:e,total:a.length,bySeverity:p,byRule:h,violations:a.slice(0,50),...o&&{detected:o}}}
@@ -0,0 +1,9 @@
1
+ // @ctx .context/src/analysis/db-analysis.ctx
2
+ import{parseProject as e}from"../core/parser.js";import{buildGraph as t}from"../core/graph-builder.js";
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
+ 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;
5
+ const n=a;if(s&&n!==s)continue;r[n]||(r[n]={readers:[],writers:[]});
6
+ const l=o.reverseLegend[e]||e,d=o.nodes[e],c={name:l,file:d?.f||"?"};"R→"===t?r[n].readers.some(e=>e.name===l)||r[n].readers.push(c):r[n].writers.some(e=>e.name===l)||r[n].writers.push(c)}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)}}
7
+ export async function getDBDeadTables(a){const s=await e(a),n=t(s),o=s.tables||[],r=new Set;for(const[,e,t]of n.edges)"R→"!==e&&"W→"!==e||r.add(t);
8
+ const l=o.filter(e=>!r.has(e.name)).map(e=>({name:e.name,file:e.file,line:e.line,columnCount:e.columns.length})),d=collectReferencedColumns(s),c=[];for(const e of o)if(r.has(e.name))for(const t of e.columns)d.has(t.name)||c.push({table:e.name,column:t.name,type:t.type});return{deadTables:l,deadColumns:c,stats:{totalSchemaTables:o.length,totalSchemaColumns:o.reduce((e,t)=>e+t.columns.length,0),deadTableCount:l.length,deadColumnCount:c.length}}}
9
+ function collectReferencedColumns(e){const t=new Set;for(const a of e.functions||[])if(a.dbReads?.length||a.dbWrites?.length)for(const e of[...a.dbReads||[],...a.dbWrites||[]])t.add(e);for(const a of e.classes||[])if(a.dbReads?.length||a.dbWrites?.length)for(const e of[...a.dbReads||[],...a.dbWrites||[]])t.add(e);return t.add("id"),t.add("uuid"),t.add("created_at"),t.add("updated_at"),t}
@@ -0,0 +1,19 @@
1
+ // @ctx .context/src/analysis/dead-code.ctx
2
+ import{readFileSync as e,readdirSync as t,statSync as n,existsSync as s}from"fs";import{join as o,relative as i,resolve as a,dirname as r}from"path";import{parse as c}from"../../vendor/acorn.mjs";import*as l from"../../vendor/walk.mjs";import{shouldExcludeDir as d,shouldExcludeFile as f,parseGitignore as p}from"../core/filters.js";function findJSFiles(e,s=e){e===s&&p(s);
3
+ const a=[];try{for(const r of t(e)){const t=o(e,r),c=i(s,t);n(t).isDirectory()?d(r,c)||a.push(...findJSFiles(t,s)):!r.endsWith(".js")||r.endsWith(".css.js")||r.endsWith(".tpl.js")?(r.endsWith(".css.js")||r.endsWith(".tpl.js"))&&(f(r,c)||a.push(t)):f(r,c)||a.push(t)}}catch(e){}return a}
4
+ function findProjectRoot(e){let t=a(e);for(;t!==r(t);){if(s(o(t,"package.json")))return t;t=r(t)}return a(e)}
5
+ function analyzeFile(e){const t=new Set,n=new Set,s=new Set,o=[],i=[];
6
+ let a;try{a=c(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return{definitions:t,calls:n,exports:s,imports:o,namedExports:i}}return l.simple(a,{FunctionDeclaration(e){e.id&&t.add(e.id.name)},ClassDeclaration(e){e.id&&t.add(e.id.name)},CallExpression(e){"Identifier"===e.callee.type?n.add(e.callee.name):"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&n.add(e.callee.object.name);for(const t of e.arguments)"Identifier"===t.type&&n.add(t.name)},NewExpression(e){"Identifier"===e.callee.type&&n.add(e.callee.name)},ImportDeclaration(e){const t=e.source.value;for(const n of e.specifiers)"ImportSpecifier"===n.type?o.push({name:n.imported.name,source:t}):"ImportDefaultSpecifier"===n.type&&o.push({name:"default",source:t})},ExportNamedDeclaration(e){if(e.declaration)if(e.declaration.id){const t=e.declaration.id.name;s.add(t),i.push({name:t,line:e.loc.start.line})}else if(e.declaration.declarations)for(const t of e.declaration.declarations)if("Identifier"===t.id.type){const n=t.id.name;s.add(n),i.push({name:n,line:e.loc.start.line})}if(e.specifiers)for(const t of e.specifiers){const n=t.exported.name;s.add(n),i.push({name:n,line:e.loc.start.line})}},ExportDefaultDeclaration(e){e.declaration?.id&&s.add(e.declaration.id.name)}}),{definitions:t,calls:n,exports:s,imports:o,namedExports:i}}
7
+ function analyzeFileLocals(e){const t=[],n=[];
8
+ let s;try{s=c(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return{unusedVars:t,unusedImports:n}}const o=new Set,i=[],a=[],r=new Set;l.simple(s,{VariableDeclaration(e){const t="ExportNamedDeclaration"===e.parent?.type;for(const n of e.declarations)"Identifier"===n.id.type&&(i.push({name:n.id.name,line:n.loc.start.line,isExported:t}),r.add(n.id))},ImportDeclaration(e){for(const t of e.specifiers){const n=t.local.name,s="ImportSpecifier"===t.type?t.imported.name:"ImportDefaultSpecifier"===t.type?"default":"*";a.push({name:s,local:n,source:e.source.value,line:e.loc.start.line}),r.add(t.local)}}});
9
+ const d=new Set;for(const e of s.body){if("ExportNamedDeclaration"===e.type&&e.declaration){if(e.declaration.declarations)for(const t of e.declaration.declarations)"Identifier"===t.id.type&&d.add(t.id.name);e.declaration.id&&d.add(e.declaration.id.name)}if("ExportNamedDeclaration"===e.type&&e.specifiers)for(const t of e.specifiers)d.add(t.local.name)}l.simple(s,{Identifier(e){o.add(e.name)}});for(const n of i){if(d.has(n.name))continue;if(n.name.startsWith("_"))continue;
10
+ const s=new RegExp(`\\b${n.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"g"),o=e.match(s);o&&o.length<=1&&t.push({name:n.name,line:n.line})}for(const t of a){if("*"===t.name)continue;
11
+ const s=new RegExp(`\\b${t.local.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"g"),o=e.match(s);o&&o.length<=1&&n.push(t)}return{unusedVars:t,unusedImports:n}}
12
+ export async function getDeadCode(t){const n=a(t),s=findJSFiles(t),o=[],d=new Set,f=new Set,p=[],m=new Map,u=findJSFiles(findProjectRoot(t));for(const t of u){let s;try{s=e(t,"utf-8")}catch{continue}const o=i(n,t),{imports:c}=analyzeFile(s);for(const e of c){if(!e.source.startsWith("."))continue;
13
+ const s=r(t);
14
+ let c=a(s,e.source);c.endsWith(".js")||(c+=".js");
15
+ const l=i(n,c),d=`${e.name}@${l}`;m.has(d)||m.set(d,new Set),m.get(d).add(o)}}for(const t of s){const s=e(t,"utf-8"),o=i(n,t),{definitions:a,calls:r,exports:c,namedExports:l}=analyzeFile(s);for(const e of r)d.add(e);for(const e of c)f.add(e);p.push({file:o,code:s,definitions:a,calls:r,exports:c,namedExports:l})}for(const{file:e,code:t,definitions:n,exports:s}of p){if(e.includes(".test.")||e.includes("/tests/"))continue;if(e.endsWith(".css.js")||e.endsWith(".tpl.js"))continue;
16
+ let n;try{n=c(t,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){continue}l.simple(n,{FunctionDeclaration(t){if(!t.id)return;
17
+ const n=t.id.name;s.has(n)||f.has(n)||d.has(n)||n.startsWith("_")||o.push({name:n,type:"function",file:e,line:t.loc.start.line,reason:"Never called"})},ClassDeclaration(t){if(!t.id)return;
18
+ const n=t.id.name;s.has(n)||f.has(n)||d.has(n)||o.push({name:n,type:"class",file:e,line:t.loc.start.line,reason:"Never instantiated"})}})}for(const{file:e,calls:t,namedExports:n}of p)if(!e.includes(".test.")&&!e.includes("/tests/"))for(const s of n){if(t.has(s.name))continue;
19
+ const n=`${s.name}@${e}`,i=m.get(n);i&&0!==i.size||o.push({name:s.name,type:"export",file:e,line:s.line,reason:"Exported but never imported"})}for(const{file:e,code:t}of p){if(e.includes(".test.")||e.includes("/tests/"))continue;if(e.endsWith(".css.js")||e.endsWith(".tpl.js"))continue;const{unusedVars:n,unusedImports:s}=analyzeFileLocals(t);for(const t of n)o.push({name:t.name,type:"variable",file:e,line:t.line,reason:"Declared but never used"});for(const t of s)o.push({name:t.local,type:"import",file:e,line:t.line,reason:`Imported from '${t.source}' but never used`})}const h={function:o.filter(e=>"function"===e.type).length,class:o.filter(e=>"class"===e.type).length,export:o.filter(e=>"export"===e.type).length,variable:o.filter(e=>"variable"===e.type).length,import:o.filter(e=>"import"===e.type).length};return{total:o.length,byType:h,items:o.slice(0,50)}}
@@ -0,0 +1,18 @@
1
+ // @ctx .context/src/analysis/full-analysis.ctx
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 l}from"./undocumented.js";import{getSimilarFunctions as r}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 g,computeContentHash as y,isCacheValid as f}from"./analysis-cache.js";import{shouldExcludeDir as x,shouldExcludeFile as j,parseGitignore as b}from"../core/filters.js";import{getWorkspaceRoot as C}from"../core/workspace.js";function calculateHealthScore(t){let e=100;
3
+ 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`);
4
+ 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`);
5
+ 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`);
6
+ const l=t.outdated.stats?.bySeverity?.error||0,r=t.outdated.stats?.bySeverity?.warning||0;if(e-=Math.min(3*l+1*r,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)}}
7
+ function findJSFiles(t,n=t){t===n&&b(n);
8
+ const i=[];try{for(const l of e(t)){const e=a(t,l),r=o(n,e);s(e).isDirectory()?x(l,r)||i.push(...findJSFiles(e,n)):!l.endsWith(".js")||l.endsWith(".css.js")||l.endsWith(".tpl.js")||j(l,r)||i.push(e)}}catch(t){}return i}
9
+ function runCacheableAnalyses(e,s){const a=n(e),i=C(),r=findJSFiles(e),d=[],u=[],m=[];
10
+ let x=0,j=0;for(const e of r){const n=o(a,e),r=o(i,e);
11
+ let b;try{b=t(e,"utf-8")}catch(t){continue}const C=y(b),S=p(s,r);if(S&&f(S,S.sig,C,"content"))x++,S.complexity&&d.push(...S.complexity),S.undocumented&&u.push(...S.undocumented),S.jsdocIssues&&m.push(...S.jsdocIssues);else{j++;
12
+ const t=c(b,n),e=l(b,n,"tests"),a=h(b,n);d.push(...t),u.push(...e),m.push(...a),g(s,r,{sig:S?.sig||C,contentHash:C,complexity:t,undocumented:e,jsdocIssues:a})}}return{complexity:d,undocumented:u,jsdocIssues:m,cacheStats:{hits:x,misses:j}}}
13
+ function aggregateComplexity(t,e=5){let s=t.filter(t=>t.complexity>=e);s.sort((t,e)=>e.complexity-t.complexity);
14
+ const a={low:s.filter(t=>"low"===t.rating).length,moderate:s.filter(t=>"moderate"===t.rating).length,high:s.filter(t=>"high"===t.rating).length,critical:s.filter(t=>"critical"===t.rating).length,average:s.length>0?Math.round(s.reduce((t,e)=>t+e.complexity,0)/s.length*10)/10:0};return{total:s.length,stats:a,items:s.slice(0,30)}}
15
+ function aggregateUndocumented(t){const e={class:t.filter(t=>"class"===t.type).length,function:t.filter(t=>"function"===t.type).length,method:t.filter(t=>"method"===t.type).length};return{total:t.length,byType:e,items:t.slice(0,20)}}
16
+ function aggregateJSDoc(t){const e=t.filter(t=>"error"===t.severity).length,s=t.filter(t=>"warning"===t.severity).length,a={};for(const e of t)a[e.file]=(a[e.file]||0)+1;return{issues:t,summary:{total:t.length,errors:e,warnings:s,byFile:a}}}
17
+ export async function getFullAnalysis(t,e={}){const s=e.includeItems||!1,o=(n(t),runCacheableAnalyses(t,a(C(),".context"))),l=aggregateComplexity(o.complexity),c=aggregateUndocumented(o.undocumented),h=aggregateJSDoc(o.jsdocIssues),[p,g,y,f,x]=await Promise.all([i(t).catch(()=>({total:0,byType:{},items:[]})),r(t,{threshold:70}).catch(()=>({total:0,pairs:[]})),d(t).catch(()=>({total:0,stats:{},items:[]})),u(t).catch(()=>({codePatterns:[],redundantDeps:[],stats:{totalPatterns:0,bySeverity:{},byPattern:{},redundantDeps:0}})),m(t).catch(()=>({tables:[],totalTables:0,totalQueries:0}))]),j=calculateHealthScore({deadCode:p,undocumented:c,similar:g,complexity:l,largeFiles:y,outdated:f,jsdocConsistency:h.summary}),b={deadCode:{total:p.total,byType:p.byType,...s&&{items:p.items.slice(0,10)}},undocumented:{total:c.total,byType:c.byType,...s&&{items:c.items.slice(0,10)}},similar:{total:g.total,...s&&{pairs:g.pairs.slice(0,5)}},complexity:{total:l.total,stats:l.stats,...s&&{items:l.items.slice(0,10)}},largeFiles:{total:y.total,stats:y.stats,...s&&{items:y.items.slice(0,10)}},outdated:{totalPatterns:f.stats.totalPatterns,redundantDeps:f.redundantDeps,...s&&{codePatterns:f.codePatterns.slice(0,10)}},jsdocConsistency:{total:h.summary.total,errors:h.summary.errors,warnings:h.summary.warnings,...s&&{issues:h.issues.slice(0,10)}},cache:o.cacheStats,overall:j};return x.totalTables>0&&(b.database={tablesUsed:x.totalTables,totalQueries:x.totalQueries,tables:x.tables.map(t=>({name:t.table,readers:t.totalReaders,writers:t.totalWriters}))}),b}
18
+ export function getAnalysisSummaryOnly(t){const e=runCacheableAnalyses(t,a(C(),".context")),s=aggregateComplexity(e.complexity),o=aggregateUndocumented(e.undocumented),n=aggregateJSDoc(e.jsdocIssues),i=calculateHealthScore({deadCode:{total:0},undocumented:o,similar:{total:0},complexity:s,largeFiles:{total:0},outdated:{stats:{totalPatterns:0}},jsdocConsistency:n.summary});return{healthScore:i.score,grade:i.rating,complexity:s.total,undocumented:o.total,jsdocIssues:n.summary.total,cache:e.cacheStats,note:"Partial score — cross-file analyses skipped for speed. Run get_full_analysis for complete health check."}}
@@ -0,0 +1,24 @@
1
+ // @ctx .context/src/analysis/jsdoc-checker.ctx
2
+ import{readFileSync as e,readdirSync as t,statSync as n}from"fs";import{join as r,relative as s,resolve as i}from"path";import{parse as o}from"../../vendor/acorn.mjs";import*as a from"../../vendor/walk.mjs";import{shouldExcludeDir as c,shouldExcludeFile as l,parseGitignore as u}from"../core/filters.js";function findJSFiles(e,i=e){e===i&&u(i);
3
+ const o=[];try{for(const a of t(e)){const t=r(e,a),u=s(i,t);n(t).isDirectory()?c(a,u)||o.push(...findJSFiles(t,i)):!a.endsWith(".js")||a.endsWith(".css.js")||a.endsWith(".tpl.js")||l(a,u)||o.push(t)}}catch(e){}return o}
4
+ function extractJSDocComments(e){const t=[],n=/\/\*\*[\s\S]*?\*\//g;
5
+ let r;for(;null!==(r=n.exec(e));){const n=r[0],s=e.slice(0,r.index+n.length).split("\n").length,i=[],o=/@param\s+\{/g;
6
+ let a;for(;null!==(a=o.exec(n));){let e=1,t=a.index+a[0].length;for(;t<n.length&&e>0;)"{"===n[t]?e++:"}"===n[t]&&e--,t++;if(0!==e)continue;
7
+ const r=n.slice(a.index+a[0].length,t-1),s=n.slice(t).match(/^\s+(\[?\w+(?:\.\w+)*\]?)/);if(!s)continue;
8
+ let o=s[1];o.startsWith("[")&&(o=o.slice(1)),o.endsWith("]")&&(o=o.slice(0,-1)),o.includes(".")||i.push({name:o,type:r})}const c=/@returns?\s/.test(n);t.push({text:n,endLine:s,params:i,hasReturns:c})}return t}
9
+ function findJSDocBefore(e,t){for(const n of e){const e=t-n.endLine;if(e>=0&&e<=2)return n}return null}
10
+ function extractParamName(e){return"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name:"RestElement"===e.type&&"Identifier"===e.argument.type?e.argument.name:"ObjectPattern"===e.type?"options":"ArrayPattern"===e.type?"args":"param"}
11
+ function inferTypeFromDefault(e){if("AssignmentPattern"!==e.type)return null;
12
+ const t=e.right;if("Literal"===t.type){if("string"==typeof t.value)return"string";if("number"==typeof t.value)return"number";if("boolean"==typeof t.value)return"boolean"}return"ArrayExpression"===t.type?"Array":"ObjectExpression"===t.type?"Object":null}
13
+ function hasReturnValue(e){let t=!1;try{a.simple(e.body,{ReturnStatement(e){e.argument&&(t=!0)},FunctionDeclaration(){},FunctionExpression(){},ArrowFunctionExpression(){}})}catch(e){}return t}
14
+ function validateFunction(e,t,n,r,s,i){const o=[];if(!e)return o;
15
+ const a=e.params;a.length>0&&a.length!==t.length&&o.push({file:s,line:i,name:r,severity:"error",message:`Param count mismatch: JSDoc has ${a.length}, function has ${t.length}`});
16
+ const c=Math.min(a.length,t.length);for(let e=0;e<c;e++){const n=a[e].name,c=extractParamName(t[e]);n!==c&&"options"!==c&&"args"!==c&&"param"!==c&&o.push({file:s,line:i,name:r,severity:"error",message:`Param name mismatch at position ${e}: JSDoc says "${n}", code has "${c}"`})}!e.hasReturns&&hasReturnValue(n)&&o.push({file:s,line:i,name:r,severity:"warning",message:"Function returns a value but JSDoc has no @returns"});for(let e=0;e<c;e++){const n=a[e].type,c=inferTypeFromDefault(t[e]);if(c&&n&&"*"!==n){let t=n.toLowerCase().includes(c.toLowerCase());!t&&"string"===c&&n.includes("'")&&n.includes("|")&&(t=!0),!t&&"Array"===c&&n.includes("[]")&&(t=!0),t||o.push({file:s,line:i,name:r,severity:"warning",message:`Type mismatch for "${a[e].name}": JSDoc says {${n}}, default value suggests {${c}}`})}}return o}
17
+ export function checkJSDocFile(e,t){const n=[];
18
+ let r;try{r=o(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return n}const s=extractJSDocComments(e);return a.simple(r,{FunctionDeclaration(e){if(!e.id)return;
19
+ const r=findJSDocBefore(s,e.loc.start.line);r&&n.push(...validateFunction(r,e.params,e,e.id.name,t,e.loc.start.line))},VariableDeclaration(e){for(const r of e.declarations){if(!r.init)continue;
20
+ const i="ArrowFunctionExpression"===r.init.type||"FunctionExpression"===r.init.type?r.init:null;if(!i||!r.id?.name)continue;
21
+ const o=findJSDocBefore(s,e.loc.start.line);o&&n.push(...validateFunction(o,i.params,i,r.id.name,t,e.loc.start.line))}},ClassDeclaration(e){const r=e.id?.name||"Anonymous";for(const i of e.body.body){if("MethodDefinition"!==i.type)continue;
22
+ const e=i.key.name||i.key.value;if(!e||"constructor"===e)continue;if("method"!==i.kind)continue;
23
+ const o=i.value,a=findJSDocBefore(s,i.loc.start.line);a&&n.push(...validateFunction(a,o.params,o,`${r}.${e}`,t,i.loc.start.line))}}}),n}
24
+ export function checkJSDocConsistency(t){const n=i(t),r=findJSFiles(t),o=[];for(const t of r){let r;try{r=e(t,"utf-8")}catch(e){continue}const i=checkJSDocFile(r,s(n,t));o.push(...i)}const a=o.filter(e=>"error"===e.severity).length,c=o.filter(e=>"warning"===e.severity).length,l={};for(const e of o)l[e.file]=(l[e.file]||0)+1;return{issues:o,summary:{total:o.length,errors:a,warnings:c,byFile:l}}}
@@ -0,0 +1,10 @@
1
+ // @ctx .context/src/analysis/jsdoc-generator.ctx
2
+ import{readFileSync as t}from"fs";import{relative as e}from"path";import{parse as r}from"../../vendor/acorn.mjs";import*as n from"../../vendor/walk.mjs";import{getWorkspaceRoot as a}from"../core/workspace.js";
3
+ export function generateJSDoc(i,o={}){const s=[],c=t(i,"utf-8"),m=e(a(),i);
4
+ let f;try{f=r(c,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(t){return s}const hasJSDocAt=t=>{const e=c.split("\n");for(let r=t-2;r>=Math.max(0,t-15);r--){const t=e[r]?.trim();if(t){if("*/"===t||t.endsWith("*/")){for(let t=r-1;t>=Math.max(0,r-20);t--){const r=e[t]?.trim();if(r?.startsWith("/**"))return!0;if(r&&!r.startsWith("*"))break}return!1}if(!t.startsWith("*")&&!t.startsWith("//"))break}}return!1};return n.simple(f,{FunctionDeclaration(t){if(!t.id)return;if(hasJSDocAt(t.loc.start.line))return;
5
+ const e=buildJSDoc({name:t.id.name,params:t.params,async:t.async});s.push({name:t.id.name,type:"function",file:m,line:t.loc.start.line,jsdoc:e})},ClassDeclaration(t){if(t.id)for(const e of t.body.body)if("MethodDefinition"===e.type){const r=e.key.name||e.key.value;if("method"!==e.kind)continue;if(r.startsWith("_"))continue;if(hasJSDocAt(e.loc.start.line))continue;
6
+ const n=e.value,a=buildJSDoc({name:r,params:n.params,async:n.async});s.push({name:`${t.id.name}.${r}`,type:"method",file:m,line:e.loc.start.line,jsdoc:a})}}}),s}
7
+ function buildJSDoc(t){const e=["/**"];e.push(` * TODO: Add description for ${t.name}`);for(const r of t.params){const t=extractParamName(r),n=inferParamType(r);e.push(` * @param {${n}} ${t}`)}return e.push(` * @returns {${t.async?"Promise<*>":"*"}}`),e.push(" */"),e.join("\n")}
8
+ function extractParamName(t){return"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}`:"ObjectPattern"===t.type?"options":"ArrayPattern"===t.type?"args":"param"}
9
+ function inferParamType(t){if("AssignmentPattern"===t.type){const e=t.right;if("Literal"===e.type){if("string"==typeof e.value)return"string";if("number"==typeof e.value)return"number";if("boolean"==typeof e.value)return"boolean"}if("ArrayExpression"===e.type)return"Array";if("ObjectExpression"===e.type)return"Object"}return"RestElement"===t.type?"Array":"ObjectPattern"===t.type?"Object":"ArrayPattern"===t.type?"Array":"*"}
10
+ export function generateJSDocFor(t,e,r={}){return generateJSDoc(t,r).find(t=>t.name===e||t.name.endsWith(`.${e}`))||null}
@@ -0,0 +1,11 @@
1
+ // @ctx .context/src/analysis/large-files.ctx
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
+ 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
+ function analyzeFile(e,t){const n=s(e,"utf-8"),r=i(t,e),a=n.split("\n").length;
5
+ let c,u=0,p=0,f=0;try{c=o(n,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(s){return{file:r,lines:a,functions:0,classes:0,exports:0,rating:"ok",reasons:[]}}l.simple(c,{FunctionDeclaration(){u++},ArrowFunctionExpression(s){"BlockStatement"===s.body.type&&u++},ClassDeclaration(){p++},ExportNamedDeclaration(){f++},ExportDefaultDeclaration(){f++}});
6
+ const h=[];
7
+ let d=0;a>500?(d+=2,h.push(`${a} lines (>500)`)):a>300&&(d+=1,h.push(`${a} lines (>300)`)),u>15?(d+=2,h.push(`${u} functions (>15)`)):u>10&&(d+=1,h.push(`${u} functions (>10)`)),p>3?(d+=2,h.push(`${p} classes (>3)`)):p>1&&(d+=1,h.push(`${p} classes (>1)`)),f>10?(d+=2,h.push(`${f} exports (>10)`)):f>5&&(d+=1,h.push(`${f} exports (>5)`));
8
+ let g="ok";return d>=4?g="critical":d>=2&&(g="warning"),{file:r,lines:a,functions:u,classes:p,exports:f,rating:g,reasons:h}}
9
+ export async function getLargeFiles(s,e={}){const t=e.onlyProblematic||!1,n=r(s),i=findJSFiles(s);
10
+ let o=i.map(s=>analyzeFile(s,n));t&&(o=o.filter(s=>"ok"!==s.rating)),o.sort((s,e)=>e.lines-s.lines);
11
+ const l={totalFiles:i.length,ok:o.filter(s=>"ok"===s.rating).length,warning:o.filter(s=>"warning"===s.rating).length,critical:o.filter(s=>"critical"===s.rating).length,totalLines:o.reduce((s,e)=>s+e.lines,0),avgLines:o.length>0?Math.round(o.reduce((s,e)=>s+e.lines,0)/o.length):0};return{total:o.length,stats:l,items:o.slice(0,30)}}
@@ -0,0 +1,12 @@
1
+ // @ctx .context/src/analysis/outdated-patterns.ctx
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
+ 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
+ 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);
5
+ const i=[];try{for(const c of r(e)){const r=s(e,c),a=o(n,r);t(r).isDirectory()?l(c,a)||i.push(...findJSFiles(r,n)):!c.endsWith(".js")||c.endsWith(".css.js")||c.endsWith(".tpl.js")||p(c,a)||i.push(r)}}catch(e){}return i}
6
+ function analyzeFilePatterns(r,t){const n=e(r,"utf-8"),s=o(t,r),i=[];
7
+ let l;try{l=c(n,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return i}const p={inAsync:!1};return a.simple(l,{FunctionDeclaration(e){p.inAsync=e.async},ArrowFunctionExpression(e){p.inAsync=e.async}}),p.inAsync=!1,a.ancestor(l,{"*"(e,r){for(const e of r)if(("FunctionDeclaration"===e.type||"ArrowFunctionExpression"===e.type||"FunctionExpression"===e.type)&&e.async){p.inAsync=!0;break}for(const r of m)r.check(e,p)&&i.push({pattern:r.name,description:r.description,file:s,line:e.loc?.start?.line||0,severity:r.severity,replacement:r.replacement})}}),i}
8
+ function analyzePackageJson(r){const t=s(r,"package.json"),o=[];if(!n(t))return o;try{const r=JSON.parse(e(t,"utf-8")),n={...r.dependencies,...r.devDependencies};for(const e of Object.keys(n))f[e]&&o.push({name:e,...f[e]})}catch(e){}return o}
9
+ export async function getOutdatedPatterns(e,r={}){const t=r.codeOnly||!1,n=r.depsOnly||!1,s=i(e);
10
+ let o=[],c=[];if(!n){const r=findJSFiles(e);for(const e of r)o.push(...analyzeFilePatterns(e,s));
11
+ const t={error:0,warning:1,info:2};o.sort((e,r)=>t[e.severity]-t[r.severity])}t||(c=analyzePackageJson(e));
12
+ const a={totalPatterns:o.length,byPattern:{},bySeverity:{error:o.filter(e=>"error"===e.severity).length,warning:o.filter(e=>"warning"===e.severity).length,info:o.filter(e=>"info"===e.severity).length},redundantDeps:c.length};for(const e of o)a.byPattern[e.pattern]=(a.byPattern[e.pattern]||0)+1;return{codePatterns:o.slice(0,50),redundantDeps:c,stats:a}}
@@ -0,0 +1,16 @@
1
+ // @ctx .context/src/analysis/similar-functions.ctx
2
+ import{readFileSync as t,readdirSync as e,statSync as a}from"fs";import{join as n,relative as s,resolve as r}from"path";import{parse as i}from"../../vendor/acorn.mjs";import*as l from"../../vendor/walk.mjs";import{shouldExcludeDir as o,shouldExcludeFile as c,parseGitignore as h}from"../core/filters.js";function findJSFiles(t,r=t){t===r&&h(r);
3
+ const i=[];try{for(const l of e(t)){const e=n(t,l),h=s(r,e);a(e).isDirectory()?o(l,h)||i.push(...findJSFiles(e,r)):!l.endsWith(".js")||l.endsWith(".css.js")||l.endsWith(".tpl.js")||c(l,h)||i.push(e)}}catch(t){}return i}
4
+ function extractSignatures(e,a){const n=t(e,"utf-8"),r=s(a,e),o=[];
5
+ let c;try{c=i(n,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(t){return o}return l.simple(c,{FunctionDeclaration(t){t.id&&o.push(buildSignature(t,t.id.name,r))},MethodDefinition(t){if("method"!==t.kind)return;
6
+ const e=t.key.name||t.key.value;e.startsWith("_")||o.push(buildSignature(t.value,e,r))}}),o}
7
+ function buildSignature(t,e,a){const n=t.params.map(t=>extractParamName(t)),s=[];l.simple(t.body,{CallExpression(t){"Identifier"===t.callee.type?s.push(t.callee.name):"MemberExpression"===t.callee.type&&"Identifier"===t.callee.property.type&&s.push(t.callee.property.name)}});
8
+ const r=hashBodyStructure(t.body);return{name:e,file:a,line:t.loc?.start?.line||0,paramCount:t.params.length,paramNames:n,async:t.async||!1,bodyHash:r,calls:[...new Set(s)]}}
9
+ function extractParamName(t){return"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:"param"}
10
+ function hashBodyStructure(t){const e=[];return l.simple(t,{IfStatement(){e.push("IF")},ForStatement(){e.push("FOR")},ForOfStatement(){e.push("FOROF")},ForInStatement(){e.push("FORIN")},WhileStatement(){e.push("WHILE")},SwitchStatement(){e.push("SWITCH")},TryStatement(){e.push("TRY")},ReturnStatement(){e.push("RET")},ThrowStatement(){e.push("THROW")},AwaitExpression(){e.push("AWAIT")}}),e.join("|")}
11
+ function calculateSimilarity(t,e){const a=[];
12
+ let n=0,s=0;s+=30,t.paramCount===e.paramCount&&(n+=30,a.push("Same param count")),s+=20;
13
+ const r=t.paramNames.filter(t=>e.paramNames.includes(t));if(r.length>0&&t.paramNames.length>0){const s=r.length/Math.max(t.paramNames.length,e.paramNames.length);n+=Math.round(20*s),s>=.5&&a.push(`Similar params: ${r.join(", ")}`)}if(s+=10,t.async===e.async&&(n+=10),s+=25,t.bodyHash===e.bodyHash&&t.bodyHash.length>0)n+=25,a.push("Identical structure");else if(t.bodyHash.length>0&&e.bodyHash.length>0){const s=t.bodyHash.split("|"),r=e.bodyHash.split("|"),i=s.filter(t=>r.includes(t));if(i.length>0){const t=i.length/Math.max(s.length,r.length);n+=Math.round(25*t),t>=.5&&a.push("Similar control flow")}}s+=15;
14
+ const i=t.calls.filter(t=>e.calls.includes(t));if(i.length>0&&t.calls.length>0&&e.calls.length>0){const s=i.length/Math.max(t.calls.length,e.calls.length);n+=Math.round(15*s),i.length>=2&&a.push(`Common calls: ${i.slice(0,3).join(", ")}`)}return{similarity:Math.round(n/100*100),reasons:a}}
15
+ export async function getSimilarFunctions(t,e={}){const a=e.threshold||60,n=r(t),s=findJSFiles(t),i=[];for(const t of s)i.push(...extractSignatures(t,n));
16
+ const l=[];for(let t=0;t<i.length;t++)for(let e=t+1;e<i.length;e++){const n=i[t],s=i[e];if(n.file===s.file&&n.name===s.name)continue;if(n.bodyHash.length<3&&s.bodyHash.length<3)continue;const{similarity:r,reasons:o}=calculateSimilarity(n,s);r>=a&&o.length>0&&l.push({a:n,b:s,similarity:r,reasons:o})}return l.sort((t,e)=>e.similarity-t.similarity),{total:l.length,pairs:l.slice(0,20)}}
@@ -0,0 +1,21 @@
1
+ // @ctx .context/src/analysis/test-annotations.ctx
2
+ import{readFileSync as t,readdirSync as e,statSync as s,writeFileSync as n}from"fs";import{join as o,relative as r,resolve as a}from"path";function findCtxMdFiles(t){const n=[];try{for(const r of e(t)){const e=o(t,r);s(e).isDirectory()&&!r.startsWith(".")?n.push(...findCtxMdFiles(e)):r.endsWith(".ctx.md")&&n.push(e)}}catch(t){}return n}
3
+ export function parseAnnotations(t,e){const s=t.split("\n"),n=[];
4
+ let o=!1,r=[];for(const t of s){if(t.startsWith("## ")){o&&r.length&&(n.push(...groupByName(r,e)),r=[]),o=t.startsWith("## Tests");continue}if(!o)continue;
5
+ const s=t.match(/^- \[([ x!])\] (\w+):\s*(.+)$/);if(!s)continue;const[,a,i,c]=s,f=c.split("→").map(t=>t.trim()),u=f[0],l=f[1]||null;
6
+ let p=null,d="pending";if("x"===a&&(d="passed"),"!"===a){d="failed";
7
+ const t=u.match(/\(FAILED:\s*(.+)\)$/);t&&(p=t[1].trim())}r.push({name:i,action:u,expected:l,status:d,failReason:p})}return o&&r.length&&n.push(...groupByName(r,e)),n}
8
+ function groupByName(t,e){const s={};
9
+ let n={};for(const e of t)s[e.name]||(s[e.name]=[],n[e.name]=0),s[e.name].push({id:`${e.name}.${n[e.name]++}`,action:e.action,expected:e.expected,status:e.status,failReason:e.failReason});return Object.entries(s).map(([t,s])=>({name:t,tests:s,file:e}))}
10
+ export function getAllFeatures(e){const s=findCtxMdFiles(o(a(e),".context")),n=[];for(const e of s)try{const s=parseAnnotations(t(e,"utf-8"),e);n.push(...s)}catch(t){}return n}
11
+ export function getPendingTests(t){const e=a(t),s=getAllFeatures(t),n=[];for(const t of s)for(const s of t.tests)"pending"===s.status&&n.push({...s,feature:t.name,file:r(e,t.file)});return n}
12
+ export function markTestPassed(t){return updateTestState(t.split(".")[0],t,"x")}
13
+ export function markTestFailed(t,e){return updateTestState(t.split(".")[0],t,"!",e)}
14
+ function updateTestState(e,s,r,a){const i=process.cwd(),c=findCtxMdFiles(o(i,".context")),f=parseInt(s.split(".")[1],10);for(const o of c)try{const i=t(o,"utf-8").split("\n");
15
+ let c=!1,u=0;for(let t=0;t<i.length;t++){if(i[t].startsWith("## ")){c=i[t].startsWith("## Tests");continue}if(!c)continue;
16
+ const l=i[t].match(/^- \[([ x!])\] (\w+):\s*(.+)$/);if(l&&l[2]===e){if(u===f){const c=l[3].replace(/\s*\(FAILED:.*\)$/,""),f=a?` (FAILED: ${a})`:"";return i[t]=`- [${r}] ${e}: ${c}${f}`,n(o,i.join("\n"),"utf-8"),{success:!0,testId:s,...a?{reason:a}:{}}}u++}}}catch(t){}return{success:!1,testId:s,error:"Test not found"}}
17
+ export function getTestSummary(t){const e=getAllFeatures(t);
18
+ let s=0,n=0,o=0,r=0;
19
+ const a=[];for(const t of e)for(const e of t.tests)s++,"passed"===e.status?n++:"failed"===e.status?(o++,a.push({id:e.id,reason:e.failReason})):r++;return{total:s,passed:n,failed:o,pending:r,progress:s>0?Math.round((n+o)/s*100):0,failures:a}}
20
+ export function resetTestState(){const e=process.cwd(),s=findCtxMdFiles(o(e,".context"));for(const e of s)try{let s=t(e,"utf-8");
21
+ const o=s.replace(/^(- )\[([x!])\] (\w+:\s*.+?)(?:\s*\(FAILED:.*\))?$/gm,"$1[ ] $3");o!==s&&n(e,o,"utf-8")}catch(t){}return{success:!0}}
@@ -0,0 +1,8 @@
1
+ // @ctx .context/src/analysis/type-checker.ctx
2
+ import{execSync as t,spawn as e}from"child_process";import{existsSync as s}from"fs";import{resolve as n,join as i}from"path";function detectTsc(){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}}}}
3
+ function parseDiagnosticLine(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}
4
+ function buildArgs(t,e={}){const n=["--noEmit"],r=i(t,"tsconfig.json"),o=i(t,"jsconfig.json");return s(r)?n.push("--project",r):s(o)?n.push("--project",o):(n.push("--allowJs","--checkJs"),n.push("--target","ESNext"),n.push("--module","NodeNext"),n.push("--moduleResolution","NodeNext"),n.push("--skipLibCheck"),e.files?.length?n.push(...e.files):n.push("--rootDir",t)),n}
5
+ export async function checkTypes(t,s={}){const i=s.maxDiagnostics||50,r=n(t),o=detectTsc();if(!o.available)return{available:!1,version:null,diagnostics:[],summary:{total:0,errors:0,warnings:0},hint:"TypeScript not found. Install: npm i -g typescript"};
6
+ const l=buildArgs(r,s),a=o.path.includes("npx")?"npx":"tsc",c=o.path.includes("npx")?["tsc",...l]:l,u=await new Promise(t=>{const s=e(a,c,{cwd:r,stdio:["ignore","pipe","pipe"]});
7
+ let n="",i="";s.stdout.on("data",t=>{n+=t}),s.stderr.on("data",t=>{i+=t});
8
+ const o=setTimeout(()=>{s.kill("SIGTERM"),t({stdout:n,stderr:i,killed:!0})},6e4);s.on("close",()=>{clearTimeout(o),t({stdout:n,stderr:i,killed:!1})}),s.on("error",e=>{clearTimeout(o),t({stdout:"",stderr:e.message,killed:!1})})}),p=((u.stdout||"")+(u.stderr||"")).split("\n").filter(t=>t.trim()),d=[];for(const t of p){const e=parseDiagnosticLine(t);e&&d.length<i&&d.push(e)}const h=d.filter(t=>"error"===t.severity).length,m=d.filter(t=>"warning"===t.severity).length,f={};for(const t of d)f[t.file]=(f[t.file]||0)+1;return{available:!0,version:o.version,diagnostics:d,summary:{total:d.length,errors:h,warnings:m,byFile:f},hint:null}}
@@ -0,0 +1,14 @@
1
+ // @ctx .context/src/analysis/undocumented.ctx
2
+ import{readFileSync as e,readdirSync as t,statSync as n}from"fs";import{join as s,relative as o,resolve as c}from"path";import{parse as i}from"../../vendor/acorn.mjs";import*as r from"../../vendor/walk.mjs";import{shouldExcludeDir as l,shouldExcludeFile as a,parseGitignore as u}from"../core/filters.js";function findJSFiles(e,c=e){e===c&&u(c);
3
+ const i=[];try{for(const r of t(e)){const t=s(e,r),u=o(c,t);n(t).isDirectory()?l(r,u)||i.push(...findJSFiles(t,c)):!r.endsWith(".js")||r.endsWith(".css.js")||r.endsWith(".tpl.js")||a(r,u)||i.push(t)}}catch(e){}return i}
4
+ function extractComments(e){const t=[],n=/\/\*\*[\s\S]*?\*\//g;
5
+ let s;for(;null!==(s=n.exec(e));){const n=e.slice(0,s.index+s[0].length).split("\n").length;t.push({text:s[0],endLine:n})}return t}
6
+ function findJSDocBefore(e,t){for(const n of e){const e=t-n.endLine;if(e>=0&&e<=2)return n.text}return null}
7
+ function checkMissing(e,t){const n=[];return e?("params"!==t&&"all"!==t||(e.includes("@param")||n.push("@param"),e.includes("@returns")||e.includes("@return")||n.push("@returns")),n):("all"===t&&n.push("description"),"params"!==t&&"all"!==t||n.push("@param","@returns"),n)}const d=["constructor","connectedCallback","disconnectedCallback","attributeChangedCallback","renderCallback"];
8
+ export function checkUndocumentedFile(e,t,n){const s=[];
9
+ let o;try{o=i(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return s}const c=extractComments(e);return r.simple(o,{ClassDeclaration(e){const o=e.id?.name||"Anonymous";"all"===n&&(findJSDocBefore(c,e.loc.start.line)||s.push({name:o,type:"class",file:t,line:e.loc.start.line,reason:"No JSDoc"}));for(const i of e.body.body)if("MethodDefinition"===i.type){const e=i.key.name||i.key.value;if("get"===i.kind||"set"===i.kind)continue;if(e?.startsWith("_"))continue;if(d.includes(e))continue;
10
+ const r=checkMissing(findJSDocBefore(c,i.loc.start.line),n);r.length>0&&s.push({name:`${o}.${e}`,type:"method",file:t,line:i.loc.start.line,reason:r.join(", ")})}},FunctionDeclaration(e){if(!e.id)return;
11
+ const o=e.id.name;if(o.startsWith("_"))return;
12
+ const i=checkMissing(findJSDocBefore(c,e.loc.start.line),n);i.length>0&&s.push({name:o,type:"function",file:t,line:e.loc.start.line,reason:i.join(", ")})}}),s}
13
+ export function getUndocumented(t,n="tests"){const s=c(t),i=findJSFiles(t),r=[];for(const t of i){let c;try{c=e(t,"utf-8")}catch(e){continue}const i=checkUndocumentedFile(c,o(s,t),n);r.push(...i)}return r}
14
+ export function getUndocumentedSummary(e,t="tests"){const n=getUndocumented(e,t),s={class:n.filter(e=>"class"===e.type).length,function:n.filter(e=>"function"===e.type).length,method:n.filter(e=>"method"===e.type).length},o={};for(const e of n)o[e.reason]=(o[e.reason]||0)+1;return{total:n.length,byType:s,byReason:o,items:n.slice(0,20)}}
@@ -0,0 +1,4 @@
1
+ // @ctx .context/src/cli/cli-handlers.ctx
2
+ import{getSkeleton as r,expand as e,deps as s,usages as t}from"../mcp/tools.js";import{getPendingTests as a,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 g}from"../analysis/complexity.js";import{getLargeFiles as u}from"../analysis/large-files.js";import{getOutdatedPatterns as p}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 A}from"../mcp/tools.js";import{parseProject as P}from"../core/parser.js";import{resolvePath as q}from"../core/workspace.js";import{checkJSDocConsistency as x}from"../analysis/jsdoc-checker.js";import{checkTypes as E}from"../analysis/type-checker.js";import{compactProject as b,expandProject as w}from"../compact/compact.js";import{injectJSDoc as S,stripJSDoc as U,validateCtxContracts as k}from"../compact/ctx-to-jsdoc.js";import{getConfig as C,setConfig as v,getModeDescription as D,getModeWorkflow as I}from"../compact/mode-config.js";function getArg(r,e){const s=r.find(r=>r.startsWith(`--${e}=`));return s?s.split("=")[1]:void 0}
3
+ function getPath(r){const e=r.find(r=>!r.startsWith("--"))||".";return q(e)}
4
+ export const CLI_HANDLERS={skeleton:{requiresArg:!0,argError:"Path required: skeleton <path>",handler:async e=>r(q(e[0]))},expand:{requiresArg:!0,argError:"Symbol required: expand <symbol>",handler:async r=>e(r[0])},deps:{requiresArg:!0,argError:"Symbol required: deps <symbol>",handler:async r=>s(r[0])},usages:{requiresArg:!0,argError:"Symbol required: usages <symbol>",handler:async r=>t(r[0])},pending:{handler:async r=>a(getPath(r))},summary:{handler:async r=>n(getPath(r))},filters:{handler:async()=>o()},instructions:{rawOutput:!0,handler:async()=>c()},undocumented:{handler:async r=>{const e=getArg(r,"level")||"tests";return i(getPath(r),e)}},deadcode:{handler:async r=>d(getPath(r))},jsdoc:{requiresArg:!0,argError:"Usage: jsdoc <file>",handler:async r=>l(q(r[0]))},similar:{handler:async r=>{const e=parseInt(getArg(r,"threshold"))||60;return m(getPath(r),{threshold:e})}},complexity:{handler:async r=>{const e=parseInt(getArg(r,"min"))||1,s=r.includes("--problematic");return g(getPath(r),{minComplexity:e,onlyProblematic:s})}},largefiles:{handler:async r=>{const e=r.includes("--problematic");return u(getPath(r),{onlyProblematic:e})}},outdated:{handler:async r=>{const e=r.includes("--code"),s=r.includes("--deps");return p(getPath(r),{codeOnly:e,depsOnly:s})}},analyze:{handler:async r=>{const e=r.includes("--items");return y(getPath(r),{includeItems:e})}},"jsdoc-check":{handler:async r=>x(getPath(r))},types:{handler:async r=>{const e=parseInt(getArg(r,"max"))||50;return E(getPath(r),{maxDiagnostics:e})}},compress:{requiresArg:!0,argError:"Usage: compress <file> [--no-beautify] [--no-legend]",handler:async r=>{const e=!r.includes("--no-beautify"),s=!r.includes("--no-legend");return h(q(r[0]),{beautify:e,legend:s})}},docs:{requiresArg:!0,argError:"Usage: docs <path> [--file=<filename>]",handler:async r=>{const e=q(r[0]),s=await A(e),t=r.find(r=>r.startsWith("--file="))?.split("=")[1];return f(s,e,{file:t})}},"generate-ctx":{requiresArg:!0,argError:"Usage: generate-ctx <path> [--overwrite] [--scope=focus|all]",handler:async r=>{const e=q(r[0]),s=await A(e),t=await P(e),a=r.includes("--overwrite"),n=r.find(r=>r.startsWith("--scope="))?.split("=")[1]||"all";return j(s,e,t,{overwrite:a,scope:n})}},compact:{requiresArg:!0,argError:"Usage: compact <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return b(e,{dryRun:s})}},beautify:{requiresArg:!0,argError:"Usage: beautify <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return w(e,{dryRun:s})}},"inject-jsdoc":{requiresArg:!0,argError:"Usage: inject-jsdoc <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return S(e,{dryRun:s})}},"strip-jsdoc":{requiresArg:!0,argError:"Usage: strip-jsdoc <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return U(e,{dryRun:s})}},"validate-ctx":{requiresArg:!0,argError:"Usage: validate-ctx <path> [--strict]",handler:async r=>{const e=q(r[0]),s=r.includes("--strict");return k(e,{strict:s})}},mode:{requiresArg:!0,argError:"Usage: mode <path>",handler:async r=>{const e=q(r[0]),s=C(e);return{...s,description:D(s.mode),workflow:I(s.mode)}}},"set-mode":{requiresArg:!0,argError:"Usage: set-mode <path> <1|2|3>",handler:async r=>{const e=q(r[0]),s=parseInt(r[1],10);if(!s||![1,2,3].includes(s))throw new Error("Mode must be 1, 2, or 3");return v(e,{mode:s})}}};
package/src/cli/cli.js ADDED
@@ -0,0 +1,5 @@
1
+ // @ctx .context/src/cli/cli.ctx
2
+ import{CLI_HANDLERS as e}from"./cli-handlers.js";
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 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 ↔ 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 — strips comments/whitespace (--dry-run)\n beautify <path> Beautify/expand all JS files — inverse of compact (--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 set-mode <path> <1|2|3> Set compact code mode (1=compact, 2=full, 3=IDE)\n filters Show current filter configuration\n instructions Show agent guidelines (JSDoc, Arch)\n help Show this help\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
+ export async function runCLI(n,t){if(!n||"help"===n||"--help"===n||"-h"===n)return void printHelp();
5
+ const o=e[n];o||(console.error(`Unknown command: ${n}`),console.error('Run with "help" for usage information'),process.exit(1)),o.requiresArg&&!t[0]&&(console.error(o.argError||`Argument required for: ${n}`),process.exit(1));try{const e=await o.handler(t);o.rawOutput?console.log(e):console.log(JSON.stringify(e,null,2))}catch(e){console.error("Error:",e.message),process.exit(1)}}
@@ -0,0 +1,7 @@
1
+ // @ctx .context/src/compact/ai-context.ctx
2
+ import{resolve as e,extname as t}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";
3
+ const c=new Set([".js",".mjs",".ts",".tsx"]);function estimateTokens(e){const t="string"==typeof e?e:JSON.stringify(e);return Math.ceil(t.length/4)}
4
+ export async function getAiContext(a,l={}){const{includeFiles:f=[],includeDocs:m=!0,includeSkeleton:d=!0}=l,p=e(a),u={};
5
+ let g=0;if(d&&(u.skeleton=await s(p),g+=estimateTokens(u.skeleton)),m){const e=await o(p);u.docs=n(e,p),g+=estimateTokens(u.docs)}if(f.length>0){u.files={};
6
+ const e=r(p);for(const s of f){const o=e.find(e=>e.endsWith(s)||e.endsWith("/"+s));if(!o){u.files[s]={error:`File not found: ${s}`};continue}const n=t(o).toLowerCase();if(c.has(n))try{const e=await i(o,{beautify:!0,legend:!0});u.files[s]=e.code,g+=e.compressed}catch(e){u.files[s]={error:e.message}}else u.files[s]={error:`Unsupported file type: ${n}`}}}const h=r(p);
7
+ let k=0;for(const e of h)try{const{readFileSync:t}=await import("fs");k+=estimateTokens(t(e,"utf-8"))}catch{}const y=k>0?Math.round(100*(1-g/k)):0;return u.totalTokens=g,u.vsOriginal=k,u.savings=`${y}%`,u}
@@ -0,0 +1,18 @@
1
+ // @ctx .context/src/compact/compact.ctx
2
+ import{readFileSync as e,writeFileSync as t,readdirSync as n,statSync as o,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
+ const d=new Set([".js",".mjs"]),f=new Set(["node_modules",".git","vendor",".context","dev-docs",".agent",".agents"]);function walkJSFiles(e,t=e){const s=[];try{for(const a of n(e)){if(a.startsWith(".")&&"."!==a)continue;
4
+ const n=r(e,a);o(n).isDirectory()?f.has(a)||s.push(...walkJSFiles(n,t)):d.has(c(a).toLowerCase())&&s.push(n)}}catch{}return s}
5
+ 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")}
6
+ function resolveCtxPath(e,t){const n=a(t,e),o=i(n,c(n))+".ctx",u=l(n),d=r(t,".context",u,o);if(s(d))return".context/"+u+"/"+o;
7
+ const f=r(t,u,o);return s(f)?u+"/"+o:null}
8
+ async function compactFile(n,o){const s=e(n,"utf-8"),r=s.length;if(!s.trim())return{original:0,compacted:0};
9
+ const c=await u(s,{compress:{dead_code:!0,drop_console:!1,passes:1,reduce_funcs:!1,inline:!1},mangle:{keep_fnames:!0,module:!0},module:!0,output:{beautify:!1,comments:!1,semicolons:!0}});if(c.error)throw c.error;
10
+ let a=addTopLevelNewlines(c.code);if(o){const e=resolveCtxPath(n,o);e&&(a.startsWith("#!")?a=a.replace(/^(#![^\n]*\n)/,"$1// @ctx "+e+"\n"):a="// @ctx "+e+"\n"+a)}return t(n,a,"utf-8"),{original:r,compacted:a.length}}
11
+ async function beautifyFile(n){const o=e(n,"utf-8"),s=o.length;if(!o.trim())return{original:0,beautified:0};
12
+ const r=await u(o,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!1,indent_level:2,semicolons:!0}});if(r.error)throw r.error;return t(n,r.code+"\n","utf-8"),{original:s,beautified:r.code.length}}
13
+ export async function compactProject(t,n={}){const{dryRun:o=!1}=n,s=walkJSFiles(t);
14
+ let r=0,c=0;
15
+ const i=[],l=[];for(const n of s){const s=a(t,n);try{const a=e(n,"utf-8");if(r+=a.length,o)c+=addTopLevelNewlines((await u(a,{compress:{dead_code:!0,drop_console:!1,passes:1,reduce_funcs:!1,inline:!1},mangle:{keep_fnames:!0,module:!0},module:!0,output:{beautify:!1,comments:!1}})).code||"").length||a.length;else{const{compacted:e}=await compactFile(n,t);c+=e}i.push(s)}catch(e){l.push({file:s,error:e.message})}}const d=r>0?Math.round(100*(1-c/r)):0;return{files:i.length,fileList:i,originalBytes:r,compactedBytes:c,savings:`${d}%`,errors:l.length>0?l:void 0,dryRun:o}}
16
+ export async function expandProject(t,n={}){const{dryRun:o=!1}=n,s=walkJSFiles(t);
17
+ let r=0,c=0;
18
+ const i=[],l=[];for(const n of s){const s=a(t,n);try{const t=e(n,"utf-8");if(r+=t.length,o){const e=await u(t,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!1,indent_level:2}});c+=e.code?.length||t.length}else{const{beautified:e}=await beautifyFile(n);c+=e}i.push(s)}catch(e){l.push({file:s,error:e.message})}}return{files:i.length,fileList:i,originalBytes:r,beautifiedBytes:c,errors:l.length>0?l:void 0,dryRun:o}}
@@ -0,0 +1,13 @@
1
+ // @ctx .context/src/compact/compress.ctx
2
+ 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
+ const o=new Set([".js",".mjs",".ts",".tsx"]);function estimateTokens(e){return Math.ceil(e.length/4)}
4
+ function extractLegend(e,n){const a=[];a.push(`--- ${t(n)} ---`);try{const t=s(e,{ecmaVersion:2022,sourceType:"module",locations:!0});r(t,{ExportNamedDeclaration(t){const n=t.declaration;if(!n)return;
5
+ let s="";if(t.start>0){const n=Math.max(0,t.start-500),a=e.slice(n,t.start).trimEnd().match(/\/\*\*[\s\S]*?\*\/\s*$/);if(a&&e.slice(n+a.index+a[0].length,t.start).split("\n").length<=3){const e=a[0].replace(/\/\*\*\s*\n?/,"").replace(/\s*\*\//,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"").trim()).filter(e=>e&&!e.startsWith("@")).join(" ").trim();e&&(s=e.length>80?e.slice(0,77)+"...":e)}}if("FunctionDeclaration"===n.type){const e=n.id?.name||"anonymous",t=n.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"...").join(","),r=`${n.async?"async ":""}${e}(${t})`;a.push(s?`${r}|${s}`:r)}if("ClassDeclaration"===n.type){const e=n.id?.name||"AnonymousClass",t=n.superClass?` extends ${n.superClass.name||"?"}`:"";a.push(`class ${e}${t}${s?"|"+s:""}`);for(const e of n.body.body)if("MethodDefinition"===e.type&&e.key?.name){const t=e.value.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"...").join(",");a.push(` .${e.key.name}(${t})`)}}if("VariableDeclaration"===n.type)for(const e of n.declarations)e.id?.name&&a.push(`${n.kind} ${e.id.name}${s?"|"+s:""}`)}})}catch(e){a.push(`PARSE_ERROR: ${e.message}`)}return a.join("\n")}
6
+ export async function compressFile(t,s={}){const{beautify:r=!0,legend:i=!0}=s,c=n(t).toLowerCase();if(!o.has(c))throw new Error(`Unsupported file type: ${c}. Supported: ${[...o].join(", ")}`);
7
+ const l=e(t,"utf-8"),m=estimateTokens(l);if(!l.trim())return{code:"",legend:"",original:0,compressed:0,savings:"0%"};
8
+ const d={compress:{dead_code:!0,drop_console:!1,passes:2},mangle:!1,module:!0,output:{beautify:r,comments:!1,semicolons:!r}};
9
+ let p;try{const e=await a(l,d);if(e.error)throw e.error;p=e.code}catch(e){p=l.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*/g,"").replace(/\n{3,}/g,"\n\n").trim()}const f=i?extractLegend(l,t):"",u=f?`/*\n${f}\n*/\n${p}`:p,y=estimateTokens(u);return{code:u,legend:f,original:m,compressed:y,savings:`${m>0?Math.round(100*(1-y/m)):0}%`}}
10
+ export async function editCompressed(t,n,r,o={}){const{beautify:i=!0,dryRun:c=!1}=o,l=e(t,"utf-8");
11
+ let m;try{m=s(l,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){throw new Error(`Failed to parse ${t}: ${e.message}`)}const d=findSymbolRange(m,l,n);if(!d)throw new Error(`Symbol "${n}" not found in ${t}`);
12
+ let p=l.slice(0,d.start)+r+l.slice(d.end);if(i)try{const e=await a(p,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!0,semicolons:!1}});e.code&&(p=e.code)}catch{}try{s(p,{ecmaVersion:"latest",sourceType:"module"})}catch(e){throw new Error(`Edit would create invalid syntax: ${e.message}`)}if(!c){const{writeFileSync:e}=await import("fs");e(t,p,"utf-8")}return{success:!0,file:t,symbol:n,oldRange:{start:d.start,end:d.end},newLength:r.length,...c?{dryRun:!0}:{}}}
13
+ function findSymbolRange(e,t,n){let a=null;return r(e,{FunctionDeclaration(e){e.id?.name===n&&(a={start:e.start,end:e.end,type:"FunctionDeclaration"})},ClassDeclaration(e){e.id?.name===n&&(a={start:e.start,end:e.end,type:"ClassDeclaration"})},VariableDeclaration(e){for(const t of e.declarations)t.id?.name===n&&(a={start:e.start,end:e.end,type:"VariableDeclaration"})},ExportNamedDeclaration(e){if(e.declaration){const t=e.declaration;(t.id?.name||t.declarations?.[0]?.id?.name)===n&&(a={start:e.start,end:e.end,type:"ExportNamedDeclaration"})}},ExportDefaultDeclaration(e){e.declaration?.id?.name===n&&(a={start:e.start,end:e.end,type:"ExportDefaultDeclaration"})}}),a}