fullcourtdefense-cli 1.21.18 → 1.21.19

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.
@@ -840,13 +840,13 @@ async function discoverCommand(args, config) {
840
840
  const { extraScanRoots: adminScanRoots, disabledScanRoots } = (0, runtimeConfig_1.getCachedScanRootOverrides)();
841
841
  const normalizedDisabledRoots = (0, discoverPaths_1.uniqueNormalizedPaths)(disabledScanRoots);
842
842
  const isDisabledRoot = (root) => (0, discoverPaths_1.isExcludedPath)(root, normalizedDisabledRoots);
843
- const scanRoots = (0, discoverPaths_1.uniqueNormalizedPaths)([...(0, discoverPaths_1.resolveScanRoots)(args.scanRoot), ...adminScanRoots])
843
+ const scanRoots = (0, discoverPaths_1.compactScanRoots)([...(0, discoverPaths_1.resolveScanRoots)(args.scanRoot), ...adminScanRoots])
844
844
  .filter(root => !isDisabledRoot(root));
845
845
  const sweep = scanRoots.length > 0
846
846
  ? (0, discoverPaths_1.scanRootsForProjectConfigs)(scanRoots, { excludedRoots: normalizedDisabledRoots })
847
847
  : { candidates: [], scannedDirs: 0, excludedDirs: 0, truncated: false, maxDepth: 4, maxDirs: 25000 };
848
848
  // Admin roots are scanned for env/secrets even when no MCP config lives there.
849
- const sweepProjectRoots = (0, discoverPaths_1.uniqueNormalizedPaths)([...deriveProjectRoots(sweep.candidates), ...adminScanRoots])
849
+ const sweepProjectRoots = (0, discoverPaths_1.compactScanRoots)([...deriveProjectRoots(sweep.candidates), ...adminScanRoots])
850
850
  .filter(root => !isDisabledRoot(root));
851
851
  const knownConfigCandidates = candidateConfigPaths(cwd, args.extraPath)
852
852
  .filter(candidate => !isDisabledRoot(candidate.path));
@@ -914,17 +914,19 @@ async function discoverCommand(args, config) {
914
914
  const home = os.homedir();
915
915
  // Effective roots the posture scan covered — proof of coverage for the console.
916
916
  // Admin-removed defaults are excluded here too, so the console mirrors reality.
917
- const defaultEnvRoots = [home, cwd, path.join(home, 'dev'), path.join(home, 'repos'), path.join(home, 'projects'), path.join(home, 'Documents'), path.join(home, 'code')]
918
- .map(root => path.resolve(root));
917
+ const defaultEnvRoots = (0, discoverPaths_1.compactScanRoots)([home, cwd, path.join(home, 'dev'), path.join(home, 'repos'), path.join(home, 'projects'), path.join(home, 'Documents'), path.join(home, 'code')]
918
+ .map(root => path.resolve(root)));
919
919
  const adminRootSet = new Set(adminScanRoots.map(root => path.resolve(root).toLowerCase()));
920
- const scannedRoots = [
920
+ const scannedRootCandidates = [
921
921
  ...[...new Set(defaultEnvRoots)].filter(root => fs.existsSync(root) && !isDisabledRoot(root)).map(root => ({ path: root, source: 'default' })),
922
922
  ...adminScanRoots.map(root => path.resolve(root)).filter(root => fs.existsSync(root)).map(root => ({ path: root, source: 'admin' })),
923
923
  ...(0, discoverPaths_1.resolveScanRoots)(args.scanRoot)
924
924
  .map(root => path.resolve(root))
925
925
  .filter(root => fs.existsSync(root) && !adminRootSet.has(root.toLowerCase()) && !isDisabledRoot(root))
926
926
  .map(root => ({ path: root, source: 'default' })),
927
- ].filter((entry, index, list) => list.findIndex(other => other.path.toLowerCase() === entry.path.toLowerCase()) === index);
927
+ ];
928
+ const compactedScannedPaths = (0, discoverPaths_1.compactScanRoots)(scannedRootCandidates.map(entry => entry.path));
929
+ const scannedRoots = compactedScannedPaths.map(root => scannedRootCandidates.find(entry => path.resolve(entry.path).toLowerCase() === path.resolve(root).toLowerCase()));
928
930
  // Shallow folder tree for the console folder picker (names only, no contents).
929
931
  const folderCatalog = surfaces.has('posture') || surfaces.has('secrets')
930
932
  ? (0, discoverPaths_1.buildFolderCatalog)({ extraRoots: adminScanRoots })
@@ -14,6 +14,11 @@ export declare function normalizedPath(value: string): string;
14
14
  export declare function pathIsWithin(candidate: string, root: string): boolean;
15
15
  export declare function isExcludedPath(candidate: string, excludedRoots?: string[]): boolean;
16
16
  export declare function uniqueNormalizedPaths(values: string[]): string[];
17
+ /**
18
+ * Keep only the shallowest non-overlapping scan roots. If C:\dev is present,
19
+ * C:\dev\repo is redundant: the bounded parent walk already covers it.
20
+ */
21
+ export declare function compactScanRoots(values: string[]): string[];
17
22
  export declare function claudeDesktopMsixCandidates(): ClaudeDesktopConfigCandidate[];
18
23
  export declare function claudeDesktopConfigCandidates(): ClaudeDesktopConfigCandidate[];
19
24
  export declare function claudeDesktopLikelyInstalled(): boolean;
@@ -37,6 +37,7 @@ exports.normalizedPath = normalizedPath;
37
37
  exports.pathIsWithin = pathIsWithin;
38
38
  exports.isExcludedPath = isExcludedPath;
39
39
  exports.uniqueNormalizedPaths = uniqueNormalizedPaths;
40
+ exports.compactScanRoots = compactScanRoots;
40
41
  exports.claudeDesktopMsixCandidates = claudeDesktopMsixCandidates;
41
42
  exports.claudeDesktopConfigCandidates = claudeDesktopConfigCandidates;
42
43
  exports.claudeDesktopLikelyInstalled = claudeDesktopLikelyInstalled;
@@ -75,6 +76,22 @@ function uniqueNormalizedPaths(values) {
75
76
  }
76
77
  return [...out.values()];
77
78
  }
79
+ /**
80
+ * Keep only the shallowest non-overlapping scan roots. If C:\dev is present,
81
+ * C:\dev\repo is redundant: the bounded parent walk already covers it.
82
+ */
83
+ function compactScanRoots(values) {
84
+ const roots = uniqueNormalizedPaths(values)
85
+ .sort((a, b) => normalizedPath(a).split(path.sep).length - normalizedPath(b).split(path.sep).length
86
+ || normalizedPath(a).length - normalizedPath(b).length);
87
+ const compacted = [];
88
+ for (const root of roots) {
89
+ if (compacted.some(parent => pathIsWithin(root, parent)))
90
+ continue;
91
+ compacted.push(root);
92
+ }
93
+ return compacted;
94
+ }
78
95
  function claudeDesktopDocumentedPath() {
79
96
  const home = os.homedir();
80
97
  if (process.platform === 'win32') {
@@ -269,7 +286,7 @@ function scanRootsForProjectConfigs(roots, options = {}) {
269
286
  let excludedDirs = 0;
270
287
  let depthLimited = false;
271
288
  const queue = [];
272
- for (const root of uniqueNormalizedPaths(roots)) {
289
+ for (const root of compactScanRoots(roots)) {
273
290
  try {
274
291
  const resolved = path.resolve(root);
275
292
  if (!isExcludedPath(resolved, options.excludedRoots) && fs.statSync(resolved).isDirectory())
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.21.18"
2
+ "version": "1.21.19"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.21.18",
3
+ "version": "1.21.19",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "test:machine-action-signing": "npm run build && node scripts/test-machine-action-signing.js",
36
36
  "test:integrity": "npm run build && node scripts/test-integrity.js",
37
37
  "test:posture-overhaul": "npm run build && node scripts/test-posture-overhaul.js",
38
+ "test:scan-root-compaction": "npm run build && node scripts/test-scan-root-compaction.js",
38
39
  "test:dpapi-config": "npm run build && node scripts/test-dpapi-config.js",
39
40
  "test:desktop-chat-guard": "npm run build && node scripts/test-desktop-chat-guard.js",
40
41
  "test:clipboard-scan": "npm run build && node scripts/test-clipboard-scan.js",