project-graph-mcp 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GUIDE.md +237 -0
- package/package.json +2 -1
- package/rules/test-rules.json +15 -0
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +3 -1
- package/src/analysis/complexity.js +9 -13
- package/src/analysis/custom-rules.js +16 -35
- package/src/analysis/db-analysis.js +2 -6
- package/src/analysis/dead-code.js +8 -18
- package/src/analysis/full-analysis.js +9 -17
- package/src/analysis/jsdoc-checker.js +11 -23
- package/src/analysis/jsdoc-generator.js +8 -9
- package/src/analysis/similar-functions.js +8 -15
- package/src/analysis/test-annotations.js +12 -20
- package/src/analysis/type-checker.js +5 -7
- package/src/analysis/undocumented.js +10 -13
- package/src/cli/cli-handlers.js +4 -3
- package/src/compact/ai-context.js +2 -2
- package/src/compact/compact-migrate.js +8 -16
- package/src/compact/compact.js +3 -5
- package/src/compact/compress.js +7 -13
- package/src/compact/ctx-resolver.js +5 -0
- package/src/compact/ctx-to-jsdoc.js +13 -28
- package/src/compact/doc-dialect.js +18 -29
- package/src/compact/expand.js +10 -36
- package/src/compact/jsdoc-builder.js +5 -0
- package/src/compact/mode-config.js +6 -6
- package/src/compact/split-declarations.js +2 -0
- package/src/compact/validate-pipeline.js +7 -8
- package/src/core/event-bus.js +2 -1
- package/src/core/file-walker.js +4 -0
- package/src/core/filters.js +6 -5
- package/src/core/graph-builder.js +4 -11
- package/src/core/parser.js +19 -29
- package/src/core/utils.js +2 -0
- package/src/lang/lang-sql.js +7 -20
- package/src/mcp/mcp-server.js +2 -3
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.js +13 -21
- package/src/network/backend-lifecycle.js +15 -18
- package/src/network/local-gateway.js +10 -22
- package/src/network/mdns.js +5 -11
- package/src/network/server.js +1 -2
- package/src/network/web-server.js +7 -33
- package/web/app.js +19 -14
- package/web/components/code-block.js +1 -0
- package/web/components/quick-open.js +1 -0
- package/web/dashboard-state.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +5 -4
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -4
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +2 -1
- package/web/panels/ProjectItem/ProjectItem.js +3 -4
- package/web/panels/ProjectItem/ProjectItem.tpl.js +2 -1
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +5 -4
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +2 -3
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +1 -0
- package/web/panels/ctx-panel.js +1 -0
- package/web/panels/dep-graph.js +1 -0
- package/web/panels/file-tree.js +4 -188
- package/web/panels/health-panel.js +1 -0
- package/web/panels/live-monitor.js +1 -0
- package/web/state.js +7 -10
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/custom-rules.ctx
|
|
2
|
-
import{readFileSync as e,writeFileSync as t,readdirSync as s,existsSync as n,statSync as r}from"fs";import{join as o,relative as
|
|
3
|
-
const p=
|
|
4
|
-
let m=[];
|
|
5
|
-
let s=t;for(;s!==
|
|
6
|
-
function
|
|
7
|
-
function
|
|
8
|
-
function
|
|
9
|
-
function
|
|
10
|
-
const
|
|
11
|
-
function
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function
|
|
18
|
-
const r=[],o=e(t,"utf-8").split("\n"),c=i(n,t);for(let e=0;e<o.length;e++){const t=o[e];
|
|
19
|
-
let n=!1,i="";if("regex"===s.patternType)try{const e=new RegExp(s.pattern,"g");
|
|
20
|
-
let r;for(;null!==(r=e.exec(t));)if(!isInStringOrComment(t,r.index)){n=!0,i=r[0];break}}catch(e){}else{const e=t.indexOf(s.pattern);-1===e||isInStringOrComment(t,e)||(n=!0,i=s.pattern)}n&&s.contextRequired&&!isWithinContext(o,e,s.contextRequired)||n&&r.push({ruleId:s.id,ruleName:s.name,severity:s.severity,file:c,line:e+1,match:i,replacement:s.replacement})}return r}
|
|
21
|
-
export async function getCustomRules(){const e=loadRuleSets();
|
|
22
|
-
let t=0;
|
|
23
|
-
const s={};for(const[n,r]of Object.entries(e))s[n]={description:r.description,ruleCount:r.rules.length,rules:r.rules.map(e=>({id:e.id,name:e.name,severity:e.severity}))},t+=r.rules.length;return{ruleSets:s,totalRules:t}}
|
|
24
|
-
export async function setCustomRule(e,t){const s=loadRuleSets();s[e]||(s[e]={name:e,description:`Custom rules for ${e}`,rules:[]});
|
|
25
|
-
const n=s[e],r=n.rules.findIndex(e=>e.id===t.id);return r>=0?n.rules[r]=t:n.rules.push(t),saveRuleSet(n),{success:!0,message:r>=0?`Updated rule "${t.id}" in ${e}`:`Added rule "${t.id}" to ${e}`}}
|
|
26
|
-
export async function deleteCustomRule(e,t){const s=loadRuleSets();if(!s[e])return{success:!1,message:`Ruleset "${e}" not found`};
|
|
27
|
-
const n=s[e],r=n.rules.findIndex(e=>e.id===t);return r<0?{success:!1,message:`Rule "${t}" not found`}:(n.rules.splice(r,1),saveRuleSet(n),{success:!0,message:`Deleted rule "${t}" from ${e}`})}
|
|
28
|
-
export function detectProjectRuleSets(t){const s=loadRuleSets(),r=[],c={};
|
|
29
|
-
let l=[];try{const s=o(t,"package.json");if(n(s)){const t=JSON.parse(e(s,"utf-8"));l=[...Object.keys(t.dependencies||{}),...Object.keys(t.devDependencies||{})]}}catch(e){}for(const[n,o]of Object.entries(s)){if(!o.detect)continue;
|
|
30
|
-
const s=o.detect;if(s.packageJson)for(const e of s.packageJson)if(l.includes(e)){r.push(n),c[n]=`Found "${e}" in package.json`;break}if(!r.includes(n)&&(s.imports||s.patterns)){const o=findFiles(t,"*.js");e:for(const l of o.slice(0,50))try{const o=e(l,"utf-8");if(s.imports)for(const e of s.imports)if(o.includes(e)){r.push(n),c[n]=`Found "${e}" in ${i(t,l)}`;break e}if(s.patterns)for(const e of s.patterns)if(o.includes(e)){r.push(n),c[n]=`Found "${e}" in ${i(t,l)}`;break e}}catch(e){}}}return{detected:r,reasons:c}}
|
|
31
|
-
export async function checkCustomRules(e,t={}){const s=l(e),n=loadRuleSets();
|
|
32
|
-
let r=[],o=null;if(t.ruleSet)n[t.ruleSet]&&(r=n[t.ruleSet].rules);else if(!1!==t.autoDetect){if(o=detectProjectRuleSets(e),o.detected.length>0)for(const e of o.detected)n[e]&&r.push(...n[e].rules);for(const[e,t]of Object.entries(n))t.alwaysApply&&!o.detected.includes(e)&&r.push(...t.rules)}else for(const e of Object.values(n))r.push(...e.rules);
|
|
33
|
-
const i={};for(const e of r){const t=e.filePattern||"*.js";i[t]||(i[t]=[]),i[t].push(e)}const c=[];for(const[t,n]of Object.entries(i)){const r=findFiles(e,t);for(const e of r)for(const t of n){const n=checkFileAgainstRule(e,t,s);c.push(...n)}}const u=new Set,f=c.filter(e=>{const t=`${e.file}:${e.line}:${e.match}`;return!u.has(t)&&(u.add(t),!0)});
|
|
34
|
-
let a=f;t.severity&&(a=f.filter(e=>e.severity===t.severity));
|
|
35
|
-
const d={error:0,warning:1,info:2};a.sort((e,t)=>{const s=d[e.severity]-d[t.severity];return 0!==s?s:e.file.localeCompare(t.file)});
|
|
36
|
-
const p={error:a.filter(e=>"error"===e.severity).length,warning:a.filter(e=>"warning"===e.severity).length,info:a.filter(e=>"info"===e.severity).length},h={};for(const e of a)h[e.ruleId]=(h[e.ruleId]||0)+1;return{basePath:e,total:a.length,bySeverity:p,byRule:h,violations:a.slice(0,50),...o&&{detected:o}}}
|
|
2
|
+
import{readFileSync as e,writeFileSync as t,readdirSync as s,existsSync as n,statSync as r}from"fs";import{join as o,relative as c,dirname as i,resolve as l}from"path";import{fileURLToPath as u}from"url";import{shouldExcludeDir as f,shouldExcludeFile as a,parseGitignore as d}from"../core/filters.js";
|
|
3
|
+
const p=i(u(import.meta.url)),h=o(p,"..","..","rules");
|
|
4
|
+
let m=[];
|
|
5
|
+
function y(t){m=[];let s=t;for(;s!==i(s);){const t=o(s,".graphignore");if(n(t))try{const s=e(t,"utf-8");return void(m=s.split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")))}catch(e){}s=i(s)}}
|
|
6
|
+
function g(e){const t=e.split("/").pop();for(const s of m)if(s.endsWith("*")){const n=s.slice(0,-1);if(e.startsWith(n)||t.startsWith(n))return!0}else if(s.startsWith("*")){const n=s.slice(1);if(e.endsWith(n)||t.endsWith(n))return!0}else if(e.includes(s)||t===s)return!0;return!1}
|
|
7
|
+
function x(){const t={};if(!n(h))return t;for(const n of s(h))if(n.endsWith(".json"))try{const s=e(o(h,n),"utf-8"),r=JSON.parse(s);t[r.name]=r}catch(e){}return t}
|
|
8
|
+
function v(e){const s=o(h,`${e.name}.json`);t(s,JSON.stringify(e,null,2))}
|
|
9
|
+
function $(e,t,n=e){e===n&&(d(n),y(n));const i=[],l=t.replace("*","");try{for(const u of s(e)){const s=o(e,u),d=c(n,s);r(s).isDirectory()?f(u,d)||i.push(...$(s,t,n)):u.endsWith(l)&&(a(u,d)||g(d)||i.push(s))}}catch(e){}return i}
|
|
10
|
+
function j(e,t){const s=e.indexOf("//");if(-1!==s&&t>s)return!0;let n=!1,r=null;for(let s=0;s<t;s++){const t=e[s],o=s>0?e[s-1]:"";n||'"'!==t&&"'"!==t&&"`"!==t?n&&t===r&&"\\"!==o&&(n=!1,r=null):(n=!0,r=t)}return n}
|
|
11
|
+
function S(e,t,s){const n=s,r=`</${n.replace(/[<>]/g,"")}>`;let o=0;for(let s=0;s<=t;s++){const t=e[s];let c=0;for(;c<t.length;){const e=t.indexOf(n,c),s=t.indexOf(r,c);if(-1===e&&-1===s)break;-1!==e&&(-1===s||e<s)?(o++,c=e+n.length):(o--,c=s+r.length)}}return o>0}
|
|
12
|
+
function b(t,s,n){if(function(e,t=[]){for(const s of t){const t=s.replace("*","");if(e.endsWith(t))return!0}return!1}(t,s.exclude))return[];const r=[],o=e(t,"utf-8").split("\n"),i=c(n,t);for(let e=0;e<o.length;e++){const t=o[e];let n=!1,c="";if("regex"===s.patternType)try{const e=new RegExp(s.pattern,"g");let r;for(;null!==(r=e.exec(t));)if(!j(t,r.index)){n=!0,c=r[0];break}}catch(e){}else{const e=t.indexOf(s.pattern);-1===e||j(t,e)||(n=!0,c=s.pattern)}n&&s.contextRequired&&!S(o,e,s.contextRequired)||n&&r.push({ruleId:s.id,ruleName:s.name,severity:s.severity,file:i,line:e+1,match:c,replacement:s.replacement})}return r}
|
|
13
|
+
export async function getCustomRules(){const e=x();let t=0;const s={};for(const[n,r]of Object.entries(e))s[n]={description:r.description,ruleCount:r.rules.length,rules:r.rules.map(e=>({id:e.id,name:e.name,severity:e.severity}))},t+=r.rules.length;return{ruleSets:s,totalRules:t}}
|
|
14
|
+
export async function setCustomRule(e,t){const s=x();s[e]||(s[e]={name:e,description:`Custom rules for ${e}`,rules:[]});const n=s[e],r=n.rules.findIndex(e=>e.id===t.id);return r>=0?n.rules[r]=t:n.rules.push(t),v(n),{success:!0,message:r>=0?`Updated rule "${t.id}" in ${e}`:`Added rule "${t.id}" to ${e}`}}
|
|
15
|
+
export async function deleteCustomRule(e,t){const s=x();if(!s[e])return{success:!1,message:`Ruleset "${e}" not found`};const n=s[e],r=n.rules.findIndex(e=>e.id===t);return r<0?{success:!1,message:`Rule "${t}" not found`}:(n.rules.splice(r,1),v(n),{success:!0,message:`Deleted rule "${t}" from ${e}`})}
|
|
16
|
+
export function detectProjectRuleSets(t){const s=x(),r=[],i={};let l=[];try{const s=o(t,"package.json");if(n(s)){const t=JSON.parse(e(s,"utf-8"));l=[...Object.keys(t.dependencies||{}),...Object.keys(t.devDependencies||{})]}}catch(e){}for(const[n,o]of Object.entries(s)){if(!o.detect)continue;const s=o.detect;if(s.packageJson)for(const e of s.packageJson)if(l.includes(e)){r.push(n),i[n]=`Found "${e}" in package.json`;break}if(!r.includes(n)&&(s.imports||s.patterns)){const o=$(t,"*.js");e:for(const l of o.slice(0,50))try{const o=e(l,"utf-8");if(s.imports)for(const e of s.imports)if(o.includes(e)){r.push(n),i[n]=`Found "${e}" in ${c(t,l)}`;break e}if(s.patterns)for(const e of s.patterns)if(o.includes(e)){r.push(n),i[n]=`Found "${e}" in ${c(t,l)}`;break e}}catch(e){}}}return{detected:r,reasons:i}}
|
|
17
|
+
export async function checkCustomRules(e,t={}){const s=l(e),n=x();let r=[],o=null;if(t.ruleSet)n[t.ruleSet]&&(r=n[t.ruleSet].rules);else if(!1!==t.autoDetect){if(o=detectProjectRuleSets(e),o.detected.length>0)for(const e of o.detected)n[e]&&r.push(...n[e].rules);for(const[e,t]of Object.entries(n))t.alwaysApply&&!o.detected.includes(e)&&r.push(...t.rules)}else for(const e of Object.values(n))r.push(...e.rules);const c={};for(const e of r){const t=e.filePattern||"*.js";c[t]||(c[t]=[]),c[t].push(e)}const i=[];for(const[t,n]of Object.entries(c)){const r=$(e,t);for(const e of r)for(const t of n){const n=b(e,t,s);i.push(...n)}}const u=new Set,f=i.filter(e=>{const t=`${e.file}:${e.line}:${e.match}`;return!u.has(t)&&(u.add(t),!0)});let a=f;t.severity&&(a=f.filter(e=>e.severity===t.severity));const d={error:0,warning:1,info:2};a.sort((e,t)=>{const s=d[e.severity]-d[t.severity];return 0!==s?s:e.file.localeCompare(t.file)});const p={error:a.filter(e=>"error"===e.severity).length,warning:a.filter(e=>"warning"===e.severity).length,info:a.filter(e=>"info"===e.severity).length},h={};for(const e of a)h[e.ruleId]=(h[e.ruleId]||0)+1;return{basePath:e,total:a.length,bySeverity:p,byRule:h,violations:a.slice(0,50),...o&&{detected:o}}}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/db-analysis.ctx
|
|
2
2
|
import{parseProject as e}from"../core/parser.js";import{buildGraph as t}from"../core/graph-builder.js";
|
|
3
3
|
export async function getDBSchema(t){const a=(await e(t)).tables||[];return{tables:a.map(e=>({name:e.name,columns:e.columns,file:e.file,line:e.line})),totalTables:a.length,totalColumns:a.reduce((e,t)=>e+t.columns.length,0)}}
|
|
4
|
-
export async function getTableUsage(a,s){const n=await e(a),o=t(n),r={};for(const[e,t,a]of o.edges){if("R→"!==t&&"W→"!==t)continue;
|
|
5
|
-
const n=
|
|
6
|
-
const l=o.reverseLegend[e]||e,d=o.nodes[e],c={name:l,file:d?.f||"?"};"R→"===t?r[n].readers.some(e=>e.name===l)||r[n].readers.push(c):r[n].writers.some(e=>e.name===l)||r[n].writers.push(c)}const l=Object.entries(r).map(([e,t])=>({table:e,readers:t.readers,writers:t.writers,totalReaders:t.readers.length,totalWriters:t.writers.length})).sort((e,t)=>t.totalReaders+t.totalWriters-(e.totalReaders+e.totalWriters));return{tables:l,totalTables:l.length,totalQueries:l.reduce((e,t)=>e+t.totalReaders+t.totalWriters,0)}}
|
|
7
|
-
export async function getDBDeadTables(a){const s=await e(a),n=t(s),o=s.tables||[],r=new Set;for(const[,e,t]of n.edges)"R→"!==e&&"W→"!==e||r.add(t);
|
|
8
|
-
const l=o.filter(e=>!r.has(e.name)).map(e=>({name:e.name,file:e.file,line:e.line,columnCount:e.columns.length})),d=collectReferencedColumns(s),c=[];for(const e of o)if(r.has(e.name))for(const t of e.columns)d.has(t.name)||c.push({table:e.name,column:t.name,type:t.type});return{deadTables:l,deadColumns:c,stats:{totalSchemaTables:o.length,totalSchemaColumns:o.reduce((e,t)=>e+t.columns.length,0),deadTableCount:l.length,deadColumnCount:c.length}}}
|
|
9
|
-
function collectReferencedColumns(e){const t=new Set;for(const a of e.functions||[])if(a.dbReads?.length||a.dbWrites?.length)for(const e of[...a.dbReads||[],...a.dbWrites||[]])t.add(e);for(const a of e.classes||[])if(a.dbReads?.length||a.dbWrites?.length)for(const e of[...a.dbReads||[],...a.dbWrites||[]])t.add(e);return t.add("id"),t.add("uuid"),t.add("created_at"),t.add("updated_at"),t}
|
|
4
|
+
export async function getTableUsage(a,s){const n=await e(a),o=t(n),r={};for(const[e,t,a]of o.edges){if("R→"!==t&&"W→"!==t)continue;const n=a;if(s&&n!==s)continue;r[n]||(r[n]={readers:[],writers:[]});const l=o.reverseLegend[e]||e,d=o.nodes[e],i={name:l,file:d?.f||"?"};"R→"===t?r[n].readers.some(e=>e.name===l)||r[n].readers.push(i):r[n].writers.some(e=>e.name===l)||r[n].writers.push(i)}const l=Object.entries(r).map(([e,t])=>({table:e,readers:t.readers,writers:t.writers,totalReaders:t.readers.length,totalWriters:t.writers.length})).sort((e,t)=>t.totalReaders+t.totalWriters-(e.totalReaders+e.totalWriters));return{tables:l,totalTables:l.length,totalQueries:l.reduce((e,t)=>e+t.totalReaders+t.totalWriters,0)}}
|
|
5
|
+
export async function getDBDeadTables(a){const s=await e(a),n=t(s),o=s.tables||[],r=new Set;for(const[,e,t]of n.edges)"R→"!==e&&"W→"!==e||r.add(t);const l=o.filter(e=>!r.has(e.name)).map(e=>({name:e.name,file:e.file,line:e.line,columnCount:e.columns.length})),d=function(e){const t=new Set;for(const a of e.functions||[])if(a.dbReads?.length||a.dbWrites?.length)for(const e of[...a.dbReads||[],...a.dbWrites||[]])t.add(e);for(const a of e.classes||[])if(a.dbReads?.length||a.dbWrites?.length)for(const e of[...a.dbReads||[],...a.dbWrites||[]])t.add(e);return t.add("id"),t.add("uuid"),t.add("created_at"),t.add("updated_at"),t}(s),i=[];for(const e of o)if(r.has(e.name))for(const t of e.columns)d.has(t.name)||i.push({table:e.name,column:t.name,type:t.type});return{deadTables:l,deadColumns:i,stats:{totalSchemaTables:o.length,totalSchemaColumns:o.reduce((e,t)=>e+t.columns.length,0),deadTableCount:l.length,deadColumnCount:i.length}}}
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/dead-code.ctx
|
|
2
|
-
import{readFileSync as e,readdirSync as t,statSync as n,existsSync as s}from"fs";import{join as o,relative as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
let s;try{s=c(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return{unusedVars:t,unusedImports:n}}const o=new Set,
|
|
9
|
-
const d=new Set;for(const
|
|
10
|
-
const s=new RegExp(`\\b${n.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"g"),o=e.match(s);o&&o.length<=1&&t.push({name:n.name,line:n.line})}for(const t of a){if("*"===t.name)continue;
|
|
11
|
-
const s=new RegExp(`\\b${t.local.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"g"),o=e.match(s);o&&o.length<=1&&n.push(t)}return{unusedVars:t,unusedImports:n}}
|
|
12
|
-
export async function getDeadCode(t){const n=a(t),s=findJSFiles(t),o=[],d=new Set,f=new Set,p=[],m=new Map,u=findJSFiles(findProjectRoot(t));for(const t of u){let s;try{s=e(t,"utf-8")}catch{continue}const o=i(n,t),{imports:c}=analyzeFile(s);for(const e of c){if(!e.source.startsWith("."))continue;
|
|
13
|
-
const s=r(t);
|
|
14
|
-
let c=a(s,e.source);c.endsWith(".js")||(c+=".js");
|
|
15
|
-
const l=i(n,c),d=`${e.name}@${l}`;m.has(d)||m.set(d,new Set),m.get(d).add(o)}}for(const t of s){const s=e(t,"utf-8"),o=i(n,t),{definitions:a,calls:r,exports:c,namedExports:l}=analyzeFile(s);for(const e of r)d.add(e);for(const e of c)f.add(e);p.push({file:o,code:s,definitions:a,calls:r,exports:c,namedExports:l})}for(const{file:e,code:t,definitions:n,exports:s}of p){if(e.includes(".test.")||e.includes("/tests/"))continue;if(e.endsWith(".css.js")||e.endsWith(".tpl.js"))continue;
|
|
16
|
-
let n;try{n=c(t,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){continue}l.simple(n,{FunctionDeclaration(t){if(!t.id)return;
|
|
17
|
-
const n=t.id.name;s.has(n)||f.has(n)||d.has(n)||n.startsWith("_")||o.push({name:n,type:"function",file:e,line:t.loc.start.line,reason:"Never called"})},ClassDeclaration(t){if(!t.id)return;
|
|
18
|
-
const n=t.id.name;s.has(n)||f.has(n)||d.has(n)||o.push({name:n,type:"class",file:e,line:t.loc.start.line,reason:"Never instantiated"})}})}for(const{file:e,calls:t,namedExports:n}of p)if(!e.includes(".test.")&&!e.includes("/tests/"))for(const s of n){if(t.has(s.name))continue;
|
|
19
|
-
const n=`${s.name}@${e}`,i=m.get(n);i&&0!==i.size||o.push({name:s.name,type:"export",file:e,line:s.line,reason:"Exported but never imported"})}for(const{file:e,code:t}of p){if(e.includes(".test.")||e.includes("/tests/"))continue;if(e.endsWith(".css.js")||e.endsWith(".tpl.js"))continue;const{unusedVars:n,unusedImports:s}=analyzeFileLocals(t);for(const t of n)o.push({name:t.name,type:"variable",file:e,line:t.line,reason:"Declared but never used"});for(const t of s)o.push({name:t.local,type:"import",file:e,line:t.line,reason:`Imported from '${t.source}' but never used`})}const h={function:o.filter(e=>"function"===e.type).length,class:o.filter(e=>"class"===e.type).length,export:o.filter(e=>"export"===e.type).length,variable:o.filter(e=>"variable"===e.type).length,import:o.filter(e=>"import"===e.type).length};return{total:o.length,byType:h,items:o.slice(0,50)}}
|
|
2
|
+
import{readFileSync as e,readdirSync as t,statSync as n,existsSync as s}from"fs";import{join as o,relative as a,resolve as i,dirname as r}from"path";import{parse as c}from"../../vendor/acorn.mjs";
|
|
3
|
+
import*as l from"../../vendor/walk.mjs";
|
|
4
|
+
import{shouldExcludeDir as d,shouldExcludeFile as f,parseGitignore as p}from"../core/filters.js";
|
|
5
|
+
function m(e,s=e){e===s&&p(s);const i=[];try{for(const r of t(e)){const t=o(e,r),c=a(s,t);n(t).isDirectory()?d(r,c)||i.push(...m(t,s)):!r.endsWith(".js")||r.endsWith(".css.js")||r.endsWith(".tpl.js")?(r.endsWith(".css.js")||r.endsWith(".tpl.js"))&&(f(r,c)||i.push(t)):f(r,c)||i.push(t)}}catch(e){}return i}
|
|
6
|
+
function u(e){let t=i(e);for(;t!==r(t);){if(s(o(t,"package.json")))return t;t=r(t)}return i(e)}
|
|
7
|
+
function h(e){const t=new Set,n=new Set,s=new Set,o=[],a=[];let i;try{i=c(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return{definitions:t,calls:n,exports:s,imports:o,namedExports:a}}return l.simple(i,{FunctionDeclaration(e){e.id&&t.add(e.id.name)},ClassDeclaration(e){e.id&&t.add(e.id.name)},CallExpression(e){"Identifier"===e.callee.type?n.add(e.callee.name):"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&n.add(e.callee.object.name);for(const t of e.arguments)"Identifier"===t.type&&n.add(t.name)},NewExpression(e){"Identifier"===e.callee.type&&n.add(e.callee.name)},ImportDeclaration(e){const t=e.source.value;for(const n of e.specifiers)"ImportSpecifier"===n.type?o.push({name:n.imported.name,source:t}):"ImportDefaultSpecifier"===n.type&&o.push({name:"default",source:t})},ExportNamedDeclaration(e){if(e.declaration)if(e.declaration.id){const t=e.declaration.id.name;s.add(t),a.push({name:t,line:e.loc.start.line})}else if(e.declaration.declarations)for(const t of e.declaration.declarations)if("Identifier"===t.id.type){const n=t.id.name;s.add(n),a.push({name:n,line:e.loc.start.line})}if(e.specifiers)for(const t of e.specifiers){const n=t.exported.name;s.add(n),a.push({name:n,line:e.loc.start.line})}},ExportDefaultDeclaration(e){e.declaration?.id&&s.add(e.declaration.id.name)}}),{definitions:t,calls:n,exports:s,imports:o,namedExports:a}}
|
|
8
|
+
function y(e){const t=[],n=[];let s;try{s=c(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return{unusedVars:t,unusedImports:n}}const o=new Set,a=[],i=[],r=new Set;l.simple(s,{VariableDeclaration(e){const t="ExportNamedDeclaration"===e.parent?.type;for(const n of e.declarations)"Identifier"===n.id.type&&(a.push({name:n.id.name,line:n.loc.start.line,isExported:t}),r.add(n.id))},ImportDeclaration(e){for(const t of e.specifiers){const n=t.local.name,s="ImportSpecifier"===t.type?t.imported.name:"ImportDefaultSpecifier"===t.type?"default":"*";i.push({name:s,local:n,source:e.source.value,line:e.loc.start.line}),r.add(t.local)}}});const d=new Set;for(const e of s.body){if("ExportNamedDeclaration"===e.type&&e.declaration){if(e.declaration.declarations)for(const t of e.declaration.declarations)"Identifier"===t.id.type&&d.add(t.id.name);e.declaration.id&&d.add(e.declaration.id.name)}if("ExportNamedDeclaration"===e.type&&e.specifiers)for(const t of e.specifiers)d.add(t.local.name)}l.simple(s,{Identifier(e){o.add(e.name)}});for(const n of a){if(d.has(n.name))continue;if(n.name.startsWith("_"))continue;const s=new RegExp(`\\b${n.name.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"g"),o=e.match(s);o&&o.length<=1&&t.push({name:n.name,line:n.line})}for(const t of i){if("*"===t.name)continue;const s=new RegExp(`\\b${t.local.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"g"),o=e.match(s);o&&o.length<=1&&n.push(t)}return{unusedVars:t,unusedImports:n}}
|
|
9
|
+
export async function getDeadCode(t){const n=i(t),s=m(t),o=[],d=new Set,f=new Set,p=[],x=new Map,g=m(u(t));for(const t of g){let s;try{s=e(t,"utf-8")}catch{continue}const o=a(n,t),{imports:c}=h(s);for(const e of c){if(!e.source.startsWith("."))continue;const s=r(t);let c=i(s,e.source);c.endsWith(".js")||(c+=".js");const l=a(n,c),d=`${e.name}@${l}`;x.has(d)||x.set(d,new Set),x.get(d).add(o)}}for(const t of s){const s=e(t,"utf-8"),o=a(n,t),{definitions:i,calls:r,exports:c,namedExports:l}=h(s);for(const e of r)d.add(e);for(const e of c)f.add(e);p.push({file:o,code:s,definitions:i,calls:r,exports:c,namedExports:l})}for(const{file:e,code:t,definitions:n,exports:s}of p){if(e.includes(".test.")||e.includes("/tests/"))continue;if(e.endsWith(".css.js")||e.endsWith(".tpl.js"))continue;let n;try{n=c(t,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){continue}l.simple(n,{FunctionDeclaration(t){if(!t.id)return;const n=t.id.name;s.has(n)||f.has(n)||d.has(n)||n.startsWith("_")||o.push({name:n,type:"function",file:e,line:t.loc.start.line,reason:"Never called"})},ClassDeclaration(t){if(!t.id)return;const n=t.id.name;s.has(n)||f.has(n)||d.has(n)||o.push({name:n,type:"class",file:e,line:t.loc.start.line,reason:"Never instantiated"})}})}for(const{file:e,calls:t,namedExports:n}of p)if(!e.includes(".test.")&&!e.includes("/tests/"))for(const s of n){if(t.has(s.name))continue;const n=`${s.name}@${e}`,a=x.get(n);a&&0!==a.size||o.push({name:s.name,type:"export",file:e,line:s.line,reason:"Exported but never imported"})}for(const{file:e,code:t}of p){if(e.includes(".test.")||e.includes("/tests/"))continue;if(e.endsWith(".css.js")||e.endsWith(".tpl.js"))continue;const{unusedVars:n,unusedImports:s}=y(t);for(const t of n)o.push({name:t.name,type:"variable",file:e,line:t.line,reason:"Declared but never used"});for(const t of s)o.push({name:t.local,type:"import",file:e,line:t.line,reason:`Imported from '${t.source}' but never used`})}const D={function:o.filter(e=>"function"===e.type).length,class:o.filter(e=>"class"===e.type).length,export:o.filter(e=>"export"===e.type).length,variable:o.filter(e=>"variable"===e.type).length,import:o.filter(e=>"import"===e.type).length};return{total:o.length,byType:D,items:o.slice(0,50)}}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/full-analysis.ctx
|
|
2
|
-
import{readFileSync as t,readdirSync as e,statSync as s}from"fs";import{join as a,relative as o,resolve as n}from"path";import{getDeadCode as i}from"./dead-code.js";import{checkUndocumentedFile as
|
|
3
|
-
const s=[];e-=Math.min(2*t.deadCode.total,20),t.deadCode.total>0&&s.push(`${t.deadCode.total} unused functions/classes`),e-=Math.min(.5*t.undocumented.total,15),t.undocumented.total>10&&s.push(`${t.undocumented.total} undocumented items`),e-=Math.min(3*t.similar.total,15),t.similar.total>0&&s.push(`${t.similar.total} similar function pairs`);
|
|
4
|
-
const a=
|
|
5
|
-
const n=
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
let b;try{b=t(e,"utf-8")}catch(t){continue}const C=y(b),S=p(s,r);if(S&&f(S,S.sig,C,"content"))x++,S.complexity&&d.push(...S.complexity),S.undocumented&&u.push(...S.undocumented),S.jsdocIssues&&m.push(...S.jsdocIssues);else{j++;
|
|
12
|
-
const t=c(b,n),e=l(b,n,"tests"),a=h(b,n);d.push(...t),u.push(...e),m.push(...a),g(s,r,{sig:S?.sig||C,contentHash:C,complexity:t,undocumented:e,jsdocIssues:a})}}return{complexity:d,undocumented:u,jsdocIssues:m,cacheStats:{hits:x,misses:j}}}
|
|
13
|
-
function aggregateComplexity(t,e=5){let s=t.filter(t=>t.complexity>=e);s.sort((t,e)=>e.complexity-t.complexity);
|
|
14
|
-
const a={low:s.filter(t=>"low"===t.rating).length,moderate:s.filter(t=>"moderate"===t.rating).length,high:s.filter(t=>"high"===t.rating).length,critical:s.filter(t=>"critical"===t.rating).length,average:s.length>0?Math.round(s.reduce((t,e)=>t+e.complexity,0)/s.length*10)/10:0};return{total:s.length,stats:a,items:s.slice(0,30)}}
|
|
15
|
-
function aggregateUndocumented(t){const e={class:t.filter(t=>"class"===t.type).length,function:t.filter(t=>"function"===t.type).length,method:t.filter(t=>"method"===t.type).length};return{total:t.length,byType:e,items:t.slice(0,20)}}
|
|
16
|
-
function aggregateJSDoc(t){const e=t.filter(t=>"error"===t.severity).length,s=t.filter(t=>"warning"===t.severity).length,a={};for(const e of t)a[e.file]=(a[e.file]||0)+1;return{issues:t,summary:{total:t.length,errors:e,warnings:s,byFile:a}}}
|
|
17
|
-
export async function getFullAnalysis(t,e={}){const s=e.includeItems||!1,o=(n(t),runCacheableAnalyses(t,a(C(),".context"))),l=aggregateComplexity(o.complexity),c=aggregateUndocumented(o.undocumented),h=aggregateJSDoc(o.jsdocIssues),[p,g,y,f,x]=await Promise.all([i(t).catch(()=>({total:0,byType:{},items:[]})),r(t,{threshold:70}).catch(()=>({total:0,pairs:[]})),d(t).catch(()=>({total:0,stats:{},items:[]})),u(t).catch(()=>({codePatterns:[],redundantDeps:[],stats:{totalPatterns:0,bySeverity:{},byPattern:{},redundantDeps:0}})),m(t).catch(()=>({tables:[],totalTables:0,totalQueries:0}))]),j=calculateHealthScore({deadCode:p,undocumented:c,similar:g,complexity:l,largeFiles:y,outdated:f,jsdocConsistency:h.summary}),b={deadCode:{total:p.total,byType:p.byType,...s&&{items:p.items.slice(0,10)}},undocumented:{total:c.total,byType:c.byType,...s&&{items:c.items.slice(0,10)}},similar:{total:g.total,...s&&{pairs:g.pairs.slice(0,5)}},complexity:{total:l.total,stats:l.stats,...s&&{items:l.items.slice(0,10)}},largeFiles:{total:y.total,stats:y.stats,...s&&{items:y.items.slice(0,10)}},outdated:{totalPatterns:f.stats.totalPatterns,redundantDeps:f.redundantDeps,...s&&{codePatterns:f.codePatterns.slice(0,10)}},jsdocConsistency:{total:h.summary.total,errors:h.summary.errors,warnings:h.summary.warnings,...s&&{issues:h.issues.slice(0,10)}},cache:o.cacheStats,overall:j};return x.totalTables>0&&(b.database={tablesUsed:x.totalTables,totalQueries:x.totalQueries,tables:x.tables.map(t=>({name:t.table,readers:t.totalReaders,writers:t.totalWriters}))}),b}
|
|
18
|
-
export function getAnalysisSummaryOnly(t){const e=runCacheableAnalyses(t,a(C(),".context")),s=aggregateComplexity(e.complexity),o=aggregateUndocumented(e.undocumented),n=aggregateJSDoc(e.jsdocIssues),i=calculateHealthScore({deadCode:{total:0},undocumented:o,similar:{total:0},complexity:s,largeFiles:{total:0},outdated:{stats:{totalPatterns:0}},jsdocConsistency:n.summary});return{healthScore:i.score,grade:i.rating,complexity:s.total,undocumented:o.total,jsdocIssues:n.summary.total,cache:e.cacheStats,note:"Partial score — cross-file analyses skipped for speed. Run get_full_analysis for complete health check."}}
|
|
2
|
+
import{readFileSync as t,readdirSync as e,statSync as s}from"fs";import{join as a,relative as o,resolve as n}from"path";import{getDeadCode as i}from"./dead-code.js";import{checkUndocumentedFile as r}from"./undocumented.js";import{getSimilarFunctions as l}from"./similar-functions.js";import{analyzeComplexityFile as c}from"./complexity.js";import{getLargeFiles as d}from"./large-files.js";import{getOutdatedPatterns as u}from"./outdated-patterns.js";import{getTableUsage as m}from"./db-analysis.js";import{checkJSDocFile as h}from"./jsdoc-checker.js";import{readCache as p,writeCache as y,computeContentHash as g,isCacheValid as f}from"./analysis-cache.js";import{shouldExcludeDir as j,shouldExcludeFile as x,parseGitignore as b}from"../core/filters.js";import{getWorkspaceRoot as C}from"../core/workspace.js";
|
|
3
|
+
function w(t){let e=100;const s=[];e-=Math.min(2*t.deadCode.total,20),t.deadCode.total>0&&s.push(`${t.deadCode.total} unused functions/classes`),e-=Math.min(.5*t.undocumented.total,15),t.undocumented.total>10&&s.push(`${t.undocumented.total} undocumented items`),e-=Math.min(3*t.similar.total,15),t.similar.total>0&&s.push(`${t.similar.total} similar function pairs`);const a=t.complexity.stats?.critical||0,o=t.complexity.stats?.high||0;e-=Math.min(5*a+2*o,20),a>0&&s.push(`${a} critical complexity functions`);const n=t.largeFiles.stats?.critical||0,i=t.largeFiles.stats?.warning||0;e-=Math.min(4*n+1*i,10),n>0&&s.push(`${n} files need splitting`);const r=t.outdated.stats?.bySeverity?.error||0,l=t.outdated.stats?.bySeverity?.warning||0;if(e-=Math.min(3*r+1*l,10),t.outdated.redundantDeps?.length>0&&s.push(`${t.outdated.redundantDeps.length} redundant npm dependencies`),t.jsdocConsistency){const a=t.jsdocConsistency.errors||0,o=t.jsdocConsistency.warnings||0;e-=Math.min(2*a+1*o,15),a>0&&s.push(`${a} JSDoc consistency errors`)}let c;return e=Math.max(0,Math.min(100,Math.round(e))),c=e>=90?"excellent":e>=70?"good":e>=50?"warning":"critical",{score:e,rating:c,topIssues:s.slice(0,5)}}
|
|
4
|
+
function F(t,n=t){t===n&&b(n);const i=[];try{for(const r of e(t)){const e=a(t,r),l=o(n,e);s(e).isDirectory()?j(r,l)||i.push(...F(e,n)):!r.endsWith(".js")||r.endsWith(".css.js")||r.endsWith(".tpl.js")||x(r,l)||i.push(e)}}catch(t){}return i}
|
|
5
|
+
function S(e,s){const a=n(e),i=C(),l=F(e),d=[],u=[],m=[];let j=0,x=0;for(const e of l){const n=o(a,e),l=o(i,e);let b;try{b=t(e,"utf-8")}catch(t){continue}const C=g(b),w=p(s,l);if(w&&f(w,w.sig,C,"content"))j++,w.complexity&&d.push(...w.complexity),w.undocumented&&u.push(...w.undocumented),w.jsdocIssues&&m.push(...w.jsdocIssues);else{x++;const t=c(b,n),e=r(b,n,"tests"),a=h(b,n);d.push(...t),u.push(...e),m.push(...a),y(s,l,{sig:w?.sig||C,contentHash:C,complexity:t,undocumented:e,jsdocIssues:a})}}return{complexity:d,undocumented:u,jsdocIssues:m,cacheStats:{hits:j,misses:x}}}
|
|
6
|
+
function D(t,e=5){let s=t.filter(t=>t.complexity>=e);s.sort((t,e)=>e.complexity-t.complexity);const a={low:s.filter(t=>"low"===t.rating).length,moderate:s.filter(t=>"moderate"===t.rating).length,high:s.filter(t=>"high"===t.rating).length,critical:s.filter(t=>"critical"===t.rating).length,average:s.length>0?Math.round(s.reduce((t,e)=>t+e.complexity,0)/s.length*10)/10:0};return{total:s.length,stats:a,items:s.slice(0,30)}}
|
|
7
|
+
function M(t){const e={class:t.filter(t=>"class"===t.type).length,function:t.filter(t=>"function"===t.type).length,method:t.filter(t=>"method"===t.type).length};return{total:t.length,byType:e,items:t.slice(0,20)}}
|
|
8
|
+
function P(t){const e=t.filter(t=>"error"===t.severity).length,s=t.filter(t=>"warning"===t.severity).length,a={};for(const e of t)a[e.file]=(a[e.file]||0)+1;return{issues:t,summary:{total:t.length,errors:e,warnings:s,byFile:a}}}
|
|
9
|
+
export async function getFullAnalysis(t,e={}){const s=e.includeItems||!1,o=(n(t),S(t,a(C(),".context"))),r=D(o.complexity),c=M(o.undocumented),h=P(o.jsdocIssues),[p,y,g,f,j]=await Promise.all([i(t).catch(()=>({total:0,byType:{},items:[]})),l(t,{threshold:70}).catch(()=>({total:0,pairs:[]})),d(t).catch(()=>({total:0,stats:{},items:[]})),u(t).catch(()=>({codePatterns:[],redundantDeps:[],stats:{totalPatterns:0,bySeverity:{},byPattern:{},redundantDeps:0}})),m(t).catch(()=>({tables:[],totalTables:0,totalQueries:0}))]),x=w({deadCode:p,undocumented:c,similar:y,complexity:r,largeFiles:g,outdated:f,jsdocConsistency:h.summary}),b={deadCode:{total:p.total,byType:p.byType,...s&&{items:p.items.slice(0,10)}},undocumented:{total:c.total,byType:c.byType,...s&&{items:c.items.slice(0,10)}},similar:{total:y.total,...s&&{pairs:y.pairs.slice(0,5)}},complexity:{total:r.total,stats:r.stats,...s&&{items:r.items.slice(0,10)}},largeFiles:{total:g.total,stats:g.stats,...s&&{items:g.items.slice(0,10)}},outdated:{totalPatterns:f.stats.totalPatterns,redundantDeps:f.redundantDeps,...s&&{codePatterns:f.codePatterns.slice(0,10)}},jsdocConsistency:{total:h.summary.total,errors:h.summary.errors,warnings:h.summary.warnings,...s&&{issues:h.issues.slice(0,10)}},cache:o.cacheStats,overall:x};return j.totalTables>0&&(b.database={tablesUsed:j.totalTables,totalQueries:j.totalQueries,tables:j.tables.map(t=>({name:t.table,readers:t.totalReaders,writers:t.totalWriters}))}),b}
|
|
10
|
+
export function getAnalysisSummaryOnly(t){const e=S(t,a(C(),".context")),s=D(e.complexity),o=M(e.undocumented),n=P(e.jsdocIssues),i=w({deadCode:{total:0},undocumented:o,similar:{total:0},complexity:s,largeFiles:{total:0},outdated:{stats:{totalPatterns:0}},jsdocConsistency:n.summary});return{healthScore:i.score,grade:i.rating,complexity:s.total,undocumented:o.total,jsdocIssues:n.summary.total,cache:e.cacheStats,note:"Partial score — cross-file analyses skipped for speed. Run get_full_analysis for complete health check."}}
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/jsdoc-checker.ctx
|
|
2
|
-
import{readFileSync as e,readdirSync as t,statSync as n}from"fs";import{join as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
function
|
|
11
|
-
function
|
|
12
|
-
const
|
|
13
|
-
function hasReturnValue(e){let t=!1;try{a.simple(e.body,{ReturnStatement(e){e.argument&&(t=!0)},FunctionDeclaration(){},FunctionExpression(){},ArrowFunctionExpression(){}})}catch(e){}return t}
|
|
14
|
-
function validateFunction(e,t,n,r,s,i){const o=[];if(!e)return o;
|
|
15
|
-
const a=e.params;a.length>0&&a.length!==t.length&&o.push({file:s,line:i,name:r,severity:"error",message:`Param count mismatch: JSDoc has ${a.length}, function has ${t.length}`});
|
|
16
|
-
const c=Math.min(a.length,t.length);for(let e=0;e<c;e++){const n=a[e].name,c=extractParamName(t[e]);n!==c&&"options"!==c&&"args"!==c&&"param"!==c&&o.push({file:s,line:i,name:r,severity:"error",message:`Param name mismatch at position ${e}: JSDoc says "${n}", code has "${c}"`})}!e.hasReturns&&hasReturnValue(n)&&o.push({file:s,line:i,name:r,severity:"warning",message:"Function returns a value but JSDoc has no @returns"});for(let e=0;e<c;e++){const n=a[e].type,c=inferTypeFromDefault(t[e]);if(c&&n&&"*"!==n){let t=n.toLowerCase().includes(c.toLowerCase());!t&&"string"===c&&n.includes("'")&&n.includes("|")&&(t=!0),!t&&"Array"===c&&n.includes("[]")&&(t=!0),t||o.push({file:s,line:i,name:r,severity:"warning",message:`Type mismatch for "${a[e].name}": JSDoc says {${n}}, default value suggests {${c}}`})}}return o}
|
|
17
|
-
export function checkJSDocFile(e,t){const n=[];
|
|
18
|
-
let r;try{r=o(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return n}const s=extractJSDocComments(e);return a.simple(r,{FunctionDeclaration(e){if(!e.id)return;
|
|
19
|
-
const r=findJSDocBefore(s,e.loc.start.line);r&&n.push(...validateFunction(r,e.params,e,e.id.name,t,e.loc.start.line))},VariableDeclaration(e){for(const r of e.declarations){if(!r.init)continue;
|
|
20
|
-
const i="ArrowFunctionExpression"===r.init.type||"FunctionExpression"===r.init.type?r.init:null;if(!i||!r.id?.name)continue;
|
|
21
|
-
const o=findJSDocBefore(s,e.loc.start.line);o&&n.push(...validateFunction(o,i.params,i,r.id.name,t,e.loc.start.line))}},ClassDeclaration(e){const r=e.id?.name||"Anonymous";for(const i of e.body.body){if("MethodDefinition"!==i.type)continue;
|
|
22
|
-
const e=i.key.name||i.key.value;if(!e||"constructor"===e)continue;if("method"!==i.kind)continue;
|
|
23
|
-
const o=i.value,a=findJSDocBefore(s,i.loc.start.line);a&&n.push(...validateFunction(a,o.params,o,`${r}.${e}`,t,i.loc.start.line))}}}),n}
|
|
24
|
-
export function checkJSDocConsistency(t){const n=i(t),r=findJSFiles(t),o=[];for(const t of r){let r;try{r=e(t,"utf-8")}catch(e){continue}const i=checkJSDocFile(r,s(n,t));o.push(...i)}const a=o.filter(e=>"error"===e.severity).length,c=o.filter(e=>"warning"===e.severity).length,l={};for(const e of o)l[e.file]=(l[e.file]||0)+1;return{issues:o,summary:{total:o.length,errors:a,warnings:c,byFile:l}}}
|
|
2
|
+
import{readFileSync as e,readdirSync as t,statSync as n}from"fs";import{join as s,relative as r,resolve as i}from"path";import{parse as o}from"../../vendor/acorn.mjs";
|
|
3
|
+
import*as a from"../../vendor/walk.mjs";
|
|
4
|
+
import{shouldExcludeDir as c,shouldExcludeFile as l,parseGitignore as u}from"../core/filters.js";
|
|
5
|
+
function f(e,i=e){e===i&&u(i);const o=[];try{for(const a of t(e)){const t=s(e,a),u=r(i,t);n(t).isDirectory()?c(a,u)||o.push(...f(t,i)):!a.endsWith(".js")||a.endsWith(".css.js")||a.endsWith(".tpl.js")||l(a,u)||o.push(t)}}catch(e){}return o}
|
|
6
|
+
function m(e,t){for(const n of e){const e=t-n.endLine;if(e>=0&&e<=2)return n}return null}
|
|
7
|
+
function p(e){return"Identifier"===e.type?e.name:"AssignmentPattern"===e.type&&"Identifier"===e.left.type?e.left.name:"RestElement"===e.type&&"Identifier"===e.argument.type?e.argument.name:"ObjectPattern"===e.type?"options":"ArrayPattern"===e.type?"args":"param"}
|
|
8
|
+
function h(e){if("AssignmentPattern"!==e.type)return null;const t=e.right;if("Literal"===t.type){if("string"==typeof t.value)return"string";if("number"==typeof t.value)return"number";if("boolean"==typeof t.value)return"boolean"}return"ArrayExpression"===t.type?"Array":"ObjectExpression"===t.type?"Object":null}
|
|
9
|
+
function y(e){let t=!1;try{a.simple(e.body,{ReturnStatement(e){e.argument&&(t=!0)},FunctionDeclaration(){},FunctionExpression(){},ArrowFunctionExpression(){}})}catch(e){}return t}
|
|
10
|
+
function d(e,t,n,s,r,i){const o=[];if(!e)return o;const a=e.params;a.length>0&&a.length!==t.length&&o.push({file:r,line:i,name:s,severity:"error",message:`Param count mismatch: JSDoc has ${a.length}, function has ${t.length}`});const c=Math.min(a.length,t.length);for(let e=0;e<c;e++){const n=a[e].name,c=p(t[e]);n!==c&&"options"!==c&&"args"!==c&&"param"!==c&&o.push({file:r,line:i,name:s,severity:"error",message:`Param name mismatch at position ${e}: JSDoc says "${n}", code has "${c}"`})}!e.hasReturns&&y(n)&&o.push({file:r,line:i,name:s,severity:"warning",message:"Function returns a value but JSDoc has no @returns"});for(let e=0;e<c;e++){const n=a[e].type,c=h(t[e]);if(c&&n&&"*"!==n){let t=n.toLowerCase().includes(c.toLowerCase());!t&&"string"===c&&n.includes("'")&&n.includes("|")&&(t=!0),!t&&"Array"===c&&n.includes("[]")&&(t=!0),t||o.push({file:r,line:i,name:s,severity:"warning",message:`Type mismatch for "${a[e].name}": JSDoc says {${n}}, default value suggests {${c}}`})}}return o}
|
|
11
|
+
export function checkJSDocFile(e,t){const n=[];let s;try{s=o(e,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(e){return n}const r=function(e){const t=[],n=/\/\*\*[\s\S]*?\*\//g;let s;for(;null!==(s=n.exec(e));){const n=s[0],r=e.slice(0,s.index+n.length).split("\n").length,i=[],o=/@param\s+\{/g;let a;for(;null!==(a=o.exec(n));){let e=1,t=a.index+a[0].length;for(;t<n.length&&e>0;)"{"===n[t]?e++:"}"===n[t]&&e--,t++;if(0!==e)continue;const s=n.slice(a.index+a[0].length,t-1),r=n.slice(t).match(/^\s+(\[?\w+(?:\.\w+)*\]?)/);if(!r)continue;let o=r[1];o.startsWith("[")&&(o=o.slice(1)),o.endsWith("]")&&(o=o.slice(0,-1)),o.includes(".")||i.push({name:o,type:s})}const c=/@returns?\s/.test(n);t.push({text:n,endLine:r,params:i,hasReturns:c})}return t}(e);return a.simple(s,{FunctionDeclaration(e){if(!e.id)return;const s=m(r,e.loc.start.line);s&&n.push(...d(s,e.params,e,e.id.name,t,e.loc.start.line))},VariableDeclaration(e){for(const s of e.declarations){if(!s.init)continue;const i="ArrowFunctionExpression"===s.init.type||"FunctionExpression"===s.init.type?s.init:null;if(!i||!s.id?.name)continue;const o=m(r,e.loc.start.line);o&&n.push(...d(o,i.params,i,s.id.name,t,e.loc.start.line))}},ClassDeclaration(e){const s=e.id?.name||"Anonymous";for(const i of e.body.body){if("MethodDefinition"!==i.type)continue;const e=i.key.name||i.key.value;if(!e||"constructor"===e)continue;if("method"!==i.kind)continue;const o=i.value,a=m(r,i.loc.start.line);a&&n.push(...d(a,o.params,o,`${s}.${e}`,t,i.loc.start.line))}}}),n}
|
|
12
|
+
export function checkJSDocConsistency(t){const n=i(t),s=f(t),o=[];for(const t of s){let s;try{s=e(t,"utf-8")}catch(e){continue}const i=checkJSDocFile(s,r(n,t));o.push(...i)}const a=o.filter(e=>"error"===e.severity).length,c=o.filter(e=>"warning"===e.severity).length,l={};for(const e of o)l[e.file]=(l[e.file]||0)+1;return{issues:o,summary:{total:o.length,errors:a,warnings:c,byFile:l}}}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/jsdoc-generator.ctx
|
|
2
|
-
import{readFileSync as t}from"fs";import{relative as e}from"path";import{parse as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const e=
|
|
6
|
-
const
|
|
7
|
-
function
|
|
8
|
-
function
|
|
9
|
-
function
|
|
10
|
-
export function generateJSDocFor(t,e,r={}){return generateJSDoc(t,r).find(t=>t.name===e||t.name.endsWith(`.${e}`))||null}
|
|
2
|
+
import{readFileSync as t}from"fs";import{relative as e}from"path";import{parse as n}from"../../vendor/acorn.mjs";
|
|
3
|
+
import*as r from"../../vendor/walk.mjs";
|
|
4
|
+
import{getWorkspaceRoot as a}from"../core/workspace.js";
|
|
5
|
+
export function generateJSDoc(o,s={}){const c=[],f=t(o,"utf-8"),m=e(a(),o);let p;try{p=n(f,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(t){return c}const u=t=>{const e=f.split("\n");for(let n=t-2;n>=Math.max(0,t-15);n--){const t=e[n]?.trim();if(t){if("*/"===t||t.endsWith("*/")){for(let t=n-1;t>=Math.max(0,n-20);t--){const n=e[t]?.trim();if(n?.startsWith("/**"))return!0;if(n&&!n.startsWith("*"))break}return!1}if(!t.startsWith("*")&&!t.startsWith("//"))break}}return!1};return r.simple(p,{FunctionDeclaration(t){if(!t.id)return;if(u(t.loc.start.line))return;const e=i({name:t.id.name,params:t.params,async:t.async});c.push({name:t.id.name,type:"function",file:m,line:t.loc.start.line,jsdoc:e})},ClassDeclaration(t){if(t.id)for(const e of t.body.body)if("MethodDefinition"===e.type){const n=e.key.name||e.key.value;if("method"!==e.kind)continue;if(n.startsWith("_"))continue;if(u(e.loc.start.line))continue;const r=e.value,a=i({name:n,params:r.params,async:r.async});c.push({name:`${t.id.name}.${n}`,type:"method",file:m,line:e.loc.start.line,jsdoc:a})}}}),c}
|
|
6
|
+
function i(t){const e=["/**"];e.push(` * TODO: Add description for ${t.name}`);for(const n of t.params){const t=o(n),r=s(n);e.push(` * @param {${r}} ${t}`)}return e.push(` * @returns {${t.async?"Promise<*>":"*"}}`),e.push(" */"),e.join("\n")}
|
|
7
|
+
function o(t){return"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&"Identifier"===t.left.type?`[${t.left.name}]`:"RestElement"===t.type&&"Identifier"===t.argument.type?`...${t.argument.name}`:"ObjectPattern"===t.type?"options":"ArrayPattern"===t.type?"args":"param"}
|
|
8
|
+
function s(t){if("AssignmentPattern"===t.type){const e=t.right;if("Literal"===e.type){if("string"==typeof e.value)return"string";if("number"==typeof e.value)return"number";if("boolean"==typeof e.value)return"boolean"}if("ArrayExpression"===e.type)return"Array";if("ObjectExpression"===e.type)return"Object"}return"RestElement"===t.type?"Array":"ObjectPattern"===t.type?"Object":"ArrayPattern"===t.type?"Array":"*"}
|
|
9
|
+
export function generateJSDocFor(t,e,n={}){return generateJSDoc(t,n).find(t=>t.name===e||t.name.endsWith(`.${e}`))||null}
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/similar-functions.ctx
|
|
2
|
-
import{readFileSync as t,readdirSync as e,statSync as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const e=t.key.name||t.key.value;e.startsWith("_")||
|
|
7
|
-
function
|
|
8
|
-
const r=
|
|
9
|
-
function
|
|
10
|
-
function hashBodyStructure(t){const e=[];return l.simple(t,{IfStatement(){e.push("IF")},ForStatement(){e.push("FOR")},ForOfStatement(){e.push("FOROF")},ForInStatement(){e.push("FORIN")},WhileStatement(){e.push("WHILE")},SwitchStatement(){e.push("SWITCH")},TryStatement(){e.push("TRY")},ReturnStatement(){e.push("RET")},ThrowStatement(){e.push("THROW")},AwaitExpression(){e.push("AWAIT")}}),e.join("|")}
|
|
11
|
-
function calculateSimilarity(t,e){const a=[];
|
|
12
|
-
let n=0,s=0;s+=30,t.paramCount===e.paramCount&&(n+=30,a.push("Same param count")),s+=20;
|
|
13
|
-
const r=t.paramNames.filter(t=>e.paramNames.includes(t));if(r.length>0&&t.paramNames.length>0){const s=r.length/Math.max(t.paramNames.length,e.paramNames.length);n+=Math.round(20*s),s>=.5&&a.push(`Similar params: ${r.join(", ")}`)}if(s+=10,t.async===e.async&&(n+=10),s+=25,t.bodyHash===e.bodyHash&&t.bodyHash.length>0)n+=25,a.push("Identical structure");else if(t.bodyHash.length>0&&e.bodyHash.length>0){const s=t.bodyHash.split("|"),r=e.bodyHash.split("|"),i=s.filter(t=>r.includes(t));if(i.length>0){const t=i.length/Math.max(s.length,r.length);n+=Math.round(25*t),t>=.5&&a.push("Similar control flow")}}s+=15;
|
|
14
|
-
const i=t.calls.filter(t=>e.calls.includes(t));if(i.length>0&&t.calls.length>0&&e.calls.length>0){const s=i.length/Math.max(t.calls.length,e.calls.length);n+=Math.round(15*s),i.length>=2&&a.push(`Common calls: ${i.slice(0,3).join(", ")}`)}return{similarity:Math.round(n/100*100),reasons:a}}
|
|
15
|
-
export async function getSimilarFunctions(t,e={}){const a=e.threshold||60,n=r(t),s=findJSFiles(t),i=[];for(const t of s)i.push(...extractSignatures(t,n));
|
|
16
|
-
const l=[];for(let t=0;t<i.length;t++)for(let e=t+1;e<i.length;e++){const n=i[t],s=i[e];if(n.file===s.file&&n.name===s.name)continue;if(n.bodyHash.length<3&&s.bodyHash.length<3)continue;const{similarity:r,reasons:o}=calculateSimilarity(n,s);r>=a&&o.length>0&&l.push({a:n,b:s,similarity:r,reasons:o})}return l.sort((t,e)=>e.similarity-t.similarity),{total:l.length,pairs:l.slice(0,20)}}
|
|
2
|
+
import{readFileSync as t,readdirSync as e,statSync as n}from"fs";import{join as s,relative as a,resolve as r}from"path";import{parse as l}from"../../vendor/acorn.mjs";
|
|
3
|
+
import*as o from"../../vendor/walk.mjs";
|
|
4
|
+
import{shouldExcludeDir as i,shouldExcludeFile as h,parseGitignore as c}from"../core/filters.js";
|
|
5
|
+
function m(t,r=t){t===r&&c(r);const l=[];try{for(const o of e(t)){const e=s(t,o),c=a(r,e);n(e).isDirectory()?i(o,c)||l.push(...m(e,r)):!o.endsWith(".js")||o.endsWith(".css.js")||o.endsWith(".tpl.js")||h(o,c)||l.push(e)}}catch(t){}return l}
|
|
6
|
+
function p(e,n){const s=t(e,"utf-8"),r=a(n,e),i=[];let h;try{h=l(s,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(t){return i}return o.simple(h,{FunctionDeclaration(t){t.id&&i.push(u(t,t.id.name,r))},MethodDefinition(t){if("method"!==t.kind)return;const e=t.key.name||t.key.value;e.startsWith("_")||i.push(u(t.value,e,r))}}),i}
|
|
7
|
+
function u(t,e,n){const s=t.params.map(t=>function(t){return"Identifier"===t.type?t.name:"AssignmentPattern"===t.type&&"Identifier"===t.left.type?t.left.name:"RestElement"===t.type&&"Identifier"===t.argument.type?t.argument.name:"param"}(t)),a=[];o.simple(t.body,{CallExpression(t){"Identifier"===t.callee.type?a.push(t.callee.name):"MemberExpression"===t.callee.type&&"Identifier"===t.callee.property.type&&a.push(t.callee.property.name)}});const r=function(t){const e=[];return o.simple(t,{IfStatement(){e.push("IF")},ForStatement(){e.push("FOR")},ForOfStatement(){e.push("FOROF")},ForInStatement(){e.push("FORIN")},WhileStatement(){e.push("WHILE")},SwitchStatement(){e.push("SWITCH")},TryStatement(){e.push("TRY")},ReturnStatement(){e.push("RET")},ThrowStatement(){e.push("THROW")},AwaitExpression(){e.push("AWAIT")}}),e.join("|")}(t.body);return{name:e,file:n,line:t.loc?.start?.line||0,paramCount:t.params.length,paramNames:s,async:t.async||!1,bodyHash:r,calls:[...new Set(a)]}}
|
|
8
|
+
function f(t,e){const n=[];let s=0,a=0;a+=30,t.paramCount===e.paramCount&&(s+=30,n.push("Same param count")),a+=20;const r=t.paramNames.filter(t=>e.paramNames.includes(t));if(r.length>0&&t.paramNames.length>0){const a=r.length/Math.max(t.paramNames.length,e.paramNames.length);s+=Math.round(20*a),a>=.5&&n.push(`Similar params: ${r.join(", ")}`)}if(a+=10,t.async===e.async&&(s+=10),a+=25,t.bodyHash===e.bodyHash&&t.bodyHash.length>0)s+=25,n.push("Identical structure");else if(t.bodyHash.length>0&&e.bodyHash.length>0){const a=t.bodyHash.split("|"),r=e.bodyHash.split("|"),l=a.filter(t=>r.includes(t));if(l.length>0){const t=l.length/Math.max(a.length,r.length);s+=Math.round(25*t),t>=.5&&n.push("Similar control flow")}}a+=15;const l=t.calls.filter(t=>e.calls.includes(t));if(l.length>0&&t.calls.length>0&&e.calls.length>0){const a=l.length/Math.max(t.calls.length,e.calls.length);s+=Math.round(15*a),l.length>=2&&n.push(`Common calls: ${l.slice(0,3).join(", ")}`)}return{similarity:Math.round(s/100*100),reasons:n}}
|
|
9
|
+
export async function getSimilarFunctions(t,e={}){const n=e.threshold||60,s=r(t),a=m(t),l=[];for(const t of a)l.push(...p(t,s));const o=[];for(let t=0;t<l.length;t++)for(let e=t+1;e<l.length;e++){const s=l[t],a=l[e];if(s.file===a.file&&s.name===a.name)continue;if(s.bodyHash.length<3&&a.bodyHash.length<3)continue;const{similarity:r,reasons:i}=f(s,a);r>=n&&i.length>0&&o.push({a:s,b:a,similarity:r,reasons:i})}return o.sort((t,e)=>e.similarity-t.similarity),{total:o.length,pairs:o.slice(0,20)}}
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/test-annotations.ctx
|
|
2
|
-
import{readFileSync as t,readdirSync as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const s=t.match(/^- \[([ x!])\] (\w+):\s*(.+)$/);if(!
|
|
6
|
-
let
|
|
7
|
-
const
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
export function
|
|
11
|
-
|
|
12
|
-
export function
|
|
13
|
-
export function
|
|
14
|
-
function updateTestState(e,s,r,a){const i=process.cwd(),c=findCtxMdFiles(o(i,".context")),f=parseInt(s.split(".")[1],10);for(const o of c)try{const i=t(o,"utf-8").split("\n");
|
|
15
|
-
let c=!1,u=0;for(let t=0;t<i.length;t++){if(i[t].startsWith("## ")){c=i[t].startsWith("## Tests");continue}if(!c)continue;
|
|
16
|
-
const l=i[t].match(/^- \[([ x!])\] (\w+):\s*(.+)$/);if(l&&l[2]===e){if(u===f){const c=l[3].replace(/\s*\(FAILED:.*\)$/,""),f=a?` (FAILED: ${a})`:"";return i[t]=`- [${r}] ${e}: ${c}${f}`,n(o,i.join("\n"),"utf-8"),{success:!0,testId:s,...a?{reason:a}:{}}}u++}}}catch(t){}return{success:!1,testId:s,error:"Test not found"}}
|
|
17
|
-
export function getTestSummary(t){const e=getAllFeatures(t);
|
|
18
|
-
let s=0,n=0,o=0,r=0;
|
|
19
|
-
const a=[];for(const t of e)for(const e of t.tests)s++,"passed"===e.status?n++:"failed"===e.status?(o++,a.push({id:e.id,reason:e.failReason})):r++;return{total:s,passed:n,failed:o,pending:r,progress:s>0?Math.round((n+o)/s*100):0,failures:a}}
|
|
20
|
-
export function resetTestState(){const e=process.cwd(),s=findCtxMdFiles(o(e,".context"));for(const e of s)try{let s=t(e,"utf-8");
|
|
21
|
-
const o=s.replace(/^(- )\[([x!])\] (\w+:\s*.+?)(?:\s*\(FAILED:.*\))?$/gm,"$1[ ] $3");o!==s&&n(e,o,"utf-8")}catch(t){}return{success:!0}}
|
|
2
|
+
import{readFileSync as t,readdirSync as s,statSync as e,writeFileSync as n}from"fs";
|
|
3
|
+
import{join as o,relative as r,resolve as c}from"path";
|
|
4
|
+
function a(t){const n=[];try{for(const r of s(t)){const s=o(t,r);e(s).isDirectory()&&!r.startsWith(".")?n.push(...a(s)):r.endsWith(".ctx.md")&&n.push(s)}}catch(t){}return n}
|
|
5
|
+
export function parseAnnotations(t,s){const e=t.split("\n"),n=[];let o=!1,r=[];for(const t of e){if(t.startsWith("## ")){o&&r.length&&(n.push(...i(r,s)),r=[]),o=t.startsWith("## Tests");continue}if(!o)continue;const e=t.match(/^- \[([ x!])\] (\w+):\s*(.+)$/);if(!e)continue;const[,c,a,f]=e,u=f.split("→").map(t=>t.trim()),l=u[0],p=u[1]||null;let d=null,h="pending";if("x"===c&&(h="passed"),"!"===c){h="failed";const t=l.match(/\(FAILED:\s*(.+)\)$/);t&&(d=t[1].trim())}r.push({name:a,action:l,expected:p,status:h,failReason:d})}return o&&r.length&&n.push(...i(r,s)),n}
|
|
6
|
+
function i(t,s){const e={};let n={};for(const s of t)e[s.name]||(e[s.name]=[],n[s.name]=0),e[s.name].push({id:`${s.name}.${n[s.name]++}`,action:s.action,expected:s.expected,status:s.status,failReason:s.failReason});return Object.entries(e).map(([t,e])=>({name:t,tests:e,file:s}))}
|
|
7
|
+
export function getAllFeatures(s){const e=a(o(c(s),".context")),n=[];for(const s of e)try{const e=parseAnnotations(t(s,"utf-8"),s);n.push(...e)}catch(t){}return n}
|
|
8
|
+
export function getPendingTests(t){const s=c(t),e=getAllFeatures(t),n=[];for(const t of e)for(const e of t.tests)"pending"===e.status&&n.push({...e,feature:t.name,file:r(s,t.file)});return n}
|
|
9
|
+
export function markTestPassed(t){return f(t.split(".")[0],t,"x")}
|
|
10
|
+
export function markTestFailed(t,s){return f(t.split(".")[0],t,"!",s)}
|
|
11
|
+
function f(s,e,r,c){const i=process.cwd(),f=a(o(i,".context")),u=parseInt(e.split(".")[1],10);for(const o of f)try{const a=t(o,"utf-8").split("\n");let i=!1,f=0;for(let t=0;t<a.length;t++){if(a[t].startsWith("## ")){i=a[t].startsWith("## Tests");continue}if(!i)continue;const l=a[t].match(/^- \[([ x!])\] (\w+):\s*(.+)$/);if(l&&l[2]===s){if(f===u){const i=l[3].replace(/\s*\(FAILED:.*\)$/,""),f=c?` (FAILED: ${c})`:"";return a[t]=`- [${r}] ${s}: ${i}${f}`,n(o,a.join("\n"),"utf-8"),{success:!0,testId:e,...c?{reason:c}:{}}}f++}}}catch(t){}return{success:!1,testId:e,error:"Test not found"}}
|
|
12
|
+
export function getTestSummary(t){const s=getAllFeatures(t);let e=0,n=0,o=0,r=0;const c=[];for(const t of s)for(const s of t.tests)e++,"passed"===s.status?n++:"failed"===s.status?(o++,c.push({id:s.id,reason:s.failReason})):r++;return{total:e,passed:n,failed:o,pending:r,progress:e>0?Math.round((n+o)/e*100):0,failures:c}}
|
|
13
|
+
export function resetTestState(){const s=process.cwd(),e=a(o(s,".context"));for(const s of e)try{let e=t(s,"utf-8");const o=e.replace(/^(- )\[([x!])\] (\w+:\s*.+?)(?:\s*\(FAILED:.*\))?$/gm,"$1[ ] $3");o!==e&&n(s,o,"utf-8")}catch(t){}return{success:!0}}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/type-checker.ctx
|
|
2
|
-
import{execSync as t,spawn as e}from"child_process";import{existsSync as s}from"fs";import{resolve as n,join as
|
|
3
|
-
function
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let n="",i="";s.stdout.on("data",t=>{n+=t}),s.stderr.on("data",t=>{i+=t});
|
|
8
|
-
const o=setTimeout(()=>{s.kill("SIGTERM"),t({stdout:n,stderr:i,killed:!0})},6e4);s.on("close",()=>{clearTimeout(o),t({stdout:n,stderr:i,killed:!1})}),s.on("error",e=>{clearTimeout(o),t({stdout:"",stderr:e.message,killed:!1})})}),p=((u.stdout||"")+(u.stderr||"")).split("\n").filter(t=>t.trim()),d=[];for(const t of p){const e=parseDiagnosticLine(t);e&&d.length<i&&d.push(e)}const h=d.filter(t=>"error"===t.severity).length,m=d.filter(t=>"warning"===t.severity).length,f={};for(const t of d)f[t.file]=(f[t.file]||0)+1;return{available:!0,version:o.version,diagnostics:d,summary:{total:d.length,errors:h,warnings:m,byFile:f},hint:null}}
|
|
2
|
+
import{execSync as t,spawn as e}from"child_process";import{existsSync as s}from"fs";import{resolve as n,join as r}from"path";
|
|
3
|
+
function o(){try{return{available:!0,version:t("tsc --version",{encoding:"utf-8",timeout:5e3}).trim(),path:t("which tsc",{encoding:"utf-8",timeout:5e3}).trim()}}catch(e){try{return{available:!0,version:t("npx tsc --version",{encoding:"utf-8",timeout:15e3}).trim(),path:"npx tsc"}}catch(t){return{available:!1,version:null,path:null}}}}
|
|
4
|
+
function i(t,e){const s=t.match(/^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/);return s?{file:s[1],line:parseInt(s[2]),column:parseInt(s[3]),severity:s[4],message:s[6],code:s[5]}:null}
|
|
5
|
+
function l(t,e={}){const n=["--noEmit"],o=r(t,"tsconfig.json"),i=r(t,"jsconfig.json");return s(o)?n.push("--project",o):s(i)?n.push("--project",i):(n.push("--allowJs","--checkJs"),n.push("--target","ESNext"),n.push("--module","NodeNext"),n.push("--moduleResolution","NodeNext"),n.push("--skipLibCheck"),e.files?.length?n.push(...e.files):n.push("--rootDir",t)),n}
|
|
6
|
+
export async function checkTypes(t,s={}){const r=s.maxDiagnostics||50,a=n(t),c=o();if(!c.available)return{available:!1,version:null,diagnostics:[],summary:{total:0,errors:0,warnings:0},hint:"TypeScript not found. Install: npm i -g typescript"};const u=l(a,s),p=c.path.includes("npx")?"npx":"tsc",d=c.path.includes("npx")?["tsc",...u]:u,h=await new Promise(t=>{const s=e(p,d,{cwd:a,stdio:["ignore","pipe","pipe"]});let n="",r="";s.stdout.on("data",t=>{n+=t}),s.stderr.on("data",t=>{r+=t});const o=setTimeout(()=>{s.kill("SIGTERM"),t({stdout:n,stderr:r,killed:!0})},6e4);s.on("close",()=>{clearTimeout(o),t({stdout:n,stderr:r,killed:!1})}),s.on("error",e=>{clearTimeout(o),t({stdout:"",stderr:e.message,killed:!1})})}),m=((h.stdout||"")+(h.stderr||"")).split("\n").filter(t=>t.trim()),f=[];for(const t of m){const e=i(t);e&&f.length<r&&f.push(e)}const g=f.filter(t=>"error"===t.severity).length,v=f.filter(t=>"warning"===t.severity).length,y={};for(const t of f)y[t.file]=(y[t.file]||0)+1;return{available:!0,version:c.version,diagnostics:f,summary:{total:f.length,errors:g,warnings:v,byFile:y},hint:null}}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
// @ctx .context/src/analysis/undocumented.ctx
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
let o;try{o=
|
|
10
|
-
const r=
|
|
11
|
-
const
|
|
12
|
-
const i=checkMissing(findJSDocBefore(c,e.loc.start.line),n);i.length>0&&s.push({name:o,type:"function",file:t,line:e.loc.start.line,reason:i.join(", ")})}}),s}
|
|
13
|
-
export function getUndocumented(t,n="tests"){const s=c(t),i=findJSFiles(t),r=[];for(const t of i){let c;try{c=e(t,"utf-8")}catch(e){continue}const i=checkUndocumentedFile(c,o(s,t),n);r.push(...i)}return r}
|
|
14
|
-
export function getUndocumentedSummary(e,t="tests"){const n=getUndocumented(e,t),s={class:n.filter(e=>"class"===e.type).length,function:n.filter(e=>"function"===e.type).length,method:n.filter(e=>"method"===e.type).length},o={};for(const e of n)o[e.reason]=(o[e.reason]||0)+1;return{total:n.length,byType:s,byReason:o,items:n.slice(0,20)}}
|
|
2
|
+
import{readFileSync as t,readdirSync as e,statSync as n}from"fs";import{join as s,relative as o,resolve as r}from"path";import{parse as c}from"../../vendor/acorn.mjs";
|
|
3
|
+
import*as i from"../../vendor/walk.mjs";
|
|
4
|
+
import{shouldExcludeDir as l,shouldExcludeFile as a,parseGitignore as u}from"../core/filters.js";
|
|
5
|
+
function d(t,r=t){t===r&&u(r);const c=[];try{for(const i of e(t)){const e=s(t,i),u=o(r,e);n(e).isDirectory()?l(i,u)||c.push(...d(e,r)):!i.endsWith(".js")||i.endsWith(".css.js")||i.endsWith(".tpl.js")||a(i,u)||c.push(e)}}catch(t){}return c}
|
|
6
|
+
function f(t,e){for(const n of t){const t=e-n.endLine;if(t>=0&&t<=2)return n.text}return null}
|
|
7
|
+
function p(t,e){const n=[];return t?("params"!==e&&"all"!==e||(t.includes("@param")||n.push("@param"),t.includes("@returns")||t.includes("@return")||n.push("@returns")),n):("all"===e&&n.push("description"),"params"!==e&&"all"!==e||n.push("@param","@returns"),n)}
|
|
8
|
+
const m=["constructor","connectedCallback","disconnectedCallback","attributeChangedCallback","renderCallback"];
|
|
9
|
+
export function checkUndocumentedFile(t,e,n){const s=[];let o;try{o=c(t,{ecmaVersion:"latest",sourceType:"module",locations:!0})}catch(t){return s}const r=function(t){const e=[],n=/\/\*\*[\s\S]*?\*\//g;let s;for(;null!==(s=n.exec(t));){const n=t.slice(0,s.index+s[0].length).split("\n").length;e.push({text:s[0],endLine:n})}return e}(t);return i.simple(o,{ClassDeclaration(t){const o=t.id?.name||"Anonymous";"all"===n&&(f(r,t.loc.start.line)||s.push({name:o,type:"class",file:e,line:t.loc.start.line,reason:"No JSDoc"}));for(const c of t.body.body)if("MethodDefinition"===c.type){const t=c.key.name||c.key.value;if("get"===c.kind||"set"===c.kind)continue;if(t?.startsWith("_"))continue;if(m.includes(t))continue;const i=p(f(r,c.loc.start.line),n);i.length>0&&s.push({name:`${o}.${t}`,type:"method",file:e,line:c.loc.start.line,reason:i.join(", ")})}},FunctionDeclaration(t){if(!t.id)return;const o=t.id.name;if(o.startsWith("_"))return;const c=p(f(r,t.loc.start.line),n);c.length>0&&s.push({name:o,type:"function",file:e,line:t.loc.start.line,reason:c.join(", ")})}}),s}
|
|
10
|
+
export function getUndocumented(e,n="tests"){const s=r(e),c=d(e),i=[];for(const e of c){let r;try{r=t(e,"utf-8")}catch(t){continue}const c=checkUndocumentedFile(r,o(s,e),n);i.push(...c)}return i}
|
|
11
|
+
export function getUndocumentedSummary(t,e="tests"){const n=getUndocumented(t,e),s={class:n.filter(t=>"class"===t.type).length,function:n.filter(t=>"function"===t.type).length,method:n.filter(t=>"method"===t.type).length},o={};for(const t of n)o[t.reason]=(o[t.reason]||0)+1;return{total:n.length,byType:s,byReason:o,items:n.slice(0,20)}}
|
package/src/cli/cli-handlers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ctx .context/src/cli/cli-handlers.ctx
|
|
2
|
-
import{getSkeleton as r,expand as e,deps as s,usages as
|
|
3
|
-
function
|
|
4
|
-
|
|
2
|
+
import{getSkeleton as r,expand as e,deps as s,usages as a}from"../mcp/tools.js";import{getPendingTests as t,getTestSummary as n}from"../analysis/test-annotations.js";import{getFilters as o}from"../core/filters.js";import{getInstructions as c}from"../compact/instructions.js";import{getUndocumentedSummary as i}from"../analysis/undocumented.js";import{getDeadCode as d}from"../analysis/dead-code.js";import{generateJSDoc as l}from"../analysis/jsdoc-generator.js";import{getSimilarFunctions as m}from"../analysis/similar-functions.js";import{getComplexity as u}from"../analysis/complexity.js";import{getLargeFiles as p}from"../analysis/large-files.js";import{getOutdatedPatterns as g}from"../analysis/outdated-patterns.js";import{getFullAnalysis as y}from"../analysis/full-analysis.js";import{compressFile as h}from"../compact/compress.js";import{getProjectDocs as f,generateContextFiles as j}from"../compact/doc-dialect.js";import{getGraph as b}from"../mcp/tools.js";import{parseProject as x}from"../core/parser.js";import{resolvePath as q}from"../core/workspace.js";import{checkJSDocConsistency as A}from"../analysis/jsdoc-checker.js";import{checkTypes as w}from"../analysis/type-checker.js";import{compactProject as E,expandProject as S}from"../compact/compact.js";import{injectJSDoc as U,stripJSDoc as P,validateCtxContracts as k}from"../compact/ctx-to-jsdoc.js";import{getConfig as v,setConfig as C,getModeDescription as D,getModeWorkflow as I}from"../compact/mode-config.js";import{compactMigrate as F}from"../compact/compact-migrate.js";
|
|
3
|
+
function O(r,e){const s=r.find(r=>r.startsWith(`--${e}=`));return s?s.split("=")[1]:void 0}
|
|
4
|
+
function R(r){const e=r.find(r=>!r.startsWith("--"))||".";return q(e)}
|
|
5
|
+
export const CLI_HANDLERS={config:{rawOutput:!0,handler:async()=>{const{execSync:r}=await import("child_process"),{dirname:e}=await import("path");let s,a;try{s=r("which npx",{encoding:"utf-8"}).trim()}catch{s="npx"}try{a=r("which node",{encoding:"utf-8"}).trim()}catch{a=""}const t={mcpServers:{"project-graph":{command:s,args:["-y","project-graph-mcp"],env:a?{PATH:`${e(a)}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`}:{}}}};return console.log("Add this to your MCP config:\n"),JSON.stringify(t,null,2)}},skeleton:{requiresArg:!0,argError:"Path required: skeleton <path>",handler:async e=>r(q(e[0]))},expand:{requiresArg:!0,argError:"Symbol required: expand <symbol>",handler:async r=>e(r[0])},deps:{requiresArg:!0,argError:"Symbol required: deps <symbol>",handler:async r=>s(r[0])},usages:{requiresArg:!0,argError:"Symbol required: usages <symbol>",handler:async r=>a(r[0])},pending:{handler:async r=>t(R(r))},summary:{handler:async r=>n(R(r))},filters:{handler:async()=>o()},instructions:{rawOutput:!0,handler:async()=>c()},undocumented:{handler:async r=>{const e=O(r,"level")||"tests";return i(R(r),e)}},deadcode:{handler:async r=>d(R(r))},jsdoc:{requiresArg:!0,argError:"Usage: jsdoc <file>",handler:async r=>l(q(r[0]))},similar:{handler:async r=>{const e=parseInt(O(r,"threshold"))||60;return m(R(r),{threshold:e})}},complexity:{handler:async r=>{const e=parseInt(O(r,"min"))||1,s=r.includes("--problematic");return u(R(r),{minComplexity:e,onlyProblematic:s})}},largefiles:{handler:async r=>{const e=r.includes("--problematic");return p(R(r),{onlyProblematic:e})}},outdated:{handler:async r=>{const e=r.includes("--code"),s=r.includes("--deps");return g(R(r),{codeOnly:e,depsOnly:s})}},analyze:{handler:async r=>{const e=r.includes("--items");return y(R(r),{includeItems:e})}},"jsdoc-check":{handler:async r=>A(R(r))},types:{handler:async r=>{const e=parseInt(O(r,"max"))||50;return w(R(r),{maxDiagnostics:e})}},compress:{requiresArg:!0,argError:"Usage: compress <file> [--no-beautify] [--no-legend]",handler:async r=>{const e=!r.includes("--no-beautify"),s=!r.includes("--no-legend");return h(q(r[0]),{beautify:e,legend:s})}},docs:{requiresArg:!0,argError:"Usage: docs <path> [--file=<filename>]",handler:async r=>{const e=q(r[0]),s=await b(e),a=r.find(r=>r.startsWith("--file="))?.split("=")[1];return f(s,e,{file:a})}},"generate-ctx":{requiresArg:!0,argError:"Usage: generate-ctx <path> [--overwrite] [--scope=focus|all]",handler:async r=>{const e=q(r[0]),s=await b(e),a=await x(e),t=r.includes("--overwrite"),n=r.find(r=>r.startsWith("--scope="))?.split("=")[1]||"all";return j(s,e,a,{overwrite:t,scope:n})}},compact:{requiresArg:!0,argError:"Usage: compact <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return E(e,{dryRun:s})}},beautify:{requiresArg:!0,argError:"Usage: beautify <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return S(e,{dryRun:s})}},"inject-jsdoc":{requiresArg:!0,argError:"Usage: inject-jsdoc <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return U(e,{dryRun:s})}},"strip-jsdoc":{requiresArg:!0,argError:"Usage: strip-jsdoc <path> [--dry-run]",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return P(e,{dryRun:s})}},"validate-ctx":{requiresArg:!0,argError:"Usage: validate-ctx <path> [--strict]",handler:async r=>{const e=q(r[0]),s=r.includes("--strict");return k(e,{strict:s})}},mode:{requiresArg:!0,argError:"Usage: mode <path>",handler:async r=>{const e=q(r[0]),s=v(e);return{...s,description:D(s.mode),workflow:I(s.mode)}}},"compact-migrate":{requiresArg:!0,argError:"Usage: compact-migrate <path>",handler:async r=>{const e=q(r[0]),s=r.includes("--dry-run");return F(e,{dryRun:s})}},"set-mode":{requiresArg:!0,argError:"Usage: set-mode <path> <1|2>",handler:async r=>{const e=q(r[0]),s=parseInt(r[1],10);if(!s||![1,2].includes(s))throw new Error("Mode must be 1 (compact) or 2 (full)");return C(e,{mode:s})}}};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ctx .context/src/compact/ai-context.ctx
|
|
2
|
-
import{resolve as e,extname as t}from"path";import{getSkeleton as s,getGraph as o}from"../mcp/tools.js";import{getProjectDocs as n}from"./doc-dialect.js";import{compressFile as i}from"./compress.js";import{findJSFiles as r}from"../core/parser.js";
|
|
3
|
-
const c=new Set([".js",".mjs",".ts",".tsx"]);
|
|
2
|
+
import{estimateTokens}from"../core/utils.js";import{resolve as e,extname as t}from"path";import{getSkeleton as s,getGraph as o}from"../mcp/tools.js";import{getProjectDocs as n}from"./doc-dialect.js";import{compressFile as i}from"./compress.js";import{findJSFiles as r}from"../core/parser.js";
|
|
3
|
+
const c=new Set([".js",".mjs",".ts",".tsx"]);
|
|
4
4
|
export async function getAiContext(a,l={}){const{includeFiles:f=[],includeDocs:m=!0,includeSkeleton:d=!0}=l,p=e(a),u={};
|
|
5
5
|
let g=0;if(d&&(u.skeleton=await s(p),g+=estimateTokens(u.skeleton)),m){const e=await o(p);u.docs=n(e,p),g+=estimateTokens(u.docs)}if(f.length>0){u.files={};
|
|
6
6
|
const e=r(p);for(const s of f){const o=e.find(e=>e.endsWith(s)||e.endsWith("/"+s));if(!o){u.files[s]={error:`File not found: ${s}`};continue}const n=t(o).toLowerCase();if(c.has(n))try{const e=await i(o,{beautify:!0,legend:!0});u.files[s]=e.code,g+=e.compressed}catch(e){u.files[s]={error:e.message}}else u.files[s]={error:`Unsupported file type: ${n}`}}}const h=r(p);
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
// @ctx .context/src/compact/compact-migrate.ctx
|
|
2
|
-
import{readFileSync as
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const n=
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
function
|
|
9
|
-
function
|
|
10
|
-
function S(e){return s(e)}
|
|
11
|
-
export async function compactMigrate(e,t={}){const{dryRun:n=!1}=t;checkGitClean(e);
|
|
12
|
-
const o=walkJS(e),p=[];for(const t of o){const n=a(e,t),o=await extractNames(t),s=R(t,"utf-8");p.push({file:n,namesCount:o.length,names:o,originalSize:s.length})}
|
|
13
|
-
if(n)return{dryRun:!0,files:p.length,fileSummary:p.map(e=>({file:e.file,identifiers:e.namesCount,originalSize:e.originalSize}))};
|
|
14
|
-
const v=await d(e,{dryRun:!1});for(const t of o){const n=R(t,"utf-8"),o=p.find(e=>t.endsWith(e.file));if(o&&o.names.length>0){const s=buildNamesDirective(o.names,n);s&&updateCtxNames(t,s,e)}}
|
|
15
|
-
let b;try{b=await m(e,{strict:!1})}catch(e){b={status:"SKIP",reason:e.message}}
|
|
16
|
-
f(e,{mode:1});
|
|
17
|
-
return{migrated:!0,files:v.files,savings:v.savings,originalBytes:v.originalBytes,compactedBytes:v.compactedBytes,validation:b?.status||"SKIP",mode:"compact (1)",hint:"Run 'expand_project' to generate .expanded/ cache for human review"}}
|
|
2
|
+
import{walkJSFiles}from"../core/file-walker.js";import{readFileSync as e,writeFileSync as t,existsSync as s}from"fs";import{join as o,extname as i,relative as r,basename as c,dirname as l}from"path";import{execSync as m}from"child_process";import{compactProject as f}from"./compact.js";import{validatePipeline as d}from"./validate-pipeline.js";import{setConfig as u}from"./mode-config.js";
|
|
3
|
+
const p=new Set([".js",".mjs"]),g=new Set(["node_modules",".git","vendor",".context","dev-docs",".agent",".agents",".expanded"]);
|
|
4
|
+
|
|
5
|
+
async function y(t){const n=e(t,"utf-8"),a=new Set;try{const{parse:e}=await import("../../vendor/acorn.mjs"),{simple:t}=await import("../../vendor/walk.mjs");t(e(n,{ecmaVersion:"latest",sourceType:"module"}),{FunctionDeclaration(e){e.id?.name&&a.add(e.id.name)},ClassDeclaration(e){e.id?.name&&a.add(e.id.name)},VariableDeclarator(e){e.id?.name&&a.add(e.id.name)},ImportSpecifier(e){e.local?.name&&a.add(e.local.name)},ImportDefaultSpecifier(e){e.local?.name&&a.add(e.local.name)},AssignmentExpression(e){"Identifier"===e.left?.type&&a.add(e.left.name)}})}catch{const e=/(?:function\s+(\w+)|(\w+)\s*(?:=\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>))|(?:const|let|var)\s+(\w+))/g;let t;for(;t=e.exec(n);){const e=t[1]||t[2]||t[3];e&&e.length>1&&a.add(e)}}return[...a].filter(e=>e.length>1)}
|
|
6
|
+
function w(e,t){const n=t.split("\n"),a=new Set,s=/(?:^|[^a-zA-Z_$])([a-z])(?:\s*[=,)}\];:]|$)/g;for(const e of n){let t;for(;t=s.exec(e);)a.add(t[1])}const o=new Map;for(const t of e){const e=t.charAt(0).toLowerCase();a.has(e)&&!o.has(e)&&o.set(e,t)}return 0===o.size?null:"@names "+[...o.entries()].map(([e,t])=>`${e}=${t}`).join(",")}
|
|
7
|
+
function S(n,a,s){const m=r(s,n),f=c(m,i(m))+".ctx",d=l(m),u=o(s,".context",d,f);if(x(u))try{let n=e(u,"utf-8");if(n.includes("@names"))n=n.replace(/@names .*/,a);else{const e=n.indexOf("\n");n=-1===e?a+"\n"+n:n.slice(0,e+1)+a+"\n"+n.slice(e+1)}t(u,n,"utf-8")}catch{}}
|
|
8
|
+
function x(e){return s(e)}
|
|
9
|
+
export async function compactMigrate(t,n={}){const{dryRun:a=!1}=n;!function(e){try{const t=m("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim();if(t)throw new Error("Working directory is not clean. Commit or stash changes first.\n\nDirty files:\n"+t)}catch(e){if(e.message.includes("not clean"))throw e;throw new Error("Not a git repository or git not available: "+e.message)}}(t);const s=h(t),o=[];for(const n of s){const a=r(t,n),s=await y(n),i=e(n,"utf-8");o.push({file:a,namesCount:s.length,names:s,originalSize:i.length})}if(a)return{dryRun:!0,files:o.length,fileSummary:o.map(e=>({file:e.file,identifiers:e.namesCount,originalSize:e.originalSize}))};const i=await f(t,{dryRun:!1});for(const n of s){const a=e(n,"utf-8"),s=o.find(e=>n.endsWith(e.file));if(s&&s.names.length>0){const e=w(s.names,a);e&&S(n,e,t)}}let c;try{c=await d(t,{strict:!1})}catch(t){c={status:"SKIP",reason:t.message}}return u(t,{mode:1}),{migrated:!0,files:i.files,savings:i.savings,originalBytes:i.originalBytes,compactedBytes:i.compactedBytes,validation:c?.status||"SKIP",mode:"compact (1)",hint:"Run 'expand_project' to generate .expanded/ cache for human review"}}
|