onbuzz 3.3.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/LICENSE +267 -0
- package/README.md +425 -0
- package/bin/cli.js +556 -0
- package/bin/loxia-terminal-v2.js +162 -0
- package/bin/loxia-terminal.js +90 -0
- package/bin/start-with-terminal.js +200 -0
- package/node_modules/@isaacs/balanced-match/LICENSE.md +23 -0
- package/node_modules/@isaacs/balanced-match/README.md +60 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts +9 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js +59 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/commonjs/package.json +3 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts +9 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.js +54 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/index.js.map +1 -0
- package/node_modules/@isaacs/balanced-match/dist/esm/package.json +3 -0
- package/node_modules/@isaacs/balanced-match/package.json +79 -0
- package/node_modules/@isaacs/brace-expansion/LICENSE +23 -0
- package/node_modules/@isaacs/brace-expansion/README.md +97 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts +6 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js +199 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/package.json +3 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts +6 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js +195 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/@isaacs/brace-expansion/dist/esm/package.json +3 -0
- package/node_modules/@isaacs/brace-expansion/package.json +60 -0
- package/node_modules/glob/LICENSE.md +63 -0
- package/node_modules/glob/README.md +1177 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts +388 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/glob.js +247 -0
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -0
- package/node_modules/glob/dist/commonjs/has-magic.d.ts +14 -0
- package/node_modules/glob/dist/commonjs/has-magic.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
- package/node_modules/glob/dist/commonjs/has-magic.js.map +1 -0
- package/node_modules/glob/dist/commonjs/ignore.d.ts +24 -0
- package/node_modules/glob/dist/commonjs/ignore.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/ignore.js +119 -0
- package/node_modules/glob/dist/commonjs/ignore.js.map +1 -0
- package/node_modules/glob/dist/commonjs/index.d.ts +97 -0
- package/node_modules/glob/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/index.js +68 -0
- package/node_modules/glob/dist/commonjs/index.js.map +1 -0
- package/node_modules/glob/dist/commonjs/index.min.js +4 -0
- package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
- package/node_modules/glob/dist/commonjs/package.json +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts +76 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/pattern.js +219 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -0
- package/node_modules/glob/dist/commonjs/processor.d.ts +59 -0
- package/node_modules/glob/dist/commonjs/processor.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/processor.js +301 -0
- package/node_modules/glob/dist/commonjs/processor.js.map +1 -0
- package/node_modules/glob/dist/commonjs/walker.d.ts +97 -0
- package/node_modules/glob/dist/commonjs/walker.d.ts.map +1 -0
- package/node_modules/glob/dist/commonjs/walker.js +387 -0
- package/node_modules/glob/dist/commonjs/walker.js.map +1 -0
- package/node_modules/glob/dist/esm/glob.d.ts +388 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/glob.js +243 -0
- package/node_modules/glob/dist/esm/glob.js.map +1 -0
- package/node_modules/glob/dist/esm/has-magic.d.ts +14 -0
- package/node_modules/glob/dist/esm/has-magic.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/has-magic.js +23 -0
- package/node_modules/glob/dist/esm/has-magic.js.map +1 -0
- package/node_modules/glob/dist/esm/ignore.d.ts +24 -0
- package/node_modules/glob/dist/esm/ignore.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/ignore.js +115 -0
- package/node_modules/glob/dist/esm/ignore.js.map +1 -0
- package/node_modules/glob/dist/esm/index.d.ts +97 -0
- package/node_modules/glob/dist/esm/index.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/index.js +55 -0
- package/node_modules/glob/dist/esm/index.js.map +1 -0
- package/node_modules/glob/dist/esm/index.min.js +4 -0
- package/node_modules/glob/dist/esm/index.min.js.map +7 -0
- package/node_modules/glob/dist/esm/package.json +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts +76 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/pattern.js +215 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -0
- package/node_modules/glob/dist/esm/processor.d.ts +59 -0
- package/node_modules/glob/dist/esm/processor.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/processor.js +294 -0
- package/node_modules/glob/dist/esm/processor.js.map +1 -0
- package/node_modules/glob/dist/esm/walker.d.ts +97 -0
- package/node_modules/glob/dist/esm/walker.d.ts.map +1 -0
- package/node_modules/glob/dist/esm/walker.js +381 -0
- package/node_modules/glob/dist/esm/walker.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/LICENSE.md +55 -0
- package/node_modules/glob/node_modules/minimatch/README.md +453 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +2 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +20 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +591 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts +8 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +152 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +15 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +30 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +94 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +1029 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/package.json +3 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +22 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +2 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +20 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +587 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts +8 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +148 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +15 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +26 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +94 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +1016 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/package.json +3 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +22 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +34 -0
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
- package/node_modules/glob/node_modules/minimatch/package.json +67 -0
- package/node_modules/glob/package.json +101 -0
- package/node_modules/minipass/LICENSE +15 -0
- package/node_modules/minipass/README.md +825 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/minipass/dist/commonjs/index.js +1028 -0
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -0
- package/node_modules/minipass/dist/commonjs/package.json +3 -0
- package/node_modules/minipass/dist/esm/index.d.ts +549 -0
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -0
- package/node_modules/minipass/dist/esm/index.js +1018 -0
- package/node_modules/minipass/dist/esm/index.js.map +1 -0
- package/node_modules/minipass/dist/esm/package.json +3 -0
- package/node_modules/minipass/package.json +82 -0
- package/node_modules/package-json-from-dist/LICENSE.md +63 -0
- package/node_modules/package-json-from-dist/README.md +110 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +89 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.js +134 -0
- package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +1 -0
- package/node_modules/package-json-from-dist/dist/commonjs/package.json +3 -0
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts +89 -0
- package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +1 -0
- package/node_modules/package-json-from-dist/dist/esm/index.js +129 -0
- package/node_modules/package-json-from-dist/dist/esm/index.js.map +1 -0
- package/node_modules/package-json-from-dist/dist/esm/package.json +3 -0
- package/node_modules/package-json-from-dist/package.json +68 -0
- package/node_modules/path-scurry/LICENSE.md +55 -0
- package/node_modules/path-scurry/README.md +636 -0
- package/node_modules/path-scurry/dist/commonjs/index.d.ts +1115 -0
- package/node_modules/path-scurry/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
- package/node_modules/path-scurry/dist/commonjs/index.js.map +1 -0
- package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
- package/node_modules/path-scurry/dist/esm/index.d.ts +1115 -0
- package/node_modules/path-scurry/dist/esm/index.d.ts.map +1 -0
- package/node_modules/path-scurry/dist/esm/index.js +1983 -0
- package/node_modules/path-scurry/dist/esm/index.js.map +1 -0
- package/node_modules/path-scurry/dist/esm/package.json +3 -0
- package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +383 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +1323 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1589 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +1323 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1585 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +101 -0
- package/node_modules/path-scurry/package.json +88 -0
- package/node_modules/rimraf/LICENSE.md +55 -0
- package/node_modules/rimraf/README.md +226 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.js +58 -0
- package/node_modules/rimraf/dist/commonjs/default-tmp.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/error.d.ts +6 -0
- package/node_modules/rimraf/dist/commonjs/error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/error.js +10 -0
- package/node_modules/rimraf/dist/commonjs/error.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.js +38 -0
- package/node_modules/rimraf/dist/commonjs/fix-eperm.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fs.d.ts +15 -0
- package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/fs.js +33 -0
- package/node_modules/rimraf/dist/commonjs/fs.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.js +24 -0
- package/node_modules/rimraf/dist/commonjs/ignore-enoent.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/index.d.ts +50 -0
- package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/index.js +78 -0
- package/node_modules/rimraf/dist/commonjs/index.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts +34 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.js +53 -0
- package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/package.json +3 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.js +48 -0
- package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.js +19 -0
- package/node_modules/rimraf/dist/commonjs/readdir-or-error.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts +8 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.js +65 -0
- package/node_modules/rimraf/dist/commonjs/retry-busy.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts +3 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.js +8 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-manual.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js +138 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.js +24 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-native.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +103 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.js +159 -0
- package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -0
- package/node_modules/rimraf/dist/commonjs/use-native.d.ts +4 -0
- package/node_modules/rimraf/dist/commonjs/use-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/commonjs/use-native.js +18 -0
- package/node_modules/rimraf/dist/commonjs/use-native.js.map +1 -0
- package/node_modules/rimraf/dist/esm/bin.d.mts +3 -0
- package/node_modules/rimraf/dist/esm/bin.d.mts.map +1 -0
- package/node_modules/rimraf/dist/esm/bin.mjs +250 -0
- package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -0
- package/node_modules/rimraf/dist/esm/default-tmp.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/default-tmp.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/default-tmp.js +55 -0
- package/node_modules/rimraf/dist/esm/default-tmp.js.map +1 -0
- package/node_modules/rimraf/dist/esm/error.d.ts +6 -0
- package/node_modules/rimraf/dist/esm/error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/error.js +5 -0
- package/node_modules/rimraf/dist/esm/error.js.map +1 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.js +33 -0
- package/node_modules/rimraf/dist/esm/fix-eperm.js.map +1 -0
- package/node_modules/rimraf/dist/esm/fs.d.ts +15 -0
- package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/fs.js +18 -0
- package/node_modules/rimraf/dist/esm/fs.js.map +1 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.js +19 -0
- package/node_modules/rimraf/dist/esm/ignore-enoent.js.map +1 -0
- package/node_modules/rimraf/dist/esm/index.d.ts +50 -0
- package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/index.js +70 -0
- package/node_modules/rimraf/dist/esm/index.js.map +1 -0
- package/node_modules/rimraf/dist/esm/opt-arg.d.ts +34 -0
- package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/opt-arg.js +46 -0
- package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -0
- package/node_modules/rimraf/dist/esm/package.json +3 -0
- package/node_modules/rimraf/dist/esm/path-arg.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/path-arg.js +46 -0
- package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.js +14 -0
- package/node_modules/rimraf/dist/esm/readdir-or-error.js.map +1 -0
- package/node_modules/rimraf/dist/esm/retry-busy.d.ts +8 -0
- package/node_modules/rimraf/dist/esm/retry-busy.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/retry-busy.js +60 -0
- package/node_modules/rimraf/dist/esm/retry-busy.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts +3 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.js +5 -0
- package/node_modules/rimraf/dist/esm/rimraf-manual.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +133 -0
- package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.js +19 -0
- package/node_modules/rimraf/dist/esm/rimraf-native.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.js +98 -0
- package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.js +154 -0
- package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -0
- package/node_modules/rimraf/dist/esm/use-native.d.ts +4 -0
- package/node_modules/rimraf/dist/esm/use-native.d.ts.map +1 -0
- package/node_modules/rimraf/dist/esm/use-native.js +15 -0
- package/node_modules/rimraf/dist/esm/use-native.js.map +1 -0
- package/node_modules/rimraf/package.json +92 -0
- package/package.json +152 -0
- package/scripts/install-scanners.js +258 -0
- package/scripts/watchdog.js +147 -0
- package/src/analyzers/CSSAnalyzer.js +297 -0
- package/src/analyzers/ConfigValidator.js +690 -0
- package/src/analyzers/ESLintAnalyzer.js +320 -0
- package/src/analyzers/JavaScriptAnalyzer.js +261 -0
- package/src/analyzers/PrettierFormatter.js +247 -0
- package/src/analyzers/PythonAnalyzer.js +283 -0
- package/src/analyzers/SecurityAnalyzer.js +729 -0
- package/src/analyzers/SparrowAnalyzer.js +341 -0
- package/src/analyzers/TypeScriptAnalyzer.js +247 -0
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
- package/src/analyzers/codeCloneDetector/detector.js +250 -0
- package/src/analyzers/codeCloneDetector/index.js +192 -0
- package/src/analyzers/codeCloneDetector/parser.js +199 -0
- package/src/analyzers/codeCloneDetector/reporter.js +148 -0
- package/src/analyzers/codeCloneDetector/scanner.js +88 -0
- package/src/core/agentPool.js +1957 -0
- package/src/core/agentScheduler.js +3212 -0
- package/src/core/contextManager.js +709 -0
- package/src/core/flowExecutor.js +928 -0
- package/src/core/messageProcessor.js +808 -0
- package/src/core/orchestrator.js +584 -0
- package/src/core/stateManager.js +1500 -0
- package/src/index.js +972 -0
- package/src/interfaces/cli.js +553 -0
- package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +208 -0
- package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +236 -0
- package/src/interfaces/terminal/__tests__/smoke/agents.test.js +138 -0
- package/src/interfaces/terminal/__tests__/smoke/components.test.js +137 -0
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +350 -0
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -0
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +332 -0
- package/src/interfaces/terminal/__tests__/smoke/messages.test.js +256 -0
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +388 -0
- package/src/interfaces/terminal/api/apiClient.js +299 -0
- package/src/interfaces/terminal/api/messageRouter.js +262 -0
- package/src/interfaces/terminal/api/session.js +266 -0
- package/src/interfaces/terminal/api/websocket.js +497 -0
- package/src/interfaces/terminal/components/AgentCreator.js +705 -0
- package/src/interfaces/terminal/components/AgentEditor.js +678 -0
- package/src/interfaces/terminal/components/AgentSwitcher.js +330 -0
- package/src/interfaces/terminal/components/ErrorBoundary.js +92 -0
- package/src/interfaces/terminal/components/ErrorPanel.js +264 -0
- package/src/interfaces/terminal/components/Header.js +28 -0
- package/src/interfaces/terminal/components/HelpPanel.js +231 -0
- package/src/interfaces/terminal/components/InputBox.js +118 -0
- package/src/interfaces/terminal/components/Layout.js +603 -0
- package/src/interfaces/terminal/components/LoadingSpinner.js +71 -0
- package/src/interfaces/terminal/components/MessageList.js +281 -0
- package/src/interfaces/terminal/components/MultilineTextInput.js +251 -0
- package/src/interfaces/terminal/components/SearchPanel.js +265 -0
- package/src/interfaces/terminal/components/SettingsPanel.js +415 -0
- package/src/interfaces/terminal/components/StatusBar.js +65 -0
- package/src/interfaces/terminal/components/TextInput.js +127 -0
- package/src/interfaces/terminal/config/agentEditorConstants.js +227 -0
- package/src/interfaces/terminal/config/constants.js +393 -0
- package/src/interfaces/terminal/index.js +168 -0
- package/src/interfaces/terminal/state/useAgentControl.js +496 -0
- package/src/interfaces/terminal/state/useAgents.js +537 -0
- package/src/interfaces/terminal/state/useConnection.js +444 -0
- package/src/interfaces/terminal/state/useMessages.js +630 -0
- package/src/interfaces/terminal/state/useTools.js +554 -0
- package/src/interfaces/terminal/utils/debugLogger.js +44 -0
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -0
- package/src/interfaces/terminal/utils/theme.js +85 -0
- package/src/interfaces/webServer.js +5457 -0
- package/src/modules/fileExplorer/controller.js +413 -0
- package/src/modules/fileExplorer/index.js +37 -0
- package/src/modules/fileExplorer/middleware.js +92 -0
- package/src/modules/fileExplorer/routes.js +158 -0
- package/src/modules/fileExplorer/types.js +44 -0
- package/src/services/agentActivityService.js +399 -0
- package/src/services/aiService.js +2618 -0
- package/src/services/apiKeyManager.js +334 -0
- package/src/services/benchmarkService.js +196 -0
- package/src/services/budgetService.js +565 -0
- package/src/services/contextInjectionService.js +268 -0
- package/src/services/conversationCompactionService.js +1103 -0
- package/src/services/credentialVault.js +685 -0
- package/src/services/errorHandler.js +810 -0
- package/src/services/fileAttachmentService.js +547 -0
- package/src/services/flowContextService.js +189 -0
- package/src/services/memoryService.js +521 -0
- package/src/services/modelRouterService.js +365 -0
- package/src/services/modelsService.js +323 -0
- package/src/services/ollamaService.js +452 -0
- package/src/services/portRegistry.js +336 -0
- package/src/services/portTracker.js +223 -0
- package/src/services/projectDetector.js +404 -0
- package/src/services/promptService.js +372 -0
- package/src/services/qualityInspector.js +796 -0
- package/src/services/scheduleService.js +725 -0
- package/src/services/serviceRegistry.js +386 -0
- package/src/services/skillsService.js +486 -0
- package/src/services/telegramService.js +920 -0
- package/src/services/tokenCountingService.js +316 -0
- package/src/services/visualEditorBridge.js +1033 -0
- package/src/services/visualEditorServer.js +1727 -0
- package/src/services/whatsappService.js +663 -0
- package/src/tools/__tests__/webTool.e2e.test.js +569 -0
- package/src/tools/__tests__/webTool.unit.test.js +195 -0
- package/src/tools/agentCommunicationTool.js +1343 -0
- package/src/tools/agentDelayTool.js +498 -0
- package/src/tools/asyncToolManager.js +604 -0
- package/src/tools/baseTool.js +887 -0
- package/src/tools/browserTool.js +897 -0
- package/src/tools/cloneDetectionTool.js +581 -0
- package/src/tools/codeMapTool.js +857 -0
- package/src/tools/dependencyResolverTool.js +1212 -0
- package/src/tools/docxTool.js +623 -0
- package/src/tools/excelTool.js +636 -0
- package/src/tools/fileContentReplaceTool.js +840 -0
- package/src/tools/fileTreeTool.js +833 -0
- package/src/tools/filesystemTool.js +1217 -0
- package/src/tools/helpTool.js +198 -0
- package/src/tools/imageTool.js +1034 -0
- package/src/tools/importAnalyzerTool.js +1056 -0
- package/src/tools/jobDoneTool.js +388 -0
- package/src/tools/memoryTool.js +554 -0
- package/src/tools/pdfTool.js +627 -0
- package/src/tools/seekTool.js +883 -0
- package/src/tools/skillsTool.js +276 -0
- package/src/tools/staticAnalysisTool.js +2146 -0
- package/src/tools/taskManagerTool.js +2836 -0
- package/src/tools/terminalTool.js +2486 -0
- package/src/tools/userPromptTool.js +474 -0
- package/src/tools/videoTool.js +1139 -0
- package/src/tools/visionTool.js +507 -0
- package/src/tools/visualEditorTool.js +1175 -0
- package/src/tools/webTool.js +3114 -0
- package/src/tools/whatsappTool.js +457 -0
- package/src/types/agent.js +519 -0
- package/src/types/contextReference.js +972 -0
- package/src/types/conversation.js +730 -0
- package/src/types/toolCommand.js +747 -0
- package/src/utilities/attachmentValidator.js +288 -0
- package/src/utilities/browserStealth.js +630 -0
- package/src/utilities/configManager.js +618 -0
- package/src/utilities/constants.js +870 -0
- package/src/utilities/directoryAccessManager.js +566 -0
- package/src/utilities/fileProcessor.js +307 -0
- package/src/utilities/humanBehavior.js +453 -0
- package/src/utilities/jsonRepair.js +242 -0
- package/src/utilities/logger.js +436 -0
- package/src/utilities/platformUtils.js +255 -0
- package/src/utilities/platformUtils.test.js +98 -0
- package/src/utilities/stealthConstants.js +377 -0
- package/src/utilities/structuredFileValidator.js +699 -0
- package/src/utilities/tagParser.js +878 -0
- package/src/utilities/toolConstants.js +415 -0
- package/src/utilities/userDataDir.js +300 -0
- package/web-ui/build/brands/autopilot/favicon.svg +1 -0
- package/web-ui/build/brands/autopilot/logo.webp +0 -0
- package/web-ui/build/brands/onbuzz/favicon.svg +1 -0
- package/web-ui/build/brands/onbuzz/logo-text.webp +0 -0
- package/web-ui/build/brands/onbuzz/logo.webp +0 -0
- package/web-ui/build/index.html +15 -0
- package/web-ui/build/logo.png +0 -0
- package/web-ui/build/logo2.png +0 -0
- package/web-ui/build/static/index-SmQFfvBs.js +746 -0
- package/web-ui/build/static/index-V2ySwjHp.css +1 -0
|
@@ -0,0 +1,2146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StaticAnalysisTool - Static code analysis for finding errors without execution
|
|
3
|
+
*
|
|
4
|
+
* Purpose:
|
|
5
|
+
* - Analyze code files for syntax, type, and import errors
|
|
6
|
+
* - Detect programming languages and frameworks
|
|
7
|
+
* - Provide actionable error references with line numbers
|
|
8
|
+
* - Support single file, multiple files, and project-wide analysis
|
|
9
|
+
* - Use official language parsers for accurate results
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { BaseTool } from './baseTool.js';
|
|
13
|
+
import TagParser from '../utilities/tagParser.js';
|
|
14
|
+
import DirectoryAccessManager from '../utilities/directoryAccessManager.js';
|
|
15
|
+
import fs from 'fs/promises';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import crypto from 'crypto';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
STATIC_ANALYSIS,
|
|
21
|
+
TOOL_STATUS,
|
|
22
|
+
SYSTEM_DEFAULTS
|
|
23
|
+
} from '../utilities/constants.js';
|
|
24
|
+
import {
|
|
25
|
+
validateContent,
|
|
26
|
+
validateStructuredFile,
|
|
27
|
+
detectFormat,
|
|
28
|
+
getSupportedFormats
|
|
29
|
+
} from '../utilities/structuredFileValidator.js';
|
|
30
|
+
|
|
31
|
+
class StaticAnalysisTool extends BaseTool {
|
|
32
|
+
constructor(config = {}, logger = null) {
|
|
33
|
+
super(config, logger);
|
|
34
|
+
|
|
35
|
+
// Tool metadata
|
|
36
|
+
this.requiresProject = true;
|
|
37
|
+
this.isAsync = false;
|
|
38
|
+
this.timeout = config.timeout || STATIC_ANALYSIS.ANALYSIS_TIMEOUT;
|
|
39
|
+
this.maxConcurrentOperations = config.maxConcurrentOperations || 1;
|
|
40
|
+
|
|
41
|
+
// Analysis settings
|
|
42
|
+
this.maxFileSize = config.maxFileSize || STATIC_ANALYSIS.MAX_FILE_SIZE_FOR_ANALYSIS;
|
|
43
|
+
this.maxFilesPerBatch = config.maxFilesPerBatch || STATIC_ANALYSIS.MAX_FILES_PER_BATCH;
|
|
44
|
+
this.enableCache = config.enableCache !== false && STATIC_ANALYSIS.ENABLE_CACHE;
|
|
45
|
+
|
|
46
|
+
// Cache for analysis results
|
|
47
|
+
this.analysisCache = new Map();
|
|
48
|
+
this.cacheExpiry = STATIC_ANALYSIS.CACHE_DURATION;
|
|
49
|
+
|
|
50
|
+
// Performance optimization settings
|
|
51
|
+
this.parallelAnalysis = config.parallelAnalysis !== false;
|
|
52
|
+
this.maxParallelFiles = config.maxParallelFiles || 10;
|
|
53
|
+
this.useContentHash = config.useContentHash !== false;
|
|
54
|
+
|
|
55
|
+
// Performance metrics
|
|
56
|
+
this.metrics = {
|
|
57
|
+
totalAnalyses: 0,
|
|
58
|
+
cacheHits: 0,
|
|
59
|
+
cacheMisses: 0,
|
|
60
|
+
totalAnalysisTime: 0,
|
|
61
|
+
filesAnalyzed: 0,
|
|
62
|
+
parallelBatches: 0
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Directory access manager
|
|
66
|
+
this.directoryAccessManager = new DirectoryAccessManager(config, logger);
|
|
67
|
+
|
|
68
|
+
// Analyzers will be initialized lazily when needed
|
|
69
|
+
this.analyzers = {
|
|
70
|
+
javascript: null,
|
|
71
|
+
typescript: null,
|
|
72
|
+
python: null,
|
|
73
|
+
css: null,
|
|
74
|
+
scss: null,
|
|
75
|
+
less: null,
|
|
76
|
+
eslint: null,
|
|
77
|
+
security: null,
|
|
78
|
+
config: null,
|
|
79
|
+
sparrow: null // Tree-sitter based SAST
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Formatters will be initialized lazily when needed
|
|
83
|
+
this.formatters = {
|
|
84
|
+
prettier: null
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get tool description for LLM consumption
|
|
90
|
+
* @returns {string} Tool description
|
|
91
|
+
*/
|
|
92
|
+
getDescription() {
|
|
93
|
+
return `
|
|
94
|
+
Static Code Analysis Tool: Analyze code files for errors without execution
|
|
95
|
+
|
|
96
|
+
This tool performs static analysis on code files to find syntax errors, type errors, import issues, and other problems without running the code. It uses official language parsers for accurate results.
|
|
97
|
+
|
|
98
|
+
SUPPORTED LANGUAGES:
|
|
99
|
+
- JavaScript (.js, .jsx, .mjs, .cjs)
|
|
100
|
+
- TypeScript (.ts, .tsx)
|
|
101
|
+
- Python (.py)
|
|
102
|
+
- CSS (.css)
|
|
103
|
+
- SCSS (.scss, .sass)
|
|
104
|
+
- LESS (.less)
|
|
105
|
+
|
|
106
|
+
USAGE:
|
|
107
|
+
\`\`\`json
|
|
108
|
+
{
|
|
109
|
+
"toolId": "staticanalysis",
|
|
110
|
+
"actions": [
|
|
111
|
+
{
|
|
112
|
+
"type": "analyze",
|
|
113
|
+
"filePath": "src/index.js"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "analyze-project",
|
|
117
|
+
"directory": "src",
|
|
118
|
+
"pattern": "**/*.{js,ts,py}"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
\`\`\`
|
|
123
|
+
|
|
124
|
+
ACTION TYPES:
|
|
125
|
+
- analyze: Analyze a single file
|
|
126
|
+
- analyze-project: Analyze all files in a directory
|
|
127
|
+
- fix: Auto-fix code issues
|
|
128
|
+
- format: Format code with Prettier
|
|
129
|
+
- security-scan: Scan for security vulnerabilities (uses external tools if available)
|
|
130
|
+
- security-scan-project: Scan entire project for security issues
|
|
131
|
+
- sparrow-scan: Tree-sitter based SAST scan (no external dependencies, 12 languages)
|
|
132
|
+
- sparrow-scan-project: Project-wide Sparrow SAST scan
|
|
133
|
+
- validate-config: Validate configuration files
|
|
134
|
+
- validate-config-directory: Validate all config files in directory
|
|
135
|
+
- validate-structured: Validate structured file formats (JSON, YAML, XML, TOML, INI, ENV)
|
|
136
|
+
|
|
137
|
+
STRUCTURED FILE FORMATS SUPPORTED:
|
|
138
|
+
JSON (.json, .jsonc, .json5), YAML (.yaml, .yml), XML (.xml), TOML (.toml), INI (.ini), ENV (.env)
|
|
139
|
+
|
|
140
|
+
SPARROW SAST LANGUAGES:
|
|
141
|
+
Python, JavaScript, TypeScript, Go, Java, Ruby, Rust, PHP, C#, Bash, HTML, CSS
|
|
142
|
+
|
|
143
|
+
PARAMETERS:
|
|
144
|
+
- filePath: Path to file to analyze (for single file actions)
|
|
145
|
+
- directory: Directory to analyze (for project-wide actions)
|
|
146
|
+
- pattern: Glob pattern for files to include (optional)
|
|
147
|
+
- includeWarnings: Include warnings in results (true/false, default: true)
|
|
148
|
+
- maxErrors: Maximum number of errors to return (default: all)
|
|
149
|
+
- writeFile: Write fixed/formatted content back to file (for fix/format actions)
|
|
150
|
+
- content: Inline content to validate (for validate-structured, requires format)
|
|
151
|
+
- format: Format override (json, yaml, xml, toml, ini, env) - auto-detected from filePath if not specified
|
|
152
|
+
|
|
153
|
+
EXAMPLES:
|
|
154
|
+
|
|
155
|
+
1. Analyze a single JavaScript file:
|
|
156
|
+
\`\`\`json
|
|
157
|
+
{
|
|
158
|
+
"toolId": "staticanalysis",
|
|
159
|
+
"actions": [{ "type": "analyze", "filePath": "src/app.js" }]
|
|
160
|
+
}
|
|
161
|
+
\`\`\`
|
|
162
|
+
|
|
163
|
+
2. Analyze all files in a directory:
|
|
164
|
+
\`\`\`json
|
|
165
|
+
{
|
|
166
|
+
"toolId": "staticanalysis",
|
|
167
|
+
"actions": [{ "type": "analyze-project", "directory": "src", "pattern": "**/*.js" }]
|
|
168
|
+
}
|
|
169
|
+
\`\`\`
|
|
170
|
+
|
|
171
|
+
3. Auto-fix code issues:
|
|
172
|
+
\`\`\`json
|
|
173
|
+
{
|
|
174
|
+
"toolId": "staticanalysis",
|
|
175
|
+
"actions": [{ "type": "fix", "filePath": "src/app.js", "writeFile": true }]
|
|
176
|
+
}
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
4. Security scan a project:
|
|
180
|
+
\`\`\`json
|
|
181
|
+
{
|
|
182
|
+
"toolId": "staticanalysis",
|
|
183
|
+
"actions": [{ "type": "security-scan-project", "directory": "src" }]
|
|
184
|
+
}
|
|
185
|
+
\`\`\`
|
|
186
|
+
|
|
187
|
+
5. Validate a JSON/YAML/XML file structure:
|
|
188
|
+
\`\`\`json
|
|
189
|
+
{
|
|
190
|
+
"toolId": "staticanalysis",
|
|
191
|
+
"actions": [{ "type": "validate-structured", "filePath": "config.json" }]
|
|
192
|
+
}
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
6. Validate inline content (specify format):
|
|
196
|
+
\`\`\`json
|
|
197
|
+
{
|
|
198
|
+
"toolId": "staticanalysis",
|
|
199
|
+
"actions": [{ "type": "validate-structured", "content": "{\"key\": \"value\"}", "format": "json" }]
|
|
200
|
+
}
|
|
201
|
+
\`\`\`
|
|
202
|
+
|
|
203
|
+
OUTPUT FORMAT:
|
|
204
|
+
Returns structured error information with: file, line, column, severity, rule, message, category, fixable, suggestion
|
|
205
|
+
|
|
206
|
+
LIMITATIONS:
|
|
207
|
+
- File size limit: ${Math.round(this.maxFileSize / 1024 / 1024)}MB per file
|
|
208
|
+
- Batch limit: ${this.maxFilesPerBatch} files per operation
|
|
209
|
+
- Analysis timeout: ${this.timeout / 1000} seconds
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parse parameters from tool command content
|
|
215
|
+
* @param {string} content - Raw tool command content
|
|
216
|
+
* @returns {Object} Parsed parameters
|
|
217
|
+
*/
|
|
218
|
+
parseParameters(content) {
|
|
219
|
+
try {
|
|
220
|
+
const params = {};
|
|
221
|
+
const actions = [];
|
|
222
|
+
|
|
223
|
+
this.logger?.debug('StaticAnalysis tool parsing parameters', {
|
|
224
|
+
contentLength: content.length,
|
|
225
|
+
contentPreview: content.substring(0, 200)
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Extract self-closing <analyze> tags
|
|
229
|
+
// Pattern: <analyze ...attributes... />
|
|
230
|
+
// We need to capture everything between 'analyze' and '/>' which includes file paths with /
|
|
231
|
+
const analyzePattern = /<analyze\s+(.+?)\/>/g;
|
|
232
|
+
let match;
|
|
233
|
+
|
|
234
|
+
while ((match = analyzePattern.exec(content)) !== null) {
|
|
235
|
+
const attributeString = match[1].trim();
|
|
236
|
+
const parser = new TagParser();
|
|
237
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
238
|
+
|
|
239
|
+
const action = {
|
|
240
|
+
type: 'analyze',
|
|
241
|
+
...attributes
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Normalize attribute names
|
|
245
|
+
if (action['file-path']) {
|
|
246
|
+
action.filePath = action['file-path'];
|
|
247
|
+
delete action['file-path'];
|
|
248
|
+
}
|
|
249
|
+
if (action['include-warnings']) {
|
|
250
|
+
action.includeWarnings = action['include-warnings'] === 'true';
|
|
251
|
+
delete action['include-warnings'];
|
|
252
|
+
}
|
|
253
|
+
if (action['max-errors']) {
|
|
254
|
+
action.maxErrors = parseInt(action['max-errors'], 10);
|
|
255
|
+
delete action['max-errors'];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
actions.push(action);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Extract self-closing <analyze-project> tags
|
|
262
|
+
const projectPattern = /<analyze-project\s+(.+?)\/>/g;
|
|
263
|
+
|
|
264
|
+
while ((match = projectPattern.exec(content)) !== null) {
|
|
265
|
+
const attributeString = match[1].trim();
|
|
266
|
+
const parser = new TagParser();
|
|
267
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
268
|
+
|
|
269
|
+
const action = {
|
|
270
|
+
type: 'analyze-project',
|
|
271
|
+
...attributes
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Normalize attribute names
|
|
275
|
+
if (action['include-warnings']) {
|
|
276
|
+
action.includeWarnings = action['include-warnings'] === 'true';
|
|
277
|
+
delete action['include-warnings'];
|
|
278
|
+
}
|
|
279
|
+
if (action['max-errors']) {
|
|
280
|
+
action.maxErrors = parseInt(action['max-errors'], 10);
|
|
281
|
+
delete action['max-errors'];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
actions.push(action);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Extract self-closing <fix> tags
|
|
288
|
+
const fixPattern = /<fix\s+(.+?)\/>/g;
|
|
289
|
+
|
|
290
|
+
while ((match = fixPattern.exec(content)) !== null) {
|
|
291
|
+
const attributeString = match[1].trim();
|
|
292
|
+
const parser = new TagParser();
|
|
293
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
294
|
+
|
|
295
|
+
const action = {
|
|
296
|
+
type: 'fix',
|
|
297
|
+
...attributes
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// Normalize attribute names
|
|
301
|
+
if (action['file-path']) {
|
|
302
|
+
action.filePath = action['file-path'];
|
|
303
|
+
delete action['file-path'];
|
|
304
|
+
}
|
|
305
|
+
if (action['write-file']) {
|
|
306
|
+
action.writeFile = action['write-file'] === 'true';
|
|
307
|
+
delete action['write-file'];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
actions.push(action);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Extract self-closing <format> tags
|
|
314
|
+
const formatPattern = /<format\s+(.+?)\/>/g;
|
|
315
|
+
|
|
316
|
+
while ((match = formatPattern.exec(content)) !== null) {
|
|
317
|
+
const attributeString = match[1].trim();
|
|
318
|
+
const parser = new TagParser();
|
|
319
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
320
|
+
|
|
321
|
+
const action = {
|
|
322
|
+
type: 'format',
|
|
323
|
+
...attributes
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Normalize attribute names
|
|
327
|
+
if (action['file-path']) {
|
|
328
|
+
action.filePath = action['file-path'];
|
|
329
|
+
delete action['file-path'];
|
|
330
|
+
}
|
|
331
|
+
if (action['write-file']) {
|
|
332
|
+
action.writeFile = action['write-file'] === 'true';
|
|
333
|
+
delete action['write-file'];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
actions.push(action);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Extract self-closing <security-scan> tags
|
|
340
|
+
const securityScanPattern = /<security-scan\s+(.+?)\/>/g;
|
|
341
|
+
|
|
342
|
+
while ((match = securityScanPattern.exec(content)) !== null) {
|
|
343
|
+
const attributeString = match[1].trim();
|
|
344
|
+
const parser = new TagParser();
|
|
345
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
346
|
+
|
|
347
|
+
const action = {
|
|
348
|
+
type: 'security-scan',
|
|
349
|
+
...attributes
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// Normalize attribute names
|
|
353
|
+
if (action['file-path']) {
|
|
354
|
+
action.filePath = action['file-path'];
|
|
355
|
+
delete action['file-path'];
|
|
356
|
+
}
|
|
357
|
+
if (action['skip-test-files']) {
|
|
358
|
+
action.skipTestFiles = action['skip-test-files'] === 'true';
|
|
359
|
+
delete action['skip-test-files'];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
actions.push(action);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Extract self-closing <security-scan-project> tags
|
|
366
|
+
const securityScanProjectPattern = /<security-scan-project\s+(.+?)\/>/g;
|
|
367
|
+
|
|
368
|
+
while ((match = securityScanProjectPattern.exec(content)) !== null) {
|
|
369
|
+
const attributeString = match[1].trim();
|
|
370
|
+
const parser = new TagParser();
|
|
371
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
372
|
+
|
|
373
|
+
const action = {
|
|
374
|
+
type: 'security-scan-project',
|
|
375
|
+
...attributes
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Normalize attribute names
|
|
379
|
+
if (action['skip-test-files']) {
|
|
380
|
+
action.skipTestFiles = action['skip-test-files'] === 'true';
|
|
381
|
+
delete action['skip-test-files'];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
actions.push(action);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Extract self-closing <validate-config> tags
|
|
388
|
+
const validateConfigPattern = /<validate-config\s+(.+?)\/>/g;
|
|
389
|
+
|
|
390
|
+
while ((match = validateConfigPattern.exec(content)) !== null) {
|
|
391
|
+
const attributeString = match[1].trim();
|
|
392
|
+
const parser = new TagParser();
|
|
393
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
394
|
+
|
|
395
|
+
const action = {
|
|
396
|
+
type: 'validate-config',
|
|
397
|
+
...attributes
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// Normalize attribute names
|
|
401
|
+
if (action['file-path']) {
|
|
402
|
+
action.filePath = action['file-path'];
|
|
403
|
+
delete action['file-path'];
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
actions.push(action);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Extract self-closing <validate-config-directory> tags
|
|
410
|
+
const validateConfigDirPattern = /<validate-config-directory\s+(.+?)\/>/g;
|
|
411
|
+
|
|
412
|
+
while ((match = validateConfigDirPattern.exec(content)) !== null) {
|
|
413
|
+
const attributeString = match[1].trim();
|
|
414
|
+
const parser = new TagParser();
|
|
415
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
416
|
+
|
|
417
|
+
const action = {
|
|
418
|
+
type: 'validate-config-directory',
|
|
419
|
+
...attributes
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// Normalize attribute names (none specific yet)
|
|
423
|
+
|
|
424
|
+
actions.push(action);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Extract self-closing <validate-structured> tags
|
|
428
|
+
const validateStructuredPattern = /<validate-structured\s+(.+?)\/>/g;
|
|
429
|
+
|
|
430
|
+
while ((match = validateStructuredPattern.exec(content)) !== null) {
|
|
431
|
+
const attributeString = match[1].trim();
|
|
432
|
+
const parser = new TagParser();
|
|
433
|
+
const attributes = parser.parseAttributes(attributeString);
|
|
434
|
+
|
|
435
|
+
const action = {
|
|
436
|
+
type: 'validate-structured',
|
|
437
|
+
...attributes
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// Normalize attribute names
|
|
441
|
+
if (action['file-path']) {
|
|
442
|
+
action.filePath = action['file-path'];
|
|
443
|
+
delete action['file-path'];
|
|
444
|
+
}
|
|
445
|
+
if (action['format']) {
|
|
446
|
+
action.format = action['format'];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
actions.push(action);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
params.actions = actions;
|
|
453
|
+
params.rawContent = content.trim();
|
|
454
|
+
|
|
455
|
+
this.logger?.debug('Parsed StaticAnalysis tool parameters', {
|
|
456
|
+
totalActions: actions.length,
|
|
457
|
+
actionTypes: actions.map(a => a.type)
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return params;
|
|
461
|
+
|
|
462
|
+
} catch (error) {
|
|
463
|
+
throw new Error(`Failed to parse static analysis parameters: ${error.message}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Get required parameters
|
|
469
|
+
* @returns {Array<string>} Array of required parameter names
|
|
470
|
+
*/
|
|
471
|
+
getRequiredParameters() {
|
|
472
|
+
return ['actions'];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Custom parameter validation
|
|
477
|
+
* @param {Object} params - Parameters to validate
|
|
478
|
+
* @returns {Object} Validation result
|
|
479
|
+
*/
|
|
480
|
+
customValidateParameters(params) {
|
|
481
|
+
const errors = [];
|
|
482
|
+
|
|
483
|
+
if (!params.actions || !Array.isArray(params.actions) || params.actions.length === 0) {
|
|
484
|
+
errors.push('At least one action is required');
|
|
485
|
+
} else {
|
|
486
|
+
// Validate each action
|
|
487
|
+
for (const [index, action] of params.actions.entries()) {
|
|
488
|
+
if (!action.type) {
|
|
489
|
+
errors.push(`Action ${index + 1}: type is required`);
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
switch (action.type) {
|
|
494
|
+
case 'analyze':
|
|
495
|
+
if (!action.filePath) {
|
|
496
|
+
errors.push(`Action ${index + 1}: file-path is required for analyze`);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
|
|
500
|
+
case 'analyze-project':
|
|
501
|
+
if (!action.directory) {
|
|
502
|
+
errors.push(`Action ${index + 1}: directory is required for analyze-project`);
|
|
503
|
+
}
|
|
504
|
+
break;
|
|
505
|
+
|
|
506
|
+
case 'fix':
|
|
507
|
+
if (!action.filePath) {
|
|
508
|
+
errors.push(`Action ${index + 1}: file-path is required for fix`);
|
|
509
|
+
}
|
|
510
|
+
break;
|
|
511
|
+
|
|
512
|
+
case 'format':
|
|
513
|
+
if (!action.filePath) {
|
|
514
|
+
errors.push(`Action ${index + 1}: file-path is required for format`);
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
|
|
518
|
+
case 'security-scan':
|
|
519
|
+
if (!action.filePath) {
|
|
520
|
+
errors.push(`Action ${index + 1}: file-path is required for security-scan`);
|
|
521
|
+
}
|
|
522
|
+
break;
|
|
523
|
+
|
|
524
|
+
case 'security-scan-project':
|
|
525
|
+
if (!action.directory) {
|
|
526
|
+
errors.push(`Action ${index + 1}: directory is required for security-scan-project`);
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
|
|
530
|
+
case 'validate-config':
|
|
531
|
+
if (!action.filePath) {
|
|
532
|
+
errors.push(`Action ${index + 1}: file-path is required for validate-config`);
|
|
533
|
+
}
|
|
534
|
+
break;
|
|
535
|
+
|
|
536
|
+
case 'validate-config-directory':
|
|
537
|
+
if (!action.directory) {
|
|
538
|
+
errors.push(`Action ${index + 1}: directory is required for validate-config-directory`);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
|
|
542
|
+
case 'sparrow-scan':
|
|
543
|
+
if (!action.filePath) {
|
|
544
|
+
errors.push(`Action ${index + 1}: file-path is required for sparrow-scan`);
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
|
|
548
|
+
case 'sparrow-scan-project':
|
|
549
|
+
if (!action.directory) {
|
|
550
|
+
errors.push(`Action ${index + 1}: directory is required for sparrow-scan-project`);
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
|
|
554
|
+
case 'validate-structured':
|
|
555
|
+
if (!action.filePath && !action.content) {
|
|
556
|
+
errors.push(`Action ${index + 1}: file-path or content is required for validate-structured`);
|
|
557
|
+
}
|
|
558
|
+
break;
|
|
559
|
+
|
|
560
|
+
default:
|
|
561
|
+
errors.push(`Action ${index + 1}: unknown action type: ${action.type}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Check batch size limit
|
|
566
|
+
if (params.actions.length > this.maxFilesPerBatch) {
|
|
567
|
+
errors.push(`Too many actions: ${params.actions.length} (max ${this.maxFilesPerBatch})`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
valid: errors.length === 0,
|
|
573
|
+
errors
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Execute tool with parsed parameters
|
|
579
|
+
* @param {Object} params - Parsed parameters
|
|
580
|
+
* @param {Object} context - Execution context
|
|
581
|
+
* @returns {Promise<Object>} Execution result
|
|
582
|
+
*/
|
|
583
|
+
async execute(params, context) {
|
|
584
|
+
const { actions } = params;
|
|
585
|
+
const { projectDir, agentId, directoryAccess } = context;
|
|
586
|
+
|
|
587
|
+
// Get directory access configuration
|
|
588
|
+
const accessConfig = directoryAccess ||
|
|
589
|
+
this.directoryAccessManager.createDirectoryAccess({
|
|
590
|
+
workingDirectory: projectDir || process.cwd(),
|
|
591
|
+
writeEnabledDirectories: [projectDir || process.cwd()],
|
|
592
|
+
restrictToProject: true
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const workingDir = this.directoryAccessManager.getWorkingDirectory(accessConfig);
|
|
596
|
+
const results = {
|
|
597
|
+
files: [],
|
|
598
|
+
summary: {
|
|
599
|
+
totalFiles: 0,
|
|
600
|
+
totalErrors: 0,
|
|
601
|
+
totalWarnings: 0,
|
|
602
|
+
totalInfo: 0,
|
|
603
|
+
errorsByCategory: {},
|
|
604
|
+
filesByLanguage: {},
|
|
605
|
+
filesWithErrors: 0
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
for (const action of actions) {
|
|
610
|
+
try {
|
|
611
|
+
let actionResult;
|
|
612
|
+
|
|
613
|
+
switch (action.type) {
|
|
614
|
+
case 'analyze':
|
|
615
|
+
actionResult = await this.analyzeFile(action.filePath, workingDir, accessConfig, action);
|
|
616
|
+
if (actionResult) {
|
|
617
|
+
results.files.push(actionResult);
|
|
618
|
+
this.updateSummary(results.summary, actionResult);
|
|
619
|
+
}
|
|
620
|
+
break;
|
|
621
|
+
|
|
622
|
+
case 'analyze-project':
|
|
623
|
+
const projectFiles = await this.analyzeProject(action.directory, action.pattern, workingDir, accessConfig, action);
|
|
624
|
+
results.files.push(...projectFiles);
|
|
625
|
+
for (const fileResult of projectFiles) {
|
|
626
|
+
this.updateSummary(results.summary, fileResult);
|
|
627
|
+
}
|
|
628
|
+
break;
|
|
629
|
+
|
|
630
|
+
case 'fix':
|
|
631
|
+
actionResult = await this.fixFile(action.filePath, workingDir, accessConfig, action);
|
|
632
|
+
if (actionResult) {
|
|
633
|
+
results.files.push(actionResult);
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
|
|
637
|
+
case 'format':
|
|
638
|
+
actionResult = await this.formatFile(action.filePath, workingDir, accessConfig, action);
|
|
639
|
+
if (actionResult) {
|
|
640
|
+
results.files.push(actionResult);
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
|
|
644
|
+
case 'security-scan':
|
|
645
|
+
actionResult = await this.securityScanFile(action.filePath, workingDir, accessConfig, action);
|
|
646
|
+
if (actionResult) {
|
|
647
|
+
results.files.push(actionResult);
|
|
648
|
+
this.updateSummary(results.summary, actionResult);
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
|
|
652
|
+
case 'security-scan-project':
|
|
653
|
+
const securityProjectFiles = await this.securityScanProject(action.directory, action.pattern, workingDir, accessConfig, action);
|
|
654
|
+
results.files.push(...securityProjectFiles);
|
|
655
|
+
for (const fileResult of securityProjectFiles) {
|
|
656
|
+
this.updateSummary(results.summary, fileResult);
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
|
|
660
|
+
case 'validate-config':
|
|
661
|
+
actionResult = await this.validateConfigFile(action.filePath, workingDir, accessConfig, action);
|
|
662
|
+
if (actionResult) {
|
|
663
|
+
results.files.push(actionResult);
|
|
664
|
+
this.updateSummary(results.summary, actionResult);
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
|
|
668
|
+
case 'validate-config-directory':
|
|
669
|
+
const configFiles = await this.validateConfigDirectory(action.directory, workingDir, accessConfig, action);
|
|
670
|
+
results.files.push(...configFiles);
|
|
671
|
+
for (const fileResult of configFiles) {
|
|
672
|
+
this.updateSummary(results.summary, fileResult);
|
|
673
|
+
}
|
|
674
|
+
break;
|
|
675
|
+
|
|
676
|
+
case 'sparrow-scan':
|
|
677
|
+
actionResult = await this.sparrowScanFile(action.filePath, workingDir, accessConfig, action);
|
|
678
|
+
if (actionResult) {
|
|
679
|
+
results.files.push(actionResult);
|
|
680
|
+
this.updateSummary(results.summary, actionResult);
|
|
681
|
+
}
|
|
682
|
+
break;
|
|
683
|
+
|
|
684
|
+
case 'sparrow-scan-project':
|
|
685
|
+
const sparrowResult = await this.sparrowScanProject(action.directory, action.pattern, workingDir, accessConfig, action);
|
|
686
|
+
if (sparrowResult.files) {
|
|
687
|
+
results.files.push(...sparrowResult.files);
|
|
688
|
+
for (const fileResult of sparrowResult.files) {
|
|
689
|
+
this.updateSummary(results.summary, fileResult);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
break;
|
|
693
|
+
|
|
694
|
+
case 'validate-structured':
|
|
695
|
+
actionResult = await this.validateStructuredFile(action.filePath, action.content, action.format, workingDir, accessConfig, action);
|
|
696
|
+
if (actionResult) {
|
|
697
|
+
results.files.push(actionResult);
|
|
698
|
+
this.updateSummary(results.summary, actionResult);
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
|
|
702
|
+
default:
|
|
703
|
+
throw new Error(`Unknown action type: ${action.type}`);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
} catch (error) {
|
|
707
|
+
this.logger?.error('Static analysis action failed', {
|
|
708
|
+
action: action.type,
|
|
709
|
+
error: error.message
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
results.files.push({
|
|
713
|
+
file: action.filePath || action.directory,
|
|
714
|
+
error: error.message,
|
|
715
|
+
success: false
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return {
|
|
721
|
+
success: true,
|
|
722
|
+
results,
|
|
723
|
+
toolUsed: 'staticanalysis',
|
|
724
|
+
performance: this.getPerformanceMetrics()
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Analyze a single file
|
|
730
|
+
* @private
|
|
731
|
+
*/
|
|
732
|
+
async analyzeFile(filePath, workingDir, accessConfig, options = {}) {
|
|
733
|
+
const fullPath = path.isAbsolute(filePath)
|
|
734
|
+
? path.normalize(filePath)
|
|
735
|
+
: path.resolve(workingDir, filePath);
|
|
736
|
+
|
|
737
|
+
// Validate read access
|
|
738
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
739
|
+
if (!accessResult.allowed) {
|
|
740
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Check file exists
|
|
744
|
+
try {
|
|
745
|
+
const stats = await fs.stat(fullPath);
|
|
746
|
+
|
|
747
|
+
if (stats.size > this.maxFileSize) {
|
|
748
|
+
throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Detect language from file extension
|
|
752
|
+
const language = this.detectLanguage(fullPath);
|
|
753
|
+
|
|
754
|
+
if (!language) {
|
|
755
|
+
return {
|
|
756
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
757
|
+
fullPath,
|
|
758
|
+
language: 'unknown',
|
|
759
|
+
errors: [],
|
|
760
|
+
warnings: [],
|
|
761
|
+
info: [],
|
|
762
|
+
skipped: true,
|
|
763
|
+
skipReason: 'Unsupported file type'
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Read file content
|
|
768
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
769
|
+
|
|
770
|
+
// Check cache (use content hash for more accurate caching)
|
|
771
|
+
const contentHash = this.useContentHash ? this.computeContentHash(content) : null;
|
|
772
|
+
const cacheKey = this.useContentHash
|
|
773
|
+
? `${fullPath}:${contentHash}`
|
|
774
|
+
: `${fullPath}:${stats.mtime.getTime()}`;
|
|
775
|
+
|
|
776
|
+
if (this.enableCache && this.analysisCache.has(cacheKey)) {
|
|
777
|
+
const cached = this.analysisCache.get(cacheKey);
|
|
778
|
+
if (Date.now() - cached.timestamp < this.cacheExpiry) {
|
|
779
|
+
this.logger?.debug('Using cached analysis result', { file: fullPath });
|
|
780
|
+
this.metrics.cacheHits++;
|
|
781
|
+
this.metrics.totalAnalyses++;
|
|
782
|
+
return cached.result;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
this.metrics.cacheMisses++;
|
|
787
|
+
this.metrics.totalAnalyses++;
|
|
788
|
+
|
|
789
|
+
// Get analyzer for language
|
|
790
|
+
const analyzer = await this.getAnalyzer(language);
|
|
791
|
+
|
|
792
|
+
if (!analyzer) {
|
|
793
|
+
return {
|
|
794
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
795
|
+
fullPath,
|
|
796
|
+
language,
|
|
797
|
+
errors: [],
|
|
798
|
+
warnings: [],
|
|
799
|
+
info: [],
|
|
800
|
+
skipped: true,
|
|
801
|
+
skipReason: `No analyzer available for ${language}`
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// Perform analysis with timing
|
|
806
|
+
const analysisStart = Date.now();
|
|
807
|
+
const diagnostics = await analyzer.analyze(fullPath, content, {
|
|
808
|
+
workingDir,
|
|
809
|
+
accessConfig,
|
|
810
|
+
framework: await this.detectFramework(workingDir, language)
|
|
811
|
+
});
|
|
812
|
+
const analysisTime = Date.now() - analysisStart;
|
|
813
|
+
|
|
814
|
+
this.metrics.totalAnalysisTime += analysisTime;
|
|
815
|
+
this.metrics.filesAnalyzed++;
|
|
816
|
+
|
|
817
|
+
// Format results
|
|
818
|
+
const result = {
|
|
819
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
820
|
+
fullPath,
|
|
821
|
+
language,
|
|
822
|
+
framework: await this.detectFramework(workingDir, language),
|
|
823
|
+
errors: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
|
|
824
|
+
warnings: options.includeWarnings !== false
|
|
825
|
+
? diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING)
|
|
826
|
+
: [],
|
|
827
|
+
info: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
|
|
828
|
+
totalIssues: diagnostics.length,
|
|
829
|
+
analyzed: true,
|
|
830
|
+
timestamp: new Date().toISOString()
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
// Apply max errors limit
|
|
834
|
+
if (options.maxErrors && result.errors.length > options.maxErrors) {
|
|
835
|
+
result.errors = result.errors.slice(0, options.maxErrors);
|
|
836
|
+
result.truncated = true;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Cache result
|
|
840
|
+
if (this.enableCache) {
|
|
841
|
+
this.analysisCache.set(cacheKey, {
|
|
842
|
+
result,
|
|
843
|
+
timestamp: Date.now()
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return result;
|
|
848
|
+
|
|
849
|
+
} catch (error) {
|
|
850
|
+
throw new Error(`Failed to analyze ${filePath}: ${error.message}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Analyze project directory
|
|
856
|
+
* @private
|
|
857
|
+
*/
|
|
858
|
+
async analyzeProject(directory, pattern, workingDir, accessConfig, options = {}) {
|
|
859
|
+
const fullDir = path.isAbsolute(directory)
|
|
860
|
+
? path.normalize(directory)
|
|
861
|
+
: path.resolve(workingDir, directory);
|
|
862
|
+
|
|
863
|
+
// Validate read access
|
|
864
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
|
|
865
|
+
if (!accessResult.allowed) {
|
|
866
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Find all matching files
|
|
870
|
+
const files = await this.findFiles(fullDir, pattern);
|
|
871
|
+
|
|
872
|
+
if (files.length > this.maxFilesPerBatch) {
|
|
873
|
+
throw new Error(`Too many files: ${files.length} (max ${this.maxFilesPerBatch})`);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Analyze files (parallel or sequential based on configuration)
|
|
877
|
+
const results = [];
|
|
878
|
+
|
|
879
|
+
if (this.parallelAnalysis && files.length > 1) {
|
|
880
|
+
// Parallel analysis in batches
|
|
881
|
+
this.logger?.debug('Using parallel analysis', {
|
|
882
|
+
totalFiles: files.length,
|
|
883
|
+
batchSize: this.maxParallelFiles
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
for (let i = 0; i < files.length; i += this.maxParallelFiles) {
|
|
887
|
+
const batch = files.slice(i, i + this.maxParallelFiles);
|
|
888
|
+
this.metrics.parallelBatches++;
|
|
889
|
+
|
|
890
|
+
// Report progress
|
|
891
|
+
const progress = {
|
|
892
|
+
completed: i,
|
|
893
|
+
total: files.length,
|
|
894
|
+
percentage: Math.round((i / files.length) * 100)
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
if (options.onProgress) {
|
|
898
|
+
options.onProgress(progress);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
this.logger?.debug('Analyzing batch', {
|
|
902
|
+
batch: Math.floor(i / this.maxParallelFiles) + 1,
|
|
903
|
+
filesInBatch: batch.length,
|
|
904
|
+
progress: `${progress.completed}/${progress.total}`
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Analyze batch in parallel
|
|
908
|
+
const batchPromises = batch.map(async (file) => {
|
|
909
|
+
try {
|
|
910
|
+
const result = await this.analyzeFile(file, workingDir, accessConfig, options);
|
|
911
|
+
return result;
|
|
912
|
+
} catch (error) {
|
|
913
|
+
this.logger?.warn('Failed to analyze file in project', {
|
|
914
|
+
file,
|
|
915
|
+
error: error.message
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
return {
|
|
919
|
+
file: this.directoryAccessManager.createRelativePath(file, accessConfig),
|
|
920
|
+
fullPath: file,
|
|
921
|
+
error: error.message,
|
|
922
|
+
success: false
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
const batchResults = await Promise.all(batchPromises);
|
|
928
|
+
results.push(...batchResults.filter(r => r !== null));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Final progress report
|
|
932
|
+
if (options.onProgress) {
|
|
933
|
+
options.onProgress({
|
|
934
|
+
completed: files.length,
|
|
935
|
+
total: files.length,
|
|
936
|
+
percentage: 100
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
} else {
|
|
941
|
+
// Sequential analysis
|
|
942
|
+
for (const file of files) {
|
|
943
|
+
try {
|
|
944
|
+
const result = await this.analyzeFile(file, workingDir, accessConfig, options);
|
|
945
|
+
if (result) {
|
|
946
|
+
results.push(result);
|
|
947
|
+
}
|
|
948
|
+
} catch (error) {
|
|
949
|
+
this.logger?.warn('Failed to analyze file in project', {
|
|
950
|
+
file,
|
|
951
|
+
error: error.message
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
results.push({
|
|
955
|
+
file: this.directoryAccessManager.createRelativePath(file, accessConfig),
|
|
956
|
+
fullPath: file,
|
|
957
|
+
error: error.message,
|
|
958
|
+
success: false
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return results;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Fix code issues in a file
|
|
969
|
+
* @private
|
|
970
|
+
*/
|
|
971
|
+
async fixFile(filePath, workingDir, accessConfig, options = {}) {
|
|
972
|
+
const fullPath = path.isAbsolute(filePath)
|
|
973
|
+
? path.normalize(filePath)
|
|
974
|
+
: path.resolve(workingDir, filePath);
|
|
975
|
+
|
|
976
|
+
// Validate read access
|
|
977
|
+
const readResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
978
|
+
if (!readResult.allowed) {
|
|
979
|
+
throw new Error(`Read access denied: ${readResult.reason}`);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Validate write access if writeFile is true
|
|
983
|
+
if (options.writeFile) {
|
|
984
|
+
const writeResult = this.directoryAccessManager.validateWriteAccess(fullPath, accessConfig);
|
|
985
|
+
if (!writeResult.allowed) {
|
|
986
|
+
throw new Error(`Write access denied: ${writeResult.reason}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
try {
|
|
991
|
+
// Read file
|
|
992
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
993
|
+
|
|
994
|
+
// Get ESLint analyzer
|
|
995
|
+
const eslintAnalyzer = await this.getESLintAnalyzer();
|
|
996
|
+
|
|
997
|
+
// Fix the code
|
|
998
|
+
const fixResult = await eslintAnalyzer.fix(fullPath, content, {
|
|
999
|
+
workingDir,
|
|
1000
|
+
accessConfig,
|
|
1001
|
+
framework: await this.detectFramework(workingDir, this.detectLanguage(fullPath))
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
// Write file if requested and changes were made
|
|
1005
|
+
if (options.writeFile && fixResult.fixed) {
|
|
1006
|
+
await fs.writeFile(fullPath, fixResult.content, 'utf-8');
|
|
1007
|
+
this.logger?.info('File fixed and written', { file: fullPath });
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
return {
|
|
1011
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1012
|
+
fullPath,
|
|
1013
|
+
action: 'fix',
|
|
1014
|
+
fixed: fixResult.fixed,
|
|
1015
|
+
fixedCount: fixResult.fixedCount,
|
|
1016
|
+
remainingErrors: fixResult.remainingErrors,
|
|
1017
|
+
remainingWarnings: fixResult.remainingWarnings,
|
|
1018
|
+
changes: fixResult.changes,
|
|
1019
|
+
written: !!(options.writeFile && fixResult.fixed),
|
|
1020
|
+
preview: !options.writeFile && fixResult.fixed ? fixResult.content : undefined
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
throw new Error(`Failed to fix ${filePath}: ${error.message}`);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Format code in a file
|
|
1030
|
+
* @private
|
|
1031
|
+
*/
|
|
1032
|
+
async formatFile(filePath, workingDir, accessConfig, options = {}) {
|
|
1033
|
+
const fullPath = path.isAbsolute(filePath)
|
|
1034
|
+
? path.normalize(filePath)
|
|
1035
|
+
: path.resolve(workingDir, filePath);
|
|
1036
|
+
|
|
1037
|
+
// Validate read access
|
|
1038
|
+
const readResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
1039
|
+
if (!readResult.allowed) {
|
|
1040
|
+
throw new Error(`Read access denied: ${readResult.reason}`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Validate write access if writeFile is true
|
|
1044
|
+
if (options.writeFile) {
|
|
1045
|
+
const writeResult = this.directoryAccessManager.validateWriteAccess(fullPath, accessConfig);
|
|
1046
|
+
if (!writeResult.allowed) {
|
|
1047
|
+
throw new Error(`Write access denied: ${writeResult.reason}`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
try {
|
|
1052
|
+
// Read file
|
|
1053
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
1054
|
+
|
|
1055
|
+
// Get Prettier formatter
|
|
1056
|
+
const prettierFormatter = await this.getPrettierFormatter();
|
|
1057
|
+
|
|
1058
|
+
// Check if file type is supported
|
|
1059
|
+
if (!prettierFormatter.isSupported(fullPath)) {
|
|
1060
|
+
return {
|
|
1061
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1062
|
+
fullPath,
|
|
1063
|
+
action: 'format',
|
|
1064
|
+
formatted: false,
|
|
1065
|
+
skipped: true,
|
|
1066
|
+
skipReason: 'File type not supported by Prettier'
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Format the code
|
|
1071
|
+
const formatResult = await prettierFormatter.format(fullPath, content, {
|
|
1072
|
+
workingDir,
|
|
1073
|
+
accessConfig
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
// Write file if requested and changes were made
|
|
1077
|
+
if (options.writeFile && formatResult.formatted) {
|
|
1078
|
+
await fs.writeFile(fullPath, formatResult.content, 'utf-8');
|
|
1079
|
+
this.logger?.info('File formatted and written', { file: fullPath });
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
return {
|
|
1083
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1084
|
+
fullPath,
|
|
1085
|
+
action: 'format',
|
|
1086
|
+
formatted: formatResult.formatted,
|
|
1087
|
+
linesChanged: formatResult.linesChanged,
|
|
1088
|
+
changes: formatResult.changes,
|
|
1089
|
+
written: !!(options.writeFile && formatResult.formatted),
|
|
1090
|
+
preview: !options.writeFile && formatResult.formatted ? formatResult.content : undefined
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
throw new Error(`Failed to format ${filePath}: ${error.message}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Security scan a single file
|
|
1100
|
+
* @private
|
|
1101
|
+
*/
|
|
1102
|
+
async securityScanFile(filePath, workingDir, accessConfig, options = {}) {
|
|
1103
|
+
const fullPath = path.isAbsolute(filePath)
|
|
1104
|
+
? path.normalize(filePath)
|
|
1105
|
+
: path.resolve(workingDir, filePath);
|
|
1106
|
+
|
|
1107
|
+
// Validate read access
|
|
1108
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
1109
|
+
if (!accessResult.allowed) {
|
|
1110
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
try {
|
|
1114
|
+
const stats = await fs.stat(fullPath);
|
|
1115
|
+
|
|
1116
|
+
if (stats.size > this.maxFileSize) {
|
|
1117
|
+
throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Detect language
|
|
1121
|
+
const language = this.detectLanguage(fullPath);
|
|
1122
|
+
|
|
1123
|
+
// Security analyzer only supports JS/TS/Python
|
|
1124
|
+
if (!language || !['javascript', 'typescript', 'python'].includes(language)) {
|
|
1125
|
+
return {
|
|
1126
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1127
|
+
fullPath,
|
|
1128
|
+
language: language || 'unknown',
|
|
1129
|
+
issues: [],
|
|
1130
|
+
skipped: true,
|
|
1131
|
+
skipReason: 'Security scanning only supports JavaScript, TypeScript, and Python files'
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Read file content
|
|
1136
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
1137
|
+
|
|
1138
|
+
// Get security analyzer
|
|
1139
|
+
const securityAnalyzer = await this.getSecurityAnalyzer();
|
|
1140
|
+
|
|
1141
|
+
// Perform security scan
|
|
1142
|
+
const issues = await securityAnalyzer.analyze(fullPath, content, {
|
|
1143
|
+
skipTestFiles: options.skipTestFiles !== false
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
// Categorize issues by severity
|
|
1147
|
+
const result = {
|
|
1148
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1149
|
+
fullPath,
|
|
1150
|
+
language,
|
|
1151
|
+
action: 'security-scan',
|
|
1152
|
+
critical: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.CRITICAL),
|
|
1153
|
+
errors: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
|
|
1154
|
+
warnings: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING),
|
|
1155
|
+
info: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
|
|
1156
|
+
totalIssues: issues.length,
|
|
1157
|
+
analyzed: true,
|
|
1158
|
+
scannersUsed: issues.map(i => i.scanner).filter((v, i, a) => a.indexOf(v) === i),
|
|
1159
|
+
timestamp: new Date().toISOString()
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
return result;
|
|
1163
|
+
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
throw new Error(`Failed to security scan ${filePath}: ${error.message}`);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Security scan project directory
|
|
1171
|
+
* @private
|
|
1172
|
+
*/
|
|
1173
|
+
async securityScanProject(directory, pattern, workingDir, accessConfig, options = {}) {
|
|
1174
|
+
const fullDir = path.isAbsolute(directory)
|
|
1175
|
+
? path.normalize(directory)
|
|
1176
|
+
: path.resolve(workingDir, directory);
|
|
1177
|
+
|
|
1178
|
+
// Validate read access
|
|
1179
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
|
|
1180
|
+
if (!accessResult.allowed) {
|
|
1181
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Get security analyzer for dependency scanning
|
|
1185
|
+
const securityAnalyzer = await this.getSecurityAnalyzer();
|
|
1186
|
+
|
|
1187
|
+
// Run dependency scans at project level
|
|
1188
|
+
const dependencyIssues = await securityAnalyzer.analyzeProject(fullDir, 'javascript', options);
|
|
1189
|
+
|
|
1190
|
+
// Find all matching files (only JS/TS/Python for security scanning)
|
|
1191
|
+
const searchPattern = pattern || '**/*.{js,jsx,mjs,cjs,ts,tsx,py}';
|
|
1192
|
+
const files = await this.findFiles(fullDir, searchPattern);
|
|
1193
|
+
|
|
1194
|
+
if (files.length > this.maxFilesPerBatch) {
|
|
1195
|
+
throw new Error(`Too many files: ${files.length} (max ${this.maxFilesPerBatch})`);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Scan files (parallel or sequential)
|
|
1199
|
+
const results = [];
|
|
1200
|
+
|
|
1201
|
+
if (this.parallelAnalysis && files.length > 1) {
|
|
1202
|
+
// Parallel scanning in batches
|
|
1203
|
+
this.logger?.debug('Using parallel security scanning', {
|
|
1204
|
+
totalFiles: files.length,
|
|
1205
|
+
batchSize: this.maxParallelFiles
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
for (let i = 0; i < files.length; i += this.maxParallelFiles) {
|
|
1209
|
+
const batch = files.slice(i, i + this.maxParallelFiles);
|
|
1210
|
+
|
|
1211
|
+
if (options.onProgress) {
|
|
1212
|
+
options.onProgress({
|
|
1213
|
+
completed: i,
|
|
1214
|
+
total: files.length,
|
|
1215
|
+
percentage: Math.round((i / files.length) * 100)
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
const batchPromises = batch.map(async (file) => {
|
|
1220
|
+
try {
|
|
1221
|
+
return await this.securityScanFile(file, workingDir, accessConfig, options);
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
this.logger?.warn('Failed to security scan file in project', {
|
|
1224
|
+
file,
|
|
1225
|
+
error: error.message
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
return {
|
|
1229
|
+
file: this.directoryAccessManager.createRelativePath(file, accessConfig),
|
|
1230
|
+
fullPath: file,
|
|
1231
|
+
error: error.message,
|
|
1232
|
+
success: false
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1238
|
+
results.push(...batchResults.filter(r => r !== null));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (options.onProgress) {
|
|
1242
|
+
options.onProgress({
|
|
1243
|
+
completed: files.length,
|
|
1244
|
+
total: files.length,
|
|
1245
|
+
percentage: 100
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
} else {
|
|
1250
|
+
// Sequential scanning
|
|
1251
|
+
for (const file of files) {
|
|
1252
|
+
try {
|
|
1253
|
+
const result = await this.securityScanFile(file, workingDir, accessConfig, options);
|
|
1254
|
+
if (result) {
|
|
1255
|
+
results.push(result);
|
|
1256
|
+
}
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
this.logger?.warn('Failed to security scan file in project', {
|
|
1259
|
+
file,
|
|
1260
|
+
error: error.message
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
results.push({
|
|
1264
|
+
file: this.directoryAccessManager.createRelativePath(file, accessConfig),
|
|
1265
|
+
fullPath: file,
|
|
1266
|
+
error: error.message,
|
|
1267
|
+
success: false
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// Add dependency scan results if any
|
|
1274
|
+
if (dependencyIssues.length > 0) {
|
|
1275
|
+
results.push({
|
|
1276
|
+
file: path.join(fullDir, 'package.json'),
|
|
1277
|
+
fullPath: path.join(fullDir, 'package.json'),
|
|
1278
|
+
action: 'dependency-scan',
|
|
1279
|
+
critical: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.CRITICAL),
|
|
1280
|
+
errors: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
|
|
1281
|
+
warnings: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING),
|
|
1282
|
+
info: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
|
|
1283
|
+
totalIssues: dependencyIssues.length,
|
|
1284
|
+
analyzed: true,
|
|
1285
|
+
scannersUsed: ['npm-audit'],
|
|
1286
|
+
timestamp: new Date().toISOString()
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
return results;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Get Sparrow analyzer (lazy initialization)
|
|
1295
|
+
* @private
|
|
1296
|
+
*/
|
|
1297
|
+
async getSparrowAnalyzer() {
|
|
1298
|
+
if (!this.analyzers.sparrow) {
|
|
1299
|
+
const SparrowAnalyzer = (await import('../analyzers/SparrowAnalyzer.js')).default;
|
|
1300
|
+
this.analyzers.sparrow = new SparrowAnalyzer(this.logger);
|
|
1301
|
+
}
|
|
1302
|
+
return this.analyzers.sparrow;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Sparrow SAST scan for a single file (tree-sitter based, no external dependencies)
|
|
1307
|
+
* @private
|
|
1308
|
+
*/
|
|
1309
|
+
async sparrowScanFile(filePath, workingDir, accessConfig, options = {}) {
|
|
1310
|
+
const fullPath = path.isAbsolute(filePath)
|
|
1311
|
+
? path.normalize(filePath)
|
|
1312
|
+
: path.resolve(workingDir, filePath);
|
|
1313
|
+
|
|
1314
|
+
// Validate read access
|
|
1315
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
1316
|
+
if (!accessResult.allowed) {
|
|
1317
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
try {
|
|
1321
|
+
const stats = await fs.stat(fullPath);
|
|
1322
|
+
|
|
1323
|
+
if (stats.size > this.maxFileSize) {
|
|
1324
|
+
throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Get Sparrow analyzer
|
|
1328
|
+
const sparrowAnalyzer = await this.getSparrowAnalyzer();
|
|
1329
|
+
|
|
1330
|
+
// Check if file is supported
|
|
1331
|
+
if (!sparrowAnalyzer.isSupported(fullPath)) {
|
|
1332
|
+
return {
|
|
1333
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1334
|
+
fullPath,
|
|
1335
|
+
action: 'sparrow-scan',
|
|
1336
|
+
skipped: true,
|
|
1337
|
+
reason: 'Unsupported file type for Sparrow SAST',
|
|
1338
|
+
analyzed: false,
|
|
1339
|
+
timestamp: new Date().toISOString()
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// Perform Sparrow scan
|
|
1344
|
+
const scanResult = await sparrowAnalyzer.scanFile(fullPath, {
|
|
1345
|
+
useBuiltinCheckers: options.useBuiltinCheckers !== false,
|
|
1346
|
+
enabledCheckers: options.enabledCheckers,
|
|
1347
|
+
disabledCheckers: options.disabledCheckers
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
if (!scanResult.success) {
|
|
1351
|
+
throw new Error(scanResult.error || 'Sparrow scan failed');
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Categorize issues by severity
|
|
1355
|
+
const result = {
|
|
1356
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1357
|
+
fullPath,
|
|
1358
|
+
action: 'sparrow-scan',
|
|
1359
|
+
critical: scanResult.issues.filter(i => i.severity === 'critical'),
|
|
1360
|
+
errors: scanResult.issues.filter(i => i.severity === 'error'),
|
|
1361
|
+
warnings: scanResult.issues.filter(i => i.severity === 'warning'),
|
|
1362
|
+
info: scanResult.issues.filter(i => i.severity === 'info'),
|
|
1363
|
+
totalIssues: scanResult.issues.length,
|
|
1364
|
+
analyzed: true,
|
|
1365
|
+
scanner: 'sparrow',
|
|
1366
|
+
executionTime: scanResult.executionTime,
|
|
1367
|
+
timestamp: new Date().toISOString()
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
return result;
|
|
1371
|
+
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
this.logger?.error('Sparrow scan file failed', { file: fullPath, error: error.message });
|
|
1374
|
+
return {
|
|
1375
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1376
|
+
fullPath,
|
|
1377
|
+
action: 'sparrow-scan',
|
|
1378
|
+
error: error.message,
|
|
1379
|
+
analyzed: false,
|
|
1380
|
+
success: false,
|
|
1381
|
+
timestamp: new Date().toISOString()
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* Sparrow SAST scan for a project directory (tree-sitter based, no external dependencies)
|
|
1388
|
+
* @private
|
|
1389
|
+
*/
|
|
1390
|
+
async sparrowScanProject(directory, pattern, workingDir, accessConfig, options = {}) {
|
|
1391
|
+
const fullDir = path.isAbsolute(directory)
|
|
1392
|
+
? path.normalize(directory)
|
|
1393
|
+
: path.resolve(workingDir, directory);
|
|
1394
|
+
|
|
1395
|
+
// Validate read access
|
|
1396
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
|
|
1397
|
+
if (!accessResult.allowed) {
|
|
1398
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
try {
|
|
1402
|
+
// Get Sparrow analyzer
|
|
1403
|
+
const sparrowAnalyzer = await this.getSparrowAnalyzer();
|
|
1404
|
+
|
|
1405
|
+
// Run project scan with Sparrow
|
|
1406
|
+
const scanResult = await sparrowAnalyzer.scanProject(fullDir, {
|
|
1407
|
+
useBuiltinCheckers: options.useBuiltinCheckers !== false,
|
|
1408
|
+
enabledCheckers: options.enabledCheckers,
|
|
1409
|
+
disabledCheckers: options.disabledCheckers,
|
|
1410
|
+
excludePatterns: options.excludePatterns,
|
|
1411
|
+
languages: options.languages
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
if (!scanResult.success) {
|
|
1415
|
+
throw new Error(scanResult.error || 'Sparrow project scan failed');
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Transform file results to standard format
|
|
1419
|
+
const results = scanResult.files.map(fileResult => ({
|
|
1420
|
+
file: this.directoryAccessManager.createRelativePath(fileResult.file, accessConfig),
|
|
1421
|
+
fullPath: fileResult.file,
|
|
1422
|
+
action: 'sparrow-scan',
|
|
1423
|
+
critical: fileResult.issues.filter(i => i.severity === 'critical'),
|
|
1424
|
+
errors: fileResult.issues.filter(i => i.severity === 'error'),
|
|
1425
|
+
warnings: fileResult.issues.filter(i => i.severity === 'warning'),
|
|
1426
|
+
info: fileResult.issues.filter(i => i.severity === 'info'),
|
|
1427
|
+
totalIssues: fileResult.issues.length,
|
|
1428
|
+
analyzed: true,
|
|
1429
|
+
scanner: 'sparrow',
|
|
1430
|
+
timestamp: new Date().toISOString()
|
|
1431
|
+
}));
|
|
1432
|
+
|
|
1433
|
+
return {
|
|
1434
|
+
success: true,
|
|
1435
|
+
files: results,
|
|
1436
|
+
summary: scanResult.summary,
|
|
1437
|
+
executionTime: scanResult.executionTime
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
this.logger?.error('Sparrow project scan failed', { directory: fullDir, error: error.message });
|
|
1442
|
+
return {
|
|
1443
|
+
success: false,
|
|
1444
|
+
files: [],
|
|
1445
|
+
error: error.message
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* Validate a configuration file
|
|
1452
|
+
* @private
|
|
1453
|
+
*/
|
|
1454
|
+
async validateConfigFile(filePath, workingDir, accessConfig, options = {}) {
|
|
1455
|
+
const fullPath = path.isAbsolute(filePath)
|
|
1456
|
+
? path.normalize(filePath)
|
|
1457
|
+
: path.resolve(workingDir, filePath);
|
|
1458
|
+
|
|
1459
|
+
// Validate read access
|
|
1460
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
1461
|
+
if (!accessResult.allowed) {
|
|
1462
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
try {
|
|
1466
|
+
const stats = await fs.stat(fullPath);
|
|
1467
|
+
|
|
1468
|
+
if (stats.size > this.maxFileSize) {
|
|
1469
|
+
throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Get config validator
|
|
1473
|
+
const configValidator = await this.getConfigValidator();
|
|
1474
|
+
|
|
1475
|
+
// Perform validation
|
|
1476
|
+
const issues = await configValidator.validate(fullPath, options);
|
|
1477
|
+
|
|
1478
|
+
// Categorize issues by severity
|
|
1479
|
+
const result = {
|
|
1480
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1481
|
+
fullPath,
|
|
1482
|
+
action: 'validate-config',
|
|
1483
|
+
critical: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.CRITICAL),
|
|
1484
|
+
errors: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
|
|
1485
|
+
warnings: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING),
|
|
1486
|
+
info: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
|
|
1487
|
+
totalIssues: issues.length,
|
|
1488
|
+
analyzed: true,
|
|
1489
|
+
validatorsUsed: issues.map(i => i.validator).filter((v, i, a) => a.indexOf(v) === i),
|
|
1490
|
+
timestamp: new Date().toISOString()
|
|
1491
|
+
};
|
|
1492
|
+
|
|
1493
|
+
return result;
|
|
1494
|
+
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
throw new Error(`Failed to validate config ${filePath}: ${error.message}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* Validate configuration files in a directory
|
|
1502
|
+
* @private
|
|
1503
|
+
*/
|
|
1504
|
+
async validateConfigDirectory(directory, workingDir, accessConfig, options = {}) {
|
|
1505
|
+
const fullDir = path.isAbsolute(directory)
|
|
1506
|
+
? path.normalize(directory)
|
|
1507
|
+
: path.resolve(workingDir, directory);
|
|
1508
|
+
|
|
1509
|
+
// Validate read access
|
|
1510
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
|
|
1511
|
+
if (!accessResult.allowed) {
|
|
1512
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// Find common config files
|
|
1516
|
+
const configFiles = await this.findConfigFiles(fullDir);
|
|
1517
|
+
|
|
1518
|
+
if (configFiles.length > this.maxFilesPerBatch) {
|
|
1519
|
+
throw new Error(`Too many config files: ${configFiles.length} (max ${this.maxFilesPerBatch})`);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Validate files
|
|
1523
|
+
const results = [];
|
|
1524
|
+
|
|
1525
|
+
for (const file of configFiles) {
|
|
1526
|
+
try {
|
|
1527
|
+
const result = await this.validateConfigFile(file, workingDir, accessConfig, options);
|
|
1528
|
+
if (result) {
|
|
1529
|
+
results.push(result);
|
|
1530
|
+
}
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
this.logger?.warn('Failed to validate config file', {
|
|
1533
|
+
file,
|
|
1534
|
+
error: error.message
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
results.push({
|
|
1538
|
+
file: this.directoryAccessManager.createRelativePath(file, accessConfig),
|
|
1539
|
+
fullPath: file,
|
|
1540
|
+
error: error.message,
|
|
1541
|
+
success: false
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
return results;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* Validate a structured file (JSON, YAML, XML, TOML, etc.)
|
|
1551
|
+
* Uses the pluggable structuredFileValidator utility
|
|
1552
|
+
* @private
|
|
1553
|
+
*/
|
|
1554
|
+
async validateStructuredFile(filePath, content, format, workingDir, accessConfig, options = {}) {
|
|
1555
|
+
// If content is provided directly, validate it
|
|
1556
|
+
if (content && format) {
|
|
1557
|
+
const validationResult = validateContent(content, format, { returnParsed: options.returnParsed });
|
|
1558
|
+
|
|
1559
|
+
return {
|
|
1560
|
+
file: filePath || '<inline-content>',
|
|
1561
|
+
fullPath: null,
|
|
1562
|
+
action: 'validate-structured',
|
|
1563
|
+
format: validationResult.format,
|
|
1564
|
+
valid: validationResult.valid,
|
|
1565
|
+
errors: validationResult.errors.filter(e => e.severity === 'error').map(e => ({
|
|
1566
|
+
line: e.line,
|
|
1567
|
+
column: e.column,
|
|
1568
|
+
message: e.message,
|
|
1569
|
+
severity: STATIC_ANALYSIS.SEVERITY.ERROR,
|
|
1570
|
+
category: 'structure'
|
|
1571
|
+
})),
|
|
1572
|
+
warnings: validationResult.errors.filter(e => e.severity === 'warning').map(e => ({
|
|
1573
|
+
line: e.line,
|
|
1574
|
+
column: e.column,
|
|
1575
|
+
message: e.message,
|
|
1576
|
+
severity: STATIC_ANALYSIS.SEVERITY.WARNING,
|
|
1577
|
+
category: 'structure'
|
|
1578
|
+
})),
|
|
1579
|
+
info: [],
|
|
1580
|
+
totalIssues: validationResult.errors.length,
|
|
1581
|
+
analyzed: true,
|
|
1582
|
+
supportedFormats: getSupportedFormats(),
|
|
1583
|
+
timestamp: new Date().toISOString()
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Otherwise, validate from file path
|
|
1588
|
+
if (!filePath) {
|
|
1589
|
+
throw new Error('Either filePath or content+format must be provided');
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
const fullPath = path.isAbsolute(filePath)
|
|
1593
|
+
? path.normalize(filePath)
|
|
1594
|
+
: path.resolve(workingDir, filePath);
|
|
1595
|
+
|
|
1596
|
+
// Validate read access
|
|
1597
|
+
const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
|
|
1598
|
+
if (!accessResult.allowed) {
|
|
1599
|
+
throw new Error(`Read access denied: ${accessResult.reason}`);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
try {
|
|
1603
|
+
const stats = await fs.stat(fullPath);
|
|
1604
|
+
|
|
1605
|
+
if (stats.size > this.maxFileSize) {
|
|
1606
|
+
throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// Detect format if not provided
|
|
1610
|
+
const detectedFormat = format || detectFormat(fullPath);
|
|
1611
|
+
|
|
1612
|
+
if (!detectedFormat) {
|
|
1613
|
+
return {
|
|
1614
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1615
|
+
fullPath,
|
|
1616
|
+
action: 'validate-structured',
|
|
1617
|
+
format: 'unknown',
|
|
1618
|
+
valid: false,
|
|
1619
|
+
errors: [{
|
|
1620
|
+
message: `Cannot detect format for file: ${filePath}. Supported: ${getSupportedFormats().join(', ')}`,
|
|
1621
|
+
severity: STATIC_ANALYSIS.SEVERITY.ERROR,
|
|
1622
|
+
category: 'structure'
|
|
1623
|
+
}],
|
|
1624
|
+
warnings: [],
|
|
1625
|
+
info: [],
|
|
1626
|
+
totalIssues: 1,
|
|
1627
|
+
analyzed: false,
|
|
1628
|
+
supportedFormats: getSupportedFormats(),
|
|
1629
|
+
timestamp: new Date().toISOString()
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// Read and validate file
|
|
1634
|
+
const fileContent = await fs.readFile(fullPath, 'utf-8');
|
|
1635
|
+
const validationResult = validateContent(fileContent, detectedFormat, { returnParsed: options.returnParsed });
|
|
1636
|
+
|
|
1637
|
+
return {
|
|
1638
|
+
file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
|
|
1639
|
+
fullPath,
|
|
1640
|
+
action: 'validate-structured',
|
|
1641
|
+
format: validationResult.format,
|
|
1642
|
+
valid: validationResult.valid,
|
|
1643
|
+
errors: validationResult.errors.filter(e => e.severity === 'error').map(e => ({
|
|
1644
|
+
line: e.line,
|
|
1645
|
+
column: e.column,
|
|
1646
|
+
message: e.message,
|
|
1647
|
+
severity: STATIC_ANALYSIS.SEVERITY.ERROR,
|
|
1648
|
+
category: 'structure'
|
|
1649
|
+
})),
|
|
1650
|
+
warnings: validationResult.errors.filter(e => e.severity === 'warning').map(e => ({
|
|
1651
|
+
line: e.line,
|
|
1652
|
+
column: e.column,
|
|
1653
|
+
message: e.message,
|
|
1654
|
+
severity: STATIC_ANALYSIS.SEVERITY.WARNING,
|
|
1655
|
+
category: 'structure'
|
|
1656
|
+
})),
|
|
1657
|
+
info: [],
|
|
1658
|
+
totalIssues: validationResult.errors.length,
|
|
1659
|
+
analyzed: true,
|
|
1660
|
+
supportedFormats: getSupportedFormats(),
|
|
1661
|
+
timestamp: new Date().toISOString()
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
throw new Error(`Failed to validate structured file ${filePath}: ${error.message}`);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/**
|
|
1670
|
+
* Find common configuration files in directory
|
|
1671
|
+
* @private
|
|
1672
|
+
*/
|
|
1673
|
+
async findConfigFiles(directory) {
|
|
1674
|
+
const files = [];
|
|
1675
|
+
const configFileNames = [
|
|
1676
|
+
'package.json',
|
|
1677
|
+
'tsconfig.json',
|
|
1678
|
+
'Dockerfile',
|
|
1679
|
+
'docker-compose.yml',
|
|
1680
|
+
'docker-compose.yaml',
|
|
1681
|
+
'.env',
|
|
1682
|
+
'.env.example',
|
|
1683
|
+
'.eslintrc.js',
|
|
1684
|
+
'.eslintrc.json',
|
|
1685
|
+
'.prettierrc',
|
|
1686
|
+
'.prettierrc.json'
|
|
1687
|
+
];
|
|
1688
|
+
|
|
1689
|
+
const configExtensions = ['.yml', '.yaml', '.json', '.tf', '.tfvars'];
|
|
1690
|
+
|
|
1691
|
+
const walk = async (dir) => {
|
|
1692
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1693
|
+
|
|
1694
|
+
for (const entry of entries) {
|
|
1695
|
+
const fullPath = path.join(dir, entry.name);
|
|
1696
|
+
|
|
1697
|
+
if (entry.isDirectory()) {
|
|
1698
|
+
// Check specific directories for config files
|
|
1699
|
+
if (entry.name === '.github' || entry.name === 'kubernetes' || entry.name === 'k8s' || entry.name === 'terraform') {
|
|
1700
|
+
await walk(fullPath);
|
|
1701
|
+
} else if (!['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
|
|
1702
|
+
// Don't recurse into all subdirectories, only known config dirs
|
|
1703
|
+
// Check this level only
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
} else if (entry.isFile()) {
|
|
1707
|
+
// Check if it's a known config file
|
|
1708
|
+
if (configFileNames.includes(entry.name)) {
|
|
1709
|
+
files.push(fullPath);
|
|
1710
|
+
} else {
|
|
1711
|
+
// Check if it's in a config directory with config extension
|
|
1712
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1713
|
+
if (configExtensions.includes(ext)) {
|
|
1714
|
+
const dirname = path.basename(path.dirname(fullPath));
|
|
1715
|
+
if (dirname === 'kubernetes' || dirname === 'k8s' || dirname === 'terraform' || dirname === 'workflows') {
|
|
1716
|
+
files.push(fullPath);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
await walk(directory);
|
|
1725
|
+
return files;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
/**
|
|
1729
|
+
* Detect programming language from file extension
|
|
1730
|
+
* @private
|
|
1731
|
+
*/
|
|
1732
|
+
detectLanguage(filePath) {
|
|
1733
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
1734
|
+
return STATIC_ANALYSIS.EXTENSION_TO_LANGUAGE[ext] || null;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* Detect framework from project directory
|
|
1739
|
+
* @private
|
|
1740
|
+
*/
|
|
1741
|
+
async detectFramework(projectDir, language) {
|
|
1742
|
+
try {
|
|
1743
|
+
if (language === STATIC_ANALYSIS.LANGUAGE.JAVASCRIPT ||
|
|
1744
|
+
language === STATIC_ANALYSIS.LANGUAGE.TYPESCRIPT) {
|
|
1745
|
+
return await this.detectJSFramework(projectDir);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (language === STATIC_ANALYSIS.LANGUAGE.PYTHON) {
|
|
1749
|
+
return await this.detectPythonFramework(projectDir);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
return null;
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
this.logger?.debug('Framework detection failed', { error: error.message });
|
|
1755
|
+
return null;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* Detect JavaScript/TypeScript framework
|
|
1761
|
+
* @private
|
|
1762
|
+
*/
|
|
1763
|
+
async detectJSFramework(projectDir) {
|
|
1764
|
+
try {
|
|
1765
|
+
const pkgPath = path.join(projectDir, STATIC_ANALYSIS.FRAMEWORK_MANIFESTS.JAVASCRIPT);
|
|
1766
|
+
const pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
1767
|
+
const pkg = JSON.parse(pkgContent);
|
|
1768
|
+
|
|
1769
|
+
const deps = {
|
|
1770
|
+
...pkg.dependencies,
|
|
1771
|
+
...pkg.devDependencies
|
|
1772
|
+
};
|
|
1773
|
+
|
|
1774
|
+
// Check for frameworks in priority order
|
|
1775
|
+
for (const [name, identifier] of Object.entries(STATIC_ANALYSIS.JS_FRAMEWORKS)) {
|
|
1776
|
+
if (deps[identifier]) {
|
|
1777
|
+
return name.toLowerCase();
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
return null;
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
/**
|
|
1788
|
+
* Detect Python framework
|
|
1789
|
+
* @private
|
|
1790
|
+
*/
|
|
1791
|
+
async detectPythonFramework(projectDir) {
|
|
1792
|
+
try {
|
|
1793
|
+
// Try requirements.txt
|
|
1794
|
+
const reqPath = path.join(projectDir, STATIC_ANALYSIS.FRAMEWORK_MANIFESTS.PYTHON);
|
|
1795
|
+
const reqContent = await fs.readFile(reqPath, 'utf-8');
|
|
1796
|
+
|
|
1797
|
+
// Check for frameworks
|
|
1798
|
+
for (const [name, identifier] of Object.entries(STATIC_ANALYSIS.PYTHON_FRAMEWORKS)) {
|
|
1799
|
+
if (reqContent.toLowerCase().includes(identifier)) {
|
|
1800
|
+
return name.toLowerCase();
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
return null;
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
// Try pyproject.toml
|
|
1807
|
+
try {
|
|
1808
|
+
const tomlPath = path.join(projectDir, STATIC_ANALYSIS.FRAMEWORK_MANIFESTS.PYTHON_POETRY);
|
|
1809
|
+
const tomlContent = await fs.readFile(tomlPath, 'utf-8');
|
|
1810
|
+
|
|
1811
|
+
for (const [name, identifier] of Object.entries(STATIC_ANALYSIS.PYTHON_FRAMEWORKS)) {
|
|
1812
|
+
if (tomlContent.toLowerCase().includes(identifier)) {
|
|
1813
|
+
return name.toLowerCase();
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
} catch {
|
|
1817
|
+
// No framework detected
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
return null;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* Find files matching pattern in directory
|
|
1826
|
+
* @private
|
|
1827
|
+
*/
|
|
1828
|
+
async findFiles(directory, pattern) {
|
|
1829
|
+
const files = [];
|
|
1830
|
+
|
|
1831
|
+
// Default patterns by language if not specified
|
|
1832
|
+
const searchPattern = pattern || '**/*.{js,jsx,mjs,cjs,ts,tsx,py,css,scss,sass,less}';
|
|
1833
|
+
|
|
1834
|
+
// Parse pattern to extract extensions
|
|
1835
|
+
// Supports patterns like "**/*.ts", "**/*.{js,ts}", "*.js", etc.
|
|
1836
|
+
const getExtensionsFromPattern = (pat) => {
|
|
1837
|
+
const exts = [];
|
|
1838
|
+
|
|
1839
|
+
// Match patterns like *.{js,ts,tsx} or *.js
|
|
1840
|
+
const bracesMatch = pat.match(/\*\.\{([^}]+)\}/);
|
|
1841
|
+
if (bracesMatch) {
|
|
1842
|
+
// Multiple extensions: *.{js,ts,tsx}
|
|
1843
|
+
const extList = bracesMatch[1].split(',').map(e => e.trim());
|
|
1844
|
+
extList.forEach(ext => exts.push(ext.startsWith('.') ? ext : '.' + ext));
|
|
1845
|
+
} else {
|
|
1846
|
+
// Single extension: *.js or **/*.ts
|
|
1847
|
+
const singleMatch = pat.match(/\*\.([a-z]+)$/i);
|
|
1848
|
+
if (singleMatch) {
|
|
1849
|
+
const ext = singleMatch[1];
|
|
1850
|
+
exts.push(ext.startsWith('.') ? ext : '.' + ext);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// If no pattern found, allow all supported extensions
|
|
1855
|
+
if (exts.length === 0) {
|
|
1856
|
+
return null; // null means "all supported extensions"
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
return exts;
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
const allowedExtensions = getExtensionsFromPattern(searchPattern);
|
|
1863
|
+
|
|
1864
|
+
// Simple recursive file search
|
|
1865
|
+
const walk = async (dir) => {
|
|
1866
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1867
|
+
|
|
1868
|
+
for (const entry of entries) {
|
|
1869
|
+
const fullPath = path.join(dir, entry.name);
|
|
1870
|
+
|
|
1871
|
+
if (entry.isDirectory()) {
|
|
1872
|
+
// Skip common ignore directories
|
|
1873
|
+
if (!['node_modules', '.git', 'dist', 'build', '__pycache__', '.venv', 'venv'].includes(entry.name)) {
|
|
1874
|
+
await walk(fullPath);
|
|
1875
|
+
}
|
|
1876
|
+
} else if (entry.isFile()) {
|
|
1877
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
1878
|
+
|
|
1879
|
+
// Check if file extension is supported
|
|
1880
|
+
if (STATIC_ANALYSIS.EXTENSION_TO_LANGUAGE[ext]) {
|
|
1881
|
+
// If pattern specified, check if extension matches
|
|
1882
|
+
if (allowedExtensions === null || allowedExtensions.includes(ext)) {
|
|
1883
|
+
files.push(fullPath);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
};
|
|
1889
|
+
|
|
1890
|
+
await walk(directory);
|
|
1891
|
+
return files;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Get analyzer for language (lazy initialization)
|
|
1896
|
+
* @private
|
|
1897
|
+
*/
|
|
1898
|
+
async getAnalyzer(language) {
|
|
1899
|
+
try {
|
|
1900
|
+
// Lazy load analyzers
|
|
1901
|
+
if (language === STATIC_ANALYSIS.LANGUAGE.JAVASCRIPT) {
|
|
1902
|
+
if (!this.analyzers.javascript) {
|
|
1903
|
+
const { default: JavaScriptAnalyzer } = await import('../analyzers/JavaScriptAnalyzer.js');
|
|
1904
|
+
this.analyzers.javascript = new JavaScriptAnalyzer(this.logger);
|
|
1905
|
+
}
|
|
1906
|
+
return this.analyzers.javascript;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
if (language === STATIC_ANALYSIS.LANGUAGE.TYPESCRIPT) {
|
|
1910
|
+
if (!this.analyzers.typescript) {
|
|
1911
|
+
const { default: TypeScriptAnalyzer } = await import('../analyzers/TypeScriptAnalyzer.js');
|
|
1912
|
+
this.analyzers.typescript = new TypeScriptAnalyzer(this.logger);
|
|
1913
|
+
}
|
|
1914
|
+
return this.analyzers.typescript;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// Python analyzer
|
|
1918
|
+
if (language === STATIC_ANALYSIS.LANGUAGE.PYTHON) {
|
|
1919
|
+
if (!this.analyzers.python) {
|
|
1920
|
+
const { default: PythonAnalyzer } = await import('../analyzers/PythonAnalyzer.js');
|
|
1921
|
+
this.analyzers.python = new PythonAnalyzer(this.logger);
|
|
1922
|
+
}
|
|
1923
|
+
return this.analyzers.python;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// CSS analyzer (handles CSS, SCSS, LESS)
|
|
1927
|
+
if (language === STATIC_ANALYSIS.LANGUAGE.CSS ||
|
|
1928
|
+
language === STATIC_ANALYSIS.LANGUAGE.SCSS ||
|
|
1929
|
+
language === STATIC_ANALYSIS.LANGUAGE.LESS) {
|
|
1930
|
+
if (!this.analyzers.css) {
|
|
1931
|
+
const { default: CSSAnalyzer } = await import('../analyzers/CSSAnalyzer.js');
|
|
1932
|
+
this.analyzers.css = new CSSAnalyzer(this.logger);
|
|
1933
|
+
}
|
|
1934
|
+
return this.analyzers.css;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
return null;
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
this.logger?.error('Failed to load analyzer', {
|
|
1940
|
+
language,
|
|
1941
|
+
error: error.message
|
|
1942
|
+
});
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
/**
|
|
1948
|
+
* Get ESLint analyzer (lazy initialization)
|
|
1949
|
+
* @private
|
|
1950
|
+
*/
|
|
1951
|
+
async getESLintAnalyzer() {
|
|
1952
|
+
if (!this.analyzers.eslint) {
|
|
1953
|
+
const { default: ESLintAnalyzer } = await import('../analyzers/ESLintAnalyzer.js');
|
|
1954
|
+
this.analyzers.eslint = new ESLintAnalyzer(this.logger);
|
|
1955
|
+
}
|
|
1956
|
+
return this.analyzers.eslint;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/**
|
|
1960
|
+
* Get Prettier formatter (lazy initialization)
|
|
1961
|
+
* @private
|
|
1962
|
+
*/
|
|
1963
|
+
async getPrettierFormatter() {
|
|
1964
|
+
if (!this.formatters.prettier) {
|
|
1965
|
+
const { default: PrettierFormatter } = await import('../analyzers/PrettierFormatter.js');
|
|
1966
|
+
this.formatters.prettier = new PrettierFormatter(this.logger);
|
|
1967
|
+
}
|
|
1968
|
+
return this.formatters.prettier;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
/**
|
|
1972
|
+
* Get Security analyzer (lazy initialization)
|
|
1973
|
+
* @private
|
|
1974
|
+
*/
|
|
1975
|
+
async getSecurityAnalyzer() {
|
|
1976
|
+
if (!this.analyzers.security) {
|
|
1977
|
+
const { default: SecurityAnalyzer } = await import('../analyzers/SecurityAnalyzer.js');
|
|
1978
|
+
this.analyzers.security = new SecurityAnalyzer(this.logger);
|
|
1979
|
+
}
|
|
1980
|
+
return this.analyzers.security;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
/**
|
|
1984
|
+
* Get Config validator (lazy initialization)
|
|
1985
|
+
* @private
|
|
1986
|
+
*/
|
|
1987
|
+
async getConfigValidator() {
|
|
1988
|
+
if (!this.analyzers.config) {
|
|
1989
|
+
const { default: ConfigValidator } = await import('../analyzers/ConfigValidator.js');
|
|
1990
|
+
this.analyzers.config = new ConfigValidator(this.logger);
|
|
1991
|
+
}
|
|
1992
|
+
return this.analyzers.config;
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
/**
|
|
1996
|
+
* Update summary statistics
|
|
1997
|
+
* @private
|
|
1998
|
+
*/
|
|
1999
|
+
updateSummary(summary, fileResult) {
|
|
2000
|
+
if (fileResult.analyzed) {
|
|
2001
|
+
summary.totalFiles++;
|
|
2002
|
+
|
|
2003
|
+
const criticalCount = fileResult.critical?.length || 0;
|
|
2004
|
+
const errorCount = fileResult.errors?.length || 0;
|
|
2005
|
+
const warningCount = fileResult.warnings?.length || 0;
|
|
2006
|
+
const infoCount = fileResult.info?.length || 0;
|
|
2007
|
+
|
|
2008
|
+
// Initialize totalCritical if not exists (for backward compatibility)
|
|
2009
|
+
if (summary.totalCritical === undefined) {
|
|
2010
|
+
summary.totalCritical = 0;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
summary.totalCritical += criticalCount;
|
|
2014
|
+
summary.totalErrors += errorCount;
|
|
2015
|
+
summary.totalWarnings += warningCount;
|
|
2016
|
+
summary.totalInfo += infoCount;
|
|
2017
|
+
|
|
2018
|
+
if (criticalCount > 0 || errorCount > 0) {
|
|
2019
|
+
summary.filesWithErrors++;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// Count by language
|
|
2023
|
+
if (fileResult.language) {
|
|
2024
|
+
summary.filesByLanguage[fileResult.language] =
|
|
2025
|
+
(summary.filesByLanguage[fileResult.language] || 0) + 1;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
// Count by category (include critical issues)
|
|
2029
|
+
const allIssues = [
|
|
2030
|
+
...(fileResult.critical || []),
|
|
2031
|
+
...(fileResult.errors || []),
|
|
2032
|
+
...(fileResult.warnings || [])
|
|
2033
|
+
];
|
|
2034
|
+
|
|
2035
|
+
for (const issue of allIssues) {
|
|
2036
|
+
if (issue.category) {
|
|
2037
|
+
summary.errorsByCategory[issue.category] =
|
|
2038
|
+
(summary.errorsByCategory[issue.category] || 0) + 1;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/**
|
|
2045
|
+
* Compute content hash for caching
|
|
2046
|
+
* @private
|
|
2047
|
+
*/
|
|
2048
|
+
computeContentHash(content) {
|
|
2049
|
+
return crypto
|
|
2050
|
+
.createHash('sha256')
|
|
2051
|
+
.update(content)
|
|
2052
|
+
.digest('hex')
|
|
2053
|
+
.substring(0, 16); // Use first 16 chars for shorter cache keys
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
/**
|
|
2057
|
+
* Get performance metrics
|
|
2058
|
+
* @returns {Object} Performance metrics
|
|
2059
|
+
*/
|
|
2060
|
+
getPerformanceMetrics() {
|
|
2061
|
+
const cacheHitRate = this.metrics.totalAnalyses > 0
|
|
2062
|
+
? (this.metrics.cacheHits / this.metrics.totalAnalyses) * 100
|
|
2063
|
+
: 0;
|
|
2064
|
+
|
|
2065
|
+
const avgAnalysisTime = this.metrics.filesAnalyzed > 0
|
|
2066
|
+
? this.metrics.totalAnalysisTime / this.metrics.filesAnalyzed
|
|
2067
|
+
: 0;
|
|
2068
|
+
|
|
2069
|
+
return {
|
|
2070
|
+
...this.metrics,
|
|
2071
|
+
cacheHitRate: Math.round(cacheHitRate * 10) / 10, // Round to 1 decimal
|
|
2072
|
+
averageAnalysisTime: Math.round(avgAnalysisTime),
|
|
2073
|
+
cacheSize: this.analysisCache.size
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
/**
|
|
2078
|
+
* Reset performance metrics
|
|
2079
|
+
*/
|
|
2080
|
+
resetPerformanceMetrics() {
|
|
2081
|
+
this.metrics = {
|
|
2082
|
+
totalAnalyses: 0,
|
|
2083
|
+
cacheHits: 0,
|
|
2084
|
+
cacheMisses: 0,
|
|
2085
|
+
totalAnalysisTime: 0,
|
|
2086
|
+
filesAnalyzed: 0,
|
|
2087
|
+
parallelBatches: 0
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
/**
|
|
2092
|
+
* Clear analysis cache
|
|
2093
|
+
*/
|
|
2094
|
+
clearCache() {
|
|
2095
|
+
this.analysisCache.clear();
|
|
2096
|
+
this.logger?.debug('Analysis cache cleared');
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/**
|
|
2100
|
+
* Get supported actions for this tool
|
|
2101
|
+
* @returns {Array<string>} Array of supported action names
|
|
2102
|
+
*/
|
|
2103
|
+
getSupportedActions() {
|
|
2104
|
+
return [
|
|
2105
|
+
'analyze', 'analyze-project', 'fix', 'format',
|
|
2106
|
+
'security-scan', 'security-scan-project',
|
|
2107
|
+
'sparrow-scan', 'sparrow-scan-project', // Tree-sitter based SAST
|
|
2108
|
+
'validate-config', 'validate-config-directory',
|
|
2109
|
+
'validate-structured' // JSON, YAML, XML, TOML, INI, ENV validation
|
|
2110
|
+
];
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
/**
|
|
2114
|
+
* Get parameter schema for validation
|
|
2115
|
+
* @returns {Object} Parameter schema
|
|
2116
|
+
*/
|
|
2117
|
+
getParameterSchema() {
|
|
2118
|
+
return {
|
|
2119
|
+
type: 'object',
|
|
2120
|
+
properties: {
|
|
2121
|
+
actions: {
|
|
2122
|
+
type: 'array',
|
|
2123
|
+
minItems: 1,
|
|
2124
|
+
items: {
|
|
2125
|
+
type: 'object',
|
|
2126
|
+
properties: {
|
|
2127
|
+
type: {
|
|
2128
|
+
type: 'string',
|
|
2129
|
+
enum: this.getSupportedActions()
|
|
2130
|
+
},
|
|
2131
|
+
filePath: { type: 'string' },
|
|
2132
|
+
directory: { type: 'string' },
|
|
2133
|
+
pattern: { type: 'string' },
|
|
2134
|
+
includeWarnings: { type: 'boolean' },
|
|
2135
|
+
maxErrors: { type: 'number' }
|
|
2136
|
+
},
|
|
2137
|
+
required: ['type']
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
},
|
|
2141
|
+
required: ['actions']
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
export default StaticAnalysisTool;
|