@bouncesecurity/aghast 0.4.4 → 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 +8 -3
- package/config/pricing.json +42 -0
- package/config/prompts/false-positive-validation.md +1 -0
- package/config/prompts/general-vuln-discovery.md +8 -3
- package/config/prompts/generic-instructions.md +3 -2
- package/dist/budget.d.ts +62 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +137 -0
- package/dist/budget.js.map +1 -0
- package/dist/build-config.d.ts +15 -0
- package/dist/build-config.d.ts.map +1 -0
- package/dist/build-config.js +568 -0
- package/dist/build-config.js.map +1 -0
- package/dist/check-library.d.ts +1 -0
- package/dist/check-library.d.ts.map +1 -1
- package/dist/check-library.js +26 -7
- package/dist/check-library.js.map +1 -1
- package/dist/check-types.d.ts +1 -1
- package/dist/check-types.d.ts.map +1 -1
- package/dist/claude-code-provider.d.ts +6 -6
- package/dist/claude-code-provider.d.ts.map +1 -1
- package/dist/claude-code-provider.js +151 -66
- package/dist/claude-code-provider.js.map +1 -1
- package/dist/cli.js +19 -3
- package/dist/cli.js.map +1 -1
- package/dist/colors.js +4 -4
- package/dist/colors.js.map +1 -1
- package/dist/cost-calculator.d.ts +80 -0
- package/dist/cost-calculator.d.ts.map +1 -0
- package/dist/cost-calculator.js +226 -0
- package/dist/cost-calculator.js.map +1 -0
- package/dist/defaults.d.ts +21 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +21 -0
- package/dist/defaults.js.map +1 -0
- package/dist/discoveries/openant-discovery.d.ts.map +1 -1
- package/dist/discoveries/openant-discovery.js +3 -2
- package/dist/discoveries/openant-discovery.js.map +1 -1
- package/dist/discoveries/sarif-discovery.d.ts.map +1 -1
- package/dist/discoveries/sarif-discovery.js +2 -1
- package/dist/discoveries/sarif-discovery.js.map +1 -1
- package/dist/discoveries/semgrep-discovery.d.ts.map +1 -1
- package/dist/discoveries/semgrep-discovery.js +11 -2
- package/dist/discoveries/semgrep-discovery.js.map +1 -1
- package/dist/discovery.d.ts +8 -2
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +8 -0
- package/dist/discovery.js.map +1 -1
- package/dist/error-codes.d.ts +3 -1
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +10 -3
- package/dist/error-codes.js.map +1 -1
- package/dist/formatters/types.d.ts +1 -1
- package/dist/formatters/types.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +257 -82
- package/dist/index.js.map +1 -1
- package/dist/logging.d.ts +1 -1
- package/dist/logging.d.ts.map +1 -1
- package/dist/logging.js +50 -31
- package/dist/logging.js.map +1 -1
- package/dist/{mock-ai-provider.d.ts → mock-agent-provider.d.ts} +10 -7
- package/dist/mock-agent-provider.d.ts.map +1 -0
- package/dist/{mock-ai-provider.js → mock-agent-provider.js} +15 -8
- package/dist/mock-agent-provider.js.map +1 -0
- package/dist/new-check.js +2 -2
- package/dist/new-check.js.map +1 -1
- package/dist/opencode-provider.d.ts +63 -0
- package/dist/opencode-provider.d.ts.map +1 -0
- package/dist/opencode-provider.js +614 -0
- package/dist/opencode-provider.js.map +1 -0
- package/dist/prompt-template.d.ts.map +1 -1
- package/dist/prompt-template.js +2 -1
- package/dist/prompt-template.js.map +1 -1
- package/dist/provider-registry.d.ts +6 -6
- package/dist/provider-registry.d.ts.map +1 -1
- package/dist/provider-registry.js +6 -4
- package/dist/provider-registry.js.map +1 -1
- package/dist/provider-utils.d.ts +52 -0
- package/dist/provider-utils.d.ts.map +1 -0
- package/dist/provider-utils.js +40 -0
- package/dist/provider-utils.js.map +1 -0
- package/dist/response-parser.d.ts +8 -6
- package/dist/response-parser.d.ts.map +1 -1
- package/dist/response-parser.js +8 -6
- package/dist/response-parser.js.map +1 -1
- package/dist/runtime-config.d.ts +4 -4
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +107 -8
- package/dist/runtime-config.js.map +1 -1
- package/dist/scan-history.d.ts +82 -0
- package/dist/scan-history.d.ts.map +1 -0
- package/dist/scan-history.js +127 -0
- package/dist/scan-history.js.map +1 -0
- package/dist/scan-runner.d.ts +67 -4
- package/dist/scan-runner.d.ts.map +1 -1
- package/dist/scan-runner.js +267 -51
- package/dist/scan-runner.js.map +1 -1
- package/dist/stats.d.ts +11 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +197 -0
- package/dist/stats.js.map +1 -0
- package/dist/types.d.ts +74 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -3
- package/dist/types.js.map +1 -1
- package/package.json +6 -4
- package/dist/mock-ai-provider.d.ts.map +0 -1
- package/dist/mock-ai-provider.js.map +0 -1
package/dist/discovery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA6D5D,6BAA6B;AAE7B,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA2B,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAA0B;IAC1D,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,WAAW,CAAC,WAAW,CAAC,KAAK,EAAE,4BAA4B,IAAI,iBAAiB,SAAS,IAAI,mBAAmB,EAAE,CAAC,CACpH,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC"}
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA6D5D,6BAA6B;AAE7B,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA2B,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAA0B;IAC1D,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,WAAW,CAAC,WAAW,CAAC,KAAK,EAAE,4BAA4B,IAAI,iBAAiB,SAAS,IAAI,mBAAmB,EAAE,CAAC,CACpH,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC"}
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Numbering scheme:
|
|
5
5
|
* E1xxx — CLI parsing (argument/flag/command errors)
|
|
6
6
|
* E2xxx — Configuration (config dir, checks, runtime config)
|
|
7
|
-
* E3xxx —
|
|
7
|
+
* E3xxx — Agent provider
|
|
8
8
|
* E4xxx — Repository/target validation
|
|
9
9
|
* E5xxx — Semgrep
|
|
10
10
|
* E6xxx — OpenAnt
|
|
@@ -26,10 +26,12 @@ export declare const ERROR_CODES: {
|
|
|
26
26
|
readonly E3001: ErrorCode;
|
|
27
27
|
readonly E3002: ErrorCode;
|
|
28
28
|
readonly E3003: ErrorCode;
|
|
29
|
+
readonly E3004: ErrorCode;
|
|
29
30
|
readonly E4001: ErrorCode;
|
|
30
31
|
readonly E5001: ErrorCode;
|
|
31
32
|
readonly E6001: ErrorCode;
|
|
32
33
|
readonly E6002: ErrorCode;
|
|
34
|
+
readonly E7001: ErrorCode;
|
|
33
35
|
readonly E9001: ErrorCode;
|
|
34
36
|
};
|
|
35
37
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,eAAO,MAAM,WAAW
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;CAsCd,CAAC;AAEX;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAWzE"}
|
package/dist/error-codes.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Numbering scheme:
|
|
5
5
|
* E1xxx — CLI parsing (argument/flag/command errors)
|
|
6
6
|
* E2xxx — Configuration (config dir, checks, runtime config)
|
|
7
|
-
* E3xxx —
|
|
7
|
+
* E3xxx — Agent provider
|
|
8
8
|
* E4xxx — Repository/target validation
|
|
9
9
|
* E5xxx — Semgrep
|
|
10
10
|
* E6xxx — OpenAnt
|
|
@@ -24,10 +24,15 @@ export const ERROR_CODES = {
|
|
|
24
24
|
E2003: ec('E2003', 'No checks found'),
|
|
25
25
|
E2004: ec('E2004', 'Invalid check definition'),
|
|
26
26
|
E2005: ec('E2005', 'Configuration error'),
|
|
27
|
-
// E3xxx —
|
|
27
|
+
// E3xxx — Agent provider
|
|
28
28
|
E3001: ec('E3001', 'API key missing'),
|
|
29
|
-
E3002: ec('E3002', 'Unknown
|
|
29
|
+
E3002: ec('E3002', 'Unknown agent provider'),
|
|
30
|
+
// E3003 retains "AI" intentionally: it refers to the file containing the
|
|
31
|
+
// mocked AI/LLM response body, not the agent harness. Same rationale as
|
|
32
|
+
// AGHAST_MOCK_AI / AGHAST_AI_MODEL — the model and its output are AI
|
|
33
|
+
// concerns; only the provider/harness layer was renamed to "agent".
|
|
30
34
|
E3003: ec('E3003', 'Mock AI response file not found'),
|
|
35
|
+
E3004: ec('E3004', 'OpenCode not installed'),
|
|
31
36
|
// E4xxx — Repository/target validation
|
|
32
37
|
E4001: ec('E4001', 'Repository path not found'),
|
|
33
38
|
// E5xxx — Semgrep
|
|
@@ -35,6 +40,8 @@ export const ERROR_CODES = {
|
|
|
35
40
|
// E6xxx — OpenAnt
|
|
36
41
|
E6001: ec('E6001', 'OpenAnt not installed'),
|
|
37
42
|
E6002: ec('E6002', 'OpenAnt execution failed'),
|
|
43
|
+
// E7xxx — Budget / cost controls
|
|
44
|
+
E7001: ec('E7001', 'Budget limit exceeded'),
|
|
38
45
|
// E9xxx — Internal
|
|
39
46
|
E9001: ec('E9001', 'Fatal internal error'),
|
|
40
47
|
};
|
package/dist/error-codes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,SAAS,EAAE,CAAC,IAAY,EAAE,KAAa;IACrC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,sBAAsB;IACtB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,wBAAwB;IACxB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,oCAAoC,CAAC;IACxD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAC9C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC;IAEzC,
|
|
1
|
+
{"version":3,"file":"error-codes.js","sourceRoot":"","sources":["../src/error-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,SAAS,EAAE,CAAC,IAAY,EAAE,KAAa;IACrC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,sBAAsB;IACtB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,wBAAwB;IACxB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC;IACpD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,oCAAoC,CAAC;IACxD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAC9C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,qBAAqB,CAAC;IAEzC,yBAAyB;IACzB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC;IACrC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC5C,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,oEAAoE;IACpE,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACrD,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAE5C,uCAAuC;IACvC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,2BAA2B,CAAC;IAE/C,kBAAkB;IAClB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAE3C,kBAAkB;IAClB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC3C,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAE9C,iCAAiC;IACjC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAE3C,mBAAmB;IACnB,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,sBAAsB,CAAC;CAClC,CAAC;AAEX;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAoB,EAAE,OAAe;IAC/D,OAAO,UAAU,SAAS,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,OAAe;IAC/D,MAAM,KAAK,GAAG,kBAAkB,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,kBAAkB,CAAC,gBAAgB,OAAO,mBAAmB,OAAO,EAAE,CAAC,CAAC;IACrF,MAAM,GAAG,GAAG,6DAA6D,KAAK,SAAS,IAAI,aAAa,CAAC;IACzG,OAAO;QACL,uBAAuB,WAAW,CAAC,KAAK,CAAC,IAAI,MAAM,OAAO,EAAE;QAC5D,YAAY,OAAO,EAAE;QACrB,EAAE;QACF,0CAA0C;QAC1C,KAAK,GAAG,EAAE;KACX,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Output formatter interface for scan results.
|
|
3
|
-
* Follows the
|
|
3
|
+
* Follows the AgentProvider interface pattern from src/types.ts.
|
|
4
4
|
*/
|
|
5
5
|
import type { ScanResults } from '../types.js';
|
|
6
6
|
export interface OutputFormatter {
|
package/dist/formatters/types.js
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,eAAe,CAAC;AA4WvB,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuX3D"}
|
package/dist/index.js
CHANGED
|
@@ -5,18 +5,21 @@
|
|
|
5
5
|
import 'dotenv/config';
|
|
6
6
|
import { readFile, writeFile, stat, mkdir, readdir } from 'node:fs/promises';
|
|
7
7
|
import { resolve, dirname } from 'node:path';
|
|
8
|
-
import {
|
|
8
|
+
import { runMultiScanWithCost } from './scan-runner.js';
|
|
9
|
+
import { loadDefaultPricing, mergePricing, formatCostSourceLabel } from './cost-calculator.js';
|
|
10
|
+
import { saveScanRecord, queryScanHistory } from './scan-history.js';
|
|
9
11
|
import { createProviderByName, getProviderNames, DEFAULT_PROVIDER_NAME } from './provider-registry.js';
|
|
10
12
|
import { loadCheckRegistry, discoverCheckFolders, resolveChecks, filterChecksForRepository, validateCheck, loadCheckDetails, } from './check-library.js';
|
|
11
13
|
import { analyzeRepository } from './repository-analyzer.js';
|
|
12
14
|
import { loadRuntimeConfig } from './runtime-config.js';
|
|
13
15
|
import { logProgress, logDebug, setLogLevel, createTimer, isValidLogLevel, initFileHandler, closeAllHandlers, getAvailableLogTypes } from './logging.js';
|
|
14
|
-
import { MOCK_MODEL_NAME,
|
|
16
|
+
import { MOCK_MODEL_NAME, DEFAULT_MODEL } from './types.js';
|
|
15
17
|
import { getFormatter } from './formatters/index.js';
|
|
16
18
|
import { verifySemgrepInstalled } from './semgrep-runner.js';
|
|
17
19
|
import { verifyOpenAntInstalled } from './openant-runner.js';
|
|
18
|
-
import {
|
|
20
|
+
import { MockAgentProvider } from './mock-agent-provider.js';
|
|
19
21
|
import { ERROR_CODES, formatError, formatFatalError } from './error-codes.js';
|
|
22
|
+
import { DEFAULT_OUTPUT_FORMAT, DEFAULT_LOG_LEVEL, DEFAULT_LOG_TYPE } from './defaults.js';
|
|
20
23
|
import { colorStatus } from './colors.js';
|
|
21
24
|
import { getCheckType } from './check-types.js';
|
|
22
25
|
import { createRequire } from 'node:module';
|
|
@@ -33,7 +36,31 @@ async function createMockProvider() {
|
|
|
33
36
|
throw new Error(`Failed to read AGHAST_MOCK_AI response file: ${mockAiValue}`, { cause: err });
|
|
34
37
|
}
|
|
35
38
|
}
|
|
36
|
-
|
|
39
|
+
// Optional mock token usage for testing cost/budget pipelines.
|
|
40
|
+
// Format: AGHAST_MOCK_TOKENS="<input>,<output>" (e.g. "1000,500")
|
|
41
|
+
// When AGHAST_LOCAL_CLAUDE=true, inject a mock reportedCost so that the
|
|
42
|
+
// coveredBySubscription path (banner "equivalent", label) is exercisable in tests.
|
|
43
|
+
let tokenUsage;
|
|
44
|
+
const mockTokensRaw = process.env.AGHAST_MOCK_TOKENS;
|
|
45
|
+
if (mockTokensRaw) {
|
|
46
|
+
const parts = mockTokensRaw.split(',').map((s) => Number(s.trim()));
|
|
47
|
+
if (parts.length === 2 && parts.every((n) => Number.isFinite(n) && n >= 0)) {
|
|
48
|
+
const useLocalClaude = process.env.AGHAST_LOCAL_CLAUDE === 'true';
|
|
49
|
+
tokenUsage = {
|
|
50
|
+
inputTokens: parts[0],
|
|
51
|
+
outputTokens: parts[1],
|
|
52
|
+
totalTokens: parts[0] + parts[1],
|
|
53
|
+
...(useLocalClaude ? {
|
|
54
|
+
reportedCost: {
|
|
55
|
+
amountUsd: (parts[0] + parts[1]) / 1_000_000,
|
|
56
|
+
source: 'claude-agent-sdk',
|
|
57
|
+
coveredBySubscription: true,
|
|
58
|
+
},
|
|
59
|
+
} : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const provider = new MockAgentProvider({ rawResponse, tokenUsage });
|
|
37
64
|
await provider.initialize({});
|
|
38
65
|
return provider;
|
|
39
66
|
}
|
|
@@ -60,8 +87,12 @@ General options:
|
|
|
60
87
|
--log-type <type> Log file handler type (default: file).
|
|
61
88
|
Available types: file
|
|
62
89
|
--model <model> AI model override (e.g. claude-sonnet-4-20250514)
|
|
63
|
-
--
|
|
90
|
+
--agent-provider <name> Agent provider name (default: claude-code)
|
|
64
91
|
--generic-prompt <file> Generic prompt template filename in prompts/ dir
|
|
92
|
+
--budget-limit-cost <usd> Abort the scan when accumulated cost exceeds this
|
|
93
|
+
USD value. Warns at 80%, aborts at 100%
|
|
94
|
+
--budget-limit-tokens <n> Abort the scan when accumulated tokens exceed n.
|
|
95
|
+
Warns at 80%, aborts at 100%
|
|
65
96
|
|
|
66
97
|
Environment variables:
|
|
67
98
|
ANTHROPIC_API_KEY API key for Claude (required for AI-based checks)
|
|
@@ -98,8 +129,10 @@ function parseArgs(args) {
|
|
|
98
129
|
let logType;
|
|
99
130
|
let runtimeConfigPath;
|
|
100
131
|
let model;
|
|
101
|
-
let
|
|
132
|
+
let agentProvider;
|
|
102
133
|
let genericPrompt;
|
|
134
|
+
let budgetLimitCost;
|
|
135
|
+
let budgetLimitTokens;
|
|
103
136
|
for (let i = startIdx; i < args.length; i++) {
|
|
104
137
|
switch (args[i]) {
|
|
105
138
|
case '--config-dir': {
|
|
@@ -150,10 +183,10 @@ function parseArgs(args) {
|
|
|
150
183
|
i++;
|
|
151
184
|
break;
|
|
152
185
|
}
|
|
153
|
-
case '--
|
|
154
|
-
|
|
155
|
-
if (!
|
|
156
|
-
console.error(formatError(ERROR_CODES.E1001, '--
|
|
186
|
+
case '--agent-provider': {
|
|
187
|
+
agentProvider = args[i + 1];
|
|
188
|
+
if (!agentProvider) {
|
|
189
|
+
console.error(formatError(ERROR_CODES.E1001, '--agent-provider requires a provider name argument'));
|
|
157
190
|
process.exit(1);
|
|
158
191
|
}
|
|
159
192
|
i++;
|
|
@@ -196,26 +229,62 @@ function parseArgs(args) {
|
|
|
196
229
|
i++;
|
|
197
230
|
break;
|
|
198
231
|
}
|
|
232
|
+
case '--budget-limit-cost': {
|
|
233
|
+
const raw = args[i + 1];
|
|
234
|
+
if (!raw) {
|
|
235
|
+
console.error(formatError(ERROR_CODES.E1001, '--budget-limit-cost requires a number argument (USD)'));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
const n = Number(raw);
|
|
239
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
240
|
+
console.error(formatError(ERROR_CODES.E1001, `--budget-limit-cost must be a positive number (got "${raw}")`));
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
budgetLimitCost = n;
|
|
244
|
+
i++;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case '--budget-limit-tokens': {
|
|
248
|
+
const raw = args[i + 1];
|
|
249
|
+
if (!raw) {
|
|
250
|
+
console.error(formatError(ERROR_CODES.E1001, '--budget-limit-tokens requires a number argument'));
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
const n = Number(raw);
|
|
254
|
+
if (!Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) {
|
|
255
|
+
console.error(formatError(ERROR_CODES.E1001, `--budget-limit-tokens must be a positive integer (got "${raw}")`));
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
budgetLimitTokens = n;
|
|
259
|
+
i++;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
199
262
|
// --fail-on-check-failure and --debug are handled above via includes()
|
|
200
263
|
}
|
|
201
264
|
}
|
|
202
265
|
return {
|
|
203
266
|
repositoryPath, configDir, outputPath, outputFormat,
|
|
204
267
|
failOnCheckFailure, debug, logLevel, logFile, logType,
|
|
205
|
-
runtimeConfigPath, model,
|
|
268
|
+
runtimeConfigPath, model, agentProvider, genericPrompt,
|
|
269
|
+
budgetLimitCost, budgetLimitTokens,
|
|
206
270
|
};
|
|
207
271
|
}
|
|
208
|
-
async function createProvider(useMock,
|
|
272
|
+
async function createProvider(useMock, agentProviderName, modelOverride) {
|
|
209
273
|
if (useMock) {
|
|
210
|
-
logProgress(TAG, `
|
|
211
|
-
|
|
274
|
+
logProgress(TAG, `Mock provider enabled via AGHAST_MOCK_AI=${process.env.AGHAST_MOCK_AI}`);
|
|
275
|
+
const provider = await createMockProvider();
|
|
276
|
+
// Honour --model in mock mode so cost-calculation tests can target a known
|
|
277
|
+
// pricing entry. Defaults to MOCK_MODEL_NAME.
|
|
278
|
+
const effectiveModel = modelOverride ?? MOCK_MODEL_NAME;
|
|
279
|
+
provider.setModel?.(effectiveModel);
|
|
280
|
+
return { provider, modelName: effectiveModel };
|
|
212
281
|
}
|
|
213
|
-
const provider = createProviderByName(
|
|
282
|
+
const provider = createProviderByName(agentProviderName);
|
|
214
283
|
await provider.initialize({
|
|
215
284
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
216
285
|
model: modelOverride,
|
|
217
286
|
});
|
|
218
|
-
const modelName = provider.getModelName?.() ??
|
|
287
|
+
const modelName = provider.getModelName?.() ?? DEFAULT_MODEL;
|
|
219
288
|
return { provider, modelName };
|
|
220
289
|
}
|
|
221
290
|
/**
|
|
@@ -278,7 +347,7 @@ export async function runScan(args) {
|
|
|
278
347
|
}
|
|
279
348
|
// Resolve log level: --log-level > AGHAST_LOG_LEVEL > runtime config > --debug/AGHAST_DEBUG > default
|
|
280
349
|
const debug = parsed.debug || process.env.AGHAST_DEBUG === 'true';
|
|
281
|
-
const resolvedLogLevel = parsed.logLevel ?? (process.env.AGHAST_LOG_LEVEL || undefined) ?? runtimeConfig.logging?.level ?? (debug ? 'debug' :
|
|
350
|
+
const resolvedLogLevel = parsed.logLevel ?? (process.env.AGHAST_LOG_LEVEL || undefined) ?? runtimeConfig.logging?.level ?? (debug ? 'debug' : DEFAULT_LOG_LEVEL);
|
|
282
351
|
if (resolvedLogLevel !== 'silent' && !isValidLogLevel(resolvedLogLevel)) {
|
|
283
352
|
console.error(formatError(ERROR_CODES.E1001, `Invalid log level "${resolvedLogLevel}". Valid levels: error, warn, info, debug, trace`));
|
|
284
353
|
process.exit(1);
|
|
@@ -287,7 +356,7 @@ export async function runScan(args) {
|
|
|
287
356
|
// Resolve log file: --log-file > AGHAST_LOG_FILE > runtime config
|
|
288
357
|
const resolvedLogFile = parsed.logFile ?? (process.env.AGHAST_LOG_FILE || undefined) ?? (runtimeConfig.logging?.logFile ? resolve(runtimeConfig.logging.logFile) : undefined);
|
|
289
358
|
if (resolvedLogFile) {
|
|
290
|
-
const resolvedLogType = parsed.logType ?? (process.env.AGHAST_LOG_TYPE || undefined) ?? runtimeConfig.logging?.logType ??
|
|
359
|
+
const resolvedLogType = parsed.logType ?? (process.env.AGHAST_LOG_TYPE || undefined) ?? runtimeConfig.logging?.logType ?? DEFAULT_LOG_TYPE;
|
|
291
360
|
const availableTypes = getAvailableLogTypes();
|
|
292
361
|
if (!availableTypes.includes(resolvedLogType)) {
|
|
293
362
|
console.error(formatError(ERROR_CODES.E1001, `Unknown log type "${resolvedLogType}". Available types: ${availableTypes.join(', ')}`));
|
|
@@ -316,7 +385,7 @@ export async function runScan(args) {
|
|
|
316
385
|
throw err;
|
|
317
386
|
}
|
|
318
387
|
// Resolve output format: CLI > runtime config > default
|
|
319
|
-
const resolvedOutputFormat = parsed.outputFormat ?? runtimeConfig.reporting?.outputFormat ??
|
|
388
|
+
const resolvedOutputFormat = parsed.outputFormat ?? runtimeConfig.reporting?.outputFormat ?? DEFAULT_OUTPUT_FORMAT;
|
|
320
389
|
// Resolve formatter early — fail fast on unknown format
|
|
321
390
|
const formatter = getFormatter(resolvedOutputFormat);
|
|
322
391
|
// Treat AGHAST_MOCK_AI=false (or empty) as disabled; any other truthy value enables mock mode
|
|
@@ -390,29 +459,30 @@ export async function runScan(args) {
|
|
|
390
459
|
const needsAI = checksWithDetails.some(c => getCheckType(c.check.checkTarget?.type).needsAI);
|
|
391
460
|
const needsSemgrep = checksWithDetails.some(c => c.check.checkTarget?.discovery === 'semgrep');
|
|
392
461
|
const needsOpenant = checksWithDetails.some(c => c.check.checkTarget?.discovery === 'openant');
|
|
393
|
-
// ─── Conditional
|
|
394
|
-
const
|
|
462
|
+
// ─── Conditional agent provider setup ───
|
|
463
|
+
const agentProviderName = parsed.agentProvider ?? runtimeConfig.agentProvider?.name ?? DEFAULT_PROVIDER_NAME;
|
|
395
464
|
if (needsAI && !useMock) {
|
|
396
|
-
// Validate
|
|
397
|
-
if (!getProviderNames().includes(
|
|
398
|
-
console.error(formatError(ERROR_CODES.E3002, `Unknown
|
|
465
|
+
// Validate agent provider name before checking credentials (config errors before auth errors)
|
|
466
|
+
if (!getProviderNames().includes(agentProviderName)) {
|
|
467
|
+
console.error(formatError(ERROR_CODES.E3002, `Unknown agent provider "${agentProviderName}". Supported providers: ${getProviderNames().join(', ')}`));
|
|
399
468
|
process.exit(1);
|
|
400
469
|
}
|
|
401
|
-
// Validate
|
|
402
|
-
|
|
403
|
-
|
|
470
|
+
// Validate provider-specific prerequisites (API keys, binaries, etc.)
|
|
471
|
+
try {
|
|
472
|
+
const tempProvider = createProviderByName(agentProviderName);
|
|
473
|
+
tempProvider.checkPrerequisites?.();
|
|
474
|
+
}
|
|
475
|
+
catch (err) {
|
|
476
|
+
console.error(formatError(ERROR_CODES.E3001, err instanceof Error ? err.message : String(err)));
|
|
404
477
|
process.exit(1);
|
|
405
478
|
}
|
|
406
479
|
}
|
|
407
480
|
// Resolve model precedence: CLI --model > env AGHAST_AI_MODEL > runtime config > default
|
|
408
|
-
const modelOverride = parsed.model ?? process.env.AGHAST_AI_MODEL ?? runtimeConfig.
|
|
481
|
+
const modelOverride = parsed.model ?? process.env.AGHAST_AI_MODEL ?? runtimeConfig.agentProvider?.model;
|
|
409
482
|
let provider;
|
|
410
483
|
let modelName;
|
|
411
484
|
if (needsAI) {
|
|
412
|
-
({ provider, modelName } = await createProvider(useMock,
|
|
413
|
-
if (resolvedLogLevel === 'debug' || resolvedLogLevel === 'trace') {
|
|
414
|
-
provider.enableDebug?.();
|
|
415
|
-
}
|
|
485
|
+
({ provider, modelName } = await createProvider(useMock, agentProviderName, modelOverride));
|
|
416
486
|
logProgress(TAG, `Using model: ${modelName}`);
|
|
417
487
|
}
|
|
418
488
|
// ─── Conditional Semgrep verification ───
|
|
@@ -435,59 +505,164 @@ export async function runScan(args) {
|
|
|
435
505
|
process.exit(1);
|
|
436
506
|
}
|
|
437
507
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
508
|
+
// ─── Pricing + budget setup ───
|
|
509
|
+
const defaultPricing = await loadDefaultPricing();
|
|
510
|
+
const pricing = mergePricing(defaultPricing, runtimeConfig.pricing);
|
|
511
|
+
const budgetLimits = (() => {
|
|
512
|
+
const cliCost = parsed.budgetLimitCost;
|
|
513
|
+
const cliTokens = parsed.budgetLimitTokens;
|
|
514
|
+
const cfg = runtimeConfig.budget;
|
|
515
|
+
if (cliCost === undefined && cliTokens === undefined && !cfg)
|
|
516
|
+
return undefined;
|
|
517
|
+
const out = {};
|
|
518
|
+
const perScan = { ...(cfg?.perScan ?? {}) };
|
|
519
|
+
if (cliCost !== undefined)
|
|
520
|
+
perScan.maxCostUsd = cliCost;
|
|
521
|
+
if (cliTokens !== undefined)
|
|
522
|
+
perScan.maxTokens = cliTokens;
|
|
523
|
+
if (perScan.maxCostUsd !== undefined || perScan.maxTokens !== undefined) {
|
|
524
|
+
out.perScan = perScan;
|
|
525
|
+
}
|
|
526
|
+
if (cfg?.perPeriod && cfg.perPeriod.window && cfg.perPeriod.maxCostUsd !== undefined) {
|
|
527
|
+
out.perPeriod = { window: cfg.perPeriod.window, maxCostUsd: cfg.perPeriod.maxCostUsd };
|
|
528
|
+
}
|
|
529
|
+
if (cfg?.thresholds)
|
|
530
|
+
out.thresholds = cfg.thresholds;
|
|
531
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
532
|
+
})();
|
|
533
|
+
// Pre-load history for period budget checks (skip when no period limit set)
|
|
534
|
+
let scanHistoryForBudget;
|
|
535
|
+
if (budgetLimits?.perPeriod) {
|
|
536
|
+
try {
|
|
537
|
+
scanHistoryForBudget = await queryScanHistory();
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
logDebug(TAG, `Could not load scan history for budget check: ${err instanceof Error ? err.message : String(err)}`);
|
|
541
|
+
}
|
|
456
542
|
}
|
|
457
|
-
|
|
458
|
-
|
|
543
|
+
try {
|
|
544
|
+
const outcome = await runMultiScanWithCost({
|
|
545
|
+
repositoryPath: effectiveRepoPath,
|
|
546
|
+
checks: checksWithDetails,
|
|
547
|
+
agentProvider: provider,
|
|
548
|
+
modelName: needsAI ? modelName : undefined,
|
|
549
|
+
repositoryInfo: repoAnalysis?.repository,
|
|
550
|
+
agentProviderName: needsAI ? (useMock ? 'mock' : agentProviderName) : undefined,
|
|
551
|
+
configDir,
|
|
552
|
+
genericPrompt,
|
|
553
|
+
pricing,
|
|
554
|
+
budgetLimits,
|
|
555
|
+
scanHistory: scanHistoryForBudget,
|
|
556
|
+
isLocalClaude: process.env.AGHAST_LOCAL_CLAUDE === 'true',
|
|
557
|
+
});
|
|
558
|
+
const results = outcome.results;
|
|
559
|
+
// Resolve output path: --output flag > runtime config dir > default
|
|
560
|
+
let resolvedOutputPath;
|
|
561
|
+
if (parsed.outputPath) {
|
|
562
|
+
resolvedOutputPath = parsed.outputPath;
|
|
563
|
+
}
|
|
564
|
+
else if (runtimeConfig.reporting?.outputDirectory) {
|
|
565
|
+
const dir = resolve(runtimeConfig.reporting.outputDirectory);
|
|
566
|
+
resolvedOutputPath = resolve(dir, 'security_checks_results' + formatter.fileExtension);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
resolvedOutputPath = resolve(effectiveRepoPath, 'security_checks_results' + formatter.fileExtension);
|
|
570
|
+
}
|
|
571
|
+
await mkdir(dirname(resolvedOutputPath), { recursive: true });
|
|
572
|
+
await writeFile(resolvedOutputPath, formatter.format(results), 'utf-8');
|
|
573
|
+
// Summary output
|
|
574
|
+
const statusIcon = results.summary.failedChecks > 0
|
|
575
|
+
? 'ISSUES DETECTED'
|
|
576
|
+
: results.summary.flaggedChecks > 0
|
|
577
|
+
? 'REVIEW REQUIRED'
|
|
578
|
+
: results.summary.errorChecks > 0
|
|
579
|
+
? 'SCAN ERROR'
|
|
580
|
+
: 'NO ISSUES DETECTED';
|
|
581
|
+
console.log('');
|
|
582
|
+
console.log('='.repeat(60));
|
|
583
|
+
console.log(`AGHAST Scan Complete: ${colorStatus(statusIcon)}`);
|
|
584
|
+
console.log('='.repeat(60));
|
|
585
|
+
console.log(` Total checks: ${results.summary.totalChecks}`);
|
|
586
|
+
console.log(` Passed: ${results.summary.passedChecks}`);
|
|
587
|
+
console.log(` Failed: ${results.summary.failedChecks}`);
|
|
588
|
+
console.log(` Flagged: ${results.summary.flaggedChecks}`);
|
|
589
|
+
console.log(` Errors: ${results.summary.errorChecks}`);
|
|
590
|
+
console.log(` Total issues: ${results.summary.totalIssues}`);
|
|
591
|
+
if (results.tokenUsage) {
|
|
592
|
+
const tu = results.tokenUsage;
|
|
593
|
+
const cacheSegments = [
|
|
594
|
+
tu.cacheReadInputTokens !== undefined ? ` cache-read: ${tu.cacheReadInputTokens.toLocaleString()}` : '',
|
|
595
|
+
tu.cacheCreationInputTokens !== undefined ? ` cache-write: ${tu.cacheCreationInputTokens.toLocaleString()}` : '',
|
|
596
|
+
tu.reasoningTokens !== undefined ? ` reasoning: ${tu.reasoningTokens.toLocaleString()}` : '',
|
|
597
|
+
].filter(Boolean).join(',');
|
|
598
|
+
const tokenDetail = `(in: ${tu.inputTokens.toLocaleString()}, out: ${tu.outputTokens.toLocaleString()}${cacheSegments ? ',' + cacheSegments : ''})`;
|
|
599
|
+
console.log(` Tokens: ${tu.totalTokens.toLocaleString()} ${tokenDetail}`);
|
|
600
|
+
}
|
|
601
|
+
if (outcome.totalCostUsd > 0 || outcome.costSource === 'estimated-unpriced') {
|
|
602
|
+
const label = formatCostSourceLabel(outcome.costSource, outcome.costReportedBy, outcome.costCoveredBySubscription);
|
|
603
|
+
const equiv = outcome.costCoveredBySubscription ? ' equivalent' : '';
|
|
604
|
+
console.log(` Cost: $${outcome.totalCostUsd.toFixed(4)}${equiv} ${label}`);
|
|
605
|
+
}
|
|
606
|
+
console.log(` Duration: ${globalTimer.elapsedStr()}`);
|
|
607
|
+
console.log(` Results: ${resolvedOutputPath}`);
|
|
608
|
+
console.log('='.repeat(60));
|
|
609
|
+
// Persist the scan record to history (best-effort — never blocks exit).
|
|
610
|
+
// We DO save the record even when the scan was aborted by budget: per-period
|
|
611
|
+
// budgets aggregate over historical scans, so the partial cost incurred
|
|
612
|
+
// before the abort must be recorded — otherwise a user could repeatedly
|
|
613
|
+
// hit the budget abort and still consume more total spend than the period
|
|
614
|
+
// limit allows.
|
|
615
|
+
try {
|
|
616
|
+
const record = {
|
|
617
|
+
scanId: results.scanId,
|
|
618
|
+
startedAt: results.startTime,
|
|
619
|
+
endedAt: results.endTime,
|
|
620
|
+
durationMs: results.executionTime,
|
|
621
|
+
repository: effectiveRepoPath,
|
|
622
|
+
repositoryUrl: repoAnalysis?.repository.remoteUrl,
|
|
623
|
+
models: outcome.models.length > 0 ? outcome.models : (modelName ? [modelName] : []),
|
|
624
|
+
tokenUsage: results.tokenUsage,
|
|
625
|
+
totalCost: outcome.totalCostUsd,
|
|
626
|
+
currency: outcome.currency,
|
|
627
|
+
costSource: outcome.costSource,
|
|
628
|
+
costReportedBy: outcome.costReportedBy,
|
|
629
|
+
costCoveredBySubscription: outcome.costCoveredBySubscription,
|
|
630
|
+
checks: results.summary.totalChecks,
|
|
631
|
+
issues: results.summary.totalIssues,
|
|
632
|
+
};
|
|
633
|
+
await saveScanRecord(record);
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
logDebug(TAG, `Failed to save scan history: ${err instanceof Error ? err.message : String(err)}`);
|
|
637
|
+
}
|
|
638
|
+
// A budget abort is a deliberate failure mode the user opted into via
|
|
639
|
+
// --budget-limit-* / runtime config — it must always exit non-zero (and
|
|
640
|
+
// surface E7001 to stderr) regardless of --fail-on-check-failure. Doing
|
|
641
|
+
// otherwise would silently drop the abort signal in CI pipelines that
|
|
642
|
+
// rely on the exit code as a guardrail.
|
|
643
|
+
//
|
|
644
|
+
// Emit through both stderr (for terminal users / CI logs) AND the logging
|
|
645
|
+
// system (so --log-file captures the abort reason — console.error bypasses
|
|
646
|
+
// registered log handlers).
|
|
647
|
+
if (outcome.budgetAborted) {
|
|
648
|
+
const reason = outcome.budgetAbortReason ?? 'Budget limit exceeded';
|
|
649
|
+
console.error(formatError(ERROR_CODES.E7001, reason));
|
|
650
|
+
logProgress(TAG, `Scan aborted by budget: ${reason}`);
|
|
651
|
+
}
|
|
652
|
+
// Exit code based on --fail-on-check-failure flag or runtime config (spec Section 9.3),
|
|
653
|
+
// OR a budget abort.
|
|
654
|
+
const failOnCheckFailure = parsed.failOnCheckFailure || runtimeConfig.failOnCheckFailure === true;
|
|
655
|
+
const shouldFail = outcome.budgetAborted ||
|
|
656
|
+
(failOnCheckFailure && (results.summary.failedChecks > 0 || results.summary.errorChecks > 0));
|
|
657
|
+
await closeAllHandlers();
|
|
658
|
+
process.exit(shouldFail ? 1 : 0);
|
|
459
659
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
: results.summary.flaggedChecks > 0
|
|
466
|
-
? 'FLAG'
|
|
467
|
-
: results.summary.errorChecks > 0
|
|
468
|
-
? 'ERROR'
|
|
469
|
-
: 'PASS';
|
|
470
|
-
console.log('');
|
|
471
|
-
console.log('='.repeat(60));
|
|
472
|
-
console.log(`AGHAST Scan Complete: ${colorStatus(statusIcon)}`);
|
|
473
|
-
console.log('='.repeat(60));
|
|
474
|
-
console.log(` Total checks: ${results.summary.totalChecks}`);
|
|
475
|
-
console.log(` Passed: ${results.summary.passedChecks}`);
|
|
476
|
-
console.log(` Failed: ${results.summary.failedChecks}`);
|
|
477
|
-
console.log(` Flagged: ${results.summary.flaggedChecks}`);
|
|
478
|
-
console.log(` Errors: ${results.summary.errorChecks}`);
|
|
479
|
-
console.log(` Total issues: ${results.summary.totalIssues}`);
|
|
480
|
-
if (results.tokenUsage) {
|
|
481
|
-
console.log(` Tokens: ${results.tokenUsage.totalTokens.toLocaleString()} (in: ${results.tokenUsage.inputTokens.toLocaleString()}, out: ${results.tokenUsage.outputTokens.toLocaleString()})`);
|
|
660
|
+
finally {
|
|
661
|
+
// Clean up provider resources (e.g. OpenCode server process)
|
|
662
|
+
if (provider && 'cleanup' in provider && typeof provider.cleanup === 'function') {
|
|
663
|
+
await provider.cleanup();
|
|
664
|
+
}
|
|
482
665
|
}
|
|
483
|
-
console.log(` Duration: ${globalTimer.elapsedStr()}`);
|
|
484
|
-
console.log(` Results: ${resolvedOutputPath}`);
|
|
485
|
-
console.log('='.repeat(60));
|
|
486
|
-
// Exit code based on --fail-on-check-failure flag or runtime config (spec Section 9.3)
|
|
487
|
-
const failOnCheckFailure = parsed.failOnCheckFailure || runtimeConfig.failOnCheckFailure === true;
|
|
488
|
-
const shouldFail = failOnCheckFailure && (results.summary.failedChecks > 0 || results.summary.errorChecks > 0);
|
|
489
|
-
await closeAllHandlers();
|
|
490
|
-
process.exit(shouldFail ? 1 : 0);
|
|
491
666
|
}
|
|
492
667
|
// Auto-run when executed directly (npm run scan / tsx src/index.ts), but not when imported by cli.ts.
|
|
493
668
|
if (!process.env._AGHAST_CLI) {
|