project-graph-mcp 2.2.3 → 2.2.6
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.
- package/package.json +1 -1
- package/src/compact/expand.js +2 -4
- package/src/network/web-server.js +5 -2
- package/web/panels/code-viewer.js +61 -10
package/package.json
CHANGED
package/src/compact/expand.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// @ctx .context/src/compact/expand.ctx
|
|
2
2
|
import{parseCtxParams as h,buildJSDocBlock as g}from"./jsdoc-builder.js";import{readCtxFile as $}from"./ctx-resolver.js";import{walkJSFiles}from"../core/file-walker.js";import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as s}from"fs";import{join as i,basename as a,extname as c,dirname as p,relative as l}from"path";import{minify as m}from"../../vendor/terser.mjs";import{parse as d}from"../../vendor/acorn.mjs";import{simple as f,ancestor as u}from"../../vendor/walk.mjs";
|
|
3
3
|
|
|
4
|
-
function y(t){if(!t)return null;const e=t.match(/^→([A-Z][\w<>\[\]|]*)/);return e?e[1]:null}
|
|
5
|
-
|
|
6
|
-
function w(t,e,n,s,o){const r=new Map,i=[];for(const t of e.body)if("ImportDeclaration"===t.type)for(const e of t.specifiers)if("ImportSpecifier"===e.type&&e.imported.name!==e.local.name)r.set(e.local.name,e.imported.name),i.push({s:e.start,e:e.end,n:e.imported.name});else if("ImportDefaultSpecifier"===e.type){const n=t.source.value.replace(/^node:/,"").split("/").pop().replace(/\.\w+$/,"").replace(/-(\w)/g,(t,e)=>e.toUpperCase());n&&/^[a-zA-Z_$][\w$]*$/.test(n)&&n!==e.local.name&&(r.set(e.local.name,n),i.push({s:e.start,e:e.end,n:n}))}else if("ImportNamespaceSpecifier"===e.type&&e.local.name.length<=2){const n=t.source.value.replace(/^node:/,"").split("/").pop().replace(/\.\w+$/,"").replace(/-(\w)/g,(t,e)=>e.toUpperCase());n&&/^[a-zA-Z_$][\w$]*$/.test(n)&&n!==e.local.name&&(r.set(e.local.name,n),i.push({s:e.start,e:e.end,n:"* as "+n}))}for(const[t,e]of s)r.set(t,e);if(o.has("__top__"))for(const[t,e]of o.get("__top__"))r.set(t,e);const a=new Set(r.values());for(const[t]of[...r])a.has(t)&&r.delete(t);const c=[],p=[],l=[];const m=t=>{const e=t.params.map(t=>"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&"Identifier"===t.left?.type?t.left.name:"RestElement"===t.type&&"Identifier"===t.argument?.type?t.argument.name:null).filter(Boolean),s=new Map;if(t.id?.name){const r=n.get(t.id.name);if(r?.params)for(let t=0;t<Math.min(e.length,r.params.length);t++)e[t]!==r.params[t].name&&s.set(e[t],r.params[t].name);const i=o.get(t.id.name);if(i)for(const[t,e]of i)s.has(t)||s.set(t,e);if(s.size>0)for(const e of t.params){const t="Identifier"===e.type?e:"AssignmentPattern"===e.type&&"Identifier"===e.left?.type?e.left:null;t&&s.has(t.name)&&p.push({s:t.start,e:t.end,n:s.get(t.name)})}}const r=function(t){const e=new Set;return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.add(t.id.name)},CatchClause(t){t.param&&"Identifier"===t.param.type&&e.add(t.param.name)}}),e}(t.body);for(const t of e)r.add(t);const i=t.id?.name&&o.get(t.id.name);for(const e of function(t){const e=[];return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.push(t.id)}}),e}(t.body)){const t=i?.get(e.name);t&&l.push({s:e.start,e:e.end,n:t})}c.push({s:t.params.length>0?t.params[0].start:t.body.start,e:t.body.end,p:r,r:s})};f(e,{FunctionDeclaration:m,FunctionExpression:m,ArrowFunctionExpression:m});for(const t of e.body)if("VariableDeclaration"===t.type)for(const e of t.declarations)e.id&&"Identifier"===e.id.type&&r.has(e.id.name)&&l.push({s:e.id.start,e:e.id.end,n:r.get(e.id.name)});const d=[...i,...p,...l];u(e,{Identifier(t,e,n){const s=n[n.length-2];if("MemberExpression"===s?.type&&s.property===t&&!s.computed)return;if("Property"===s?.type&&s.key===t&&!s.computed&&s.value!==t)return;if("ExportSpecifier"===s?.type)return;const o=t.name;let i=null;for(const e of c)t.start>=e.s&&t.end<=e.e&&(!i||e.e-e.s<i.e-i.s)&&(i=e);i&&i.p.has(o)?i.r.has(o)&&d.push({s:t.start,e:t.end,n:i.r.get(o)}):r.has(o)&&d.push({s:t.start,e:t.end,n:r.get(o)})}}),d.sort((t,e)=>e.s-t.s);let h=t;for(const t of d)h=h.slice(0,t.s)+t.n+h.slice(t.e);return h}
|
|
7
|
-
export async function expandFile(e,n,s={}){const{indentLevel:o=2}=s,r=t(e,"utf-8");if(!r.trim())return{code:"",injected:0,original:0,decompiled:0};let i;try{i=(await m(r,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!1,indent_level:o,semicolons:!0}})).code||r}catch{i=r}{const t=i.split("\n"),e=[];for(let n=0;n<t.length;n++){const s=t[n];if(""===s.trim()){let s=n+1;for(;s<t.length&&""===t[s].trim();)s++;if(n>0&&e.length>0&&e[e.length-1].startsWith("import ")&&s<t.length&&t[s].startsWith("import "))continue}e.push(s)}i=e.join("\n")}i=i.replace(/^( +)/gm,t=>{const e=Math.floor(t.length/o);return"\t".repeat(e)+" ".repeat(t.length%o)});const a=function(t){const e=new Map;if(!t)return e;for(const n of t.split("\n")){const t=n.trim();if(!t||t.startsWith("---")||t.startsWith("@")||t.startsWith("CALLS")||t.startsWith("R→")||t.startsWith("W→")||t.startsWith("PATTERNS:")||t.startsWith("EDGE_CASES:")||t.startsWith("Rules:")||t.startsWith("Save this"))continue;const s=t.match(/^class\s+([\w]+)([^|]*)\|([^|]*)\|?(.*)$/);if(s){e.set(s[1],{type:"class",extends:s[2].replace(/\s*extends\s*/,"").trim()||null,meta:s[3].trim(),description:s[4]?.trim()||"",exported:!1});continue}const o=t.match(/^\s+\.(\w+)\(([^)]*)\)\|?(.*)$/);if(o){e.set(o[1],{type:"method",params:h(o[2]),description:o[3]?.trim()||""});continue}const r=t.match(/^(export\s+)?(\w+)\(([^)]*)\)(→[^|]*)?\|(.*)$/);if(r){const t=r[2],n=r[3],s=r[4]||"",o=(r[5]||"").split("|"),i=o[0]?.trim()||"";e.set(t,{type:"function",params:h(n),returnType:y(s),description:i,exported:!!r[1]});continue}}return e}(n),c=function(t){const e=new Map;if(!t)return e;for(const n of t.split("\n")){const t=n.trim();if(t.startsWith("@vars ")){const n=t.slice(6).split(",");for(const t of n){const n=t.trim().split("=");2===n.length&&e.set(n[0].trim(),n[1].trim())}}}return e}(n),p=function(t){const e=new Map;if(!t)return e;for(const n of t.split("\n")){const t=n.trim();if(t.startsWith("@names ")){const n=t.slice(7).split(/\s+/);for(const t of n){const n=t.indexOf(":");if(-1===n)continue;const s=t.slice(0,n),o=t.slice(n+1),r=new Map;for(const t of o.split(",")){const e=t.trim().split("=");2===e.length&&r.set(e[0].trim(),e[1].trim())}r.size>0&&e.set(s,r)}}}return e}(n);let l;try{l=d(i,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{return{code:i,injected:0,original:r.length,decompiled:i.length}}try{i=w(i,l,a,c,p),l=d(i,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{}if(0===a.size)return{code:i,injected:0,original:r.length,decompiled:i.length};const u=[];f(l,{ExportNamedDeclaration(t){const e=t.declaration;if(e){if("FunctionDeclaration"===e.type&&e.id?.name){const n=a.get(e.id.name);n&&u.push({pos:t.start,jsdoc:g(n)})}if("ClassDeclaration"===e.type&&e.id?.name){const n=a.get(e.id.name);n&&n.description&&u.push({pos:t.start,jsdoc:`/**\n * ${n.description}\n */`})}}},FunctionDeclaration(t){if(!t.id?.name)return;const e=a.get(t.id.name);e&&!e.exported&&u.push({pos:t.start,jsdoc:g(e)})},ClassDeclaration(t){if(!t.id?.name)return;const e=a.get(t.id.name);e&&!e.exported&&e.description&&u.push({pos:t.start,jsdoc:`/**\n * ${e.description}\n */`})}}),u.sort((t,e)=>e.pos-t.pos);let x=i,j=0;for(const{pos:t,jsdoc:e}of u){const n=x.lastIndexOf("\n",t-1),s=-1===n?0:n+1,o=x.slice(s,t).match(/^(\s*)/)?.[1]||"",r=e.split("\n").map(t=>o+t).join("\n");x=x.slice(0,t)+r+"\n"+x.slice(t),j++}return{code:x,injected:j,original:r.length,decompiled:x.length}}
|
|
4
|
+
function y(t){if(!t)return null;const e=t.match(/^→([A-Z][\w<>\[\]|]*)/);return e?e[1]:null}function w(t,e,n,s,o){const r=new Map;let i=[];for(const t of e.body)if("ImportDeclaration"===t.type)for(const e of t.specifiers)if("ImportSpecifier"===e.type&&e.imported.name!==e.local.name)r.set(e.local.name,e.imported.name),i.push({s:e.start,e:e.end,n:e.imported.name,k:e.local.name});else if("ImportDefaultSpecifier"===e.type){const n=t.source.value.replace(/^node:/,"").split("/").pop().replace(/\.\\w+$/,"").replace(/-(\w)/g,(t,e)=>e.toUpperCase());n&&/^[a-zA-Z_$][\w$]*$/.test(n)&&n!==e.local.name&&(r.set(e.local.name,n),i.push({s:e.start,e:e.end,n:n,k:e.local.name}))}else if("ImportNamespaceSpecifier"===e.type&&e.local.name.length<=2){const n=t.source.value.replace(/^node:/,"").split("/").pop().replace(/\.\\w+$/,"").replace(/-(\w)/g,(t,e)=>e.toUpperCase());n&&/^[a-zA-Z_$][\w$]*$/.test(n)&&n!==e.local.name&&(r.set(e.local.name,n),i.push({s:e.start,e:e.end,n:"* as "+n,k:e.local.name}))}for(const[t,e]of s)r.set(t,e);if(o.has("__top__"))for(const[t,e]of o.get("__top__"))r.set(t,e);const a=new Set(r.values());for(const[t]of[...r])a.has(t)&&r.delete(t);{const _u=new Set;for(const[k,v]of[...r])_u.has(v)?(r.delete(k),i=i.filter(x=>x.k!==k)):_u.add(v)}const c=[],p=[],l=[];const m=t=>{const e=t.params.map(t=>"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&"Identifier"===t.left?.type?t.left.name:"RestElement"===t.type&&"Identifier"===t.argument?.type?t.argument.name:null).filter(Boolean),s=new Map;if(t.id?.name){const r=n.get(t.id.name);if(r?.params)for(let t=0;t<Math.min(e.length,r.params.length);t++)e[t]!==r.params[t].name&&s.set(e[t],r.params[t].name);const i=o.get(t.id.name);if(i)for(const[t,e]of i)s.has(t)||s.set(t,e);if(s.size>0)for(const e of t.params){const t="Identifier"===e.type?e:"AssignmentPattern"===e.type&&"Identifier"===e.left?.type?e.left:null;t&&s.has(t.name)&&p.push({s:t.start,e:t.end,n:s.get(t.name)})}}const r=function(t){const e=new Set;return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.add(t.id.name)},CatchClause(t){t.param&&"Identifier"===t.param.type&&e.add(t.param.name)}}),e}(t.body);for(const t of e)r.add(t);const _tgts=Array.from(s.values());for(const t of _tgts){if(r.has(t)&&!s.has(t)){let n=t+"_";while(r.has(n)||_tgts.includes(n))n+="_";s.set(t,n)}}const i=t.id?.name&&o.get(t.id.name);for(const e of function(t){const e=[];return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.push(t.id)},CatchClause(t){t.param&&"Identifier"===t.param.type&&e.push(t.param)}}),e}(t.body)){const t=s.has(e.name)?s.get(e.name):i?.get(e.name);t&&l.push({s:e.start,e:e.end,n:t})}c.push({s:t.params.length>0?t.params[0].start:t.body.start,e:t.body.end,p:r,r:s})};f(e,{FunctionDeclaration:m,FunctionExpression:m,ArrowFunctionExpression:m});for(const t of e.body)if("VariableDeclaration"===t.type)for(const e of t.declarations)e.id&&"Identifier"===e.id.type&&r.has(e.id.name)&&l.push({s:e.id.start,e:e.id.end,n:r.get(e.id.name)});const d=[...i,...p,...l];u(e,{Identifier(t,e,n){const s=n[n.length-2];if("MemberExpression"===s?.type&&s.property===t&&!s.computed)return;if("Property"===s?.type&&s.key===t&&!s.computed&&s.value!==t)return;if("ExportSpecifier"===s?.type)return;const o=t.name;const scopes=c.filter(e=>t.start>=e.s&&t.end<=e.e).sort((a,b)=>(a.e-a.s)-(b.e-b.s));let mapped=!1,isLocal=!1;for(const e of scopes){if(e.r.has(o)){d.push({s:t.start,e:t.end,n:e.r.get(o)});mapped=!0;break}if(e.p.has(o)){isLocal=!0;break}}if(!mapped&&!isLocal&&r.has(o)){d.push({s:t.start,e:t.end,n:r.get(o)})}}});d.sort((t,e)=>e.s-t.s);let h=t;for(const t of d)h=h.slice(0,t.s)+t.n+h.slice(t.e);return h}
|
|
5
|
+
export async function expandFile(e,n,s={}){const{indentLevel:o=2}=s,r=t(e,"utf-8");if(!r.trim())return{code:"",injected:0,original:0,decompiled:0};let i;try{i=(await m(r,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!1,indent_level:o,semicolons:!0}})).code||r}catch{i=r}{const t=i.split("\n"),e=[];for(let n=0;n<t.length;n++){const s=t[n];if(""===s.trim()){let s=n+1;for(;s<t.length&&""===t[s].trim();)s++;if(n>0&&e.length>0&&e[e.length-1].startsWith("import ")&&s<t.length&&t[s].startsWith("import "))continue}e.push(s)}i=e.join("\n")}i=i.replace(/^( +)/gm,t=>{const e=Math.floor(t.length/o);return"\t".repeat(e)+" ".repeat(t.length%o)});const a=function(t){const e=new Map;if(!t)return e;for(const n of t.split("\n")){const t=n.trim();if(!t||t.startsWith("---")||t.startsWith("@")||t.startsWith("CALLS")||t.startsWith("R→")||t.startsWith("W→")||t.startsWith("PATTERNS:")||t.startsWith("EDGE_CASES:")||t.startsWith("Rules:")||t.startsWith("Save this"))continue;const s=t.match(/^class\s+([\w]+)([^|]*)\|([^|]*)\|?(.*)$/);if(s){e.set(s[1],{type:"class",extends:s[2].replace(/\s*extends\s*/,"").trim()||null,meta:s[3].trim(),description:s[4]?.trim()||"",exported:!1});continue}const o=t.match(/^\s+\.(\w+)\(([^)]*)\)\|?(.*)$/);if(o){e.set(o[1],{type:"method",params:h(o[2]),description:o[3]?.trim()||""});continue}const r=t.match(/^(export\s+)?(\w+)\(([^)]*)\)(→[^|]*)?\|(.*)$/);if(r){const t=r[2],n=r[3],s=r[4]||"",o=(r[5]||"").split("|"),i=o[0]?.trim()||"";e.set(t,{type:"function",params:h(n),returnType:y(s),description:i,exported:!!r[1]});continue}}return e}(n),c=function(t){const e=new Map;if(!t)return e;for(const n of t.split("\n")){const t=n.trim();if(t.startsWith("@vars ")){const n=t.slice(6).split(",");for(const t of n){const n=t.trim().split("=");2===n.length&&e.set(n[0].trim(),n[1].trim())}}}return e}(n),p=function(t){const e=new Map;if(!t)return e;for(const n of t.split("\n")){const t=n.trim();if(t.startsWith("@names ")){const n=t.slice(7).split(/\s+/);for(const t of n){const n=t.indexOf(":");if(-1===n)continue;const s=t.slice(0,n),o=t.slice(n+1),r=new Map;for(const t of o.split(",")){const e=t.trim().split("=");2===e.length&&r.set(e[0].trim(),e[1].trim())}r.size>0&&e.set(s,r)}}}return e}(n);let l;try{l=d(i,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{return{code:i,injected:0,original:r.length,decompiled:i.length}}i=w(i,l,a,c,p);l=d(i,{ecmaVersion:"latest",sourceType:"module",locations:!0});if(0===a.size)return{code:i,injected:0,original:r.length,decompiled:i.length};const u=[];f(l,{ExportNamedDeclaration(t){const e=t.declaration;if(e){if("FunctionDeclaration"===e.type&&e.id?.name){const n=a.get(e.id.name);n&&u.push({pos:t.start,jsdoc:g(n)})}if("ClassDeclaration"===e.type&&e.id?.name){const n=a.get(e.id.name);n&&n.description&&u.push({pos:t.start,jsdoc:`/**\n * ${n.description}\n */`})}}},FunctionDeclaration(t){if(!t.id?.name)return;const e=a.get(t.id.name);e&&!e.exported&&u.push({pos:t.start,jsdoc:g(e)})},ClassDeclaration(t){if(!t.id?.name)return;const e=a.get(t.id.name);e&&!e.exported&&e.description&&u.push({pos:t.start,jsdoc:`/**\n * ${e.description}\n */`})}}),u.sort((t,e)=>e.pos-t.pos);let x=i,j=0;for(const{pos:t,jsdoc:e}of u){const n=x.lastIndexOf("\n",t-1),s=-1===n?0:n+1,o=x.slice(s,t).match(/^(\s*)/)?.[1]||"",r=e.split("\n").map(t=>o+t).join("\n");x=x.slice(0,t)+r+"\n"+x.slice(t),j++}return{code:x,injected:j,original:r.length,decompiled:x.length}}
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// @ctx .context/src/network/web-server.ctx
|
|
2
|
-
import e from"node:http";import t from"node:fs";import o from"node:path";import n from"node:crypto";import{fileURLToPath as a}from"node:url";import{createRequire as _createRequire}from"node:module";import{WebSocketServer as s}from"ws";import{createServer as i}from"../mcp/mcp-server.js";import c from"../core/event-bus.js";import{registerService as r}from"./local-gateway.js";import{compressFile as _cf}from"../compact/compress.js";import{setRoots as _setRoots}from"../core/workspace.js";
|
|
2
|
+
import e from"node:http";import t from"node:fs";import o from"node:path";import n from"node:crypto";import{fileURLToPath as a}from"node:url";import{createRequire as _createRequire}from"node:module";import{WebSocketServer as s}from"ws";import{createServer as i}from"../mcp/mcp-server.js";import c from"../core/event-bus.js";import{registerService as r}from"./local-gateway.js";import{compressFile as _cf}from"../compact/compress.js";import{expandFile as _ef}from"../compact/expand.js";import{setRoots as _setRoots}from"../core/workspace.js";
|
|
3
3
|
const d=o.dirname(a(import.meta.url)),p=o.join(d,"..","..");const _rq=_createRequire(import.meta.url);let _pkgVersion="0.0.0";try{_pkgVersion=JSON.parse(t.readFileSync(o.join(p,"package.json"),"utf8")).version}catch{}function _rv(k){try{const r=_rq.resolve(k);const marker=o.sep+"node_modules"+o.sep+k.replace(/\//g,o.sep);const idx=r.lastIndexOf(marker);if(idx>=0)return r.substring(0,idx+marker.length);return o.dirname(r)}catch{return o.join(p,"node_modules",...k.split("/"))}}const m=o.join(p,"web"),h={"symbiote-node":_rv("symbiote-node"),symbiote:_rv("@symbiotejs/symbiote")},f={".html":"text/html",".js":"text/javascript",".mjs":"text/javascript",".css":"text/css",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".ico":"image/x-icon",".woff2":"font/woff2"};
|
|
4
4
|
function g(e,n){const a=o.normalize(e).replace(/^(\.\.[/\\])+/,""),s=a.match(/^[/\\]?vendor[/\\]([^/\\]+)[/\\]?(.*)/);let i,c;if(s&&h[s[1]]?(c=h[s[1]],i=o.join(c,s[2]||"index.js")):(c=m,i=o.join(m,"/"===a?"index.html":a)),!i.startsWith(c))return n.writeHead(403),void n.end("Forbidden");if(t.existsSync(i)&&t.statSync(i).isDirectory()&&(i=o.join(i,"index.html")),!t.existsSync(i))return n.writeHead(404),void n.end("Not Found");const r=o.extname(i),l=f[r]||"application/octet-stream",d=t.readFileSync(i);n.writeHead(200,{"Content-Type":l,"Cache-Control":"no-cache, no-store, must-revalidate"}),n.end(d)}
|
|
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),_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/image":{const e=a.get("path");if(e){try{const _fs=await import("fs"),_pt=await import("path"),_imgPath=_pt.resolve(t,e);if(!_fs.existsSync(_imgPath)){i.writeHead(404);i.end("Not Found");return}const _imgTypes={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".svg":"image/svg+xml",".webp":"image/webp",".ico":"image/x-icon",".bmp":"image/bmp"};const ct=_imgTypes[_pt.extname(e).toLowerCase()]||"application/octet-stream";const buf=_fs.readFileSync(_imgPath);i.writeHead(200,{"Content-Type":ct,"Cache-Control":"public, max-age=3600","Content-Length":buf.length});i.end(buf);return}catch(err){i.writeHead(500);i.end(err.message);return}}i.writeHead(400);i.end("No path");return}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}
|
|
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/expand-file":{const e=a.get("path");if(e){try{const _pt2=await import("path"),_fs2=await import("fs"),_abs=_pt2.resolve(t,e),_ext3=_pt2.extname(e).toLowerCase(),_jsExts3=new Set([".js",".mjs",".ts",".tsx"]);if(!_jsExts3.has(_ext3)){const raw=_fs2.readFileSync(_abs,"utf-8");c={code:raw,file:e,injected:0};break}let _ctxContent=null;const _ctxName=_pt2.basename(e,_pt2.extname(e))+".ctx";const _ctxPath1=_pt2.join(t,_pt2.dirname(e),_ctxName);const _ctxPath2=_pt2.join(t,".context",_pt2.dirname(e),_ctxName);if(_fs2.existsSync(_ctxPath1))_ctxContent=_fs2.readFileSync(_ctxPath1,"utf-8");else if(_fs2.existsSync(_ctxPath2))_ctxContent=_fs2.readFileSync(_ctxPath2,"utf-8");const _result=await _ef(_abs,_ctxContent);
|
|
9
|
+
// Validation removed: we enforce a zero-fallback policy. Code must be generated correctly or fail loudly.
|
|
10
|
+
let _expandedCode=_result.code;
|
|
11
|
+
c={code:_expandedCode,file:e,injected:_result.injected,original:_result.original,decompiled:_result.decompiled}}catch(err){c={code:`// Expand failed: ${err.message}`,file:e}}}else c={code:"// No file specified",file:""};break}case"/api/image":{const e=a.get("path");if(e){try{const _fs=await import("fs"),_pt=await import("path"),_imgPath=_pt.resolve(t,e);if(!_fs.existsSync(_imgPath)){i.writeHead(404);i.end("Not Found");return}const _imgTypes={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".svg":"image/svg+xml",".webp":"image/webp",".ico":"image/x-icon",".bmp":"image/bmp"};const ct=_imgTypes[_pt.extname(e).toLowerCase()]||"application/octet-stream";const buf=_fs.readFileSync(_imgPath);i.writeHead(200,{"Content-Type":ct,"Cache-Control":"public, max-age=3600","Content-Length":buf.length});i.end(buf);return}catch(err){i.writeHead(500);i.end(err.message);return}}i.writeHead(400);i.end("No path");return}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}
|
|
@@ -4,22 +4,35 @@ import e from"@symbiotejs/symbiote";import{api as n,events as t,state as o,forma
|
|
|
4
4
|
const _extLang={'.md':'md','.markdown':'md','.sql':'sql','.json':'json','.css':'css','.html':'html','.htm':'html','.xml':'xml','.yaml':'yaml','.yml':'yaml','.toml':'toml','.sh':'sh','.bash':'bash','.env':'env','.ini':'ini','.conf':'conf','.cfg':'cfg','.txt':'plain','.csv':'csv','.gitignore':'plain','.dockerignore':'plain','.editorconfig':'plain','.png':'image','.jpg':'image','.jpeg':'image','.gif':'image','.svg':'image','.webp':'image','.bmp':'image','.ico':'image','.pdf':'binary','.zip':'binary','.tar':'binary','.gz':'binary','.woff':'binary','.woff2':'binary','.ttf':'binary','.eot':'binary','.mp3':'binary','.mp4':'binary','.wav':'binary','.avi':'binary','.mov':'binary'};
|
|
5
5
|
function _getLang(path){if(!path)return'js';const i=path.lastIndexOf('.');if(i<0){const base=path.split('/').pop()||'';if(['Dockerfile','Makefile','Procfile','LICENSE','README','CHANGELOG'].some(n=>base.startsWith(n)))return'plain';return'plain'}return _extLang[path.substring(i).toLowerCase()]||'js'}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Two operational modes for JS files:
|
|
8
|
+
//
|
|
9
|
+
// MODE A — Readable source (normal projects like 1sim_local):
|
|
10
|
+
// Source file is human-written, readable code.
|
|
11
|
+
// Toggle button label: "COMPACT" — shows Terser-compressed view.
|
|
12
|
+
// _isReadable = true (compression saves >15% tokens)
|
|
13
|
+
//
|
|
14
|
+
// MODE B — Compact source (compact projects like project-graph-mcp):
|
|
15
|
+
// Source file is already minified/compressed on disk.
|
|
16
|
+
// Toggle button label: "EXPAND" — beautifies via Terser + injects JSDoc from .ctx.
|
|
17
|
+
// _isReadable = false (compression saves <15% — already compact)
|
|
18
|
+
|
|
19
|
+
export class CodeViewer extends e{init$={filename:"Select a file",hasFile:!1,viewMode:"source",modeLabel:"source",statsText:"",showToggle:!1,toggleLabel:"",onToggleMode:()=>{
|
|
8
20
|
const lang=_getLang(this._currentPath);
|
|
9
21
|
if(lang==='md'){
|
|
10
22
|
this.$.viewMode=this.$.viewMode==="rendered"?"raw":"rendered";
|
|
11
23
|
this._showCurrentMode();
|
|
12
24
|
return;
|
|
13
25
|
}
|
|
14
|
-
|
|
26
|
+
// Toggle between source and the transformation
|
|
27
|
+
this.$.viewMode=this.$.viewMode==="source"?"transformed":"source";
|
|
15
28
|
this._showCurrentMode();
|
|
16
|
-
}};_fileData=null;_isReadable=!1;
|
|
29
|
+
}};_fileData=null;_isReadable=!1;_transformCache=null;_loadingTransform=!1;_currentPath=null;initCallback(){t.addEventListener("file-selected",e=>this._loadFile(e.detail.path))}renderCallback(){this.sub("hasFile",e=>{this.toggleAttribute("has-file",e)}),this.sub("viewMode",e=>{
|
|
17
30
|
const lang=_getLang(this._currentPath);
|
|
18
|
-
this.toggleAttribute("mode-raw","
|
|
31
|
+
this.toggleAttribute("mode-raw","source"!==e);
|
|
19
32
|
if(lang==='md'){
|
|
20
33
|
this.$.modeLabel=e==="rendered"?"rendered":"source";
|
|
21
34
|
}else{
|
|
22
|
-
this.$.modeLabel=
|
|
35
|
+
this.$.modeLabel=e==="source"?"source":(this._isReadable?"compact":"expanded");
|
|
23
36
|
}
|
|
24
37
|
})}_getCodeBlock(){return this.querySelector("code-block")}async _showCurrentMode(){if(!this._fileData)return;const e=this._getCodeBlock();if(!e)return;
|
|
25
38
|
const lang=_getLang(this._currentPath);
|
|
@@ -35,32 +48,70 @@ if(lang==='md'){
|
|
|
35
48
|
return;
|
|
36
49
|
}
|
|
37
50
|
e.$.lang=lang;
|
|
38
|
-
if("
|
|
51
|
+
if("transformed"===this.$.viewMode){
|
|
52
|
+
// Show cached transform if available
|
|
53
|
+
if(this._transformCache){e.$.code=this._transformCache;return}
|
|
54
|
+
if(this._loadingTransform)return;
|
|
55
|
+
this._loadingTransform=!0;
|
|
56
|
+
e.$.code=this._isReadable?"// Compressing...":"// Expanding...";
|
|
57
|
+
try{
|
|
58
|
+
if(this._isReadable){
|
|
59
|
+
// MODE A: readable source → compress
|
|
60
|
+
const t=await n("/api/compact-file",{path:this._currentPath});
|
|
61
|
+
this._transformCache=t?.code||"// Compression unavailable";
|
|
62
|
+
}else{
|
|
63
|
+
// MODE B: compact source → expand (beautify + inject JSDoc from .ctx)
|
|
64
|
+
const t=await n("/api/expand-file",{path:this._currentPath});
|
|
65
|
+
this._transformCache=t?.code||"// Expand unavailable";
|
|
66
|
+
}
|
|
67
|
+
e.$.code=this._transformCache;
|
|
68
|
+
}catch{e.$.code=this._isReadable?"// Compression failed":"// Expand failed"}
|
|
69
|
+
finally{this._loadingTransform=!1}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Source mode — raw file as-is
|
|
73
|
+
e.$.code=this._fileData.raw;
|
|
74
|
+
}async _loadFile(e){this.$.filename=e,this.$.hasFile=!1,this._fileData=null,this.$.statsText="",this._transformCache=null,this._currentPath=e;
|
|
39
75
|
const lang=_getLang(e);
|
|
40
76
|
if(lang==='image'){
|
|
41
77
|
const i=this._getCodeBlock();
|
|
42
78
|
if(i){i.$.lang='image';i.setBasePath(e);i.$.code=e}
|
|
43
79
|
this.$.viewMode="rendered";
|
|
44
80
|
this.$.modeLabel="image";
|
|
81
|
+
this.$.showToggle=!1;
|
|
45
82
|
this.$.hasFile=!0;
|
|
46
83
|
return;
|
|
47
84
|
}
|
|
48
85
|
if(lang==='binary'){
|
|
49
86
|
const i=this._getCodeBlock();
|
|
50
87
|
if(i){i.$.lang='plain';i.$.code=`// Binary file: ${e}\n// Cannot display binary content`}
|
|
51
|
-
this.$.viewMode="
|
|
88
|
+
this.$.viewMode="source";
|
|
52
89
|
this.$.modeLabel="binary";
|
|
90
|
+
this.$.showToggle=!1;
|
|
53
91
|
this.$.hasFile=!0;
|
|
54
92
|
return;
|
|
55
93
|
}
|
|
56
94
|
try{const[t,_raw]=await Promise.all([n("/api/file",{path:e}),n("/api/raw-file",{path:e}).catch(()=>null)]);const o="string"==typeof t.code?t.code:"string"==typeof t.compressed?t.compressed:t.content||JSON.stringify(t,null,2);
|
|
57
|
-
let s=_raw?.content||o;
|
|
95
|
+
let s=_raw?.content||o;
|
|
96
|
+
// Detect mode: if .ctx documentation exists (ctxTok > 0), source is compact → EXPAND available
|
|
97
|
+
// If no .ctx, source is readable → COMPACT available
|
|
98
|
+
const hasCtx=!!(t.ctxTok&&t.ctxTok>0);
|
|
99
|
+
this._isReadable=!hasCtx;
|
|
100
|
+
this._fileData={compact:o,raw:s,codeTok:t.codeTok||0,ctxTok:t.ctxTok||0,totalTok:t.totalTok||0,expanded:t.expanded||0,savings:t.savings||"0%"},t.codeTok&&t.expanded&&(this.$.statsText=formatStats(t));const i=this._getCodeBlock();
|
|
58
101
|
if(lang==='md'){
|
|
59
102
|
this.$.viewMode="rendered";
|
|
60
103
|
this.$.modeLabel="rendered";
|
|
104
|
+
this.$.showToggle=!0;
|
|
105
|
+
this.$.toggleLabel="source";
|
|
61
106
|
if(i){i.$.lang='md';i.setBasePath(e);i.$.code=s}
|
|
62
107
|
}else{
|
|
63
108
|
i&&(i.$.lang=lang);
|
|
64
|
-
|
|
109
|
+
// Always start in SOURCE mode
|
|
110
|
+
this.$.viewMode="source";
|
|
111
|
+
this.$.modeLabel="source";
|
|
112
|
+
// Toggle: readable → COMPACT button, compact → EXPAND button
|
|
113
|
+
this.$.showToggle=!0;
|
|
114
|
+
this.$.toggleLabel=this._isReadable?"compact":"expand";
|
|
115
|
+
i&&(i.$.code=s);
|
|
65
116
|
}
|
|
66
|
-
this.$.hasFile=!0}catch(e){const n=this._getCodeBlock();n&&(n.$.lang='plain',n.$.code=`// Error: ${e.message}`),this.$.hasFile=!0}}}CodeViewer.template='\n <div class="pg-code-header">\n <span class="pg-code-filename" bind="textContent: filename"></span>\n <div class="pg-code-controls">\n <span class="pg-code-stats" bind="textContent: statsText"></span>\n <button class="pg-mode-toggle" bind="onclick: onToggleMode" title="Toggle view mode">\n <span class="material-symbols-outlined" style="font-size:14px">compress</span>\n <span class="pg-mode-label" bind="textContent: modeLabel"></span>\n </button>\n </div>\n </div>\n <code-block></code-block>\n',CodeViewer.rootStyles="\n pg-code-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n pg-code-viewer:not([has-file]) code-block {\n display: none;\n }\n .pg-code-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 11px;\n color: var(--sn-text-dim, hsl(30, 10%, 45%));\n border-bottom: 1px solid var(--sn-node-border, hsl(35, 18%, 80%));\n background: var(--sn-node-header-bg, hsl(37, 25%, 93%));\n gap: 8px;\n }\n .pg-code-filename {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n .pg-code-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n }\n .pg-code-stats {\n font-size: 10px;\n color: var(--sn-cat-server, hsl(210, 45%, 45%));\n white-space: nowrap;\n }\n .pg-mode-toggle {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 8px;\n border: 1px solid var(--sn-node-border, hsl(35, 18%, 80%));\n border-radius: 4px;\n background: var(--sn-bg, hsl(37, 30%, 91%));\n color: var(--sn-text-dim, hsl(30, 10%, 45%));\n font-family: inherit;\n font-size: 10px;\n cursor: pointer;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n transition: all 120ms ease;\n }\n .pg-mode-toggle:hover {\n background: var(--sn-node-hover, hsl(36, 22%, 88%));\n color: var(--sn-text, hsl(30, 15%, 18%));\n }\n pg-code-viewer[mode-raw] .pg-mode-toggle {\n background: hsla(210, 45%, 45%, 0.12);\n border-color: var(--sn-cat-server, hsl(210, 45%, 45%));\n color: var(--sn-cat-server, hsl(210, 45%, 45%));\n }\n code-block {\n flex: 1;\n min-height: 0;\n }\n",CodeViewer.reg("pg-code-viewer");
|
|
117
|
+
this.$.hasFile=!0}catch(e){const n=this._getCodeBlock();n&&(n.$.lang='plain',n.$.code=`// Error: ${e.message}`),this.$.showToggle=!1,this.$.hasFile=!0}}}CodeViewer.template='\n <div class="pg-code-header">\n <span class="pg-code-filename" bind="textContent: filename"></span>\n <div class="pg-code-controls">\n <span class="pg-code-stats" bind="textContent: statsText"></span>\n <button class="pg-mode-toggle" bind="onclick: onToggleMode; hidden: !showToggle" title="Toggle view mode">\n <span class="material-symbols-outlined" style="font-size:14px">compress</span>\n <span class="pg-mode-label" bind="textContent: modeLabel"></span>\n </button>\n </div>\n </div>\n <code-block></code-block>\n',CodeViewer.rootStyles="\n pg-code-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n pg-code-viewer:not([has-file]) code-block {\n display: none;\n }\n .pg-code-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 11px;\n color: var(--sn-text-dim, hsl(30, 10%, 45%));\n border-bottom: 1px solid var(--sn-node-border, hsl(35, 18%, 80%));\n background: var(--sn-node-header-bg, hsl(37, 25%, 93%));\n gap: 8px;\n }\n .pg-code-filename {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n .pg-code-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n }\n .pg-code-stats {\n font-size: 10px;\n color: var(--sn-cat-server, hsl(210, 45%, 45%));\n white-space: nowrap;\n }\n .pg-mode-toggle {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 8px;\n border: 1px solid var(--sn-node-border, hsl(35, 18%, 80%));\n border-radius: 4px;\n background: var(--sn-bg, hsl(37, 30%, 91%));\n color: var(--sn-text-dim, hsl(30, 10%, 45%));\n font-family: inherit;\n font-size: 10px;\n cursor: pointer;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n transition: all 120ms ease;\n }\n .pg-mode-toggle:hover {\n background: var(--sn-node-hover, hsl(36, 22%, 88%));\n color: var(--sn-text, hsl(30, 15%, 18%));\n }\n pg-code-viewer[mode-raw] .pg-mode-toggle {\n background: hsla(210, 45%, 45%, 0.12);\n border-color: var(--sn-cat-server, hsl(210, 45%, 45%));\n color: var(--sn-cat-server, hsl(210, 45%, 45%));\n }\n .pg-mode-toggle[hidden] {\n display: none;\n }\n code-block {\n flex: 1;\n min-height: 0;\n }\n",CodeViewer.reg("pg-code-viewer");
|