project-graph-mcp 2.1.2 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/package.json +1 -1
  2. package/rules/test-rules.json +15 -0
  3. package/src/.project-graph-cache.json +1 -1
  4. package/src/analysis/analysis-cache.js +3 -1
  5. package/src/analysis/complexity.js +9 -13
  6. package/src/analysis/custom-rules.js +16 -35
  7. package/src/analysis/db-analysis.js +2 -6
  8. package/src/analysis/dead-code.js +8 -18
  9. package/src/analysis/full-analysis.js +9 -17
  10. package/src/analysis/jsdoc-checker.js +11 -23
  11. package/src/analysis/jsdoc-generator.js +8 -9
  12. package/src/analysis/similar-functions.js +8 -15
  13. package/src/analysis/test-annotations.js +12 -20
  14. package/src/analysis/type-checker.js +5 -7
  15. package/src/analysis/undocumented.js +10 -13
  16. package/src/cli/cli-handlers.js +4 -3
  17. package/src/compact/ai-context.js +2 -2
  18. package/src/compact/compact-migrate.js +8 -16
  19. package/src/compact/compact.js +3 -5
  20. package/src/compact/compress.js +7 -13
  21. package/src/compact/ctx-resolver.js +5 -0
  22. package/src/compact/ctx-to-jsdoc.js +13 -28
  23. package/src/compact/doc-dialect.js +18 -29
  24. package/src/compact/expand.js +10 -36
  25. package/src/compact/jsdoc-builder.js +5 -0
  26. package/src/compact/mode-config.js +6 -6
  27. package/src/compact/split-declarations.js +2 -0
  28. package/src/compact/validate-pipeline.js +7 -8
  29. package/src/core/event-bus.js +2 -1
  30. package/src/core/file-walker.js +4 -0
  31. package/src/core/filters.js +6 -5
  32. package/src/core/graph-builder.js +4 -11
  33. package/src/core/parser.js +19 -29
  34. package/src/core/utils.js +2 -0
  35. package/src/lang/lang-sql.js +7 -20
  36. package/src/mcp/mcp-server.js +2 -3
  37. package/src/mcp/tool-defs.js +1 -1
  38. package/src/mcp/tools.js +13 -21
  39. package/src/network/backend-lifecycle.js +15 -18
  40. package/src/network/local-gateway.js +10 -22
  41. package/src/network/mdns.js +5 -11
  42. package/src/network/server.js +1 -2
  43. package/src/network/web-server.js +7 -33
  44. package/web/app.js +19 -14
  45. package/web/components/code-block.js +1 -0
  46. package/web/components/quick-open.js +1 -0
  47. package/web/dashboard-state.js +1 -0
  48. package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
  49. package/web/panels/ActionBoard/ActionBoard.js +5 -4
  50. package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
  51. package/web/panels/EventItem/EventItem.css.js +1 -0
  52. package/web/panels/EventItem/EventItem.js +4 -4
  53. package/web/panels/EventItem/EventItem.tpl.js +1 -0
  54. package/web/panels/ProjectItem/ProjectItem.css.js +2 -1
  55. package/web/panels/ProjectItem/ProjectItem.js +3 -4
  56. package/web/panels/ProjectItem/ProjectItem.tpl.js +2 -1
  57. package/web/panels/ProjectList/ProjectList.css.js +1 -0
  58. package/web/panels/ProjectList/ProjectList.js +5 -4
  59. package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
  60. package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
  61. package/web/panels/SettingsPanel/SettingsPanel.js +2 -3
  62. package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
  63. package/web/panels/code-viewer.js +1 -0
  64. package/web/panels/ctx-panel.js +1 -0
  65. package/web/panels/dep-graph.js +1 -0
  66. package/web/panels/file-tree.js +4 -188
  67. package/web/panels/health-panel.js +1 -0
  68. package/web/panels/live-monitor.js +1 -0
  69. package/web/state.js +7 -10
@@ -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";import{join as i,basename as r,extname as a,dirname as l,relative as h}from"path";import{execSync as u}from"child_process";import{createHash as f}from"crypto";import{writeCache as p,computeContentHash as m}from"../analysis/analysis-cache.js";import{analyzeComplexityFile as d}from"../analysis/complexity.js";import{checkUndocumentedFile as $}from"../analysis/undocumented.js";import{checkJSDocFile as x}from"../analysis/jsdoc-checker.js";
3
- 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("|")}`);
4
- 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(",")}`);
5
- const u={};for(const[e,s]of Object.entries(t.nodes)){const t=s.f||"?";u[t]||(u[t]=[]),u[t].push({shortName:e,...s})}for(const[e,n]of Object.entries(u)){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")}
6
- function walkCtxFiles(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(...walkCtxFiles(e,n)):(s.endsWith(".ctx")||s.endsWith(".ctx.md"))&&o.push({relPath:h(n,e),absPath:e})}catch{}}}catch{}return o}
7
- function resolveCtxPath(t,e){const n=r(e,a(e))+".ctx",o=l(e),c=i(t,o,n);if(s(c))return c;
8
- const h=i(t,".context",o,n);return s(h)?h:null}
9
- function resolveCtxMdPath(t,e){const n=r(e,a(e))+".ctx.md",o=l(e),c=i(t,o,n);if(s(c))return c;
10
- const h=i(t,".context",o,n);return s(h)?h:null}
11
- export function readContextDocs(e){const s=i(e,".context"),n=new Map;for(const{relPath:e,absPath:o}of walkCtxFiles(s,s))try{const s=t(o,"utf-8").trim();s&&n.set(e,s)}catch{}const o=walkCtxFiles(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}}
12
- export function getProjectDocs(e,s,n={}){const{file:o}=n,c=readContextDocs(s);if(o){const n=resolveCtxPath(s,o);
13
- 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}`;
14
- const r=t.indexOf("\n---",i+n.length);c=-1===r?t.slice(i).trim():t.slice(i,r).trim()}const i=resolveCtxMdPath(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)}
15
- function computeSignature(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(),f("md5").update(s.join("|")).digest("hex").slice(0,8)}
16
- function parseCtxDescriptions(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;
17
- 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}
18
- export function checkStaleness(e,s){const n=i(e,".context"),o=[];
19
- let c=0,r=0;for(const{relPath:e,absPath:i}of walkCtxFiles(n,n))try{const e=t(i,"utf-8"),n=e.match(/@sig\s+(\w+)/),a=e.match(/^--- (.+) ---/m);if(!n||!a){r++;continue}computeSignature(a[1],s)!==n[1]?o.push(a[1]):c++}catch{}for(const{absPath:n}of walkCtxFiles(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}computeSignature(a[1],s)!==i[1]?o.push(a[1]):c++}catch{}return{stale:o,fresh:c,unknown:r}}
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}}
@@ -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,readdirSync as r,statSync as o}from"fs";import{join as a,basename as i,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";function parseCtxSignatures(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;
3
- 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 r=t.match(/^\s+\.(\w+)\(([^)]*)\)\|?(.*)$/);if(r){e.set(r[1],{type:"method",params:parseCtxParams(r[2]),description:r[3]?.trim()||""});continue}const o=t.match(/^(export\s+)?(\w+)\(([^)]*)\)(→[^|]*)?\|(.*)$/);if(o){const t=o[2],n=o[3],s=o[4]||"",r=(o[5]||"").split("|"),a=r[0]?.trim()||"";e.set(t,{type:"function",params:parseCtxParams(n),returnType:extractReturnType(s),description:a,exported:!!o[1]});continue}}return e}
4
- function parseCtxParams(t){return t&&t.trim()?t.split(",").map(t=>{const e=t.trim();if(!e)return null;
5
- const n=e.match(/^(\w+)(\?)?(?::(\w[\w<>\[\]|.]*))?(\=)?$/);if(n)return{name:n[1],type:n[3]||null,optional:!(!n[2]&&!n[4])};
6
- 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):[]}
7
- function extractReturnType(t){if(!t)return null;
8
- const e=t.match(/^→([A-Z][\w<>\[\]|]*)/);return e?e[1]:null}
9
- function sanitizeJSDocText(t){return t.replace(/\*\//g,"*\\/")}
10
- function generateJSDoc(t){const e=["/**"];if(t.description&&"{DESCRIBE}"!==t.description&&e.push(` * ${sanitizeJSDocText(t.description)}`),t.params&&t.params.length>0)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")}
11
- function parseCtxVars(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}
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 d,mkdirSync as o}from"fs";import{join as a,dirname as n}from"path";
3
- const r=".context/config.json",c={mode:2,beautify:!0,autoValidate:!1,stripJSDoc:!1};
4
- export function getConfig(t){const o=a(t,r);if(!d(o))return{...c};try{const t=e(o,"utf-8"),d=JSON.parse(t),s={...c,...d};if(![1,2].includes(s.mode)){s.mode=2}return s}catch{return{...c}}}
5
- export function setConfig(e,c){const i=a(e,r),s=n(i);d(s)||o(s,{recursive:!0});
6
- const f={...getConfig(e),...c};if(![1,2].includes(f.mode))throw new Error(`Invalid mode: ${f.mode}. Valid: 1 (compact), 2 (full)`);return t(i,JSON.stringify(f,null,2)+"\n","utf-8"),{saved:!0,path:i,config:f}}
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 s,readdirSync as e,statSync as t,existsSync as o}from"fs";import{join as r,extname as a,relative as n}from"path";import{parse as i}from"../../vendor/acorn.mjs";import{validateCtxContracts as c}from"./ctx-to-jsdoc.js";import{expandProject as l}from"./expand.js";
3
- const f=new Set([".js",".mjs"]),d=new Set(["node_modules",".git","vendor",".context","dev-docs",".agent",".agents",".expanded","web"]);function walkJSFiles(s){const o=[];try{for(const n of e(s)){if(n.startsWith(".")&&"."!==n)continue;
4
- const e=r(s,n);t(e).isDirectory()?d.has(n)||o.push(...walkJSFiles(e)):f.has(a(n).toLowerCase())&&o.push(e)}}catch{}return o}
5
- function estimateTokens(s){return Math.ceil(s.length/4)}
6
- export async function validatePipeline(e,t={}){const{strict:a=!1,skipDecompile:f=!1}=t,d=Date.now(),m=c(e,{strict:a});
7
- let u=null;f||(u=await l(e));
8
- const p=r(e,".expanded"),S={passed:0,failed:0,errors:[]};if(o(p)){const e=walkJSFiles(p);for(const t of e){const e=n(p,t);try{const e=s(t,"utf-8");i(e,{ecmaVersion:"latest",sourceType:"module"}),S.passed++}catch(s){S.failed++,S.errors.push({file:e,error:s.message,line:s.loc?.line})}}}const j=r(e,"src"),y={compact:0,full:0,savings:"0%"};if(o(j)&&o(p)){const e=walkJSFiles(j);for(const t of e){const e=n(j,t),a=s(t,"utf-8");y.compact+=estimateTokens(a);
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};
@@ -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;t.setMaxListeners(50);
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}
@@ -1,5 +1,6 @@
1
1
  // @ctx .context/src/core/filters.ctx
2
- import{readFileSync as e,existsSync as t}from"fs";import{join as r}from"path";
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(matchGitignorePattern(r,e,t))return!0;return!1}
12
- export function shouldExcludeFile(e,t=""){for(const t of s.excludePatterns)if(matchWildcard(t,e))return!0;if(s.useGitignore)for(const r of s.gitignorePatterns)if(matchGitignorePattern(r,e,t))return!0;return!1}
13
- function matchWildcard(e,t){const r=e.replace(/\./g,"\\.").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(t)}
14
- function matchGitignorePattern(e,t,r){return e===t||(e.includes("*")?matchWildcard(e,t):!!(r?`${r}/${t}`:t).includes(e))}
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(e){const s={},t=new Set;for(const o of e){let e=createShortName(o),n=1;for(;t.has(e);)e=createShortName(o)+n,n++;t.add(e),s[o]=e}return s}
3
- function createShortName(e){const s=e.replace(/[a-z]/g,"");if(s.length>=2)return s.slice(0,3);
4
- const t=e.match(/[A-Z]/g);return t&&t.length>0?e[0].toLowerCase()+t[0]:e.slice(0,2)}
5
- 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};
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}
@@ -1,31 +1,21 @@
1
1
  // @ctx .context/src/core/parser.ctx
2
- import{readFileSync as e,readdirSync as t,statSync as s,existsSync as n}from"fs";import{join as r,relative as o,resolve as a}from"path";import{parse as i}from"../../vendor/acorn.mjs";import*as l from"../../vendor/walk.mjs";import{shouldExcludeDir as c,shouldExcludeFile as p,parseGitignore as u}from"./filters.js";import{parseTypeScript as f}from"../lang/lang-typescript.js";import{parsePython as d}from"../lang/lang-python.js";import{parseGo as m}from"../lang/lang-go.js";import{parseSQL as h,extractSQLFromString as y,isSQLString as g}from"../lang/lang-sql.js";
2
+ import{readFileSync as e,readdirSync as s,statSync as t,existsSync as n}from"fs";
3
+ import{join as r,relative as o,resolve as i}from"path";
4
+ import{parse as a}from"../../vendor/acorn.mjs";
5
+ import*as c from"../../vendor/walk.mjs";
6
+ import{shouldExcludeDir as l,shouldExcludeFile as p,parseGitignore as u}from"./filters.js";
7
+ import{parseTypeScript as f}from"../lang/lang-typescript.js";
8
+ import{parsePython as d}from"../lang/lang-python.js";
9
+ import{parseGo as m}from"../lang/lang-go.js";
10
+ import{parseSQL as h,extractSQLFromString as y,isSQLString as g}from"../lang/lang-sql.js";
3
11
  const x=[".js",".ts",".tsx",".py",".go",".sql"];
4
- export async function parseFile(e,t){const s={file:t,classes:[],functions:[],imports:[],exports:[]},n=[];
5
- let r;try{r=i(e,{ecmaVersion:"latest",sourceType:"module",locations:!0,onComment:n})}catch(e){return console.warn(`Parse error in ${t}:`,e.message),s}const o=buildJSDocTypeMap(n,e),a=new Set;l.simple(r,{ImportDeclaration(e){for(const t of e.specifiers)"ImportDefaultSpecifier"===t.type?s.imports.push(t.local.name):"ImportSpecifier"===t.type&&s.imports.push(t.imported.name)},ExportNamedDeclaration(e){if(e.declaration)if(e.declaration.id)a.add(e.declaration.id.name);else if(e.declaration.declarations)for(const t of e.declaration.declarations)a.add(t.id.name);if(e.specifiers)for(const t of e.specifiers)a.add(t.exported.name)},ExportDefaultDeclaration(e){e.declaration&&e.declaration.id&&a.add(e.declaration.id.name)},ClassDeclaration(e){const n={name:e.id.name,extends:e.superClass?e.superClass.name:null,methods:[],properties:[],calls:[],dbReads:[],dbWrites:[],file:t,line:e.loc.start.line};for(const t of e.body.body)if("MethodDefinition"===t.type&&"constructor"!==t.key.name)n.methods.push(t.key.name),extractCallsAndSQL(t.value.body,n.calls,n.dbReads,n.dbWrites);else if("PropertyDefinition"===t.type&&"init$"===t.key.name&&t.value&&"ObjectExpression"===t.value.type)for(const e of t.value.properties)e.key&&e.key.name&&n.properties.push(e.key.name);s.classes.push(n)},FunctionDeclaration(e){if(e.id){const n=e.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"RestElement"===e.type&&"Identifier"===e.argument.type?"..."+e.argument.name:"ObjectPattern"===e.type?"options":"?"),r=findJSDocForNode(o,e.loc.start.line),a=enrichParamsWithTypes(n,r),i={name:e.id.name,exported:!1,params:a,async:e.async||!1,returns:r?.returns||null,calls:[],dbReads:[],dbWrites:[],file:t,line:e.loc.start.line};extractCallsAndSQL(e.body,i.calls,i.dbReads,i.dbWrites),s.functions.push(i)}}});for(const e of s.functions)e.exported=a.has(e.name);return s.exports=[...a],s}const S=new Set(["query","execute","raw","exec","queryFile","none","one","many","any","oneOrNone","manyOrNone","result"]);function extractCallsAndSQL(e,t,s,n){e&&l.simple(e,{CallExpression(e){const r=e.callee;if("MemberExpression"===r.type){const e=r.object,s=r.property;if("Identifier"===s.type)if("Identifier"===e.type){const n=`${e.name}.${s.name}`;t.includes(n)||t.push(n)}else if("MemberExpression"===e.type&&"Identifier"===e.property.type){const n=`${e.property.name}.${s.name}`;t.includes(n)||t.push(n)}else if("ThisExpression"===e.type){const e=s.name;t.includes(e)||t.push(e)}}else if("Identifier"===r.type){const e=r.name;t.includes(e)||t.push(e)}if(s&&n){const t=getCallMethodName(e);if(t&&S.has(t)&&e.arguments.length>0){const t=extractStringValue(e.arguments[0]);if(t&&g(t)){const e=y(t);e.reads.forEach(e=>{s.includes(e)||s.push(e)}),e.writes.forEach(e=>{n.includes(e)||n.push(e)})}}}},TaggedTemplateExpression(e){if(!s||!n)return;
6
- const t=getTagName(e.tag);if(t&&/sql/i.test(t)){const t=templateToString(e.quasi);if(t){const e=y(t);e.reads.forEach(e=>{s.includes(e)||s.push(e)}),e.writes.forEach(e=>{n.includes(e)||n.push(e)})}}},TemplateLiteral(e){if(!s||!n)return;
7
- const t=templateToString(e);if(t&&g(t)){const e=y(t);e.reads.forEach(e=>{s.includes(e)||s.push(e)}),e.writes.forEach(e=>{n.includes(e)||n.push(e)})}},Literal(e){if(s&&n&&"string"==typeof e.value&&g(e.value)){const t=y(e.value);t.reads.forEach(e=>{s.includes(e)||s.push(e)}),t.writes.forEach(e=>{n.includes(e)||n.push(e)})}}})}
8
- function getTagName(e){return"Identifier"===e.type?e.name:"MemberExpression"===e.type&&"Identifier"===e.property.type?e.property.name:null}
9
- function getCallMethodName(e){const t=e.callee;return"MemberExpression"===t.type&&"Identifier"===t.property.type?t.property.name:null}
10
- function extractStringValue(e){return e?"Literal"===e.type&&"string"==typeof e.value?e.value:"TemplateLiteral"===e.type?templateToString(e):null:null}
11
- function templateToString(e){if(!e||!e.quasis)return"";
12
- let t="";for(let s=0;s<e.quasis.length;s++)t+=e.quasis[s].value.cooked||e.quasis[s].value.raw||"",s<e.expressions?.length&&(t+="$"+(s+1));return t}
13
- export function discoverSubProjects(i){const l=a(i),c=[],p=["packages","apps","services","modules","libs","plugins"];for(const a of p){const i=r(l,a);if(n(i))try{for(const a of t(i)){const t=r(i,a),p=r(t,"package.json");if(s(t).isDirectory()&&n(p))try{const s=JSON.parse(e(p,"utf-8"));c.push({name:s.name||a,path:o(l,t),absolutePath:t})}catch{c.push({name:a,path:o(l,t),absolutePath:t})}}}catch{}}return c}
14
- export async function parseProject(t,s={}){const n={files:[],classes:[],functions:[],imports:[],exports:[],tables:[]},i=a(t),l=findJSFiles(t);for(const t of l)try{const s=e(t,"utf-8"),r=o(i,t),a=await parseFileByExtension(s,r);n.files.push(r),n.classes.push(...a.classes),n.functions.push(...a.functions),n.imports.push(...a.imports),n.exports.push(...a.exports),a.tables?.length&&n.tables.push(...a.tables)}catch(e){}if(s.recursive){const e=discoverSubProjects(t);n.subProjects=[];for(const t of e)try{const e=await parseProject(t.absolutePath);for(const s of e.files)n.files.push(r(t.path,s));for(const s of e.classes)s.file=r(t.path,s.file),n.classes.push(s);for(const s of e.functions)s.file=r(t.path,s.file),n.functions.push(s);n.imports.push(...e.imports),n.exports.push(...e.exports),e.tables?.length&&n.tables.push(...e.tables),n.subProjects.push({name:t.name,path:t.path,files:e.files.length})}catch{}}return n.imports=[...new Set(n.imports)],n.exports=[...new Set(n.exports)],n}
15
- async function parseFileByExtension(e,t){return t.endsWith(".sql")?h(e,t):t.endsWith(".py")?d(e,t):t.endsWith(".go")?m(e,t):t.endsWith(".ts")||t.endsWith(".tsx")?f(e,t):parseFile(e,t)}
16
- function isSourceFile(e){return!e.endsWith(".css.js")&&!e.endsWith(".tpl.js")&&x.some(t=>e.endsWith(t))}
17
- export function findJSFiles(e,n=e){e===n&&u(n);
18
- const a=[];try{for(const i of t(e)){const t=r(e,i),l=s(t),u=o(n,e);l.isDirectory()?c(i,u)||a.push(...findJSFiles(t,n)):isSourceFile(i)&&(p(i,u)||a.push(t))}}catch(t){console.warn(`Cannot read directory ${e}:`,t.message)}return a}
19
- export function findAllProjectFiles(e,n=e){e===n&&u(n);
20
- const i=[],l=a(n);try{for(const a of t(e)){const t=r(e,a),u=s(t),f=o(l,e);u.isDirectory()?c(a,f)||i.push(...findAllProjectFiles(t,n)):p(a,f)||i.push(o(l,t))}}catch(t){console.warn(`Cannot read directory ${e}:`,t.message)}return i}
21
- function buildJSDocTypeMap(e,t){const s=new Map;for(const n of e){if("Block"!==n.type||!n.value.startsWith("*"))continue;
22
- const e="/*"+n.value+"*/",r=t.slice(0,n.end).split("\n").length,o=[],a=/@param\s+\{/g;
23
- let i;for(;null!==(i=a.exec(e));){let t=1,s=i.index+i[0].length;for(;s<e.length&&t>0;)"{"===e[s]?t++:"}"===e[s]&&t--,s++;if(0!==t)continue;
24
- const n=e.slice(i.index+i[0].length,s-1),r=e.slice(s).match(/^\s+(\[?\w+(?:\.\w+)*\]?)/);if(!r)continue;
25
- let a=r[1];a.startsWith("[")&&(a=a.slice(1)),a.endsWith("]")&&(a=a.slice(0,-1)),a.includes(".")||o.push({name:a,type:n})}let l=null;
26
- const c=e.match(/@returns?\s+\{([^}]+)\}/);c&&(l=c[1]),(o.length>0||l)&&s.set(r,{params:o,returns:l})}return s}
27
- function findJSDocForNode(e,t){for(let s=1;s<=3;s++){const n=e.get(t-s);if(n)return n}return null}
28
- function enrichParamsWithTypes(e,t){if(!t||0===t.params.length)return e;
29
- const s=new Map;for(const e of t.params)s.set(e.name,e.type);return e.map(e=>{const t=e.startsWith("..."),n=e.endsWith("=");
30
- let r=e;t&&(r=r.slice(3)),n&&(r=r.slice(0,-1));
31
- let o=s.get(r);return o?(o.startsWith("...")&&(o=o.slice(3)),`${t?"...":""}${r}:${o}${n?"=":""}`):e})}
12
+ export async function parseFile(e,s){const t={file:s,classes:[],functions:[],imports:[],exports:[]},n=[];let r;try{r=a(e,{ecmaVersion:"latest",sourceType:"module",locations:!0,onComment:n})}catch(e){return console.warn(`Parse error in ${s}:`,e.message),t}const o=function(e,s){const t=new Map;for(const n of e){if("Block"!==n.type||!n.value.startsWith("*"))continue;const e="/*"+n.value+"*/",r=s.slice(0,n.end).split("\n").length,o=[],i=/@param\s+\{/g;let a;for(;null!==(a=i.exec(e));){let s=1,t=a.index+a[0].length;for(;t<e.length&&s>0;)"{"===e[t]?s++:"}"===e[t]&&s--,t++;if(0!==s)continue;const n=e.slice(a.index+a[0].length,t-1),r=e.slice(t).match(/^\s+(\[?\w+(?:\.\w+)*\]?)/);if(!r)continue;let i=r[1];i.startsWith("[")&&(i=i.slice(1)),i.endsWith("]")&&(i=i.slice(0,-1)),i.includes(".")||o.push({name:i,type:n})}let c=null;const l=e.match(/@returns?\s+\{([^}]+)\}/);l&&(c=l[1]),(o.length>0||c)&&t.set(r,{params:o,returns:c})}return t}(n,e),i=new Set;c.simple(r,{ImportDeclaration(e){for(const s of e.specifiers)"ImportDefaultSpecifier"===s.type?t.imports.push(s.local.name):"ImportSpecifier"===s.type&&t.imports.push(s.imported.name)},ExportNamedDeclaration(e){if(e.declaration)if(e.declaration.id)i.add(e.declaration.id.name);else if(e.declaration.declarations)for(const s of e.declaration.declarations)i.add(s.id.name);if(e.specifiers)for(const s of e.specifiers)i.add(s.exported.name)},ExportDefaultDeclaration(e){e.declaration&&e.declaration.id&&i.add(e.declaration.id.name)},ClassDeclaration(e){const n={name:e.id.name,extends:e.superClass?e.superClass.name:null,methods:[],properties:[],calls:[],dbReads:[],dbWrites:[],file:s,line:e.loc.start.line};for(const s of e.body.body)if("MethodDefinition"===s.type&&"constructor"!==s.key.name)n.methods.push(s.key.name),j(s.value.body,n.calls,n.dbReads,n.dbWrites);else if("PropertyDefinition"===s.type&&"init$"===s.key.name&&s.value&&"ObjectExpression"===s.value.type)for(const e of s.value.properties)e.key&&e.key.name&&n.properties.push(e.key.name);t.classes.push(n)},FunctionDeclaration(e){if(e.id){const n=e.params.map(e=>"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name+"=":"RestElement"===e.type&&"Identifier"===e.argument.type?"..."+e.argument.name:"ObjectPattern"===e.type?"options":"?"),r=function(e,s){for(let t=1;t<=3;t++){const n=e.get(s-t);if(n)return n}return null}(o,e.loc.start.line),i=function(e,s){if(!s||0===s.params.length)return e;const t=new Map;for(const e of s.params)t.set(e.name,e.type);return e.map(e=>{const s=e.startsWith("..."),n=e.endsWith("=");let r=e;s&&(r=r.slice(3)),n&&(r=r.slice(0,-1));let o=t.get(r);return o?(o.startsWith("...")&&(o=o.slice(3)),`${s?"...":""}${r}:${o}${n?"=":""}`):e})}(n,r),a={name:e.id.name,exported:!1,params:i,async:e.async||!1,returns:r?.returns||null,calls:[],dbReads:[],dbWrites:[],file:s,line:e.loc.start.line};j(e.body,a.calls,a.dbReads,a.dbWrites),t.functions.push(a)}}});for(const e of t.functions)e.exported=i.has(e.name);return t.exports=[...i],t}
13
+ const b=new Set(["query","execute","raw","exec","queryFile","none","one","many","any","oneOrNone","manyOrNone","result"]);
14
+ function j(e,s,t,n){e&&c.simple(e,{CallExpression(e){const r=e.callee;if("MemberExpression"===r.type){const e=r.object,t=r.property;if("Identifier"===t.type)if("Identifier"===e.type){const n=`${e.name}.${t.name}`;s.includes(n)||s.push(n)}else if("MemberExpression"===e.type&&"Identifier"===e.property.type){const n=`${e.property.name}.${t.name}`;s.includes(n)||s.push(n)}else if("ThisExpression"===e.type){const e=t.name;s.includes(e)||s.push(e)}}else if("Identifier"===r.type){const e=r.name;s.includes(e)||s.push(e)}if(t&&n){const s=function(e){const s=e.callee;return"MemberExpression"===s.type&&"Identifier"===s.property.type?s.property.name:null}(e);if(s&&b.has(s)&&e.arguments.length>0){const s=function(e){return e?"Literal"===e.type&&"string"==typeof e.value?e.value:"TemplateLiteral"===e.type?S(e):null:null}(e.arguments[0]);if(s&&g(s)){const e=y(s);e.reads.forEach(e=>{t.includes(e)||t.push(e)}),e.writes.forEach(e=>{n.includes(e)||n.push(e)})}}}},TaggedTemplateExpression(e){if(!t||!n)return;const s=function(e){return"Identifier"===e.type?e.name:"MemberExpression"===e.type&&"Identifier"===e.property.type?e.property.name:null}(e.tag);if(s&&/sql/i.test(s)){const s=S(e.quasi);if(s){const e=y(s);e.reads.forEach(e=>{t.includes(e)||t.push(e)}),e.writes.forEach(e=>{n.includes(e)||n.push(e)})}}},TemplateLiteral(e){if(!t||!n)return;const s=S(e);if(s&&g(s)){const e=y(s);e.reads.forEach(e=>{t.includes(e)||t.push(e)}),e.writes.forEach(e=>{n.includes(e)||n.push(e)})}},Literal(e){if(t&&n&&"string"==typeof e.value&&g(e.value)){const s=y(e.value);s.reads.forEach(e=>{t.includes(e)||t.push(e)}),s.writes.forEach(e=>{n.includes(e)||n.push(e)})}}})}
15
+ function S(e){if(!e||!e.quasis)return"";let s="";for(let t=0;t<e.quasis.length;t++)s+=e.quasis[t].value.cooked||e.quasis[t].value.raw||"",t<e.expressions?.length&&(s+="$"+(t+1));return s}
16
+ export function discoverSubProjects(a){const c=i(a),l=[],p=["packages","apps","services","modules","libs","plugins"];for(const i of p){const a=r(c,i);if(n(a))try{for(const i of s(a)){const s=r(a,i),p=r(s,"package.json");if(t(s).isDirectory()&&n(p))try{const t=JSON.parse(e(p,"utf-8"));l.push({name:t.name||i,path:o(c,s),absolutePath:s})}catch{l.push({name:i,path:o(c,s),absolutePath:s})}}}catch{}}return l}
17
+ export async function parseProject(s,t={}){const n={files:[],classes:[],functions:[],imports:[],exports:[],tables:[]},a=i(s),c=findJSFiles(s);for(const s of c)try{const t=e(s,"utf-8"),r=o(a,s),i=await v(t,r);n.files.push(r),n.classes.push(...i.classes),n.functions.push(...i.functions),n.imports.push(...i.imports),n.exports.push(...i.exports),i.tables?.length&&n.tables.push(...i.tables)}catch(e){}if(t.recursive){const e=discoverSubProjects(s);n.subProjects=[];for(const s of e)try{const e=await parseProject(s.absolutePath);for(const t of e.files)n.files.push(r(s.path,t));for(const t of e.classes)t.file=r(s.path,t.file),n.classes.push(t);for(const t of e.functions)t.file=r(s.path,t.file),n.functions.push(t);n.imports.push(...e.imports),n.exports.push(...e.exports),e.tables?.length&&n.tables.push(...e.tables),n.subProjects.push({name:s.name,path:s.path,files:e.files.length})}catch{}}return n.imports=[...new Set(n.imports)],n.exports=[...new Set(n.exports)],n}
18
+ async function v(e,s){return s.endsWith(".sql")?h(e,s):s.endsWith(".py")?d(e,s):s.endsWith(".go")?m(e,s):s.endsWith(".ts")||s.endsWith(".tsx")?f(e,s):parseFile(e,s)}
19
+ function E(e){return!e.endsWith(".css.js")&&!e.endsWith(".tpl.js")&&x.some(s=>e.endsWith(s))}
20
+ export function findJSFiles(e,n=e){e===n&&u(n);const i=[];try{for(const a of s(e)){const s=r(e,a),c=t(s),u=o(n,e);c.isDirectory()?l(a,u)||i.push(...findJSFiles(s,n)):E(a)&&(p(a,u)||i.push(s))}}catch(s){console.warn(`Cannot read directory ${e}:`,s.message)}return i}
21
+ export function findAllProjectFiles(e,n=e){e===n&&u(n);const a=[],c=i(n);try{for(const i of s(e)){const s=r(e,i),u=t(s),f=o(c,e);u.isDirectory()?l(i,f)||a.push(...findAllProjectFiles(s,n)):p(i,f)||a.push(o(c,s))}}catch(s){console.warn(`Cannot read directory ${e}:`,s.message)}return a}
@@ -0,0 +1,2 @@
1
+ // @ctx .context/src/core/utils.ctx
2
+ export function estimateTokens(e){const t="string"==typeof e?e:JSON.stringify(e);return Math.ceil(t.length/4)}
@@ -1,23 +1,10 @@
1
1
  // @ctx .context/src/lang/lang-sql.ctx
2
2
  const e=new Set(["select","from","where","and","or","not","in","on","as","join","left","right","inner","outer","cross","full","group","order","by","having","limit","offset","union","all","distinct","case","when","then","else","end","null","true","false","is","between","like","ilike","exists","any","some","set","values","into","table","create","alter","drop","index","primary","key","foreign","references","constraint","default","check","unique","if","begin","commit","rollback","transaction","returning","conflict","nothing","do","update","cascade","restrict","lateral","each","row","with","recursive","only","integer","int","bigint","smallint","serial","bigserial","text","varchar","char","character","boolean","bool","timestamp","timestamptz","date","time","timetz","interval","numeric","decimal","real","float","double","json","jsonb","uuid","bytea","inet","cidr","macaddr","array","point","line","box","circle","polygon","path","count","sum","avg","min","max","coalesce","cast","extract","now","current_timestamp","current_date","generate_series","unnest","string_agg","array_agg","row_number","rank","dense_rank","over","partition","asc","desc","nulls","first","last","filter","columns","rows","tables","schema","schemas","information_schema","pg_catalog","pg_tables","pg_class"]);
3
3
  export function isSQLString(e){return!(!e||"string"!=typeof e)&&/^\s*(SELECT|INSERT|UPDATE|DELETE|WITH|CREATE\s+TABLE)\b/i.test(e)}
4
- function isValidTableName(t){return!(!t||t.length<2||e.has(t.toLowerCase())||!/^[a-zA-Z_]\w*$/.test(t)||/^[A-Z][A-Z_]*$/.test(t)||/^[A-Z][a-z]/.test(t)||/^(pg_|jsonb_|array_|string_|regexp_)/.test(t))}
5
- export function extractSQLFromString(e){if(!e||"string"!=typeof e)return{reads:[],writes:[]};
6
- const t=new Set,n=new Set,s=e.replace(/--[^\n]*/g,"").replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim(),a=/\bFROM\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;
7
- let r;for(;null!==(r=a.exec(s));){if(s.slice(r.index+r[0].length).trimStart().startsWith("("))continue;
8
- const e=r[1].split(".").pop();isValidTableName(e)&&t.add(e)}const i=/\bJOIN\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(r=i.exec(s));){if(s.slice(r.index+r[0].length).trimStart().startsWith("("))continue;
9
- const e=r[1].split(".").pop();isValidTableName(e)&&t.add(e)}const o=/\bINSERT\s+INTO\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(r=o.exec(s));){const e=r[1].split(".").pop();isValidTableName(e)&&n.add(e)}const l=/\bUPDATE\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(r=l.exec(s));){const e=r[1].split(".").pop();isValidTableName(e)&&n.add(e)}const c=/\bDELETE\s+FROM\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(r=c.exec(s));){const e=r[1].split(".").pop();isValidTableName(e)&&n.add(e)}for(const e of n)if(/\bDELETE\s+FROM\s+/i.test(s)){const n=s.match(/\bDELETE\s+FROM\s+([a-zA-Z_]\w*)/i);if(n){const s=n[1].split(".").pop();e===s&&t.delete(s)}}return{reads:[...t],writes:[...n]}}
10
- export function parseSQL(e="",t=""){const n={file:t,classes:[],functions:[],imports:[],exports:[],tables:[]};if(!e)return n;
11
- const s=/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:[a-zA-Z_]\w*\.)?([a-zA-Z_]\w*)\s*\(([\s\S]*?)\);/gi;
12
- let a;for(;null!==(a=s.exec(e));){const s=a[1],r=a[2],i=e.substring(0,a.index).split("\n").length,o=parseColumns(r);n.tables.push({name:s,columns:o,file:t,line:i})}return n}
13
- function parseColumns(t){const n=[],s=splitByTopLevelComma(t);for(const t of s){const s=t.trim();if(/^\s*(PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT|EXCLUDE)\b/i.test(s))continue;
14
- const a=s.match(/^([a-zA-Z_]\w*)\s+([A-Za-z]\w*(?:\s*\([^)]*\))?(?:\s*\[\])?)/);if(a){const t=a[1],s=a[2].trim();e.has(t.toLowerCase())||n.push({name:t,type:s})}}return n}
15
- function splitByTopLevelComma(e){const t=[];
16
- let n="",s=0;for(let a=0;a<e.length;a++){const r=e[a];if("("===r)s++;else if(")"===r)s--;else if(","===r&&0===s){t.push(n),n="";continue}n+=r}return n.trim()&&t.push(n),t}
17
- export function extractSQLFromCode(e){const t=new Set,n=new Set;if(!e)return{reads:[],writes:[]};
18
- const s=[/"""([\s\S]*?)"""/g,/'''([\s\S]*?)'''/g,/`([\s\S]*?)`/g,/"((?:[^"\\]|\\.)*)"/g,/'((?:[^'\\]|\\.)*)'/g];for(const a of s){let s;for(;null!==(s=a.exec(e));){const e=s[1];if(isSQLString(e)){const s=extractSQLFromString(e);s.reads.forEach(e=>t.add(e)),s.writes.forEach(e=>n.add(e))}}}const a=extractORMFromCode(e);return a.reads.forEach(e=>t.add(e)),a.writes.forEach(e=>n.add(e)),{reads:[...t],writes:[...n]}}const t=new Set(["findmany","findfirst","findunique","findraw","findall","findone","findbypk","findandcountall","count","aggregate","groupby","select","where","first","pluck"]),n=new Set(["create","createmany","update","updatemany","upsert","delete","deletemany","destroy","bulkcreate","insert","del","truncate"]);
19
- export function extractORMFromCode(e){const s=new Set,a=new Set;if(!e)return{reads:[],writes:[]};
20
- const r=/\bprisma\.(\w+)\.(findMany|findFirst|findUnique|findRaw|create|createMany|update|updateMany|upsert|delete|deleteMany|count|aggregate|groupBy)\s*\(/g;
21
- let i;for(;null!==(i=r.exec(e));){const e=i[1],r=i[2].toLowerCase();if(e.startsWith("$"))continue;
22
- const o=e;t.has(r)?s.add(o):n.has(r)&&a.add(o)}const o=/\b([A-Z][a-zA-Z]+)\.(findAll|findOne|findByPk|findAndCountAll|create|bulkCreate|update|destroy|count|sum|min|max)\s*\(/g;for(;null!==(i=o.exec(e));){const e=i[1],r=i[2].toLowerCase();if(["Promise","Object","Array","Map","Set","Date","Error","JSON","Math","Buffer","RegExp","Symbol","String","Number","Boolean","Request","Response","Console"].includes(e))continue;
23
- const o=e.toLowerCase();t.has(r)?s.add(o):n.has(r)&&a.add(o)}const l=/\bknex\s*\(\s*['"](\w+)['"]\s*\)/g;for(;null!==(i=l.exec(e));){const t=i[1];if(isValidTableName(t)){const n=e.slice(i.index,i.index+200);/\.(insert|update|del|delete|truncate)\s*\(/i.test(n)?a.add(t):s.add(t)}}const c=/\.(from|into|table)\s*\(\s*['"](\w+)['"]\s*\)/g;for(;null!==(i=c.exec(e));){const e=i[1].toLowerCase(),t=i[2];isValidTableName(t)&&("into"===e?a.add(t):s.add(t))}return{reads:[...s],writes:[...a]}}
4
+ function t(t){return!(!t||t.length<2||e.has(t.toLowerCase())||!/^[a-zA-Z_]\w*$/.test(t)||/^[A-Z][A-Z_]*$/.test(t)||/^[A-Z][a-z]/.test(t)||/^(pg_|jsonb_|array_|string_|regexp_)/.test(t))}
5
+ export function extractSQLFromString(e){if(!e||"string"!=typeof e)return{reads:[],writes:[]};const n=new Set,s=new Set,r=e.replace(/--[^\n]*/g,"").replace(/\/\*[\s\S]*?\*\//g,"").replace(/\s+/g," ").trim(),a=/\bFROM\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;let o;for(;null!==(o=a.exec(r));){if(r.slice(o.index+o[0].length).trimStart().startsWith("("))continue;const e=o[1].split(".").pop();t(e)&&n.add(e)}const i=/\bJOIN\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(o=i.exec(r));){if(r.slice(o.index+o[0].length).trimStart().startsWith("("))continue;const e=o[1].split(".").pop();t(e)&&n.add(e)}const c=/\bINSERT\s+INTO\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(o=c.exec(r));){const e=o[1].split(".").pop();t(e)&&s.add(e)}const l=/\bUPDATE\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(o=l.exec(r));){const e=o[1].split(".").pop();t(e)&&s.add(e)}const d=/\bDELETE\s+FROM\s+([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)?)/gi;for(;null!==(o=d.exec(r));){const e=o[1].split(".").pop();t(e)&&s.add(e)}for(const e of s)if(/\bDELETE\s+FROM\s+/i.test(r)){const t=r.match(/\bDELETE\s+FROM\s+([a-zA-Z_]\w*)/i);if(t){const s=t[1].split(".").pop();e===s&&n.delete(s)}}return{reads:[...n],writes:[...s]}}
6
+ export function parseSQL(e="",t=""){const s={file:t,classes:[],functions:[],imports:[],exports:[],tables:[]};if(!e)return s;const r=/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:[a-zA-Z_]\w*\.)?([a-zA-Z_]\w*)\s*\(([\s\S]*?)\);/gi;let a;for(;null!==(a=r.exec(e));){const r=a[1],o=a[2],i=e.substring(0,a.index).split("\n").length,c=n(o);s.tables.push({name:r,columns:c,file:t,line:i})}return s}
7
+ function n(t){const n=[],s=function(e){const t=[];let n="",s=0;for(let r=0;r<e.length;r++){const a=e[r];if("("===a)s++;else if(")"===a)s--;else if(","===a&&0===s){t.push(n),n="";continue}n+=a}return n.trim()&&t.push(n),t}(t);for(const t of s){const s=t.trim();if(/^\s*(PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT|EXCLUDE)\b/i.test(s))continue;const r=s.match(/^([a-zA-Z_]\w*)\s+([A-Za-z]\w*(?:\s*\([^)]*\))?(?:\s*\[\])?)/);if(r){const t=r[1],s=r[2].trim();e.has(t.toLowerCase())||n.push({name:t,type:s})}}return n}
8
+ export function extractSQLFromCode(e){const t=new Set,n=new Set;if(!e)return{reads:[],writes:[]};const s=[/"""([\s\S]*?)"""/g,/'''([\s\S]*?)'''/g,/`([\s\S]*?)`/g,/"((?:[^"\\]|\\.)*)"/g,/'((?:[^'\\]|\\.)*)'/g];for(const r of s){let s;for(;null!==(s=r.exec(e));){const e=s[1];if(isSQLString(e)){const s=extractSQLFromString(e);s.reads.forEach(e=>t.add(e)),s.writes.forEach(e=>n.add(e))}}}const r=extractORMFromCode(e);return r.reads.forEach(e=>t.add(e)),r.writes.forEach(e=>n.add(e)),{reads:[...t],writes:[...n]}}
9
+ const s=new Set(["findmany","findfirst","findunique","findraw","findall","findone","findbypk","findandcountall","count","aggregate","groupby","select","where","first","pluck"]),r=new Set(["create","createmany","update","updatemany","upsert","delete","deletemany","destroy","bulkcreate","insert","del","truncate"]);
10
+ export function extractORMFromCode(e){const n=new Set,a=new Set;if(!e)return{reads:[],writes:[]};const o=/\bprisma\.(\w+)\.(findMany|findFirst|findUnique|findRaw|create|createMany|update|updateMany|upsert|delete|deleteMany|count|aggregate|groupBy)\s*\(/g;let i;for(;null!==(i=o.exec(e));){const e=i[1],t=i[2].toLowerCase();if(e.startsWith("$"))continue;const o=e;s.has(t)?n.add(o):r.has(t)&&a.add(o)}const c=/\b([A-Z][a-zA-Z]+)\.(findAll|findOne|findByPk|findAndCountAll|create|bulkCreate|update|destroy|count|sum|min|max)\s*\(/g;for(;null!==(i=c.exec(e));){const e=i[1],t=i[2].toLowerCase();if(["Promise","Object","Array","Map","Set","Date","Error","JSON","Math","Buffer","RegExp","Symbol","String","Number","Boolean","Request","Response","Console"].includes(e))continue;const o=e.toLowerCase();s.has(t)?n.add(o):r.has(t)&&a.add(o)}const l=/\bknex\s*\(\s*['"](\w+)['"]\s*\)/g;for(;null!==(i=l.exec(e));){const s=i[1];if(t(s)){const t=e.slice(i.index,i.index+200);/\.(insert|update|del|delete|truncate)\s*\(/i.test(t)?a.add(s):n.add(s)}}const d=/\.(from|into|table)\s*\(\s*['"](\w+)['"]\s*\)/g;for(;null!==(i=d.exec(e));){const e=i[1].toLowerCase(),s=i[2];t(s)&&("into"===e?a.add(s):n.add(s))}return{reads:[...n],writes:[...a]}}