code-gauge 1.13.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,7 +63,8 @@ A finding is reported when a measured value is **greater than or equal to** its
63
63
  },
64
64
  "duplication": {
65
65
  "minTokens": 40,
66
- "maxGapTokens": 30
66
+ "maxGapTokens": 30,
67
+ "minSimilarityPercent": 70
67
68
  },
68
69
  "languageThresholds": {
69
70
  "python": { "stateMutation": 90, "structuralCoordination": 350 },
@@ -125,10 +126,11 @@ Command-line `--<metric>-threshold` flags set the global base only; use the conf
125
126
 
126
127
  ### Duplication detection settings
127
128
 
128
- The `duplication` config section (or the `--duplication-min-tokens` and `--duplication-max-gap-tokens` flags) tunes how clones are detected rather than when they are reported:
129
+ The `duplication` config section (or the `--duplication-min-tokens`, `--duplication-max-gap-tokens`, and `--duplication-min-similarity-percent` flags) tunes how clones are detected rather than when they are reported:
129
130
 
130
131
  - `minTokens` (default 40): minimum normalized token count for a region to count as a duplicate. Raise it to report only substantial copies; lower it to catch small ones.
131
- - `maxGapTokens` (default 30): copies edited in one spot split into two exact matches around the edit; adjacent matches separated by at most this many tokens are merged back into a single gapped (Type-3) clone group. `0` disables merging. Applies to within-file detection only; cross-file matching compares whole candidates. Merging changes only the grouping, not the counting: each matched fragment still counts toward `duplicateBlock`, so an edited two-fragment pair keeps counting 2.
132
+ - `maxGapTokens` (default 30): copies edited in one spot split into two exact matches around the edit; adjacent matches separated by at most this many tokens are merged back into a single gapped (Type-3) clone group. `0` disables merging. Applies to within-file detection and to cross-file matching alike: cross-file candidates are joined by a project-level window index over per-statement fingerprint sequences, and gap-adjacent cross-file matches merge into gapped clone groups the same way. Merging changes only the grouping, not the counting: each matched fragment still counts toward `duplicateBlock` (and each token span counts at most once), so an edited two-fragment pair keeps counting 2.
133
+ - `minSimilarityPercent` (default 70): blocks the exact pipeline misses are additionally compared by similarity (n-gram filtration, then token-level longest-common-subsequence verification, following NIL and NiCad), so a near-miss (Type-3) clone with scattered small edits is still reported when both blocks are at least this similar and share more than half of their content-bearing tokens (names and literal values). `100` disables near-miss detection and reports exact (Type-1/2) matches plus gapped merges only. Applies to within-file detection only; literal-dense (data-table) blocks never near-miss match. A near-miss occurrence counts all of its code lines (edited lines included) toward `duplicateLineCount`, because the whole fragment is the clone.
132
134
 
133
135
  Custom detection settings are measured by the TypeScript backend; the native backend implements the defaults only.
134
136
 
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./metrics.cjs"),r=require("./architectureMetrics.cjs"),i=require("./cliConfig.cjs"),a=require("./typescriptProject.cjs");let o=require("node:fs/promises"),s=require("node:os");s=e.__toESM(s,1);let c=require("node:path");c=e.__toESM(c,1);let l=require("commander");const u=new Map([[`.c`,`c`],[`.c++`,`cpp`],[`.cc`,`cpp`],[`.cjs`,`javascript`],[`.cp`,`cpp`],[`.cpp`,`cpp`],[`.tcc`,`cpp`],[`.cts`,`typescript`],[`.cxx`,`cpp`],[`.go`,`go`],[`.h`,`cpp`],[`.hh`,`cpp`],[`.hpp`,`cpp`],[`.hxx`,`cpp`],[`.java`,`java`],[`.js`,`javascript`],[`.jsx`,`jsx`],[`.mjs`,`javascript`],[`.mts`,`typescript`],[`.py`,`python`],[`.rb`,`ruby`],[`.rs`,`rust`],[`.ts`,`typescript`],[`.tsx`,`tsx`]]),d=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`target`,`test-fixtures`,`vendor`,`venv`]),f=new Set([`__tests__`,`test`,`tests`,`spec`]),p=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,m=/Test\.java$/u;h().catch(e=>{Q(`Error: ${$(e)}\n`),process.exitCode=1});async function h(){let e=new l.Command().name(`code-gauge`).description(`Measure code metrics and list high-risk findings.`).argument(`[target]`,`file or directory to measure`,`.`).option(`--config <path>`,`config file to use instead of the auto-detected ${i.configFileName}`).option(`--file-loc-threshold <number>`,`minimum file code LOC to report`,Y).option(`--function-loc-threshold <number>`,`minimum function physical LOC span to report`,Y).option(`--component-loc-threshold <number>`,`minimum React component physical LOC span to report`,Y).option(`--cognitive-threshold <number>`,`minimum cognitive complexity to report`,Y).option(`--cyclomatic-threshold <number>`,`minimum cyclomatic complexity to report`,Y).option(`--call-threshold <number>`,`minimum function call count to report`,Y).option(`--import-threshold <number>`,`minimum unique import sources per file to report`,Y).option(`--fan-out-threshold <number>`,`minimum intra-file fan-out per function to report`,Y).option(`--parameter-threshold <number>`,`minimum function parameter count to report`,Y).option(`--duplicate-block-threshold <number>`,`minimum count of duplicated code blocks per file to report`,Y).option(`--duplication-ratio-percent-threshold <number>`,`minimum percentage (1-100) of duplicated lines per file to report`,se).option(`--cross-file-duplicate-block-threshold <number>`,`minimum count of cross-file duplicate block groups per file to report`,Y).option(`--duplication-min-tokens <number>`,`minimum normalized token count for a duplicate region (default 40)`,Y).option(`--duplication-max-gap-tokens <number>`,`maximum token gap merged into one gapped clone group; 0 disables merging (default 30)`,ce).option(`--transitive-dependency-threshold <number>`,`minimum transitively reachable local files to report`,Y).option(`--structural-breadth-threshold <number>`,`minimum structural breadth score to report`,Y).option(`--structural-coordination-threshold <number>`,`minimum structural coordination score to report`,Y).option(`--state-mutation-threshold <number>`,`minimum state mutation score to report`,Y).option(`--duplicate-symbol-group-threshold <number>`,`minimum duplicate symbol group count to report`,Y).option(`--max-findings <number>`,`maximum number of risk findings to print`,Y).option(`--largest-files <number>`,`number of largest files by code LOC to list`,Y).option(`--include-tests`,`include test files and test directories`).option(`--tsconfig <path>`,`TypeScript project file to use instead of auto-detected tsconfig.json`).option(`--json`,`print JSON output`).option(`--fail-on-error`,`exit with code 1 when files or directories cannot be scanned`).option(`--fail-on-risk`,`exit with code 1 when high-risk findings are found`);e.action(async(e,t)=>{let n=g(e),r=await i.loadConfig(t.config,await _(n)),a=i.resolveOptions(t,r),o=await v(n,a);E(o),await te(o),await b(o,a,n);let s=re(o.files,o.architecture,o.crossFileDuplication,o.componentFunctionKeys,o.namedComponentFunctionKeys,a,o.displayRoot);a.json?F(o,s,a):I(n,o,s,a),(o.fatalError||a.failOnError&&o.errors.length>0||a.failOnRisk&&s.length>0)&&(process.exitCode=1)}),await e.parseAsync()}function g(e){return e===`~`?s.default.homedir():e.startsWith(`~/`)?c.default.join(s.default.homedir(),e.slice(2)):c.default.resolve(e)}async function _(e){try{return(await(0,o.stat)(e)).isDirectory()?e:c.default.dirname(e)}catch{return c.default.dirname(e)}}async function v(e,t){let n=[],r=[],i=[],a=e;try{a=await(0,o.realpath)(e)}catch{}let s=c.default.dirname(a),l;try{l=await(0,o.stat)(a)}catch(e){let t=`${X(a,s)}: ${$(e)}`;return{displayRoot:s,files:n,errors:[t],warnings:i,fatalError:t}}if(l.isFile()){let e=c.default.dirname(a),o=J(a,t,!0);if(!o){let t=`${X(a,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let s=y(t,n,r,i,e);return await T(a,o,`single-file`,s,a),{displayRoot:e,files:n,errors:r,warnings:i}}return await C(a,y(t,n,r,i,a)),{displayRoot:a,files:n,errors:r,warnings:i}}function y(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function b(e,t,n){if(e.fatalError||e.files.length===0)return;let r=t.tsconfig,i=r!==void 0;if(!i&&!e.files.some(({file:e})=>x(e)))return;let o=r?g(r):await S(n);if(o)try{e.typeScriptProject=await a.measureTypeScriptProject(o,e.files.map(({file:e})=>e)),e.componentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.map(e=>j(e.file,e.startLine,e.startColumn))),e.namedComponentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.flatMap(e=>e.name?[M(e.file,e.name,e.startLine)]:[]))}catch(t){i&&e.errors.push(`${X(o,e.displayRoot)}: ${$(t)}`)}}function x(e){return[`.cjs`,`.cts`,`.js`,`.jsx`,`.mjs`,`.mts`,`.ts`,`.tsx`].includes(c.default.extname(e))}async function S(e){let t=(await(0,o.stat)(e)).isDirectory()?e:c.default.dirname(e);for(;;){let e=c.default.join(t,`tsconfig.json`);if(await ee(e))return e;let n=c.default.dirname(t);if(n===t)return;t=n}}async function ee(e){try{return(await(0,o.stat)(e)).isFile()}catch{return!1}}async function te(e){if(!e.fatalError)try{e.architecture=r.measureArchitecture(e.files.map(({file:e,metrics:t})=>({file:e,metrics:t})),e.displayRoot)}catch(t){e.errors.push(`architecture metrics: ${$(t)}`)}}async function C(e,t){let n;try{n=await(0,o.realpath)(e)}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}if(!q(n,t.rootDirectory)||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r;try{r=await(0,o.readdir)(e,{withFileTypes:!0})}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}for(let n of r){let r=c.default.join(e,n.name);if(n.isSymbolicLink()){await ne(n.name,r,t);continue}if(n.isDirectory()){if(K(n.name,t.options))continue;await C(r,t);continue}n.isFile()&&await w(r,t)}}async function ne(e,t,n){let r;try{r=await(0,o.realpath)(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(!q(r,n.rootDirectory))return;let i;try{i=await(0,o.stat)(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(i.isDirectory()){if(K(e,n.options)||K(c.default.basename(r),n.options))return;await C(t,n);return}i.isFile()&&await w(t,n,r,r)}async function w(e,t,n=e,r){let i=J(n,t.options);i&&await T(e,i,`directory`,t,r)}async function T(e,t,r,i,a){try{let s=a??await(0,o.realpath)(e);if(i.visitedFiles.has(s))return;i.visitedFiles.add(s);let c=await(0,o.readFile)(e,`utf8`),l={language:t,duplication:i.options.duplication},u={file:e,metrics:n.measureCode(c,l)};if(r===`directory`)try{u.duplicationCandidates=n.collectDuplicationCandidates(c,l)}catch(t){i.warnings.push(`${X(e,i.rootDirectory)}: cross-file duplication candidates unavailable: ${$(t)}`)}i.files.push(u)}catch(t){i.errors.push(`${X(e,i.rootDirectory)}: ${$(t)}`)}}function E(e){if(e.fatalError||e.files.length<2)return;let n=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:X(t,e.displayRoot),candidates:n}]:[]);n.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(n))}function re(e,t,n,r,a,o,s){let c=new Map(t?.files.map(e=>[e.file,e])),l=e.flatMap(({file:e,metrics:t})=>{let l=t.functions.some(t=>t.returnsJsx||A(e,t,r,a)),u=i.resolveThresholds(o,t.language,l);return[...ie(e,t,c.get(X(e,s)),n,u,s),...t.functions.flatMap(n=>ae(e,t.language,n,u,s,r,a))]});return l.sort(P),l}function ie(e,t,n,r,i,a){let o=[],s=X(e,a);D(o,`file LOC`,t.lines.code,i.fileLoc),D(o,`import sources`,t.coupling.importSourceCount,i.import);let c=k(t.duplication.duplicateBlockGroups);return D(o,`duplicated blocks`,t.duplication.duplicateBlockCount,i.duplicateBlock,c),D(o,`duplicated lines (%)`,Math.floor(t.duplication.duplicationRatio*100),i.duplicationRatioPercent,c),r&&D(o,`cross-file duplicated blocks`,Object.hasOwn(r.duplicateBlockGroupCountByFile,s)?r.duplicateBlockGroupCountByFile[s]??0:0,i.crossFileDuplicateBlock,O(r,s)),n&&((t.lines.code>=100||n.directLocalDependencyCount>=8)&&D(o,`transitive local dependencies`,n.transitiveLocalDependencyCount,i.transitiveDependency),(o.length>0||n.directLocalDependencyCount>=8||n.structuralCoordination.score>=i.structuralCoordination)&&D(o,`structural breadth`,n.structuralBreadthScore,i.structuralBreadth),D(o,`structural coordination`,n.structuralCoordination.score,i.structuralCoordination),D(o,`state mutation`,n.structuralCoordination.stateMutationScore,i.stateMutation),D(o,`duplicate symbol groups`,n.duplicateSymbolGroupCount,i.duplicateSymbolGroup)),o.length===0?[]:[{file:s,language:t.language,kind:`file`,cyclomaticComplexity:t.cyclomaticComplexity,cognitiveComplexity:t.cognitiveComplexity,triggers:o,score:N(o)}]}function ae(e,t,n,r,i,a,o){let s=n.endLine-n.startLine+1,c=A(e,n,a,o),l=c?`component`:`function`,u=[];return D(u,`cognitive complexity`,n.cognitiveComplexity,r.cognitive),D(u,`cyclomatic complexity`,n.cyclomaticComplexity,r.cyclomatic),D(u,c?`component LOC`:`function LOC`,s,oe(c,r)),D(u,`function calls`,n.callCount,r.call),D(u,`fan-out`,n.fanOut,r.fanOut),D(u,`parameters`,n.parameterCount,r.parameter),u.length===0?[]:[{file:X(e,i),language:t,kind:l,name:n.name??`<anonymous>`,startLine:n.startLine,endLine:n.endLine,cyclomaticComplexity:n.cyclomaticComplexity,cognitiveComplexity:n.cognitiveComplexity,triggers:u,score:N(u)}]}function D(e,t,n,r,i){n<r||e.push({metric:t,value:n,threshold:r,score:n/r,detail:i})}function O(e,t){let n=e.groups.filter(e=>e.files.includes(t));if(n.length!==0)return`${n.slice(0,3).map(e=>e.occurrences.map(({file:e,startLine:n,endLine:r})=>e===t?`${n}-${r}`:`${e}:${n}-${r}`).join(` ~ `)).join(`; `)}${n.length>3?`; ...`:``}`}function k(e){if(e.length!==0)return e.map(e=>e.map(({startLine:e,endLine:t})=>`${e}-${t}`).join(` ~ `)).join(`; `)}function A(e,t,n,r){return n?.has(j(e,t.startLine,t.startColumn))||(t.name?r?.has(M(e,t.name,t.startLine)):!1)||!1}function oe(e,t){return e?t.componentLoc:t.functionLoc}function j(e,t,n){return`${c.default.resolve(e)}:${t}:${n}`}function M(e,t,n){return`${c.default.resolve(e)}:${t}:${n}`}function N(e){return Math.max(...e.map(e=>e.score))}function P(e,t){return t.score-e.score||e.file.localeCompare(t.file)||(e.startLine??0)-(t.startLine??0)||(e.endLine??0)-(t.endLine??0)||e.kind.localeCompare(t.kind)}function F(e,t,n){let r=G(e.files),i=t.slice(0,n.maxFindings);Z(JSON.stringify({summary:r,thresholds:n.thresholds,profileThresholds:n.profileThresholds,totalRisks:t.length,truncated:i.length<t.length,largestFiles:n.largestFiles>0?L(e.files,n.largestFiles,e.displayRoot):void 0,architecture:e.architecture,crossFileDuplication:e.crossFileDuplication,typeScriptProject:e.typeScriptProject,risks:i,errors:e.errors,warnings:e.warnings},void 0,2)+`
3
- `)}function I(e,t,n,r){if(t.fatalError){Q(`Error: ${t.fatalError}\n`);return}let{thresholds:i}=r,a=G(t.files);Z(`Measured ${a.fileCount} files under ${e}\n`),Z(`LOC ${a.linesOfCode}, NCSS ${a.ncssCount}, functions ${a.functionCount}, max cyclomatic ${a.maxCyclomaticComplexity}, max cognitive ${a.maxCognitiveComplexity}\n`),Z(`Calls ${a.callCount}, internal edges ${a.internalCallCount}, max call depth ${a.maxCallDepth}, imports ${a.importSourceCount}, exports ${a.exportCount}\n`),Z(`Type annotations ${a.typeAnnotationCount}, type aliases ${a.typeAliasCount}, interfaces ${a.interfaceCount}, avg cohesion ${a.averageFunctionIdentifierOverlap.toFixed(2)}\n`),t.architecture&&Z(`${U(t.architecture)}\n`),t.typeScriptProject&&Z(`${W(t.typeScriptProject)}\n`),Z(`Risk thresholds: file LOC >= ${i.fileLoc}, function LOC >= ${i.functionLoc}, component LOC >= ${i.componentLoc}, cognitive >= ${i.cognitive}, cyclomatic >= ${i.cyclomatic}, calls >= ${i.call}, imports >= ${i.import}, fan-out >= ${i.fanOut}, parameters >= ${i.parameter}, duplicated blocks >= ${i.duplicateBlock}, duplicated lines (%) >= ${i.duplicationRatioPercent}, cross-file duplicated blocks >= ${i.crossFileDuplicateBlock}\n`);let o=R(r.profileThresholds);if(o&&Z(`Per-language overrides: ${o}\n`),n.length===0)Z(`No high-risk findings found.
4
- `);else{let e=n.slice(0,r.maxFindings),t=n.length>e.length?` of ${n.length}`:``;Z(`\nHigh-risk findings (top ${e.length}${t}):\n`);for(let t of e)Z(`${z(t)} ${B(t)} ${V(t)}\n`)}let s=t.crossFileDuplication?.groups??[];if(s.length>0){let e=s.slice(0,10),t=s.length>e.length?` of ${s.length}`:``;Z(`\nCross-file duplicate blocks (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.tokenCount} tokens: ${t.occurrences.map(({file:e,startLine:t,endLine:n})=>`${e}:${t}-${n}`).join(`, `)}\n`)}let c=t.architecture?.duplicateSymbolGroups??[];if(c.length>0){let e=c.toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name)).slice(0,10),t=c.length>e.length?` of ${c.length}`:``;Z(`\nDuplicate symbols (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.name}: ${t.declarations.map(e=>`${e.file}:${e.line}`).join(`, `)}\n`)}if(r.largestFiles>0){let e=L(t.files,r.largestFiles,t.displayRoot);Z(`\nLargest files by code LOC (top ${e.length}):\n`);for(let{file:t,codeLoc:n}of e)Z(`${t} (code LOC ${n})\n`)}if(t.warnings.length>0){Q(`\nDegraded ${t.warnings.length} files (measured, but excluded from cross-file matching):\n`);for(let e of t.warnings.slice(0,10))Q(`- ${e}\n`);t.warnings.length>10&&Q(`- ... ${t.warnings.length-10} more\n`)}if(t.errors.length>0){Q(`\nSkipped ${t.errors.length} files or directories:\n`);for(let e of t.errors.slice(0,10))Q(`- ${e}\n`);t.errors.length>10&&Q(`- ... ${t.errors.length-10} more\n`)}}function L(e,t,n){return e.map(({file:e,metrics:t})=>({file:X(e,n),codeLoc:t.lines.code})).toSorted((e,t)=>t.codeLoc-e.codeLoc||e.file.localeCompare(t.file)).slice(0,t)}function R(e){return Object.entries(e).map(([e,t])=>`${e} { ${Object.entries(t).map(([e,t])=>`${e} ${t}`).join(`, `)} }`).join(`; `)}function z(e){return e.startLine===void 0||e.endLine===void 0?e.file:`${e.file}:${e.startLine}-${e.endLine}`}function B(e){return e.name?`${e.kind} ${e.name}`:e.kind}function V(e){return`(${e.triggers.map(e=>`${e.metric} ${H(e.value)} >= ${H(e.threshold)}${e.detail?` [${e.detail}]`:``}`).join(`, `)}; cyclomatic ${e.cyclomaticComplexity}, cognitive ${e.cognitiveComplexity})`}function H(e){return Number.isInteger(e)?String(e):e.toFixed(2)}function U(e){let t=Math.max(0,...e.files.map(e=>e.structuralCoordination.stateMutationScore));return`Architecture max reachable files ${e.maxTransitiveLocalDependencyCount}, max structural breadth ${e.maxStructuralBreadthScore}, max structural coordination ${e.maxStructuralCoordinationScore}, max state mutation ${t}, duplicate symbol groups ${e.duplicateSymbolGroups.length}`}function W(e){return`TypeScript project root files ${e.rootFileCount}, measured roots ${e.measuredRootFileCount}, semantic diagnostics ${e.semanticDiagnosticCount}, resolved calls ${e.resolvedCallExpressionCount}/${e.callExpressionCount} (${(e.resolvedCallExpressionRatio*100).toFixed(1)}%)`}function G(e){let t=0,n=0,r=0,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0,_=0;for(let v of e)t+=v.metrics.functionCount,n+=v.metrics.lines.code,r=Math.max(r,v.metrics.maxCyclomaticComplexity),i=Math.max(i,v.metrics.maxCognitiveComplexity),a+=v.metrics.ncssCount,o+=v.metrics.callGraph.callCount,s+=v.metrics.callGraph.internalCallCount,c=Math.max(c,v.metrics.callGraph.maxCallDepth),l+=v.metrics.coupling.importSourceCount,u+=v.metrics.coupling.relativeImportCount,d+=v.metrics.coupling.externalImportCount,f+=v.metrics.coupling.exportCount,p+=v.metrics.cohesion.averageFunctionIdentifierOverlap,m+=v.metrics.typeComplexity.typeAnnotationCount,h+=v.metrics.typeComplexity.typeAliasCount,g+=v.metrics.typeComplexity.interfaceCount,_+=v.metrics.typeComplexity.genericParameterCount;return{fileCount:e.length,functionCount:t,linesOfCode:n,maxCyclomaticComplexity:r,maxCognitiveComplexity:i,ncssCount:a,callCount:o,internalCallCount:s,maxCallDepth:c,importSourceCount:l,relativeImportCount:u,externalImportCount:d,exportCount:f,averageFunctionIdentifierOverlap:e.length===0?0:p/e.length,typeAnnotationCount:m,typeAliasCount:h,interfaceCount:g,genericParameterCount:_}}function K(e,t){return d.has(e)?!0:!t.includeTests&&f.has(e)}function q(e,t){let n=c.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${c.default.sep}`)&&!c.default.isAbsolute(n)}function J(e,t,n=!1){let r=e.toLowerCase();if(!(!n&&(r.endsWith(`.d.ts`)||r.endsWith(`.d.mts`)||r.endsWith(`.d.cts`)||r.endsWith(`.min.js`)||r.endsWith(`.pnp.cjs`)))&&!(!n&&!t.includeTests&&(p.test(c.default.basename(e))||m.test(c.default.basename(e)))))return c.default.extname(e)===`.C`?`cpp`:u.get(c.default.extname(r))}function se(e){let t=Y(e);if(t>100)throw new l.InvalidArgumentError(`Expected an integer between 1 and 100.`);return t}function ce(e){if(!/^\d+$/u.test(e))throw new l.InvalidArgumentError(`Expected a non-negative integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<0)throw new l.InvalidArgumentError(`Expected a non-negative integer.`);return t}function Y(e){if(!/^[1-9]\d*$/u.test(e))throw new l.InvalidArgumentError(`Expected a positive integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<1)throw new l.InvalidArgumentError(`Expected a positive integer.`);return t}function X(e,t){return c.default.relative(t,e)||c.default.basename(e)}function Z(e){process.stdout.write(e)}function Q(e){process.stderr.write(e)}function $(e){return e instanceof Error?e.message:String(e)}
2
+ "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./crossFileDuplication.cjs"),n=require("./metrics.cjs"),r=require("./architectureMetrics.cjs"),i=require("./cliConfig.cjs"),a=require("./typescriptProject.cjs");let o=require("node:fs/promises"),s=require("node:os");s=e.__toESM(s,1);let c=require("node:path");c=e.__toESM(c,1);let l=require("commander");const u=new Map([[`.c`,`c`],[`.c++`,`cpp`],[`.cc`,`cpp`],[`.cjs`,`javascript`],[`.cp`,`cpp`],[`.cpp`,`cpp`],[`.tcc`,`cpp`],[`.cts`,`typescript`],[`.cxx`,`cpp`],[`.go`,`go`],[`.h`,`cpp`],[`.hh`,`cpp`],[`.hpp`,`cpp`],[`.hxx`,`cpp`],[`.java`,`java`],[`.js`,`javascript`],[`.jsx`,`jsx`],[`.mjs`,`javascript`],[`.mts`,`typescript`],[`.py`,`python`],[`.rb`,`ruby`],[`.rs`,`rust`],[`.ts`,`typescript`],[`.tsx`,`tsx`]]),d=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`target`,`test-fixtures`,`vendor`,`venv`]),f=new Set([`__tests__`,`test`,`tests`,`spec`]),p=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,m=/Test\.java$/u;h().catch(e=>{Q(`Error: ${$(e)}\n`),process.exitCode=1});async function h(){let e=new l.Command().name(`code-gauge`).description(`Measure code metrics and list high-risk findings.`).argument(`[target]`,`file or directory to measure`,`.`).option(`--config <path>`,`config file to use instead of the auto-detected ${i.configFileName}`).option(`--file-loc-threshold <number>`,`minimum file code LOC to report`,Y).option(`--function-loc-threshold <number>`,`minimum function physical LOC span to report`,Y).option(`--component-loc-threshold <number>`,`minimum React component physical LOC span to report`,Y).option(`--cognitive-threshold <number>`,`minimum cognitive complexity to report`,Y).option(`--cyclomatic-threshold <number>`,`minimum cyclomatic complexity to report`,Y).option(`--call-threshold <number>`,`minimum function call count to report`,Y).option(`--import-threshold <number>`,`minimum unique import sources per file to report`,Y).option(`--fan-out-threshold <number>`,`minimum intra-file fan-out per function to report`,Y).option(`--parameter-threshold <number>`,`minimum function parameter count to report`,Y).option(`--duplicate-block-threshold <number>`,`minimum count of duplicated code blocks per file to report`,Y).option(`--duplication-ratio-percent-threshold <number>`,`minimum percentage (1-100) of duplicated lines per file to report`,J).option(`--cross-file-duplicate-block-threshold <number>`,`minimum count of cross-file duplicate block groups per file to report`,Y).option(`--duplication-min-tokens <number>`,`minimum normalized token count for a duplicate region (default 40)`,Y).option(`--duplication-max-gap-tokens <number>`,`maximum token gap merged into one gapped clone group; 0 disables merging (default 30)`,ce).option(`--duplication-min-similarity-percent <number>`,`minimum similarity percent (1-100) for near-miss (Type-3) clone blocks; 100 reports exact matches only (default 70)`,J).option(`--transitive-dependency-threshold <number>`,`minimum transitively reachable local files to report`,Y).option(`--structural-breadth-threshold <number>`,`minimum structural breadth score to report`,Y).option(`--structural-coordination-threshold <number>`,`minimum structural coordination score to report`,Y).option(`--state-mutation-threshold <number>`,`minimum state mutation score to report`,Y).option(`--duplicate-symbol-group-threshold <number>`,`minimum duplicate symbol group count to report`,Y).option(`--max-findings <number>`,`maximum number of risk findings to print`,Y).option(`--largest-files <number>`,`number of largest files by code LOC to list`,Y).option(`--include-tests`,`include test files and test directories`).option(`--tsconfig <path>`,`TypeScript project file to use instead of auto-detected tsconfig.json`).option(`--json`,`print JSON output`).option(`--fail-on-error`,`exit with code 1 when files or directories cannot be scanned`).option(`--fail-on-risk`,`exit with code 1 when high-risk findings are found`);e.action(async(e,t)=>{let n=g(e),r=await i.loadConfig(t.config,await _(n)),a=i.resolveOptions(t,r),o=await v(n,a);T(o,a),await ne(o),await ee(o,a,n);let s=ie(o.files,o.architecture,o.crossFileDuplication,o.componentFunctionKeys,o.namedComponentFunctionKeys,a,o.displayRoot);a.json?P(o,s,a):F(n,o,s,a),(o.fatalError||a.failOnError&&o.errors.length>0||a.failOnRisk&&s.length>0)&&(process.exitCode=1)}),await e.parseAsync()}function g(e){return e===`~`?s.default.homedir():e.startsWith(`~/`)?c.default.join(s.default.homedir(),e.slice(2)):c.default.resolve(e)}async function _(e){try{return(await(0,o.stat)(e)).isDirectory()?e:c.default.dirname(e)}catch{return c.default.dirname(e)}}async function v(e,t){let n=[],r=[],i=[],a=e;try{a=await(0,o.realpath)(e)}catch{}let s=c.default.dirname(a),l;try{l=await(0,o.stat)(a)}catch(e){let t=`${X(a,s)}: ${$(e)}`;return{displayRoot:s,files:n,errors:[t],warnings:i,fatalError:t}}if(l.isFile()){let e=c.default.dirname(a),o=q(a,t,!0);if(!o){let t=`${X(a,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let s=y(t,n,r,i,e);return await w(a,o,`single-file`,s,a),{displayRoot:e,files:n,errors:r,warnings:i}}return await S(a,y(t,n,r,i,a)),{displayRoot:a,files:n,errors:r,warnings:i}}function y(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function ee(e,t,n){if(e.fatalError||e.files.length===0)return;let r=t.tsconfig,i=r!==void 0;if(!i&&!e.files.some(({file:e})=>b(e)))return;let o=r?g(r):await x(n);if(o)try{e.typeScriptProject=await a.measureTypeScriptProject(o,e.files.map(({file:e})=>e)),e.componentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.map(e=>A(e.file,e.startLine,e.startColumn))),e.namedComponentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.flatMap(e=>e.name?[j(e.file,e.name,e.startLine)]:[]))}catch(t){i&&e.errors.push(`${X(o,e.displayRoot)}: ${$(t)}`)}}function b(e){return[`.cjs`,`.cts`,`.js`,`.jsx`,`.mjs`,`.mts`,`.ts`,`.tsx`].includes(c.default.extname(e))}async function x(e){let t=(await(0,o.stat)(e)).isDirectory()?e:c.default.dirname(e);for(;;){let e=c.default.join(t,`tsconfig.json`);if(await te(e))return e;let n=c.default.dirname(t);if(n===t)return;t=n}}async function te(e){try{return(await(0,o.stat)(e)).isFile()}catch{return!1}}async function ne(e){if(!e.fatalError)try{e.architecture=r.measureArchitecture(e.files.map(({file:e,metrics:t})=>({file:e,metrics:t})),e.displayRoot)}catch(t){e.errors.push(`architecture metrics: ${$(t)}`)}}async function S(e,t){let n;try{n=await(0,o.realpath)(e)}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}if(!K(n,t.rootDirectory)||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r;try{r=await(0,o.readdir)(e,{withFileTypes:!0})}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}for(let n of r){let r=c.default.join(e,n.name);if(n.isSymbolicLink()){await re(n.name,r,t);continue}if(n.isDirectory()){if(G(n.name,t.options))continue;await S(r,t);continue}n.isFile()&&await C(r,t)}}async function re(e,t,n){let r;try{r=await(0,o.realpath)(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(!K(r,n.rootDirectory))return;let i;try{i=await(0,o.stat)(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(i.isDirectory()){if(G(e,n.options)||G(c.default.basename(r),n.options))return;await S(t,n);return}i.isFile()&&await C(t,n,r,r)}async function C(e,t,n=e,r){let i=q(n,t.options);i&&await w(e,i,`directory`,t,r)}async function w(e,t,r,i,a){try{let s=a??await(0,o.realpath)(e);if(i.visitedFiles.has(s))return;i.visitedFiles.add(s);let c=await(0,o.readFile)(e,`utf8`),l={language:t,duplication:i.options.duplication},u={file:e,metrics:n.measureCode(c,l)};if(r===`directory`)try{u.duplicationCandidates=n.collectCrossFileDuplicationFileData(c,l)}catch(t){i.warnings.push(`${X(e,i.rootDirectory)}: cross-file duplication candidates unavailable: ${$(t)}`)}i.files.push(u)}catch(t){i.errors.push(`${X(e,i.rootDirectory)}: ${$(t)}`)}}function T(e,n){if(e.fatalError||e.files.length<2)return;let r=e.files.flatMap(({file:t,duplicationCandidates:n})=>n?[{file:X(t,e.displayRoot),...n}]:[]);r.length<2||(e.crossFileDuplication=t.measureCrossFileDuplication(r,n.duplication))}function ie(e,t,n,r,a,o,s){let c=new Map(t?.files.map(e=>[e.file,e])),l=e.flatMap(({file:e,metrics:t})=>{let l=t.functions.some(t=>t.returnsJsx||k(e,t,r,a)),u=i.resolveThresholds(o,t.language,l);return[...ae(e,t,c.get(X(e,s)),n,u,s),...t.functions.flatMap(n=>oe(e,t.language,n,u,s,r,a))]});return l.sort(N),l}function ae(e,t,n,r,i,a){let o=[],s=X(e,a);E(o,`file LOC`,t.lines.code,i.fileLoc),E(o,`import sources`,t.coupling.importSourceCount,i.import);let c=O(t.duplication.duplicateBlockGroups);return E(o,`duplicated blocks`,t.duplication.duplicateBlockCount,i.duplicateBlock,c),E(o,`duplicated lines (%)`,Math.floor(t.duplication.duplicationRatio*100),i.duplicationRatioPercent,c),r&&E(o,`cross-file duplicated blocks`,Object.hasOwn(r.duplicateBlockGroupCountByFile,s)?r.duplicateBlockGroupCountByFile[s]??0:0,i.crossFileDuplicateBlock,D(r,s)),n&&((t.lines.code>=100||n.directLocalDependencyCount>=8)&&E(o,`transitive local dependencies`,n.transitiveLocalDependencyCount,i.transitiveDependency),(o.length>0||n.directLocalDependencyCount>=8||n.structuralCoordination.score>=i.structuralCoordination)&&E(o,`structural breadth`,n.structuralBreadthScore,i.structuralBreadth),E(o,`structural coordination`,n.structuralCoordination.score,i.structuralCoordination),E(o,`state mutation`,n.structuralCoordination.stateMutationScore,i.stateMutation),E(o,`duplicate symbol groups`,n.duplicateSymbolGroupCount,i.duplicateSymbolGroup)),o.length===0?[]:[{file:s,language:t.language,kind:`file`,cyclomaticComplexity:t.cyclomaticComplexity,cognitiveComplexity:t.cognitiveComplexity,triggers:o,score:M(o)}]}function oe(e,t,n,r,i,a,o){let s=n.endLine-n.startLine+1,c=k(e,n,a,o),l=c?`component`:`function`,u=[];return E(u,`cognitive complexity`,n.cognitiveComplexity,r.cognitive),E(u,`cyclomatic complexity`,n.cyclomaticComplexity,r.cyclomatic),E(u,c?`component LOC`:`function LOC`,s,se(c,r)),E(u,`function calls`,n.callCount,r.call),E(u,`fan-out`,n.fanOut,r.fanOut),E(u,`parameters`,n.parameterCount,r.parameter),u.length===0?[]:[{file:X(e,i),language:t,kind:l,name:n.name??`<anonymous>`,startLine:n.startLine,endLine:n.endLine,cyclomaticComplexity:n.cyclomaticComplexity,cognitiveComplexity:n.cognitiveComplexity,triggers:u,score:M(u)}]}function E(e,t,n,r,i){n<r||e.push({metric:t,value:n,threshold:r,score:n/r,detail:i})}function D(e,t){let n=e.groups.filter(e=>e.files.includes(t));if(n.length!==0)return`${n.slice(0,3).map(e=>e.occurrences.map(({file:e,startLine:n,endLine:r})=>e===t?`${n}-${r}`:`${e}:${n}-${r}`).join(` ~ `)).join(`; `)}${n.length>3?`; ...`:``}`}function O(e){if(e.length!==0)return e.map(e=>e.map(({startLine:e,endLine:t})=>`${e}-${t}`).join(` ~ `)).join(`; `)}function k(e,t,n,r){return n?.has(A(e,t.startLine,t.startColumn))||(t.name?r?.has(j(e,t.name,t.startLine)):!1)||!1}function se(e,t){return e?t.componentLoc:t.functionLoc}function A(e,t,n){return`${c.default.resolve(e)}:${t}:${n}`}function j(e,t,n){return`${c.default.resolve(e)}:${t}:${n}`}function M(e){return Math.max(...e.map(e=>e.score))}function N(e,t){return t.score-e.score||e.file.localeCompare(t.file)||(e.startLine??0)-(t.startLine??0)||(e.endLine??0)-(t.endLine??0)||e.kind.localeCompare(t.kind)}function P(e,t,n){let r=W(e.files),i=t.slice(0,n.maxFindings);Z(JSON.stringify({summary:r,thresholds:n.thresholds,profileThresholds:n.profileThresholds,totalRisks:t.length,truncated:i.length<t.length,largestFiles:n.largestFiles>0?I(e.files,n.largestFiles,e.displayRoot):void 0,architecture:e.architecture,crossFileDuplication:e.crossFileDuplication,typeScriptProject:e.typeScriptProject,risks:i,errors:e.errors,warnings:e.warnings},void 0,2)+`
3
+ `)}function F(e,t,n,r){if(t.fatalError){Q(`Error: ${t.fatalError}\n`);return}let{thresholds:i}=r,a=W(t.files);Z(`Measured ${a.fileCount} files under ${e}\n`),Z(`LOC ${a.linesOfCode}, NCSS ${a.ncssCount}, functions ${a.functionCount}, max cyclomatic ${a.maxCyclomaticComplexity}, max cognitive ${a.maxCognitiveComplexity}\n`),Z(`Calls ${a.callCount}, internal edges ${a.internalEdgeCount}, max call depth ${a.maxCallDepth}, imports ${a.importSourceCount}, exports ${a.exportCount}\n`),Z(`Type annotations ${a.typeAnnotationCount}, type aliases ${a.typeAliasCount}, interfaces ${a.interfaceCount}, avg cohesion ${a.averageFunctionIdentifierOverlap.toFixed(2)}\n`),t.architecture&&Z(`${H(t.architecture)}\n`),t.typeScriptProject&&Z(`${U(t.typeScriptProject)}\n`),Z(`Risk thresholds: file LOC >= ${i.fileLoc}, function LOC >= ${i.functionLoc}, component LOC >= ${i.componentLoc}, cognitive >= ${i.cognitive}, cyclomatic >= ${i.cyclomatic}, calls >= ${i.call}, imports >= ${i.import}, fan-out >= ${i.fanOut}, parameters >= ${i.parameter}, duplicated blocks >= ${i.duplicateBlock}, duplicated lines (%) >= ${i.duplicationRatioPercent}, cross-file duplicated blocks >= ${i.crossFileDuplicateBlock}\n`);let o=L(r.profileThresholds);if(o&&Z(`Per-language overrides: ${o}\n`),n.length===0)Z(`No high-risk findings found.
4
+ `);else{let e=n.slice(0,r.maxFindings),t=n.length>e.length?` of ${n.length}`:``;Z(`\nHigh-risk findings (top ${e.length}${t}):\n`);for(let t of e)Z(`${R(t)} ${z(t)} ${B(t)}\n`)}let s=t.crossFileDuplication?.groups??[];if(s.length>0){let e=s.slice(0,10),t=s.length>e.length?` of ${s.length}`:``;Z(`\nCross-file duplicate blocks (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.tokenCount} tokens: ${t.occurrences.map(({file:e,startLine:t,endLine:n})=>`${e}:${t}-${n}`).join(`, `)}\n`)}let c=t.architecture?.duplicateSymbolGroups??[];if(c.length>0){let e=c.toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name)).slice(0,10),t=c.length>e.length?` of ${c.length}`:``;Z(`\nDuplicate symbols (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.name}: ${t.declarations.map(e=>`${e.file}:${e.line}`).join(`, `)}\n`)}if(r.largestFiles>0){let e=I(t.files,r.largestFiles,t.displayRoot);Z(`\nLargest files by code LOC (top ${e.length}):\n`);for(let{file:t,codeLoc:n}of e)Z(`${t} (code LOC ${n})\n`)}if(t.warnings.length>0){Q(`\nDegraded ${t.warnings.length} files (measured, but excluded from cross-file matching):\n`);for(let e of t.warnings.slice(0,10))Q(`- ${e}\n`);t.warnings.length>10&&Q(`- ... ${t.warnings.length-10} more\n`)}if(t.errors.length>0){Q(`\nSkipped ${t.errors.length} files or directories:\n`);for(let e of t.errors.slice(0,10))Q(`- ${e}\n`);t.errors.length>10&&Q(`- ... ${t.errors.length-10} more\n`)}}function I(e,t,n){return e.map(({file:e,metrics:t})=>({file:X(e,n),codeLoc:t.lines.code})).toSorted((e,t)=>t.codeLoc-e.codeLoc||e.file.localeCompare(t.file)).slice(0,t)}function L(e){return Object.entries(e).map(([e,t])=>`${e} { ${Object.entries(t).map(([e,t])=>`${e} ${t}`).join(`, `)} }`).join(`; `)}function R(e){return e.startLine===void 0||e.endLine===void 0?e.file:`${e.file}:${e.startLine}-${e.endLine}`}function z(e){return e.name?`${e.kind} ${e.name}`:e.kind}function B(e){return`(${e.triggers.map(e=>`${e.metric} ${V(e.value)} >= ${V(e.threshold)}${e.detail?` [${e.detail}]`:``}`).join(`, `)}; cyclomatic ${e.cyclomaticComplexity}, cognitive ${e.cognitiveComplexity})`}function V(e){return Number.isInteger(e)?String(e):e.toFixed(2)}function H(e){let t=Math.max(0,...e.files.map(e=>e.structuralCoordination.stateMutationScore));return`Architecture max reachable files ${e.maxTransitiveLocalDependencyCount}, max structural breadth ${e.maxStructuralBreadthScore}, max structural coordination ${e.maxStructuralCoordinationScore}, max state mutation ${t}, duplicate symbol groups ${e.duplicateSymbolGroups.length}`}function U(e){return`TypeScript project root files ${e.rootFileCount}, measured roots ${e.measuredRootFileCount}, semantic diagnostics ${e.semanticDiagnosticCount}, resolved calls ${e.resolvedCallExpressionCount}/${e.callExpressionCount} (${(e.resolvedCallExpressionRatio*100).toFixed(1)}%)`}function W(e){let t=0,n=0,r=0,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0,_=0;for(let v of e)t+=v.metrics.functionCount,n+=v.metrics.lines.code,r=Math.max(r,v.metrics.maxCyclomaticComplexity),i=Math.max(i,v.metrics.maxCognitiveComplexity),a+=v.metrics.ncssCount,o+=v.metrics.callGraph.callCount,s+=v.metrics.callGraph.internalEdgeCount,c=Math.max(c,v.metrics.callGraph.maxCallDepth),l+=v.metrics.coupling.importSourceCount,u+=v.metrics.coupling.relativeImportCount,d+=v.metrics.coupling.externalImportCount,f+=v.metrics.coupling.exportCount,p+=v.metrics.cohesion.averageFunctionIdentifierOverlap,m+=v.metrics.typeComplexity.typeAnnotationCount,h+=v.metrics.typeComplexity.typeAliasCount,g+=v.metrics.typeComplexity.interfaceCount,_+=v.metrics.typeComplexity.genericParameterCount;return{fileCount:e.length,functionCount:t,linesOfCode:n,maxCyclomaticComplexity:r,maxCognitiveComplexity:i,ncssCount:a,callCount:o,internalEdgeCount:s,maxCallDepth:c,importSourceCount:l,relativeImportCount:u,externalImportCount:d,exportCount:f,averageFunctionIdentifierOverlap:e.length===0?0:p/e.length,typeAnnotationCount:m,typeAliasCount:h,interfaceCount:g,genericParameterCount:_}}function G(e,t){return d.has(e)?!0:!t.includeTests&&f.has(e)}function K(e,t){let n=c.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${c.default.sep}`)&&!c.default.isAbsolute(n)}function q(e,t,n=!1){let r=e.toLowerCase();if(!(!n&&(r.endsWith(`.d.ts`)||r.endsWith(`.d.mts`)||r.endsWith(`.d.cts`)||r.endsWith(`.min.js`)||r.endsWith(`.pnp.cjs`)))&&!(!n&&!t.includeTests&&(p.test(c.default.basename(e))||m.test(c.default.basename(e)))))return c.default.extname(e)===`.C`?`cpp`:u.get(c.default.extname(r))}function J(e){let t=Y(e);if(t>100)throw new l.InvalidArgumentError(`Expected an integer between 1 and 100.`);return t}function ce(e){if(!/^\d+$/u.test(e))throw new l.InvalidArgumentError(`Expected a non-negative integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<0)throw new l.InvalidArgumentError(`Expected a non-negative integer.`);return t}function Y(e){if(!/^[1-9]\d*$/u.test(e))throw new l.InvalidArgumentError(`Expected a positive integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<1)throw new l.InvalidArgumentError(`Expected a positive integer.`);return t}function X(e,t){return c.default.relative(t,e)||c.default.basename(e)}function Z(e){process.stdout.write(e)}function Q(e){process.stderr.write(e)}function $(e){return e instanceof Error?e.message:String(e)}
5
5
  //# sourceMappingURL=cli.cjs.map
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","names":["Command","configFileName","loadConfig","resolveOptions","os","path","stat","realpath","measureTypeScriptProject","measureArchitecture","readdir","readFile","measureCode","collectDuplicationCandidates","measureCrossFileDuplication","resolveThresholds","InvalidArgumentError"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readdir, readFile, realpath, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { measureArchitecture, type ArchitectureFileMetrics, type ArchitectureMetrics } from './architectureMetrics.js';\nimport {\n type CliOptions,\n configFileName,\n loadConfig,\n type ResolvedOptions,\n resolveOptions,\n resolveThresholds,\n type Thresholds,\n} from './cliConfig.js';\nimport { measureCrossFileDuplication, type CrossFileDuplicationMetrics } from './crossFileDuplication.js';\nimport type { CrossFileDuplicateCandidate } from './duplication.js';\nimport { collectDuplicationCandidates, measureCode } from './metrics.js';\nimport { measureTypeScriptProject, type TypeScriptProjectMetrics } from './typescriptProject.js';\nimport type { CodeMetrics, FunctionMetrics, LanguageName } from './types.js';\n\ninterface FileMetrics {\n file: string;\n metrics: CodeMetrics;\n /** Cross-file duplicate candidates, collected only for directory scans. */\n duplicationCandidates?: CrossFileDuplicateCandidate[];\n}\n\ninterface RiskTrigger {\n /** Optional location hint (e.g. duplicated block line ranges) appended to the printed trigger. */\n detail?: string;\n metric: string;\n score: number;\n threshold: number;\n value: number;\n}\n\ninterface RiskFinding {\n cognitiveComplexity: number;\n cyclomaticComplexity: number;\n endLine?: number;\n file: string;\n kind: 'component' | 'file' | 'function';\n language: LanguageName;\n name?: string;\n score: number;\n startLine?: number;\n triggers: RiskTrigger[];\n}\n\ninterface ScanResult {\n architecture?: ArchitectureMetrics;\n componentFunctionKeys?: Set<string>;\n crossFileDuplication?: CrossFileDuplicationMetrics;\n displayRoot: string;\n errors: string[];\n /** Non-fatal degradations (e.g. cross-file candidates unavailable); the file is still measured. */\n warnings: string[];\n fatalError?: string;\n files: FileMetrics[];\n namedComponentFunctionKeys?: Set<string>;\n typeScriptProject?: TypeScriptProjectMetrics;\n}\n\nconst languageByExtension = new Map<string, LanguageName>([\n ['.c', 'c'],\n ['.c++', 'cpp'],\n ['.cc', 'cpp'],\n ['.cjs', 'javascript'],\n ['.cp', 'cpp'],\n ['.cpp', 'cpp'],\n ['.tcc', 'cpp'],\n ['.cts', 'typescript'],\n ['.cxx', 'cpp'],\n ['.go', 'go'],\n // Headers may be C or C++; the C++ grammar parses both.\n ['.h', 'cpp'],\n ['.hh', 'cpp'],\n ['.hpp', 'cpp'],\n ['.hxx', 'cpp'],\n ['.java', 'java'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rb', 'ruby'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\nconst ignoredDirectoryNames = new Set([\n '.agents',\n '.claude',\n '.cursor',\n '.git',\n '.next',\n '.playwright-cli',\n '.tox',\n '.tmp',\n '.turbo',\n '.venv',\n '.yarn',\n '__fixtures__',\n '__generated__',\n '__pycache__',\n 'coverage',\n 'dist',\n 'fixtures',\n 'generated',\n 'node_modules',\n 'target',\n 'test-fixtures',\n 'vendor',\n 'venv',\n]);\n\n/** Caps the `Duplicate symbols` section so large repositories do not flood the report. */\nconst maxDuplicateSymbolGroupLines = 10;\n/** Caps the `Cross-file duplicate blocks` section so large repositories do not flood the report. */\nconst maxCrossFileDuplicateGroupLines = 10;\n/** Caps how many cross-file group locations a single risk finding repeats as detail. */\nconst maxCrossFileDuplicateDetailGroups = 3;\n\nconst testDirectoryNames = new Set(['__tests__', 'test', 'tests', 'spec']);\nconst testFilePattern = /(?:^test(?:[_-].*)?|\\.(?:spec|test)|[_-](?:test|spec))\\.[^.]+$/iu;\n// JUnit tests use a case-sensitive `Test.java` suffix; case-insensitive matching would catch\n// production files like `contest.java`.\nconst javaTestFilePattern = /Test\\.java$/u;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- CommonJS build output cannot preserve top-level await.\nvoid main().catch((error: unknown) => {\n writeStderr(`Error: ${formatError(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const program = new Command()\n .name('code-gauge')\n .description('Measure code metrics and list high-risk findings.')\n .argument('[target]', 'file or directory to measure', '.')\n .option('--config <path>', `config file to use instead of the auto-detected ${configFileName}`)\n .option('--file-loc-threshold <number>', 'minimum file code LOC to report', parsePositiveInteger)\n .option('--function-loc-threshold <number>', 'minimum function physical LOC span to report', parsePositiveInteger)\n .option(\n '--component-loc-threshold <number>',\n 'minimum React component physical LOC span to report',\n parsePositiveInteger\n )\n .option('--cognitive-threshold <number>', 'minimum cognitive complexity to report', parsePositiveInteger)\n .option('--cyclomatic-threshold <number>', 'minimum cyclomatic complexity to report', parsePositiveInteger)\n .option('--call-threshold <number>', 'minimum function call count to report', parsePositiveInteger)\n .option('--import-threshold <number>', 'minimum unique import sources per file to report', parsePositiveInteger)\n .option('--fan-out-threshold <number>', 'minimum intra-file fan-out per function to report', parsePositiveInteger)\n .option('--parameter-threshold <number>', 'minimum function parameter count to report', parsePositiveInteger)\n .option(\n '--duplicate-block-threshold <number>',\n 'minimum count of duplicated code blocks per file to report',\n parsePositiveInteger\n )\n .option(\n '--duplication-ratio-percent-threshold <number>',\n 'minimum percentage (1-100) of duplicated lines per file to report',\n parsePercentInteger\n )\n .option(\n '--cross-file-duplicate-block-threshold <number>',\n 'minimum count of cross-file duplicate block groups per file to report',\n parsePositiveInteger\n )\n .option(\n '--duplication-min-tokens <number>',\n 'minimum normalized token count for a duplicate region (default 40)',\n parsePositiveInteger\n )\n .option(\n '--duplication-max-gap-tokens <number>',\n 'maximum token gap merged into one gapped clone group; 0 disables merging (default 30)',\n parseNonNegativeInteger\n )\n .option(\n '--transitive-dependency-threshold <number>',\n 'minimum transitively reachable local files to report',\n parsePositiveInteger\n )\n .option(\n '--structural-breadth-threshold <number>',\n 'minimum structural breadth score to report',\n parsePositiveInteger\n )\n .option(\n '--structural-coordination-threshold <number>',\n 'minimum structural coordination score to report',\n parsePositiveInteger\n )\n .option('--state-mutation-threshold <number>', 'minimum state mutation score to report', parsePositiveInteger)\n .option(\n '--duplicate-symbol-group-threshold <number>',\n 'minimum duplicate symbol group count to report',\n parsePositiveInteger\n )\n .option('--max-findings <number>', 'maximum number of risk findings to print', parsePositiveInteger)\n .option('--largest-files <number>', 'number of largest files by code LOC to list', parsePositiveInteger)\n .option('--include-tests', 'include test files and test directories')\n .option('--tsconfig <path>', 'TypeScript project file to use instead of auto-detected tsconfig.json')\n .option('--json', 'print JSON output')\n .option('--fail-on-error', 'exit with code 1 when files or directories cannot be scanned')\n .option('--fail-on-risk', 'exit with code 1 when high-risk findings are found');\n\n program.action(async (target: string, cliOptions: CliOptions) => {\n const resolvedTarget = resolveTarget(target);\n const config = await loadConfig(cliOptions.config, await configSearchDirectory(resolvedTarget));\n const options = resolveOptions(cliOptions, config);\n const result = await scanTarget(resolvedTarget, options);\n addCrossFileDuplication(result);\n await addArchitectureMetrics(result);\n await addTypeScriptProjectMetrics(result, options, resolvedTarget);\n const risks = findRiskyFunctions(\n result.files,\n result.architecture,\n result.crossFileDuplication,\n result.componentFunctionKeys,\n result.namedComponentFunctionKeys,\n options,\n result.displayRoot\n );\n\n if (options.json) {\n printJson(result, risks, options);\n } else {\n printTextReport(resolvedTarget, result, risks, options);\n }\n\n if (\n result.fatalError ||\n (options.failOnError && result.errors.length > 0) ||\n (options.failOnRisk && risks.length > 0)\n ) {\n process.exitCode = 1;\n }\n });\n\n await program.parseAsync();\n}\n\nfunction resolveTarget(target: string): string {\n if (target === '~') {\n return os.homedir();\n }\n\n if (target.startsWith('~/')) {\n return path.join(os.homedir(), target.slice(2));\n }\n\n return path.resolve(target);\n}\n\n/** Returns the directory from which the config file search should start (the target itself if it is a directory). */\nasync function configSearchDirectory(target: string): Promise<string> {\n try {\n const targetStat = await stat(target);\n return targetStat.isDirectory() ? target : path.dirname(target);\n } catch {\n return path.dirname(target);\n }\n}\n\n/** Shared state of one scan, threaded through the directory walk instead of positional plumbing. */\ninterface ScanContext {\n options: ResolvedOptions;\n files: FileMetrics[];\n errors: string[];\n warnings: string[];\n visitedDirectories: Set<string>;\n visitedFiles: Set<string>;\n /** Scan root: paths are displayed relative to it, and symbolic links may not escape it. */\n rootDirectory: string;\n}\n\nasync function scanTarget(target: string, options: ResolvedOptions): Promise<ScanResult> {\n const files: FileMetrics[] = [];\n const errors: string[] = [];\n const warnings: string[] = [];\n let canonicalTarget = target;\n try {\n canonicalTarget = await realpath(target);\n } catch {\n // stat below reports missing targets with the original path.\n }\n\n const fallbackDisplayRoot = path.dirname(canonicalTarget);\n let targetStat;\n\n try {\n targetStat = await stat(canonicalTarget);\n } catch (error) {\n const fatalError = `${formatPath(canonicalTarget, fallbackDisplayRoot)}: ${formatError(error)}`;\n return { displayRoot: fallbackDisplayRoot, files, errors: [fatalError], warnings, fatalError };\n }\n\n if (targetStat.isFile()) {\n const displayRoot = path.dirname(canonicalTarget);\n const language = getLanguage(canonicalTarget, options, true);\n if (!language) {\n const fatalError = `${formatPath(canonicalTarget, displayRoot)}: unsupported file type`;\n return { displayRoot, files, errors: [fatalError], warnings, fatalError };\n }\n\n const context = makeScanContext(options, files, errors, warnings, displayRoot);\n await measureFile(canonicalTarget, language, 'single-file', context, canonicalTarget);\n return { displayRoot, files, errors, warnings };\n }\n\n await scanDirectory(canonicalTarget, makeScanContext(options, files, errors, warnings, canonicalTarget));\n return { displayRoot: canonicalTarget, files, errors, warnings };\n}\n\nfunction makeScanContext(\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n warnings: string[],\n rootDirectory: string\n): ScanContext {\n return { options, files, errors, warnings, visitedDirectories: new Set(), visitedFiles: new Set(), rootDirectory };\n}\n\nasync function addTypeScriptProjectMetrics(\n result: ScanResult,\n options: ResolvedOptions,\n resolvedTarget: string\n): Promise<void> {\n if (result.fatalError) {\n return;\n }\n if (result.files.length === 0) {\n return;\n }\n\n const explicitConfigFile = options.tsconfig;\n const isExplicitConfig = explicitConfigFile !== undefined;\n if (!isExplicitConfig && !result.files.some(({ file }) => isTypeScriptProjectCandidateFile(file))) {\n return;\n }\n\n const configFile = explicitConfigFile ? resolveTarget(explicitConfigFile) : await findNearestTsconfig(resolvedTarget);\n if (!configFile) {\n return;\n }\n\n try {\n result.typeScriptProject = await measureTypeScriptProject(\n configFile,\n result.files.map(({ file }) => file)\n );\n result.componentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.map((component) =>\n functionLocationKey(component.file, component.startLine, component.startColumn)\n )\n );\n result.namedComponentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.flatMap((component) =>\n component.name ? [functionNameLocationKey(component.file, component.name, component.startLine)] : []\n )\n );\n } catch (error) {\n if (isExplicitConfig) {\n result.errors.push(`${formatPath(configFile, result.displayRoot)}: ${formatError(error)}`);\n }\n }\n}\n\nfunction isTypeScriptProjectCandidateFile(file: string): boolean {\n return ['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'].includes(path.extname(file));\n}\n\nasync function findNearestTsconfig(target: string): Promise<string | undefined> {\n const targetStat = await stat(target);\n let currentDirectory = targetStat.isDirectory() ? target : path.dirname(target);\n while (true) {\n const configFile = path.join(currentDirectory, 'tsconfig.json');\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nasync function addArchitectureMetrics(result: ScanResult): Promise<void> {\n if (result.fatalError) {\n return;\n }\n\n try {\n result.architecture = measureArchitecture(\n result.files.map(({ file, metrics }) => ({ file, metrics })),\n result.displayRoot\n );\n } catch (error) {\n result.errors.push(`architecture metrics: ${formatError(error)}`);\n }\n}\n\nasync function scanDirectory(directory: string, context: ScanContext): Promise<void> {\n let resolvedDirectory;\n try {\n resolvedDirectory = await realpath(directory);\n } catch (error) {\n context.errors.push(`${formatPath(directory, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedDirectory, context.rootDirectory)) {\n return;\n }\n\n if (context.visitedDirectories.has(resolvedDirectory)) {\n return;\n }\n context.visitedDirectories.add(resolvedDirectory);\n\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch (error) {\n context.errors.push(`${formatPath(directory, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n await scanSymbolicLink(entry.name, entryPath, context);\n continue;\n }\n\n if (entry.isDirectory()) {\n if (shouldSkipDirectory(entry.name, context.options)) {\n continue;\n }\n await scanDirectory(entryPath, context);\n continue;\n }\n\n if (entry.isFile()) {\n await measureScannableFile(entryPath, context);\n }\n }\n}\n\nasync function scanSymbolicLink(name: string, entryPath: string, context: ScanContext): Promise<void> {\n let resolvedPath;\n try {\n resolvedPath = await realpath(entryPath);\n } catch (error) {\n context.errors.push(`${formatPath(entryPath, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedPath, context.rootDirectory)) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await stat(entryPath);\n } catch (error) {\n context.errors.push(`${formatPath(entryPath, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (entryStat.isDirectory()) {\n if (\n shouldSkipDirectory(name, context.options) ||\n shouldSkipDirectory(path.basename(resolvedPath), context.options)\n ) {\n return;\n }\n await scanDirectory(entryPath, context);\n return;\n }\n\n if (entryStat.isFile()) {\n await measureScannableFile(entryPath, context, resolvedPath, resolvedPath);\n }\n}\n\nasync function measureScannableFile(\n file: string,\n context: ScanContext,\n languageFile = file,\n realFile?: string\n): Promise<void> {\n const language = getLanguage(languageFile, context.options);\n if (language) {\n await measureFile(file, language, 'directory', context, realFile);\n }\n}\n\nasync function measureFile(\n file: string,\n language: LanguageName,\n mode: 'single-file' | 'directory',\n context: ScanContext,\n realFile?: string\n): Promise<void> {\n try {\n const resolvedFile = realFile ?? (await realpath(file));\n if (context.visitedFiles.has(resolvedFile)) {\n return;\n }\n context.visitedFiles.add(resolvedFile);\n\n const code = await readFile(file, 'utf8');\n const measureOptions = { language, duplication: context.options.duplication };\n const fileMetrics: FileMetrics = { file, metrics: measureCode(code, measureOptions) };\n // Only directory scans compare files against each other; a single-file target has no peers.\n // Candidate collection failing (it always parses with the JavaScript binding, which can give\n // up where the native backend measured fine) must not discard the measured metrics.\n if (mode === 'directory') {\n try {\n fileMetrics.duplicationCandidates = collectDuplicationCandidates(code, measureOptions);\n } catch (error) {\n // A warning, not an error: the file's metrics are complete, only its participation in\n // cross-file matching is lost, so it is not \"skipped\" and must not fail --fail-on-error.\n context.warnings.push(\n `${formatPath(file, context.rootDirectory)}: cross-file duplication candidates unavailable: ${formatError(error)}`\n );\n }\n }\n context.files.push(fileMetrics);\n } catch (error) {\n context.errors.push(`${formatPath(file, context.rootDirectory)}: ${formatError(error)}`);\n }\n}\n\n/** Runs after the scan so every measured file's candidates participate. */\nfunction addCrossFileDuplication(result: ScanResult): void {\n if (result.fatalError || result.files.length < 2) {\n return;\n }\n const sourceFiles = result.files.flatMap(({ file, duplicationCandidates }) =>\n duplicationCandidates ? [{ file: formatPath(file, result.displayRoot), candidates: duplicationCandidates }] : []\n );\n if (sourceFiles.length < 2) {\n return;\n }\n result.crossFileDuplication = measureCrossFileDuplication(sourceFiles);\n}\n\nfunction findRiskyFunctions(\n files: FileMetrics[],\n architecture: ArchitectureMetrics | undefined,\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined,\n options: ResolvedOptions,\n displayRoot: string\n): RiskFinding[] {\n const architectureByFile = new Map(architecture?.files.map((file) => [file.file, file]));\n const findings = files.flatMap(({ file, metrics }) => {\n const isReactFile = metrics.functions.some(\n (fn) => fn.returnsJsx || isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys)\n );\n const thresholds = resolveThresholds(options, metrics.language, isReactFile);\n return [\n ...findRiskyFileMetrics(\n file,\n metrics,\n architectureByFile.get(formatPath(file, displayRoot)),\n crossFileDuplication,\n thresholds,\n displayRoot\n ),\n ...metrics.functions.flatMap((fn) =>\n findRiskyFunctionMetrics(\n file,\n metrics.language,\n fn,\n thresholds,\n displayRoot,\n componentFunctionKeys,\n namedComponentFunctionKeys\n )\n ),\n ];\n });\n\n findings.sort(compareRiskFindings);\n return findings;\n}\n\nfunction findRiskyFileMetrics(\n file: string,\n metrics: CodeMetrics,\n architecture: ArchitectureFileMetrics | undefined,\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n thresholds: Thresholds,\n displayRoot: string\n): RiskFinding[] {\n const triggers: RiskTrigger[] = [];\n const formattedFile = formatPath(file, displayRoot);\n addTrigger(triggers, 'file LOC', metrics.lines.code, thresholds.fileLoc);\n addTrigger(triggers, 'import sources', metrics.coupling.importSourceCount, thresholds.import);\n const duplicateBlockDetail = formatDuplicateBlockGroups(metrics.duplication.duplicateBlockGroups);\n addTrigger(\n triggers,\n 'duplicated blocks',\n metrics.duplication.duplicateBlockCount,\n thresholds.duplicateBlock,\n duplicateBlockDetail\n );\n // Maximal-region selection deliberately compresses adjacent clones into few blocks, so severity\n // must track line coverage, not the block count. Flooring compares like the unrounded ratio\n // against the integer threshold (29.5% must not trigger a >= 30 threshold). The block ranges are\n // repeated as detail because this trigger can fire alone, and a percentage without locations is\n // not actionable.\n addTrigger(\n triggers,\n 'duplicated lines (%)',\n Math.floor(metrics.duplication.duplicationRatio * 100),\n thresholds.duplicationRatioPercent,\n duplicateBlockDetail\n );\n if (crossFileDuplication) {\n // Object.hasOwn: a file named like an Object.prototype member must not read an inherited value.\n addTrigger(\n triggers,\n 'cross-file duplicated blocks',\n Object.hasOwn(crossFileDuplication.duplicateBlockGroupCountByFile, formattedFile)\n ? (crossFileDuplication.duplicateBlockGroupCountByFile[formattedFile] ?? 0)\n : 0,\n thresholds.crossFileDuplicateBlock,\n formatCrossFileDuplicateDetail(crossFileDuplication, formattedFile)\n );\n }\n if (architecture) {\n const hasFileScaleRisk = metrics.lines.code >= 100 || architecture.directLocalDependencyCount >= 8;\n if (hasFileScaleRisk) {\n addTrigger(\n triggers,\n 'transitive local dependencies',\n architecture.transitiveLocalDependencyCount,\n thresholds.transitiveDependency\n );\n }\n if (\n triggers.length > 0 ||\n architecture.directLocalDependencyCount >= 8 ||\n architecture.structuralCoordination.score >= thresholds.structuralCoordination\n ) {\n addTrigger(triggers, 'structural breadth', architecture.structuralBreadthScore, thresholds.structuralBreadth);\n }\n addTrigger(\n triggers,\n 'structural coordination',\n architecture.structuralCoordination.score,\n thresholds.structuralCoordination\n );\n addTrigger(\n triggers,\n 'state mutation',\n architecture.structuralCoordination.stateMutationScore,\n thresholds.stateMutation\n );\n addTrigger(\n triggers,\n 'duplicate symbol groups',\n architecture.duplicateSymbolGroupCount,\n thresholds.duplicateSymbolGroup\n );\n }\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formattedFile,\n language: metrics.language,\n kind: 'file',\n cyclomaticComplexity: metrics.cyclomaticComplexity,\n cognitiveComplexity: metrics.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction findRiskyFunctionMetrics(\n file: string,\n language: LanguageName,\n fn: FunctionMetrics,\n thresholds: Thresholds,\n displayRoot: string,\n componentFunctionKeys?: Set<string>,\n namedComponentFunctionKeys?: Set<string>\n): RiskFinding[] {\n const loc = fn.endLine - fn.startLine + 1;\n const isComponent = isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys);\n const kind = isComponent ? 'component' : 'function';\n const triggers: RiskTrigger[] = [];\n addTrigger(triggers, 'cognitive complexity', fn.cognitiveComplexity, thresholds.cognitive);\n addTrigger(triggers, 'cyclomatic complexity', fn.cyclomaticComplexity, thresholds.cyclomatic);\n addTrigger(triggers, isComponent ? 'component LOC' : 'function LOC', loc, getLocThreshold(isComponent, thresholds));\n addTrigger(triggers, 'function calls', fn.callCount, thresholds.call);\n addTrigger(triggers, 'fan-out', fn.fanOut, thresholds.fanOut);\n addTrigger(triggers, 'parameters', fn.parameterCount, thresholds.parameter);\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formatPath(file, displayRoot),\n language,\n kind,\n name: fn.name ?? '<anonymous>',\n startLine: fn.startLine,\n endLine: fn.endLine,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction addTrigger(triggers: RiskTrigger[], metric: string, value: number, threshold: number, detail?: string): void {\n if (value < threshold) {\n return;\n }\n\n triggers.push({ metric, value, threshold, score: value / threshold, detail });\n}\n\n/** Formats the cross-file groups a file participates in as `12-34 ~ b.ts:56-78; ...` (capped). */\nfunction formatCrossFileDuplicateDetail(\n crossFileDuplication: CrossFileDuplicationMetrics,\n formattedFile: string\n): string | undefined {\n const involved = crossFileDuplication.groups.filter((group) => group.files.includes(formattedFile));\n if (involved.length === 0) {\n return undefined;\n }\n const formatted = involved\n .slice(0, maxCrossFileDuplicateDetailGroups)\n .map((group) =>\n group.occurrences\n .map(({ file, startLine, endLine }) =>\n file === formattedFile ? `${startLine}-${endLine}` : `${file}:${startLine}-${endLine}`\n )\n .join(' ~ ')\n )\n .join('; ');\n const truncatedSuffix = involved.length > maxCrossFileDuplicateDetailGroups ? '; ...' : '';\n return `${formatted}${truncatedSuffix}`;\n}\n\n/** Formats duplicated block groups as `12-34 ~ 56-78; 90-99 ~ 100-109` (copies joined by ` ~ `, groups by `; `). */\nfunction formatDuplicateBlockGroups(groups: { endLine: number; startLine: number }[][]): string | undefined {\n if (groups.length === 0) {\n return undefined;\n }\n\n return groups.map((group) => group.map(({ startLine, endLine }) => `${startLine}-${endLine}`).join(' ~ ')).join('; ');\n}\n\nfunction isReactComponent(\n file: string,\n fn: FunctionMetrics,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined\n): boolean {\n return (\n componentFunctionKeys?.has(functionLocationKey(file, fn.startLine, fn.startColumn)) ||\n (fn.name ? namedComponentFunctionKeys?.has(functionNameLocationKey(file, fn.name, fn.startLine)) : false) ||\n false\n );\n}\n\nfunction getLocThreshold(isComponent: boolean, thresholds: Thresholds): number {\n return isComponent ? thresholds.componentLoc : thresholds.functionLoc;\n}\n\nfunction functionLocationKey(file: string, startLine: number, startColumn: number): string {\n return `${path.resolve(file)}:${startLine}:${startColumn}`;\n}\n\nfunction functionNameLocationKey(file: string, name: string, startLine: number): string {\n return `${path.resolve(file)}:${name}:${startLine}`;\n}\n\nfunction maxTriggerScore(triggers: RiskTrigger[]): number {\n return Math.max(...triggers.map((trigger) => trigger.score));\n}\n\nfunction compareRiskFindings(left: RiskFinding, right: RiskFinding): number {\n return (\n right.score - left.score ||\n left.file.localeCompare(right.file) ||\n (left.startLine ?? 0) - (right.startLine ?? 0) ||\n (left.endLine ?? 0) - (right.endLine ?? 0) ||\n left.kind.localeCompare(right.kind)\n );\n}\n\nfunction printJson(result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n const summary = summarize(result.files);\n const reportedRisks = risks.slice(0, options.maxFindings);\n writeStdout(\n JSON.stringify(\n {\n summary,\n thresholds: options.thresholds,\n profileThresholds: options.profileThresholds,\n totalRisks: risks.length,\n truncated: reportedRisks.length < risks.length,\n largestFiles:\n options.largestFiles > 0\n ? findLargestFiles(result.files, options.largestFiles, result.displayRoot)\n : undefined,\n architecture: result.architecture,\n crossFileDuplication: result.crossFileDuplication,\n typeScriptProject: result.typeScriptProject,\n risks: reportedRisks,\n errors: result.errors,\n warnings: result.warnings,\n },\n undefined,\n 2\n ) + '\\n'\n );\n}\n\nfunction printTextReport(target: string, result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n if (result.fatalError) {\n writeStderr(`Error: ${result.fatalError}\\n`);\n return;\n }\n\n const { thresholds } = options;\n const summary = summarize(result.files);\n writeStdout(`Measured ${summary.fileCount} files under ${target}\\n`);\n writeStdout(\n `LOC ${summary.linesOfCode}, NCSS ${summary.ncssCount}, functions ${summary.functionCount}, max cyclomatic ${summary.maxCyclomaticComplexity}, max cognitive ${summary.maxCognitiveComplexity}\\n`\n );\n writeStdout(\n `Calls ${summary.callCount}, internal edges ${summary.internalCallCount}, max call depth ${summary.maxCallDepth}, imports ${summary.importSourceCount}, exports ${summary.exportCount}\\n`\n );\n writeStdout(\n `Type annotations ${summary.typeAnnotationCount}, type aliases ${summary.typeAliasCount}, interfaces ${summary.interfaceCount}, avg cohesion ${summary.averageFunctionIdentifierOverlap.toFixed(2)}\\n`\n );\n if (result.architecture) {\n writeStdout(`${formatArchitectureMetrics(result.architecture)}\\n`);\n }\n if (result.typeScriptProject) {\n writeStdout(`${formatTypeScriptProjectMetrics(result.typeScriptProject)}\\n`);\n }\n writeStdout(\n `Risk thresholds: file LOC >= ${thresholds.fileLoc}, function LOC >= ${thresholds.functionLoc}, component LOC >= ${thresholds.componentLoc}, cognitive >= ${thresholds.cognitive}, cyclomatic >= ${thresholds.cyclomatic}, calls >= ${thresholds.call}, imports >= ${thresholds.import}, fan-out >= ${thresholds.fanOut}, parameters >= ${thresholds.parameter}, duplicated blocks >= ${thresholds.duplicateBlock}, duplicated lines (%) >= ${thresholds.duplicationRatioPercent}, cross-file duplicated blocks >= ${thresholds.crossFileDuplicateBlock}\\n`\n );\n const profileOverrides = formatProfileOverrides(options.profileThresholds);\n if (profileOverrides) {\n writeStdout(`Per-language overrides: ${profileOverrides}\\n`);\n }\n\n if (risks.length === 0) {\n writeStdout('No high-risk findings found.\\n');\n } else {\n const reportedRisks = risks.slice(0, options.maxFindings);\n const totalSuffix = risks.length > reportedRisks.length ? ` of ${risks.length}` : '';\n writeStdout(`\\nHigh-risk findings (top ${reportedRisks.length}${totalSuffix}):\\n`);\n for (const risk of reportedRisks) {\n writeStdout(`${formatRiskLocation(risk)} ${formatRiskName(risk)} ${formatRiskMetrics(risk)}\\n`);\n }\n }\n\n const crossFileGroups = result.crossFileDuplication?.groups ?? [];\n if (crossFileGroups.length > 0) {\n const reportedGroups = crossFileGroups.slice(0, maxCrossFileDuplicateGroupLines);\n const totalSuffix = crossFileGroups.length > reportedGroups.length ? ` of ${crossFileGroups.length}` : '';\n writeStdout(`\\nCross-file duplicate blocks (top ${reportedGroups.length}${totalSuffix}):\\n`);\n for (const group of reportedGroups) {\n writeStdout(\n `${group.tokenCount} tokens: ${group.occurrences\n .map(({ file, startLine, endLine }) => `${file}:${startLine}-${endLine}`)\n .join(', ')}\\n`\n );\n }\n }\n\n const duplicateSymbolGroups = result.architecture?.duplicateSymbolGroups ?? [];\n if (duplicateSymbolGroups.length > 0) {\n const reportedGroups = duplicateSymbolGroups\n .toSorted((left, right) => right.files.length - left.files.length || left.name.localeCompare(right.name))\n .slice(0, maxDuplicateSymbolGroupLines);\n const totalSuffix =\n duplicateSymbolGroups.length > reportedGroups.length ? ` of ${duplicateSymbolGroups.length}` : '';\n writeStdout(`\\nDuplicate symbols (top ${reportedGroups.length}${totalSuffix}):\\n`);\n for (const group of reportedGroups) {\n writeStdout(\n `${group.name}: ${group.declarations.map((declaration) => `${declaration.file}:${declaration.line}`).join(', ')}\\n`\n );\n }\n }\n\n if (options.largestFiles > 0) {\n const largestFiles = findLargestFiles(result.files, options.largestFiles, result.displayRoot);\n writeStdout(`\\nLargest files by code LOC (top ${largestFiles.length}):\\n`);\n for (const { file, codeLoc } of largestFiles) {\n writeStdout(`${file} (code LOC ${codeLoc})\\n`);\n }\n }\n\n if (result.warnings.length > 0) {\n writeStderr(`\\nDegraded ${result.warnings.length} files (measured, but excluded from cross-file matching):\\n`);\n for (const warning of result.warnings.slice(0, 10)) {\n writeStderr(`- ${warning}\\n`);\n }\n if (result.warnings.length > 10) {\n writeStderr(`- ... ${result.warnings.length - 10} more\\n`);\n }\n }\n\n if (result.errors.length > 0) {\n writeStderr(`\\nSkipped ${result.errors.length} files or directories:\\n`);\n for (const error of result.errors.slice(0, 10)) {\n writeStderr(`- ${error}\\n`);\n }\n if (result.errors.length > 10) {\n writeStderr(`- ... ${result.errors.length - 10} more\\n`);\n }\n }\n}\n\nfunction findLargestFiles(\n files: FileMetrics[],\n count: number,\n displayRoot: string\n): { file: string; codeLoc: number }[] {\n return files\n .map(({ file, metrics }) => ({ file: formatPath(file, displayRoot), codeLoc: metrics.lines.code }))\n .toSorted((left, right) => right.codeLoc - left.codeLoc || left.file.localeCompare(right.file))\n .slice(0, count);\n}\n\nfunction formatProfileOverrides(profileThresholds: ResolvedOptions['profileThresholds']): string {\n return Object.entries(profileThresholds)\n .map(\n ([profile, overrides]) =>\n `${profile} { ${Object.entries(overrides)\n .map(([metric, value]) => `${metric} ${value}`)\n .join(', ')} }`\n )\n .join('; ');\n}\n\nfunction formatRiskLocation(risk: RiskFinding): string {\n return risk.startLine === undefined || risk.endLine === undefined\n ? risk.file\n : `${risk.file}:${risk.startLine}-${risk.endLine}`;\n}\n\nfunction formatRiskName(risk: RiskFinding): string {\n return risk.name ? `${risk.kind} ${risk.name}` : risk.kind;\n}\n\nfunction formatRiskMetrics(risk: RiskFinding): string {\n const triggerText = risk.triggers\n .map(\n (trigger) =>\n `${trigger.metric} ${formatMetricValue(trigger.value)} >= ${formatMetricValue(trigger.threshold)}${trigger.detail ? ` [${trigger.detail}]` : ''}`\n )\n .join(', ');\n return `(${triggerText}; cyclomatic ${risk.cyclomaticComplexity}, cognitive ${risk.cognitiveComplexity})`;\n}\n\nfunction formatMetricValue(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\nfunction formatArchitectureMetrics(metrics: ArchitectureMetrics): string {\n const maxStateMutationScore = Math.max(\n 0,\n ...metrics.files.map((file) => file.structuralCoordination.stateMutationScore)\n );\n return `Architecture max reachable files ${metrics.maxTransitiveLocalDependencyCount}, max structural breadth ${metrics.maxStructuralBreadthScore}, max structural coordination ${metrics.maxStructuralCoordinationScore}, max state mutation ${maxStateMutationScore}, duplicate symbol groups ${metrics.duplicateSymbolGroups.length}`;\n}\n\nfunction formatTypeScriptProjectMetrics(metrics: TypeScriptProjectMetrics): string {\n return `TypeScript project root files ${metrics.rootFileCount}, measured roots ${metrics.measuredRootFileCount}, semantic diagnostics ${metrics.semanticDiagnosticCount}, resolved calls ${metrics.resolvedCallExpressionCount}/${metrics.callExpressionCount} (${(metrics.resolvedCallExpressionRatio * 100).toFixed(1)}%)`;\n}\n\nfunction summarize(files: FileMetrics[]): {\n fileCount: number;\n functionCount: number;\n linesOfCode: number;\n maxCognitiveComplexity: number;\n maxCyclomaticComplexity: number;\n ncssCount: number;\n callCount: number;\n internalCallCount: number;\n maxCallDepth: number;\n importSourceCount: number;\n relativeImportCount: number;\n externalImportCount: number;\n exportCount: number;\n averageFunctionIdentifierOverlap: number;\n typeAnnotationCount: number;\n typeAliasCount: number;\n interfaceCount: number;\n genericParameterCount: number;\n} {\n let functionCount = 0;\n let linesOfCode = 0;\n let maxCyclomaticComplexity = 0;\n let maxCognitiveComplexity = 0;\n let ncssCount = 0;\n let callCount = 0;\n let internalCallCount = 0;\n let maxCallDepth = 0;\n let importSourceCount = 0;\n let relativeImportCount = 0;\n let externalImportCount = 0;\n let exportCount = 0;\n let cohesionTotal = 0;\n let typeAnnotationCount = 0;\n let typeAliasCount = 0;\n let interfaceCount = 0;\n let genericParameterCount = 0;\n\n for (const file of files) {\n functionCount += file.metrics.functionCount;\n linesOfCode += file.metrics.lines.code;\n maxCyclomaticComplexity = Math.max(maxCyclomaticComplexity, file.metrics.maxCyclomaticComplexity);\n maxCognitiveComplexity = Math.max(maxCognitiveComplexity, file.metrics.maxCognitiveComplexity);\n ncssCount += file.metrics.ncssCount;\n callCount += file.metrics.callGraph.callCount;\n internalCallCount += file.metrics.callGraph.internalCallCount;\n maxCallDepth = Math.max(maxCallDepth, file.metrics.callGraph.maxCallDepth);\n importSourceCount += file.metrics.coupling.importSourceCount;\n relativeImportCount += file.metrics.coupling.relativeImportCount;\n externalImportCount += file.metrics.coupling.externalImportCount;\n exportCount += file.metrics.coupling.exportCount;\n cohesionTotal += file.metrics.cohesion.averageFunctionIdentifierOverlap;\n typeAnnotationCount += file.metrics.typeComplexity.typeAnnotationCount;\n typeAliasCount += file.metrics.typeComplexity.typeAliasCount;\n interfaceCount += file.metrics.typeComplexity.interfaceCount;\n genericParameterCount += file.metrics.typeComplexity.genericParameterCount;\n }\n\n return {\n fileCount: files.length,\n functionCount,\n linesOfCode,\n maxCyclomaticComplexity,\n maxCognitiveComplexity,\n ncssCount,\n callCount,\n internalCallCount,\n maxCallDepth,\n importSourceCount,\n relativeImportCount,\n externalImportCount,\n exportCount,\n averageFunctionIdentifierOverlap: files.length === 0 ? 0 : cohesionTotal / files.length,\n typeAnnotationCount,\n typeAliasCount,\n interfaceCount,\n genericParameterCount,\n };\n}\n\nfunction shouldSkipDirectory(name: string, options: ResolvedOptions): boolean {\n if (ignoredDirectoryNames.has(name)) {\n return true;\n }\n\n if (options.includeTests) {\n return false;\n }\n\n return testDirectoryNames.has(name);\n}\n\nfunction isWithinDirectory(candidate: string, directory: string): boolean {\n const relative = path.relative(directory, candidate);\n return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));\n}\n\nfunction getLanguage(file: string, options: ResolvedOptions, explicitTarget = false): LanguageName | undefined {\n const lowerFile = file.toLowerCase();\n if (\n !explicitTarget &&\n (lowerFile.endsWith('.d.ts') ||\n lowerFile.endsWith('.d.mts') ||\n lowerFile.endsWith('.d.cts') ||\n lowerFile.endsWith('.min.js') ||\n lowerFile.endsWith('.pnp.cjs'))\n ) {\n return undefined;\n }\n\n if (\n !explicitTarget &&\n !options.includeTests &&\n (testFilePattern.test(path.basename(file)) || javaTestFilePattern.test(path.basename(file)))\n ) {\n return undefined;\n }\n\n // GCC treats an uppercase `.C` as C++; lowercasing first would misparse it with the C grammar.\n if (path.extname(file) === '.C') {\n return 'cpp';\n }\n\n return languageByExtension.get(path.extname(lowerFile));\n}\n\nfunction parsePercentInteger(value: string): number {\n const parsed = parsePositiveInteger(value);\n if (parsed > 100) {\n throw new InvalidArgumentError('Expected an integer between 1 and 100.');\n }\n return parsed;\n}\n\nfunction parseNonNegativeInteger(value: string): number {\n if (!/^\\d+$/u.test(value)) {\n throw new InvalidArgumentError('Expected a non-negative integer.');\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 0) {\n throw new InvalidArgumentError('Expected a non-negative integer.');\n }\n return parsed;\n}\n\nfunction parsePositiveInteger(value: string): number {\n if (!/^[1-9]\\d*$/u.test(value)) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n return parsed;\n}\n\nfunction formatPath(file: string, base: string): string {\n return path.relative(base, file) || path.basename(file);\n}\n\nfunction writeStdout(message: string): void {\n process.stdout.write(message);\n}\n\nfunction writeStderr(message: string): void {\n process.stderr.write(message);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.cjs","names":["Command","configFileName","loadConfig","resolveOptions","os","path","stat","realpath","measureTypeScriptProject","measureArchitecture","readdir","readFile","measureCode","collectCrossFileDuplicationFileData","measureCrossFileDuplication","resolveThresholds","InvalidArgumentError"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readdir, readFile, realpath, stat } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { Command, InvalidArgumentError } from 'commander';\nimport { measureArchitecture, type ArchitectureFileMetrics, type ArchitectureMetrics } from './architectureMetrics.js';\nimport {\n type CliOptions,\n configFileName,\n loadConfig,\n type ResolvedOptions,\n resolveOptions,\n resolveThresholds,\n type Thresholds,\n} from './cliConfig.js';\nimport { measureCrossFileDuplication, type CrossFileDuplicationMetrics } from './crossFileDuplication.js';\nimport type { CrossFileDuplicationFileData } from './duplication.js';\nimport { collectCrossFileDuplicationFileData, measureCode } from './metrics.js';\nimport { measureTypeScriptProject, type TypeScriptProjectMetrics } from './typescriptProject.js';\nimport type { CodeMetrics, FunctionMetrics, LanguageName } from './types.js';\n\ninterface FileMetrics {\n file: string;\n metrics: CodeMetrics;\n /** Cross-file duplicate candidates and token/statement data, collected only for directory scans. */\n duplicationCandidates?: CrossFileDuplicationFileData;\n}\n\ninterface RiskTrigger {\n /** Optional location hint (e.g. duplicated block line ranges) appended to the printed trigger. */\n detail?: string;\n metric: string;\n score: number;\n threshold: number;\n value: number;\n}\n\ninterface RiskFinding {\n cognitiveComplexity: number;\n cyclomaticComplexity: number;\n endLine?: number;\n file: string;\n kind: 'component' | 'file' | 'function';\n language: LanguageName;\n name?: string;\n score: number;\n startLine?: number;\n triggers: RiskTrigger[];\n}\n\ninterface ScanResult {\n architecture?: ArchitectureMetrics;\n componentFunctionKeys?: Set<string>;\n crossFileDuplication?: CrossFileDuplicationMetrics;\n displayRoot: string;\n errors: string[];\n /** Non-fatal degradations (e.g. cross-file candidates unavailable); the file is still measured. */\n warnings: string[];\n fatalError?: string;\n files: FileMetrics[];\n namedComponentFunctionKeys?: Set<string>;\n typeScriptProject?: TypeScriptProjectMetrics;\n}\n\nconst languageByExtension = new Map<string, LanguageName>([\n ['.c', 'c'],\n ['.c++', 'cpp'],\n ['.cc', 'cpp'],\n ['.cjs', 'javascript'],\n ['.cp', 'cpp'],\n ['.cpp', 'cpp'],\n ['.tcc', 'cpp'],\n ['.cts', 'typescript'],\n ['.cxx', 'cpp'],\n ['.go', 'go'],\n // Headers may be C or C++; the C++ grammar parses both.\n ['.h', 'cpp'],\n ['.hh', 'cpp'],\n ['.hpp', 'cpp'],\n ['.hxx', 'cpp'],\n ['.java', 'java'],\n ['.js', 'javascript'],\n ['.jsx', 'jsx'],\n ['.mjs', 'javascript'],\n ['.mts', 'typescript'],\n ['.py', 'python'],\n ['.rb', 'ruby'],\n ['.rs', 'rust'],\n ['.ts', 'typescript'],\n ['.tsx', 'tsx'],\n]);\n\nconst ignoredDirectoryNames = new Set([\n '.agents',\n '.claude',\n '.cursor',\n '.git',\n '.next',\n '.playwright-cli',\n '.tox',\n '.tmp',\n '.turbo',\n '.venv',\n '.yarn',\n '__fixtures__',\n '__generated__',\n '__pycache__',\n 'coverage',\n 'dist',\n 'fixtures',\n 'generated',\n 'node_modules',\n 'target',\n 'test-fixtures',\n 'vendor',\n 'venv',\n]);\n\n/** Caps the `Duplicate symbols` section so large repositories do not flood the report. */\nconst maxDuplicateSymbolGroupLines = 10;\n/** Caps the `Cross-file duplicate blocks` section so large repositories do not flood the report. */\nconst maxCrossFileDuplicateGroupLines = 10;\n/** Caps how many cross-file group locations a single risk finding repeats as detail. */\nconst maxCrossFileDuplicateDetailGroups = 3;\n\nconst testDirectoryNames = new Set(['__tests__', 'test', 'tests', 'spec']);\nconst testFilePattern = /(?:^test(?:[_-].*)?|\\.(?:spec|test)|[_-](?:test|spec))\\.[^.]+$/iu;\n// JUnit tests use a case-sensitive `Test.java` suffix; case-insensitive matching would catch\n// production files like `contest.java`.\nconst javaTestFilePattern = /Test\\.java$/u;\n\n// oxlint-disable-next-line unicorn/prefer-top-level-await -- CommonJS build output cannot preserve top-level await.\nvoid main().catch((error: unknown) => {\n writeStderr(`Error: ${formatError(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const program = new Command()\n .name('code-gauge')\n .description('Measure code metrics and list high-risk findings.')\n .argument('[target]', 'file or directory to measure', '.')\n .option('--config <path>', `config file to use instead of the auto-detected ${configFileName}`)\n .option('--file-loc-threshold <number>', 'minimum file code LOC to report', parsePositiveInteger)\n .option('--function-loc-threshold <number>', 'minimum function physical LOC span to report', parsePositiveInteger)\n .option(\n '--component-loc-threshold <number>',\n 'minimum React component physical LOC span to report',\n parsePositiveInteger\n )\n .option('--cognitive-threshold <number>', 'minimum cognitive complexity to report', parsePositiveInteger)\n .option('--cyclomatic-threshold <number>', 'minimum cyclomatic complexity to report', parsePositiveInteger)\n .option('--call-threshold <number>', 'minimum function call count to report', parsePositiveInteger)\n .option('--import-threshold <number>', 'minimum unique import sources per file to report', parsePositiveInteger)\n .option('--fan-out-threshold <number>', 'minimum intra-file fan-out per function to report', parsePositiveInteger)\n .option('--parameter-threshold <number>', 'minimum function parameter count to report', parsePositiveInteger)\n .option(\n '--duplicate-block-threshold <number>',\n 'minimum count of duplicated code blocks per file to report',\n parsePositiveInteger\n )\n .option(\n '--duplication-ratio-percent-threshold <number>',\n 'minimum percentage (1-100) of duplicated lines per file to report',\n parsePercentInteger\n )\n .option(\n '--cross-file-duplicate-block-threshold <number>',\n 'minimum count of cross-file duplicate block groups per file to report',\n parsePositiveInteger\n )\n .option(\n '--duplication-min-tokens <number>',\n 'minimum normalized token count for a duplicate region (default 40)',\n parsePositiveInteger\n )\n .option(\n '--duplication-max-gap-tokens <number>',\n 'maximum token gap merged into one gapped clone group; 0 disables merging (default 30)',\n parseNonNegativeInteger\n )\n .option(\n '--duplication-min-similarity-percent <number>',\n 'minimum similarity percent (1-100) for near-miss (Type-3) clone blocks; 100 reports exact matches only (default 70)',\n parsePercentInteger\n )\n .option(\n '--transitive-dependency-threshold <number>',\n 'minimum transitively reachable local files to report',\n parsePositiveInteger\n )\n .option(\n '--structural-breadth-threshold <number>',\n 'minimum structural breadth score to report',\n parsePositiveInteger\n )\n .option(\n '--structural-coordination-threshold <number>',\n 'minimum structural coordination score to report',\n parsePositiveInteger\n )\n .option('--state-mutation-threshold <number>', 'minimum state mutation score to report', parsePositiveInteger)\n .option(\n '--duplicate-symbol-group-threshold <number>',\n 'minimum duplicate symbol group count to report',\n parsePositiveInteger\n )\n .option('--max-findings <number>', 'maximum number of risk findings to print', parsePositiveInteger)\n .option('--largest-files <number>', 'number of largest files by code LOC to list', parsePositiveInteger)\n .option('--include-tests', 'include test files and test directories')\n .option('--tsconfig <path>', 'TypeScript project file to use instead of auto-detected tsconfig.json')\n .option('--json', 'print JSON output')\n .option('--fail-on-error', 'exit with code 1 when files or directories cannot be scanned')\n .option('--fail-on-risk', 'exit with code 1 when high-risk findings are found');\n\n program.action(async (target: string, cliOptions: CliOptions) => {\n const resolvedTarget = resolveTarget(target);\n const config = await loadConfig(cliOptions.config, await configSearchDirectory(resolvedTarget));\n const options = resolveOptions(cliOptions, config);\n const result = await scanTarget(resolvedTarget, options);\n addCrossFileDuplication(result, options);\n await addArchitectureMetrics(result);\n await addTypeScriptProjectMetrics(result, options, resolvedTarget);\n const risks = findRiskyFunctions(\n result.files,\n result.architecture,\n result.crossFileDuplication,\n result.componentFunctionKeys,\n result.namedComponentFunctionKeys,\n options,\n result.displayRoot\n );\n\n if (options.json) {\n printJson(result, risks, options);\n } else {\n printTextReport(resolvedTarget, result, risks, options);\n }\n\n if (\n result.fatalError ||\n (options.failOnError && result.errors.length > 0) ||\n (options.failOnRisk && risks.length > 0)\n ) {\n process.exitCode = 1;\n }\n });\n\n await program.parseAsync();\n}\n\nfunction resolveTarget(target: string): string {\n if (target === '~') {\n return os.homedir();\n }\n\n if (target.startsWith('~/')) {\n return path.join(os.homedir(), target.slice(2));\n }\n\n return path.resolve(target);\n}\n\n/** Returns the directory from which the config file search should start (the target itself if it is a directory). */\nasync function configSearchDirectory(target: string): Promise<string> {\n try {\n const targetStat = await stat(target);\n return targetStat.isDirectory() ? target : path.dirname(target);\n } catch {\n return path.dirname(target);\n }\n}\n\n/** Shared state of one scan, threaded through the directory walk instead of positional plumbing. */\ninterface ScanContext {\n options: ResolvedOptions;\n files: FileMetrics[];\n errors: string[];\n warnings: string[];\n visitedDirectories: Set<string>;\n visitedFiles: Set<string>;\n /** Scan root: paths are displayed relative to it, and symbolic links may not escape it. */\n rootDirectory: string;\n}\n\nasync function scanTarget(target: string, options: ResolvedOptions): Promise<ScanResult> {\n const files: FileMetrics[] = [];\n const errors: string[] = [];\n const warnings: string[] = [];\n let canonicalTarget = target;\n try {\n canonicalTarget = await realpath(target);\n } catch {\n // stat below reports missing targets with the original path.\n }\n\n const fallbackDisplayRoot = path.dirname(canonicalTarget);\n let targetStat;\n\n try {\n targetStat = await stat(canonicalTarget);\n } catch (error) {\n const fatalError = `${formatPath(canonicalTarget, fallbackDisplayRoot)}: ${formatError(error)}`;\n return { displayRoot: fallbackDisplayRoot, files, errors: [fatalError], warnings, fatalError };\n }\n\n if (targetStat.isFile()) {\n const displayRoot = path.dirname(canonicalTarget);\n const language = getLanguage(canonicalTarget, options, true);\n if (!language) {\n const fatalError = `${formatPath(canonicalTarget, displayRoot)}: unsupported file type`;\n return { displayRoot, files, errors: [fatalError], warnings, fatalError };\n }\n\n const context = makeScanContext(options, files, errors, warnings, displayRoot);\n await measureFile(canonicalTarget, language, 'single-file', context, canonicalTarget);\n return { displayRoot, files, errors, warnings };\n }\n\n await scanDirectory(canonicalTarget, makeScanContext(options, files, errors, warnings, canonicalTarget));\n return { displayRoot: canonicalTarget, files, errors, warnings };\n}\n\nfunction makeScanContext(\n options: ResolvedOptions,\n files: FileMetrics[],\n errors: string[],\n warnings: string[],\n rootDirectory: string\n): ScanContext {\n return { options, files, errors, warnings, visitedDirectories: new Set(), visitedFiles: new Set(), rootDirectory };\n}\n\nasync function addTypeScriptProjectMetrics(\n result: ScanResult,\n options: ResolvedOptions,\n resolvedTarget: string\n): Promise<void> {\n if (result.fatalError) {\n return;\n }\n if (result.files.length === 0) {\n return;\n }\n\n const explicitConfigFile = options.tsconfig;\n const isExplicitConfig = explicitConfigFile !== undefined;\n if (!isExplicitConfig && !result.files.some(({ file }) => isTypeScriptProjectCandidateFile(file))) {\n return;\n }\n\n const configFile = explicitConfigFile ? resolveTarget(explicitConfigFile) : await findNearestTsconfig(resolvedTarget);\n if (!configFile) {\n return;\n }\n\n try {\n result.typeScriptProject = await measureTypeScriptProject(\n configFile,\n result.files.map(({ file }) => file)\n );\n result.componentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.map((component) =>\n functionLocationKey(component.file, component.startLine, component.startColumn)\n )\n );\n result.namedComponentFunctionKeys = new Set(\n result.typeScriptProject.reactComponentFunctions.flatMap((component) =>\n component.name ? [functionNameLocationKey(component.file, component.name, component.startLine)] : []\n )\n );\n } catch (error) {\n if (isExplicitConfig) {\n result.errors.push(`${formatPath(configFile, result.displayRoot)}: ${formatError(error)}`);\n }\n }\n}\n\nfunction isTypeScriptProjectCandidateFile(file: string): boolean {\n return ['.cjs', '.cts', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx'].includes(path.extname(file));\n}\n\nasync function findNearestTsconfig(target: string): Promise<string | undefined> {\n const targetStat = await stat(target);\n let currentDirectory = targetStat.isDirectory() ? target : path.dirname(target);\n while (true) {\n const configFile = path.join(currentDirectory, 'tsconfig.json');\n if (await fileExists(configFile)) {\n return configFile;\n }\n\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n}\n\nasync function fileExists(file: string): Promise<boolean> {\n try {\n const fileStat = await stat(file);\n return fileStat.isFile();\n } catch {\n return false;\n }\n}\n\nasync function addArchitectureMetrics(result: ScanResult): Promise<void> {\n if (result.fatalError) {\n return;\n }\n\n try {\n result.architecture = measureArchitecture(\n result.files.map(({ file, metrics }) => ({ file, metrics })),\n result.displayRoot\n );\n } catch (error) {\n result.errors.push(`architecture metrics: ${formatError(error)}`);\n }\n}\n\nasync function scanDirectory(directory: string, context: ScanContext): Promise<void> {\n let resolvedDirectory;\n try {\n resolvedDirectory = await realpath(directory);\n } catch (error) {\n context.errors.push(`${formatPath(directory, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedDirectory, context.rootDirectory)) {\n return;\n }\n\n if (context.visitedDirectories.has(resolvedDirectory)) {\n return;\n }\n context.visitedDirectories.add(resolvedDirectory);\n\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch (error) {\n context.errors.push(`${formatPath(directory, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n await scanSymbolicLink(entry.name, entryPath, context);\n continue;\n }\n\n if (entry.isDirectory()) {\n if (shouldSkipDirectory(entry.name, context.options)) {\n continue;\n }\n await scanDirectory(entryPath, context);\n continue;\n }\n\n if (entry.isFile()) {\n await measureScannableFile(entryPath, context);\n }\n }\n}\n\nasync function scanSymbolicLink(name: string, entryPath: string, context: ScanContext): Promise<void> {\n let resolvedPath;\n try {\n resolvedPath = await realpath(entryPath);\n } catch (error) {\n context.errors.push(`${formatPath(entryPath, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (!isWithinDirectory(resolvedPath, context.rootDirectory)) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await stat(entryPath);\n } catch (error) {\n context.errors.push(`${formatPath(entryPath, context.rootDirectory)}: ${formatError(error)}`);\n return;\n }\n\n if (entryStat.isDirectory()) {\n if (\n shouldSkipDirectory(name, context.options) ||\n shouldSkipDirectory(path.basename(resolvedPath), context.options)\n ) {\n return;\n }\n await scanDirectory(entryPath, context);\n return;\n }\n\n if (entryStat.isFile()) {\n await measureScannableFile(entryPath, context, resolvedPath, resolvedPath);\n }\n}\n\nasync function measureScannableFile(\n file: string,\n context: ScanContext,\n languageFile = file,\n realFile?: string\n): Promise<void> {\n const language = getLanguage(languageFile, context.options);\n if (language) {\n await measureFile(file, language, 'directory', context, realFile);\n }\n}\n\nasync function measureFile(\n file: string,\n language: LanguageName,\n mode: 'single-file' | 'directory',\n context: ScanContext,\n realFile?: string\n): Promise<void> {\n try {\n const resolvedFile = realFile ?? (await realpath(file));\n if (context.visitedFiles.has(resolvedFile)) {\n return;\n }\n context.visitedFiles.add(resolvedFile);\n\n const code = await readFile(file, 'utf8');\n const measureOptions = { language, duplication: context.options.duplication };\n const fileMetrics: FileMetrics = { file, metrics: measureCode(code, measureOptions) };\n // Only directory scans compare files against each other; a single-file target has no peers.\n // Candidate collection failing (it always parses with the JavaScript binding, which can give\n // up where the native backend measured fine) must not discard the measured metrics.\n if (mode === 'directory') {\n try {\n fileMetrics.duplicationCandidates = collectCrossFileDuplicationFileData(code, measureOptions);\n } catch (error) {\n // A warning, not an error: the file's metrics are complete, only its participation in\n // cross-file matching is lost, so it is not \"skipped\" and must not fail --fail-on-error.\n context.warnings.push(\n `${formatPath(file, context.rootDirectory)}: cross-file duplication candidates unavailable: ${formatError(error)}`\n );\n }\n }\n context.files.push(fileMetrics);\n } catch (error) {\n context.errors.push(`${formatPath(file, context.rootDirectory)}: ${formatError(error)}`);\n }\n}\n\n/** Runs after the scan so every measured file's candidates participate. */\nfunction addCrossFileDuplication(result: ScanResult, options: ResolvedOptions): void {\n if (result.fatalError || result.files.length < 2) {\n return;\n }\n const sourceFiles = result.files.flatMap(({ file, duplicationCandidates }) =>\n duplicationCandidates ? [{ file: formatPath(file, result.displayRoot), ...duplicationCandidates }] : []\n );\n if (sourceFiles.length < 2) {\n return;\n }\n result.crossFileDuplication = measureCrossFileDuplication(sourceFiles, options.duplication);\n}\n\nfunction findRiskyFunctions(\n files: FileMetrics[],\n architecture: ArchitectureMetrics | undefined,\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined,\n options: ResolvedOptions,\n displayRoot: string\n): RiskFinding[] {\n const architectureByFile = new Map(architecture?.files.map((file) => [file.file, file]));\n const findings = files.flatMap(({ file, metrics }) => {\n const isReactFile = metrics.functions.some(\n (fn) => fn.returnsJsx || isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys)\n );\n const thresholds = resolveThresholds(options, metrics.language, isReactFile);\n return [\n ...findRiskyFileMetrics(\n file,\n metrics,\n architectureByFile.get(formatPath(file, displayRoot)),\n crossFileDuplication,\n thresholds,\n displayRoot\n ),\n ...metrics.functions.flatMap((fn) =>\n findRiskyFunctionMetrics(\n file,\n metrics.language,\n fn,\n thresholds,\n displayRoot,\n componentFunctionKeys,\n namedComponentFunctionKeys\n )\n ),\n ];\n });\n\n findings.sort(compareRiskFindings);\n return findings;\n}\n\nfunction findRiskyFileMetrics(\n file: string,\n metrics: CodeMetrics,\n architecture: ArchitectureFileMetrics | undefined,\n crossFileDuplication: CrossFileDuplicationMetrics | undefined,\n thresholds: Thresholds,\n displayRoot: string\n): RiskFinding[] {\n const triggers: RiskTrigger[] = [];\n const formattedFile = formatPath(file, displayRoot);\n addTrigger(triggers, 'file LOC', metrics.lines.code, thresholds.fileLoc);\n addTrigger(triggers, 'import sources', metrics.coupling.importSourceCount, thresholds.import);\n const duplicateBlockDetail = formatDuplicateBlockGroups(metrics.duplication.duplicateBlockGroups);\n addTrigger(\n triggers,\n 'duplicated blocks',\n metrics.duplication.duplicateBlockCount,\n thresholds.duplicateBlock,\n duplicateBlockDetail\n );\n // Maximal-region selection deliberately compresses adjacent clones into few blocks, so severity\n // must track line coverage, not the block count. Flooring compares like the unrounded ratio\n // against the integer threshold (29.5% must not trigger a >= 30 threshold). The block ranges are\n // repeated as detail because this trigger can fire alone, and a percentage without locations is\n // not actionable.\n addTrigger(\n triggers,\n 'duplicated lines (%)',\n Math.floor(metrics.duplication.duplicationRatio * 100),\n thresholds.duplicationRatioPercent,\n duplicateBlockDetail\n );\n if (crossFileDuplication) {\n // Object.hasOwn: a file named like an Object.prototype member must not read an inherited value.\n addTrigger(\n triggers,\n 'cross-file duplicated blocks',\n Object.hasOwn(crossFileDuplication.duplicateBlockGroupCountByFile, formattedFile)\n ? (crossFileDuplication.duplicateBlockGroupCountByFile[formattedFile] ?? 0)\n : 0,\n thresholds.crossFileDuplicateBlock,\n formatCrossFileDuplicateDetail(crossFileDuplication, formattedFile)\n );\n }\n if (architecture) {\n const hasFileScaleRisk = metrics.lines.code >= 100 || architecture.directLocalDependencyCount >= 8;\n if (hasFileScaleRisk) {\n addTrigger(\n triggers,\n 'transitive local dependencies',\n architecture.transitiveLocalDependencyCount,\n thresholds.transitiveDependency\n );\n }\n if (\n triggers.length > 0 ||\n architecture.directLocalDependencyCount >= 8 ||\n architecture.structuralCoordination.score >= thresholds.structuralCoordination\n ) {\n addTrigger(triggers, 'structural breadth', architecture.structuralBreadthScore, thresholds.structuralBreadth);\n }\n addTrigger(\n triggers,\n 'structural coordination',\n architecture.structuralCoordination.score,\n thresholds.structuralCoordination\n );\n addTrigger(\n triggers,\n 'state mutation',\n architecture.structuralCoordination.stateMutationScore,\n thresholds.stateMutation\n );\n addTrigger(\n triggers,\n 'duplicate symbol groups',\n architecture.duplicateSymbolGroupCount,\n thresholds.duplicateSymbolGroup\n );\n }\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formattedFile,\n language: metrics.language,\n kind: 'file',\n cyclomaticComplexity: metrics.cyclomaticComplexity,\n cognitiveComplexity: metrics.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction findRiskyFunctionMetrics(\n file: string,\n language: LanguageName,\n fn: FunctionMetrics,\n thresholds: Thresholds,\n displayRoot: string,\n componentFunctionKeys?: Set<string>,\n namedComponentFunctionKeys?: Set<string>\n): RiskFinding[] {\n const loc = fn.endLine - fn.startLine + 1;\n const isComponent = isReactComponent(file, fn, componentFunctionKeys, namedComponentFunctionKeys);\n const kind = isComponent ? 'component' : 'function';\n const triggers: RiskTrigger[] = [];\n addTrigger(triggers, 'cognitive complexity', fn.cognitiveComplexity, thresholds.cognitive);\n addTrigger(triggers, 'cyclomatic complexity', fn.cyclomaticComplexity, thresholds.cyclomatic);\n addTrigger(triggers, isComponent ? 'component LOC' : 'function LOC', loc, getLocThreshold(isComponent, thresholds));\n addTrigger(triggers, 'function calls', fn.callCount, thresholds.call);\n addTrigger(triggers, 'fan-out', fn.fanOut, thresholds.fanOut);\n addTrigger(triggers, 'parameters', fn.parameterCount, thresholds.parameter);\n if (triggers.length === 0) {\n return [];\n }\n\n return [\n {\n file: formatPath(file, displayRoot),\n language,\n kind,\n name: fn.name ?? '<anonymous>',\n startLine: fn.startLine,\n endLine: fn.endLine,\n cyclomaticComplexity: fn.cyclomaticComplexity,\n cognitiveComplexity: fn.cognitiveComplexity,\n triggers,\n score: maxTriggerScore(triggers),\n },\n ];\n}\n\nfunction addTrigger(triggers: RiskTrigger[], metric: string, value: number, threshold: number, detail?: string): void {\n if (value < threshold) {\n return;\n }\n\n triggers.push({ metric, value, threshold, score: value / threshold, detail });\n}\n\n/** Formats the cross-file groups a file participates in as `12-34 ~ b.ts:56-78; ...` (capped). */\nfunction formatCrossFileDuplicateDetail(\n crossFileDuplication: CrossFileDuplicationMetrics,\n formattedFile: string\n): string | undefined {\n const involved = crossFileDuplication.groups.filter((group) => group.files.includes(formattedFile));\n if (involved.length === 0) {\n return undefined;\n }\n const formatted = involved\n .slice(0, maxCrossFileDuplicateDetailGroups)\n .map((group) =>\n group.occurrences\n .map(({ file, startLine, endLine }) =>\n file === formattedFile ? `${startLine}-${endLine}` : `${file}:${startLine}-${endLine}`\n )\n .join(' ~ ')\n )\n .join('; ');\n const truncatedSuffix = involved.length > maxCrossFileDuplicateDetailGroups ? '; ...' : '';\n return `${formatted}${truncatedSuffix}`;\n}\n\n/** Formats duplicated block groups as `12-34 ~ 56-78; 90-99 ~ 100-109` (copies joined by ` ~ `, groups by `; `). */\nfunction formatDuplicateBlockGroups(groups: { endLine: number; startLine: number }[][]): string | undefined {\n if (groups.length === 0) {\n return undefined;\n }\n\n return groups.map((group) => group.map(({ startLine, endLine }) => `${startLine}-${endLine}`).join(' ~ ')).join('; ');\n}\n\nfunction isReactComponent(\n file: string,\n fn: FunctionMetrics,\n componentFunctionKeys: Set<string> | undefined,\n namedComponentFunctionKeys: Set<string> | undefined\n): boolean {\n return (\n componentFunctionKeys?.has(functionLocationKey(file, fn.startLine, fn.startColumn)) ||\n (fn.name ? namedComponentFunctionKeys?.has(functionNameLocationKey(file, fn.name, fn.startLine)) : false) ||\n false\n );\n}\n\nfunction getLocThreshold(isComponent: boolean, thresholds: Thresholds): number {\n return isComponent ? thresholds.componentLoc : thresholds.functionLoc;\n}\n\nfunction functionLocationKey(file: string, startLine: number, startColumn: number): string {\n return `${path.resolve(file)}:${startLine}:${startColumn}`;\n}\n\nfunction functionNameLocationKey(file: string, name: string, startLine: number): string {\n return `${path.resolve(file)}:${name}:${startLine}`;\n}\n\nfunction maxTriggerScore(triggers: RiskTrigger[]): number {\n return Math.max(...triggers.map((trigger) => trigger.score));\n}\n\nfunction compareRiskFindings(left: RiskFinding, right: RiskFinding): number {\n return (\n right.score - left.score ||\n left.file.localeCompare(right.file) ||\n (left.startLine ?? 0) - (right.startLine ?? 0) ||\n (left.endLine ?? 0) - (right.endLine ?? 0) ||\n left.kind.localeCompare(right.kind)\n );\n}\n\nfunction printJson(result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n const summary = summarize(result.files);\n const reportedRisks = risks.slice(0, options.maxFindings);\n writeStdout(\n JSON.stringify(\n {\n summary,\n thresholds: options.thresholds,\n profileThresholds: options.profileThresholds,\n totalRisks: risks.length,\n truncated: reportedRisks.length < risks.length,\n largestFiles:\n options.largestFiles > 0\n ? findLargestFiles(result.files, options.largestFiles, result.displayRoot)\n : undefined,\n architecture: result.architecture,\n crossFileDuplication: result.crossFileDuplication,\n typeScriptProject: result.typeScriptProject,\n risks: reportedRisks,\n errors: result.errors,\n warnings: result.warnings,\n },\n undefined,\n 2\n ) + '\\n'\n );\n}\n\nfunction printTextReport(target: string, result: ScanResult, risks: RiskFinding[], options: ResolvedOptions): void {\n if (result.fatalError) {\n writeStderr(`Error: ${result.fatalError}\\n`);\n return;\n }\n\n const { thresholds } = options;\n const summary = summarize(result.files);\n writeStdout(`Measured ${summary.fileCount} files under ${target}\\n`);\n writeStdout(\n `LOC ${summary.linesOfCode}, NCSS ${summary.ncssCount}, functions ${summary.functionCount}, max cyclomatic ${summary.maxCyclomaticComplexity}, max cognitive ${summary.maxCognitiveComplexity}\\n`\n );\n writeStdout(\n `Calls ${summary.callCount}, internal edges ${summary.internalEdgeCount}, max call depth ${summary.maxCallDepth}, imports ${summary.importSourceCount}, exports ${summary.exportCount}\\n`\n );\n writeStdout(\n `Type annotations ${summary.typeAnnotationCount}, type aliases ${summary.typeAliasCount}, interfaces ${summary.interfaceCount}, avg cohesion ${summary.averageFunctionIdentifierOverlap.toFixed(2)}\\n`\n );\n if (result.architecture) {\n writeStdout(`${formatArchitectureMetrics(result.architecture)}\\n`);\n }\n if (result.typeScriptProject) {\n writeStdout(`${formatTypeScriptProjectMetrics(result.typeScriptProject)}\\n`);\n }\n writeStdout(\n `Risk thresholds: file LOC >= ${thresholds.fileLoc}, function LOC >= ${thresholds.functionLoc}, component LOC >= ${thresholds.componentLoc}, cognitive >= ${thresholds.cognitive}, cyclomatic >= ${thresholds.cyclomatic}, calls >= ${thresholds.call}, imports >= ${thresholds.import}, fan-out >= ${thresholds.fanOut}, parameters >= ${thresholds.parameter}, duplicated blocks >= ${thresholds.duplicateBlock}, duplicated lines (%) >= ${thresholds.duplicationRatioPercent}, cross-file duplicated blocks >= ${thresholds.crossFileDuplicateBlock}\\n`\n );\n const profileOverrides = formatProfileOverrides(options.profileThresholds);\n if (profileOverrides) {\n writeStdout(`Per-language overrides: ${profileOverrides}\\n`);\n }\n\n if (risks.length === 0) {\n writeStdout('No high-risk findings found.\\n');\n } else {\n const reportedRisks = risks.slice(0, options.maxFindings);\n const totalSuffix = risks.length > reportedRisks.length ? ` of ${risks.length}` : '';\n writeStdout(`\\nHigh-risk findings (top ${reportedRisks.length}${totalSuffix}):\\n`);\n for (const risk of reportedRisks) {\n writeStdout(`${formatRiskLocation(risk)} ${formatRiskName(risk)} ${formatRiskMetrics(risk)}\\n`);\n }\n }\n\n const crossFileGroups = result.crossFileDuplication?.groups ?? [];\n if (crossFileGroups.length > 0) {\n const reportedGroups = crossFileGroups.slice(0, maxCrossFileDuplicateGroupLines);\n const totalSuffix = crossFileGroups.length > reportedGroups.length ? ` of ${crossFileGroups.length}` : '';\n writeStdout(`\\nCross-file duplicate blocks (top ${reportedGroups.length}${totalSuffix}):\\n`);\n for (const group of reportedGroups) {\n writeStdout(\n `${group.tokenCount} tokens: ${group.occurrences\n .map(({ file, startLine, endLine }) => `${file}:${startLine}-${endLine}`)\n .join(', ')}\\n`\n );\n }\n }\n\n const duplicateSymbolGroups = result.architecture?.duplicateSymbolGroups ?? [];\n if (duplicateSymbolGroups.length > 0) {\n const reportedGroups = duplicateSymbolGroups\n .toSorted((left, right) => right.files.length - left.files.length || left.name.localeCompare(right.name))\n .slice(0, maxDuplicateSymbolGroupLines);\n const totalSuffix =\n duplicateSymbolGroups.length > reportedGroups.length ? ` of ${duplicateSymbolGroups.length}` : '';\n writeStdout(`\\nDuplicate symbols (top ${reportedGroups.length}${totalSuffix}):\\n`);\n for (const group of reportedGroups) {\n writeStdout(\n `${group.name}: ${group.declarations.map((declaration) => `${declaration.file}:${declaration.line}`).join(', ')}\\n`\n );\n }\n }\n\n if (options.largestFiles > 0) {\n const largestFiles = findLargestFiles(result.files, options.largestFiles, result.displayRoot);\n writeStdout(`\\nLargest files by code LOC (top ${largestFiles.length}):\\n`);\n for (const { file, codeLoc } of largestFiles) {\n writeStdout(`${file} (code LOC ${codeLoc})\\n`);\n }\n }\n\n if (result.warnings.length > 0) {\n writeStderr(`\\nDegraded ${result.warnings.length} files (measured, but excluded from cross-file matching):\\n`);\n for (const warning of result.warnings.slice(0, 10)) {\n writeStderr(`- ${warning}\\n`);\n }\n if (result.warnings.length > 10) {\n writeStderr(`- ... ${result.warnings.length - 10} more\\n`);\n }\n }\n\n if (result.errors.length > 0) {\n writeStderr(`\\nSkipped ${result.errors.length} files or directories:\\n`);\n for (const error of result.errors.slice(0, 10)) {\n writeStderr(`- ${error}\\n`);\n }\n if (result.errors.length > 10) {\n writeStderr(`- ... ${result.errors.length - 10} more\\n`);\n }\n }\n}\n\nfunction findLargestFiles(\n files: FileMetrics[],\n count: number,\n displayRoot: string\n): { file: string; codeLoc: number }[] {\n return files\n .map(({ file, metrics }) => ({ file: formatPath(file, displayRoot), codeLoc: metrics.lines.code }))\n .toSorted((left, right) => right.codeLoc - left.codeLoc || left.file.localeCompare(right.file))\n .slice(0, count);\n}\n\nfunction formatProfileOverrides(profileThresholds: ResolvedOptions['profileThresholds']): string {\n return Object.entries(profileThresholds)\n .map(\n ([profile, overrides]) =>\n `${profile} { ${Object.entries(overrides)\n .map(([metric, value]) => `${metric} ${value}`)\n .join(', ')} }`\n )\n .join('; ');\n}\n\nfunction formatRiskLocation(risk: RiskFinding): string {\n return risk.startLine === undefined || risk.endLine === undefined\n ? risk.file\n : `${risk.file}:${risk.startLine}-${risk.endLine}`;\n}\n\nfunction formatRiskName(risk: RiskFinding): string {\n return risk.name ? `${risk.kind} ${risk.name}` : risk.kind;\n}\n\nfunction formatRiskMetrics(risk: RiskFinding): string {\n const triggerText = risk.triggers\n .map(\n (trigger) =>\n `${trigger.metric} ${formatMetricValue(trigger.value)} >= ${formatMetricValue(trigger.threshold)}${trigger.detail ? ` [${trigger.detail}]` : ''}`\n )\n .join(', ');\n return `(${triggerText}; cyclomatic ${risk.cyclomaticComplexity}, cognitive ${risk.cognitiveComplexity})`;\n}\n\nfunction formatMetricValue(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n}\n\nfunction formatArchitectureMetrics(metrics: ArchitectureMetrics): string {\n const maxStateMutationScore = Math.max(\n 0,\n ...metrics.files.map((file) => file.structuralCoordination.stateMutationScore)\n );\n return `Architecture max reachable files ${metrics.maxTransitiveLocalDependencyCount}, max structural breadth ${metrics.maxStructuralBreadthScore}, max structural coordination ${metrics.maxStructuralCoordinationScore}, max state mutation ${maxStateMutationScore}, duplicate symbol groups ${metrics.duplicateSymbolGroups.length}`;\n}\n\nfunction formatTypeScriptProjectMetrics(metrics: TypeScriptProjectMetrics): string {\n return `TypeScript project root files ${metrics.rootFileCount}, measured roots ${metrics.measuredRootFileCount}, semantic diagnostics ${metrics.semanticDiagnosticCount}, resolved calls ${metrics.resolvedCallExpressionCount}/${metrics.callExpressionCount} (${(metrics.resolvedCallExpressionRatio * 100).toFixed(1)}%)`;\n}\n\nfunction summarize(files: FileMetrics[]): {\n fileCount: number;\n functionCount: number;\n linesOfCode: number;\n maxCognitiveComplexity: number;\n maxCyclomaticComplexity: number;\n ncssCount: number;\n callCount: number;\n internalEdgeCount: number;\n maxCallDepth: number;\n importSourceCount: number;\n relativeImportCount: number;\n externalImportCount: number;\n exportCount: number;\n averageFunctionIdentifierOverlap: number;\n typeAnnotationCount: number;\n typeAliasCount: number;\n interfaceCount: number;\n genericParameterCount: number;\n} {\n let functionCount = 0;\n let linesOfCode = 0;\n let maxCyclomaticComplexity = 0;\n let maxCognitiveComplexity = 0;\n let ncssCount = 0;\n let callCount = 0;\n let internalEdgeCount = 0;\n let maxCallDepth = 0;\n let importSourceCount = 0;\n let relativeImportCount = 0;\n let externalImportCount = 0;\n let exportCount = 0;\n let cohesionTotal = 0;\n let typeAnnotationCount = 0;\n let typeAliasCount = 0;\n let interfaceCount = 0;\n let genericParameterCount = 0;\n\n for (const file of files) {\n functionCount += file.metrics.functionCount;\n linesOfCode += file.metrics.lines.code;\n maxCyclomaticComplexity = Math.max(maxCyclomaticComplexity, file.metrics.maxCyclomaticComplexity);\n maxCognitiveComplexity = Math.max(maxCognitiveComplexity, file.metrics.maxCognitiveComplexity);\n ncssCount += file.metrics.ncssCount;\n callCount += file.metrics.callGraph.callCount;\n internalEdgeCount += file.metrics.callGraph.internalEdgeCount;\n maxCallDepth = Math.max(maxCallDepth, file.metrics.callGraph.maxCallDepth);\n importSourceCount += file.metrics.coupling.importSourceCount;\n relativeImportCount += file.metrics.coupling.relativeImportCount;\n externalImportCount += file.metrics.coupling.externalImportCount;\n exportCount += file.metrics.coupling.exportCount;\n cohesionTotal += file.metrics.cohesion.averageFunctionIdentifierOverlap;\n typeAnnotationCount += file.metrics.typeComplexity.typeAnnotationCount;\n typeAliasCount += file.metrics.typeComplexity.typeAliasCount;\n interfaceCount += file.metrics.typeComplexity.interfaceCount;\n genericParameterCount += file.metrics.typeComplexity.genericParameterCount;\n }\n\n return {\n fileCount: files.length,\n functionCount,\n linesOfCode,\n maxCyclomaticComplexity,\n maxCognitiveComplexity,\n ncssCount,\n callCount,\n internalEdgeCount,\n maxCallDepth,\n importSourceCount,\n relativeImportCount,\n externalImportCount,\n exportCount,\n averageFunctionIdentifierOverlap: files.length === 0 ? 0 : cohesionTotal / files.length,\n typeAnnotationCount,\n typeAliasCount,\n interfaceCount,\n genericParameterCount,\n };\n}\n\nfunction shouldSkipDirectory(name: string, options: ResolvedOptions): boolean {\n if (ignoredDirectoryNames.has(name)) {\n return true;\n }\n\n if (options.includeTests) {\n return false;\n }\n\n return testDirectoryNames.has(name);\n}\n\nfunction isWithinDirectory(candidate: string, directory: string): boolean {\n const relative = path.relative(directory, candidate);\n return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));\n}\n\nfunction getLanguage(file: string, options: ResolvedOptions, explicitTarget = false): LanguageName | undefined {\n const lowerFile = file.toLowerCase();\n if (\n !explicitTarget &&\n (lowerFile.endsWith('.d.ts') ||\n lowerFile.endsWith('.d.mts') ||\n lowerFile.endsWith('.d.cts') ||\n lowerFile.endsWith('.min.js') ||\n lowerFile.endsWith('.pnp.cjs'))\n ) {\n return undefined;\n }\n\n if (\n !explicitTarget &&\n !options.includeTests &&\n (testFilePattern.test(path.basename(file)) || javaTestFilePattern.test(path.basename(file)))\n ) {\n return undefined;\n }\n\n // GCC treats an uppercase `.C` as C++; lowercasing first would misparse it with the C grammar.\n if (path.extname(file) === '.C') {\n return 'cpp';\n }\n\n return languageByExtension.get(path.extname(lowerFile));\n}\n\nfunction parsePercentInteger(value: string): number {\n const parsed = parsePositiveInteger(value);\n if (parsed > 100) {\n throw new InvalidArgumentError('Expected an integer between 1 and 100.');\n }\n return parsed;\n}\n\nfunction parseNonNegativeInteger(value: string): number {\n if (!/^\\d+$/u.test(value)) {\n throw new InvalidArgumentError('Expected a non-negative integer.');\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 0) {\n throw new InvalidArgumentError('Expected a non-negative integer.');\n }\n return parsed;\n}\n\nfunction parsePositiveInteger(value: string): number {\n if (!/^[1-9]\\d*$/u.test(value)) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1) {\n throw new InvalidArgumentError('Expected a positive integer.');\n }\n return parsed;\n}\n\nfunction formatPath(file: string, base: string): string {\n return path.relative(base, file) || path.basename(file);\n}\n\nfunction writeStdout(message: string): void {\n process.stdout.write(message);\n}\n\nfunction writeStderr(message: string): void {\n process.stderr.write(message);\n}\n\nfunction formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{collectDuplicationCandidates as t,measureCode as n}from"./metrics.js";import{measureArchitecture as r}from"./architectureMetrics.js";import{configFileName as i,loadConfig as a,resolveOptions as o,resolveThresholds as s}from"./cliConfig.js";import{measureTypeScriptProject as c}from"./typescriptProject.js";import{readFile as l,readdir as u,realpath as d,stat as f}from"node:fs/promises";import p from"node:os";import m from"node:path";import{Command as h,InvalidArgumentError as g}from"commander";const _=new Map([[`.c`,`c`],[`.c++`,`cpp`],[`.cc`,`cpp`],[`.cjs`,`javascript`],[`.cp`,`cpp`],[`.cpp`,`cpp`],[`.tcc`,`cpp`],[`.cts`,`typescript`],[`.cxx`,`cpp`],[`.go`,`go`],[`.h`,`cpp`],[`.hh`,`cpp`],[`.hpp`,`cpp`],[`.hxx`,`cpp`],[`.java`,`java`],[`.js`,`javascript`],[`.jsx`,`jsx`],[`.mjs`,`javascript`],[`.mts`,`typescript`],[`.py`,`python`],[`.rb`,`ruby`],[`.rs`,`rust`],[`.ts`,`typescript`],[`.tsx`,`tsx`]]),v=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`target`,`test-fixtures`,`vendor`,`venv`]),y=new Set([`__tests__`,`test`,`tests`,`spec`]),ee=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,te=/Test\.java$/u;ne().catch(e=>{Q(`Error: ${$(e)}\n`),process.exitCode=1});async function ne(){let e=new h().name(`code-gauge`).description(`Measure code metrics and list high-risk findings.`).argument(`[target]`,`file or directory to measure`,`.`).option(`--config <path>`,`config file to use instead of the auto-detected ${i}`).option(`--file-loc-threshold <number>`,`minimum file code LOC to report`,Y).option(`--function-loc-threshold <number>`,`minimum function physical LOC span to report`,Y).option(`--component-loc-threshold <number>`,`minimum React component physical LOC span to report`,Y).option(`--cognitive-threshold <number>`,`minimum cognitive complexity to report`,Y).option(`--cyclomatic-threshold <number>`,`minimum cyclomatic complexity to report`,Y).option(`--call-threshold <number>`,`minimum function call count to report`,Y).option(`--import-threshold <number>`,`minimum unique import sources per file to report`,Y).option(`--fan-out-threshold <number>`,`minimum intra-file fan-out per function to report`,Y).option(`--parameter-threshold <number>`,`minimum function parameter count to report`,Y).option(`--duplicate-block-threshold <number>`,`minimum count of duplicated code blocks per file to report`,Y).option(`--duplication-ratio-percent-threshold <number>`,`minimum percentage (1-100) of duplicated lines per file to report`,me).option(`--cross-file-duplicate-block-threshold <number>`,`minimum count of cross-file duplicate block groups per file to report`,Y).option(`--duplication-min-tokens <number>`,`minimum normalized token count for a duplicate region (default 40)`,Y).option(`--duplication-max-gap-tokens <number>`,`maximum token gap merged into one gapped clone group; 0 disables merging (default 30)`,he).option(`--transitive-dependency-threshold <number>`,`minimum transitively reachable local files to report`,Y).option(`--structural-breadth-threshold <number>`,`minimum structural breadth score to report`,Y).option(`--structural-coordination-threshold <number>`,`minimum structural coordination score to report`,Y).option(`--state-mutation-threshold <number>`,`minimum state mutation score to report`,Y).option(`--duplicate-symbol-group-threshold <number>`,`minimum duplicate symbol group count to report`,Y).option(`--max-findings <number>`,`maximum number of risk findings to print`,Y).option(`--largest-files <number>`,`number of largest files by code LOC to list`,Y).option(`--include-tests`,`include test files and test directories`).option(`--tsconfig <path>`,`TypeScript project file to use instead of auto-detected tsconfig.json`).option(`--json`,`print JSON output`).option(`--fail-on-error`,`exit with code 1 when files or directories cannot be scanned`).option(`--fail-on-risk`,`exit with code 1 when high-risk findings are found`);e.action(async(e,t)=>{let n=b(e),r=await a(t.config,await re(n)),i=o(t,r),s=await ie(n,i);A(s),await T(s),await ae(s,i,n);let c=oe(s.files,s.architecture,s.crossFileDuplication,s.componentFunctionKeys,s.namedComponentFunctionKeys,i,s.displayRoot);i.json?z(s,c,i):B(n,s,c,i),(s.fatalError||i.failOnError&&s.errors.length>0||i.failOnRisk&&c.length>0)&&(process.exitCode=1)}),await e.parseAsync()}function b(e){return e===`~`?p.homedir():e.startsWith(`~/`)?m.join(p.homedir(),e.slice(2)):m.resolve(e)}async function re(e){try{return(await f(e)).isDirectory()?e:m.dirname(e)}catch{return m.dirname(e)}}async function ie(e,t){let n=[],r=[],i=[],a=e;try{a=await d(e)}catch{}let o=m.dirname(a),s;try{s=await f(a)}catch(e){let t=`${X(a,o)}: ${$(e)}`;return{displayRoot:o,files:n,errors:[t],warnings:i,fatalError:t}}if(s.isFile()){let e=m.dirname(a),o=J(a,t,!0);if(!o){let t=`${X(a,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let s=x(t,n,r,i,e);return await k(a,o,`single-file`,s,a),{displayRoot:e,files:n,errors:r,warnings:i}}return await E(a,x(t,n,r,i,a)),{displayRoot:a,files:n,errors:r,warnings:i}}function x(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function ae(e,t,n){if(e.fatalError||e.files.length===0)return;let r=t.tsconfig,i=r!==void 0;if(!i&&!e.files.some(({file:e})=>S(e)))return;let a=r?b(r):await C(n);if(a)try{e.typeScriptProject=await c(a,e.files.map(({file:e})=>e)),e.componentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.map(e=>F(e.file,e.startLine,e.startColumn))),e.namedComponentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.flatMap(e=>e.name?[I(e.file,e.name,e.startLine)]:[]))}catch(t){i&&e.errors.push(`${X(a,e.displayRoot)}: ${$(t)}`)}}function S(e){return[`.cjs`,`.cts`,`.js`,`.jsx`,`.mjs`,`.mts`,`.ts`,`.tsx`].includes(m.extname(e))}async function C(e){let t=(await f(e)).isDirectory()?e:m.dirname(e);for(;;){let e=m.join(t,`tsconfig.json`);if(await w(e))return e;let n=m.dirname(t);if(n===t)return;t=n}}async function w(e){try{return(await f(e)).isFile()}catch{return!1}}async function T(e){if(!e.fatalError)try{e.architecture=r(e.files.map(({file:e,metrics:t})=>({file:e,metrics:t})),e.displayRoot)}catch(t){e.errors.push(`architecture metrics: ${$(t)}`)}}async function E(e,t){let n;try{n=await d(e)}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}if(!q(n,t.rootDirectory)||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r;try{r=await u(e,{withFileTypes:!0})}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}for(let n of r){let r=m.join(e,n.name);if(n.isSymbolicLink()){await D(n.name,r,t);continue}if(n.isDirectory()){if(K(n.name,t.options))continue;await E(r,t);continue}n.isFile()&&await O(r,t)}}async function D(e,t,n){let r;try{r=await d(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(!q(r,n.rootDirectory))return;let i;try{i=await f(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(i.isDirectory()){if(K(e,n.options)||K(m.basename(r),n.options))return;await E(t,n);return}i.isFile()&&await O(t,n,r,r)}async function O(e,t,n=e,r){let i=J(n,t.options);i&&await k(e,i,`directory`,t,r)}async function k(e,r,i,a,o){try{let s=o??await d(e);if(a.visitedFiles.has(s))return;a.visitedFiles.add(s);let c=await l(e,`utf8`),u={language:r,duplication:a.options.duplication},f={file:e,metrics:n(c,u)};if(i===`directory`)try{f.duplicationCandidates=t(c,u)}catch(t){a.warnings.push(`${X(e,a.rootDirectory)}: cross-file duplication candidates unavailable: ${$(t)}`)}a.files.push(f)}catch(t){a.errors.push(`${X(e,a.rootDirectory)}: ${$(t)}`)}}function A(t){if(t.fatalError||t.files.length<2)return;let n=t.files.flatMap(({file:e,duplicationCandidates:n})=>n?[{file:X(e,t.displayRoot),candidates:n}]:[]);n.length<2||(t.crossFileDuplication=e(n))}function oe(e,t,n,r,i,a,o){let c=new Map(t?.files.map(e=>[e.file,e])),l=e.flatMap(({file:e,metrics:t})=>{let l=t.functions.some(t=>t.returnsJsx||N(e,t,r,i)),u=s(a,t.language,l);return[...se(e,t,c.get(X(e,o)),n,u,o),...t.functions.flatMap(n=>ce(e,t.language,n,u,o,r,i))]});return l.sort(R),l}function se(e,t,n,r,i,a){let o=[],s=X(e,a);j(o,`file LOC`,t.lines.code,i.fileLoc),j(o,`import sources`,t.coupling.importSourceCount,i.import);let c=M(t.duplication.duplicateBlockGroups);return j(o,`duplicated blocks`,t.duplication.duplicateBlockCount,i.duplicateBlock,c),j(o,`duplicated lines (%)`,Math.floor(t.duplication.duplicationRatio*100),i.duplicationRatioPercent,c),r&&j(o,`cross-file duplicated blocks`,Object.hasOwn(r.duplicateBlockGroupCountByFile,s)?r.duplicateBlockGroupCountByFile[s]??0:0,i.crossFileDuplicateBlock,le(r,s)),n&&((t.lines.code>=100||n.directLocalDependencyCount>=8)&&j(o,`transitive local dependencies`,n.transitiveLocalDependencyCount,i.transitiveDependency),(o.length>0||n.directLocalDependencyCount>=8||n.structuralCoordination.score>=i.structuralCoordination)&&j(o,`structural breadth`,n.structuralBreadthScore,i.structuralBreadth),j(o,`structural coordination`,n.structuralCoordination.score,i.structuralCoordination),j(o,`state mutation`,n.structuralCoordination.stateMutationScore,i.stateMutation),j(o,`duplicate symbol groups`,n.duplicateSymbolGroupCount,i.duplicateSymbolGroup)),o.length===0?[]:[{file:s,language:t.language,kind:`file`,cyclomaticComplexity:t.cyclomaticComplexity,cognitiveComplexity:t.cognitiveComplexity,triggers:o,score:L(o)}]}function ce(e,t,n,r,i,a,o){let s=n.endLine-n.startLine+1,c=N(e,n,a,o),l=c?`component`:`function`,u=[];return j(u,`cognitive complexity`,n.cognitiveComplexity,r.cognitive),j(u,`cyclomatic complexity`,n.cyclomaticComplexity,r.cyclomatic),j(u,c?`component LOC`:`function LOC`,s,P(c,r)),j(u,`function calls`,n.callCount,r.call),j(u,`fan-out`,n.fanOut,r.fanOut),j(u,`parameters`,n.parameterCount,r.parameter),u.length===0?[]:[{file:X(e,i),language:t,kind:l,name:n.name??`<anonymous>`,startLine:n.startLine,endLine:n.endLine,cyclomaticComplexity:n.cyclomaticComplexity,cognitiveComplexity:n.cognitiveComplexity,triggers:u,score:L(u)}]}function j(e,t,n,r,i){n<r||e.push({metric:t,value:n,threshold:r,score:n/r,detail:i})}function le(e,t){let n=e.groups.filter(e=>e.files.includes(t));if(n.length!==0)return`${n.slice(0,3).map(e=>e.occurrences.map(({file:e,startLine:n,endLine:r})=>e===t?`${n}-${r}`:`${e}:${n}-${r}`).join(` ~ `)).join(`; `)}${n.length>3?`; ...`:``}`}function M(e){if(e.length!==0)return e.map(e=>e.map(({startLine:e,endLine:t})=>`${e}-${t}`).join(` ~ `)).join(`; `)}function N(e,t,n,r){return n?.has(F(e,t.startLine,t.startColumn))||(t.name?r?.has(I(e,t.name,t.startLine)):!1)||!1}function P(e,t){return e?t.componentLoc:t.functionLoc}function F(e,t,n){return`${m.resolve(e)}:${t}:${n}`}function I(e,t,n){return`${m.resolve(e)}:${t}:${n}`}function L(e){return Math.max(...e.map(e=>e.score))}function R(e,t){return t.score-e.score||e.file.localeCompare(t.file)||(e.startLine??0)-(t.startLine??0)||(e.endLine??0)-(t.endLine??0)||e.kind.localeCompare(t.kind)}function z(e,t,n){let r=G(e.files),i=t.slice(0,n.maxFindings);Z(JSON.stringify({summary:r,thresholds:n.thresholds,profileThresholds:n.profileThresholds,totalRisks:t.length,truncated:i.length<t.length,largestFiles:n.largestFiles>0?V(e.files,n.largestFiles,e.displayRoot):void 0,architecture:e.architecture,crossFileDuplication:e.crossFileDuplication,typeScriptProject:e.typeScriptProject,risks:i,errors:e.errors,warnings:e.warnings},void 0,2)+`
3
- `)}function B(e,t,n,r){if(t.fatalError){Q(`Error: ${t.fatalError}\n`);return}let{thresholds:i}=r,a=G(t.files);Z(`Measured ${a.fileCount} files under ${e}\n`),Z(`LOC ${a.linesOfCode}, NCSS ${a.ncssCount}, functions ${a.functionCount}, max cyclomatic ${a.maxCyclomaticComplexity}, max cognitive ${a.maxCognitiveComplexity}\n`),Z(`Calls ${a.callCount}, internal edges ${a.internalCallCount}, max call depth ${a.maxCallDepth}, imports ${a.importSourceCount}, exports ${a.exportCount}\n`),Z(`Type annotations ${a.typeAnnotationCount}, type aliases ${a.typeAliasCount}, interfaces ${a.interfaceCount}, avg cohesion ${a.averageFunctionIdentifierOverlap.toFixed(2)}\n`),t.architecture&&Z(`${fe(t.architecture)}\n`),t.typeScriptProject&&Z(`${pe(t.typeScriptProject)}\n`),Z(`Risk thresholds: file LOC >= ${i.fileLoc}, function LOC >= ${i.functionLoc}, component LOC >= ${i.componentLoc}, cognitive >= ${i.cognitive}, cyclomatic >= ${i.cyclomatic}, calls >= ${i.call}, imports >= ${i.import}, fan-out >= ${i.fanOut}, parameters >= ${i.parameter}, duplicated blocks >= ${i.duplicateBlock}, duplicated lines (%) >= ${i.duplicationRatioPercent}, cross-file duplicated blocks >= ${i.crossFileDuplicateBlock}\n`);let o=H(r.profileThresholds);if(o&&Z(`Per-language overrides: ${o}\n`),n.length===0)Z(`No high-risk findings found.
4
- `);else{let e=n.slice(0,r.maxFindings),t=n.length>e.length?` of ${n.length}`:``;Z(`\nHigh-risk findings (top ${e.length}${t}):\n`);for(let t of e)Z(`${U(t)} ${ue(t)} ${de(t)}\n`)}let s=t.crossFileDuplication?.groups??[];if(s.length>0){let e=s.slice(0,10),t=s.length>e.length?` of ${s.length}`:``;Z(`\nCross-file duplicate blocks (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.tokenCount} tokens: ${t.occurrences.map(({file:e,startLine:t,endLine:n})=>`${e}:${t}-${n}`).join(`, `)}\n`)}let c=t.architecture?.duplicateSymbolGroups??[];if(c.length>0){let e=c.toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name)).slice(0,10),t=c.length>e.length?` of ${c.length}`:``;Z(`\nDuplicate symbols (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.name}: ${t.declarations.map(e=>`${e.file}:${e.line}`).join(`, `)}\n`)}if(r.largestFiles>0){let e=V(t.files,r.largestFiles,t.displayRoot);Z(`\nLargest files by code LOC (top ${e.length}):\n`);for(let{file:t,codeLoc:n}of e)Z(`${t} (code LOC ${n})\n`)}if(t.warnings.length>0){Q(`\nDegraded ${t.warnings.length} files (measured, but excluded from cross-file matching):\n`);for(let e of t.warnings.slice(0,10))Q(`- ${e}\n`);t.warnings.length>10&&Q(`- ... ${t.warnings.length-10} more\n`)}if(t.errors.length>0){Q(`\nSkipped ${t.errors.length} files or directories:\n`);for(let e of t.errors.slice(0,10))Q(`- ${e}\n`);t.errors.length>10&&Q(`- ... ${t.errors.length-10} more\n`)}}function V(e,t,n){return e.map(({file:e,metrics:t})=>({file:X(e,n),codeLoc:t.lines.code})).toSorted((e,t)=>t.codeLoc-e.codeLoc||e.file.localeCompare(t.file)).slice(0,t)}function H(e){return Object.entries(e).map(([e,t])=>`${e} { ${Object.entries(t).map(([e,t])=>`${e} ${t}`).join(`, `)} }`).join(`; `)}function U(e){return e.startLine===void 0||e.endLine===void 0?e.file:`${e.file}:${e.startLine}-${e.endLine}`}function ue(e){return e.name?`${e.kind} ${e.name}`:e.kind}function de(e){return`(${e.triggers.map(e=>`${e.metric} ${W(e.value)} >= ${W(e.threshold)}${e.detail?` [${e.detail}]`:``}`).join(`, `)}; cyclomatic ${e.cyclomaticComplexity}, cognitive ${e.cognitiveComplexity})`}function W(e){return Number.isInteger(e)?String(e):e.toFixed(2)}function fe(e){let t=Math.max(0,...e.files.map(e=>e.structuralCoordination.stateMutationScore));return`Architecture max reachable files ${e.maxTransitiveLocalDependencyCount}, max structural breadth ${e.maxStructuralBreadthScore}, max structural coordination ${e.maxStructuralCoordinationScore}, max state mutation ${t}, duplicate symbol groups ${e.duplicateSymbolGroups.length}`}function pe(e){return`TypeScript project root files ${e.rootFileCount}, measured roots ${e.measuredRootFileCount}, semantic diagnostics ${e.semanticDiagnosticCount}, resolved calls ${e.resolvedCallExpressionCount}/${e.callExpressionCount} (${(e.resolvedCallExpressionRatio*100).toFixed(1)}%)`}function G(e){let t=0,n=0,r=0,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0,_=0;for(let v of e)t+=v.metrics.functionCount,n+=v.metrics.lines.code,r=Math.max(r,v.metrics.maxCyclomaticComplexity),i=Math.max(i,v.metrics.maxCognitiveComplexity),a+=v.metrics.ncssCount,o+=v.metrics.callGraph.callCount,s+=v.metrics.callGraph.internalCallCount,c=Math.max(c,v.metrics.callGraph.maxCallDepth),l+=v.metrics.coupling.importSourceCount,u+=v.metrics.coupling.relativeImportCount,d+=v.metrics.coupling.externalImportCount,f+=v.metrics.coupling.exportCount,p+=v.metrics.cohesion.averageFunctionIdentifierOverlap,m+=v.metrics.typeComplexity.typeAnnotationCount,h+=v.metrics.typeComplexity.typeAliasCount,g+=v.metrics.typeComplexity.interfaceCount,_+=v.metrics.typeComplexity.genericParameterCount;return{fileCount:e.length,functionCount:t,linesOfCode:n,maxCyclomaticComplexity:r,maxCognitiveComplexity:i,ncssCount:a,callCount:o,internalCallCount:s,maxCallDepth:c,importSourceCount:l,relativeImportCount:u,externalImportCount:d,exportCount:f,averageFunctionIdentifierOverlap:e.length===0?0:p/e.length,typeAnnotationCount:m,typeAliasCount:h,interfaceCount:g,genericParameterCount:_}}function K(e,t){return v.has(e)?!0:!t.includeTests&&y.has(e)}function q(e,t){let n=m.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${m.sep}`)&&!m.isAbsolute(n)}function J(e,t,n=!1){let r=e.toLowerCase();if(!(!n&&(r.endsWith(`.d.ts`)||r.endsWith(`.d.mts`)||r.endsWith(`.d.cts`)||r.endsWith(`.min.js`)||r.endsWith(`.pnp.cjs`)))&&!(!n&&!t.includeTests&&(ee.test(m.basename(e))||te.test(m.basename(e)))))return m.extname(e)===`.C`?`cpp`:_.get(m.extname(r))}function me(e){let t=Y(e);if(t>100)throw new g(`Expected an integer between 1 and 100.`);return t}function he(e){if(!/^\d+$/u.test(e))throw new g(`Expected a non-negative integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<0)throw new g(`Expected a non-negative integer.`);return t}function Y(e){if(!/^[1-9]\d*$/u.test(e))throw new g(`Expected a positive integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<1)throw new g(`Expected a positive integer.`);return t}function X(e,t){return m.relative(t,e)||m.basename(e)}function Z(e){process.stdout.write(e)}function Q(e){process.stderr.write(e)}function $(e){return e instanceof Error?e.message:String(e)}
2
+ import{measureCrossFileDuplication as e}from"./crossFileDuplication.js";import{collectCrossFileDuplicationFileData as t,measureCode as n}from"./metrics.js";import{measureArchitecture as r}from"./architectureMetrics.js";import{configFileName as i,loadConfig as a,resolveOptions as o,resolveThresholds as s}from"./cliConfig.js";import{measureTypeScriptProject as c}from"./typescriptProject.js";import{readFile as l,readdir as u,realpath as d,stat as f}from"node:fs/promises";import p from"node:os";import m from"node:path";import{Command as h,InvalidArgumentError as g}from"commander";const _=new Map([[`.c`,`c`],[`.c++`,`cpp`],[`.cc`,`cpp`],[`.cjs`,`javascript`],[`.cp`,`cpp`],[`.cpp`,`cpp`],[`.tcc`,`cpp`],[`.cts`,`typescript`],[`.cxx`,`cpp`],[`.go`,`go`],[`.h`,`cpp`],[`.hh`,`cpp`],[`.hpp`,`cpp`],[`.hxx`,`cpp`],[`.java`,`java`],[`.js`,`javascript`],[`.jsx`,`jsx`],[`.mjs`,`javascript`],[`.mts`,`typescript`],[`.py`,`python`],[`.rb`,`ruby`],[`.rs`,`rust`],[`.ts`,`typescript`],[`.tsx`,`tsx`]]),v=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`target`,`test-fixtures`,`vendor`,`venv`]),y=new Set([`__tests__`,`test`,`tests`,`spec`]),ee=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,te=/Test\.java$/u;ne().catch(e=>{Q(`Error: ${$(e)}\n`),process.exitCode=1});async function ne(){let e=new h().name(`code-gauge`).description(`Measure code metrics and list high-risk findings.`).argument(`[target]`,`file or directory to measure`,`.`).option(`--config <path>`,`config file to use instead of the auto-detected ${i}`).option(`--file-loc-threshold <number>`,`minimum file code LOC to report`,Y).option(`--function-loc-threshold <number>`,`minimum function physical LOC span to report`,Y).option(`--component-loc-threshold <number>`,`minimum React component physical LOC span to report`,Y).option(`--cognitive-threshold <number>`,`minimum cognitive complexity to report`,Y).option(`--cyclomatic-threshold <number>`,`minimum cyclomatic complexity to report`,Y).option(`--call-threshold <number>`,`minimum function call count to report`,Y).option(`--import-threshold <number>`,`minimum unique import sources per file to report`,Y).option(`--fan-out-threshold <number>`,`minimum intra-file fan-out per function to report`,Y).option(`--parameter-threshold <number>`,`minimum function parameter count to report`,Y).option(`--duplicate-block-threshold <number>`,`minimum count of duplicated code blocks per file to report`,Y).option(`--duplication-ratio-percent-threshold <number>`,`minimum percentage (1-100) of duplicated lines per file to report`,J).option(`--cross-file-duplicate-block-threshold <number>`,`minimum count of cross-file duplicate block groups per file to report`,Y).option(`--duplication-min-tokens <number>`,`minimum normalized token count for a duplicate region (default 40)`,Y).option(`--duplication-max-gap-tokens <number>`,`maximum token gap merged into one gapped clone group; 0 disables merging (default 30)`,he).option(`--duplication-min-similarity-percent <number>`,`minimum similarity percent (1-100) for near-miss (Type-3) clone blocks; 100 reports exact matches only (default 70)`,J).option(`--transitive-dependency-threshold <number>`,`minimum transitively reachable local files to report`,Y).option(`--structural-breadth-threshold <number>`,`minimum structural breadth score to report`,Y).option(`--structural-coordination-threshold <number>`,`minimum structural coordination score to report`,Y).option(`--state-mutation-threshold <number>`,`minimum state mutation score to report`,Y).option(`--duplicate-symbol-group-threshold <number>`,`minimum duplicate symbol group count to report`,Y).option(`--max-findings <number>`,`maximum number of risk findings to print`,Y).option(`--largest-files <number>`,`number of largest files by code LOC to list`,Y).option(`--include-tests`,`include test files and test directories`).option(`--tsconfig <path>`,`TypeScript project file to use instead of auto-detected tsconfig.json`).option(`--json`,`print JSON output`).option(`--fail-on-error`,`exit with code 1 when files or directories cannot be scanned`).option(`--fail-on-risk`,`exit with code 1 when high-risk findings are found`);e.action(async(e,t)=>{let n=b(e),r=await a(t.config,await re(n)),i=o(t,r),s=await ie(n,i);A(s,i),await T(s),await ae(s,i,n);let c=oe(s.files,s.architecture,s.crossFileDuplication,s.componentFunctionKeys,s.namedComponentFunctionKeys,i,s.displayRoot);i.json?R(s,c,i):z(n,s,c,i),(s.fatalError||i.failOnError&&s.errors.length>0||i.failOnRisk&&c.length>0)&&(process.exitCode=1)}),await e.parseAsync()}function b(e){return e===`~`?p.homedir():e.startsWith(`~/`)?m.join(p.homedir(),e.slice(2)):m.resolve(e)}async function re(e){try{return(await f(e)).isDirectory()?e:m.dirname(e)}catch{return m.dirname(e)}}async function ie(e,t){let n=[],r=[],i=[],a=e;try{a=await d(e)}catch{}let o=m.dirname(a),s;try{s=await f(a)}catch(e){let t=`${X(a,o)}: ${$(e)}`;return{displayRoot:o,files:n,errors:[t],warnings:i,fatalError:t}}if(s.isFile()){let e=m.dirname(a),o=q(a,t,!0);if(!o){let t=`${X(a,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],warnings:i,fatalError:t}}let s=x(t,n,r,i,e);return await k(a,o,`single-file`,s,a),{displayRoot:e,files:n,errors:r,warnings:i}}return await E(a,x(t,n,r,i,a)),{displayRoot:a,files:n,errors:r,warnings:i}}function x(e,t,n,r,i){return{options:e,files:t,errors:n,warnings:r,visitedDirectories:new Set,visitedFiles:new Set,rootDirectory:i}}async function ae(e,t,n){if(e.fatalError||e.files.length===0)return;let r=t.tsconfig,i=r!==void 0;if(!i&&!e.files.some(({file:e})=>S(e)))return;let a=r?b(r):await C(n);if(a)try{e.typeScriptProject=await c(a,e.files.map(({file:e})=>e)),e.componentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.map(e=>P(e.file,e.startLine,e.startColumn))),e.namedComponentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.flatMap(e=>e.name?[F(e.file,e.name,e.startLine)]:[]))}catch(t){i&&e.errors.push(`${X(a,e.displayRoot)}: ${$(t)}`)}}function S(e){return[`.cjs`,`.cts`,`.js`,`.jsx`,`.mjs`,`.mts`,`.ts`,`.tsx`].includes(m.extname(e))}async function C(e){let t=(await f(e)).isDirectory()?e:m.dirname(e);for(;;){let e=m.join(t,`tsconfig.json`);if(await w(e))return e;let n=m.dirname(t);if(n===t)return;t=n}}async function w(e){try{return(await f(e)).isFile()}catch{return!1}}async function T(e){if(!e.fatalError)try{e.architecture=r(e.files.map(({file:e,metrics:t})=>({file:e,metrics:t})),e.displayRoot)}catch(t){e.errors.push(`architecture metrics: ${$(t)}`)}}async function E(e,t){let n;try{n=await d(e)}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}if(!K(n,t.rootDirectory)||t.visitedDirectories.has(n))return;t.visitedDirectories.add(n);let r;try{r=await u(e,{withFileTypes:!0})}catch(n){t.errors.push(`${X(e,t.rootDirectory)}: ${$(n)}`);return}for(let n of r){let r=m.join(e,n.name);if(n.isSymbolicLink()){await D(n.name,r,t);continue}if(n.isDirectory()){if(G(n.name,t.options))continue;await E(r,t);continue}n.isFile()&&await O(r,t)}}async function D(e,t,n){let r;try{r=await d(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(!K(r,n.rootDirectory))return;let i;try{i=await f(t)}catch(e){n.errors.push(`${X(t,n.rootDirectory)}: ${$(e)}`);return}if(i.isDirectory()){if(G(e,n.options)||G(m.basename(r),n.options))return;await E(t,n);return}i.isFile()&&await O(t,n,r,r)}async function O(e,t,n=e,r){let i=q(n,t.options);i&&await k(e,i,`directory`,t,r)}async function k(e,r,i,a,o){try{let s=o??await d(e);if(a.visitedFiles.has(s))return;a.visitedFiles.add(s);let c=await l(e,`utf8`),u={language:r,duplication:a.options.duplication},f={file:e,metrics:n(c,u)};if(i===`directory`)try{f.duplicationCandidates=t(c,u)}catch(t){a.warnings.push(`${X(e,a.rootDirectory)}: cross-file duplication candidates unavailable: ${$(t)}`)}a.files.push(f)}catch(t){a.errors.push(`${X(e,a.rootDirectory)}: ${$(t)}`)}}function A(t,n){if(t.fatalError||t.files.length<2)return;let r=t.files.flatMap(({file:e,duplicationCandidates:n})=>n?[{file:X(e,t.displayRoot),...n}]:[]);r.length<2||(t.crossFileDuplication=e(r,n.duplication))}function oe(e,t,n,r,i,a,o){let c=new Map(t?.files.map(e=>[e.file,e])),l=e.flatMap(({file:e,metrics:t})=>{let l=t.functions.some(t=>t.returnsJsx||M(e,t,r,i)),u=s(a,t.language,l);return[...se(e,t,c.get(X(e,o)),n,u,o),...t.functions.flatMap(n=>ce(e,t.language,n,u,o,r,i))]});return l.sort(L),l}function se(e,t,n,r,i,a){let o=[],s=X(e,a);j(o,`file LOC`,t.lines.code,i.fileLoc),j(o,`import sources`,t.coupling.importSourceCount,i.import);let c=ue(t.duplication.duplicateBlockGroups);return j(o,`duplicated blocks`,t.duplication.duplicateBlockCount,i.duplicateBlock,c),j(o,`duplicated lines (%)`,Math.floor(t.duplication.duplicationRatio*100),i.duplicationRatioPercent,c),r&&j(o,`cross-file duplicated blocks`,Object.hasOwn(r.duplicateBlockGroupCountByFile,s)?r.duplicateBlockGroupCountByFile[s]??0:0,i.crossFileDuplicateBlock,le(r,s)),n&&((t.lines.code>=100||n.directLocalDependencyCount>=8)&&j(o,`transitive local dependencies`,n.transitiveLocalDependencyCount,i.transitiveDependency),(o.length>0||n.directLocalDependencyCount>=8||n.structuralCoordination.score>=i.structuralCoordination)&&j(o,`structural breadth`,n.structuralBreadthScore,i.structuralBreadth),j(o,`structural coordination`,n.structuralCoordination.score,i.structuralCoordination),j(o,`state mutation`,n.structuralCoordination.stateMutationScore,i.stateMutation),j(o,`duplicate symbol groups`,n.duplicateSymbolGroupCount,i.duplicateSymbolGroup)),o.length===0?[]:[{file:s,language:t.language,kind:`file`,cyclomaticComplexity:t.cyclomaticComplexity,cognitiveComplexity:t.cognitiveComplexity,triggers:o,score:I(o)}]}function ce(e,t,n,r,i,a,o){let s=n.endLine-n.startLine+1,c=M(e,n,a,o),l=c?`component`:`function`,u=[];return j(u,`cognitive complexity`,n.cognitiveComplexity,r.cognitive),j(u,`cyclomatic complexity`,n.cyclomaticComplexity,r.cyclomatic),j(u,c?`component LOC`:`function LOC`,s,N(c,r)),j(u,`function calls`,n.callCount,r.call),j(u,`fan-out`,n.fanOut,r.fanOut),j(u,`parameters`,n.parameterCount,r.parameter),u.length===0?[]:[{file:X(e,i),language:t,kind:l,name:n.name??`<anonymous>`,startLine:n.startLine,endLine:n.endLine,cyclomaticComplexity:n.cyclomaticComplexity,cognitiveComplexity:n.cognitiveComplexity,triggers:u,score:I(u)}]}function j(e,t,n,r,i){n<r||e.push({metric:t,value:n,threshold:r,score:n/r,detail:i})}function le(e,t){let n=e.groups.filter(e=>e.files.includes(t));if(n.length!==0)return`${n.slice(0,3).map(e=>e.occurrences.map(({file:e,startLine:n,endLine:r})=>e===t?`${n}-${r}`:`${e}:${n}-${r}`).join(` ~ `)).join(`; `)}${n.length>3?`; ...`:``}`}function ue(e){if(e.length!==0)return e.map(e=>e.map(({startLine:e,endLine:t})=>`${e}-${t}`).join(` ~ `)).join(`; `)}function M(e,t,n,r){return n?.has(P(e,t.startLine,t.startColumn))||(t.name?r?.has(F(e,t.name,t.startLine)):!1)||!1}function N(e,t){return e?t.componentLoc:t.functionLoc}function P(e,t,n){return`${m.resolve(e)}:${t}:${n}`}function F(e,t,n){return`${m.resolve(e)}:${t}:${n}`}function I(e){return Math.max(...e.map(e=>e.score))}function L(e,t){return t.score-e.score||e.file.localeCompare(t.file)||(e.startLine??0)-(t.startLine??0)||(e.endLine??0)-(t.endLine??0)||e.kind.localeCompare(t.kind)}function R(e,t,n){let r=W(e.files),i=t.slice(0,n.maxFindings);Z(JSON.stringify({summary:r,thresholds:n.thresholds,profileThresholds:n.profileThresholds,totalRisks:t.length,truncated:i.length<t.length,largestFiles:n.largestFiles>0?B(e.files,n.largestFiles,e.displayRoot):void 0,architecture:e.architecture,crossFileDuplication:e.crossFileDuplication,typeScriptProject:e.typeScriptProject,risks:i,errors:e.errors,warnings:e.warnings},void 0,2)+`
3
+ `)}function z(e,t,n,r){if(t.fatalError){Q(`Error: ${t.fatalError}\n`);return}let{thresholds:i}=r,a=W(t.files);Z(`Measured ${a.fileCount} files under ${e}\n`),Z(`LOC ${a.linesOfCode}, NCSS ${a.ncssCount}, functions ${a.functionCount}, max cyclomatic ${a.maxCyclomaticComplexity}, max cognitive ${a.maxCognitiveComplexity}\n`),Z(`Calls ${a.callCount}, internal edges ${a.internalEdgeCount}, max call depth ${a.maxCallDepth}, imports ${a.importSourceCount}, exports ${a.exportCount}\n`),Z(`Type annotations ${a.typeAnnotationCount}, type aliases ${a.typeAliasCount}, interfaces ${a.interfaceCount}, avg cohesion ${a.averageFunctionIdentifierOverlap.toFixed(2)}\n`),t.architecture&&Z(`${pe(t.architecture)}\n`),t.typeScriptProject&&Z(`${me(t.typeScriptProject)}\n`),Z(`Risk thresholds: file LOC >= ${i.fileLoc}, function LOC >= ${i.functionLoc}, component LOC >= ${i.componentLoc}, cognitive >= ${i.cognitive}, cyclomatic >= ${i.cyclomatic}, calls >= ${i.call}, imports >= ${i.import}, fan-out >= ${i.fanOut}, parameters >= ${i.parameter}, duplicated blocks >= ${i.duplicateBlock}, duplicated lines (%) >= ${i.duplicationRatioPercent}, cross-file duplicated blocks >= ${i.crossFileDuplicateBlock}\n`);let o=V(r.profileThresholds);if(o&&Z(`Per-language overrides: ${o}\n`),n.length===0)Z(`No high-risk findings found.
4
+ `);else{let e=n.slice(0,r.maxFindings),t=n.length>e.length?` of ${n.length}`:``;Z(`\nHigh-risk findings (top ${e.length}${t}):\n`);for(let t of e)Z(`${H(t)} ${de(t)} ${fe(t)}\n`)}let s=t.crossFileDuplication?.groups??[];if(s.length>0){let e=s.slice(0,10),t=s.length>e.length?` of ${s.length}`:``;Z(`\nCross-file duplicate blocks (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.tokenCount} tokens: ${t.occurrences.map(({file:e,startLine:t,endLine:n})=>`${e}:${t}-${n}`).join(`, `)}\n`)}let c=t.architecture?.duplicateSymbolGroups??[];if(c.length>0){let e=c.toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name)).slice(0,10),t=c.length>e.length?` of ${c.length}`:``;Z(`\nDuplicate symbols (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.name}: ${t.declarations.map(e=>`${e.file}:${e.line}`).join(`, `)}\n`)}if(r.largestFiles>0){let e=B(t.files,r.largestFiles,t.displayRoot);Z(`\nLargest files by code LOC (top ${e.length}):\n`);for(let{file:t,codeLoc:n}of e)Z(`${t} (code LOC ${n})\n`)}if(t.warnings.length>0){Q(`\nDegraded ${t.warnings.length} files (measured, but excluded from cross-file matching):\n`);for(let e of t.warnings.slice(0,10))Q(`- ${e}\n`);t.warnings.length>10&&Q(`- ... ${t.warnings.length-10} more\n`)}if(t.errors.length>0){Q(`\nSkipped ${t.errors.length} files or directories:\n`);for(let e of t.errors.slice(0,10))Q(`- ${e}\n`);t.errors.length>10&&Q(`- ... ${t.errors.length-10} more\n`)}}function B(e,t,n){return e.map(({file:e,metrics:t})=>({file:X(e,n),codeLoc:t.lines.code})).toSorted((e,t)=>t.codeLoc-e.codeLoc||e.file.localeCompare(t.file)).slice(0,t)}function V(e){return Object.entries(e).map(([e,t])=>`${e} { ${Object.entries(t).map(([e,t])=>`${e} ${t}`).join(`, `)} }`).join(`; `)}function H(e){return e.startLine===void 0||e.endLine===void 0?e.file:`${e.file}:${e.startLine}-${e.endLine}`}function de(e){return e.name?`${e.kind} ${e.name}`:e.kind}function fe(e){return`(${e.triggers.map(e=>`${e.metric} ${U(e.value)} >= ${U(e.threshold)}${e.detail?` [${e.detail}]`:``}`).join(`, `)}; cyclomatic ${e.cyclomaticComplexity}, cognitive ${e.cognitiveComplexity})`}function U(e){return Number.isInteger(e)?String(e):e.toFixed(2)}function pe(e){let t=Math.max(0,...e.files.map(e=>e.structuralCoordination.stateMutationScore));return`Architecture max reachable files ${e.maxTransitiveLocalDependencyCount}, max structural breadth ${e.maxStructuralBreadthScore}, max structural coordination ${e.maxStructuralCoordinationScore}, max state mutation ${t}, duplicate symbol groups ${e.duplicateSymbolGroups.length}`}function me(e){return`TypeScript project root files ${e.rootFileCount}, measured roots ${e.measuredRootFileCount}, semantic diagnostics ${e.semanticDiagnosticCount}, resolved calls ${e.resolvedCallExpressionCount}/${e.callExpressionCount} (${(e.resolvedCallExpressionRatio*100).toFixed(1)}%)`}function W(e){let t=0,n=0,r=0,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0,_=0;for(let v of e)t+=v.metrics.functionCount,n+=v.metrics.lines.code,r=Math.max(r,v.metrics.maxCyclomaticComplexity),i=Math.max(i,v.metrics.maxCognitiveComplexity),a+=v.metrics.ncssCount,o+=v.metrics.callGraph.callCount,s+=v.metrics.callGraph.internalEdgeCount,c=Math.max(c,v.metrics.callGraph.maxCallDepth),l+=v.metrics.coupling.importSourceCount,u+=v.metrics.coupling.relativeImportCount,d+=v.metrics.coupling.externalImportCount,f+=v.metrics.coupling.exportCount,p+=v.metrics.cohesion.averageFunctionIdentifierOverlap,m+=v.metrics.typeComplexity.typeAnnotationCount,h+=v.metrics.typeComplexity.typeAliasCount,g+=v.metrics.typeComplexity.interfaceCount,_+=v.metrics.typeComplexity.genericParameterCount;return{fileCount:e.length,functionCount:t,linesOfCode:n,maxCyclomaticComplexity:r,maxCognitiveComplexity:i,ncssCount:a,callCount:o,internalEdgeCount:s,maxCallDepth:c,importSourceCount:l,relativeImportCount:u,externalImportCount:d,exportCount:f,averageFunctionIdentifierOverlap:e.length===0?0:p/e.length,typeAnnotationCount:m,typeAliasCount:h,interfaceCount:g,genericParameterCount:_}}function G(e,t){return v.has(e)?!0:!t.includeTests&&y.has(e)}function K(e,t){let n=m.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${m.sep}`)&&!m.isAbsolute(n)}function q(e,t,n=!1){let r=e.toLowerCase();if(!(!n&&(r.endsWith(`.d.ts`)||r.endsWith(`.d.mts`)||r.endsWith(`.d.cts`)||r.endsWith(`.min.js`)||r.endsWith(`.pnp.cjs`)))&&!(!n&&!t.includeTests&&(ee.test(m.basename(e))||te.test(m.basename(e)))))return m.extname(e)===`.C`?`cpp`:_.get(m.extname(r))}function J(e){let t=Y(e);if(t>100)throw new g(`Expected an integer between 1 and 100.`);return t}function he(e){if(!/^\d+$/u.test(e))throw new g(`Expected a non-negative integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<0)throw new g(`Expected a non-negative integer.`);return t}function Y(e){if(!/^[1-9]\d*$/u.test(e))throw new g(`Expected a positive integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<1)throw new g(`Expected a positive integer.`);return t}function X(e,t){return m.relative(t,e)||m.basename(e)}function Z(e){process.stdout.write(e)}function Q(e){process.stderr.write(e)}function $(e){return e instanceof Error?e.message:String(e)}
5
5
  //# sourceMappingURL=cli.js.map