codegate-ai 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/jonathansantilli/codegate/actions/workflows/ci.yml/badge.svg)](https://github.com/jonathansantilli/codegate/actions/workflows/ci.yml)
4
4
  [![CodeQL](https://github.com/jonathansantilli/codegate/actions/workflows/codeql.yml/badge.svg)](https://github.com/jonathansantilli/codegate/actions/workflows/codeql.yml)
5
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg?url=https://deepwiki.com/jonathansantilli/codegate)](https://deepwiki.com/jonathansantilli/codegate)
5
6
  [![npm version](https://img.shields.io/npm/v/codegate-ai)](https://www.npmjs.com/package/codegate-ai)
6
7
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
8
 
@@ -70,6 +71,21 @@ npm install -g codegate-ai
70
71
  codegate scan .
71
72
  ```
72
73
 
74
+ Recommended first run:
75
+
76
+ ```bash
77
+ codegate init
78
+ ```
79
+
80
+ Why run `init`:
81
+
82
+ - Creates `~/.codegate/config.json` so your behavior is explicit and easy to tune.
83
+ - Makes it straightforward to set preferences like `scan_user_scope`, output format, thresholds, and tool discovery options.
84
+
85
+ If you skip `init`, CodeGate still works with built-in defaults. Nothing breaks.
86
+
87
+ See the [Configuration](#configuration) section for full settings and examples.
88
+
73
89
  ## System Capabilities
74
90
 
75
91
  1. Multi-layer analysis pipeline:
@@ -187,6 +203,7 @@ Behavior:
187
203
  Wrapper flags (consumed by CodeGate, not forwarded):
188
204
 
189
205
  - `--cg-force`
206
+ - `--cg-deep`
190
207
  - `--cg-no-tui`
191
208
  - `--cg-include-user-scope`
192
209
  - `--cg-format <type>`
@@ -197,8 +214,10 @@ Examples:
197
214
  ```bash
198
215
  codegate skills add https://github.com/vercel-labs/skills --skill find-skills
199
216
  codegate skills add https://github.com/owner/repo --skill security-review --cg-force
217
+ codegate skills add https://github.com/owner/repo --skill security-review --cg-deep
200
218
  codegate clawhub install security-auditor
201
219
  codegate clawhub install security-auditor --version 1.0.0
220
+ codegate clawhub install security-auditor --cg-deep
202
221
  codegate clawhub search security
203
222
  ```
204
223
 
package/dist/cli.js CHANGED
@@ -68,6 +68,51 @@ function mapAcquisitionFailure(status, error) {
68
68
  error: error ?? "tool description acquisition failed",
69
69
  };
70
70
  }
71
+ async function runMetaAgentCommandWithSandbox(context) {
72
+ const commandResult = await runSandboxCommand({
73
+ command: context.command.command,
74
+ args: context.command.args,
75
+ cwd: context.command.cwd,
76
+ timeoutMs: context.command.timeoutMs,
77
+ });
78
+ return {
79
+ command: context.command,
80
+ code: commandResult.code,
81
+ stdout: commandResult.stdout,
82
+ stderr: commandResult.stderr,
83
+ };
84
+ }
85
+ function resolveInteractiveCallback(input) {
86
+ if (!input.enabled) {
87
+ return undefined;
88
+ }
89
+ return input.provided ?? input.fallback;
90
+ }
91
+ function buildWrapperScanBridgeDeps(deps) {
92
+ const isTTY = deps.isTTY();
93
+ return {
94
+ prepareScanDiscovery: deps.prepareScanDiscovery,
95
+ discoverDeepResources: deps.discoverDeepResources,
96
+ discoverLocalTextTargets: deps.discoverLocalTextTargets,
97
+ requestDeepScanConsent: resolveInteractiveCallback({
98
+ enabled: true,
99
+ provided: deps.requestDeepScanConsent,
100
+ fallback: isTTY ? promptDeepScanConsent : undefined,
101
+ }),
102
+ requestDeepAgentSelection: resolveInteractiveCallback({
103
+ enabled: true,
104
+ provided: deps.requestDeepAgentSelection,
105
+ fallback: isTTY ? promptDeepAgentSelection : undefined,
106
+ }),
107
+ requestMetaAgentCommandConsent: resolveInteractiveCallback({
108
+ enabled: true,
109
+ provided: deps.requestMetaAgentCommandConsent,
110
+ fallback: isTTY ? promptMetaAgentCommandConsent : undefined,
111
+ }),
112
+ runMetaAgentCommand: deps.runMetaAgentCommand ?? runMetaAgentCommandWithSandbox,
113
+ executeDeepResource: deps.executeDeepResource,
114
+ };
115
+ }
71
116
  async function promptRunWarningConsent(context) {
72
117
  const rl = createInterface({
73
118
  input: process.stdin,
@@ -247,38 +292,28 @@ function addScanCommand(program, version, deps) {
247
292
  prepareScanDiscovery: deps.prepareScanDiscovery,
248
293
  discoverDeepResources: deps.discoverDeepResources,
249
294
  discoverLocalTextTargets: deps.discoverLocalTextTargets,
250
- requestDeepScanConsent: promptCallbacksEnabled
251
- ? (deps.requestDeepScanConsent ??
252
- (interactivePromptsEnabled ? promptDeepScanConsent : undefined))
253
- : undefined,
254
- requestDeepAgentSelection: promptCallbacksEnabled
255
- ? (deps.requestDeepAgentSelection ??
256
- (interactivePromptsEnabled ? promptDeepAgentSelection : undefined))
257
- : undefined,
258
- requestMetaAgentCommandConsent: promptCallbacksEnabled
259
- ? (deps.requestMetaAgentCommandConsent ??
260
- (interactivePromptsEnabled ? promptMetaAgentCommandConsent : undefined))
261
- : undefined,
295
+ requestDeepScanConsent: resolveInteractiveCallback({
296
+ enabled: promptCallbacksEnabled,
297
+ provided: deps.requestDeepScanConsent,
298
+ fallback: interactivePromptsEnabled ? promptDeepScanConsent : undefined,
299
+ }),
300
+ requestDeepAgentSelection: resolveInteractiveCallback({
301
+ enabled: promptCallbacksEnabled,
302
+ provided: deps.requestDeepAgentSelection,
303
+ fallback: interactivePromptsEnabled ? promptDeepAgentSelection : undefined,
304
+ }),
305
+ requestMetaAgentCommandConsent: resolveInteractiveCallback({
306
+ enabled: promptCallbacksEnabled,
307
+ provided: deps.requestMetaAgentCommandConsent,
308
+ fallback: interactivePromptsEnabled ? promptMetaAgentCommandConsent : undefined,
309
+ }),
262
310
  executeDeepResource: deps.executeDeepResource,
263
- runMetaAgentCommand: deps.runMetaAgentCommand ??
264
- (async (context) => {
265
- const commandResult = await runSandboxCommand({
266
- command: context.command.command,
267
- args: context.command.args,
268
- cwd: context.command.cwd,
269
- timeoutMs: context.command.timeoutMs,
270
- });
271
- return {
272
- command: context.command,
273
- code: commandResult.code,
274
- stdout: commandResult.stdout,
275
- stderr: commandResult.stderr,
276
- };
277
- }),
278
- requestRemediationConsent: promptCallbacksEnabled
279
- ? (deps.requestRemediationConsent ??
280
- (interactivePromptsEnabled ? promptRemediationConsent : undefined))
281
- : undefined,
311
+ runMetaAgentCommand: deps.runMetaAgentCommand ?? runMetaAgentCommandWithSandbox,
312
+ requestRemediationConsent: resolveInteractiveCallback({
313
+ enabled: promptCallbacksEnabled,
314
+ provided: deps.requestRemediationConsent,
315
+ fallback: interactivePromptsEnabled ? promptRemediationConsent : undefined,
316
+ }),
282
317
  runRemediation: deps.runRemediation,
283
318
  stdout: deps.stdout,
284
319
  stderr: deps.stderr,
@@ -378,6 +413,7 @@ function addSkillsCommand(program, version, deps) {
378
413
  .addHelpText("after", renderExampleHelp([
379
414
  "codegate skills add vercel-labs/skills --skill find-skills",
380
415
  "codegate skills add https://github.com/owner/repo --skill security-review",
416
+ "codegate skills add owner/repo --skill demo --cg-deep",
381
417
  "codegate skills find security",
382
418
  "codegate skills add owner/repo --skill demo --cg-force",
383
419
  ]))
@@ -390,6 +426,7 @@ function addSkillsCommand(program, version, deps) {
390
426
  pathExists: deps.pathExists,
391
427
  resolveConfig: deps.resolveConfig,
392
428
  runScan: deps.runScan,
429
+ ...buildWrapperScanBridgeDeps(deps),
393
430
  resolveScanTarget: deps.resolveScanTarget,
394
431
  requestSkillSelection: deps.requestSkillSelection,
395
432
  requestWarningProceed: deps.requestRunWarningConsent,
@@ -420,6 +457,7 @@ function addClawhubCommand(program, version, deps) {
420
457
  .addHelpText("after", renderExampleHelp([
421
458
  "codegate clawhub install security-auditor",
422
459
  "codegate clawhub install security-auditor --version 1.0.0",
460
+ "codegate clawhub install security-auditor --cg-deep",
423
461
  "codegate clawhub search security",
424
462
  "codegate clawhub install security-auditor --cg-force",
425
463
  ]))
@@ -432,6 +470,7 @@ function addClawhubCommand(program, version, deps) {
432
470
  pathExists: deps.pathExists,
433
471
  resolveConfig: deps.resolveConfig,
434
472
  runScan: deps.runScan,
473
+ ...buildWrapperScanBridgeDeps(deps),
435
474
  resolveScanTarget: deps.resolveScanTarget,
436
475
  requestWarningProceed: deps.requestRunWarningConsent,
437
476
  launchClawhub: deps.launchClawhub ?? ((args, cwd) => launchClawhubPassthrough(args, cwd)),
@@ -1,13 +1,14 @@
1
1
  import { type CodeGateConfig, type OutputFormat, type ResolveConfigOptions } from "../config.js";
2
2
  import { type ResolvedScanTarget } from "../scan-target.js";
3
3
  import type { CodeGateReport } from "../types/report.js";
4
- import type { ScanRunnerInput } from "./scan-command.js";
4
+ import { type ScanAnalysisDeps, type ScanRunnerInput } from "./scan-command.js";
5
5
  interface SourceDetectionContext {
6
6
  cwd: string;
7
7
  pathExists: (path: string) => boolean;
8
8
  }
9
9
  export interface ClawhubWrapperRuntimeOptions {
10
10
  force: boolean;
11
+ deep: boolean;
11
12
  noTui: boolean;
12
13
  includeUserScope: boolean;
13
14
  format?: OutputFormat;
@@ -38,6 +39,14 @@ export interface ClawhubWrapperDeps {
38
39
  pathExists?: (path: string) => boolean;
39
40
  resolveConfig: (options: ResolveConfigOptions) => CodeGateConfig;
40
41
  runScan: (input: ScanRunnerInput) => Promise<CodeGateReport>;
42
+ prepareScanDiscovery?: ScanAnalysisDeps["prepareScanDiscovery"];
43
+ discoverDeepResources?: ScanAnalysisDeps["discoverDeepResources"];
44
+ discoverLocalTextTargets?: ScanAnalysisDeps["discoverLocalTextTargets"];
45
+ requestDeepScanConsent?: ScanAnalysisDeps["requestDeepScanConsent"];
46
+ requestDeepAgentSelection?: ScanAnalysisDeps["requestDeepAgentSelection"];
47
+ requestMetaAgentCommandConsent?: ScanAnalysisDeps["requestMetaAgentCommandConsent"];
48
+ runMetaAgentCommand?: ScanAnalysisDeps["runMetaAgentCommand"];
49
+ executeDeepResource?: ScanAnalysisDeps["executeDeepResource"];
41
50
  resolveScanTarget?: (input: {
42
51
  rawTarget: string;
43
52
  cwd: string;
@@ -3,9 +3,9 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:
3
3
  import { tmpdir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { createInterface } from "node:readline/promises";
6
- import { applyConfigPolicy, OUTPUT_FORMATS, } from "../config.js";
7
- import { reorderRequestedTargetFindings } from "../report/requested-target-findings.js";
6
+ import { OUTPUT_FORMATS, } from "../config.js";
8
7
  import { resolveScanTarget } from "../scan-target.js";
8
+ import { runScanAnalysis } from "./scan-command.js";
9
9
  import { renderByFormat, summarizeRequestedTargetFindings } from "./scan-command/helpers.js";
10
10
  const CLAWHUB_GLOBAL_OPTIONS_WITH_VALUE = new Set(["--workdir", "--dir", "--site", "--registry"]);
11
11
  const CLAWHUB_INSTALL_OPTIONS_WITH_VALUE = new Set(["--version"]);
@@ -381,6 +381,7 @@ async function stageClawhubTargetDefault(input, deps) {
381
381
  export function parseClawhubInvocation(rawArgs, context) {
382
382
  const wrapper = {
383
383
  force: false,
384
+ deep: false,
384
385
  noTui: false,
385
386
  includeUserScope: false,
386
387
  format: undefined,
@@ -400,6 +401,10 @@ export function parseClawhubInvocation(rawArgs, context) {
400
401
  wrapper.force = true;
401
402
  continue;
402
403
  }
404
+ if (token === "--cg-deep") {
405
+ wrapper.deep = true;
406
+ continue;
407
+ }
403
408
  if (token === "--cg-no-tui") {
404
409
  wrapper.noTui = true;
405
410
  continue;
@@ -527,41 +532,55 @@ export async function executeClawhubWrapper(input, deps) {
527
532
  const config = parsed.wrapper.includeUserScope
528
533
  ? { ...baseConfig, scan_user_scope: true }
529
534
  : baseConfig;
530
- let report = await deps.runScan({
535
+ const { report, deepScanNotes } = await runScanAnalysis({
531
536
  version: input.version,
532
537
  scanTarget: resolvedTarget.scanTarget,
538
+ displayTarget: resolvedTarget.displayTarget,
539
+ explicitCandidates: resolvedTarget.explicitCandidates,
533
540
  config,
534
- flags: {
541
+ options: {
535
542
  noTui,
536
543
  format: parsed.wrapper.format,
537
544
  force: parsed.wrapper.force,
538
545
  includeUserScope: parsed.wrapper.includeUserScope,
546
+ deep: parsed.wrapper.deep,
539
547
  },
540
- discoveryContext: undefined,
548
+ }, {
549
+ isTTY: deps.isTTY,
550
+ runScan: deps.runScan,
551
+ prepareScanDiscovery: deps.prepareScanDiscovery,
552
+ discoverDeepResources: deps.discoverDeepResources,
553
+ discoverLocalTextTargets: deps.discoverLocalTextTargets,
554
+ requestDeepScanConsent: interactivePromptsEnabled ? deps.requestDeepScanConsent : undefined,
555
+ requestDeepAgentSelection: interactivePromptsEnabled
556
+ ? deps.requestDeepAgentSelection
557
+ : undefined,
558
+ requestMetaAgentCommandConsent: interactivePromptsEnabled
559
+ ? deps.requestMetaAgentCommandConsent
560
+ : undefined,
561
+ runMetaAgentCommand: deps.runMetaAgentCommand,
562
+ executeDeepResource: deps.executeDeepResource,
541
563
  });
542
- if (resolvedTarget.displayTarget && resolvedTarget.displayTarget !== report.scan_target) {
543
- report = {
544
- ...report,
545
- scan_target: resolvedTarget.displayTarget,
546
- };
547
- }
548
- report = applyConfigPolicy(report, config);
549
- report = reorderRequestedTargetFindings(report, resolvedTarget.displayTarget);
550
564
  const shouldUseTui = config.tui.enabled && isTTY && deps.renderTui !== undefined && noTui !== true;
551
565
  const targetSummaryNote = config.output_format === "terminal"
552
566
  ? summarizeRequestedTargetFindings(report, resolvedTarget.displayTarget)
553
567
  : null;
568
+ const scanNotes = config.output_format === "terminal"
569
+ ? targetSummaryNote
570
+ ? [...deepScanNotes, targetSummaryNote]
571
+ : deepScanNotes
572
+ : [];
554
573
  if (shouldUseTui) {
555
574
  deps.renderTui?.({
556
575
  view: "dashboard",
557
576
  report,
558
- notices: targetSummaryNote ? [targetSummaryNote] : undefined,
577
+ notices: scanNotes.length > 0 ? scanNotes : undefined,
559
578
  });
560
579
  deps.renderTui?.({ view: "summary", report });
561
580
  }
562
581
  else {
563
- if (targetSummaryNote) {
564
- deps.stdout(targetSummaryNote);
582
+ for (const note of scanNotes) {
583
+ deps.stdout(note);
565
584
  }
566
585
  deps.stdout(renderByFormat(config.output_format, report));
567
586
  }
@@ -88,4 +88,13 @@ export interface ExecuteScanCommandDeps {
88
88
  notices?: string[];
89
89
  }) => void;
90
90
  }
91
+ type ScanAnalysisDepKeys = "isTTY" | "runScan" | "prepareScanDiscovery" | "discoverDeepResources" | "discoverLocalTextTargets" | "requestDeepScanConsent" | "requestDeepAgentSelection" | "requestMetaAgentCommandConsent" | "runMetaAgentCommand" | "executeDeepResource";
92
+ export type ScanAnalysisDeps = Pick<ExecuteScanCommandDeps, ScanAnalysisDepKeys>;
93
+ export type ScanAnalysisInput = Omit<ExecuteScanCommandInput, "cwd">;
94
+ export interface ScanAnalysisResult {
95
+ report: CodeGateReport;
96
+ deepScanNotes: string[];
97
+ }
98
+ export declare function runScanAnalysis(input: ScanAnalysisInput, deps: ScanAnalysisDeps): Promise<ScanAnalysisResult>;
91
99
  export declare function executeScanCommand(input: ExecuteScanCommandInput, deps: ExecuteScanCommandDeps): Promise<void>;
100
+ export {};
@@ -68,229 +68,249 @@ function deepAgentOptions(report, config) {
68
68
  return order.indexOf(left.id) - order.indexOf(right.id);
69
69
  });
70
70
  }
71
- export async function executeScanCommand(input, deps) {
72
- try {
73
- const interactivePromptsEnabled = deps.isTTY() && input.options.noTui !== true;
74
- const discoveryContext = deps.prepareScanDiscovery
75
- ? await deps.prepareScanDiscovery(input.scanTarget, input.config, {
76
- explicitCandidates: input.explicitCandidates,
77
- })
78
- : undefined;
79
- let report = await deps.runScan({
80
- version: input.version,
81
- scanTarget: input.scanTarget,
82
- config: input.config,
83
- flags: input.options,
84
- discoveryContext,
85
- });
86
- if (input.displayTarget && input.displayTarget !== report.scan_target) {
87
- report = {
88
- ...report,
89
- scan_target: input.displayTarget,
90
- };
71
+ export async function runScanAnalysis(input, deps) {
72
+ const interactivePromptsEnabled = deps.isTTY() && input.options.noTui !== true;
73
+ const discoveryContext = deps.prepareScanDiscovery
74
+ ? await deps.prepareScanDiscovery(input.scanTarget, input.config, {
75
+ explicitCandidates: input.explicitCandidates,
76
+ })
77
+ : undefined;
78
+ let report = await deps.runScan({
79
+ version: input.version,
80
+ scanTarget: input.scanTarget,
81
+ config: input.config,
82
+ flags: input.options,
83
+ discoveryContext,
84
+ });
85
+ if (input.displayTarget && input.displayTarget !== report.scan_target) {
86
+ report = {
87
+ ...report,
88
+ scan_target: input.displayTarget,
89
+ };
90
+ }
91
+ const deepScanNotes = [];
92
+ if (input.options.deep) {
93
+ const discoverResources = deps.discoverDeepResources ?? (async () => []);
94
+ const discoverLocalTextTargets = deps.discoverLocalTextTargets ?? (async () => []);
95
+ const resources = await discoverResources(input.scanTarget, input.config, discoveryContext);
96
+ const localTextTargets = await discoverLocalTextTargets(input.scanTarget, input.config, discoveryContext);
97
+ const optionsForAgent = deepAgentOptions(report, input.config);
98
+ let selectedAgent = null;
99
+ if ((resources.length > 0 || localTextTargets.length > 0) && optionsForAgent.length > 0) {
100
+ if (input.options.force || !interactivePromptsEnabled) {
101
+ selectedAgent = optionsForAgent[0] ?? null;
102
+ }
103
+ else if (deps.requestDeepAgentSelection) {
104
+ selectedAgent = await deps.requestDeepAgentSelection(optionsForAgent);
105
+ }
91
106
  }
92
- const deepScanNotes = [];
93
- if (input.options.deep) {
94
- const discoverResources = deps.discoverDeepResources ?? (async () => []);
95
- const discoverLocalTextTargets = deps.discoverLocalTextTargets ?? (async () => []);
96
- const resources = await discoverResources(input.scanTarget, input.config, discoveryContext);
97
- const localTextTargets = await discoverLocalTextTargets(input.scanTarget, input.config, discoveryContext);
98
- const optionsForAgent = deepAgentOptions(report, input.config);
99
- let selectedAgent = null;
100
- if ((resources.length > 0 || localTextTargets.length > 0) && optionsForAgent.length > 0) {
101
- if (input.options.force || !interactivePromptsEnabled) {
102
- selectedAgent = optionsForAgent[0] ?? null;
103
- }
104
- else if (deps.requestDeepAgentSelection) {
105
- selectedAgent = await deps.requestDeepAgentSelection(optionsForAgent);
106
- }
107
+ if (resources.length === 0 && localTextTargets.length === 0) {
108
+ deepScanNotes.push(...noEligibleDeepResourceNotes());
109
+ }
110
+ else {
111
+ if (selectedAgent) {
112
+ deepScanNotes.push(`Deep scan meta-agent selected: ${selectedAgent.label} (${selectedAgent.binary})`);
107
113
  }
108
- if (resources.length === 0 && localTextTargets.length === 0) {
109
- deepScanNotes.push(...noEligibleDeepResourceNotes());
114
+ else if (optionsForAgent.length > 0) {
115
+ deepScanNotes.push("Deep scan meta-agent skipped. Running deterministic Layer 3 checks only.");
110
116
  }
111
117
  else {
112
- if (selectedAgent) {
113
- deepScanNotes.push(`Deep scan meta-agent selected: ${selectedAgent.label} (${selectedAgent.binary})`);
114
- }
115
- else if (optionsForAgent.length > 0) {
116
- deepScanNotes.push("Deep scan meta-agent skipped. Running deterministic Layer 3 checks only.");
117
- }
118
- else {
119
- deepScanNotes.push("No supported deep-scan agent detected (Claude Code, Codex CLI, or OpenCode). Running deterministic Layer 3 checks only.");
118
+ deepScanNotes.push("No supported deep-scan agent detected (Claude Code, Codex CLI, or OpenCode). Running deterministic Layer 3 checks only.");
119
+ }
120
+ if (resources.length > 0) {
121
+ if (!deps.executeDeepResource) {
122
+ throw new Error("Deep resource executor not configured");
120
123
  }
121
- if (resources.length > 0) {
122
- if (!deps.executeDeepResource) {
123
- throw new Error("Deep resource executor not configured");
124
+ let resourcesWithFetchedMetadata = 0;
125
+ let executedMetaAgentCommands = 0;
126
+ const outcomes = await runDeepScanWithConsent(resources, async (resource) => {
127
+ if (input.options.force) {
128
+ return true;
124
129
  }
125
- let resourcesWithFetchedMetadata = 0;
126
- let executedMetaAgentCommands = 0;
127
- const outcomes = await runDeepScanWithConsent(resources, async (resource) => {
128
- if (input.options.force) {
129
- return true;
130
- }
131
- if (deps.requestDeepScanConsent) {
132
- return await deps.requestDeepScanConsent(resource);
133
- }
134
- return false;
135
- }, async (resource) => {
136
- const fetched = await deps.executeDeepResource(resource);
137
- if (fetched.status !== "ok" || !selectedAgent) {
138
- return fetched;
139
- }
140
- resourcesWithFetchedMetadata += 1;
141
- const prompt = buildSecurityAnalysisPrompt({
142
- resourceId: resource.id,
143
- resourceSummary: metadataSummary(fetched.metadata),
144
- });
145
- const command = buildMetaAgentCommand({
146
- tool: selectedAgent.metaTool,
147
- prompt,
148
- workingDirectory: input.scanTarget,
149
- binaryPath: selectedAgent.binary,
150
- });
151
- const commandContext = {
152
- resource,
153
- agent: selectedAgent,
154
- command,
130
+ if (deps.requestDeepScanConsent) {
131
+ return await deps.requestDeepScanConsent(resource);
132
+ }
133
+ return false;
134
+ }, async (resource) => {
135
+ const fetched = await deps.executeDeepResource(resource);
136
+ if (fetched.status !== "ok" || !selectedAgent) {
137
+ return fetched;
138
+ }
139
+ resourcesWithFetchedMetadata += 1;
140
+ const prompt = buildSecurityAnalysisPrompt({
141
+ resourceId: resource.id,
142
+ resourceSummary: metadataSummary(fetched.metadata),
143
+ });
144
+ const command = buildMetaAgentCommand({
145
+ tool: selectedAgent.metaTool,
146
+ prompt,
147
+ workingDirectory: input.scanTarget,
148
+ binaryPath: selectedAgent.binary,
149
+ });
150
+ const commandContext = {
151
+ resource,
152
+ agent: selectedAgent,
153
+ command,
154
+ };
155
+ const approvedCommand = input.options.force ||
156
+ (deps.requestMetaAgentCommandConsent
157
+ ? await deps.requestMetaAgentCommandConsent(commandContext)
158
+ : false);
159
+ if (!approvedCommand) {
160
+ return {
161
+ ...fetched,
162
+ metadata: withMetaAgentFinding(fetched.metadata, {
163
+ id: `layer3-meta-agent-skipped-${resource.id}`,
164
+ severity: "INFO",
165
+ description: `Deep scan meta-agent command skipped for ${resource.id}`,
166
+ }),
155
167
  };
156
- const approvedCommand = input.options.force ||
157
- (deps.requestMetaAgentCommandConsent
158
- ? await deps.requestMetaAgentCommandConsent(commandContext)
159
- : false);
160
- if (!approvedCommand) {
161
- return {
162
- ...fetched,
163
- metadata: withMetaAgentFinding(fetched.metadata, {
164
- id: `layer3-meta-agent-skipped-${resource.id}`,
165
- severity: "INFO",
166
- description: `Deep scan meta-agent command skipped for ${resource.id}`,
167
- }),
168
- };
169
- }
170
- if (!deps.runMetaAgentCommand) {
171
- throw new Error("Meta-agent command runner not configured");
172
- }
173
- executedMetaAgentCommands += 1;
174
- const commandResult = await deps.runMetaAgentCommand(commandContext);
175
- if (commandResult.code !== 0) {
176
- return {
177
- ...fetched,
178
- metadata: withMetaAgentFinding(fetched.metadata, {
179
- id: `layer3-meta-agent-command-error-${resource.id}`,
180
- severity: "LOW",
181
- description: `Deep scan meta-agent command failed for ${resource.id}`,
182
- evidence: commandResult.stderr || `exit code: ${commandResult.code}`,
183
- }),
184
- };
185
- }
186
- const parsedOutput = parseMetaAgentOutput(commandResult.stdout);
187
- if (parsedOutput === null) {
188
- return {
189
- ...fetched,
190
- metadata: withMetaAgentFinding(fetched.metadata, {
191
- id: `layer3-meta-agent-parse-error-${resource.id}`,
192
- severity: "LOW",
193
- description: `Deep scan meta-agent output was not valid JSON for ${resource.id}`,
194
- evidence: commandResult.stdout.slice(0, 400),
195
- }),
196
- };
197
- }
198
- const normalizedOutput = Array.isArray(parsedOutput)
199
- ? { findings: parsedOutput }
200
- : parsedOutput;
168
+ }
169
+ if (!deps.runMetaAgentCommand) {
170
+ throw new Error("Meta-agent command runner not configured");
171
+ }
172
+ executedMetaAgentCommands += 1;
173
+ const commandResult = await deps.runMetaAgentCommand(commandContext);
174
+ if (commandResult.code !== 0) {
201
175
  return {
202
176
  ...fetched,
203
- metadata: mergeMetaAgentMetadata(fetched.metadata, normalizedOutput),
177
+ metadata: withMetaAgentFinding(fetched.metadata, {
178
+ id: `layer3-meta-agent-command-error-${resource.id}`,
179
+ severity: "LOW",
180
+ description: `Deep scan meta-agent command failed for ${resource.id}`,
181
+ evidence: commandResult.stderr || `exit code: ${commandResult.code}`,
182
+ }),
204
183
  };
205
- });
206
- const layer3Findings = layer3OutcomesToFindings(outcomes, {
207
- unicodeAnalysis: input.config.unicode_analysis,
208
- });
209
- report = mergeLayer3Findings(report, layer3Findings);
210
- if (selectedAgent) {
211
- if (resourcesWithFetchedMetadata === 0) {
212
- deepScanNotes.push("Selected meta-agent was not executed because no approved resources returned metadata successfully.");
213
- }
214
- else if (executedMetaAgentCommands === 0) {
215
- deepScanNotes.push("Selected meta-agent was not executed because meta-agent command execution was not approved.");
216
- }
217
- else {
218
- const suffix = executedMetaAgentCommands === 1 ? "" : "s";
219
- deepScanNotes.push(`Deep scan meta-agent executed for ${executedMetaAgentCommands} resource${suffix}.`);
220
- }
221
184
  }
222
- }
223
- if (localTextTargets.length > 0) {
224
- if (!selectedAgent) {
225
- deepScanNotes.push("Local instruction-file analysis skipped because no meta-agent was selected.");
185
+ const parsedOutput = parseMetaAgentOutput(commandResult.stdout);
186
+ if (parsedOutput === null) {
187
+ return {
188
+ ...fetched,
189
+ metadata: withMetaAgentFinding(fetched.metadata, {
190
+ id: `layer3-meta-agent-parse-error-${resource.id}`,
191
+ severity: "LOW",
192
+ description: `Deep scan meta-agent output was not valid JSON for ${resource.id}`,
193
+ evidence: commandResult.stdout.slice(0, 400),
194
+ }),
195
+ };
196
+ }
197
+ const normalizedOutput = Array.isArray(parsedOutput)
198
+ ? { findings: parsedOutput }
199
+ : parsedOutput;
200
+ return {
201
+ ...fetched,
202
+ metadata: mergeMetaAgentMetadata(fetched.metadata, normalizedOutput),
203
+ };
204
+ });
205
+ const layer3Findings = layer3OutcomesToFindings(outcomes, {
206
+ unicodeAnalysis: input.config.unicode_analysis,
207
+ });
208
+ report = mergeLayer3Findings(report, layer3Findings);
209
+ if (selectedAgent) {
210
+ if (resourcesWithFetchedMetadata === 0) {
211
+ deepScanNotes.push("Selected meta-agent was not executed because no approved resources returned metadata successfully.");
226
212
  }
227
- else if (!supportsToollessLocalTextAnalysis(selectedAgent.metaTool)) {
228
- deepScanNotes.push("Local instruction-file analysis was skipped because the selected agent does not support tool-less analysis.");
213
+ else if (executedMetaAgentCommands === 0) {
214
+ deepScanNotes.push("Selected meta-agent was not executed because meta-agent command execution was not approved.");
229
215
  }
230
216
  else {
231
- // Local instruction files are analyzed as inert text only; referenced URLs stay as evidence, not inputs.
232
- if (!deps.runMetaAgentCommand) {
233
- throw new Error("Meta-agent command runner not configured");
234
- }
235
- const isolatedWorkingDirectory = mkdtempSync(join(tmpdir(), "codegate-local-analysis-"));
236
- let executedLocalAnalyses = 0;
237
- try {
238
- for (const target of localTextTargets) {
239
- const prompt = buildLocalTextAnalysisPrompt({
240
- filePath: target.reportPath,
241
- textContent: buildPromptEvidenceText(target.textContent),
242
- referencedUrls: target.referencedUrls,
243
- });
244
- const command = buildMetaAgentCommand({
245
- tool: selectedAgent.metaTool,
246
- prompt,
247
- workingDirectory: isolatedWorkingDirectory,
248
- binaryPath: selectedAgent.binary,
249
- });
250
- command.timeoutMs = 60_000;
251
- const commandContext = {
252
- localFile: target,
253
- agent: selectedAgent,
254
- command,
255
- };
256
- const approvedCommand = input.options.force ||
257
- (deps.requestMetaAgentCommandConsent
258
- ? await deps.requestMetaAgentCommandConsent(commandContext)
259
- : false);
260
- if (!approvedCommand) {
261
- continue;
262
- }
263
- executedLocalAnalyses += 1;
264
- const commandResult = await deps.runMetaAgentCommand(commandContext);
265
- if (commandResult.code !== 0) {
266
- deepScanNotes.push(`Local instruction-file analysis failed for ${target.reportPath}: ${commandResult.stderr || `exit code: ${commandResult.code}`}`);
267
- continue;
268
- }
269
- const parsedOutput = parseMetaAgentOutput(commandResult.stdout);
270
- if (parsedOutput === null) {
271
- deepScanNotes.push(`Local instruction-file analysis returned invalid JSON for ${target.reportPath}.`);
272
- continue;
273
- }
274
- const normalizedOutput = Array.isArray(parsedOutput)
275
- ? { findings: parsedOutput }
276
- : parsedOutput;
277
- const localFindings = parseLocalTextFindings(target.reportPath, normalizedOutput);
278
- report = mergeLayer3Findings(report, localFindings);
217
+ const suffix = executedMetaAgentCommands === 1 ? "" : "s";
218
+ deepScanNotes.push(`Deep scan meta-agent executed for ${executedMetaAgentCommands} resource${suffix}.`);
219
+ }
220
+ }
221
+ }
222
+ if (localTextTargets.length > 0) {
223
+ if (!selectedAgent) {
224
+ deepScanNotes.push("Local instruction-file analysis skipped because no meta-agent was selected.");
225
+ }
226
+ else if (!supportsToollessLocalTextAnalysis(selectedAgent.metaTool)) {
227
+ deepScanNotes.push("Local instruction-file analysis was skipped because the selected agent does not support tool-less analysis.");
228
+ }
229
+ else {
230
+ // Local instruction files are analyzed as inert text only; referenced URLs stay as evidence, not inputs.
231
+ if (!deps.runMetaAgentCommand) {
232
+ throw new Error("Meta-agent command runner not configured");
233
+ }
234
+ const isolatedWorkingDirectory = mkdtempSync(join(tmpdir(), "codegate-local-analysis-"));
235
+ let executedLocalAnalyses = 0;
236
+ try {
237
+ for (const target of localTextTargets) {
238
+ const prompt = buildLocalTextAnalysisPrompt({
239
+ filePath: target.reportPath,
240
+ textContent: buildPromptEvidenceText(target.textContent),
241
+ referencedUrls: target.referencedUrls,
242
+ });
243
+ const command = buildMetaAgentCommand({
244
+ tool: selectedAgent.metaTool,
245
+ prompt,
246
+ workingDirectory: isolatedWorkingDirectory,
247
+ binaryPath: selectedAgent.binary,
248
+ });
249
+ command.timeoutMs = 60_000;
250
+ const commandContext = {
251
+ localFile: target,
252
+ agent: selectedAgent,
253
+ command,
254
+ };
255
+ const approvedCommand = input.options.force ||
256
+ (deps.requestMetaAgentCommandConsent
257
+ ? await deps.requestMetaAgentCommandConsent(commandContext)
258
+ : false);
259
+ if (!approvedCommand) {
260
+ continue;
279
261
  }
280
- }
281
- finally {
282
- rmSync(isolatedWorkingDirectory, { recursive: true, force: true });
283
- }
284
- if (executedLocalAnalyses > 0) {
285
- const suffix = executedLocalAnalyses === 1 ? "" : "s";
286
- deepScanNotes.push(`Local instruction-file analysis executed for ${executedLocalAnalyses} file${suffix}.`);
262
+ executedLocalAnalyses += 1;
263
+ const commandResult = await deps.runMetaAgentCommand(commandContext);
264
+ if (commandResult.code !== 0) {
265
+ deepScanNotes.push(`Local instruction-file analysis failed for ${target.reportPath}: ${commandResult.stderr || `exit code: ${commandResult.code}`}`);
266
+ continue;
267
+ }
268
+ const parsedOutput = parseMetaAgentOutput(commandResult.stdout);
269
+ if (parsedOutput === null) {
270
+ deepScanNotes.push(`Local instruction-file analysis returned invalid JSON for ${target.reportPath}.`);
271
+ continue;
272
+ }
273
+ const normalizedOutput = Array.isArray(parsedOutput)
274
+ ? { findings: parsedOutput }
275
+ : parsedOutput;
276
+ const localFindings = parseLocalTextFindings(target.reportPath, normalizedOutput);
277
+ report = mergeLayer3Findings(report, localFindings);
287
278
  }
288
279
  }
280
+ finally {
281
+ rmSync(isolatedWorkingDirectory, { recursive: true, force: true });
282
+ }
283
+ if (executedLocalAnalyses > 0) {
284
+ const suffix = executedLocalAnalyses === 1 ? "" : "s";
285
+ deepScanNotes.push(`Local instruction-file analysis executed for ${executedLocalAnalyses} file${suffix}.`);
286
+ }
289
287
  }
290
288
  }
291
289
  }
292
- report = applyConfigPolicy(report, input.config);
293
- report = reorderRequestedTargetFindings(report, input.displayTarget);
290
+ }
291
+ report = applyConfigPolicy(report, input.config);
292
+ report = reorderRequestedTargetFindings(report, input.displayTarget);
293
+ return {
294
+ report,
295
+ deepScanNotes,
296
+ };
297
+ }
298
+ export async function executeScanCommand(input, deps) {
299
+ try {
300
+ const interactivePromptsEnabled = deps.isTTY() && input.options.noTui !== true;
301
+ const { report: analyzedReport, deepScanNotes } = await runScanAnalysis(input, {
302
+ isTTY: deps.isTTY,
303
+ runScan: deps.runScan,
304
+ prepareScanDiscovery: deps.prepareScanDiscovery,
305
+ discoverDeepResources: deps.discoverDeepResources,
306
+ discoverLocalTextTargets: deps.discoverLocalTextTargets,
307
+ requestDeepScanConsent: deps.requestDeepScanConsent,
308
+ requestDeepAgentSelection: deps.requestDeepAgentSelection,
309
+ requestMetaAgentCommandConsent: deps.requestMetaAgentCommandConsent,
310
+ runMetaAgentCommand: deps.runMetaAgentCommand,
311
+ executeDeepResource: deps.executeDeepResource,
312
+ });
313
+ let report = analyzedReport;
294
314
  const remediationRequested = input.options.remediate ||
295
315
  input.options.fixSafe ||
296
316
  input.options.dryRun ||
@@ -378,7 +398,11 @@ export async function executeScanCommand(input, deps) {
378
398
  const targetSummaryNote = input.config.output_format === "terminal"
379
399
  ? summarizeRequestedTargetFindings(report, input.displayTarget)
380
400
  : null;
381
- const scanNotes = targetSummaryNote ? [...deepScanNotes, targetSummaryNote] : deepScanNotes;
401
+ const scanNotes = input.config.output_format === "terminal"
402
+ ? targetSummaryNote
403
+ ? [...deepScanNotes, targetSummaryNote]
404
+ : deepScanNotes
405
+ : [];
382
406
  if (shouldUseTui) {
383
407
  deps.renderTui?.({ view: "dashboard", report, notices: scanNotes });
384
408
  deps.renderTui?.({ view: "summary", report });
@@ -1,9 +1,10 @@
1
1
  import { type CodeGateConfig, type OutputFormat, type ResolveConfigOptions } from "../config.js";
2
2
  import { type ResolvedScanTarget } from "../scan-target.js";
3
- import type { ScanRunnerInput } from "./scan-command.js";
3
+ import { type ScanAnalysisDeps, type ScanRunnerInput } from "./scan-command.js";
4
4
  import type { CodeGateReport } from "../types/report.js";
5
5
  export interface SkillsWrapperRuntimeOptions {
6
6
  force: boolean;
7
+ deep: boolean;
7
8
  noTui: boolean;
8
9
  includeUserScope: boolean;
9
10
  format?: OutputFormat;
@@ -34,6 +35,14 @@ export interface SkillsWrapperDeps {
34
35
  pathExists?: (path: string) => boolean;
35
36
  resolveConfig: (options: ResolveConfigOptions) => CodeGateConfig;
36
37
  runScan: (input: ScanRunnerInput) => Promise<CodeGateReport>;
38
+ prepareScanDiscovery?: ScanAnalysisDeps["prepareScanDiscovery"];
39
+ discoverDeepResources?: ScanAnalysisDeps["discoverDeepResources"];
40
+ discoverLocalTextTargets?: ScanAnalysisDeps["discoverLocalTextTargets"];
41
+ requestDeepScanConsent?: ScanAnalysisDeps["requestDeepScanConsent"];
42
+ requestDeepAgentSelection?: ScanAnalysisDeps["requestDeepAgentSelection"];
43
+ requestMetaAgentCommandConsent?: ScanAnalysisDeps["requestMetaAgentCommandConsent"];
44
+ runMetaAgentCommand?: ScanAnalysisDeps["runMetaAgentCommand"];
45
+ executeDeepResource?: ScanAnalysisDeps["executeDeepResource"];
37
46
  resolveScanTarget?: (input: {
38
47
  rawTarget: string;
39
48
  cwd: string;
@@ -2,10 +2,10 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { resolve } from "node:path";
5
- import { applyConfigPolicy, OUTPUT_FORMATS, } from "../config.js";
5
+ import { OUTPUT_FORMATS, } from "../config.js";
6
6
  import { renderByFormat, summarizeRequestedTargetFindings } from "./scan-command/helpers.js";
7
- import { reorderRequestedTargetFindings } from "../report/requested-target-findings.js";
8
7
  import { resolveScanTarget } from "../scan-target.js";
8
+ import { runScanAnalysis } from "./scan-command.js";
9
9
  function parseWrapperOptionValue(args, index, flag) {
10
10
  const current = args[index] ?? "";
11
11
  const withEquals = `${flag}=`;
@@ -177,6 +177,7 @@ function findAddSubcommandIndex(args, context) {
177
177
  export function parseSkillsInvocation(rawArgs, context) {
178
178
  const wrapper = {
179
179
  force: false,
180
+ deep: false,
180
181
  noTui: false,
181
182
  includeUserScope: false,
182
183
  format: undefined,
@@ -196,6 +197,10 @@ export function parseSkillsInvocation(rawArgs, context) {
196
197
  wrapper.force = true;
197
198
  continue;
198
199
  }
200
+ if (token === "--cg-deep") {
201
+ wrapper.deep = true;
202
+ continue;
203
+ }
199
204
  if (token === "--cg-no-tui") {
200
205
  wrapper.noTui = true;
201
206
  continue;
@@ -320,42 +325,56 @@ export async function executeSkillsWrapper(input, deps) {
320
325
  const config = parsed.wrapper.includeUserScope
321
326
  ? { ...baseConfig, scan_user_scope: true }
322
327
  : baseConfig;
323
- let report = await deps.runScan({
328
+ const { report, deepScanNotes } = await runScanAnalysis({
324
329
  version: input.version,
325
330
  scanTarget: resolvedTarget.scanTarget,
331
+ displayTarget: resolvedTarget.displayTarget,
332
+ explicitCandidates: resolvedTarget.explicitCandidates,
326
333
  config,
327
- flags: {
334
+ options: {
328
335
  noTui,
329
336
  format: parsed.wrapper.format,
330
337
  force: parsed.wrapper.force,
331
338
  includeUserScope: parsed.wrapper.includeUserScope,
332
339
  skill: preferredSkill,
340
+ deep: parsed.wrapper.deep,
333
341
  },
334
- discoveryContext: undefined,
342
+ }, {
343
+ isTTY: deps.isTTY,
344
+ runScan: deps.runScan,
345
+ prepareScanDiscovery: deps.prepareScanDiscovery,
346
+ discoverDeepResources: deps.discoverDeepResources,
347
+ discoverLocalTextTargets: deps.discoverLocalTextTargets,
348
+ requestDeepScanConsent: interactivePromptsEnabled ? deps.requestDeepScanConsent : undefined,
349
+ requestDeepAgentSelection: interactivePromptsEnabled
350
+ ? deps.requestDeepAgentSelection
351
+ : undefined,
352
+ requestMetaAgentCommandConsent: interactivePromptsEnabled
353
+ ? deps.requestMetaAgentCommandConsent
354
+ : undefined,
355
+ runMetaAgentCommand: deps.runMetaAgentCommand,
356
+ executeDeepResource: deps.executeDeepResource,
335
357
  });
336
- if (resolvedTarget.displayTarget && resolvedTarget.displayTarget !== report.scan_target) {
337
- report = {
338
- ...report,
339
- scan_target: resolvedTarget.displayTarget,
340
- };
341
- }
342
- report = applyConfigPolicy(report, config);
343
- report = reorderRequestedTargetFindings(report, resolvedTarget.displayTarget);
344
358
  const shouldUseTui = config.tui.enabled && isTTY && deps.renderTui !== undefined && noTui !== true;
345
359
  const targetSummaryNote = config.output_format === "terminal"
346
360
  ? summarizeRequestedTargetFindings(report, resolvedTarget.displayTarget)
347
361
  : null;
362
+ const scanNotes = config.output_format === "terminal"
363
+ ? targetSummaryNote
364
+ ? [...deepScanNotes, targetSummaryNote]
365
+ : deepScanNotes
366
+ : [];
348
367
  if (shouldUseTui) {
349
368
  deps.renderTui?.({
350
369
  view: "dashboard",
351
370
  report,
352
- notices: targetSummaryNote ? [targetSummaryNote] : undefined,
371
+ notices: scanNotes.length > 0 ? scanNotes : undefined,
353
372
  });
354
373
  deps.renderTui?.({ view: "summary", report });
355
374
  }
356
375
  else {
357
- if (targetSummaryNote) {
358
- deps.stdout(targetSummaryNote);
376
+ for (const note of scanNotes) {
377
+ deps.stdout(note);
359
378
  }
360
379
  deps.stdout(renderByFormat(config.output_format, report));
361
380
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegate-ai",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Pre-flight security scanner for AI coding tool configurations.",
5
5
  "license": "MIT",
6
6
  "type": "module",