project-graph-mcp 2.1.13 → 2.2.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.
@@ -0,0 +1,91 @@
1
+
2
+ # 🤖 Project Guidelines for AI Agents
3
+
4
+ ## 1. Architecture Standards (Symbiote.js)
5
+ - **Component Structure**: Always use Triple-File Partitioning for components:
6
+ - `MyComponent.js`: Class logic (extends Symbiote)
7
+ - `MyComponent.tpl.js`: HTML template (export template)
8
+ - `MyComponent.css.js`: CSS styles (export rootStyles/shadowStyles)
9
+ - **State Management**: Use `this.init$` for local state and `this.sub()` for reactivity.
10
+ - **Directives**: Use `itemize` for lists, `js-d-kit` for static generation.
11
+
12
+ ## 2. General Coding Rules
13
+ - **ESM Only**: Use `import` / `export`. No `require`.
14
+ - **No Dependencies**: Avoid adding new npm packages unless critical.
15
+ - **Comments**: Write clear JSDoc for all public methods.
16
+ - **Async/Await**: Prefer async/await over promises.
17
+
18
+ ## 3. MCP Tools — Recommended Workflow
19
+
20
+ ### Three Modes of `get_ai_context`
21
+
22
+ | Mode | Call | Returns | Tokens |
23
+ |------|------|---------|--------|
24
+ | **Code only** | `includeFiles: ["*"]` | All compressed source files | ~40-50k |
25
+ | **Code + context** | `includeFiles: ["*"], includeSkeleton: true, includeDocs: true` | Source + skeleton + docs | ~50-55k |
26
+ | **Overview** | _(default, no includeFiles)_ | Skeleton + docs only | ~2-3k |
27
+
28
+ ### Quick Start: Full Codebase Context
29
+ For small/medium projects (under 100k tokens), load ALL code at once:
30
+ ```
31
+ get_ai_context({ path: ".", includeFiles: ["*"] })
32
+ ```
33
+ Returns compressed source of all JS files — pure code, no metadata noise.
34
+
35
+ To also get structural overview and documentation:
36
+ ```
37
+ get_ai_context({ path: ".", includeFiles: ["*"], includeSkeleton: true, includeDocs: true })
38
+ ```
39
+
40
+ ### Step-by-Step: Large Projects
41
+ 1. **Overview**: `get_ai_context({ path: "." })` → skeleton + docs (~2-3k tokens)
42
+ 2. **Navigate**: `expand("ClassName")` → read specific class code
43
+ 3. **Dependencies**: `deps("symbol")` / `usages("symbol")` → trace connections
44
+ 4. **Focus Zone**: `get_focus_zone({ useGitDiff: true })` → recently changed files
45
+
46
+ ### After Code Changes
47
+ - Call `invalidate_cache()` to refresh the graph.
48
+
49
+ ### .contextignore
50
+ Placed in project root, controls which files are excluded from `includeFiles: ["*"]`.
51
+ Auto-created with sensible defaults (vendor/, *.min.js, chart.js, etc.).
52
+ Users can edit to add project-specific exclusions.
53
+
54
+ ## 4. Custom Rules System
55
+ Configurable code analysis with auto-detection.
56
+
57
+ ### Available Tools
58
+ - `get_custom_rules`: List all rulesets and their rules
59
+ - `set_custom_rule`: Add or update a rule in a ruleset
60
+ - `check_custom_rules`: Run analysis (auto-detects applicable rulesets)
61
+
62
+ ### Auto-Detection
63
+ Rulesets are applied automatically based on:
64
+ 1. `package.json` dependencies
65
+ 2. Import patterns in source code
66
+ 3. Code patterns (e.g., `extends Symbiote`)
67
+
68
+ ### Creating New Rules
69
+ Use `set_custom_rule` to add framework-specific rules:
70
+ ```json
71
+ {
72
+ "ruleSet": "my-framework-2x",
73
+ "rule": {
74
+ "id": "my-rule-id",
75
+ "name": "Rule Name",
76
+ "description": "What this rule checks",
77
+ "pattern": "badPattern",
78
+ "patternType": "string",
79
+ "replacement": "Use goodPattern instead",
80
+ "severity": "warning",
81
+ "filePattern": "*.js",
82
+ "docs": "https://docs.example.com/rule"
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Severity Levels
88
+ - `error`: Critical issues that must be fixed
89
+ - `warning`: Important but not blocking
90
+ - `info`: Suggestions and best practices
91
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-graph-mcp",
3
- "version": "2.1.13",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for AI agents — project graph, code quality analysis, visual web explorer. JS, TS, Python, Go.",
6
6
  "main": "src/network/server.js",
@@ -0,0 +1,22 @@
1
+ # Files and directories excluded from AI context (get_ai_context includeFiles: ["*"])
2
+ # Glob patterns — one per line
3
+
4
+ # Vendored / bundled libs
5
+ vendor/
6
+ *.min.js
7
+ *.bundle.js
8
+ chart.js
9
+ d3.js
10
+ three.js
11
+ lodash.js
12
+ jquery*.js
13
+
14
+ # Generated
15
+ dist/
16
+ build/
17
+ coverage/
18
+ *.d.ts
19
+
20
+ # Tests
21
+ *.test.js
22
+ *.spec.js
@@ -1 +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":1776021880954.865,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/complexity.js":1776020743745.4976,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/custom-rules.js":1776020743746.4695,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/db-analysis.js":1776020743747.036,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/dead-code.js":1776020743747.205,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/full-analysis.js":1776020743747.434,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/jsdoc-checker.js":1776020743747.778,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/jsdoc-generator.js":1776020743748.1309,"/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":1776020743748.3684,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/test-annotations.js":1776021880973.3127,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/type-checker.js":1776020743790.671,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/undocumented.js":1776020743791.3872,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/cli/cli-handlers.js":1776020743791.534,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/cli/cli.js":1776009281031.448,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ai-context.js":1776024855845.1536,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compact-migrate.js":1776025662126.3894,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compact.js":1776025662121.6743,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compress.js":1776024835288.3477,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ctx-resolver.js":1776024988359.2908,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ctx-to-jsdoc.js":1776025604473.7043,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/doc-dialect.js":1776021881027.7375,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/expand.js":1776025662125.8457,"/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/jsdoc-builder.js":1776024792784.1958,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/mode-config.js":1776021881032.5818,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/split-declarations.js":1776020731393.9968,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/validate-pipeline.js":1776025626422.5903,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/event-bus.js":1776021881034.3083,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/file-walker.js":1776024739329.9265,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/filters.js":1776021881038.4585,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/graph-builder.js":1776020731524.8525,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/parser.js":1776021881058.1765,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/utils.js":1776024855849.509,"/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":1776021881068.4143,"/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":1776017634423.9546,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/tool-defs.js":1776018403086.5388,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/tools.js":1776021881078.6213,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/backend-lifecycle.js":1776021881086.6484,"/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":1776020743913.1072,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/mdns.js":1776020743913.2654,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/server.js":1776016300633.3318,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/web-server.js":1776026310263.7048},"graph":{"v":1,"legend":{"computeContentHash":"CH","getCachePath":"CP","readCache":"rC","writeCache":"wC","isCacheValid":"CV","p":"p","u":"u","h":"h","analyzeComplexityFile":"CF","f":"f","getComplexity":"gC","y":"y","g":"g","x":"x","v":"v","$":"$","j":"j","S":"sS","b":"b","getCustomRules":"CR","setCustomRule":"CR1","deleteCustomRule":"CR2","detectProjectRuleSets":"PRS","checkCustomRules":"CR3","getDBSchema":"DBS","getTableUsage":"TU","getDBDeadTables":"DBD","m":"m","getDeadCode":"DC","w":"w","F":"fF","D":"dD","M":"mM","P":"pP","getFullAnalysis":"FA","getAnalysisSummaryOnly":"ASO","d":"d","checkJSDocFile":"JSD","checkJSDocConsistency":"JSD1","generateJSDoc":"JSD2","i":"i","o":"o","s":"s","generateJSDocFor":"JSD3","findJSFiles":"JSF","analyzeFile":"aF","getLargeFiles":"LF","analyzeFilePatterns":"FP","analyzePackageJson":"PJ","getOutdatedPatterns":"OP","getSimilarFunctions":"SF","a":"a","parseAnnotations":"pA","getAllFeatures":"AF","getPendingTests":"PT","markTestPassed":"TP","markTestFailed":"TF","getTestSummary":"TS","resetTestState":"TS1","l":"l","checkTypes":"cT","checkUndocumentedFile":"UF","getUndocumented":"gU","getUndocumentedSummary":"US","O":"oO","R":"rR","printHelp":"pH","runCLI":"CLI","getAiContext":"AC","compactMigrate":"cM","addTopLevelNewlines":"TLN","compactFile":"cF","beautifyFile":"bF","compactProject":"cP","expandProject":"eP","c":"c","compressFile":"cF1","editCompressed":"eC","resolveCtxPath":"CP1","resolveCtxRelPath":"CRP","readCtxFile":"CF1","parseCtxFile":"CF2","injectJSDoc":"JSD4","stripJSDoc":"JSD5","validateCtxContracts":"CC","generateDocDialect":"DD","readContextDocs":"CD","getProjectDocs":"PD","E":"eE","checkStaleness":"cS","generateContextFiles":"CF3","expandFile":"eF","fetchReference":"fR","listAvailable":"lA","getFrameworkReference":"FR","getInstructions":"gI","parseCtxParams":"CP2","sanitize":"sa","buildJSDocBlock":"JSD6","buildJSDocFromRaw":"JSD7","getConfig":"gC1","setConfig":"sC","getModeDescription":"MD","getModeWorkflow":"MW","splitDeclarations":"sD","isSingleLineBlob":"SLB","validatePipeline":"vP","emitToolCall":"TC","emitToolResult":"TR","onToolCall":"TC1","onToolResult":"TR1","removeToolListener":"TL","walkJSFiles":"JSF1","getFilters":"gF","setFilters":"sF","addExcludes":"aE","removeExcludes":"rE","resetFilters":"rF","parseGitignore":"pG","shouldExcludeDir":"ED","shouldExcludeFile":"EF","minifyLegend":"mL","e":"e","buildGraph":"bG","createSkeleton":"cS1","parseFile":"pF","discoverSubProjects":"SP","parseProject":"pP1","findAllProjectFiles":"APF","estimateTokens":"eT","setRoots":"sR","getWorkspaceRoot":"WR","resolvePath":"rP","parseGo":"pG1","extractImports":"eI","getBody":"gB","extractCalls":"eC1","parsePython":"pP2","isSQLString":"SQL","t":"t","extractSQLFromString":"SQL1","parseSQL":"SQL2","n":"n","extractSQLFromCode":"SQL3","extractORMFromCode":"ORM","parseTypeScript":"TS2","extractParams":"eP1","stripStringsAndComments":"SAC","createServer":"cS2","startStdioServer":"SS","getGraph":"gG","getSkeleton":"gS","getFocusZone":"FZ","expand":"ex","deps":"de","usages":"us","getCallChain":"CC1","invalidateCache":"iC","B":"bB","writePortFile":"PF","removePortFile":"PF1","listBackends":"lB","ensureBackend":"eB","startStdioProxy":"SP1","cleanup":"cl","registerService":"rS","getGatewayPort":"GP","registerLocal":"rL","k":"k","N":"nN","z":"z","A":"aA","startWebServer":"WS"},"reverseLegend":{"CH":"computeContentHash","CP":"getCachePath","rC":"readCache","wC":"writeCache","CV":"isCacheValid","p":"p","u":"u","h":"h","CF":"analyzeComplexityFile","f":"f","gC":"getComplexity","y":"y","g":"g","x":"x","v":"v","$":"$","j":"j","sS":"S","b":"b","CR":"getCustomRules","CR1":"setCustomRule","CR2":"deleteCustomRule","PRS":"detectProjectRuleSets","CR3":"checkCustomRules","DBS":"getDBSchema","TU":"getTableUsage","DBD":"getDBDeadTables","m":"m","DC":"getDeadCode","w":"w","fF":"F","dD":"D","mM":"M","pP":"P","FA":"getFullAnalysis","ASO":"getAnalysisSummaryOnly","d":"d","JSD":"checkJSDocFile","JSD1":"checkJSDocConsistency","JSD2":"generateJSDoc","i":"i","o":"o","s":"s","JSD3":"generateJSDocFor","JSF":"findJSFiles","aF":"analyzeFile","LF":"getLargeFiles","FP":"analyzeFilePatterns","PJ":"analyzePackageJson","OP":"getOutdatedPatterns","SF":"getSimilarFunctions","a":"a","pA":"parseAnnotations","AF":"getAllFeatures","PT":"getPendingTests","TP":"markTestPassed","TF":"markTestFailed","TS":"getTestSummary","TS1":"resetTestState","l":"l","cT":"checkTypes","UF":"checkUndocumentedFile","gU":"getUndocumented","US":"getUndocumentedSummary","oO":"O","rR":"R","pH":"printHelp","CLI":"runCLI","AC":"getAiContext","cM":"compactMigrate","TLN":"addTopLevelNewlines","cF":"compactFile","bF":"beautifyFile","cP":"compactProject","eP":"expandProject","c":"c","cF1":"compressFile","eC":"editCompressed","CP1":"resolveCtxPath","CRP":"resolveCtxRelPath","CF1":"readCtxFile","CF2":"parseCtxFile","JSD4":"injectJSDoc","JSD5":"stripJSDoc","CC":"validateCtxContracts","DD":"generateDocDialect","CD":"readContextDocs","PD":"getProjectDocs","eE":"E","cS":"checkStaleness","CF3":"generateContextFiles","eF":"expandFile","fR":"fetchReference","lA":"listAvailable","FR":"getFrameworkReference","gI":"getInstructions","CP2":"parseCtxParams","sa":"sanitize","JSD6":"buildJSDocBlock","JSD7":"buildJSDocFromRaw","gC1":"getConfig","sC":"setConfig","MD":"getModeDescription","MW":"getModeWorkflow","sD":"splitDeclarations","SLB":"isSingleLineBlob","vP":"validatePipeline","TC":"emitToolCall","TR":"emitToolResult","TC1":"onToolCall","TR1":"onToolResult","TL":"removeToolListener","JSF1":"walkJSFiles","gF":"getFilters","sF":"setFilters","aE":"addExcludes","rE":"removeExcludes","rF":"resetFilters","pG":"parseGitignore","ED":"shouldExcludeDir","EF":"shouldExcludeFile","mL":"minifyLegend","e":"e","bG":"buildGraph","cS1":"createSkeleton","pF":"parseFile","SP":"discoverSubProjects","pP1":"parseProject","APF":"findAllProjectFiles","eT":"estimateTokens","sR":"setRoots","WR":"getWorkspaceRoot","rP":"resolvePath","pG1":"parseGo","eI":"extractImports","gB":"getBody","eC1":"extractCalls","pP2":"parsePython","SQL":"isSQLString","t":"t","SQL1":"extractSQLFromString","SQL2":"parseSQL","n":"n","SQL3":"extractSQLFromCode","ORM":"extractORMFromCode","TS2":"parseTypeScript","eP1":"extractParams","SAC":"stripStringsAndComments","cS2":"createServer","SS":"startStdioServer","gG":"getGraph","gS":"getSkeleton","FZ":"getFocusZone","ex":"expand","de":"deps","us":"usages","CC1":"getCallChain","iC":"invalidateCache","bB":"B","PF":"writePortFile","PF1":"removePortFile","lB":"listBackends","eB":"ensureBackend","SP1":"startStdioProxy","cl":"cleanup","rS":"registerService","GP":"getGatewayPort","rL":"registerLocal","k":"k","nN":"N","z":"z","aA":"A","WS":"startWebServer"},"stats":{"files":51,"classes":0,"functions":240,"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"},"p":{"t":"F","e":false,"f":"network/local-gateway.js"},"u":{"t":"F","e":false,"f":"network/web-server.js"},"h":{"t":"F","e":false,"f":"network/local-gateway.js"},"CF":{"t":"F","e":true,"f":"analysis/complexity.js"},"f":{"t":"F","e":false,"f":"network/local-gateway.js"},"gC":{"t":"F","e":true,"f":"analysis/complexity.js"},"y":{"t":"F","e":false,"f":"network/web-server.js"},"g":{"t":"F","e":false,"f":"network/web-server.js"},"x":{"t":"F","e":false,"f":"network/web-server.js"},"v":{"t":"F","e":false,"f":"core/parser.js"},"$":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"j":{"t":"F","e":false,"f":"core/parser.js"},"sS":{"t":"F","e":false,"f":"network/web-server.js"},"b":{"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"},"m":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"DC":{"t":"F","e":true,"f":"analysis/dead-code.js"},"w":{"t":"F","e":false,"f":"network/web-server.js"},"fF":{"t":"F","e":false,"f":"compact/ctx-to-jsdoc.js"},"dD":{"t":"F","e":false,"f":"compact/validate-pipeline.js"},"mM":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"pP":{"t":"F","e":false,"f":"network/web-server.js"},"FA":{"t":"F","e":true,"f":"analysis/full-analysis.js"},"ASO":{"t":"F","e":true,"f":"analysis/full-analysis.js"},"d":{"t":"F","e":false,"f":"network/local-gateway.js"},"JSD":{"t":"F","e":true,"f":"analysis/jsdoc-checker.js"},"JSD1":{"t":"F","e":true,"f":"analysis/jsdoc-checker.js"},"JSD2":{"t":"F","e":true,"f":"analysis/jsdoc-generator.js"},"i":{"t":"F","e":false,"f":"analysis/type-checker.js"},"o":{"t":"F","e":false,"f":"network/mdns.js"},"s":{"t":"F","e":false,"f":"analysis/jsdoc-generator.js"},"JSD3":{"t":"F","e":true,"f":"analysis/jsdoc-generator.js"},"JSF":{"t":"F","e":true,"f":"core/parser.js"},"aF":{"t":"F","e":false,"f":"analysis/large-files.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"},"SF":{"t":"F","e":true,"f":"analysis/similar-functions.js"},"a":{"t":"F","e":false,"f":"analysis/test-annotations.js"},"pA":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"AF":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"PT":{"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":true,"f":"analysis/test-annotations.js"},"TS1":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"l":{"t":"F","e":false,"f":"network/local-gateway.js"},"cT":{"t":"F","e":true,"f":"analysis/type-checker.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"},"oO":{"t":"F","e":false,"f":"network/web-server.js"},"rR":{"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"},"AC":{"t":"F","e":true,"f":"compact/ai-context.js"},"cM":{"t":"F","e":true,"f":"compact/compact-migrate.js"},"TLN":{"t":"F","e":false,"f":"compact/compact.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"},"c":{"t":"F","e":false,"f":"network/mdns.js"},"cF1":{"t":"F","e":true,"f":"compact/compress.js"},"eC":{"t":"F","e":true,"f":"compact/compress.js"},"CP1":{"t":"F","e":true,"f":"compact/ctx-resolver.js"},"CRP":{"t":"F","e":true,"f":"compact/ctx-resolver.js"},"CF1":{"t":"F","e":true,"f":"compact/ctx-resolver.js"},"CF2":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"JSD4":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"JSD5":{"t":"F","e":true,"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"},"CD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"PD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"eE":{"t":"F","e":false,"f":"core/parser.js"},"cS":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"CF3":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"eF":{"t":"F","e":true,"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"},"CP2":{"t":"F","e":true,"f":"compact/jsdoc-builder.js"},"sa":{"t":"F","e":false,"f":"compact/jsdoc-builder.js"},"JSD6":{"t":"F","e":true,"f":"compact/jsdoc-builder.js"},"JSD7":{"t":"F","e":true,"f":"compact/jsdoc-builder.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"},"sD":{"t":"F","e":true,"f":"compact/split-declarations.js"},"SLB":{"t":"F","e":true,"f":"compact/split-declarations.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"},"JSF1":{"t":"F","e":true,"f":"core/file-walker.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"},"pG":{"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"},"mL":{"t":"F","e":true,"f":"core/graph-builder.js"},"e":{"t":"F","e":false,"f":"core/graph-builder.js"},"bG":{"t":"F","e":true,"f":"core/graph-builder.js"},"cS1":{"t":"F","e":true,"f":"core/graph-builder.js"},"pF":{"t":"F","e":true,"f":"core/parser.js"},"SP":{"t":"F","e":true,"f":"core/parser.js"},"pP1":{"t":"F","e":true,"f":"core/parser.js"},"APF":{"t":"F","e":true,"f":"core/parser.js"},"eT":{"t":"F","e":true,"f":"core/utils.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"},"pG1":{"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"},"eC1":{"t":"F","e":false,"f":"lang/lang-go.js"},"pP2":{"t":"F","e":true,"f":"lang/lang-python.js"},"SQL":{"t":"F","e":true,"f":"lang/lang-sql.js"},"t":{"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"},"n":{"t":"F","e":false,"f":"network/mdns.js"},"SQL3":{"t":"F","e":true,"f":"lang/lang-sql.js"},"ORM":{"t":"F","e":true,"f":"lang/lang-sql.js"},"TS2":{"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"},"cS2":{"t":"F","e":true,"f":"mcp/mcp-server.js"},"SS":{"t":"F","e":true,"f":"mcp/mcp-server.js"},"gG":{"t":"F","e":true,"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"},"CC1":{"t":"F","e":true,"f":"mcp/tools.js"},"iC":{"t":"F","e":true,"f":"mcp/tools.js"},"bB":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"PF":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"PF1":{"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"},"SP1":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"cl":{"t":"F","e":false,"f":"network/backend.js"},"rS":{"t":"F","e":true,"f":"network/local-gateway.js"},"GP":{"t":"F","e":true,"f":"network/local-gateway.js"},"rL":{"t":"F","e":true,"f":"network/mdns.js"},"k":{"t":"F","e":false,"f":"network/web-server.js"},"nN":{"t":"F","e":false,"f":"network/web-server.js"},"z":{"t":"F","e":false,"f":"network/web-server.js"},"aA":{"t":"F","e":false,"f":"network/web-server.js"},"WS":{"t":"F","e":true,"f":"network/web-server.js"}},"edges":[],"orphans":["p","u","h","f","y","g","x","v","$","j","S","b","m","w","F","D","M","P","d","i","o","s","analyzeFile","analyzeFilePatterns","analyzePackageJson","a","l","O","R","addTopLevelNewlines","compactFile","beautifyFile","c","E","fetchReference","listAvailable","sanitize","e","extractImports","getBody","extractCalls","t","n","extractParams","B","cleanup","k","N","z","A"],"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-migrate.js","compact/compact.js","compact/compress.js","compact/ctx-resolver.js","compact/ctx-to-jsdoc.js","compact/doc-dialect.js","compact/expand.js","compact/framework-references.js","compact/instructions.js","compact/jsdoc-builder.js","compact/mode-config.js","compact/split-declarations.js","compact/validate-pipeline.js","core/event-bus.js","core/file-walker.js","core/filters.js","core/graph-builder.js","core/parser.js","core/utils.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"]}}
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":1776021880954.865,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/complexity.js":1776020743745.4976,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/custom-rules.js":1776020743746.4695,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/db-analysis.js":1776020743747.036,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/dead-code.js":1776020743747.205,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/full-analysis.js":1776020743747.434,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/jsdoc-checker.js":1776020743747.778,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/jsdoc-generator.js":1776020743748.1309,"/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":1776020743748.3684,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/test-annotations.js":1776021880973.3127,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/type-checker.js":1776020743790.671,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/analysis/undocumented.js":1776020743791.3872,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/cli/cli-handlers.js":1776020743791.534,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/cli/cli.js":1776009281031.448,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ai-context.js":1776090090709.129,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compact-migrate.js":1776025662126.3894,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compact.js":1776025662121.6743,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/compress.js":1776077544228.2026,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ctx-resolver.js":1776024988359.2908,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/ctx-to-jsdoc.js":1776025604473.7043,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/doc-dialect.js":1776021881027.7375,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/expand.js":1776025662125.8457,"/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":1776089868557.5547,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/jsdoc-builder.js":1776024792784.1958,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/mode-config.js":1776021881032.5818,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/split-declarations.js":1776020731393.9968,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/compact/validate-pipeline.js":1776025626422.5903,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/event-bus.js":1776021881034.3083,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/file-walker.js":1776024739329.9265,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/filters.js":1776021881038.4585,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/graph-builder.js":1776020731524.8525,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/parser.js":1776021881058.1765,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/core/utils.js":1776024855849.509,"/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":1776021881068.4143,"/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":1776087281350.1672,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/tool-defs.js":1776089667319.6912,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/mcp/tools.js":1776021881078.6213,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/backend-lifecycle.js":1776087982767.4238,"/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":1776081624832.668,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/mdns.js":1776020743913.2654,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/server.js":1776088001724.466,"/Users/v.matiyasevich/Documents/GitHub/project-graph-mcp/src/network/web-server.js":1776087374451.2217},"graph":{"v":1,"legend":{"computeContentHash":"CH","getCachePath":"CP","readCache":"rC","writeCache":"wC","isCacheValid":"CV","p":"p","u":"u","h":"h","analyzeComplexityFile":"CF","f":"f","getComplexity":"gC","y":"y","g":"g","x":"x","v":"v","$":"$","j":"j","S":"sS","b":"b","getCustomRules":"CR","setCustomRule":"CR1","deleteCustomRule":"CR2","detectProjectRuleSets":"PRS","checkCustomRules":"CR3","getDBSchema":"DBS","getTableUsage":"TU","getDBDeadTables":"DBD","m":"m","getDeadCode":"DC","w":"w","F":"fF","D":"dD","M":"mM","P":"pP","getFullAnalysis":"FA","getAnalysisSummaryOnly":"ASO","d":"d","checkJSDocFile":"JSD","checkJSDocConsistency":"JSD1","generateJSDoc":"JSD2","i":"i","o":"o","s":"s","generateJSDocFor":"JSD3","findJSFiles":"JSF","analyzeFile":"aF","getLargeFiles":"LF","analyzeFilePatterns":"FP","analyzePackageJson":"PJ","getOutdatedPatterns":"OP","getSimilarFunctions":"SF","a":"a","parseAnnotations":"pA","getAllFeatures":"AF","getPendingTests":"PT","markTestPassed":"TP","markTestFailed":"TF","getTestSummary":"TS","resetTestState":"TS1","l":"l","checkTypes":"cT","checkUndocumentedFile":"UF","getUndocumented":"gU","getUndocumentedSummary":"US","O":"oO","R":"rR","printHelp":"pH","runCLI":"CLI","_loadIgnore":"_I","_parseIgnore":"_I1","_shouldIgnore":"_I2","getAiContext":"AC","compactMigrate":"cM","addTopLevelNewlines":"TLN","compactFile":"cF","beautifyFile":"bF","compactProject":"cP","expandProject":"eP","c":"c","compressFile":"cF1","editCompressed":"eC","resolveCtxPath":"CP1","resolveCtxRelPath":"CRP","readCtxFile":"CF1","parseCtxFile":"CF2","injectJSDoc":"JSD4","stripJSDoc":"JSD5","validateCtxContracts":"CC","generateDocDialect":"DD","readContextDocs":"CD","getProjectDocs":"PD","E":"eE","checkStaleness":"cS","generateContextFiles":"CF3","expandFile":"eF","fetchReference":"fR","listAvailable":"lA","getFrameworkReference":"FR","getInstructions":"gI","parseCtxParams":"CP2","sanitize":"sa","buildJSDocBlock":"JSD6","buildJSDocFromRaw":"JSD7","getConfig":"gC1","setConfig":"sC","getModeDescription":"MD","getModeWorkflow":"MW","splitDeclarations":"sD","isSingleLineBlob":"SLB","validatePipeline":"vP","emitToolCall":"TC","emitToolResult":"TR","onToolCall":"TC1","onToolResult":"TR1","removeToolListener":"TL","walkJSFiles":"JSF1","getFilters":"gF","setFilters":"sF","addExcludes":"aE","removeExcludes":"rE","resetFilters":"rF","parseGitignore":"pG","shouldExcludeDir":"ED","shouldExcludeFile":"EF","minifyLegend":"mL","e":"e","buildGraph":"bG","createSkeleton":"cS1","parseFile":"pF","discoverSubProjects":"SP","parseProject":"pP1","findAllProjectFiles":"APF","estimateTokens":"eT","setRoots":"sR","getWorkspaceRoot":"WR","resolvePath":"rP","parseGo":"pG1","extractImports":"eI","getBody":"gB","extractCalls":"eC1","parsePython":"pP2","isSQLString":"SQL","t":"t","extractSQLFromString":"SQL1","parseSQL":"SQL2","n":"n","extractSQLFromCode":"SQL3","extractORMFromCode":"ORM","parseTypeScript":"TS2","extractParams":"eP1","stripStringsAndComments":"SAC","createServer":"cS2","startStdioServer":"SS","getGraph":"gG","getSkeleton":"gS","getFocusZone":"FZ","expand":"ex","deps":"de","usages":"us","getCallChain":"CC1","invalidateCache":"iC","_getVersion":"_V","B":"bB","writePortFile":"PF","removePortFile":"PF1","listBackends":"lB","ensureBackend":"eB","startStdioProxy":"SP1","cleanup":"cl","registerService":"rS","getGatewayPort":"GP","registerLocal":"rL","_rv":"_r","k":"k","_clearCache":"_C","N":"nN","z":"z","A":"aA","startWebServer":"WS"},"reverseLegend":{"CH":"computeContentHash","CP":"getCachePath","rC":"readCache","wC":"writeCache","CV":"isCacheValid","p":"p","u":"u","h":"h","CF":"analyzeComplexityFile","f":"f","gC":"getComplexity","y":"y","g":"g","x":"x","v":"v","$":"$","j":"j","sS":"S","b":"b","CR":"getCustomRules","CR1":"setCustomRule","CR2":"deleteCustomRule","PRS":"detectProjectRuleSets","CR3":"checkCustomRules","DBS":"getDBSchema","TU":"getTableUsage","DBD":"getDBDeadTables","m":"m","DC":"getDeadCode","w":"w","fF":"F","dD":"D","mM":"M","pP":"P","FA":"getFullAnalysis","ASO":"getAnalysisSummaryOnly","d":"d","JSD":"checkJSDocFile","JSD1":"checkJSDocConsistency","JSD2":"generateJSDoc","i":"i","o":"o","s":"s","JSD3":"generateJSDocFor","JSF":"findJSFiles","aF":"analyzeFile","LF":"getLargeFiles","FP":"analyzeFilePatterns","PJ":"analyzePackageJson","OP":"getOutdatedPatterns","SF":"getSimilarFunctions","a":"a","pA":"parseAnnotations","AF":"getAllFeatures","PT":"getPendingTests","TP":"markTestPassed","TF":"markTestFailed","TS":"getTestSummary","TS1":"resetTestState","l":"l","cT":"checkTypes","UF":"checkUndocumentedFile","gU":"getUndocumented","US":"getUndocumentedSummary","oO":"O","rR":"R","pH":"printHelp","CLI":"runCLI","_I":"_loadIgnore","_I1":"_parseIgnore","_I2":"_shouldIgnore","AC":"getAiContext","cM":"compactMigrate","TLN":"addTopLevelNewlines","cF":"compactFile","bF":"beautifyFile","cP":"compactProject","eP":"expandProject","c":"c","cF1":"compressFile","eC":"editCompressed","CP1":"resolveCtxPath","CRP":"resolveCtxRelPath","CF1":"readCtxFile","CF2":"parseCtxFile","JSD4":"injectJSDoc","JSD5":"stripJSDoc","CC":"validateCtxContracts","DD":"generateDocDialect","CD":"readContextDocs","PD":"getProjectDocs","eE":"E","cS":"checkStaleness","CF3":"generateContextFiles","eF":"expandFile","fR":"fetchReference","lA":"listAvailable","FR":"getFrameworkReference","gI":"getInstructions","CP2":"parseCtxParams","sa":"sanitize","JSD6":"buildJSDocBlock","JSD7":"buildJSDocFromRaw","gC1":"getConfig","sC":"setConfig","MD":"getModeDescription","MW":"getModeWorkflow","sD":"splitDeclarations","SLB":"isSingleLineBlob","vP":"validatePipeline","TC":"emitToolCall","TR":"emitToolResult","TC1":"onToolCall","TR1":"onToolResult","TL":"removeToolListener","JSF1":"walkJSFiles","gF":"getFilters","sF":"setFilters","aE":"addExcludes","rE":"removeExcludes","rF":"resetFilters","pG":"parseGitignore","ED":"shouldExcludeDir","EF":"shouldExcludeFile","mL":"minifyLegend","e":"e","bG":"buildGraph","cS1":"createSkeleton","pF":"parseFile","SP":"discoverSubProjects","pP1":"parseProject","APF":"findAllProjectFiles","eT":"estimateTokens","sR":"setRoots","WR":"getWorkspaceRoot","rP":"resolvePath","pG1":"parseGo","eI":"extractImports","gB":"getBody","eC1":"extractCalls","pP2":"parsePython","SQL":"isSQLString","t":"t","SQL1":"extractSQLFromString","SQL2":"parseSQL","n":"n","SQL3":"extractSQLFromCode","ORM":"extractORMFromCode","TS2":"parseTypeScript","eP1":"extractParams","SAC":"stripStringsAndComments","cS2":"createServer","SS":"startStdioServer","gG":"getGraph","gS":"getSkeleton","FZ":"getFocusZone","ex":"expand","de":"deps","us":"usages","CC1":"getCallChain","iC":"invalidateCache","_V":"_getVersion","bB":"B","PF":"writePortFile","PF1":"removePortFile","lB":"listBackends","eB":"ensureBackend","SP1":"startStdioProxy","cl":"cleanup","rS":"registerService","GP":"getGatewayPort","rL":"registerLocal","_r":"_rv","k":"k","_C":"_clearCache","nN":"N","z":"z","aA":"A","WS":"startWebServer"},"stats":{"files":51,"classes":0,"functions":246,"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"},"p":{"t":"F","e":false,"f":"network/local-gateway.js"},"u":{"t":"F","e":false,"f":"network/web-server.js"},"h":{"t":"F","e":false,"f":"network/local-gateway.js"},"CF":{"t":"F","e":true,"f":"analysis/complexity.js"},"f":{"t":"F","e":false,"f":"network/local-gateway.js"},"gC":{"t":"F","e":true,"f":"analysis/complexity.js"},"y":{"t":"F","e":false,"f":"network/web-server.js"},"g":{"t":"F","e":false,"f":"network/web-server.js"},"x":{"t":"F","e":false,"f":"network/web-server.js"},"v":{"t":"F","e":false,"f":"core/parser.js"},"$":{"t":"F","e":false,"f":"analysis/custom-rules.js"},"j":{"t":"F","e":false,"f":"core/parser.js"},"sS":{"t":"F","e":false,"f":"network/web-server.js"},"b":{"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"},"m":{"t":"F","e":false,"f":"analysis/similar-functions.js"},"DC":{"t":"F","e":true,"f":"analysis/dead-code.js"},"w":{"t":"F","e":false,"f":"network/web-server.js"},"fF":{"t":"F","e":false,"f":"compact/ctx-to-jsdoc.js"},"dD":{"t":"F","e":false,"f":"compact/validate-pipeline.js"},"mM":{"t":"F","e":false,"f":"analysis/full-analysis.js"},"pP":{"t":"F","e":false,"f":"network/web-server.js"},"FA":{"t":"F","e":true,"f":"analysis/full-analysis.js"},"ASO":{"t":"F","e":true,"f":"analysis/full-analysis.js"},"d":{"t":"F","e":false,"f":"network/local-gateway.js"},"JSD":{"t":"F","e":true,"f":"analysis/jsdoc-checker.js"},"JSD1":{"t":"F","e":true,"f":"analysis/jsdoc-checker.js"},"JSD2":{"t":"F","e":true,"f":"analysis/jsdoc-generator.js"},"i":{"t":"F","e":false,"f":"analysis/type-checker.js"},"o":{"t":"F","e":false,"f":"network/mdns.js"},"s":{"t":"F","e":false,"f":"analysis/jsdoc-generator.js"},"JSD3":{"t":"F","e":true,"f":"analysis/jsdoc-generator.js"},"JSF":{"t":"F","e":true,"f":"core/parser.js"},"aF":{"t":"F","e":false,"f":"analysis/large-files.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"},"SF":{"t":"F","e":true,"f":"analysis/similar-functions.js"},"a":{"t":"F","e":false,"f":"analysis/test-annotations.js"},"pA":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"AF":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"PT":{"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":true,"f":"analysis/test-annotations.js"},"TS1":{"t":"F","e":true,"f":"analysis/test-annotations.js"},"l":{"t":"F","e":false,"f":"network/local-gateway.js"},"cT":{"t":"F","e":true,"f":"analysis/type-checker.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"},"oO":{"t":"F","e":false,"f":"network/web-server.js"},"rR":{"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"},"_I":{"t":"F","e":false,"f":"compact/ai-context.js"},"_I1":{"t":"F","e":false,"f":"compact/ai-context.js"},"_I2":{"t":"F","e":false,"f":"compact/ai-context.js"},"AC":{"t":"F","e":true,"f":"compact/ai-context.js"},"cM":{"t":"F","e":true,"f":"compact/compact-migrate.js"},"TLN":{"t":"F","e":false,"f":"compact/compact.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"},"c":{"t":"F","e":false,"f":"network/mdns.js"},"cF1":{"t":"F","e":true,"f":"compact/compress.js"},"eC":{"t":"F","e":true,"f":"compact/compress.js"},"CP1":{"t":"F","e":true,"f":"compact/ctx-resolver.js"},"CRP":{"t":"F","e":true,"f":"compact/ctx-resolver.js"},"CF1":{"t":"F","e":true,"f":"compact/ctx-resolver.js"},"CF2":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"JSD4":{"t":"F","e":true,"f":"compact/ctx-to-jsdoc.js"},"JSD5":{"t":"F","e":true,"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"},"CD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"PD":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"eE":{"t":"F","e":false,"f":"core/parser.js"},"cS":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"CF3":{"t":"F","e":true,"f":"compact/doc-dialect.js"},"eF":{"t":"F","e":true,"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"},"CP2":{"t":"F","e":true,"f":"compact/jsdoc-builder.js"},"sa":{"t":"F","e":false,"f":"compact/jsdoc-builder.js"},"JSD6":{"t":"F","e":true,"f":"compact/jsdoc-builder.js"},"JSD7":{"t":"F","e":true,"f":"compact/jsdoc-builder.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"},"sD":{"t":"F","e":true,"f":"compact/split-declarations.js"},"SLB":{"t":"F","e":true,"f":"compact/split-declarations.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"},"JSF1":{"t":"F","e":true,"f":"core/file-walker.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"},"pG":{"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"},"mL":{"t":"F","e":true,"f":"core/graph-builder.js"},"e":{"t":"F","e":false,"f":"core/graph-builder.js"},"bG":{"t":"F","e":true,"f":"core/graph-builder.js"},"cS1":{"t":"F","e":true,"f":"core/graph-builder.js"},"pF":{"t":"F","e":true,"f":"core/parser.js"},"SP":{"t":"F","e":true,"f":"core/parser.js"},"pP1":{"t":"F","e":true,"f":"core/parser.js"},"APF":{"t":"F","e":true,"f":"core/parser.js"},"eT":{"t":"F","e":true,"f":"core/utils.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"},"pG1":{"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"},"eC1":{"t":"F","e":false,"f":"lang/lang-go.js"},"pP2":{"t":"F","e":true,"f":"lang/lang-python.js"},"SQL":{"t":"F","e":true,"f":"lang/lang-sql.js"},"t":{"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"},"n":{"t":"F","e":false,"f":"network/mdns.js"},"SQL3":{"t":"F","e":true,"f":"lang/lang-sql.js"},"ORM":{"t":"F","e":true,"f":"lang/lang-sql.js"},"TS2":{"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"},"cS2":{"t":"F","e":true,"f":"mcp/mcp-server.js"},"SS":{"t":"F","e":true,"f":"mcp/mcp-server.js"},"gG":{"t":"F","e":true,"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"},"CC1":{"t":"F","e":true,"f":"mcp/tools.js"},"iC":{"t":"F","e":true,"f":"mcp/tools.js"},"_V":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"bB":{"t":"F","e":false,"f":"network/backend-lifecycle.js"},"PF":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"PF1":{"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"},"SP1":{"t":"F","e":true,"f":"network/backend-lifecycle.js"},"cl":{"t":"F","e":false,"f":"network/backend.js"},"rS":{"t":"F","e":true,"f":"network/local-gateway.js"},"GP":{"t":"F","e":true,"f":"network/local-gateway.js"},"rL":{"t":"F","e":true,"f":"network/mdns.js"},"_r":{"t":"F","e":false,"f":"network/web-server.js"},"k":{"t":"F","e":false,"f":"network/web-server.js"},"_C":{"t":"F","e":false,"f":"network/web-server.js"},"nN":{"t":"F","e":false,"f":"network/web-server.js"},"z":{"t":"F","e":false,"f":"network/web-server.js"},"aA":{"t":"F","e":false,"f":"network/web-server.js"},"WS":{"t":"F","e":true,"f":"network/web-server.js"}},"edges":[],"orphans":["p","u","h","f","y","g","x","v","$","j","S","b","m","w","F","D","M","P","d","i","o","s","analyzeFile","analyzeFilePatterns","analyzePackageJson","a","l","O","R","_loadIgnore","_parseIgnore","_shouldIgnore","addTopLevelNewlines","compactFile","beautifyFile","c","E","fetchReference","listAvailable","sanitize","e","extractImports","getBody","extractCalls","t","n","extractParams","_getVersion","B","cleanup","_rv","k","_clearCache","N","z","A"],"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-migrate.js","compact/compact.js","compact/compress.js","compact/ctx-resolver.js","compact/ctx-to-jsdoc.js","compact/doc-dialect.js","compact/expand.js","compact/framework-references.js","compact/instructions.js","compact/jsdoc-builder.js","compact/mode-config.js","compact/split-declarations.js","compact/validate-pipeline.js","core/event-bus.js","core/file-walker.js","core/filters.js","core/graph-builder.js","core/parser.js","core/utils.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"]}}
@@ -1,7 +1,34 @@
1
1
  // @ctx .context/src/compact/ai-context.ctx
2
- import{estimateTokens}from"../core/utils.js";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";
2
+ import{estimateTokens}from"../core/utils.js";import{resolve as e,extname as t,relative as _rel,join as _join,basename as _base}from"path";import{getSkeleton as s,getGraph as o}from"../mcp/tools.js";import{getProjectDocs as n}from"./doc-dialect.js";import{compressFile as i}from"./compress.js";import{findJSFiles as r}from"../core/parser.js";import{readFileSync as _rf,existsSync as _ex,writeFileSync as _wf}from"fs";
3
3
  const c=new Set([".js",".mjs",".ts",".tsx"]);
4
- 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}
4
+ const IGNORE_FILE=".contextignore";
5
+ const DEFAULT_IGNORE=`# Files and directories excluded from AI context (get_ai_context includeFiles: ["*"])
6
+ # Glob patterns one per line
7
+
8
+ # Vendored / bundled libs
9
+ vendor/
10
+ *.min.js
11
+ *.bundle.js
12
+ chart.js
13
+ d3.js
14
+ three.js
15
+ lodash.js
16
+ jquery*.js
17
+
18
+ # Generated
19
+ dist/
20
+ build/
21
+ coverage/
22
+ *.d.ts
23
+
24
+ # Tests
25
+ *.test.js
26
+ *.spec.js
27
+ `;
28
+ function _loadIgnore(p){const f=_join(p,IGNORE_FILE);if(!_ex(f)){try{_wf(f,DEFAULT_IGNORE)}catch{}return _parseIgnore(DEFAULT_IGNORE)}return _parseIgnore(_rf(f,"utf-8"))}
29
+ function _parseIgnore(s){return s.split("\n").map(l=>l.trim()).filter(l=>l&&!l.startsWith("#"))}
30
+ function _shouldIgnore(relPath,patterns){for(const p of patterns){if(p.endsWith("/")&&relPath.startsWith(p.slice(0,-1)))return true;if(p.endsWith("/")&&relPath.includes("/"+p.slice(0,-1)))return true;const re=new RegExp("^"+p.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/\?/g,".")+"$");if(re.test(_base(relPath))||re.test(relPath))return true}return false}
31
+ export async function getAiContext(a,l={}){if(!a)return{error:"path is required",hint:'Usage: get_ai_context({ path: ".", includeFiles: ["*"] })',modes:{overview:'get_ai_context({ path: "." }) → skeleton + docs (~2-3k tokens)',code:'get_ai_context({ path: ".", includeFiles: ["*"] }) → all source files',full:'get_ai_context({ path: ".", includeFiles: ["*"], includeSkeleton: true, includeDocs: true }) → everything'}};const p=e(a);if(!_ex(p))return{error:`Path not found: ${p}`,hint:"Provide an existing directory path"};const f=l.includeFiles||[];if(typeof f==="string")return{error:"includeFiles must be an array, got string",hint:`Use includeFiles: ["${f}"] or includeFiles: ["*"] for all files`};const u={};const allJS=r(p);const wantAll=f.includes("*");const useSkeleton="includeSkeleton"in l?l.includeSkeleton:!wantAll;const useDocs="includeDocs"in l?l.includeDocs:!wantAll;if(allJS.length===0){u.hint="No JS/TS files found. Check the path — it should point to your source directory.";}
32
+ let g=0;if(useSkeleton&&(u.skeleton=await s(p),g+=estimateTokens(u.skeleton)),useDocs){const e=await o(p);u.docs=n(e,p),g+=estimateTokens(u.docs)}if(wantAll||f.length>0){u.files={};const ignorePatterns=wantAll?_loadIgnore(p):[];
33
+ let targets;if(wantAll){targets=allJS.map(e=>_rel(p,e)).filter(f=>!_shouldIgnore(f,ignorePatterns));u.ignored=allJS.length-targets.length}else{targets=f}for(const s of targets){const jsMatch=allJS.find(e=>e.endsWith(s)||e.endsWith("/"+s));if(jsMatch){const n=t(jsMatch).toLowerCase();if(c.has(n))try{const e=await i(jsMatch,{beautify:!1,legend:!1});u.files[s]=e.code,g+=e.compressed}catch(e){u.files[s]={error:e.message}}else{const txt=_rf(jsMatch,"utf-8");u.files[s]=txt;g+=estimateTokens(txt)}}else{const absPath=_join(p,s);if(_ex(absPath)){try{const txt=_rf(absPath,"utf-8");u.files[s]=txt;g+=estimateTokens(txt)}catch(e){u.files[s]={error:e.message}}}else{u.files[s]={error:`File not found: ${s}`}}}}}
34
+ let k=0;for(const e of allJS)try{k+=estimateTokens(_rf(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}
@@ -1,3 +1,7 @@
1
1
  // @ctx .context/src/compact/instructions.ctx
2
- export const AGENT_INSTRUCTIONS='\n# 🤖 Project Guidelines for AI Agents\n\n## 1. Architecture Standards (Symbiote.js)\n- **Component Structure**: Always use Triple-File Partitioning for components:\n - `MyComponent.js`: Class logic (extends Symbiote)\n - `MyComponent.tpl.js`: HTML template (export template)\n - `MyComponent.css.js`: CSS styles (export rootStyles/shadowStyles)\n- **State Management**: Use `this.init$` for local state and `this.sub()` for reactivity.\n- **Directives**: Use `itemize` for lists, `js-d-kit` for static generation.\n\n## 2. General Coding Rules\n- **ESM Only**: Use `import` / `export`. No `require`.\n- **No Dependencies**: Avoid adding new npm packages unless critical.\n- **Comments**: Write clear JSDoc for all public methods.\n- **Async/Await**: Prefer async/await over promises.\n\n## 3. MCP Tools Usage\n- **Graph**: Use `get_skeleton` first to map the codebase.\n- **Deep Dive**: Use `expand` to read class details.\n- **Tests**: Use `get_pending_tests` to see what needs verification.\n- **Guidelines**: Use `get_agent_instructions` to refresh these rules.\n\n## 4. Custom Rules System\nConfigurable code analysis with auto-detection.\n\n### Available Tools\n- `get_custom_rules`: List all rulesets and their rules\n- `set_custom_rule`: Add or update a rule in a ruleset\n- `check_custom_rules`: Run analysis (auto-detects applicable rulesets)\n\n### Auto-Detection\nRulesets are applied automatically based on:\n1. `package.json` dependencies\n2. Import patterns in source code\n3. Code patterns (e.g., `extends Symbiote`)\n\n### Creating New Rules\nUse `set_custom_rule` to add framework-specific rules:\n```json\n{\n "ruleSet": "my-framework-2x",\n "rule": {\n "id": "my-rule-id",\n "name": "Rule Name",\n "description": "What this rule checks",\n "pattern": "badPattern",\n "patternType": "string",\n "replacement": "Use goodPattern instead",\n "severity": "warning",\n "filePattern": "*.js",\n "docs": "https://docs.example.com/rule"\n }\n}\n```\n\n### Severity Levels\n- `error`: Critical issues that must be fixed\n- `warning`: Important but not blocking\n- `info`: Suggestions and best practices\n';
3
- export function getInstructions(){return AGENT_INSTRUCTIONS}
2
+ import{readFileSync}from"fs";import{join,dirname}from"path";import{fileURLToPath}from"url";
3
+ const __dir=dirname(fileURLToPath(import.meta.url));
4
+ const _mdPath=join(__dir,"..","..","docs","AGENT_INSTRUCTIONS.md");
5
+ let _cached=null;
6
+ export function getInstructions(){if(!_cached)try{_cached=readFileSync(_mdPath,"utf-8")}catch{_cached="# Agent Instructions\n\nFile not found: "+_mdPath}return _cached}
7
+ export const AGENT_INSTRUCTIONS=getInstructions();
@@ -1,3 +1,3 @@
1
1
  // @ctx .context/src/mcp/tool-defs.ctx
2
- const e={get_skeleton:{name:"get_skeleton",description:"Get compact minified project overview (10-50x smaller than source). Returns legend, stats, and node summaries.",inputSchema:{type:"object",properties:{path:{type:"string",description:'Path to scan (e.g., "src/components")'}},required:["path"]}},get_focus_zone:{name:"get_focus_zone",description:"Get enriched context for recently modified files. Auto-detects from git or accepts explicit file list.",inputSchema:{type:"object",properties:{path:{type:"string"},useGitDiff:{type:"boolean",description:"Auto-detect from git diff"},recentFiles:{type:"array",items:{type:"string"},description:"Explicit list of files to expand"}}}},get_ai_context:{name:"get_ai_context",description:"Boot AI agent context: skeleton + doc-dialect + optional compressed files in one call. Call FIRST when starting work on a new project. Returns totalTokens and savings vs reading raw source.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Project root path"},includeFiles:{type:"array",items:{type:"string"},description:'Specific files to include compressed (e.g., ["parser.js", "tools.js"])'},includeDocs:{type:"boolean",description:"Include doc-dialect documentation (default: true)"},includeSkeleton:{type:"boolean",description:"Include project skeleton (default: true)"}},required:["path"]}},invalidate_cache:{name:"invalidate_cache",description:"Invalidate the cached graph. Use after making code changes.",inputSchema:{type:"object",properties:{}}},get_usage_guide:{name:"get_usage_guide",description:"Get the comprehensive usage guide for project-graph with examples and best practices.\nCall this FIRST when planning how to analyze, navigate, or audit a codebase.\nReturns practical examples and recommended workflow for each feature area.\n\nAvailable topics: navigation, analysis, testing, documentation, rules, workflow.\nOmit topic to get the full guide.",inputSchema:{type:"object",properties:{topic:{type:"string",description:"Optional topic filter: navigation, analysis, testing, documentation, rules, workflow"}}}},get_agent_instructions:{name:"get_agent_instructions",description:"Get coding guidelines, architectural standards, and JSDoc rules for this project.",inputSchema:{type:"object",properties:{}}},get_custom_rules:{name:"get_custom_rules",description:"List all custom code analysis rules. Rules are stored in JSON files in rules/ directory.",inputSchema:{type:"object",properties:{}}},set_custom_rule:{name:"set_custom_rule",description:"Add or update a custom code analysis rule. Creates ruleset if it does not exist.",inputSchema:{type:"object",properties:{ruleSet:{type:"string",description:'Name of ruleset (e.g., "symbiote", "react", "custom")'},rule:{type:"object",description:"Rule definition with id, name, description, pattern, patternType, replacement, severity, filePattern"}},required:["ruleSet","rule"]}},check_custom_rules:{name:"check_custom_rules",description:"Run custom rules analysis on a directory. Returns violations found.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Path to scan"},ruleSet:{type:"string",description:"Optional: specific ruleset to use"},severity:{type:"string",description:"Optional: filter by severity (error/warning/info)"}},required:["path"]}},get_framework_reference:{name:"get_framework_reference",description:"Get framework-specific AI reference documentation. Auto-detects framework from project or accepts explicit name. Returns full API reference, patterns, and common mistakes as agent context.",inputSchema:{type:"object",properties:{framework:{type:"string",description:'Framework reference name (e.g., "symbiote-3x"). If omitted, auto-detects from path.'},path:{type:"string",description:'Project path for auto-detection (e.g., "src/")'}}}}};
2
+ const e={get_skeleton:{name:"get_skeleton",description:"Get compact minified project overview (10-50x smaller than source). Returns legend, stats, and node summaries.",inputSchema:{type:"object",properties:{path:{type:"string",description:'Path to scan (e.g., "src/components")'}},required:["path"]}},get_focus_zone:{name:"get_focus_zone",description:"Get enriched context for recently modified files. Auto-detects from git or accepts explicit file list.",inputSchema:{type:"object",properties:{path:{type:"string"},useGitDiff:{type:"boolean",description:"Auto-detect from git diff"},recentFiles:{type:"array",items:{type:"string"},description:"Explicit list of files to expand"}}}},get_ai_context:{name:"get_ai_context",description:"Boot AI agent context. Two modes:\n1. Default: returns skeleton + docs (2-3k tokens) for structure overview.\n2. includeFiles: ['*']: returns ALL compressed source code without skeleton/docs (pure code dump). Filters vendored files via .contextignore (auto-created on first call).\nCall FIRST when starting work. Use ['*'] for small/medium projects that fit in context.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Project root path"},includeFiles:{type:"array",items:{type:"string"},description:'Files to include. Use ["*"] for ALL source files (filtered by .contextignore). Or specific files: ["parser.js", "tools.js"]'},includeDocs:{type:"boolean",description:"Include doc-dialect documentation (default: true, auto-disabled with '*')"},includeSkeleton:{type:"boolean",description:"Include project skeleton (default: true, auto-disabled with '*')"}},required:["path"]}},invalidate_cache:{name:"invalidate_cache",description:"Invalidate the cached graph. Use after making code changes.",inputSchema:{type:"object",properties:{}}},get_usage_guide:{name:"get_usage_guide",description:"Get the comprehensive usage guide for project-graph with examples and best practices.\nCall this FIRST when planning how to analyze, navigate, or audit a codebase.\nReturns practical examples and recommended workflow for each feature area.\n\nAvailable topics: navigation, analysis, testing, documentation, rules, workflow.\nOmit topic to get the full guide.",inputSchema:{type:"object",properties:{topic:{type:"string",description:"Optional topic filter: navigation, analysis, testing, documentation, rules, workflow"}}}},get_agent_instructions:{name:"get_agent_instructions",description:"Get coding guidelines, architectural standards, and JSDoc rules for this project.",inputSchema:{type:"object",properties:{}}},get_custom_rules:{name:"get_custom_rules",description:"List all custom code analysis rules. Rules are stored in JSON files in rules/ directory.",inputSchema:{type:"object",properties:{}}},set_custom_rule:{name:"set_custom_rule",description:"Add or update a custom code analysis rule. Creates ruleset if it does not exist.",inputSchema:{type:"object",properties:{ruleSet:{type:"string",description:'Name of ruleset (e.g., "symbiote", "react", "custom")'},rule:{type:"object",description:"Rule definition with id, name, description, pattern, patternType, replacement, severity, filePattern"}},required:["ruleSet","rule"]}},check_custom_rules:{name:"check_custom_rules",description:"Run custom rules analysis on a directory. Returns violations found.",inputSchema:{type:"object",properties:{path:{type:"string",description:"Path to scan"},ruleSet:{type:"string",description:"Optional: specific ruleset to use"},severity:{type:"string",description:"Optional: filter by severity (error/warning/info)"}},required:["path"]}},get_framework_reference:{name:"get_framework_reference",description:"Get framework-specific AI reference documentation. Auto-detects framework from project or accepts explicit name. Returns full API reference, patterns, and common mistakes as agent context.",inputSchema:{type:"object",properties:{framework:{type:"string",description:'Framework reference name (e.g., "symbiote-3x"). If omitted, auto-detects from path.'},path:{type:"string",description:'Project path for auto-detection (e.g., "src/")'}}}}};
3
3
  export const TOOLS=[e.get_skeleton,e.get_focus_zone,e.get_ai_context,e.invalidate_cache,e.get_usage_guide,e.get_agent_instructions,e.get_custom_rules,e.set_custom_rule,e.check_custom_rules,e.get_framework_reference,{name:"navigate",description:"Navigate the project graph. Actions: expand|deps|usages|call_chain|sub_projects",inputSchema:{type:"object",properties:{action:{type:"string",enum:["expand","deps","usages","call_chain","sub_projects"],description:"Navigation action to perform"},symbol:{type:"string",description:"Symbol name (for expand, deps, usages)"},from:{type:"string",description:"Starting symbol (for call_chain)"},to:{type:"string",description:"Target symbol (for call_chain)"},path:{type:"string",description:"Path to scan (for call_chain, sub_projects)"}},required:["action"]}},{name:"analyze",description:"Code quality analysis. Actions: dead_code|similar_functions|complexity|large_files|outdated_patterns|full_analysis|analysis_summary|undocumented",inputSchema:{type:"object",properties:{action:{type:"string",enum:["dead_code","similar_functions","complexity","large_files","outdated_patterns","full_analysis","analysis_summary","undocumented"],description:"Analysis type to run"},path:{type:"string",description:"Path to scan"},minComplexity:{type:"number",description:"For complexity: minimum threshold (default: 1)"},onlyProblematic:{type:"boolean",description:"For complexity/large_files: only show issues"},threshold:{type:"number",description:"For similar_functions: min similarity % (default: 60)"},includeItems:{type:"boolean",description:"For full_analysis: include individual items"},level:{type:"string",enum:["tests","params","all"],description:"For undocumented: strictness level"},codeOnly:{type:"boolean",description:"For outdated_patterns: only check code"},depsOnly:{type:"boolean",description:"For outdated_patterns: only check deps"}},required:["action","path"]}},{name:"testing",description:"Test checklist management. Actions: pending|pass|fail|summary|reset",inputSchema:{type:"object",properties:{action:{type:"string",enum:["pending","pass","fail","summary","reset"],description:"Test action to perform"},path:{type:"string",description:"Path to scan (for pending, summary)"},testId:{type:"string",description:"Test ID (for pass, fail)"},reason:{type:"string",description:"Failure reason (for fail)"}},required:["action"]}},{name:"filters",description:"Filter configuration. Actions: get|set|add_excludes|remove_excludes|reset",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","set","add_excludes","remove_excludes","reset"],description:"Filter action to perform"},excludeDirs:{type:"array",items:{type:"string"},description:"For set: directories to exclude"},excludePatterns:{type:"array",items:{type:"string"},description:"For set: file patterns to exclude"},useGitignore:{type:"boolean",description:"For set: use .gitignore patterns"},includeHidden:{type:"boolean",description:"For set: include hidden directories"},dirs:{type:"array",items:{type:"string"},description:"For add_excludes/remove_excludes"}},required:["action"]}},{name:"jsdoc",description:"JSDoc operations. Actions: check_consistency|check_types|generate",inputSchema:{type:"object",properties:{action:{type:"string",enum:["check_consistency","check_types","generate"],description:"JSDoc action to perform"},path:{type:"string",description:"Path to scan"},name:{type:"string",description:"For generate: specific function name"},files:{type:"array",items:{type:"string"},description:"For check_types: specific files"},maxDiagnostics:{type:"number",description:"For check_types: max diagnostics (default: 50)"}},required:["action","path"]}},{name:"docs",description:"Documentation (.ctx) management. Actions: get|generate|check_stale|validate_contracts",inputSchema:{type:"object",properties:{action:{type:"string",enum:["get","generate","check_stale","validate_contracts"],description:"Documentation action to perform"},path:{type:"string",description:"Project root path"},file:{type:"string",description:"For get: specific file docs"},overwrite:{type:"boolean",description:"For generate: overwrite existing (merge preserves descriptions)"},scope:{description:'For generate: "all", "focus" (git diff), or array of file paths'},strict:{type:"boolean",description:"For validate_contracts: report functions missing from .ctx"}},required:["action","path"]}},{name:"compact",description:"Compact code operations. Actions: compact_file|edit|compact_all|beautify|expand_file|expand_project|validate_pipeline|get_mode|set_mode",inputSchema:{type:"object",properties:{action:{type:"string",enum:["compact_file","edit","compact_all","beautify","expand_file","expand_project","validate_pipeline","get_mode","set_mode"],description:"Compact action to perform"},path:{type:"string",description:"Path to file or directory"},symbol:{type:"string",description:"For edit: function/class name to replace"},code:{type:"string",description:"For edit: new code for the symbol"},beautify:{type:"boolean",description:"Beautify output (default: true)"},legend:{type:"boolean",description:"For compact_file: include export legend"},dryRun:{type:"boolean",description:"Preview without modifying"},mode:{type:"number",description:"For set_mode: 1 (compact, recommended) or 2 (full)"},autoValidate:{type:"boolean",description:"For set_mode: auto-validate after edits"},stripJSDoc:{type:"boolean",description:"For set_mode: strip JSDoc when compacting"},strict:{type:"boolean",description:"For validate_pipeline: report fns missing from .ctx"},fix:{type:"boolean",description:"For validate_pipeline: auto-fix all style issues — generates .ctx documentation from readable code, then minifies (headers, imports, indentation, long names). Bidirectional: expand restores from .ctx."}},required:["action"]}},{name:"db",description:"Database analysis. Actions: schema|table_usage|dead_tables",inputSchema:{type:"object",properties:{action:{type:"string",enum:["schema","table_usage","dead_tables"],description:"Database analysis action"},path:{type:"string",description:"Path to scan"},table:{type:"string",description:"For table_usage: filter to specific table"}},required:["action","path"]}}];
@@ -13,5 +13,6 @@ function B(e){const t=y(e);if(!r(t))return null;try{const e=JSON.parse(o(t,"utf8
13
13
  export function writePortFile(e,t){n(g,{recursive:!0});const r=l(e),i={port:t,pid:process.pid,project:r,name:f(r)||"root",version:_getVersion(),startedAt:Date.now()};c(y(e),JSON.stringify(i,null,2))}
14
14
  export function removePortFile(e){try{s(y(e))}catch{}}
15
15
  export function listBackends(){if(!r(g))return[];const e=i(g).filter(e=>e.endsWith(".json")),t=[];for(const r of e)try{const e=JSON.parse(o(a(g,r),"utf8"));try{process.kill(e.pid,0),t.push(e)}catch{try{s(a(g,r))}catch{}}}catch{}return t}
16
- export async function ensureBackend(e,{force:f}={}){const t=l(e),n=B(t);if(n){const cv=_getVersion();if(f||n.version&&n.version!==cv){if(n.version!==cv)console.error(`[project-graph] Version mismatch: running ${n.version}, installed ${cv}`);console.error("[project-graph] Restarting backend...");try{process.kill(n.pid,"SIGTERM")}catch{}try{s(y(t))}catch{}await new Promise(r=>setTimeout(r,500))}else{return n.port}}const o=a(m,"backend.js");u(process.execPath,[o,t],{detached:!0,stdio:"ignore",env:{...process.env,PROJECT_GRAPH_BACKEND:"1"}}).unref();const c=y(t),_t=Date.now();for(;Date.now()-_t<1e4;)if(await new Promise(e=>setTimeout(e,200)),r(c)){const e=B(t);if(e)return e.port}throw new Error("Backend failed to start within 10s")}
16
+ function _srcChanged(startedAt){try{const dir=a(m,"..","..","src");const{statSync:st,readdirSync:rd}=require("fs");const files=rd(dir,{recursive:true});for(const f of files){try{const{mtimeMs}=st(a(dir,String(f)));if(mtimeMs>startedAt)return true}catch{}}return false}catch{return false}}
17
+ export async function ensureBackend(e,{force:f}={}){const t=l(e),n=B(t);if(n){const cv=_getVersion();const needRestart=f||(n.version&&n.version!==cv)||_srcChanged(n.startedAt);if(needRestart){if(n.version!==cv)console.error(`[project-graph] Version mismatch: running ${n.version}, installed ${cv}`);else if(_srcChanged(n.startedAt))console.error("[project-graph] Source files changed, restarting...");console.error("[project-graph] Restarting backend...");try{process.kill(n.pid,"SIGTERM")}catch{}try{s(y(t))}catch{}await new Promise(r=>setTimeout(r,500))}else{return n.port}}const o=a(m,"backend.js");u(process.execPath,[o,t],{detached:!0,stdio:"ignore",env:{...process.env,PROJECT_GRAPH_BACKEND:"1"}}).unref();const c=y(t),_t=Date.now();for(;Date.now()-_t<1e4;)if(await new Promise(e=>setTimeout(e,200)),r(c)){const e=B(t);if(e)return e.port}throw new Error("Backend failed to start within 10s")}
17
18
  export function startStdioProxy(e,r=[]){const n=t(16).toString("base64"),o=d({host:"127.0.0.1",port:e},()=>{o.write(`GET /mcp-ws HTTP/1.1\r\nHost: 127.0.0.1:${e}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: ${n}\r\nSec-WebSocket-Version: 13\r\n\r\n`)});let c=!1,s=Buffer.alloc(0),i=[...r];const a=p({input:process.stdin,terminal:!1});function l(e){const r=Buffer.from(e,"utf8"),n=t(4),o=Buffer.alloc(r.length);for(let e=0;e<r.length;e++)o[e]=r[e]^n[e%4];let c;return r.length<126?(c=Buffer.alloc(2),c[0]=129,c[1]=128|r.length):r.length<65536?(c=Buffer.alloc(4),c[0]=129,c[1]=254,c.writeUInt16BE(r.length,2)):(c=Buffer.alloc(10),c[0]=129,c[1]=255,c.writeBigUInt64BE(BigInt(r.length),2)),Buffer.concat([c,n,o])}function f(e){if(e.length<2)return null;const t=15&e[0];let r=127&e[1],n=2;if(126===r){if(e.length<4)return null;r=e.readUInt16BE(2),n=4}else if(127===r){if(e.length<10)return null;r=Number(e.readBigUInt64BE(2)),n=10}return e.length<n+r?null:{opcode:t,data:e.slice(n,n+r).toString("utf8"),totalLen:n+r}}a.on("line",e=>{if(c)try{o.write(l(e))}catch{}else i.push(e)}),a.on("close",()=>{o.end(),process.exit(0)}),o.on("data",e=>{if(c)s=Buffer.concat([s,e]);else{const t=Buffer.concat([s,e]),r=t.indexOf("\r\n\r\n");if(-1===r)return void(s=t);t.slice(0,r).toString().includes("101")||(console.error("[project-graph] WebSocket handshake failed"),process.exit(1)),c=!0,s=t.slice(r+4);for(const e of i)try{o.write(l(e))}catch{}i=[]}for(;s.length>=2;){const e=f(s);if(!e)break;if(s=s.slice(e.totalLen),1===e.opcode)process.stdout.write(e.data+"\n");else if(8===e.opcode)process.exit(0);else if(9===e.opcode){const e=Buffer.alloc(2);e[0]=138,e[1]=0,o.write(e)}}}),o.on("close",()=>process.exit(0)),o.on("error",e=>{console.error(`[project-graph] Proxy connection error: ${e.message}`),process.exit(1)})}
@@ -4,8 +4,10 @@ const s=o.join(process.env.HOME||process.env.USERPROFILE||"/tmp",".local-gateway
4
4
  function p(){try{return JSON.parse(r.readFileSync(c,"utf8"))}catch{return{}}}
5
5
  function l(t){r.mkdirSync(s,{recursive:!0}),r.writeFileSync(c,JSON.stringify(t,null,2))}
6
6
  function d(){if(!r.existsSync(a))return;const t=r.readdirSync(a).filter(t=>t.endsWith(".json"));let e=!1;const n=p();for(const s of t)try{const t=JSON.parse(r.readFileSync(o.join(a,s),"utf8"));try{process.kill(t.pid,0)}catch{r.unlinkSync(o.join(a,s));continue}const c=t.name||"root",i=`/${c}`;n["project-graph.local"]=n["project-graph.local"]||{name:"project-graph",routes:{}},n["project-graph.local"].routes[i]||(n["project-graph.local"].routes[i]={port:t.port,pid:t.pid,projectPath:t.project,projectName:c},e=!0)}catch{}e&&l(n)}
7
- export function registerService(t,e,r={}){const o=`${t}.local`,s=p();if(r.projectName){s[o]||(s[o]={name:t,routes:{}});const n=`/${r.projectName}`;s[o].routes=s[o].routes||{},s[o].routes[n]={port:e,pid:process.pid,projectPath:r.projectPath,projectName:r.projectName}}else s[o]={port:e,pid:process.pid,name:t};l(s);const c=n(o,80);f();const i=()=>{c.cleanup()};process.on("exit",i),process.on("SIGINT",()=>{i(),process.exit()}),process.on("SIGTERM",()=>{i(),process.exit()});const a=getGatewayPort(),d=80===a?"":`:${a}`,u=r.projectName?`http://${o}${d}/${r.projectName}/`:`http://${o}${d}/`;return{cleanup:i,url:u,directUrl:`http://localhost:${e}/`}}
8
- function u(t,e,r){const o=r[t];if(!o)return null;if(o.routes){const t=Object.keys(o.routes).sort((t,e)=>e.length-t.length);for(const r of t)if(e===r||e.startsWith(r+"/")){const t=o.routes[r];try{process.kill(t.pid,0)}catch{continue}const n=e.slice(r.length)||"/";return{port:t.port,rewritePath:n,prefix:r}}for(const r of t)try{const t=o.routes[r];process.kill(t.pid,0);const n="/"===e||""===e?"/dashboard.html":e;return{port:t.port,rewritePath:n}}catch{continue}}if(o.port){const t="/"===e||""===e?"/dashboard.html":e;return{port:o.port,rewritePath:t}}return null}
7
+ export function registerService(t,e,r={}){const o=`${t}.local`,s=p();if(r.projectName){s[o]||(s[o]={name:t,routes:{}});const n=`/${r.projectName}`;s[o].routes=s[o].routes||{},s[o].routes[n]={port:e,pid:process.pid,projectPath:r.projectPath,projectName:r.projectName}}else s[o]={port:e,pid:process.pid,name:t};l(s);f();const gwPort=getGatewayPort();const c=n(o,gwPort);const i=()=>{c.cleanup()};process.on("exit",i),process.on("SIGINT",()=>{i(),process.exit()}),process.on("SIGTERM",()=>{i(),process.exit()});const d=80===gwPort?"":`:${gwPort}`,u=r.projectName?`http://${o}${d}/${r.projectName}/`:`http://${o}${d}/`;return{cleanup:i,url:u,directUrl:`http://localhost:${e}/`}}
8
+ function u(t,e,r){const o=r[t];if(!o)return null;if(o.routes){const t=Object.keys(o.routes).sort((t,e)=>e.length-t.length);for(const r of t)if(e===r||e.startsWith(r+"/")){const t=o.routes[r];try{process.kill(t.pid,0)}catch{if(t.projectPath){_autoRestart(t.projectPath);return{port:t.port,rewritePath:"/restarting",restarting:true,prefix:r}}continue}const n=e.slice(r.length)||"/";return{port:t.port,rewritePath:n,prefix:r}}for(const r of t)try{const t=o.routes[r];process.kill(t.pid,0);const n="/"===e||""===e?"/dashboard.html":e;return{port:t.port,rewritePath:n}}catch{continue}}if(o.port){const t="/"===e||""===e?"/dashboard.html":e;return{port:o.port,rewritePath:t}}return null}
9
+ const _restarts=new Map();function _autoRestart(projectPath){const now=Date.now();if(_restarts.has(projectPath)&&now-_restarts.get(projectPath)<15e3)return;_restarts.set(projectPath,now);console.error(`[gateway] Auto-restarting backend for ${projectPath}`);try{const{spawn:s}=require("node:child_process");const b=o.join(o.dirname(new URL(import.meta.url).pathname),"backend.js");s(process.execPath,[b,projectPath],{detached:true,stdio:"ignore",env:{...process.env,PROJECT_GRAPH_BACKEND:"1"}}).unref()}catch(e){console.error(`[gateway] Auto-restart failed: ${e.message}`)}}
9
10
  function h(){try{const t=r.readFileSync(i,"utf8");return t.trim().startsWith("{")?JSON.parse(t):{pid:parseInt(t,10),port:80}}catch{return null}}
10
11
  export function getGatewayPort(){const t=h();return t?.port||80}
11
- function f(){if(!function(){const t=h();if(!t)return!1;try{return process.kill(t.pid,0),!0}catch{return!1}}())try{process.on("uncaughtException",t=>{});process.on("unhandledRejection",t=>{});const o=t.createServer((e,r)=>{const o=(e.headers.host||"").split(":")[0];let n=p(),s=u(o,e.url,n);if(!s&&(d(),n=p(),s=u(o,e.url,n),!s))return r.writeHead(404,{"Content-Type":"text/plain"}),void r.end(`Unknown host: ${o}\nRegistered: ${Object.keys(n).join(", ")}`);if("/api/gateway-info"===e.url){const t=JSON.stringify(n["project-graph.local"]||{routes:{}});return r.writeHead(200,{"Content-Type":"application/json"}),void r.end(t)}if("POST"===e.method&&"/api/remove-project"===e.url){let t="";return e.on("data",e=>t+=e),void e.on("end",()=>{try{const e=JSON.parse(t).route,o=p();if(o["project-graph.local"]?.routes?.[e]){const t=o["project-graph.local"].routes[e];try{process.kill(t.pid,9)}catch{}delete o["project-graph.local"].routes[e],l(o)}r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!0}))}catch(t){r.writeHead(400,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:t.message}))}})}if("/api/instances"===e.url){const n2=p(),rt=n2["project-graph.local"]?.routes||{};const list=Object.entries(rt).map(([k,v])=>({name:v.projectName||k,project:v.projectPath||"",pid:v.pid,port:v.port,prefix:k,startedAt:v.startedAt||0}));r.writeHead(200,{"Content-Type":"application/json"});return void r.end(JSON.stringify(list))}if("/api/project-info"===e.url){const n2=p(),rt=n2["project-graph.local"]?.routes||{};r.writeHead(200,{"Content-Type":"application/json"});return void r.end(JSON.stringify({name:"Gateway",path:"project-graph.local",agents:Object.keys(rt).length,pid:process.pid}))}if("/api/server-status"===e.url){const n2=p(),rt=n2["project-graph.local"]?.routes||{};r.writeHead(200,{"Content-Type":"application/json"});return void r.end(JSON.stringify({uptime:Math.round(process.uptime()),agents:0,monitors:Object.keys(rt).length,shutdownAt:null}))}const c=t.request({hostname:"127.0.0.1",port:s.port,path:s.rewritePath,method:e.method,headers:{...e.headers,host:`localhost:${s.port}`}},t=>{if((t.headers["content-type"]||"").includes("text/html")&&s.prefix){const e=[];t.on("data",t=>e.push(t)),t.on("end",()=>{let o=Buffer.concat(e).toString("utf8");const n=`<base href="${s.prefix}/">`;o=o.includes("<head>")?o.replace("<head>",`<head>\n ${n}`):n+"\n"+o;const c=Buffer.from(o,"utf8"),i={...t.headers};i["content-length"]=c.length,delete i["transfer-encoding"];try{r.writeHead(t.statusCode,i),r.end(c)}catch{}})}else{try{r.writeHead(t.statusCode,t.headers)}catch{}t.pipe(r).on("error",()=>{})}});c.on("error",()=>{try{r.writeHead(502,{"Content-Type":"text/plain"}),r.end(`Backend unavailable on port ${s.port}`)}catch{}}),e.pipe(c).on("error",()=>{})});function n(t){o.listen(t,"0.0.0.0",()=>{const t=o.address().port;r.mkdirSync(s,{recursive:!0}),r.writeFileSync(i,JSON.stringify({pid:process.pid,port:t})),d()})}o.on("upgrade",(t,r,o)=>{const n=(t.headers.host||"").split(":")[0],s=p(),c=u(n,t.url,s);if(!c||c.isDashboard)return void r.destroy();const i=e.createConnection({host:"127.0.0.1",port:c.port},()=>{const e=c.rewritePath,n=`${t.method} ${e} HTTP/1.1\r\n`+Object.entries(t.headers).map(([t,e])=>`${t}: ${e}`).join("\r\n")+"\r\n\r\n";i.write(n),o.length&&i.write(o);let s=Buffer.alloc(0);i.on("data",function t(e){s=Buffer.concat([s,e]),-1!==s.indexOf("\r\n\r\n")&&(r.write(s),i.removeListener("data",t),r.pipe(i).on("error",()=>{}),i.pipe(r).on("error",()=>{}))})});i.on("error",()=>{try{r.destroy()}catch{}}),r.on("error",()=>{try{i.destroy()}catch{}})}),o.on("error",t=>{"EACCES"===t.code&&!1===o.listening?n(8080):"EADDRINUSE"===t.code&&o.listening}),n(80)}catch{}}
12
+ const RESTART_HTML='<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="3"><title>Restarting...</title><style>body{background:#1a1a2e;color:#e0e0e0;font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}.box{text-align:center}.spinner{width:40px;height:40px;border:3px solid #333;border-top:3px solid #7c3aed;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 16px}@keyframes spin{to{transform:rotate(360deg)}}</style></head><body><div class="box"><div class="spinner"></div><h2>⬡ Backend restarting...</h2><p>Auto-refreshing in 3 seconds</p></div></body></html>';
13
+ function f(){if(!function(){const t=h();if(!t)return!1;try{return process.kill(t.pid,0),!0}catch{return!1}}())try{process.on("uncaughtException",t=>{});process.on("unhandledRejection",t=>{});const o=t.createServer((e,r)=>{const o=(e.headers.host||"").split(":")[0];let n=p(),s=u(o,e.url,n);if(!s&&(d(),n=p(),s=u(o,e.url,n),!s))return r.writeHead(404,{"Content-Type":"text/plain"}),void r.end(`Unknown host: ${o}\nRegistered: ${Object.keys(n).join(", ")}`);if("/api/gateway-info"===e.url){const t=JSON.stringify(n["project-graph.local"]||{routes:{}});return r.writeHead(200,{"Content-Type":"application/json"}),void r.end(t)}if("POST"===e.method&&"/api/remove-project"===e.url){let t="";return e.on("data",e=>t+=e),void e.on("end",()=>{try{const e=JSON.parse(t).route,o=p();if(o["project-graph.local"]?.routes?.[e]){const t=o["project-graph.local"].routes[e];try{process.kill(t.pid,9)}catch{}delete o["project-graph.local"].routes[e],l(o)}r.writeHead(200,{"Content-Type":"application/json"}),r.end(JSON.stringify({ok:!0}))}catch(t){r.writeHead(400,{"Content-Type":"application/json"}),r.end(JSON.stringify({error:t.message}))}})}if("/api/instances"===e.url){const n2=p(),rt=n2["project-graph.local"]?.routes||{};const list=Object.entries(rt).map(([k,v])=>({name:v.projectName||k,project:v.projectPath||"",pid:v.pid,port:v.port,prefix:k,startedAt:v.startedAt||0}));r.writeHead(200,{"Content-Type":"application/json"});return void r.end(JSON.stringify(list))}if("/api/project-info"===e.url){const n2=p(),rt=n2["project-graph.local"]?.routes||{};r.writeHead(200,{"Content-Type":"application/json"});return void r.end(JSON.stringify({name:"Gateway",path:"project-graph.local",agents:Object.keys(rt).length,pid:process.pid}))}if("/api/server-status"===e.url){const n2=p(),rt=n2["project-graph.local"]?.routes||{};r.writeHead(200,{"Content-Type":"application/json"});return void r.end(JSON.stringify({uptime:Math.round(process.uptime()),agents:0,monitors:Object.keys(rt).length,shutdownAt:null}))}if(s.restarting){r.writeHead(200,{"Content-Type":"text/html"});return void r.end(RESTART_HTML)}const c=t.request({hostname:"127.0.0.1",port:s.port,path:s.rewritePath,method:e.method,headers:{...e.headers,host:`localhost:${s.port}`}},t=>{if((t.headers["content-type"]||"").includes("text/html")&&s.prefix){const e=[];t.on("data",t=>e.push(t)),t.on("end",()=>{let o=Buffer.concat(e).toString("utf8");const n=`<base href="${s.prefix}/">`;o=o.includes("<head>")?o.replace("<head>",`<head>\n ${n}`):n+"\n"+o;const c=Buffer.from(o,"utf8"),i={...t.headers};i["content-length"]=c.length,delete i["transfer-encoding"];try{r.writeHead(t.statusCode,i),r.end(c)}catch{}})}else{try{r.writeHead(t.statusCode,t.headers)}catch{}t.pipe(r).on("error",()=>{})}});c.on("error",()=>{try{r.writeHead(502,{"Content-Type":"text/plain"}),r.end(`Backend unavailable on port ${s.port}`)}catch{}}),e.pipe(c).on("error",()=>{})});function n(t){o.listen(t,"0.0.0.0",()=>{const t=o.address().port;r.mkdirSync(s,{recursive:!0}),r.writeFileSync(i,JSON.stringify({pid:process.pid,port:t})),d()})}o.on("upgrade",(t,r,o)=>{const n=(t.headers.host||"").split(":")[0],s=p(),c=u(n,t.url,s);if(!c||c.isDashboard)return void r.destroy();const i=e.createConnection({host:"127.0.0.1",port:c.port},()=>{const e=c.rewritePath,n=`${t.method} ${e} HTTP/1.1\r\n`+Object.entries(t.headers).map(([t,e])=>`${t}: ${e}`).join("\r\n")+"\r\n\r\n";i.write(n),o.length&&i.write(o);let s=Buffer.alloc(0);i.on("data",function t(e){s=Buffer.concat([s,e]),-1!==s.indexOf("\r\n\r\n")&&(r.write(s),i.removeListener("data",t),r.pipe(i).on("error",()=>{}),i.pipe(r).on("error",()=>{}))})});i.on("error",()=>{try{r.destroy()}catch{}}),r.on("error",()=>{try{i.destroy()}catch{}})}),o.on("error",t=>{"EACCES"===t.code&&!1===o.listening?n(8080):"EADDRINUSE"===t.code&&o.listening}),n(80)}catch{}}
@@ -2,6 +2,6 @@
2
2
  import{spawn as t}from"node:child_process";import e from"node:dgram";
3
3
  const r="224.0.0.251";
4
4
  export function registerLocal(t,e){if("darwin"===process.platform)return n(t,e);if("linux"===process.platform){const e=c(t);if(e)return e}return o(t)}
5
- function n(e,r){const n=t("dns-sd",["-P","Project Graph","_http._tcp","",String(r),e,"127.0.0.1"],{stdio:"ignore",detached:!1});return n.unref(),{method:"Bonjour (dns-sd)",cleanup:()=>{try{n.kill()}catch{}}}}
5
+ function n(e,r){try{const{execSync:x}=require("node:child_process");x('pkill -f "dns-sd.*Project Graph" 2>/dev/null',{stdio:"ignore"})}catch{}const n=t("dns-sd",["-P","Project Graph","_http._tcp","",String(r),e,"127.0.0.1"],{stdio:"ignore",detached:!1});return n.unref(),{method:"Bonjour (dns-sd)",cleanup:()=>{try{n.kill()}catch{}}}}
6
6
  function c(e){try{const r=t("avahi-publish-address",["-R",e,"127.0.0.1"],{stdio:"ignore",detached:!1});let n=!1;return r.on("error",()=>{n=!0}),r.unref(),n?null:{method:"Avahi",cleanup:()=>{try{r.kill()}catch{}}}}catch{return null}}
7
7
  function o(t){const n=t.split("."),c=Buffer.concat([...n.map(t=>{const e=Buffer.alloc(1+t.length);return e[0]=t.length,e.write(t,1,"ascii"),e}),Buffer.from([0])]);let o;try{o=e.createSocket({type:"udp4",reuseAddr:!0})}catch{return{method:"none",cleanup:()=>{}}}o.on("message",t=>{if(t.length<12)return;if(32768&t.readUInt16BE(2))return;if(0===t.readUInt16BE(4))return;if(12+c.length+4>t.length)return;if(0!==t.compare(c,0,c.length,12,12+c.length))return;const e=12+c.length,n=t.readUInt16BE(e),i=32767&t.readUInt16BE(e+2);if(1!==n||1!==i)return;const a=Buffer.alloc(12+c.length+10+4);let l=0;a.writeUInt16BE(0,l),l+=2,a.writeUInt16BE(33792,l),l+=2,a.writeUInt16BE(0,l),l+=2,a.writeUInt16BE(1,l),l+=2,a.writeUInt16BE(0,l),l+=2,a.writeUInt16BE(0,l),l+=2,c.copy(a,l),l+=c.length,a.writeUInt16BE(1,l),l+=2,a.writeUInt16BE(32769,l),l+=2,a.writeUInt32BE(120,l),l+=4,a.writeUInt16BE(4,l),l+=2,a[l++]=127,a[l++]=0,a[l++]=0,a[l++]=1,o.send(a,0,l,5353,r)}),o.on("error",()=>{try{o.close()}catch{}});try{o.bind({port:5353,exclusive:!1},()=>{try{o.addMembership(r),o.setMulticastTTL(255)}catch{try{o.close()}catch{}}})}catch{return{method:"none",cleanup:()=>{}}}return{method:"Node.js mDNS",cleanup:()=>{try{o.close()}catch{}}}}
@@ -5,4 +5,4 @@ function g(e,n){const a=o.normalize(e).replace(/^(\.\.[/\\])+/,""),s=a.match(/^[
5
5
  function u(e){return n.createHash("sha1").update(e+"258EAFA5-E914-47DA-95CA-5AB5ADF35C70").digest("base64")}
6
6
  function y(e){const t=Buffer.from(e,"utf8"),o=t.length;let n;return o<126?(n=Buffer.alloc(2),n[0]=129,n[1]=o):o<65536?(n=Buffer.alloc(4),n[0]=129,n[1]=126,n.writeUInt16BE(o,2)):(n=Buffer.alloc(10),n[0]=129,n[1]=127,n.writeBigUInt64BE(BigInt(o),2)),Buffer.concat([n,t])}
7
7
  function w(e){if(e.length<2)return null;const t=15&e[0],o=!!(128&e[1]);let n=127&e[1],a=2;if(126===n){if(e.length<4)return null;n=e.readUInt16BE(2),a=4}else if(127===n){if(e.length<10)return null;n=Number(e.readBigUInt64BE(2)),a=10}if(o){if(e.length<a+4+n)return null;const o=e.slice(a,a+4);a+=4;const s=e.slice(a,a+n);for(let e=0;e<s.length;e++)s[e]^=o[e%4];return{opcode:t,data:s.toString("utf8"),totalLen:a+n}}return e.length<a+n?null:{opcode:t,data:e.slice(a,a+n).toString("utf8"),totalLen:a+n}}
8
- export function startWebServer(t,a){_setRoots([{uri:"file://"+o.resolve(t)}]);const d=i(()=>{}),p=o.basename(o.resolve(t))||"root";let m=1;const _startedAt=Date.now();const h=o.resolve(t),f=n.createHash("md5").update(h).digest("hex"),v=parseInt(f.slice(0,4),16)%360,j={project:{name:p,path:h,color:`hsl(${v}, 65%, 55%)`,agents:0,pid:process.pid},skeleton:null,events:[]};function x(e,t){const o=JSON.stringify({jsonrpc:"2.0",method:e,params:t});for(const e of T)try{e.send(o)}catch{T.delete(e)}}function S(e,t){const o=e.split(".");let n=j;for(let e=0;e<o.length-1;e++)n=n[o[e]];n[o[o.length-1]]=t,x("patch",{path:e,value:t})}async function k(){if(!j.skeleton)try{j.skeleton=await d.executeTool("get_skeleton",{path:t})}catch(e){console.error("[project-graph] Failed to load skeleton:",e.message)}return j.skeleton}const b=new Map,T=new Set;let C=null;const _cache={cs:null,cst:0,as:null,ast:0,fa:null,fat:0};function _clearCache(){_cache.cs=null;_cache.cst=0;_cache.as=null;_cache.ast=0;_cache.fa=null;_cache.fat=0;j.skeleton=null}function N(){return b.size>0||T.size>0}function O(){C&&(clearTimeout(C),C=null);_shutdownAt=0}let _shutdownAt=0;function P(){N()||(O(),_shutdownAt=Date.now()+9e5,C=setTimeout(()=>{N()||(console.log("[project-graph] No clients for 15 min — shutting down."),process.exit(0))},9e5))}function z(){O(),P()}async function A(e,a,s,i){try{let c;const r=a.get("path")||t;switch(e){case"/api/skeleton":c=await d.executeTool("get_skeleton",{path:r});break;case"/api/file":{const e=a.get("path");if(e){const{resolve:n,basename:a,extname:s}=await import("path"),{readFileSync:i,existsSync:r}=await import("fs"),d=n(t,e),p=a(e,s(e))+".ctx",m=o.resolve(t,".context",o.dirname(e),p),f=await _cf(d,{beautify:false,legend:false}),y=r(m)?Math.ceil(i(m,"utf-8").length/4):0,w=f.compressed+y;c={code:f.code,file:e,codeTok:f.compressed,ctxTok:y,totalTok:w,expanded:f.expanded||f.original,savings:f.savings}}else c={code:"// No file specified",file:""};break}case"/api/compact-file":{const e=a.get("path");if(e){const{resolve:n}=await import("path"),s=n(t,e),i=await _cf(s,{beautify:!1,legend:!1});c={code:i.code,file:e,original:i.original,compressed:i.compressed,savings:i.savings}}else c={code:"// No file specified",file:""};break}case"/api/raw-file":{const e=a.get("path");try{const{readFileSync:o}=await import("fs"),{resolve:n,relative:a}=await import("path");c={content:o(n(t,e),"utf-8"),file:e}}catch(t){c={content:`// Cannot read: ${t.message}`,file:e}}break}case"/api/compression-stats":if(_cache.cs&&Date.now()-_cache.cst<6e4){c=_cache.cs;break}{const{readdirSync:e,statSync:n,readFileSync:a,existsSync:s}=await import("fs"),{join:i,extname:r,basename:d,dirname:p,relative:m}=await import("path"),h=new Set([".js",".mjs"]),f=[],g=["node_modules",".git","vendor",".context",".expanded","web"];!function t(o){try{for(const a of e(o)){if(a.startsWith("."))continue;const e=i(o,a);n(e).isDirectory()?g.includes(a)||t(e):h.has(r(a))&&f.push(e)}}catch{}}(o.resolve(t,"src"));let u=0,y=0,w=0,v=0,_og=0;const j=o.resolve(t);for(const e of f){try{const t=m(j,e),i=d(t,".js"),c=o.resolve(j,".context",p(t),i+".ctx");let r=0;s(c)&&(r=n(c).size,w+=r);try{const t=s(c)?a(c,"utf-8"):null;{const _r=await _cf(e,{beautify:false,legend:false});v+=(_r.expanded||_r.original);u+=_r.compressed;_og+=_r.original}}catch{v+=Math.ceil(1.3*n(e).size/4);_og+=Math.ceil(n(e).size/4)}}catch{continue}y++}c={files:y,codeTok:u,ctxTok:Math.ceil(w/4),totalTok:u+Math.ceil(w/4),expanded:v,original:_og};_cache.cs=c;_cache.cst=Date.now();break}case"/api/docs":{const e=a.get("file");if(e){try{const{readFileSync:n,existsSync:a}=await import("fs"),{basename:s,extname:i,dirname:r}=await import("path"),d=s(e,i(e))+".ctx",p=o.resolve(t,".context",r(e),d);c=a(p)?{docs:n(p,"utf-8"),file:e}:{docs:"",file:e}}catch(t){c={docs:"",file:e}}}else{c=await d.executeTool("docs",{action:"get",path:r})}break}case"/api/analysis":if(_cache.fa&&Date.now()-_cache.fat<12e4){c=_cache.fa}else{c=await d.executeTool("analyze",{action:"full_analysis",path:r});_cache.fa=c;_cache.fat=Date.now()}break;case"/api/analysis-summary":if(_cache.as&&Date.now()-_cache.ast<12e4){c=_cache.as}else{c=await d.executeTool("analyze",{action:"analysis_summary",path:r});_cache.as=c;_cache.ast=Date.now()}break;case"/api/deps":c=await d.executeTool("navigate",{action:"deps",symbol:a.get("symbol")});break;case"/api/usages":c=await d.executeTool("navigate",{action:"usages",symbol:a.get("symbol")});break;case"/api/expand":c=await d.executeTool("navigate",{action:"expand",symbol:a.get("symbol")});break;case"/api/chain":c=await d.executeTool("navigate",{action:"call_chain",from:a.get("from"),to:a.get("to")});break;case"/api/server-status":{const _now=Date.now();c={version:_pkgVersion,uptime:Math.round((_now-_startedAt)/1e3),agents:b.size,monitors:T.size,shutdownAt:_shutdownAt?Math.max(0,Math.round((_shutdownAt-_now)/1e3)):null};break}case"/api/stop":i.writeHead(200,{"Content-Type":"application/json"});i.end(JSON.stringify({ok:true}));setTimeout(()=>process.exit(0),200);return;case"/api/project-info":{const e=o.resolve(t),a=n.createHash("md5").update(e).digest("hex"),s=parseInt(a.slice(0,4),16)%360;c={name:p,path:e,color:`hsl(${s}, 65%, 55%)`,version:_pkgVersion,agents:b.size,pid:process.pid};break}case"/api/instances":try{const{listBackends:e}=await import("./backend-lifecycle.js");c=e()}catch{c=[{name:p,path:o.resolve(t),agents:b.size}]}break;default:return"POST"===s&&"/api/restart"===e?(i.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-cache"}),i.end(JSON.stringify({ok:!0,message:"Server restarting..."})),void setTimeout(async()=>{const{spawn:e}=await import("child_process"),{removePortFile:n}=await import("./backend-lifecycle.js"),{fileURLToPath:a}=await import("url"),s=o.join(o.dirname(a(import.meta.url)),"backend.js");n(t),e(process.execPath,[s,o.resolve(t)],{detached:!0,stdio:"ignore",env:{...process.env,PROJECT_GRAPH_BACKEND:"1"}}).unref(),setTimeout(()=>process.exit(0),300)},200)):(i.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-cache, no-store, must-revalidate"}),void i.end(JSON.stringify({error:"Unknown API endpoint"})))}i.writeHead(200,{"Content-Type":"application/json","Access-Control-Allow-Origin":"*","Cache-Control":"no-cache"}),i.end(JSON.stringify(c))}catch(e){i.writeHead(500,{"Content-Type":"application/json","Cache-Control":"no-cache, no-store, must-revalidate"}),i.end(JSON.stringify({error:e.message}))}}P(),k(),c.on("tool:call",e=>{j.events.push(e),j.events.length>500&&j.events.shift(),x("event",e);const _n=e.tool||e.name||"";if("invalidate_cache"===_n||"set_custom_rule"===_n||"delete_custom_rule"===_n)_clearCache();else if("docs"===_n||"compact"===_n||"filters"===_n){const _a=e.args?.action||"";if("generate"===_a||"set_mode"===_a||"set"===_a||"reset"===_a)_clearCache()}}),c.on("tool:result",e=>{j.events.push(e),j.events.length>500&&j.events.shift(),x("event",e)});const B=e.createServer((e,t)=>{const o=new URL(e.url,`http://localhost:${a||0}`);return"OPTIONS"===e.method?(t.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST","Access-Control-Allow-Headers":"Content-Type"}),void t.end()):o.pathname.startsWith("/api/")?(z(),void A(o.pathname,o.searchParams,e.method,t)):("/ws/monitor"===o.pathname&&console.log("UNEXPECTED HTTP /ws/monitor",e.headers),void g(o.pathname,t))}),M=new s({noServer:!0});M.on("connection",async e=>{T.add(e),z();const t={project:j.project};try{e.send(JSON.stringify({jsonrpc:"2.0",method:"snapshot",params:{state:t}}))}catch{}e.on("message",async t=>{let n;z();try{n=JSON.parse(t.toString())}catch{return}if(n.jsonrpc&&n.id&&n.method)if("tool"===n.method){const{name:a,args:s}=n.params||{};try{let t;if("compact"===a&&"compact_file"===s?.action&&s?.path){const e=o.resolve(h,s.path),n=o.basename(s.path,o.extname(s.path))+".ctx",a=o.resolve(h,".context",o.dirname(s.path),n);let i=null;try{const{readFileSync:e,existsSync:t}=await import("fs");t(a)&&(i=e(a,"utf-8"))}catch{}const c=await _cf(e,{beautify:false,legend:false}),p=i?Math.ceil(i.length/4):0,m=c.compressed+p;t={code:c.code,file:s.path,codeTok:c.compressed,ctxTok:p,totalTok:m,expanded:c.expanded||c.original,savings:c.savings}}else t=await d.executeTool(a,s||{});e.send(JSON.stringify({jsonrpc:"2.0",id:n.id,result:t}))}catch(t){e.send(JSON.stringify({jsonrpc:"2.0",id:n.id,error:{code:-32e3,message:t.message}}))}}else e.send(JSON.stringify({jsonrpc:"2.0",id:n.id,error:{code:-32601,message:`Unknown method: ${n.method}`}}))}),e.on("close",()=>{T.delete(e),P()}),e.on("error",()=>{T.delete(e),P()})}),B.on("upgrade",(e,t,o)=>{const n=e.headers["sec-websocket-key"];if(n)if("/ws/monitor"!==e.url){if("/mcp-ws"===e.url){const e=u(n);t.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${e}\r\n\r\n`);const o="agent-"+m++,a=i(e=>{try{t.write(y(JSON.stringify(e)))}catch{}});b.set(t,{agentId:o,mcpServer:a,connectedAt:Date.now()}),O(),S("project.agents",b.size),x("event",{type:"agent_connect",agentId:o,agents:b.size,ts:Date.now()});let s=Buffer.alloc(0);return t.on("data",e=>{for(s=Buffer.concat([s,e]);s.length>=2;){const n=w(s);if(!n)break;if(s=s.slice(n.totalLen),8===n.opcode)return b.delete(t),S("project.agents",b.size),x("event",{type:"agent_disconnect",agentId:o,agents:b.size,ts:Date.now()}),t.end(),void(0===b.size&&P());if(9===n.opcode){const o=Buffer.from(e);o[0]=240&o[0]|10,t.write(o);continue}1===n.opcode&&(async()=>{try{const e=JSON.parse(n.data),o=await a.handleMessage(e);null!==o&&t.write(y(JSON.stringify(o)))}catch(e){t.write(y(JSON.stringify({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"}})))}})()}}),t.on("close",()=>{b.delete(t),S("project.agents",b.size),x("event",{type:"agent_disconnect",agentId:o,agents:b.size,ts:Date.now()}),0===b.size&&P()}),void t.on("error",()=>{b.delete(t),0===b.size&&P()})}t.destroy()}else M.handleUpgrade(e,t,o,t=>{M.emit("connection",t,e)});else t.destroy()});const H=!a,J=a||0;return B.listen(J,"127.0.0.1",()=>{const e=B.address().port;if(H){const n=r("project-graph",e,{projectPath:o.resolve(t),projectName:p});setTimeout(()=>{const a=n.url;console.log(`\n ⬡ project-graph-mcp v${_pkgVersion}`),console.log(" ─────────────────────────────"),console.log(` → ${a}`),console.log(` → ${n.directUrl} (direct)`),console.log(` → Project: ${o.resolve(t)}`),console.log(` → MCP WebSocket: ws://127.0.0.1:${e}/mcp-ws\n`)},200)}else console.log(`\n ⬡ project-graph-mcp v${_pkgVersion}`),console.log(" ─────────────────────────────"),console.log(` → http://localhost:${e}/`),console.log(` → Project: ${o.resolve(t)}`),console.log(` → MCP WebSocket: ws://127.0.0.1:${e}/mcp-ws\n`)}),B}
8
+ export function startWebServer(t,a){_setRoots([{uri:"file://"+o.resolve(t)}]);const d=i(()=>{}),p=o.basename(o.resolve(t))||"root";let m=1;const _startedAt=Date.now();const h=o.resolve(t),f=n.createHash("md5").update(h).digest("hex"),v=parseInt(f.slice(0,4),16)%360,j={project:{name:p,path:h,color:`hsl(${v}, 65%, 55%)`,agents:0,pid:process.pid},skeleton:null,events:[]};function x(e,t){const o=JSON.stringify({jsonrpc:"2.0",method:e,params:t});for(const e of T)try{e.send(o)}catch{T.delete(e)}}function S(e,t){const o=e.split(".");let n=j;for(let e=0;e<o.length-1;e++)n=n[o[e]];n[o[o.length-1]]=t,x("patch",{path:e,value:t})}async function k(){if(!j.skeleton)try{j.skeleton=await d.executeTool("get_skeleton",{path:t})}catch(e){console.error("[project-graph] Failed to load skeleton:",e.message)}return j.skeleton}const b=new Map,T=new Set;let C=null;const _cache={cs:null,cst:0,as:null,ast:0,fa:null,fat:0};function _clearCache(){_cache.cs=null;_cache.cst=0;_cache.as=null;_cache.ast=0;_cache.fa=null;_cache.fat=0;j.skeleton=null}function N(){return b.size>0||T.size>0}function O(){C&&(clearTimeout(C),C=null);_shutdownAt=0}let _shutdownAt=0;function P(){N()||(O(),_shutdownAt=Date.now()+9e5,C=setTimeout(()=>{N()||(console.log("[project-graph] No clients for 15 min — shutting down."),process.exit(0))},9e5))}function z(){O(),P()}async function A(e,a,s,i){try{let c;const r=a.get("path")||t;switch(e){case"/api/skeleton":c=await d.executeTool("get_skeleton",{path:r});break;case"/api/file":{const e=a.get("path");if(e){const{resolve:n,basename:a,extname:s}=await import("path"),{readFileSync:i,existsSync:r}=await import("fs"),d=n(t,e),_ext=s(e).toLowerCase(),_jsExts=new Set([".js",".mjs",".ts",".tsx"]);if(_jsExts.has(_ext)){const p=a(e,s(e))+".ctx",m=o.resolve(t,".context",o.dirname(e),p),f=await _cf(d,{beautify:false,legend:false}),y=r(m)?Math.ceil(i(m,"utf-8").length/4):0,w=f.compressed+y;c={code:f.code,file:e,codeTok:f.compressed,ctxTok:y,totalTok:w,expanded:f.expanded||f.original,savings:f.savings}}else{try{const raw=i(d,"utf-8"),tok=Math.ceil(raw.length/4);c={code:raw,file:e,codeTok:tok,ctxTok:0,totalTok:tok,expanded:tok,savings:"0%",raw:true}}catch(err){c={code:`// Cannot read: ${err.message}`,file:e}}}}else c={code:"// No file specified",file:""};break}case"/api/compact-file":{const e=a.get("path");if(e){const{resolve:n,extname:_ex2}=await import("path"),{readFileSync:_rf2}=await import("fs"),s=n(t,e),_ext2=_ex2(e).toLowerCase(),_jsExts2=new Set([".js",".mjs",".ts",".tsx"]);if(_jsExts2.has(_ext2)){const i=await _cf(s,{beautify:!1,legend:!1});c={code:i.code,file:e,original:i.original,compressed:i.compressed,savings:i.savings}}else{try{const raw=_rf2(s,"utf-8"),tok=Math.ceil(raw.length/4);c={code:raw,file:e,original:tok,compressed:tok,savings:"0%",raw:true}}catch(err){c={code:`// Cannot read: ${err.message}`,file:e}}}}else c={code:"// No file specified",file:""};break}case"/api/raw-file":{const e=a.get("path");try{const{readFileSync:o}=await import("fs"),{resolve:n,relative:a}=await import("path");c={content:o(n(t,e),"utf-8"),file:e}}catch(t){c={content:`// Cannot read: ${t.message}`,file:e}}break}case"/api/compression-stats":if(_cache.cs&&Date.now()-_cache.cst<6e4){c=_cache.cs;break}{const{readdirSync:e,statSync:n,readFileSync:a,existsSync:s}=await import("fs"),{join:i,extname:r,basename:d,dirname:p,relative:m}=await import("path"),h=new Set([".js",".mjs"]),f=[],g=["node_modules",".git","vendor",".context",".expanded","web"];!function t(o){try{for(const a of e(o)){if(a.startsWith("."))continue;const e=i(o,a);n(e).isDirectory()?g.includes(a)||t(e):h.has(r(a))&&f.push(e)}}catch{}}(o.resolve(t,"src"));let u=0,y=0,w=0,v=0,_og=0;const j=o.resolve(t);for(const e of f){try{const t=m(j,e),i=d(t,".js"),c=o.resolve(j,".context",p(t),i+".ctx");let r=0;s(c)&&(r=n(c).size,w+=r);try{const t=s(c)?a(c,"utf-8"):null;{const _r=await _cf(e,{beautify:false,legend:false});v+=(_r.expanded||_r.original);u+=_r.compressed;_og+=_r.original}}catch{v+=Math.ceil(1.3*n(e).size/4);_og+=Math.ceil(n(e).size/4)}}catch{continue}y++}c={files:y,codeTok:u,ctxTok:Math.ceil(w/4),totalTok:u+Math.ceil(w/4),expanded:v,original:_og};_cache.cs=c;_cache.cst=Date.now();break}case"/api/docs":{const e=a.get("file");if(e){try{const{readFileSync:n,existsSync:a}=await import("fs"),{basename:s,extname:i,dirname:r}=await import("path"),d=s(e,i(e))+".ctx",p=o.resolve(t,".context",r(e),d);c=a(p)?{docs:n(p,"utf-8"),file:e}:{docs:"",file:e}}catch(t){c={docs:"",file:e}}}else{c=await d.executeTool("docs",{action:"get",path:r})}break}case"/api/analysis":if(_cache.fa&&Date.now()-_cache.fat<12e4){c=_cache.fa}else{c=await d.executeTool("analyze",{action:"full_analysis",path:r});_cache.fa=c;_cache.fat=Date.now()}break;case"/api/analysis-summary":if(_cache.as&&Date.now()-_cache.ast<12e4){c=_cache.as}else{c=await d.executeTool("analyze",{action:"analysis_summary",path:r});_cache.as=c;_cache.ast=Date.now()}break;case"/api/deps":c=await d.executeTool("navigate",{action:"deps",symbol:a.get("symbol")});break;case"/api/usages":c=await d.executeTool("navigate",{action:"usages",symbol:a.get("symbol")});break;case"/api/expand":c=await d.executeTool("navigate",{action:"expand",symbol:a.get("symbol")});break;case"/api/chain":c=await d.executeTool("navigate",{action:"call_chain",from:a.get("from"),to:a.get("to")});break;case"/api/server-status":{const _now=Date.now();c={version:_pkgVersion,uptime:Math.round((_now-_startedAt)/1e3),agents:b.size,monitors:T.size,shutdownAt:_shutdownAt?Math.max(0,Math.round((_shutdownAt-_now)/1e3)):null};break}case"/api/stop":i.writeHead(200,{"Content-Type":"application/json"});i.end(JSON.stringify({ok:true}));setTimeout(()=>process.exit(0),200);return;case"/api/project-info":{const e=o.resolve(t),a=n.createHash("md5").update(e).digest("hex"),s=parseInt(a.slice(0,4),16)%360;c={name:p,path:e,color:`hsl(${s}, 65%, 55%)`,version:_pkgVersion,agents:b.size,pid:process.pid};break}case"/api/instances":try{const{listBackends:e}=await import("./backend-lifecycle.js");c=e()}catch{c=[{name:p,path:o.resolve(t),agents:b.size}]}break;default:return"POST"===s&&"/api/restart"===e?(i.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-cache"}),i.end(JSON.stringify({ok:!0,message:"Server restarting..."})),void setTimeout(async()=>{const{spawn:e}=await import("child_process"),{removePortFile:n}=await import("./backend-lifecycle.js"),{fileURLToPath:a}=await import("url"),s=o.join(o.dirname(a(import.meta.url)),"backend.js");n(t),e(process.execPath,[s,o.resolve(t)],{detached:!0,stdio:"ignore",env:{...process.env,PROJECT_GRAPH_BACKEND:"1"}}).unref(),setTimeout(()=>process.exit(0),300)},200)):(i.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-cache, no-store, must-revalidate"}),void i.end(JSON.stringify({error:"Unknown API endpoint"})))}i.writeHead(200,{"Content-Type":"application/json","Access-Control-Allow-Origin":"*","Cache-Control":"no-cache"}),i.end(JSON.stringify(c))}catch(e){i.writeHead(500,{"Content-Type":"application/json","Cache-Control":"no-cache, no-store, must-revalidate"}),i.end(JSON.stringify({error:e.message}))}}P(),k(),c.on("tool:call",e=>{j.events.push(e),j.events.length>500&&j.events.shift(),x("event",e);const _n=e.tool||e.name||"";if("invalidate_cache"===_n||"set_custom_rule"===_n||"delete_custom_rule"===_n)_clearCache();else if("docs"===_n||"compact"===_n||"filters"===_n){const _a=e.args?.action||"";if("generate"===_a||"set_mode"===_a||"set"===_a||"reset"===_a)_clearCache()}}),c.on("tool:result",e=>{j.events.push(e),j.events.length>500&&j.events.shift(),x("event",e)});const B=e.createServer((e,t)=>{const o=new URL(e.url,`http://localhost:${a||0}`);return"OPTIONS"===e.method?(t.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST","Access-Control-Allow-Headers":"Content-Type"}),void t.end()):o.pathname.startsWith("/api/")?(z(),void A(o.pathname,o.searchParams,e.method,t)):("/ws/monitor"===o.pathname&&console.log("UNEXPECTED HTTP /ws/monitor",e.headers),void g(o.pathname,t))}),M=new s({noServer:!0});M.on("connection",async e=>{T.add(e),z();const t={project:j.project};try{e.send(JSON.stringify({jsonrpc:"2.0",method:"snapshot",params:{state:t}}))}catch{}e.on("message",async t=>{let n;z();try{n=JSON.parse(t.toString())}catch{return}if(n.jsonrpc&&n.id&&n.method)if("tool"===n.method){const{name:a,args:s}=n.params||{};try{let t;if("compact"===a&&"compact_file"===s?.action&&s?.path){const e=o.resolve(h,s.path),_wsExt=o.extname(s.path).toLowerCase(),_wsJsExts=new Set([".js",".mjs",".ts",".tsx"]);if(_wsJsExts.has(_wsExt)){const n=o.basename(s.path,o.extname(s.path))+".ctx",a=o.resolve(h,".context",o.dirname(s.path),n);let i=null;try{const{readFileSync:e,existsSync:t}=await import("fs");t(a)&&(i=e(a,"utf-8"))}catch{}const c=await _cf(e,{beautify:false,legend:false}),p=i?Math.ceil(i.length/4):0,m=c.compressed+p;t={code:c.code,file:s.path,codeTok:c.compressed,ctxTok:p,totalTok:m,expanded:c.expanded||c.original,savings:c.savings}}else{try{const{readFileSync:rf}=await import("fs");const raw=rf(e,"utf-8"),tok=Math.ceil(raw.length/4);t={code:raw,file:s.path,codeTok:tok,ctxTok:0,totalTok:tok,expanded:tok,savings:"0%",raw:true}}catch(err){t={code:`// Cannot read: ${err.message}`,file:s.path}}}}else t=await d.executeTool(a,s||{});e.send(JSON.stringify({jsonrpc:"2.0",id:n.id,result:t}))}catch(t){e.send(JSON.stringify({jsonrpc:"2.0",id:n.id,error:{code:-32e3,message:t.message}}))}}else e.send(JSON.stringify({jsonrpc:"2.0",id:n.id,error:{code:-32601,message:`Unknown method: ${n.method}`}}))}),e.on("close",()=>{T.delete(e),P()}),e.on("error",()=>{T.delete(e),P()})}),B.on("upgrade",(e,t,o)=>{const n=e.headers["sec-websocket-key"];if(n)if("/ws/monitor"!==e.url){if("/mcp-ws"===e.url){const e=u(n);t.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${e}\r\n\r\n`);const o="agent-"+m++,a=i(e=>{try{t.write(y(JSON.stringify(e)))}catch{}});b.set(t,{agentId:o,mcpServer:a,connectedAt:Date.now()}),O(),S("project.agents",b.size),x("event",{type:"agent_connect",agentId:o,agents:b.size,ts:Date.now()});let s=Buffer.alloc(0);return t.on("data",e=>{for(s=Buffer.concat([s,e]);s.length>=2;){const n=w(s);if(!n)break;if(s=s.slice(n.totalLen),8===n.opcode)return b.delete(t),S("project.agents",b.size),x("event",{type:"agent_disconnect",agentId:o,agents:b.size,ts:Date.now()}),t.end(),void(0===b.size&&P());if(9===n.opcode){const o=Buffer.from(e);o[0]=240&o[0]|10,t.write(o);continue}1===n.opcode&&(async()=>{try{const e=JSON.parse(n.data),o=await a.handleMessage(e);null!==o&&t.write(y(JSON.stringify(o)))}catch(e){t.write(y(JSON.stringify({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"}})))}})()}}),t.on("close",()=>{b.delete(t),S("project.agents",b.size),x("event",{type:"agent_disconnect",agentId:o,agents:b.size,ts:Date.now()}),0===b.size&&P()}),void t.on("error",()=>{b.delete(t),0===b.size&&P()})}t.destroy()}else M.handleUpgrade(e,t,o,t=>{M.emit("connection",t,e)});else t.destroy()});const H=!a,J=a||0;return B.listen(J,"127.0.0.1",()=>{const e=B.address().port;if(H){const n=r("project-graph",e,{projectPath:o.resolve(t),projectName:p});setTimeout(()=>{const a=n.url;console.log(`\n ⬡ project-graph-mcp v${_pkgVersion}`),console.log(" ─────────────────────────────"),console.log(` → ${a}`),console.log(` → ${n.directUrl} (direct)`),console.log(` → Project: ${o.resolve(t)}`),console.log(` → MCP WebSocket: ws://127.0.0.1:${e}/mcp-ws\n`)},200)}else console.log(`\n ⬡ project-graph-mcp v${_pkgVersion}`),console.log(" ─────────────────────────────"),console.log(` → http://localhost:${e}/`),console.log(` → Project: ${o.resolve(t)}`),console.log(` → MCP WebSocket: ws://127.0.0.1:${e}/mcp-ws\n`)}),B}