project-graph-mcp 2.1.2 → 2.1.4
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/GUIDE.md +237 -0
- package/package.json +2 -1
- package/rules/test-rules.json +15 -0
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +3 -1
- package/src/analysis/complexity.js +9 -13
- package/src/analysis/custom-rules.js +16 -35
- package/src/analysis/db-analysis.js +2 -6
- package/src/analysis/dead-code.js +8 -18
- package/src/analysis/full-analysis.js +9 -17
- package/src/analysis/jsdoc-checker.js +11 -23
- package/src/analysis/jsdoc-generator.js +8 -9
- package/src/analysis/similar-functions.js +8 -15
- package/src/analysis/test-annotations.js +12 -20
- package/src/analysis/type-checker.js +5 -7
- package/src/analysis/undocumented.js +10 -13
- package/src/cli/cli-handlers.js +4 -3
- package/src/compact/ai-context.js +2 -2
- package/src/compact/compact-migrate.js +8 -16
- package/src/compact/compact.js +3 -5
- package/src/compact/compress.js +7 -13
- package/src/compact/ctx-resolver.js +5 -0
- package/src/compact/ctx-to-jsdoc.js +13 -28
- package/src/compact/doc-dialect.js +18 -29
- package/src/compact/expand.js +10 -36
- package/src/compact/jsdoc-builder.js +5 -0
- package/src/compact/mode-config.js +6 -6
- package/src/compact/split-declarations.js +2 -0
- package/src/compact/validate-pipeline.js +7 -8
- package/src/core/event-bus.js +2 -1
- package/src/core/file-walker.js +4 -0
- package/src/core/filters.js +6 -5
- package/src/core/graph-builder.js +4 -11
- package/src/core/parser.js +19 -29
- package/src/core/utils.js +2 -0
- package/src/lang/lang-sql.js +7 -20
- package/src/mcp/mcp-server.js +2 -3
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.js +13 -21
- package/src/network/backend-lifecycle.js +15 -18
- package/src/network/local-gateway.js +10 -22
- package/src/network/mdns.js +5 -11
- package/src/network/server.js +1 -2
- package/src/network/web-server.js +7 -33
- package/web/app.js +19 -14
- package/web/components/code-block.js +1 -0
- package/web/components/quick-open.js +1 -0
- package/web/dashboard-state.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +5 -4
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -4
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +2 -1
- package/web/panels/ProjectItem/ProjectItem.js +3 -4
- package/web/panels/ProjectItem/ProjectItem.tpl.js +2 -1
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +5 -4
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +2 -3
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +1 -0
- package/web/panels/ctx-panel.js +1 -0
- package/web/panels/dep-graph.js +1 -0
- package/web/panels/file-tree.js +4 -188
- package/web/panels/health-panel.js +1 -0
- package/web/panels/live-monitor.js +1 -0
- package/web/state.js +7 -10
package/src/compact/compact.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// @ctx .context/src/compact/compact.ctx
|
|
2
|
-
import{readFileSync as e,writeFileSync as t,
|
|
3
|
-
|
|
4
|
-
const n=r(e,a);o(n).isDirectory()?f.has(a)||s.push(...walkJSFiles(n,t)):d.has(c(a).toLowerCase())&&s.push(n)}}catch{}return s}
|
|
2
|
+
import{resolveCtxRelPath as resolveCtxPath}from"./ctx-resolver.js";import{walkJSFiles}from"../core/file-walker.js";import{readFileSync as e,writeFileSync as t,existsSync as s}from"fs";import{join as r,extname as c,relative as a,basename as i,dirname as l}from"path";import{minify as u}from"../../vendor/terser.mjs";
|
|
3
|
+
|
|
5
4
|
function addTopLevelNewlines(e){return e.replace(/;(import )/g,";\n$1").replace(/;(export )/g,";\n$1").replace(/\}(export )/g,"}\n$1").replace(/\}(function )/g,"}\n$1").replace(/\}(async function )/g,"}\n$1").replace(/\}(class )/g,"}\n$1").replace(/;(const |let |var )/g,";\n$1")}
|
|
6
|
-
|
|
7
|
-
const f=r(t,u,o);return s(f)?u+"/"+o:null}
|
|
5
|
+
|
|
8
6
|
async function compactFile(n,o){const s=e(n,"utf-8"),r=s.length;if(!s.trim())return{original:0,compacted:0};
|
|
9
7
|
const c=await u(s,{compress:{dead_code:!0,drop_console:!1,passes:1,reduce_funcs:!1,inline:!1},mangle:{keep_fnames:!0,module:!0},module:!0,output:{beautify:!1,comments:!1,semicolons:!0}});if(c.error)throw c.error;
|
|
10
8
|
let a=addTopLevelNewlines(c.code);if(o){const e=resolveCtxPath(n,o);e&&(a.startsWith("#!")?a=a.replace(/^(#![^\n]*\n)/,"$1// @ctx "+e+"\n"):a="// @ctx "+e+"\n"+a)}return t(n,a,"utf-8"),{original:r,compacted:a.length}}
|
package/src/compact/compress.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
// @ctx .context/src/compact/compress.ctx
|
|
2
|
-
import{readFileSync as e}from"fs";import{basename as t,extname as n}from"path";import{minify as a}from"../../vendor/terser.mjs";import{parse as s}from"../../vendor/acorn.mjs";import{simple as r}from"../../vendor/walk.mjs";
|
|
3
|
-
const o=new Set([".js",".mjs",".ts",".tsx"]);
|
|
4
|
-
|
|
5
|
-
let s="";if(t.start>0){const n=Math.max(0,t.start-500),a=e.slice(n,t.start).trimEnd().match(/\/\*\*[\s\S]*?\*\/\s*$/);if(a&&e.slice(n+a.index+a[0].length,t.start).split("\n").length<=3){const e=a[0].replace(/\/\*\*\s*\n?/,"").replace(/\s*\*\//,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"").trim()).filter(e=>e&&!e.startsWith("@")).join(" ").trim();e&&(s=e.length>80?e.slice(0,77)+"...":e)}}if("FunctionDeclaration"===n.type){const e=n.id?.name||"anonymous",t=n.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"...").join(","),r=`${n.async?"async ":""}${e}(${t})`;a.push(s?`${r}|${s}`:r)}if("ClassDeclaration"===n.type){const e=n.id?.name||"AnonymousClass",t=n.superClass?` extends ${n.superClass.name||"?"}`:"";a.push(`class ${e}${t}${s?"|"+s:""}`);for(const e of n.body.body)if("MethodDefinition"===e.type&&e.key?.name){const t=e.value.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"...").join(",");a.push(` .${e.key.name}(${t})`)}}if("VariableDeclaration"===n.type)for(const e of n.declarations)e.id?.name&&a.push(`${n.kind} ${e.id.name}${s?"|"+s:""}`)}})}catch(e){a.push(`PARSE_ERROR: ${e.message}`)}return a.join("\n")}
|
|
6
|
-
export async function compressFile(t,s={}){const{beautify:r=!0,legend:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let p;try{const e=await a(l,d);if(e.error)throw e.error;p=e.code}catch(e){p=l.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*/g,"").replace(/\n{3,}/g,"\n\n").trim()}// Join consecutive import lines into single line
|
|
10
|
-
p=p.replace(/(import\s.+?;)\n+(?=import\s)/g,"$1");const f=i?extractLegend(l,t):"",u=f?`/*\n${f}\n*/\n${p}`:p,y=estimateTokens(u);return{code:u,legend:f,original:m,compressed:y,savings:`${m>0?Math.round(100*(1-y/m)):0}%`}}
|
|
11
|
-
export async function editCompressed(t,n,r,o={}){const{beautify:i=!0,dryRun:c=!1}=o,l=e(t,"utf-8");
|
|
12
|
-
let m;try{m=s(l,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){throw new Error(`Failed to parse ${t}: ${e.message}`)}const d=findSymbolRange(m,l,n);if(!d)throw new Error(`Symbol "${n}" not found in ${t}`);
|
|
13
|
-
let p=l.slice(0,d.start)+r+l.slice(d.end);if(i)try{const e=await a(p,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!0,semicolons:!1}});e.code&&(p=e.code)}catch{}try{s(p,{ecmaVersion:"latest",sourceType:"module"})}catch(e){throw new Error(`Edit would create invalid syntax: ${e.message}`)}if(!c){const{writeFileSync:e}=await import("fs");e(t,p,"utf-8")}return{success:!0,file:t,symbol:n,oldRange:{start:d.start,end:d.end},newLength:r.length,...c?{dryRun:!0}:{}}}
|
|
14
|
-
function findSymbolRange(e,t,n){let a=null;return r(e,{FunctionDeclaration(e){e.id?.name===n&&(a={start:e.start,end:e.end,type:"FunctionDeclaration"})},ClassDeclaration(e){e.id?.name===n&&(a={start:e.start,end:e.end,type:"ClassDeclaration"})},VariableDeclaration(e){for(const t of e.declarations)t.id?.name===n&&(a={start:e.start,end:e.end,type:"VariableDeclaration"})},ExportNamedDeclaration(e){if(e.declaration){const t=e.declaration;(t.id?.name||t.declarations?.[0]?.id?.name)===n&&(a={start:e.start,end:e.end,type:"ExportNamedDeclaration"})}},ExportDefaultDeclaration(e){e.declaration?.id?.name===n&&(a={start:e.start,end:e.end,type:"ExportDefaultDeclaration"})}}),a}
|
|
2
|
+
import{estimateTokens as i}from"../core/utils.js";import{readFileSync as e}from"fs";import{basename as t,extname as n}from"path";import{minify as a}from"../../vendor/terser.mjs";import{parse as s}from"../../vendor/acorn.mjs";import{simple as r}from"../../vendor/walk.mjs";
|
|
3
|
+
const o=new Set([".js",".mjs",".ts",".tsx"]);
|
|
4
|
+
|
|
5
|
+
function c(e,n){const a=[];a.push(`--- ${t(n)} ---`);try{const t=s(e,{ecmaVersion:2022,sourceType:"module",locations:!0});r(t,{ExportNamedDeclaration(t){const n=t.declaration;if(!n)return;let s="";if(t.start>0){const n=Math.max(0,t.start-500),a=e.slice(n,t.start).trimEnd().match(/\/\*\*[\s\S]*?\*\/\s*$/);if(a&&e.slice(n+a.index+a[0].length,t.start).split("\n").length<=3){const e=a[0].replace(/\/\*\*\s*\n?/,"").replace(/\s*\*\//,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"").trim()).filter(e=>e&&!e.startsWith("@")).join(" ").trim();e&&(s=e.length>80?e.slice(0,77)+"...":e)}}if("FunctionDeclaration"===n.type){const e=n.id?.name||"anonymous",t=n.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"...").join(","),r=`${n.async?"async ":""}${e}(${t})`;a.push(s?`${r}|${s}`:r)}if("ClassDeclaration"===n.type){const e=n.id?.name||"AnonymousClass",t=n.superClass?` extends ${n.superClass.name||"?"}`:"";a.push(`class ${e}${t}${s?"|"+s:""}`);for(const e of n.body.body)if("MethodDefinition"===e.type&&e.key?.name){const t=e.value.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"...").join(",");a.push(` .${e.key.name}(${t})`)}}if("VariableDeclaration"===n.type)for(const e of n.declarations)e.id?.name&&a.push(`${n.kind} ${e.id.name}${s?"|"+s:""}`)}})}catch(e){a.push(`PARSE_ERROR: ${e.message}`)}return a.join("\n")}
|
|
6
|
+
export async function compressFile(t,s={}){const{beautify:r=!0,legend:l=!0}=s,m=n(t).toLowerCase();if(!o.has(m))throw new Error(`Unsupported file type: ${m}. Supported: ${[...o].join(", ")}`);const d=e(t,"utf-8"),p=i(d);if(!d.trim())return{code:"",legend:"",original:0,compressed:0,savings:"0%"};const u={compress:{dead_code:!0,drop_console:!1,passes:2},mangle:!1,module:!0,output:{beautify:r,comments:!1,semicolons:!r}};let f;try{const e=await a(d,u);if(e.error)throw e.error;f=e.code}catch(e){f=d.replace(/\/\*[\s\S]*?\*\//g,"").replace(/\/\/.*/g,"").replace(/\n{3,}/g,"\n\n").trim()}f=f.replace(/(import\s.+?;)\n+(?=import\s)/g,"$1");const y=l?c(d,t):"",h=y?`/*\n${y}\n*/\n${f}`:f,$=i(h);return{code:h,legend:y,original:p,compressed:$,savings:`${p>0?Math.round(100*(1-$/p)):0}%`}}
|
|
7
|
+
export async function editCompressed(t,n,r,o={}){const{beautify:i=!0,dryRun:c=!1}=o,m=e(t,"utf-8");let d;try{d=s(m,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){throw new Error(`Failed to parse ${t}: ${e.message}`)}const p=l(d,m,n);if(!p)throw new Error(`Symbol "${n}" not found in ${t}`);let u=m.slice(0,p.start)+r+m.slice(p.end);if(i)try{const e=await a(u,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!0,semicolons:!1}});e.code&&(u=e.code)}catch{}try{s(u,{ecmaVersion:"latest",sourceType:"module"})}catch(e){throw new Error(`Edit would create invalid syntax: ${e.message}`)}if(!c){const{writeFileSync:e}=await import("fs");e(t,u,"utf-8")}return{success:!0,file:t,symbol:n,oldRange:{start:p.start,end:p.end},newLength:r.length,...c?{dryRun:!0}:{}}}
|
|
8
|
+
function l(e,t,n){let a=null;return r(e,{FunctionDeclaration(e){e.id?.name===n&&(a={start:e.start,end:e.end,type:"FunctionDeclaration"})},ClassDeclaration(e){e.id?.name===n&&(a={start:e.start,end:e.end,type:"ClassDeclaration"})},VariableDeclaration(e){for(const t of e.declarations)t.id?.name===n&&(a={start:e.start,end:e.end,type:"VariableDeclaration"})},ExportNamedDeclaration(e){if(e.declaration){const t=e.declaration;(t.id?.name||t.declarations?.[0]?.id?.name)===n&&(a={start:e.start,end:e.end,type:"ExportNamedDeclaration"})}},ExportDefaultDeclaration(e){e.declaration?.id?.name===n&&(a={start:e.start,end:e.end,type:"ExportDefaultDeclaration"})}}),a}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @ctx .context/src/compact/ctx-resolver.ctx
|
|
2
|
+
import{readFileSync as t,existsSync as s}from"fs";import{join as i,basename as a,extname as c,dirname as p,relative as l}from"path";
|
|
3
|
+
export function resolveCtxPath(e,n){const o=a(n,c(n))+".ctx",r=p(n),d=i(e,".context",r,o);if(s(d))return d;const f=i(e,r,o);return s(f)?f:null}
|
|
4
|
+
export function resolveCtxRelPath(e,n){const o=l(n,e),r=a(o,c(o))+".ctx",u=p(o),d=i(n,".context",u,r);if(s(d))return".context/"+u+"/"+r;const f=i(n,u,r);return s(f)?u+"/"+r:null}
|
|
5
|
+
export function readCtxFile(e,n){const o=resolveCtxPath(e,n);return o?t(o,"utf-8"):null}
|
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
// @ctx .context/src/compact/ctx-to-jsdoc.ctx
|
|
2
|
-
import{readFileSync as t,writeFileSync as e,readdirSync as n,statSync as s,existsSync as o}from"fs";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let p=0,f=0;
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const s=
|
|
14
|
-
const o=
|
|
15
|
-
export function stripJSDoc(n,s={}){const{dryRun:o=!1}=s,r=walkJSFiles(n);
|
|
16
|
-
let i=0,l=0;
|
|
17
|
-
const p=[];for(const s of r){const r=t(s,"utf-8"),f=[];
|
|
18
|
-
let u,m=!1;try{c(r,{ecmaVersion:"latest",sourceType:"module",onComment:f}),m=!0}catch{}if(m){const t=f.filter(t=>"Block"===t.type&&t.value.startsWith("*")).sort((t,e)=>e.start-t.start);u=r;for(const{start:e,end:n}of t){let t=n;for(;t<u.length&&("\n"===u[t]||"\r"===u[t]);)t++;u=u.slice(0,e)+u.slice(t)}}else u=r.replace(/\/\*\*[\s\S]*?\*\/\s*\n?/g,"");
|
|
19
|
-
const d=u.replace(/\n{3,}/g,"\n\n"),h=r.length-d.length;h>0&&(i++,l+=h,p.push({file:a(n,s),saved:h}),o||e(s,d,"utf-8"))}return{files:r.length,stripped:i,savedBytes:l,dryRun:o,details:p}}
|
|
20
|
-
function walkJSFiles(t){const e=[];try{for(const o of n(t)){if(o.startsWith(".")&&"."!==o)continue;
|
|
21
|
-
const n=r(t,o);s(n).isDirectory()?f.has(o)||e.push(...walkJSFiles(n)):p.has(i(o).toLowerCase())&&e.push(n)}}catch{}return e}
|
|
22
|
-
function splitTopLevelParams(t){const e=[];
|
|
23
|
-
let n=0,s="";for(const o of t)if("{"===o||"<"===o||"("===o?n++:"}"!==o&&">"!==o&&")"!==o||n--,","===o&&0===n){const t=s.trim();t&&e.push(t),s=""}else s+=o;
|
|
24
|
-
const o=s.trim();return o&&e.push(o),e}
|
|
25
|
-
export function validateCtxContracts(e,n={}){const s=n.strict||!1,o=walkJSFiles(e),r=[];
|
|
26
|
-
let i=0;for(const n of o){const o=a(e,n),p=findCtxFile(o,e);if(!p)continue;i++;
|
|
27
|
-
const f=parseCtxFile(t(p,"utf-8"));
|
|
28
|
-
let u,m;try{u=t(n,"utf-8")}catch{continue}try{m=c(u,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{continue}const d=new Map;l(m,{FunctionDeclaration(t){t.id&&d.set(t.id.name,{paramCount:t.params.length,params:t.params.map(t=>"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&t.left?.name?t.left.name:"RestElement"===t.type&&t.argument?.name?t.argument.name:"ObjectPattern"===t.type?"options":"?"),async:t.async||!1,line:t.loc.start.line})}});
|
|
29
|
-
const h=new Set;l(m,{ExportNamedDeclaration(t){if(t.declaration?.id&&h.add(t.declaration.id.name),t.specifiers)for(const e of t.specifiers)h.add(e.exported.name)}});for(const t of f.functions){const e=d.get(t.name);if(!e){r.push({file:o,severity:t.exported?"error":"warning",message:`Function "${t.name}" in .ctx not found in source`});continue}const n=t.params?splitTopLevelParams(t.params):[];n.length!==e.paramCount&&r.push({file:o,severity:"error",message:`"${t.name}": .ctx has ${n.length} params, AST has ${e.paramCount}`});for(let s=0;s<Math.min(n.length,e.params.length);s++){const i=n[s].replace(/^\.\.\./,"").replace(/:.*/,"").replace(/=$/,""),a=e.params[s];i!==a&&"?"!==i&&"?"!==a&&r.push({file:o,severity:"warning",message:`"${t.name}" param ${s}: .ctx="${i}", AST="${a}"`})}const s=h.has(t.name);t.exported!==s&&r.push({file:o,severity:"warning",message:`"${t.name}": .ctx says ${t.exported?"exported":"private"}, AST says ${s?"exported":"private"}`}),d.delete(t.name)}if(s&&d.size>0)for(const[t]of d)r.push({file:o,severity:"info",message:`Function "${t}" in source (line ${d.get(t)?.line}) not documented in .ctx`})}const p=r.filter(t=>"error"===t.severity).length,f=r.filter(t=>"warning"===t.severity).length;return{files:i,violations:r,summary:{errors:p,warnings:f}}}
|
|
2
|
+
import{walkJSFiles}from"../core/file-walker.js";import{resolveCtxPath}from"./ctx-resolver.js";import{buildJSDocFromRaw as u}from"./jsdoc-builder.js";import{readFileSync as t,writeFileSync as e,readdirSync as n,statSync as s,existsSync as o}from"fs";
|
|
3
|
+
import{join as r,extname as i,relative as a}from"path";
|
|
4
|
+
import{parse as c}from"../../vendor/acorn.mjs";
|
|
5
|
+
import{simple as l}from"../../vendor/walk.mjs";
|
|
6
|
+
|
|
7
|
+
export function parseCtxFile(t){const e=t.split("\n"),n={file:null,functions:[]};for(const t of e){const e=t.match(/^--- (.+) ---$/);if(e){n.file=e[1];continue}const s=t.match(/^(export\s+)?(\w+)\(([^)]*)\)((?:→[^→|]+)*)(?:\|(.*))?$/);if(s){const[,t,e,o,r,i]=s;let a="";if(r){const t=r.split("→").filter(Boolean);t.length>0&&/^[A-Z]|^Promise|^Array|^Object|^string|^number|^boolean|^void|^null/.test(t[0])&&(a=t[0])}const c=i&&"{DESCRIBE}"!==i?i.trim():"";n.functions.push({name:e,params:o||"",exported:!!t,description:c,returns:a});continue}t.match(/^class\s+(\w+)/)}return n}
|
|
8
|
+
|
|
9
|
+
const m=(t,e)=>resolveCtxPath(e,t);
|
|
10
|
+
export function injectJSDoc(n,s={}){const{dryRun:o=!1}=s,r=n,i=d(n);let p=0,f=0;const h=[];for(const y of i){const g=a(r,y),x=m(g,r);if(!x){f++;continue}const $=parseCtxFile(t(x,"utf-8"));if(0===$.functions.length){f++;continue}let v,w=t(y,"utf-8"),S=!1,j=0;try{v=c(w,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{f++;continue}const C=[];function F(t){for(const e of v.body)if("ExportNamedDeclaration"===e.type&&e.declaration===t)return e.start;return t.start}l(v,{FunctionDeclaration(t){if(!t.id)return;const e=t.id.name,n=$.functions.find(t=>t.name===e);if(!n)return;const s=F(t);if(w.slice(0,s).trimEnd().endsWith("*/"))return;const o=u(n);C.push({position:s,jsdoc:o}),j++}}),C.sort((t,e)=>e.position-t.position);for(const{position:D,jsdoc:A}of C){const E=w.slice(0,D).lastIndexOf("\n")+1,R=w.slice(E,D).match(/^(\s*)/)?.[1]||"",T=A.split("\n").map(t=>R+t).join("\n")+"\n";w=w.slice(0,D)+T+w.slice(D),S=!0}S&&!o&&e(y,w,"utf-8"),j>0&&(p+=j,h.push({file:g,injected:j}))}return{files:i.length,injected:p,skipped:f,dryRun:o,details:h}}
|
|
11
|
+
export function stripJSDoc(n,s={}){const{dryRun:o=!1}=s,r=d(n);let i=0,l=0;const p=[];for(const s of r){const r=t(s,"utf-8"),f=[];let u,m=!1;try{c(r,{ecmaVersion:"latest",sourceType:"module",onComment:f}),m=!0}catch{}if(m){const t=f.filter(t=>"Block"===t.type&&t.value.startsWith("*")).sort((t,e)=>e.start-t.start);u=r;for(const{start:e,end:n}of t){let t=n;for(;t<u.length&&("\n"===u[t]||"\r"===u[t]);)t++;u=u.slice(0,e)+u.slice(t)}}else u=r.replace(/\/\*\*[\s\S]*?\*\/\s*\n?/g,"");const d=u.replace(/\n{3,}/g,"\n\n"),h=r.length-d.length;h>0&&(i++,l+=h,p.push({file:a(n,s),saved:h}),o||e(s,d,"utf-8"))}return{files:r.length,stripped:i,savedBytes:l,dryRun:o,details:p}}
|
|
12
|
+
const d=walkJSFiles;
|
|
13
|
+
function h(t){const e=[];let n=0,s="";for(const o of t)if("{"===o||"<"===o||"("===o?n++:"}"!==o&&">"!==o&&")"!==o||n--,","===o&&0===n){const t=s.trim();t&&e.push(t),s=""}else s+=o;const o=s.trim();return o&&e.push(o),e}
|
|
14
|
+
export function validateCtxContracts(e,n={}){const s=n.strict||!1,o=d(e),r=[];let i=0;for(const n of o){const o=a(e,n),p=m(o,e);if(!p)continue;i++;const f=parseCtxFile(t(p,"utf-8"));let u,d;try{u=t(n,"utf-8")}catch{continue}try{d=c(u,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{continue}const y=new Map;l(d,{FunctionDeclaration(t){t.id&&y.set(t.id.name,{paramCount:t.params.length,params:t.params.map(t=>"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&t.left?.name?t.left.name:"RestElement"===t.type&&t.argument?.name?t.argument.name:"ObjectPattern"===t.type?"options":"?"),async:t.async||!1,line:t.loc.start.line})}});const g=new Set;l(d,{ExportNamedDeclaration(t){if(t.declaration?.id&&g.add(t.declaration.id.name),t.specifiers)for(const e of t.specifiers)g.add(e.exported.name)}});for(const t of f.functions){const e=y.get(t.name);if(!e){r.push({file:o,severity:t.exported?"error":"warning",message:`Function "${t.name}" in .ctx not found in source`});continue}const n=t.params?h(t.params):[];n.length!==e.paramCount&&r.push({file:o,severity:"error",message:`"${t.name}": .ctx has ${n.length} params, AST has ${e.paramCount}`});for(let s=0;s<Math.min(n.length,e.params.length);s++){const i=n[s].replace(/^\.\.\./,"").replace(/:.*/,"").replace(/=$/,""),a=e.params[s];i!==a&&"?"!==i&&"?"!==a&&r.push({file:o,severity:"warning",message:`"${t.name}" param ${s}: .ctx="${i}", AST="${a}"`})}const s=g.has(t.name);t.exported!==s&&r.push({file:o,severity:"warning",message:`"${t.name}": .ctx says ${t.exported?"exported":"private"}, AST says ${s?"exported":"private"}`}),y.delete(t.name)}if(s&&y.size>0)for(const[t]of y)r.push({file:o,severity:"info",message:`Function "${t}" in source (line ${y.get(t)?.line}) not documented in .ctx`})}const p=r.filter(t=>"error"===t.severity).length,f=r.filter(t=>"warning"===t.severity).length;return{files:i,violations:r,summary:{errors:p,warnings:f}}}
|
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
// @ctx .context/src/compact/doc-dialect.ctx
|
|
2
|
-
import{readFileSync as t,readdirSync as e,existsSync as s,mkdirSync as n,writeFileSync as o,statSync as c}from"fs";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const h=i(t,".
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
function
|
|
16
|
-
function
|
|
17
|
-
const n=
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
function buildFileTemplate(t,e,s,n,o){const c=[`--- ${t} ---`,`@sig ${computeSignature(t,n)}`],i=o||new Map,r={};for(const e of n.functions||[])e.file===t&&(r[e.name]=e);
|
|
21
|
-
const a={};for(const e of n.classes||[])e.file===t&&(a[e.name]=e);for(const t of e){const e=s.reverseLegend[t.shortName]||t.shortName;if("C"===t.t){const n=a[e]||{},o=t.x?` extends ${t.x}`:"",r=t.m?.length||0,l=t.$?.length||0,h=[];r>0&&h.push(`${r}m`),l>0&&h.push(`${l}$`);
|
|
22
|
-
const u=i.get(e)||"{DESCRIBE}";if(c.push(`class ${e}${o}|${h.join(",")}|${u}`),t.m)for(const e of t.m){const t=s.reverseLegend[e]||e,n=i.get(t)||"{DESCRIBE}";c.push(` .${t}()|${n}`)}n.calls?.length>0&&c.push(` CALLS→${n.calls.slice(0,8).join(",")}`),n.dbReads?.length>0&&c.push(` R→${n.dbReads.join(",")}`),n.dbWrites?.length>0&&c.push(` W→${n.dbWrites.join(",")}`)}else if("F"===t.t){const s=r[e]||{},n=t.e?"export ":"",o=s.params?.length>0?s.params.join(","):"",a=s.returns?`→${s.returns}`:"",l=s.calls?.slice(0,6)||[],h=l.length>0?`→${l.join(",")}`:"",u=i.get(e)||"{DESCRIBE}";c.push(`${n}${e}(${o})${a}${h}|${u}`),s.dbReads?.length>0&&c.push(` R→${s.dbReads.join(",")}`),s.dbWrites?.length>0&&c.push(` W→${s.dbWrites.join(",")}`)}}const l=i.get("PATTERNS")||"{DESCRIBE}",h=i.get("EDGE_CASES")||"{DESCRIBE}";return c.push(`PATTERNS: ${l}`),c.push(`EDGE_CASES: ${h}`),c.join("\n").includes("{DESCRIBE}")&&c.splice(2,0,`@enrich: Replace each {DESCRIBE} below. Read ${t} for context.`," Rules: max 80ch, pipe|separated, abbrev (fn/ret/cfg/init/auth/db/msg)."," Save this file after filling all markers. Remove @enrich lines when done."),c.join("\n")}
|
|
23
|
-
export async function generateContextFiles(t,e,c,a={}){const{overwrite:l=!1,scope:h="all"}=a,f=i(e,".context"),p=[],m=[],d={};s(f)||n(f,{recursive:!0});
|
|
24
|
-
const $=i(f,"project.ctx");if(!s($)||l){const s=r(e),{stats:n}=t,c=[];n.files>0&&c.push(`${n.files} files`),n.classes>0&&c.push(`${n.classes} classes`),n.functions>0&&c.push(`${n.functions} functions`);
|
|
25
|
-
let i=[`=== PROJECT: ${s} ===`,"ARCH: {DESCRIBE}","FLOW: {DESCRIBE}",`STATS: ${c.join("|")}`].join("\n")+"\n";o($,i,"utf-8"),p.push("project.ctx"),d["project.ctx"]=i}else m.push("project.ctx");
|
|
26
|
-
const x={};for(const[e,s]of Object.entries(t.nodes)){const t=s.f;t&&(x[t]||(x[t]=[]),x[t].push({shortName:e,...s}))}let g=null;if("focus"===h)try{const t=u("git diff --name-only HEAD~5",{cwd:e,encoding:"utf-8"});g=new Set(t.split("\n").filter(t=>t.endsWith(".js")||t.endsWith(".mjs")||t.endsWith(".ts")).map(t=>t.trim()).filter(Boolean))}catch{g=null}else Array.isArray(h)&&(g=new Set(h));
|
|
27
|
-
const S=Object.entries(x).filter(([t])=>!g||g.has(t));for(let s=0;s<S.length;s+=5){const n=S.slice(s,s+5),o=await Promise.allSettled(n.map(([s,n])=>processFileCtx(s,n,t,c,f,e,l)));for(const t of o)if("fulfilled"===t.status&&t.value){const{action:e,path:s,template:n}=t.value;"created"===e?(p.push(s),d[s]=n):m.push(s)}}const j={created:p,skipped:m};return p.length>0&&(j.templates=d),j}
|
|
28
|
-
async function processFileCtx(e,c,h,u,f,g,S){const j=r(e,a(e))+".ctx",C=l(e),E=i(f,C),D=i(E,j),y=i(C,j),b=i(g,C,j);if((s(D)||s(b))&&!S)return{action:"skipped",path:y};
|
|
29
|
-
let R;s(E)||n(E,{recursive:!0});
|
|
30
|
-
const W=s(b)?b:s(D)?D:null;if(W&&S)try{R=parseCtxDescriptions(t(W,"utf-8"))}catch{}let w=buildFileTemplate(e,c,h,u,R);o(D,w+"\n","utf-8");try{const n=i(g,e);if(s(n)&&e.endsWith(".js")){const s=t(n,"utf-8"),o=m(s),c=d(s,e),i=$(s,e,"tests"),r=x(s,e);p(f,e,{sig:o,contentHash:o,complexity:c,undocumented:i,jsdocIssues:r})}}catch{}const P=r(e,a(e))+".ctx.md",v=i(E,P);if(!s(v)){const t=`# ${r(e)}\n\n## Notes\n\n## TODO\n\n## Decisions\n`;o(v,t,"utf-8")}return{action:"created",path:y,template:w}}
|
|
2
|
+
import{readFileSync as t,readdirSync as e,existsSync as s,mkdirSync as n,writeFileSync as o,statSync as c}from"fs";
|
|
3
|
+
import{join as i,basename as r,extname as a,dirname as l,relative as h}from"path";
|
|
4
|
+
import{execSync as f}from"child_process";
|
|
5
|
+
import{createHash as u}from"crypto";
|
|
6
|
+
import{writeCache as p,computeContentHash as m}from"../analysis/analysis-cache.js";
|
|
7
|
+
import{analyzeComplexityFile as d}from"../analysis/complexity.js";
|
|
8
|
+
import{checkUndocumentedFile as $}from"../analysis/undocumented.js";
|
|
9
|
+
import{checkJSDocFile as g}from"../analysis/jsdoc-checker.js";
|
|
10
|
+
export function generateDocDialect(t,e){const s=[],n=e?r(e):"unknown";s.push(`=== PROJECT: ${n} ===`);const{stats:o}=t,c=[];o.files>0&&c.push(`${o.files} files`),o.classes>0&&c.push(`${o.classes} classes`),o.functions>0&&c.push(`${o.functions} functions`),o.tables>0&&c.push(`${o.tables} tables`),s.push(`STATS: ${c.join("|")}`);const i=t.edges.filter(t=>"→"===t[1]).length,a=t.edges.filter(t=>"R→"===t[1]).length,l=t.edges.filter(t=>"W→"===t[1]).length,h=[];i>0&&h.push(`${i} calls`),a>0&&h.push(`${a} db_reads`),l>0&&h.push(`${l} db_writes`),h.length>0&&s.push(`EDGES: ${h.join("|")}`),t.orphans.length>0&&s.push(`ORPHANS: ${t.orphans.join(",")}`),Object.keys(t.duplicates).length>0&&s.push(`DUPLICATES: ${Object.keys(t.duplicates).join(",")}`);const f={};for(const[e,s]of Object.entries(t.nodes)){const t=s.f||"?";f[t]||(f[t]=[]),f[t].push({shortName:e,...s})}for(const[e,n]of Object.entries(f)){if("?"===e)continue;s.push(""),s.push(`--- ${e} ---`);for(const e of n){const n=t.reverseLegend[e.shortName]||e.shortName;if("C"===e.t){const o=e.x?` extends ${e.x}`:"",c=e.m?.length||0,i=e.$?.length||0,r=[];if(c>0&&r.push(`${c}m`),i>0&&r.push(`${i}$`),s.push(`class ${n}${o}|${r.join(",")}`),e.m)for(const n of e.m){const e=t.reverseLegend[n]||n;s.push(` .${e}`)}}else if("F"===e.t){const t=e.e?"export ":"";s.push(`${t}${n}()`)}else if("T"===e.t){const t=e.cols?.join(",")||"";s.push(`TABLE ${n}|${t}`)}}const o=t.edges.filter(s=>{const n=t.nodes[s[0]];return n?.f===e});if(o.length>0){const e=o.map(e=>{const s=t.reverseLegend[e[0]]||e[0],n=t.reverseLegend[e[2]?.split(".")[0]]||e[2];return`${s}${e[1]}${n}`}),n=[...new Set(e)];n.length<=5?s.push(`CALLS: ${n.join("|")}`):s.push(`CALLS: ${n.slice(0,5).join("|")}|+${n.length-5} more`)}}return s.join("\n")}
|
|
11
|
+
function x(t,n){const o=[];if(!s(t))return o;try{for(const s of e(t)){const e=i(t,s);try{c(e).isDirectory()?o.push(...x(e,n)):(s.endsWith(".ctx")||s.endsWith(".ctx.md"))&&o.push({relPath:h(n,e),absPath:e})}catch{}}}catch{}return o}
|
|
12
|
+
function j(t,e){const n=r(e,a(e))+".ctx",o=l(e),c=i(t,o,n);if(s(c))return c;const h=i(t,".context",o,n);return s(h)?h:null}
|
|
13
|
+
function S(t,e){const n=r(e,a(e))+".ctx.md",o=l(e),c=i(t,o,n);if(s(c))return c;const h=i(t,".context",o,n);return s(h)?h:null}
|
|
14
|
+
export function readContextDocs(e){const s=i(e,".context"),n=new Map;for(const{relPath:e,absPath:o}of x(s,s))try{const s=t(o,"utf-8").trim();s&&n.set(e,s)}catch{}const o=x(e,e).filter(t=>!t.relPath.startsWith(".context"));for(const{relPath:e,absPath:s}of o)try{const o=t(s,"utf-8").trim();o&&n.set(e,o)}catch{}const c=[...n.keys()].sort((t,e)=>"project.ctx"===t?-1:"project.ctx"===e?1:r(t).startsWith("_")?-1:r(e).startsWith("_")?1:t.localeCompare(e)),a=c,l=c.map(t=>n.get(t)),h=n.has("project.ctx");return{combined:l.join("\n\n"),files:a,hasProjectCtx:h}}
|
|
15
|
+
export function getProjectDocs(e,s,n={}){const{file:o}=n,c=readContextDocs(s);if(o){const n=j(s,o);let c="";if(n)c=t(n,"utf-8").trim();else{const t=generateDocDialect(e,s),n=`--- ${o} ---`,i=t.indexOf(n);if(-1===i)return`No documentation found for: ${o}`;const r=t.indexOf("\n---",i+n.length);c=-1===r?t.slice(i).trim():t.slice(i,r).trim()}const i=S(s,o);if(i){const e=t(i,"utf-8").trim();e&&!e.match(/^#[^\n]*\n+## Notes\n+## TODO\n+## Decisions\s*$/)&&(c+="\n\n"+e)}return c}if(c.combined){const t=generateDocDialect(e,s);return`${c.combined}\n\n${t}`}return generateDocDialect(e,s)}
|
|
16
|
+
function E(t,e){const s=[];for(const n of(e.functions||[]).filter(e=>e.file===t))s.push(`F:${n.exported?"e":""}:${n.name}(${n.params?.join(",")||""})`);for(const n of(e.classes||[]).filter(e=>e.file===t)){const t=n.methods?.sort().join(",")||"";s.push(`C:${n.name}:${n.extends||""}:${t}`)}return s.sort(),u("md5").update(s.join("|")).digest("hex").slice(0,8)}
|
|
17
|
+
export function checkStaleness(e,s){const n=i(e,".context"),o=[];let c=0,r=0;for(const{relPath:e,absPath:i}of x(n,n))try{const e=t(i,"utf-8"),n=e.match(/@sig\s+(\w+)/),a=e.match(/^--- (.+) ---/m);if(!n||!a){r++;continue}E(a[1],s)!==n[1]?o.push(a[1]):c++}catch{}for(const{absPath:n}of x(e,e).filter(t=>!t.relPath.startsWith(".context")))try{const e=t(n,"utf-8"),i=e.match(/@sig\s+(\w+)/),a=e.match(/^--- (.+) ---/m);if(!i||!a){r++;continue}E(a[1],s)!==i[1]?o.push(a[1]):c++}catch{}return{stale:o,fresh:c,unknown:r}}
|
|
18
|
+
export async function generateContextFiles(t,e,c,a={}){const{overwrite:l=!1,scope:h="all"}=a,u=i(e,".context"),p=[],m=[],d={};s(u)||n(u,{recursive:!0});const $=i(u,"project.ctx");if(!s($)||l){const s=r(e),{stats:n}=t,c=[];n.files>0&&c.push(`${n.files} files`),n.classes>0&&c.push(`${n.classes} classes`),n.functions>0&&c.push(`${n.functions} functions`);let i=[`=== PROJECT: ${s} ===`,"ARCH: {DESCRIBE}","FLOW: {DESCRIBE}",`STATS: ${c.join("|")}`].join("\n")+"\n";o($,i,"utf-8"),p.push("project.ctx"),d["project.ctx"]=i}else m.push("project.ctx");const g={};for(const[e,s]of Object.entries(t.nodes)){const t=s.f;t&&(g[t]||(g[t]=[]),g[t].push({shortName:e,...s}))}let x=null;if("focus"===h)try{const t=f("git diff --name-only HEAD~5",{cwd:e,encoding:"utf-8"});x=new Set(t.split("\n").filter(t=>t.endsWith(".js")||t.endsWith(".mjs")||t.endsWith(".ts")).map(t=>t.trim()).filter(Boolean))}catch{x=null}else Array.isArray(h)&&(x=new Set(h));const j=Object.entries(g).filter(([t])=>!x||x.has(t));for(let s=0;s<j.length;s+=5){const n=j.slice(s,s+5),o=await Promise.allSettled(n.map(([s,n])=>y(s,n,t,c,u,e,l)));for(const t of o)if("fulfilled"===t.status&&t.value){const{action:e,path:s,template:n}=t.value;"created"===e?(p.push(s),d[s]=n):m.push(s)}}const S={created:p,skipped:m};return p.length>0&&(S.templates=d),S}
|
|
19
|
+
async function y(e,c,h,f,u,x,j){const S=r(e,a(e))+".ctx",y=l(e),D=i(u,y),C=i(D,S),b=i(y,S),R=i(x,y,S);if((s(C)||s(R))&&!j)return{action:"skipped",path:b};let W;s(D)||n(D,{recursive:!0});const P=s(R)?R:s(C)?C:null;if(P&&j)try{W=function(t){const e=new Map;for(const s of t.split("\n")){const t=s.trim();if(!t||t.startsWith("---")||t.startsWith("@sig")||t.startsWith("@enrich")||t.startsWith("Rules:")||t.startsWith("Save this")||t.startsWith("CALLS→")||t.startsWith("R→")||t.startsWith("W→"))continue;const n=t.match(/^(PATTERNS|EDGE_CASES):\s*(.+)/);if(n&&"{DESCRIBE}"!==n[2]){e.set(n[1],n[2]);continue}const o=t.match(/^(?:export\s+)?(?:class\s+)?\.?([\w]+)\([^)]*\)(?:[^|]*?)\|(.+)/);if(o&&"{DESCRIBE}"!==o[2]){e.set(o[1],o[2]);continue}const c=t.match(/^class\s+([\w]+)[^|]*\|[^|]*\|(.+)/);c&&"{DESCRIBE}"!==c[2]&&e.set(c[1],c[2])}return e}(t(P,"utf-8"))}catch{}let w=function(t,e,s,n,o){const c=[`--- ${t} ---`,`@sig ${E(t,n)}`],i=o||new Map,r={};for(const e of n.functions||[])e.file===t&&(r[e.name]=e);const a={};for(const e of n.classes||[])e.file===t&&(a[e.name]=e);for(const t of e){const e=s.reverseLegend[t.shortName]||t.shortName;if("C"===t.t){const n=a[e]||{},o=t.x?` extends ${t.x}`:"",r=t.m?.length||0,l=t.$?.length||0,h=[];r>0&&h.push(`${r}m`),l>0&&h.push(`${l}$`);const f=i.get(e)||"{DESCRIBE}";if(c.push(`class ${e}${o}|${h.join(",")}|${f}`),t.m)for(const e of t.m){const t=s.reverseLegend[e]||e,n=i.get(t)||"{DESCRIBE}";c.push(` .${t}()|${n}`)}n.calls?.length>0&&c.push(` CALLS→${n.calls.slice(0,8).join(",")}`),n.dbReads?.length>0&&c.push(` R→${n.dbReads.join(",")}`),n.dbWrites?.length>0&&c.push(` W→${n.dbWrites.join(",")}`)}else if("F"===t.t){const s=r[e]||{},n=t.e?"export ":"",o=s.params?.length>0?s.params.join(","):"",a=s.returns?`→${s.returns}`:"",l=s.calls?.slice(0,6)||[],h=l.length>0?`→${l.join(",")}`:"",f=i.get(e)||"{DESCRIBE}";c.push(`${n}${e}(${o})${a}${h}|${f}`),s.dbReads?.length>0&&c.push(` R→${s.dbReads.join(",")}`),s.dbWrites?.length>0&&c.push(` W→${s.dbWrites.join(",")}`)}}const l=i.get("PATTERNS")||"{DESCRIBE}",h=i.get("EDGE_CASES")||"{DESCRIBE}";return c.push(`PATTERNS: ${l}`),c.push(`EDGE_CASES: ${h}`),c.join("\n").includes("{DESCRIBE}")&&c.splice(2,0,`@enrich: Replace each {DESCRIBE} below. Read ${t} for context.`," Rules: max 80ch, pipe|separated, abbrev (fn/ret/cfg/init/auth/db/msg)."," Save this file after filling all markers. Remove @enrich lines when done."),c.join("\n")}(e,c,h,f,W);o(C,w+"\n","utf-8");try{const n=i(x,e);if(s(n)&&e.endsWith(".js")){const s=t(n,"utf-8"),o=m(s),c=d(s,e),i=$(s,e,"tests"),r=g(s,e);p(u,e,{sig:o,contentHash:o,complexity:c,undocumented:i,jsdocIssues:r})}}catch{}const A=r(e,a(e))+".ctx.md",L=i(D,A);if(!s(L)){const t=`# ${r(e)}\n\n## Notes\n\n## TODO\n\n## Decisions\n`;o(L,t,"utf-8")}return{action:"created",path:b,template:w}}
|
package/src/compact/expand.js
CHANGED
|
@@ -1,37 +1,11 @@
|
|
|
1
1
|
// @ctx .context/src/compact/expand.ctx
|
|
2
|
-
import{readFileSync as t,writeFileSync as e,mkdirSync as n,existsSync as s
|
|
3
|
-
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
function
|
|
12
|
-
function parseCtxNames(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;
|
|
13
|
-
const s=t.slice(0,n),r=t.slice(n+1),o=new Map;for(const t of r.split(",")){const e=t.trim().split("=");2===e.length&&o.set(e[0].trim(),e[1].trim())}o.size>0&&e.set(s,o)}}}return e}
|
|
14
|
-
function restoreNames(t,e,n,s,r){const o=new Map,a=[];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)o.set(e.local.name,e.imported.name),a.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&&(o.set(e.local.name,n),a.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&&(o.set(e.local.name,n),a.push({s:e.start,e:e.end,n:"* as "+n}))}for(const[t,e]of s)o.set(t,e);if(r.has("__top__"))for(const[t,e]of r.get("__top__"))o.set(t,e);
|
|
15
|
-
const i=new Set(o.values());for(const[t]of[...o])i.has(t)&&o.delete(t);
|
|
16
|
-
const c=[],p=[],l=[];function collectLocals(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}
|
|
17
|
-
function collectLocalDecls(t){const e=[];return f(t,{VariableDeclarator(t){t.id&&"Identifier"===t.id.type&&e.push(t.id)}}),e}const pf=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 o=n.get(t.id.name);if(o?.params)for(let t=0;t<Math.min(e.length,o.params.length);t++)e[t]!==o.params[t].name&&s.set(e[t],o.params[t].name);
|
|
18
|
-
const a=r.get(t.id.name);if(a)for(const[t,e]of a)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 o=collectLocals(t.body);for(const t of e)o.add(t);
|
|
19
|
-
const a=t.id?.name&&r.get(t.id.name);for(const e of collectLocalDecls(t.body)){const t=a?.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:o,r:s})};f(e,{FunctionDeclaration:pf,FunctionExpression:pf,ArrowFunctionExpression:pf});for(const t of e.body)if("VariableDeclaration"===t.type)for(const e of t.declarations)e.id&&"Identifier"===e.id.type&&o.has(e.id.name)&&l.push({s:e.id.start,e:e.id.end,n:o.get(e.id.name)});
|
|
20
|
-
const m=[...a,...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;
|
|
21
|
-
const r=t.name;
|
|
22
|
-
let a=null;for(const e of c)t.start>=e.s&&t.end<=e.e&&(!a||e.e-e.s<a.e-a.s)&&(a=e);a&&a.p.has(r)?a.r.has(r)&&m.push({s:t.start,e:t.end,n:a.r.get(r)}):o.has(r)&&m.push({s:t.start,e:t.end,n:o.get(r)})}}),m.sort((t,e)=>e.s-t.s);
|
|
23
|
-
let d=t;for(const t of m)d=d.slice(0,t.s)+t.n+d.slice(t.e);return d}
|
|
24
|
-
export async function expandFile(e,n,s={}){const{indentLevel:r=2}=s,o=t(e,"utf-8");if(!o.trim())return{code:"",injected:0,original:0,decompiled:0};
|
|
25
|
-
let a;try{a=(await m(o,{compress:!1,mangle:!1,module:!0,output:{beautify:!0,comments:!1,indent_level:r,semicolons:!0}})).code||o}catch{a=o}{const t=a.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)}a=e.join("\n")}a=a.replace(/^( +)/gm,t=>{const e=Math.floor(t.length/r);return"\t".repeat(e)+" ".repeat(t.length%r)});
|
|
26
|
-
const i=parseCtxSignatures(n),c=parseCtxVars(n),p=parseCtxNames(n);
|
|
27
|
-
let l;try{l=d(a,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{return{code:a,injected:0,original:o.length,decompiled:a.length}}try{a=restoreNames(a,l,i,c,p),l=d(a,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch{}if(0===i.size)return{code:a,injected:0,original:o.length,decompiled:a.length};
|
|
28
|
-
const u=[];f(l,{ExportNamedDeclaration(t){const e=t.declaration;if(e){if("FunctionDeclaration"===e.type&&e.id?.name){const n=i.get(e.id.name);n&&u.push({pos:t.start,jsdoc:generateJSDoc(n)})}if("ClassDeclaration"===e.type&&e.id?.name){const n=i.get(e.id.name);n&&n.description&&u.push({pos:t.start,jsdoc:`/**\n * ${n.description}\n */`})}}},FunctionDeclaration(t){if(!t.id?.name)return;
|
|
29
|
-
const e=i.get(t.id.name);e&&!e.exported&&u.push({pos:t.start,jsdoc:generateJSDoc(e)})},ClassDeclaration(t){if(!t.id?.name)return;
|
|
30
|
-
const e=i.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);
|
|
31
|
-
let h=a,y=0;for(const{pos:t,jsdoc:e}of u){const n=h.lastIndexOf("\n",t-1),s=-1===n?0:n+1,r=h.slice(s,t).match(/^(\s*)/)?.[1]||"",o=e.split("\n").map(t=>r+t).join("\n");h=h.slice(0,t)+o+"\n"+h.slice(t),y++}return{code:h,injected:y,original:o.length,decompiled:h.length}}const h=new Set(["node_modules",".git","vendor",".context","dev-docs",".agent",".agents",".expanded","web"]),y=new Set([".js",".mjs"]);function walkJSFiles(t,e=t){const n=[];try{for(const s of r(t)){if(s.startsWith(".")&&"."!==s)continue;
|
|
32
|
-
const r=a(t,s);o(r).isDirectory()?h.has(s)||n.push(...walkJSFiles(r,e)):y.has(c(s).toLowerCase())&&n.push(r)}}catch{}return n}
|
|
33
|
-
function resolveCtx(e,n){const r=i(n,c(n))+".ctx",o=p(n),l=a(e,o,r);if(s(l))return t(l,"utf-8");
|
|
34
|
-
const m=a(e,".context",o,r);return s(m)?t(m,"utf-8"):null}
|
|
35
|
-
export async function expandProject(t,r={}){const{dryRun:o=!1,outputDir:i}=r,c=i||a(t,".expanded"),m=a(t,"src");if(!s(m))return{error:"No src/ directory found",files:0};
|
|
36
|
-
const d=walkJSFiles(m,t),f=[],u=[];
|
|
37
|
-
let h=0;for(const r of d){const i=l(t,r);try{const l=resolveCtx(t,i),m=await expandFile(r,l);if(!o){const t=a(c,i),r=p(t);s(r)||n(r,{recursive:!0}),e(t,m.code,"utf-8")}f.push({file:i,injected:m.injected,original:m.original,decompiled:m.decompiled}),h+=m.injected}catch(t){u.push({file:i,error:t.message})}}return{outputDir:c,files:f.length,totalJSDocInjected:h,fileDetails:f,errors:u.length>0?u:void 0,dryRun:o}}
|
|
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
|
+
|
|
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}}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export async function expandProject(t,o={}){const{dryRun:r=!1,outputDir:a}=o,c=a||i(t,".expanded"),m=i(t,"src");if(!s(m))return{error:"No src/ directory found",files:0};const d=walkJSFiles(m,t),f=[],u=[];let h=0;for(const o of d){const a=l(t,o);try{const l=$(t,a),m=await expandFile(o,l);if(!r){const t=i(c,a),o=p(t);s(o)||n(o,{recursive:!0}),e(t,m.code,"utf-8")}f.push({file:a,injected:m.injected,original:m.original,decompiled:m.decompiled}),h+=m.injected}catch(t){u.push({file:a,error:t.message})}}return{outputDir:c,files:f.length,totalJSDocInjected:h,fileDetails:f,errors:u.length>0?u:void 0,dryRun:r}}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// @ctx .context/src/compact/jsdoc-builder.ctx
|
|
2
|
+
export function parseCtxParams(t){return t&&t.trim()?t.split(",").map(t=>{const e=t.trim();if(!e)return null;const n=e.match(/^(\w+)(\?)?(?::(\w[\w<>\[\]|.]*))?(\=)?$/);if(n)return{name:n[1],type:n[3]||null,optional:!(!n[2]&&!n[4])};const s=e.match(/^(\w+)(=)?$/);return s?{name:s[1],type:null,optional:!!s[2]}:"..."===e?{name:"args",type:null,rest:!0}:{name:e.replace(/[=?:].*/g,""),type:null}}).filter(Boolean):[]}
|
|
3
|
+
function sanitize(t){return t.replace(/\*\//g,"*\\/")}
|
|
4
|
+
export function buildJSDocBlock(t){const e=["/**"];if(t.description&&"{DESCRIBE}"!==t.description&&e.push(` * ${sanitize(t.description)}`),t.params&&t.params.length>0&&!t.params.every(p=>!p.type&&p.name.length<=1))for(const n of t.params){const t=n.type||"*",s=n.optional?`[${n.name}]`:n.name;e.push(` * @param {${t}} ${s}`)}return t.returnType&&e.push(` * @returns {${t.returnType}}`),e.push(" */"),e.join("\n")}
|
|
5
|
+
export function buildJSDocFromRaw(t){const e=["/**"];if(t.description?e.push(` * ${t.description}`):e.push(` * ${t.name}`),t.params){const n=t.params.split(",").map(t=>t.trim()).filter(Boolean);if(!n.every(t=>{const m=t.match(/^(\.\.\.)?(\w+)(?::(\w))?/);return m&&!m[3]&&m[2].length<=1}))for(const t of n){const n=t.match(/^(\.\.\.)?\s*(\w+)(?::(\w+(?:<[^>]+>)?))?(=)?$/);if(n){const[,t,s,o,r]=n,i=o||"*",a=t||"";r?e.push(` * @param {${i}} [${a}${s}]`):e.push(` * @param {${i}} ${a}${s}`)}}}return t.returns&&e.push(` * @returns {${t.returns}}`),e.push(" */"),e.join("\n")}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// @ctx .context/src/compact/mode-config.ctx
|
|
2
|
-
import{readFileSync as e,writeFileSync as t,existsSync as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export function
|
|
6
|
-
const f={...getConfig(e),...
|
|
2
|
+
import{readFileSync as e,writeFileSync as t,existsSync as r,mkdirSync as o}from"fs";
|
|
3
|
+
import{join as i,dirname as c}from"path";
|
|
4
|
+
const n=".context/config.json",a={mode:2,beautify:!0,autoValidate:!1,stripJSDoc:!1};
|
|
5
|
+
export function getConfig(t){const o=i(t,n);if(!r(o))return{...a};try{const t=e(o,"utf-8"),r=JSON.parse(t),i={...a,...r};return[1,2].includes(i.mode)||(i.mode=2),i}catch{return{...a}}}
|
|
6
|
+
export function setConfig(e,a){const s=i(e,n),d=c(s);r(d)||o(d,{recursive:!0});const f={...getConfig(e),...a};if(![1,2].includes(f.mode))throw new Error(`Invalid mode: ${f.mode}. Valid: 1 (compact), 2 (full)`);return t(s,JSON.stringify(f,null,2)+"\n","utf-8"),{saved:!0,path:s,config:f}}
|
|
7
7
|
export function getModeDescription(e){switch(e){case 1:return"Compact — minified source + .expanded/ cache for human review (recommended)";case 2:return"Full — formatted source, agents use compressed view for reading";default:return`Unknown mode: ${e}`}}
|
|
8
|
-
export function getModeWorkflow(e){switch(e){case 1:return{read:"Read .js files directly (compact source = fewer tokens)",write:"Write .js files directly (compact output = cheaper output tokens)",review:".expanded/ cache with restored names + JSDoc for human review",validate:"Run validate_pipeline → contracts + expand + AST verify",expand:"Run expand_project to regenerate .expanded/ from compact + .ctx",migrate:"Run compact-migrate to convert formatted source → compact"};case 2:return{read:"Use get_compressed_file for token-efficient reading",write:"Use edit_compressed(path, symbol, code) for AST-safe editing",review:"Read source files directly (already formatted)",validate:"Run validate-ctx to check .ctx ↔ AST consistency"};default:return{read:"N/A",write:"N/A",review:"N/A",validate:"N/A"}}}
|
|
8
|
+
export function getModeWorkflow(e){switch(e){case 1:return{read:"Read .js files directly (compact source = fewer tokens)",write:"Write .js files directly (compact output = cheaper output tokens)",edit:"Write .js files directly — terser-style compact code",review:".expanded/ cache with restored names + JSDoc for human review",validate:"Run validate_pipeline → contracts + expand + AST verify",expand:"Run expand_project to regenerate .expanded/ from compact + .ctx",migrate:"Run compact-migrate to convert formatted source → compact"};case 2:return{read:"Use get_compressed_file for token-efficient reading",write:"Use edit_compressed(path, symbol, code) for AST-safe editing",edit:"Use edit_compressed(path, symbol, code) for AST-safe editing",review:"Read source files directly (already formatted)",validate:"Run validate-ctx to check .ctx ↔ AST consistency"};default:return{read:"N/A",write:"N/A",edit:"N/A",review:"N/A",validate:"N/A"}}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @ctx .context/src/compact/split-declarations.ctx
|
|
2
|
+
import{parse as t}from"../../vendor/acorn.mjs";export function splitDeclarations(r){try{const e=t(r,{ecmaVersion:"latest",sourceType:"module"});if(e.body.length<=3)return r;const n=[];for(const t of e.body)n.push(t.start);n.sort((t,r)=>t-r);const o=[];let s=0;for(const t of n)t>0&&(o.push(r.slice(s,t).trimEnd()),s=t);return o.push(r.slice(s).trimEnd()),o.filter(t=>t).join("\n")}catch{return r}}export function isSingleLineBlob(r){try{if(r.split("\n").filter(t=>!t.startsWith("// @ctx ")&&!t.startsWith("#!")).length>2)return!1;return t(r,{ecmaVersion:"latest",sourceType:"module"}).body.length>3}catch{return!1}}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// @ctx .context/src/compact/validate-pipeline.ctx
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
let
|
|
8
|
-
|
|
9
|
-
const i=r(p,"src",e);if(o(i)){const e=s(i,"utf-8");y.full+=estimateTokens(e)}else y.full+=estimateTokens(a)}y.full>0&&(y.savings=Math.round(100*(1-y.compact/y.full))+"%")}const w=Date.now()-d,h=m.summary?.errors||0,v=S.failed,g=h+v;return{status:0===g?"PASS":"FAIL",duration:`${w}ms`,contracts:{files:m.files,errors:m.summary?.errors||0,warnings:m.summary?.warnings||0,violations:m.violations?.slice(0,20)},expand:u?{files:u.files,jsdocInjected:u.totalJSDocInjected,errors:u.errors}:null,astVerify:{passed:S.passed,failed:S.failed,errors:S.errors.slice(0,10)},tokens:y,summary:{totalErrors:g,contractErrors:h,astErrors:v,filesProcessed:u?.files||0,jsdocInjected:u?.totalJSDocInjected||0,tokenSavings:y.savings}}}
|
|
2
|
+
import{walkJSFiles as S}from"../core/file-walker.js";import{estimateTokens as W}from"../core/utils.js";import{readFileSync as e,readdirSync as t,statSync as s,existsSync as n,writeFileSync as i,mkdirSync as o}from"fs";import{join as r,extname as a,relative as c,dirname as l,basename as d}from"path";import{execSync as f}from"node:child_process";import{parse as m}from"../../vendor/acorn.mjs";import{validateCtxContracts as p}from"./ctx-to-jsdoc.js";import{expandProject as u}from"./expand.js";import{parseProject as h}from"../core/parser.js";import{getGraph as g}from"../mcp/tools.js";import{generateContextFiles as x}from"./doc-dialect.js";import{simple as y}from"../../vendor/walk.mjs";import{splitDeclarations as j,isSingleLineBlob as w}from"./split-declarations.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function D(t,s){const i=[];for(const o of s){const s=c(t,o),f=e(o,"utf-8"),p=f.split("\n");let u=p[0]?.trim();u?.startsWith("#!")&&(u=p[1]?.trim()),u?.startsWith("// @ctx ")||i.push({file:s,rule:"missing-ctx-header",message:"Missing // @ctx header comment"});const h=r(t,".context",c(t,l(o))),g=d(o,a(o))+".ctx",x=r(h,g);if(n(x)){const t=e(x,"utf-8"),n=(t.match(/\{DESCRIBE\}/g)||[]).length,o=(t.match(/\(auto-documented\)/g)||[]).length;o>0&&i.push({file:s,rule:"incomplete-ctx",message:o+" placeholder (auto-documented) description(s) — needs real descriptions"}),n>0&&i.push({file:s,rule:"incomplete-ctx",message:`${n} unfilled {DESCRIBE} marker(s) in .ctx — descriptions required for full restore`});try{const e=m(f,{ecmaVersion:"latest",sourceType:"module"}),n=new Set;for(const t of e.body){if("ExportNamedDeclaration"===t.type){if(t.declaration?.id&&n.add(t.declaration.id.name),t.declaration?.declarations)for(const e of t.declaration.declarations)e.id?.name&&n.add(e.id.name);if(t.specifiers)for(const e of t.specifiers)n.add(e.exported.name)}"ExportDefaultDeclaration"===t.type&&t.declaration?.id&&n.add(t.declaration.id.name)}if(n.size>0){const e=[];for(const s of n)new RegExp("(?:^|[^a-zA-Z_$])"+s+"(?:[^a-zA-Z0-9_$]|$)").test(t)||e.push(s);e.length>0&&i.push({file:s,rule:"incomplete-ctx",message:`${e.length} export(s) not in .ctx: ${e.slice(0,5).join(", ")}${e.length>5?"...":""}`})}}catch{}w(f)&&i.push({file:s,rule:"single-line-blob",message:"Multiple declarations on 1 line — each should be on separate line"});const r=t.match(/^export\s+\w+\([^)]*\)/gm)||[];for(const e of r){const t=e.match(/\(([^)]*)\)/);if(t&&t[1]){const n=t[1].split(",").map(e=>e.trim().replace(/[?=:].*/,"")),o=n.filter(e=>1===e.length);o.length>0&&i.push({file:s,rule:"ctx-minified-params",message:"signature has minified params: "+e.slice(0,60)})}}const a=t.match(/^export\s+\w+\([^)]+\)/gm)||[];for(const e of a){const t=e.match(/\(([^)]*)\)/);if(t&&t[1]){const n=t[1].split(",").map(e=>e.trim()),o=n.filter(e=>e&&!e.includes(":"));o.length>0&&i.push({file:s,rule:"ctx-untyped-params",message:"params without types: "+o.join(", ")+" in "+e.slice(0,50)})}}const c=[];try{if(y(m(f,{ecmaVersion:"latest",sourceType:"module"}),{FunctionDeclaration(e){e.id&&c.push(e.id.name)}}),c.length>0){const e=c.filter(e=>!new RegExp("(?:^|[^a-zA-Z_$])"+e.replace(/\$/g,"\\$")+"(?:[^a-zA-Z0-9_$]|$)").test(t));e.length>0&&i.push({file:s,rule:"ctx-coverage",message:e.length+"/"+c.length+" function(s) not in .ctx: "+e.slice(0,5).join(", ")+(e.length>5?"...":"")})}}catch{}}else i.push({file:s,rule:"missing-ctx-file",message:`No .ctx file at .context/${c(t,l(o))}/${g}`});const j=p.filter(e=>e.trim().startsWith("import "));if(j.length>1){const e=p.findIndex(e=>e.trim().startsWith("import ")),t=p.length-1-[...p].reverse().findIndex(e=>e.trim().startsWith("import "));t>e&&i.push({file:s,rule:"multi-line-imports",message:`${j.length} import statements on separate lines (should be single line in compact mode)`})}const $=[];for(let e=0;e<p.length;e++){const t=p[e];t.length>0&&(" "===t[0]||"\t"===t[0])&&$.push(e+1)}$.length>0&&i.push({file:s,rule:"indented-lines",message:`${$.length} indented line(s) — compact mode requires no indentation (lines: ${$.slice(0,5).join(", ")}${$.length>5?"...":""})`});const v=f.replace(/\/\/ @ctx [^\n]*/,"").replace(/import[^;]+;/g,"").replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`(?:[^`\\]|\\.)*`/g,"``"),S=/(?:const|let|var|function)\s+([a-zA-Z_$][a-zA-Z0-9_$]{2,})/g,W=new Set;let D;for(;null!==(D=S.exec(v));)W.add(D[1]);const E=new Set;try{const e=m(f,{ecmaVersion:"latest",sourceType:"module"});for(const t of e.body){if("ExportNamedDeclaration"===t.type){if(t.declaration?.id&&E.add(t.declaration.id.name),t.declaration?.declarations)for(const e of t.declaration.declarations)e.id?.name&&E.add(e.id.name);if(t.specifiers)for(const e of t.specifiers)E.add(e.exported.name)}"ExportDefaultDeclaration"===t.type&&t.declaration?.id&&E.add(t.declaration.id.name)}}catch{}for(const e of E)W.delete(e);if(W.size>3){const e=[...W];i.push({file:s,rule:"long-names",message:`${e.length} unminified identifiers (non-export): ${e.slice(0,5).join(", ")}${e.length>5?"...":""}`})}}return i}
|
|
6
|
+
async function E(t,s){const{minify:f}=await import("../vendor/terser.mjs".replace("../","../../"));try{const e=await g(t),s=await h(t);await x(e,t,s,{overwrite:!0})}catch{}let m=0;for(const p of s){const s=c(t,p),u=e(p,"utf-8"),h=u.split("\n");let g=!1,x=u;const y=a(p),$=d(p,y),v=l(p),S=".context/"+c(t,v)+"/"+$+".ctx",W=r(t,S);n(W)||(o(l(W),{recursive:!0}),i(W,"# "+$+"\nContext for "+s+"\n"));let D=h[0]?.trim();const E=D?.startsWith("#!");if(E&&(D=h[1]?.trim()),!D?.startsWith("// @ctx ")){const e="// @ctx "+S;x=E?h[0]+"\n"+e+"\n"+h.slice(1).join("\n"):e+"\n"+x,g=!0}const z=x.split("\n"),A=[],_=[];for(const e of z)e.trim().startsWith("import ")||e.trim().startsWith("import{")?A.push(e.trim()):(A.length>0&&(_.push(A.join("")),A.length=0),_.push(e));A.length>0&&_.push(A.join(""));const b=_.join("\n");b!==x&&(x=b,g=!0);const I=x.split("\n").some(e=>e.length>0&&(" "===e[0]||"\t"===e[0])),C=x.replace(/\/\/ @ctx [^\n]*/,"").replace(/import[^;]+;/g,"").replace(/"(?:[^"\\]|\\.)*"/g,'""').replace(/'(?:[^'\\]|\\.)*'/g,"''").replace(/`(?:[^`\\]|\\.)*`/g,"``"),Z=/(?:const|let|var|function)\s+([a-zA-Z_$][a-zA-Z0-9_$]{2,})/g,k=new Set;let F;for(;null!==(F=Z.exec(C));)k.add(F[1]);const P=k.size>3;if(I||P)try{const e=x.split("\n").filter(e=>e.startsWith("// @ctx ")||e.startsWith("#!"))[0]||"",t=await f(x,{compress:!0,mangle:!0,module:!0});t.code&&(x=(e?e+"\n":"")+j(t.code),g=!0)}catch{}if(!g&&w(x)){const e=x.split("\n").filter(e=>e.startsWith("// @ctx ")||e.startsWith("#!")),t=x.split("\n").filter(e=>!e.startsWith("// @ctx ")&&!e.startsWith("#!")).join("\n"),s=j(t);s!==t&&(x=e.join("\n")+"\n"+s,g=!0)}g&&(i(p,x),m++)}return{fixed:m,total:s.length}}
|
|
7
|
+
export async function validatePipeline(t,s={}){const{strict:o=!1,skipDecompile:a=!1,fix:l=!1}=s,d=Date.now(),h=p(t,{strict:o});let g=null;a||(g=await u(t));const x=r(t,".expanded"),y={passed:0,failed:0,errors:[]};if(n(x)){const t=S(x);for(const s of t){const t=c(x,s);try{const t=e(s,"utf-8");m(t,{ecmaVersion:"latest",sourceType:"module"}),y.passed++}catch(e){y.failed++,y.errors.push({file:t,error:e.message,line:e.loc?.line})}}}const j=r(t,"src"),w={compact:0,full:0,savings:"0%"};if(n(j)&&n(x)){const t=S(j);for(const s of t){const t=c(j,s),i=e(s,"utf-8");w.compact+=W(i);const o=r(x,"src",t);if(n(o)){const t=e(o,"utf-8");w.full+=W(t)}else w.full+=W(i)}w.full>0&&(w.savings=Math.round(100*(1-w.compact/w.full))+"%")}const $=function(t){try{const s=f("git ls-files --cached --others --exclude-standard",{cwd:t,encoding:"utf-8"}),o=r(t,".pgignore");let a=[];if(n(o))a=e(o,"utf-8").split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#"));else{try{i(o,"# Third-party vendored code\nvendor/\n\n# Generated files\n.context/\n.expanded/\n")}catch{}a=["vendor/",".context/",".expanded/"]}return s.split("\n").filter(e=>e&&(e.endsWith(".js")||e.endsWith(".mjs"))&&!a.some(t=>t.endsWith("/")?e.startsWith(t):e===t||e.includes("/"+t))).map(e=>r(t,e))}catch{return[]}}(t);let v=null;l&&(v=await E(t,$));const z=D(t,$),A=Date.now()-d,_=h.summary?.errors||0,b=y.failed,I=z.length,C=_+b+I;return{status:0===C?"PASS":"FAIL",duration:`${A}ms`,contracts:{files:h.files,errors:h.summary?.errors||0,warnings:h.summary?.warnings||0,violations:h.violations?.slice(0,20)},expand:g?{files:g.files,jsdocInjected:g.totalJSDocInjected,errors:g.errors}:null,astVerify:{passed:y.passed,failed:y.failed,errors:y.errors.slice(0,10)},style:{files:$.length,issues:z.length,details:z.slice(0,30)},fix:v,tokens:w,summary:{totalErrors:C,contractErrors:_,astErrors:b,styleErrors:I,filesProcessed:g?.files||0,jsdocInjected:g?.totalJSDocInjected||0,tokenSavings:w.savings}}}
|
|
8
|
+
export{E as fixCompactStyle};
|
package/src/core/event-bus.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ctx .context/src/core/event-bus.ctx
|
|
2
2
|
import{EventEmitter as o}from"node:events";
|
|
3
|
-
const t=new o;
|
|
3
|
+
const t=new o;
|
|
4
|
+
t.setMaxListeners(50);
|
|
4
5
|
export function emitToolCall(o,e){t.emit("tool:call",{type:"tool_call",tool:o,args:e,ts:Date.now()})}
|
|
5
6
|
export function emitToolResult(o,e,l,n,s){t.emit("tool:result",{type:"tool_result",tool:o,args:e,duration_ms:n,success:s,result_keys:l?Object.keys(l):[],ts:Date.now()})}
|
|
6
7
|
export function onToolCall(o){t.on("tool:call",o)}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// @ctx .context/src/core/file-walker.ctx
|
|
2
|
+
import{readdirSync as n,statSync as o}from"fs";import{join as r,extname as c}from"path";
|
|
3
|
+
const f=new Set(["node_modules",".git","vendor",".context","dev-docs",".agent",".agents",".expanded","web"]),d=new Set([".js",".mjs"]);
|
|
4
|
+
export function walkJSFiles(e,t=e){const s=[];try{for(const a of n(e)){if(a.startsWith(".")&&"."!==a)continue;const n=r(e,a);o(n).isDirectory()?f.has(a)||s.push(...walkJSFiles(n,t)):d.has(c(a).toLowerCase())&&s.push(n)}}catch{}return s}
|
package/src/core/filters.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ctx .context/src/core/filters.ctx
|
|
2
|
-
import{readFileSync as e,existsSync as t}from"fs";
|
|
2
|
+
import{readFileSync as e,existsSync as t}from"fs";
|
|
3
|
+
import{join as r}from"path";
|
|
3
4
|
const i=["node_modules","dist","build","coverage",".next",".nuxt",".output","__pycache__",".cache",".turbo","out"],n=["*.test.js","*.spec.js","*.min.js","*.bundle.js","*.d.ts",".project-graph-cache.json"];
|
|
4
5
|
let s={excludeDirs:[...i],excludePatterns:[...n],includeHidden:!1,useGitignore:!0,gitignorePatterns:[]};
|
|
5
6
|
export function getFilters(){return{...s}}
|
|
@@ -8,7 +9,7 @@ export function addExcludes(e){return s.excludeDirs=[...new Set([...s.excludeDir
|
|
|
8
9
|
export function removeExcludes(e){return s.excludeDirs=s.excludeDirs.filter(t=>!e.includes(t)),getFilters()}
|
|
9
10
|
export function resetFilters(){return s={excludeDirs:[...i],excludePatterns:[...n],includeHidden:!1,useGitignore:!0,gitignorePatterns:[]},getFilters()}
|
|
10
11
|
export function parseGitignore(i){const n=r(i,".gitignore");if(!t(n))return[];try{const t=e(n,"utf-8").split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")).map(e=>e.replace(/\/$/,""));return s.gitignorePatterns=t,t}catch(e){return[]}}
|
|
11
|
-
export function shouldExcludeDir(e,t=""){if(!s.includeHidden&&e.startsWith("."))return!0;if(s.excludeDirs.includes(e))return!0;if(s.useGitignore)for(const r of s.gitignorePatterns)if(
|
|
12
|
-
export function shouldExcludeFile(e,t=""){for(const t of s.excludePatterns)if(
|
|
13
|
-
function
|
|
14
|
-
function
|
|
12
|
+
export function shouldExcludeDir(e,t=""){if(!s.includeHidden&&e.startsWith("."))return!0;if(s.excludeDirs.includes(e))return!0;if(s.useGitignore)for(const r of s.gitignorePatterns)if(o(r,e,t))return!0;return!1}
|
|
13
|
+
export function shouldExcludeFile(e,t=""){for(const t of s.excludePatterns)if(u(t,e))return!0;if(s.useGitignore)for(const r of s.gitignorePatterns)if(o(r,e,t))return!0;return!1}
|
|
14
|
+
function u(e,t){const r=e.replace(/\./g,"\\.").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(t)}
|
|
15
|
+
function o(e,t,r){return e===t||(e.includes("*")?u(e,t):!!(r?`${r}/${t}`:t).includes(e))}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
// @ctx .context/src/core/graph-builder.ctx
|
|
2
|
-
export function minifyLegend(
|
|
3
|
-
function
|
|
4
|
-
const t=e.
|
|
5
|
-
export function
|
|
6
|
-
const r=new Set;for(const e of f.edges){const s=e[2].split(".")[0];r.add(s)}for(const e of Object.keys(f.nodes))r.has(e)||"F"!==f.nodes[e].t||f.nodes[e].e||f.orphans.push(c[e]);
|
|
7
|
-
const l=Object.create(null);for(const e of s)for(const s of e.methods||[])l[s]||(l[s]=[]),l[s].push(`${e.name}:${e.line}`);for(const[e,s]of Object.entries(l))s.length>1&&(f.duplicates[e]=s);return f}
|
|
8
|
-
export function createSkeleton(e,s=null){const t={},o={};for(const[s,n]of Object.entries(e.legend)){const c=e.nodes[n];if(c&&"C"===c.t){const e=c.m?.length||0,f=c.$?.length||0;if(0===e&&0===f)continue;t[n]=s;
|
|
9
|
-
const r={m:e};f>0&&(r.$=f),c.f&&(r.f=c.f),o[n]=r}}const n={};for(const[s,o]of Object.entries(e.legend)){const c=e.nodes[o];if("F"===c?.t&&c.e){t[o]=s;
|
|
10
|
-
const e=c.f||"?";n[e]||(n[e]=[]),n[e].push(o)}}const c=new Set;for(const e of Object.values(o))e.f&&c.add(e.f);for(const e of Object.keys(n))c.add(e);
|
|
11
|
-
const f={};for(const s of e.files||[]){if(c.has(s))continue;
|
|
12
|
-
const e=s.lastIndexOf("/"),t=e>=0?s.slice(0,e+1):"./",o=e>=0?s.slice(e+1):s;f[t]||(f[t]=[]),f[t].push(o)}const r={v:e.v,L:t,s:e.stats,n:o,X:n,e:e.edges.length,o:e.orphans.length,d:Object.keys(e.duplicates).length};if(Object.keys(f).length>0&&(r.f=f),s&&s.length>0){const t=new Set(e.files||[]),o=s.filter(e=>!t.has(e));if(o.length>0){const e={};for(const s of o){const t=s.lastIndexOf("/"),o=t>=0?s.slice(0,t+1):"./",n=t>=0?s.slice(t+1):s;e[o]||(e[o]=[]),e[o].push(n)}r.a=e}}return r}
|
|
2
|
+
export function minifyLegend(s){const t={},o=new Set;for(const n of s){let s=e(n),c=1;for(;o.has(s);)s=e(n)+c,c++;o.add(s),t[n]=s}return t}
|
|
3
|
+
function e(e){const s=e.replace(/[a-z]/g,"");if(s.length>=2)return s.slice(0,3);const t=e.match(/[A-Z]/g);return t&&t.length>0?e[0].toLowerCase()+t[0]:e.slice(0,2)}
|
|
4
|
+
export function buildGraph(e){const s=e.classes||[],t=e.functions||[],o=[...s.map(e=>e.name),...t.map(e=>e.name),...s.flatMap(e=>e.methods||[])],n=minifyLegend([...new Set(o)]),c=Object.fromEntries(Object.entries(n).map(([e,s])=>[s,e])),f={v:1,legend:n,reverseLegend:c,stats:{files:(e.files||[]).length,classes:s.length,functions:t.length,tables:(e.tables||[]).length},nodes:{},edges:[],orphans:[],duplicates:{},files:e.files||[]};for(const e of s){const s=n[e.name];f.nodes[s]={t:"C",x:e.extends||void 0,m:(e.methods||[]).map(e=>n[e]||e),$:(e.properties||[]).length?e.properties:void 0,i:e.imports?.length?e.imports:void 0,f:e.file||void 0};for(const t of e.calls||[])if(t.includes(".")){const[e,o]=t.split(".");if(n[e]){const t=[s,"→",`${n[e]}.${n[o]||o}`];f.edges.push(t)}}else if(n[t]){const e=[s,"→",n[t]];f.edges.push(e)}}for(const e of t){const s=n[e.name];f.nodes[s]={t:"F",e:e.exported,f:e.file||void 0};for(const t of e.dbReads||[])f.edges.push([s,"R→",t]);for(const t of e.dbWrites||[])f.edges.push([s,"W→",t])}for(const e of s){const s=n[e.name];for(const t of e.dbReads||[])f.edges.push([s,"R→",t]);for(const t of e.dbWrites||[])f.edges.push([s,"W→",t])}for(const s of e.tables||[])f.nodes[s.name]={t:"T",cols:s.columns.map(e=>e.name),f:s.file||void 0};const l=new Set;for(const e of f.edges){const s=e[2].split(".")[0];l.add(s)}for(const e of Object.keys(f.nodes))l.has(e)||"F"!==f.nodes[e].t||f.nodes[e].e||f.orphans.push(c[e]);const i=Object.create(null);for(const e of s)for(const s of e.methods||[])i[s]||(i[s]=[]),i[s].push(`${e.name}:${e.line}`);for(const[e,s]of Object.entries(i))s.length>1&&(f.duplicates[e]=s);return f}
|
|
5
|
+
export function createSkeleton(e,s=null){const t={},o={};for(const[s,n]of Object.entries(e.legend)){const c=e.nodes[n];if(c&&"C"===c.t){const e=c.m?.length||0,f=c.$?.length||0;if(0===e&&0===f)continue;t[n]=s;const l={m:e};f>0&&(l.$=f),c.f&&(l.f=c.f),o[n]=l}}const n={};for(const[s,o]of Object.entries(e.legend)){const c=e.nodes[o];if("F"===c?.t&&c.e){t[o]=s;const e=c.f||"?";n[e]||(n[e]=[]),n[e].push(o)}}const c=new Set;for(const e of Object.values(o))e.f&&c.add(e.f);for(const e of Object.keys(n))c.add(e);const f={};for(const s of e.files||[]){if(c.has(s))continue;const e=s.lastIndexOf("/"),t=e>=0?s.slice(0,e+1):"./",o=e>=0?s.slice(e+1):s;f[t]||(f[t]=[]),f[t].push(o)}const l={v:e.v,L:t,s:e.stats,n:o,X:n,e:e.edges.length,o:e.orphans.length,d:Object.keys(e.duplicates).length};if(Object.keys(f).length>0&&(l.f=f),s&&s.length>0){const t=new Set(e.files||[]),o=s.filter(e=>!t.has(e));if(o.length>0){const e={};for(const s of o){const t=s.lastIndexOf("/"),o=t>=0?s.slice(0,t+1):"./",n=t>=0?s.slice(t+1):s;e[o]||(e[o]=[]),e[o].push(n)}l.a=e}}return l}
|