codegate-ai 0.1.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.
Files changed (147) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +390 -0
  3. package/dist/cli-prompts.d.ts +6 -0
  4. package/dist/cli-prompts.js +94 -0
  5. package/dist/cli.d.ts +64 -0
  6. package/dist/cli.js +443 -0
  7. package/dist/commands/run-policy.d.ts +27 -0
  8. package/dist/commands/run-policy.js +39 -0
  9. package/dist/commands/scan-command/helpers.d.ts +28 -0
  10. package/dist/commands/scan-command/helpers.js +233 -0
  11. package/dist/commands/scan-command.d.ts +90 -0
  12. package/dist/commands/scan-command.js +403 -0
  13. package/dist/commands/undo.d.ts +5 -0
  14. package/dist/commands/undo.js +14 -0
  15. package/dist/config.d.ts +50 -0
  16. package/dist/config.js +187 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/knowledge-base/claude-code.json +152 -0
  20. package/dist/knowledge-base/cline.json +224 -0
  21. package/dist/knowledge-base/codex.json +162 -0
  22. package/dist/knowledge-base/copilot.json +132 -0
  23. package/dist/knowledge-base/cursor.json +134 -0
  24. package/dist/knowledge-base/gemini-cli.json +112 -0
  25. package/dist/knowledge-base/jetbrains-junie.json +208 -0
  26. package/dist/knowledge-base/kiro.json +102 -0
  27. package/dist/knowledge-base/opencode.json +128 -0
  28. package/dist/knowledge-base/roo-code.json +116 -0
  29. package/dist/knowledge-base/schema.json +77 -0
  30. package/dist/knowledge-base/windsurf.json +80 -0
  31. package/dist/knowledge-base/zed.json +88 -0
  32. package/dist/layer1-discovery/config-parser.d.ts +12 -0
  33. package/dist/layer1-discovery/config-parser.js +52 -0
  34. package/dist/layer1-discovery/file-walker.d.ts +13 -0
  35. package/dist/layer1-discovery/file-walker.js +77 -0
  36. package/dist/layer1-discovery/knowledge-base.d.ts +36 -0
  37. package/dist/layer1-discovery/knowledge-base.js +58 -0
  38. package/dist/layer1-discovery/tool-detector.d.ts +20 -0
  39. package/dist/layer1-discovery/tool-detector.js +138 -0
  40. package/dist/layer2-static/detectors/command-exec.d.ts +11 -0
  41. package/dist/layer2-static/detectors/command-exec.js +343 -0
  42. package/dist/layer2-static/detectors/consent-bypass.d.ts +8 -0
  43. package/dist/layer2-static/detectors/consent-bypass.js +330 -0
  44. package/dist/layer2-static/detectors/env-override.d.ts +8 -0
  45. package/dist/layer2-static/detectors/env-override.js +132 -0
  46. package/dist/layer2-static/detectors/git-hooks.d.ts +11 -0
  47. package/dist/layer2-static/detectors/git-hooks.js +61 -0
  48. package/dist/layer2-static/detectors/ide-settings.d.ts +8 -0
  49. package/dist/layer2-static/detectors/ide-settings.js +66 -0
  50. package/dist/layer2-static/detectors/plugin-manifest.d.ts +9 -0
  51. package/dist/layer2-static/detectors/plugin-manifest.js +1943 -0
  52. package/dist/layer2-static/detectors/rule-file.d.ts +7 -0
  53. package/dist/layer2-static/detectors/rule-file.js +299 -0
  54. package/dist/layer2-static/detectors/symlink.d.ts +9 -0
  55. package/dist/layer2-static/detectors/symlink.js +45 -0
  56. package/dist/layer2-static/engine.d.ts +28 -0
  57. package/dist/layer2-static/engine.js +83 -0
  58. package/dist/layer2-static/evidence.d.ts +12 -0
  59. package/dist/layer2-static/evidence.js +128 -0
  60. package/dist/layer2-static/rule-engine.d.ts +24 -0
  61. package/dist/layer2-static/rule-engine.js +138 -0
  62. package/dist/layer2-static/state/scan-state.d.ts +32 -0
  63. package/dist/layer2-static/state/scan-state.js +296 -0
  64. package/dist/layer3-dynamic/command-builder.d.ts +15 -0
  65. package/dist/layer3-dynamic/command-builder.js +39 -0
  66. package/dist/layer3-dynamic/local-text-analysis.d.ts +19 -0
  67. package/dist/layer3-dynamic/local-text-analysis.js +73 -0
  68. package/dist/layer3-dynamic/meta-agent.d.ts +17 -0
  69. package/dist/layer3-dynamic/meta-agent.js +33 -0
  70. package/dist/layer3-dynamic/prompt-templates/local-text-analysis.md +32 -0
  71. package/dist/layer3-dynamic/prompt-templates/security-analysis.md +13 -0
  72. package/dist/layer3-dynamic/prompt-templates/tool-poisoning.md +15 -0
  73. package/dist/layer3-dynamic/resource-fetcher.d.ts +25 -0
  74. package/dist/layer3-dynamic/resource-fetcher.js +119 -0
  75. package/dist/layer3-dynamic/sandbox.d.ts +13 -0
  76. package/dist/layer3-dynamic/sandbox.js +40 -0
  77. package/dist/layer3-dynamic/tool-description-acquisition.d.ts +22 -0
  78. package/dist/layer3-dynamic/tool-description-acquisition.js +76 -0
  79. package/dist/layer3-dynamic/tool-description-scanner.d.ts +11 -0
  80. package/dist/layer3-dynamic/tool-description-scanner.js +53 -0
  81. package/dist/layer3-dynamic/toxic-flow.d.ts +12 -0
  82. package/dist/layer3-dynamic/toxic-flow.js +57 -0
  83. package/dist/layer4-remediation/actions/quarantine.d.ts +1 -0
  84. package/dist/layer4-remediation/actions/quarantine.js +8 -0
  85. package/dist/layer4-remediation/actions/remove-field.d.ts +5 -0
  86. package/dist/layer4-remediation/actions/remove-field.js +53 -0
  87. package/dist/layer4-remediation/actions/replace-value.d.ts +5 -0
  88. package/dist/layer4-remediation/actions/replace-value.js +26 -0
  89. package/dist/layer4-remediation/actions/strip-unicode.d.ts +5 -0
  90. package/dist/layer4-remediation/actions/strip-unicode.js +8 -0
  91. package/dist/layer4-remediation/backup-manager.d.ts +32 -0
  92. package/dist/layer4-remediation/backup-manager.js +138 -0
  93. package/dist/layer4-remediation/diff-generator.d.ts +6 -0
  94. package/dist/layer4-remediation/diff-generator.js +29 -0
  95. package/dist/layer4-remediation/remediation-runner.d.ts +36 -0
  96. package/dist/layer4-remediation/remediation-runner.js +230 -0
  97. package/dist/layer4-remediation/remediator.d.ts +36 -0
  98. package/dist/layer4-remediation/remediator.js +117 -0
  99. package/dist/path-display.d.ts +1 -0
  100. package/dist/path-display.js +20 -0
  101. package/dist/pipeline.d.ts +34 -0
  102. package/dist/pipeline.js +259 -0
  103. package/dist/report-summary.d.ts +6 -0
  104. package/dist/report-summary.js +48 -0
  105. package/dist/reporter/html.d.ts +2 -0
  106. package/dist/reporter/html.js +103 -0
  107. package/dist/reporter/json.d.ts +2 -0
  108. package/dist/reporter/json.js +3 -0
  109. package/dist/reporter/markdown.d.ts +2 -0
  110. package/dist/reporter/markdown.js +52 -0
  111. package/dist/reporter/sarif.d.ts +2 -0
  112. package/dist/reporter/sarif.js +84 -0
  113. package/dist/reporter/terminal.d.ts +5 -0
  114. package/dist/reporter/terminal.js +94 -0
  115. package/dist/runtime/signal-handlers.d.ts +10 -0
  116. package/dist/runtime/signal-handlers.js +17 -0
  117. package/dist/scan-target/helpers.d.ts +20 -0
  118. package/dist/scan-target/helpers.js +268 -0
  119. package/dist/scan-target/staging.d.ts +5 -0
  120. package/dist/scan-target/staging.js +114 -0
  121. package/dist/scan-target/types.d.ts +18 -0
  122. package/dist/scan-target/types.js +1 -0
  123. package/dist/scan-target.d.ts +3 -0
  124. package/dist/scan-target.js +31 -0
  125. package/dist/scan.d.ts +54 -0
  126. package/dist/scan.js +593 -0
  127. package/dist/tui/app.d.ts +10 -0
  128. package/dist/tui/app.js +21 -0
  129. package/dist/tui/theme.d.ts +8 -0
  130. package/dist/tui/theme.js +7 -0
  131. package/dist/tui/views/dashboard.d.ts +6 -0
  132. package/dist/tui/views/dashboard.js +8 -0
  133. package/dist/tui/views/deep-scan-consent.d.ts +5 -0
  134. package/dist/tui/views/deep-scan-consent.js +6 -0
  135. package/dist/tui/views/progress.d.ts +4 -0
  136. package/dist/tui/views/progress.js +6 -0
  137. package/dist/tui/views/summary.d.ts +5 -0
  138. package/dist/tui/views/summary.js +16 -0
  139. package/dist/types/discovery.d.ts +12 -0
  140. package/dist/types/discovery.js +1 -0
  141. package/dist/types/finding.d.ts +46 -0
  142. package/dist/types/finding.js +15 -0
  143. package/dist/types/report.d.ts +25 -0
  144. package/dist/types/report.js +23 -0
  145. package/dist/wrapper.d.ts +35 -0
  146. package/dist/wrapper.js +220 -0
  147. package/package.json +97 -0
package/dist/cli.js ADDED
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { homedir } from "node:os";
5
+ import { dirname, resolve } from "node:path";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { pathToFileURL } from "node:url";
8
+ import { Command, Option } from "commander";
9
+ import { DEFAULT_CONFIG, OUTPUT_FORMATS, resolveEffectiveConfig, } from "./config.js";
10
+ import { APP_NAME } from "./index.js";
11
+ import { fetchResourceMetadata, } from "./layer3-dynamic/resource-fetcher.js";
12
+ import { acquireToolDescriptions } from "./layer3-dynamic/tool-description-acquisition.js";
13
+ import { runSandboxCommand } from "./layer3-dynamic/sandbox.js";
14
+ import { loadKnowledgeBase } from "./layer1-discovery/knowledge-base.js";
15
+ import { createScanDiscoveryContext, discoverDeepScanResources, discoverDeepScanResourcesFromContext, discoverLocalTextAnalysisTargetsFromContext, runScanEngine, } from "./scan.js";
16
+ import { registerSignalHandlers } from "./runtime/signal-handlers.js";
17
+ import { resolveScanTarget } from "./scan-target.js";
18
+ import { renderTuiApp } from "./tui/app.js";
19
+ import { executeWrapperRun } from "./wrapper.js";
20
+ import { runRemediation as runRemediationWorkflow, } from "./layer4-remediation/remediation-runner.js";
21
+ import { undoLatestSession } from "./commands/undo.js";
22
+ import { executeScanCommand } from "./commands/scan-command.js";
23
+ import { promptDeepAgentSelection, promptDeepScanConsent, promptMetaAgentCommandConsent, promptRemediationConsent, } from "./cli-prompts.js";
24
+ import { resetScanState } from "./layer2-static/state/scan-state.js";
25
+ const require = createRequire(import.meta.url);
26
+ const packageJson = require("../package.json");
27
+ function isNoTuiEnabled(options) {
28
+ return options.noTui === true || options.tui === false;
29
+ }
30
+ function mapAcquisitionFailure(status, error) {
31
+ if (status === "auth_failure" ||
32
+ status === "timeout" ||
33
+ status === "network_error" ||
34
+ status === "command_error") {
35
+ return {
36
+ status,
37
+ attempts: 1,
38
+ elapsedMs: 0,
39
+ error,
40
+ };
41
+ }
42
+ return {
43
+ status: "network_error",
44
+ attempts: 1,
45
+ elapsedMs: 0,
46
+ error: error ?? "tool description acquisition failed",
47
+ };
48
+ }
49
+ async function promptRunWarningConsent(context) {
50
+ const rl = createInterface({
51
+ input: process.stdin,
52
+ output: process.stdout,
53
+ });
54
+ const prompt = [
55
+ `Warning findings detected for ${context.target}.`,
56
+ `Findings: ${context.report.summary.total}`,
57
+ "These findings are below the blocking threshold but still require confirmation to launch.",
58
+ "Proceed with launch? [y/N]: ",
59
+ ].join("\n");
60
+ try {
61
+ const answer = await rl.question(prompt);
62
+ return /^y(es)?$/iu.test(answer.trim());
63
+ }
64
+ finally {
65
+ rl.close();
66
+ }
67
+ }
68
+ const defaultCliDeps = {
69
+ cwd: () => process.cwd(),
70
+ isTTY: () => process.stdout.isTTY === true,
71
+ homeDir: () => homedir(),
72
+ pathExists: (path) => existsSync(path),
73
+ resolveConfig: (options) => resolveEffectiveConfig(options),
74
+ runScan: async (input) => runScanEngine({
75
+ version: input.version,
76
+ scanTarget: input.scanTarget,
77
+ config: input.config,
78
+ scanStatePath: input.config.scan_state_path,
79
+ discoveryContext: input.discoveryContext,
80
+ }),
81
+ prepareScanDiscovery: (scanTarget, config, options) => createScanDiscoveryContext(scanTarget, undefined, {
82
+ includeUserScope: config?.scan_user_scope === true,
83
+ parseSelected: true,
84
+ explicitCandidates: options?.explicitCandidates,
85
+ }),
86
+ resolveScanTarget: (input) => resolveScanTarget(input),
87
+ stdout: (message) => {
88
+ process.stdout.write(`${message}\n`);
89
+ },
90
+ stderr: (message) => {
91
+ process.stderr.write(`${message}\n`);
92
+ },
93
+ writeFile: (path, content) => {
94
+ mkdirSync(dirname(path), { recursive: true });
95
+ writeFileSync(path, content, "utf8");
96
+ },
97
+ setExitCode: (code) => {
98
+ process.exitCode = code;
99
+ },
100
+ renderTui: (props) => {
101
+ renderTuiApp({
102
+ view: props.view,
103
+ report: props.report,
104
+ notices: props.notices,
105
+ });
106
+ },
107
+ runRemediation: (input) => runRemediationWorkflow(input),
108
+ runUndo: (projectRoot) => undoLatestSession({ projectRoot }),
109
+ resetScanState: (path) => resetScanState(path),
110
+ discoverDeepResources: (scanTarget, config, discoveryContext) => discoveryContext
111
+ ? discoverDeepScanResourcesFromContext(discoveryContext)
112
+ : discoverDeepScanResources(scanTarget, undefined, {
113
+ includeUserScope: config?.scan_user_scope === true,
114
+ }),
115
+ discoverLocalTextTargets: (_scanTarget, _config, discoveryContext) => discoveryContext ? discoverLocalTextAnalysisTargetsFromContext(discoveryContext) : [],
116
+ // Keep the default CLI dependency layer as a thin bridge from user-facing commands into the scan engine.
117
+ executeDeepResource: async (resource) => {
118
+ if (resource.request.kind === "http" || resource.request.kind === "sse") {
119
+ const acquisition = await acquireToolDescriptions({
120
+ serverId: resource.id,
121
+ transport: resource.request.kind,
122
+ url: resource.request.locator,
123
+ });
124
+ if (acquisition.status === "ok") {
125
+ return {
126
+ status: "ok",
127
+ attempts: 1,
128
+ elapsedMs: 0,
129
+ metadata: {
130
+ tools: acquisition.tools,
131
+ },
132
+ };
133
+ }
134
+ return mapAcquisitionFailure(acquisition.status, acquisition.error);
135
+ }
136
+ return fetchResourceMetadata(resource.request);
137
+ },
138
+ };
139
+ function addScanCommand(program, version, deps) {
140
+ program
141
+ .command("scan [dir]")
142
+ .description("Scan a directory, file, or safely staged artifact target for AI tool config risks")
143
+ .option("--deep", "enable Layer 3 dynamic analysis")
144
+ .option("--remediate", "enter remediation mode after scan")
145
+ .option("--fix-safe", "auto-fix unambiguous critical findings")
146
+ .option("--dry-run", "show proposed fixes but write nothing")
147
+ .option("--patch", "generate a patch file for review")
148
+ .option("--no-tui", "disable TUI and interactive prompts")
149
+ .addOption(new Option("--format <type>", "output format")
150
+ .choices([...OUTPUT_FORMATS])
151
+ .argParser((value) => value))
152
+ .option("--output <path>", "write report to file")
153
+ .option("--verbose", "show extended output")
154
+ .option("--config <path>", "use a specific global config file")
155
+ .option("--force", "skip interactive confirmations")
156
+ .option("--include-user-scope", "include user/home AI tool config paths in scan")
157
+ .option("--reset-state", "clear persisted scan-state history and exit")
158
+ .action(async (dir, options) => {
159
+ const rawTarget = dir ?? ".";
160
+ const noTui = isNoTuiEnabled(options);
161
+ const promptCallbacksEnabled = noTui !== true;
162
+ const cliConfig = {
163
+ format: options.format,
164
+ configPath: options.config,
165
+ noTui: noTui || !deps.isTTY(),
166
+ };
167
+ let resolvedTarget;
168
+ try {
169
+ const resolveTarget = deps.resolveScanTarget ??
170
+ ((input) => resolveScanTarget(input));
171
+ resolvedTarget = await resolveTarget({
172
+ rawTarget,
173
+ cwd: deps.cwd(),
174
+ });
175
+ const scanTarget = resolvedTarget.scanTarget;
176
+ const baseConfig = deps.resolveConfig({
177
+ scanTarget,
178
+ cli: cliConfig,
179
+ });
180
+ const config = options.includeUserScope === true
181
+ ? {
182
+ ...baseConfig,
183
+ scan_user_scope: true,
184
+ }
185
+ : baseConfig;
186
+ if (options.resetState) {
187
+ const reset = deps.resetScanState ?? ((path) => resetScanState(path));
188
+ await reset(config.scan_state_path);
189
+ deps.stdout("Scan state reset.");
190
+ deps.setExitCode(0);
191
+ return;
192
+ }
193
+ const interactivePromptsEnabled = deps.isTTY() && noTui !== true;
194
+ await executeScanCommand({
195
+ version,
196
+ cwd: resolve(deps.cwd()),
197
+ scanTarget,
198
+ displayTarget: resolvedTarget.displayTarget,
199
+ explicitCandidates: resolvedTarget.explicitCandidates,
200
+ config,
201
+ options: {
202
+ ...options,
203
+ noTui,
204
+ },
205
+ }, {
206
+ isTTY: deps.isTTY,
207
+ runScan: deps.runScan,
208
+ prepareScanDiscovery: deps.prepareScanDiscovery,
209
+ discoverDeepResources: deps.discoverDeepResources,
210
+ discoverLocalTextTargets: deps.discoverLocalTextTargets,
211
+ requestDeepScanConsent: promptCallbacksEnabled
212
+ ? (deps.requestDeepScanConsent ??
213
+ (interactivePromptsEnabled ? promptDeepScanConsent : undefined))
214
+ : undefined,
215
+ requestDeepAgentSelection: promptCallbacksEnabled
216
+ ? (deps.requestDeepAgentSelection ??
217
+ (interactivePromptsEnabled ? promptDeepAgentSelection : undefined))
218
+ : undefined,
219
+ requestMetaAgentCommandConsent: promptCallbacksEnabled
220
+ ? (deps.requestMetaAgentCommandConsent ??
221
+ (interactivePromptsEnabled ? promptMetaAgentCommandConsent : undefined))
222
+ : undefined,
223
+ executeDeepResource: deps.executeDeepResource,
224
+ runMetaAgentCommand: deps.runMetaAgentCommand ??
225
+ (async (context) => {
226
+ const commandResult = await runSandboxCommand({
227
+ command: context.command.command,
228
+ args: context.command.args,
229
+ cwd: context.command.cwd,
230
+ timeoutMs: context.command.timeoutMs,
231
+ });
232
+ return {
233
+ command: context.command,
234
+ code: commandResult.code,
235
+ stdout: commandResult.stdout,
236
+ stderr: commandResult.stderr,
237
+ };
238
+ }),
239
+ requestRemediationConsent: promptCallbacksEnabled
240
+ ? (deps.requestRemediationConsent ??
241
+ (interactivePromptsEnabled ? promptRemediationConsent : undefined))
242
+ : undefined,
243
+ runRemediation: deps.runRemediation,
244
+ stdout: deps.stdout,
245
+ stderr: deps.stderr,
246
+ writeFile: deps.writeFile,
247
+ setExitCode: deps.setExitCode,
248
+ renderTui: deps.renderTui,
249
+ });
250
+ }
251
+ catch (error) {
252
+ const message = error instanceof Error ? error.message : String(error);
253
+ deps.stderr(`Scan failed: ${message}`);
254
+ deps.setExitCode(3);
255
+ }
256
+ finally {
257
+ if (resolvedTarget?.cleanup) {
258
+ try {
259
+ await resolvedTarget.cleanup();
260
+ }
261
+ catch (error) {
262
+ const message = error instanceof Error ? error.message : String(error);
263
+ deps.stderr(`Scan target cleanup failed: ${message}`);
264
+ }
265
+ }
266
+ }
267
+ });
268
+ }
269
+ function addRunCommand(program, version, deps) {
270
+ program
271
+ .command("run <tool>")
272
+ .description("Scan current directory, then launch an AI coding tool")
273
+ .option("--no-tui", "disable TUI and interactive prompts")
274
+ .option("--config <path>", "use a specific global config file")
275
+ .option("--force", "skip interactive confirmations")
276
+ .action(async (tool, options) => {
277
+ const cwd = resolve(deps.cwd());
278
+ const noTui = isNoTuiEnabled(options);
279
+ const cliConfig = {
280
+ configPath: options.config,
281
+ noTui: noTui || !deps.isTTY(),
282
+ };
283
+ try {
284
+ const config = deps.resolveConfig({
285
+ scanTarget: cwd,
286
+ cli: cliConfig,
287
+ });
288
+ const runWrapper = deps.runWrapper ??
289
+ ((input) => {
290
+ const shouldUseTui = config.tui.enabled && deps.isTTY() && deps.renderTui !== undefined;
291
+ return executeWrapperRun({
292
+ target: input.target,
293
+ cwd: input.cwd,
294
+ version: input.version,
295
+ config: input.config,
296
+ force: input.force,
297
+ onReport: shouldUseTui
298
+ ? (report) => {
299
+ deps.renderTui?.({ view: "dashboard", report });
300
+ deps.renderTui?.({ view: "summary", report });
301
+ }
302
+ : undefined,
303
+ requestWarningProceed: input.requestWarningProceed,
304
+ });
305
+ });
306
+ await runWrapper({
307
+ target: tool,
308
+ cwd,
309
+ version,
310
+ config,
311
+ force: options.force,
312
+ requestWarningProceed: options.force || !deps.isTTY() || noTui === true
313
+ ? undefined
314
+ : async (report) => {
315
+ const requestConsent = deps.requestRunWarningConsent ??
316
+ ((context) => promptRunWarningConsent(context));
317
+ return await requestConsent({ target: tool, report });
318
+ },
319
+ });
320
+ }
321
+ catch (error) {
322
+ const message = error instanceof Error ? error.message : String(error);
323
+ deps.stderr(`Run failed: ${message}`);
324
+ deps.setExitCode(3);
325
+ }
326
+ });
327
+ }
328
+ function addUndoCommand(program, deps) {
329
+ program
330
+ .command("undo [dir]")
331
+ .description("Restore the most recent remediation backup session")
332
+ .action((dir) => {
333
+ const projectRoot = resolve(deps.cwd(), dir ?? ".");
334
+ try {
335
+ const runUndo = deps.runUndo ?? ((target) => undoLatestSession({ projectRoot: target }));
336
+ const result = runUndo(projectRoot);
337
+ deps.stdout(`Restored ${result.restoredFiles} file(s) from backup session ${result.sessionId}.`);
338
+ deps.setExitCode(0);
339
+ }
340
+ catch (error) {
341
+ const message = error instanceof Error ? error.message : String(error);
342
+ deps.stderr(`Undo failed: ${message}`);
343
+ deps.setExitCode(3);
344
+ }
345
+ });
346
+ }
347
+ function addInitCommand(program, deps) {
348
+ program
349
+ .command("init")
350
+ .description("Create ~/.codegate/config.json with defaults")
351
+ .option("--path <path>", "write to a custom config path")
352
+ .option("--force", "overwrite existing config file")
353
+ .action((options) => {
354
+ try {
355
+ const home = deps.homeDir?.() ?? homedir();
356
+ const targetPath = options.path
357
+ ? resolve(deps.cwd(), options.path)
358
+ : resolve(home, ".codegate", "config.json");
359
+ const pathExists = deps.pathExists ?? ((path) => existsSync(path));
360
+ if (pathExists(targetPath) && !options.force) {
361
+ deps.stderr(`Config already exists: ${targetPath}. Use --force to overwrite.`);
362
+ deps.setExitCode(3);
363
+ return;
364
+ }
365
+ deps.writeFile(targetPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`);
366
+ deps.stdout(`Created config: ${targetPath}`);
367
+ deps.setExitCode(0);
368
+ }
369
+ catch (error) {
370
+ const message = error instanceof Error ? error.message : String(error);
371
+ deps.stderr(`Init failed: ${message}`);
372
+ deps.setExitCode(3);
373
+ }
374
+ });
375
+ }
376
+ function addUpdateCommands(program, deps) {
377
+ const guidance = [
378
+ "Updates are bundled with CodeGate releases in v1/v2.",
379
+ "Run: npm update -g codegate-ai",
380
+ "Or run latest directly: npx codegate-ai@latest scan .",
381
+ ];
382
+ program
383
+ .command("update-kb")
384
+ .description("Check for newer knowledge-base content")
385
+ .action(() => {
386
+ deps.stdout("update-kb:");
387
+ for (const line of guidance) {
388
+ deps.stdout(line);
389
+ }
390
+ deps.setExitCode(0);
391
+ });
392
+ program
393
+ .command("update-rules")
394
+ .description("Check for newer rules content")
395
+ .action(() => {
396
+ deps.stdout("update-rules:");
397
+ for (const line of guidance) {
398
+ deps.stdout(line);
399
+ }
400
+ deps.setExitCode(0);
401
+ });
402
+ }
403
+ function resolveKnowledgeBaseVersion() {
404
+ try {
405
+ return loadKnowledgeBase().schemaVersion;
406
+ }
407
+ catch {
408
+ return "unknown";
409
+ }
410
+ }
411
+ export function createCli(version = packageJson.version ?? "0.0.0-dev", deps = defaultCliDeps) {
412
+ const program = new Command();
413
+ const versionDisplay = `${version} (kb ${resolveKnowledgeBaseVersion()})`;
414
+ program
415
+ .name(APP_NAME)
416
+ .description("Pre-flight security scanner for AI coding tool configurations.")
417
+ .version(versionDisplay)
418
+ .helpOption("-h, --help", "display help for command");
419
+ addScanCommand(program, version, deps);
420
+ addRunCommand(program, version, deps);
421
+ addUndoCommand(program, deps);
422
+ addInitCommand(program, deps);
423
+ addUpdateCommands(program, deps);
424
+ return program;
425
+ }
426
+ export async function runCli(argv = process.argv, version = packageJson.version ?? "0.0.0-dev", deps = defaultCliDeps) {
427
+ const cli = createCli(version, deps);
428
+ const cleanupSignals = registerSignalHandlers({
429
+ onSignal: (signal) => {
430
+ process.exitCode = signal === "SIGINT" ? 130 : 143;
431
+ },
432
+ });
433
+ try {
434
+ await cli.parseAsync(argv);
435
+ }
436
+ finally {
437
+ cleanupSignals();
438
+ }
439
+ }
440
+ const invokedPath = process.argv[1] ? pathToFileURL(process.argv[1]).href : null;
441
+ if (invokedPath && import.meta.url === invokedPath) {
442
+ await runCli();
443
+ }
@@ -0,0 +1,27 @@
1
+ import type { CodeGateReport } from "../types/report.js";
2
+ interface BlockDecision {
3
+ kind: "block";
4
+ exitCode: number;
5
+ message: string;
6
+ stream: "stdout" | "stderr";
7
+ }
8
+ interface PromptDecision {
9
+ kind: "prompt";
10
+ }
11
+ interface AllowDecision {
12
+ kind: "allow";
13
+ }
14
+ export type RunPolicyDecision = BlockDecision | PromptDecision | AllowDecision;
15
+ export interface PostScanGuardInput {
16
+ report: CodeGateReport;
17
+ scanSurfaceChanged: boolean;
18
+ force: boolean;
19
+ autoProceedBelowThreshold: boolean;
20
+ insideTrustedDirectory: boolean;
21
+ }
22
+ export interface PreLaunchGuardInput {
23
+ launchSurfaceChanged: boolean;
24
+ }
25
+ export declare function evaluatePostScanGuard(input: PostScanGuardInput): RunPolicyDecision;
26
+ export declare function evaluatePreLaunchGuard(input: PreLaunchGuardInput): RunPolicyDecision;
27
+ export {};
@@ -0,0 +1,39 @@
1
+ const RESCAN_MESSAGE = "Config files changed after scan. Re-run `codegate scan` before launch.";
2
+ export function evaluatePostScanGuard(input) {
3
+ if (input.report.summary.exit_code === 2) {
4
+ return {
5
+ kind: "block",
6
+ exitCode: 2,
7
+ message: "Dangerous findings detected. Resolve issues before launching tool.",
8
+ stream: "stderr",
9
+ };
10
+ }
11
+ if (input.scanSurfaceChanged) {
12
+ return {
13
+ kind: "block",
14
+ exitCode: 3,
15
+ message: RESCAN_MESSAGE,
16
+ stream: "stderr",
17
+ };
18
+ }
19
+ const needsWarningProceed = input.report.summary.exit_code === 1 &&
20
+ input.report.findings.length > 0 &&
21
+ input.force !== true &&
22
+ input.autoProceedBelowThreshold !== true &&
23
+ !input.insideTrustedDirectory;
24
+ if (needsWarningProceed) {
25
+ return { kind: "prompt" };
26
+ }
27
+ return { kind: "allow" };
28
+ }
29
+ export function evaluatePreLaunchGuard(input) {
30
+ if (input.launchSurfaceChanged) {
31
+ return {
32
+ kind: "block",
33
+ exitCode: 3,
34
+ message: RESCAN_MESSAGE,
35
+ stream: "stderr",
36
+ };
37
+ }
38
+ return { kind: "allow" };
39
+ }
@@ -0,0 +1,28 @@
1
+ import type { OutputFormat } from "../../config.js";
2
+ import type { RemediationRunnerResult } from "../../layer4-remediation/remediation-runner.js";
3
+ import type { CodeGateReport } from "../../types/report.js";
4
+ export declare function metadataSummary(metadata: unknown): string;
5
+ export declare function parseMetaAgentOutput(stdout: string): unknown | null;
6
+ export declare function withMetaAgentFinding(metadata: unknown, finding: {
7
+ id: string;
8
+ severity: "INFO" | "LOW";
9
+ description: string;
10
+ evidence?: string;
11
+ }): unknown;
12
+ export declare function mergeMetaAgentMetadata(baseMetadata: unknown, agentMetadata: unknown): unknown;
13
+ export declare function noEligibleDeepResourceNotes(): string[];
14
+ export declare function parseLocalTextFindings(filePath: string, metadata: unknown): CodeGateReport["findings"];
15
+ export declare function remediationSummaryLines(input: {
16
+ scanTarget: string;
17
+ options: {
18
+ fixSafe?: boolean;
19
+ remediate?: boolean;
20
+ dryRun?: boolean;
21
+ patch?: boolean;
22
+ };
23
+ before: CodeGateReport;
24
+ result: RemediationRunnerResult;
25
+ }): string[];
26
+ export declare function renderByFormat(format: OutputFormat, report: CodeGateReport, options?: {
27
+ verbose?: boolean;
28
+ }): string;