auditor-lambda 0.2.8 → 0.2.9
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 +6 -0
- package/audit-code-wrapper-lib.mjs +1 -1
- package/dist/adapters/eslint.js +9 -5
- package/dist/cli.d.ts +42 -1
- package/dist/cli.js +114 -64
- package/dist/extractors/bucketing.d.ts +4 -0
- package/dist/extractors/bucketing.js +6 -2
- package/dist/extractors/disposition.d.ts +4 -0
- package/dist/extractors/disposition.js +6 -2
- package/dist/extractors/fileInventory.js +24 -28
- package/dist/extractors/flows.d.ts +5 -0
- package/dist/extractors/flows.js +18 -38
- package/dist/extractors/pathPatterns.d.ts +10 -3
- package/dist/extractors/pathPatterns.js +109 -61
- package/dist/extractors/surfaces.d.ts +4 -0
- package/dist/extractors/surfaces.js +11 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/io/artifacts.d.ts +55 -40
- package/dist/io/artifacts.js +73 -110
- package/dist/io/json.js +52 -21
- package/dist/io/runArtifacts.d.ts +1 -1
- package/dist/io/runArtifacts.js +26 -3
- package/dist/orchestrator/advance.js +83 -62
- package/dist/orchestrator/flowCoverage.js +11 -5
- package/dist/orchestrator/flowPlanning.d.ts +7 -2
- package/dist/orchestrator/flowPlanning.js +46 -21
- package/dist/orchestrator/flowRequeue.js +28 -8
- package/dist/orchestrator/internalExecutors.js +12 -8
- package/dist/orchestrator/planning.js +25 -3
- package/dist/orchestrator/requeue.js +11 -1
- package/dist/orchestrator/taskBuilder.d.ts +4 -2
- package/dist/orchestrator/taskBuilder.js +153 -52
- package/dist/orchestrator/unitBuilder.d.ts +3 -1
- package/dist/orchestrator/unitBuilder.js +24 -16
- package/dist/prompts/renderWorkerPrompt.d.ts +1 -1
- package/dist/prompts/renderWorkerPrompt.js +16 -8
- package/dist/providers/claudeCodeProvider.d.ts +4 -1
- package/dist/providers/claudeCodeProvider.js +8 -5
- package/dist/providers/localSubprocessProvider.d.ts +4 -0
- package/dist/providers/localSubprocessProvider.js +7 -2
- package/dist/providers/spawnLoggedCommand.d.ts +9 -1
- package/dist/providers/spawnLoggedCommand.js +77 -29
- package/dist/reporting/synthesis.d.ts +2 -0
- package/dist/reporting/synthesis.js +12 -9
- package/dist/supervisor/operatorHandoff.js +48 -18
- package/dist/supervisor/runLedger.d.ts +1 -1
- package/dist/supervisor/runLedger.js +112 -5
- package/dist/supervisor/sessionConfig.js +10 -10
- package/dist/types/externalAnalyzer.d.ts +3 -0
- package/dist/types/flowCoverage.d.ts +5 -1
- package/dist/types/flowCoverage.js +5 -1
- package/dist/types/flows.d.ts +5 -1
- package/dist/types/flows.js +1 -1
- package/dist/types/runLedger.d.ts +5 -1
- package/dist/types/runLedger.js +6 -1
- package/dist/types/runtimeValidation.d.ts +12 -3
- package/dist/types/runtimeValidation.js +16 -1
- package/dist/types/sessionConfig.d.ts +15 -2
- package/dist/types/sessionConfig.js +15 -1
- package/dist/types/surfaces.d.ts +4 -1
- package/dist/types/surfaces.js +1 -1
- package/dist/types/workerSession.d.ts +9 -0
- package/dist/types/workerSession.js +5 -1
- package/dist/validation/artifacts.d.ts +1 -1
- package/dist/validation/artifacts.js +33 -20
- package/dist/validation/auditResults.d.ts +2 -2
- package/dist/validation/auditResults.js +7 -15
- package/dist/validation/basic.d.ts +9 -1
- package/dist/validation/basic.js +40 -3
- package/dist/validation/sessionConfig.d.ts +4 -2
- package/dist/validation/sessionConfig.js +62 -15
- package/docs/agent-integrations.md +29 -9
- package/docs/next-steps.md +21 -4
- package/docs/packaging.md +14 -0
- package/docs/product-direction.md +22 -0
- package/docs/production-launch-bar.md +2 -0
- package/docs/releasing.md +17 -0
- package/docs/remediation-baseline.md +75 -0
- package/docs/run-flow.md +23 -11
- package/docs/session-config.md +50 -5
- package/docs/supervisor.md +7 -0
- package/docs/workflow-refactor-brief.md +177 -0
- package/package.json +1 -1
- package/schemas/audit_result.schema.json +4 -1
- package/schemas/audit_task.schema.json +3 -1
- package/schemas/coverage_matrix.schema.json +3 -3
- package/schemas/critical_flows.schema.json +6 -2
- package/schemas/file_disposition.schema.json +2 -2
- package/schemas/finding.schema.json +9 -4
- package/schemas/flow_coverage.schema.json +2 -2
- package/schemas/repo_manifest.schema.json +4 -4
- package/schemas/risk_register.schema.json +2 -2
- package/schemas/runtime_validation_report.schema.json +2 -2
- package/schemas/runtime_validation_tasks.schema.json +8 -2
- package/schemas/surface_manifest.schema.json +6 -3
- package/schemas/unit_manifest.schema.json +3 -2
- package/skills/audit-code/SKILL.md +5 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** One normalized result imported from an external analyzer such as eslint or tsc. */
|
|
1
2
|
export interface ExternalAnalyzerResultItem {
|
|
2
3
|
id: string;
|
|
3
4
|
category: string;
|
|
@@ -7,8 +8,10 @@ export interface ExternalAnalyzerResultItem {
|
|
|
7
8
|
line_end?: number;
|
|
8
9
|
summary: string;
|
|
9
10
|
rule?: string;
|
|
11
|
+
/** Preserves the analyzer-native payload when consumers need original detail. */
|
|
10
12
|
raw?: unknown;
|
|
11
13
|
}
|
|
14
|
+
/** Imported analyzer output captured at a single generation time. */
|
|
12
15
|
export interface ExternalAnalyzerResults {
|
|
13
16
|
tool: string;
|
|
14
17
|
generated_at?: string;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
export declare const FLOW_COVERAGE_STATUSES: readonly ["pending", "partial", "complete"];
|
|
2
|
+
export type FlowCoverageStatus = (typeof FLOW_COVERAGE_STATUSES)[number];
|
|
3
|
+
/** Coverage for one critical flow across the lenses the audit expects to see. */
|
|
1
4
|
export interface FlowCoverageRecord {
|
|
2
5
|
flow_id: string;
|
|
3
6
|
paths: string[];
|
|
4
7
|
required_lenses: string[];
|
|
5
8
|
completed_lenses: string[];
|
|
6
|
-
status:
|
|
9
|
+
status: FlowCoverageStatus;
|
|
7
10
|
notes?: string[];
|
|
8
11
|
}
|
|
12
|
+
/** Aggregated flow coverage written beside the critical flow manifest. */
|
|
9
13
|
export interface FlowCoverageManifest {
|
|
10
14
|
flows: FlowCoverageRecord[];
|
|
11
15
|
}
|
package/dist/types/flows.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
export declare const FLOW_CONFIDENCE_LEVELS: readonly ["high", "low"];
|
|
2
|
+
export type FlowConfidenceLevel = (typeof FLOW_CONFIDENCE_LEVELS)[number];
|
|
3
|
+
/** A critical user or system flow that must be covered by the audit. */
|
|
1
4
|
export interface CriticalFlow {
|
|
2
5
|
id: string;
|
|
3
6
|
name: string;
|
|
4
7
|
entrypoints: string[];
|
|
5
8
|
paths: string[];
|
|
6
9
|
concerns: string[];
|
|
7
|
-
confidence?:
|
|
10
|
+
confidence?: FlowConfidenceLevel;
|
|
8
11
|
notes?: string[];
|
|
9
12
|
}
|
|
13
|
+
/** The set of critical flows inferred from intake artifacts. */
|
|
10
14
|
export interface CriticalFlowManifest {
|
|
11
15
|
flows: CriticalFlow[];
|
|
12
16
|
fallback_required?: boolean;
|
package/dist/types/flows.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const FLOW_CONFIDENCE_LEVELS = ["high", "low"];
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
export declare const RUN_LEDGER_STATUSES: readonly ["completed", "blocked", "failed", "no_progress"];
|
|
2
|
+
export type RunLedgerStatus = (typeof RUN_LEDGER_STATUSES)[number];
|
|
3
|
+
/** One persisted supervisor run entry, including the terminal worker outcome. */
|
|
1
4
|
export interface RunLedgerEntry {
|
|
2
5
|
run_id: string;
|
|
3
6
|
provider: string;
|
|
4
7
|
obligation_id: string | null;
|
|
5
8
|
selected_executor: string | null;
|
|
6
|
-
status:
|
|
9
|
+
status: RunLedgerStatus;
|
|
7
10
|
started_at: string;
|
|
8
11
|
ended_at: string;
|
|
9
12
|
result_path: string;
|
|
10
13
|
}
|
|
14
|
+
/** Append-only ledger used to explain how the audit advanced over time. */
|
|
11
15
|
export interface RunLedger {
|
|
12
16
|
runs: RunLedgerEntry[];
|
|
13
17
|
}
|
package/dist/types/runLedger.js
CHANGED
|
@@ -1,24 +1,33 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const RUNTIME_VALIDATION_KINDS: readonly ["unit-risk-check", "critical-flow-check"];
|
|
2
|
+
export type RuntimeValidationKind = (typeof RUNTIME_VALIDATION_KINDS)[number];
|
|
3
|
+
export declare const RUNTIME_VALIDATION_PRIORITIES: readonly ["high", "medium", "low"];
|
|
4
|
+
export type RuntimeValidationPriority = (typeof RUNTIME_VALIDATION_PRIORITIES)[number];
|
|
5
|
+
export declare const RUNTIME_VALIDATION_STATUSES: readonly ["pending", "confirmed", "not_confirmed", "inconclusive", "not_required"];
|
|
6
|
+
export type RuntimeValidationStatus = (typeof RUNTIME_VALIDATION_STATUSES)[number];
|
|
7
|
+
/** A deterministic runtime check queued after static review highlights risk. */
|
|
2
8
|
export interface RuntimeValidationTask {
|
|
3
9
|
id: string;
|
|
4
10
|
kind: RuntimeValidationKind;
|
|
5
11
|
target_paths: string[];
|
|
6
12
|
reason: string;
|
|
7
|
-
priority:
|
|
13
|
+
priority: RuntimeValidationPriority;
|
|
8
14
|
command?: string[];
|
|
9
15
|
suggested_checks?: string[];
|
|
10
16
|
source_artifacts?: string[];
|
|
11
17
|
}
|
|
18
|
+
/** Planner output for the runtime validation stage. */
|
|
12
19
|
export interface RuntimeValidationTaskManifest {
|
|
13
20
|
tasks: RuntimeValidationTask[];
|
|
14
21
|
}
|
|
22
|
+
/** Result recorded after a runtime validation task runs or is intentionally skipped. */
|
|
15
23
|
export interface RuntimeValidationResult {
|
|
16
24
|
task_id: string;
|
|
17
|
-
status:
|
|
25
|
+
status: RuntimeValidationStatus;
|
|
18
26
|
summary: string;
|
|
19
27
|
evidence?: string[];
|
|
20
28
|
notes?: string[];
|
|
21
29
|
}
|
|
30
|
+
/** Persisted runtime validation outcomes keyed by generated task id. */
|
|
22
31
|
export interface RuntimeValidationReport {
|
|
23
32
|
results: RuntimeValidationResult[];
|
|
24
33
|
}
|
|
@@ -1 +1,16 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const RUNTIME_VALIDATION_KINDS = [
|
|
2
|
+
"unit-risk-check",
|
|
3
|
+
"critical-flow-check",
|
|
4
|
+
];
|
|
5
|
+
export const RUNTIME_VALIDATION_PRIORITIES = [
|
|
6
|
+
"high",
|
|
7
|
+
"medium",
|
|
8
|
+
"low",
|
|
9
|
+
];
|
|
10
|
+
export const RUNTIME_VALIDATION_STATUSES = [
|
|
11
|
+
"pending",
|
|
12
|
+
"confirmed",
|
|
13
|
+
"not_confirmed",
|
|
14
|
+
"inconclusive",
|
|
15
|
+
"not_required",
|
|
16
|
+
];
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const PROVIDER_NAMES: readonly ["auto", "local-subprocess", "subprocess-template", "claude-code", "opencode", "vscode-task"];
|
|
2
|
+
export type ProviderName = (typeof PROVIDER_NAMES)[number];
|
|
2
3
|
export type ResolvedProviderName = Exclude<ProviderName, "auto">;
|
|
4
|
+
export declare const SESSION_UI_MODES: readonly ["visible", "headless"];
|
|
5
|
+
export type SessionUiMode = (typeof SESSION_UI_MODES)[number];
|
|
3
6
|
export interface SubprocessTemplateConfig {
|
|
4
7
|
command_template: string[];
|
|
5
8
|
env?: Record<string, string>;
|
|
@@ -16,10 +19,20 @@ export interface VSCodeTaskConfig {
|
|
|
16
19
|
command_template: string[];
|
|
17
20
|
env?: Record<string, string>;
|
|
18
21
|
}
|
|
22
|
+
export declare const PROVIDER_SECTION_KEYS: {
|
|
23
|
+
readonly "subprocess-template": "subprocess_template";
|
|
24
|
+
readonly "claude-code": "claude_code";
|
|
25
|
+
readonly opencode: "opencode";
|
|
26
|
+
readonly "vscode-task": "vscode_task";
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Provider names use CLI-friendly hyphenation, while nested provider config
|
|
30
|
+
* sections stay snake_case because they serialize directly into JSON files.
|
|
31
|
+
*/
|
|
19
32
|
export interface SessionConfig {
|
|
20
33
|
provider?: ProviderName;
|
|
21
34
|
timeout_ms?: number;
|
|
22
|
-
ui_mode?:
|
|
35
|
+
ui_mode?: SessionUiMode;
|
|
23
36
|
subprocess_template?: SubprocessTemplateConfig;
|
|
24
37
|
claude_code?: ClaudeCodeConfig;
|
|
25
38
|
opencode?: OpenCodeConfig;
|
|
@@ -1 +1,15 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const PROVIDER_NAMES = [
|
|
2
|
+
"auto",
|
|
3
|
+
"local-subprocess",
|
|
4
|
+
"subprocess-template",
|
|
5
|
+
"claude-code",
|
|
6
|
+
"opencode",
|
|
7
|
+
"vscode-task",
|
|
8
|
+
];
|
|
9
|
+
export const SESSION_UI_MODES = ["visible", "headless"];
|
|
10
|
+
export const PROVIDER_SECTION_KEYS = {
|
|
11
|
+
"subprocess-template": "subprocess_template",
|
|
12
|
+
"claude-code": "claude_code",
|
|
13
|
+
opencode: "opencode",
|
|
14
|
+
"vscode-task": "vscode_task",
|
|
15
|
+
};
|
package/dist/types/surfaces.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const SURFACE_KINDS: readonly ["interface", "background"];
|
|
2
|
+
export type SurfaceKind = (typeof SURFACE_KINDS)[number];
|
|
3
|
+
/** Discovered execution surfaces that define where the product can be reached. */
|
|
2
4
|
export interface SurfaceRecord {
|
|
3
5
|
id: string;
|
|
4
6
|
kind: SurfaceKind;
|
|
@@ -7,6 +9,7 @@ export interface SurfaceRecord {
|
|
|
7
9
|
methods?: string[];
|
|
8
10
|
notes?: string[];
|
|
9
11
|
}
|
|
12
|
+
/** Intake output that summarizes externally reachable product surfaces. */
|
|
10
13
|
export interface SurfaceManifest {
|
|
11
14
|
surfaces: SurfaceRecord[];
|
|
12
15
|
}
|
package/dist/types/surfaces.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const SURFACE_KINDS = ["interface", "background"];
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export declare const WORKER_COMMAND_MODES: readonly ["run", "deferred"];
|
|
2
|
+
export type WorkerCommandMode = (typeof WORKER_COMMAND_MODES)[number];
|
|
3
|
+
/**
|
|
4
|
+
* Worker tasks serialize directly to task.json, so their persisted field names
|
|
5
|
+
* intentionally stay snake_case for consistency across providers and bridges.
|
|
6
|
+
*/
|
|
1
7
|
export interface WorkerTask {
|
|
2
8
|
contract_version: "audit-code-worker/v1alpha1";
|
|
3
9
|
run_id: string;
|
|
@@ -11,7 +17,10 @@ export interface WorkerTask {
|
|
|
11
17
|
pending_audit_tasks_path?: string;
|
|
12
18
|
runtime_updates_path?: string;
|
|
13
19
|
external_analyzer_results_path?: string;
|
|
20
|
+
worker_command_mode?: WorkerCommandMode;
|
|
21
|
+
/** @deprecated Prefer worker_command_mode: "deferred" for new task files. */
|
|
14
22
|
skip_worker_command?: boolean;
|
|
15
23
|
timeout_ms?: number;
|
|
16
24
|
max_retries?: number;
|
|
17
25
|
}
|
|
26
|
+
export declare function usesDeferredWorkerCommand(task: Pick<WorkerTask, "worker_command_mode" | "skip_worker_command">): boolean;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { requireKeys } from "./basic.js";
|
|
1
|
+
import { pushValidationIssue, requireKeys, } from "./basic.js";
|
|
2
2
|
function pushIssue(issues, path, message) {
|
|
3
|
-
issues
|
|
3
|
+
pushValidationIssue(issues, path, message);
|
|
4
|
+
}
|
|
5
|
+
function asArray(value) {
|
|
6
|
+
return Array.isArray(value) ? value : [];
|
|
4
7
|
}
|
|
5
8
|
export function validateArtifactBundle(bundle) {
|
|
6
9
|
const issues = [];
|
|
@@ -37,14 +40,24 @@ export function validateArtifactBundle(bundle) {
|
|
|
37
40
|
if (bundle.external_analyzer_results) {
|
|
38
41
|
issues.push(...requireKeys(bundle.external_analyzer_results, "external_analyzer_results", ["tool", "results"]));
|
|
39
42
|
}
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
43
|
+
const repoManifestFiles = asArray(bundle.repo_manifest?.files);
|
|
44
|
+
const fileDispositionEntries = asArray(bundle.file_disposition?.files);
|
|
45
|
+
const unitManifestUnits = asArray(bundle.unit_manifest?.units);
|
|
46
|
+
const criticalFlows = asArray(bundle.critical_flows?.flows);
|
|
47
|
+
const flowCoverageEntries = asArray(bundle.flow_coverage?.flows);
|
|
48
|
+
const riskRegisterItems = asArray(bundle.risk_register?.items);
|
|
49
|
+
const surfaceEntries = asArray(bundle.surface_manifest?.surfaces);
|
|
50
|
+
const runtimeValidationTasks = asArray(bundle.runtime_validation_tasks?.tasks);
|
|
51
|
+
const runtimeValidationResults = asArray(bundle.runtime_validation_report?.results);
|
|
52
|
+
const externalAnalyzerResults = asArray(bundle.external_analyzer_results?.results);
|
|
53
|
+
const coverageFiles = asArray(bundle.coverage_matrix?.files);
|
|
54
|
+
const repoPaths = new Set(repoManifestFiles.map((file) => file.path));
|
|
55
|
+
const dispositionMap = new Map(fileDispositionEntries.map((item) => [item.path, item.status]));
|
|
56
|
+
const unitIds = new Set(unitManifestUnits.map((unit) => unit.unit_id));
|
|
57
|
+
const flowIds = new Set(criticalFlows.map((flow) => flow.id));
|
|
58
|
+
const runtimeTaskIds = new Set(runtimeValidationTasks.map((task) => task.id));
|
|
46
59
|
if (bundle.repo_manifest && bundle.coverage_matrix) {
|
|
47
|
-
const coveragePaths = new Set(
|
|
60
|
+
const coveragePaths = new Set(coverageFiles.map((file) => file.path));
|
|
48
61
|
for (const path of repoPaths) {
|
|
49
62
|
if (!coveragePaths.has(path)) {
|
|
50
63
|
pushIssue(issues, "coverage_matrix", `Missing coverage entry for ${path}`);
|
|
@@ -52,7 +65,7 @@ export function validateArtifactBundle(bundle) {
|
|
|
52
65
|
}
|
|
53
66
|
}
|
|
54
67
|
if (bundle.repo_manifest && bundle.file_disposition) {
|
|
55
|
-
const dispositionPaths = new Set(
|
|
68
|
+
const dispositionPaths = new Set(fileDispositionEntries.map((file) => file.path));
|
|
56
69
|
for (const path of repoPaths) {
|
|
57
70
|
if (!dispositionPaths.has(path)) {
|
|
58
71
|
pushIssue(issues, "file_disposition", `Missing disposition entry for ${path}`);
|
|
@@ -60,7 +73,7 @@ export function validateArtifactBundle(bundle) {
|
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
if (bundle.unit_manifest) {
|
|
63
|
-
for (const unit of
|
|
76
|
+
for (const unit of unitManifestUnits) {
|
|
64
77
|
if (unit.files.length === 0) {
|
|
65
78
|
pushIssue(issues, `unit_manifest:${unit.unit_id}`, "Unit has no files");
|
|
66
79
|
}
|
|
@@ -79,7 +92,7 @@ export function validateArtifactBundle(bundle) {
|
|
|
79
92
|
}
|
|
80
93
|
}
|
|
81
94
|
if (bundle.coverage_matrix && bundle.unit_manifest) {
|
|
82
|
-
for (const file of
|
|
95
|
+
for (const file of coverageFiles) {
|
|
83
96
|
if (!repoPaths.has(file.path)) {
|
|
84
97
|
pushIssue(issues, "coverage_matrix", `Coverage contains unknown file ${file.path}`);
|
|
85
98
|
}
|
|
@@ -103,7 +116,7 @@ export function validateArtifactBundle(bundle) {
|
|
|
103
116
|
}
|
|
104
117
|
}
|
|
105
118
|
if (bundle.critical_flows) {
|
|
106
|
-
for (const flow of
|
|
119
|
+
for (const flow of criticalFlows) {
|
|
107
120
|
if (flow.paths.length === 0) {
|
|
108
121
|
pushIssue(issues, `critical_flows:${flow.id}`, "Flow has no paths");
|
|
109
122
|
}
|
|
@@ -122,7 +135,7 @@ export function validateArtifactBundle(bundle) {
|
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
if (bundle.flow_coverage && bundle.critical_flows) {
|
|
125
|
-
for (const flow of
|
|
138
|
+
for (const flow of flowCoverageEntries) {
|
|
126
139
|
if (!flowIds.has(flow.flow_id)) {
|
|
127
140
|
pushIssue(issues, `flow_coverage:${flow.flow_id}`, `Flow coverage references unknown flow ${flow.flow_id}`);
|
|
128
141
|
}
|
|
@@ -143,15 +156,15 @@ export function validateArtifactBundle(bundle) {
|
|
|
143
156
|
}
|
|
144
157
|
}
|
|
145
158
|
if (bundle.risk_register && bundle.unit_manifest) {
|
|
146
|
-
const riskUnitIds = new Set(
|
|
147
|
-
for (const unit of
|
|
159
|
+
const riskUnitIds = new Set(riskRegisterItems.map((item) => item.unit_id));
|
|
160
|
+
for (const unit of unitManifestUnits) {
|
|
148
161
|
if (!riskUnitIds.has(unit.unit_id)) {
|
|
149
162
|
pushIssue(issues, "risk_register", `Missing risk entry for unit ${unit.unit_id}`);
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
166
|
if (bundle.surface_manifest) {
|
|
154
|
-
for (const surface of
|
|
167
|
+
for (const surface of surfaceEntries) {
|
|
155
168
|
if (!repoPaths.has(surface.entrypoint)) {
|
|
156
169
|
pushIssue(issues, `surface_manifest:${surface.id}`, `Surface references unknown entrypoint ${surface.entrypoint}`);
|
|
157
170
|
}
|
|
@@ -162,7 +175,7 @@ export function validateArtifactBundle(bundle) {
|
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
if (bundle.runtime_validation_tasks) {
|
|
165
|
-
for (const task of
|
|
178
|
+
for (const task of runtimeValidationTasks) {
|
|
166
179
|
if (task.target_paths.length === 0) {
|
|
167
180
|
pushIssue(issues, `runtime_validation_tasks:${task.id}`, "Runtime validation task has no target paths");
|
|
168
181
|
}
|
|
@@ -174,14 +187,14 @@ export function validateArtifactBundle(bundle) {
|
|
|
174
187
|
}
|
|
175
188
|
}
|
|
176
189
|
if (bundle.runtime_validation_report) {
|
|
177
|
-
for (const result of
|
|
190
|
+
for (const result of runtimeValidationResults) {
|
|
178
191
|
if (!runtimeTaskIds.has(result.task_id)) {
|
|
179
192
|
pushIssue(issues, `runtime_validation_report:${result.task_id}`, `Runtime validation result references unknown task ${result.task_id}`);
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
195
|
}
|
|
183
196
|
if (bundle.external_analyzer_results) {
|
|
184
|
-
for (const item of
|
|
197
|
+
for (const item of externalAnalyzerResults) {
|
|
185
198
|
if (!repoPaths.has(item.path) && bundle.repo_manifest) {
|
|
186
199
|
pushIssue(issues, `external_analyzer_results:${item.id}`, `External analyzer result references unknown path ${item.path}`);
|
|
187
200
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { AuditTask } from "../types.js";
|
|
2
|
+
import { type ValidationIssue } from "./basic.js";
|
|
2
3
|
export type IssueSeverity = "error" | "warning";
|
|
3
|
-
export interface AuditResultIssue {
|
|
4
|
+
export interface AuditResultIssue extends ValidationIssue {
|
|
4
5
|
result_index: number;
|
|
5
6
|
task_id: string;
|
|
6
7
|
severity: IssueSeverity;
|
|
7
8
|
field: string;
|
|
8
|
-
message: string;
|
|
9
9
|
}
|
|
10
10
|
export interface ValidateAuditResultOptions {
|
|
11
11
|
lineIndex?: Record<string, number>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describeValue, formatValidationIssues, isRecord, } from "./basic.js";
|
|
1
2
|
const REQUIRED_FINDING_FIELDS = [
|
|
2
3
|
"id",
|
|
3
4
|
"title",
|
|
@@ -24,21 +25,10 @@ const VALID_LENSES = new Set([
|
|
|
24
25
|
function pushIssue(issues, params) {
|
|
25
26
|
issues.push({
|
|
26
27
|
...params,
|
|
28
|
+
path: params.path ?? params.field,
|
|
27
29
|
severity: params.severity ?? "error",
|
|
28
30
|
});
|
|
29
31
|
}
|
|
30
|
-
function describeValue(value) {
|
|
31
|
-
if (Array.isArray(value)) {
|
|
32
|
-
return "array";
|
|
33
|
-
}
|
|
34
|
-
if (value === null) {
|
|
35
|
-
return "null";
|
|
36
|
-
}
|
|
37
|
-
return typeof value;
|
|
38
|
-
}
|
|
39
|
-
function isRecord(value) {
|
|
40
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
-
}
|
|
42
32
|
function isNonEmptyString(value) {
|
|
43
33
|
return typeof value === "string" && value.trim().length > 0;
|
|
44
34
|
}
|
|
@@ -404,7 +394,9 @@ export function validateAuditResults(results, tasks, options = {}) {
|
|
|
404
394
|
return issues;
|
|
405
395
|
}
|
|
406
396
|
export function formatAuditResultIssues(issues) {
|
|
407
|
-
return issues
|
|
408
|
-
|
|
409
|
-
.
|
|
397
|
+
return formatValidationIssues(issues.map((issue) => ({
|
|
398
|
+
path: `${issue.task_id} / ${issue.field}`,
|
|
399
|
+
message: issue.message,
|
|
400
|
+
severity: issue.severity,
|
|
401
|
+
})));
|
|
410
402
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
export type ValidationSeverity = "error" | "warning";
|
|
1
2
|
export interface ValidationIssue {
|
|
2
3
|
path: string;
|
|
3
4
|
message: string;
|
|
5
|
+
severity: ValidationSeverity;
|
|
4
6
|
}
|
|
5
|
-
export declare function
|
|
7
|
+
export declare function describeValue(value: unknown): string;
|
|
8
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
9
|
+
export declare function createValidationIssue(path: string, message: string, severity?: ValidationSeverity): ValidationIssue;
|
|
10
|
+
export declare function pushValidationIssue(issues: ValidationIssue[], path: string, message: string, severity?: ValidationSeverity): void;
|
|
11
|
+
export declare function prefixValidationIssues(prefix: string, issues: ValidationIssue[]): ValidationIssue[];
|
|
12
|
+
export declare function formatValidationIssues(issues: ValidationIssue[]): string;
|
|
13
|
+
export declare function requireKeys(value: unknown, path: string, keys: readonly string[]): ValidationIssue[];
|
package/dist/validation/basic.js
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function describeValue(value) {
|
|
2
|
+
if (Array.isArray(value)) {
|
|
3
|
+
return "array";
|
|
4
|
+
}
|
|
5
|
+
if (value === null) {
|
|
6
|
+
return "null";
|
|
7
|
+
}
|
|
8
|
+
return typeof value;
|
|
9
|
+
}
|
|
10
|
+
export function isRecord(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
export function createValidationIssue(path, message, severity = "error") {
|
|
14
|
+
return { path, message, severity };
|
|
15
|
+
}
|
|
16
|
+
export function pushValidationIssue(issues, path, message, severity = "error") {
|
|
17
|
+
issues.push(createValidationIssue(path, message, severity));
|
|
18
|
+
}
|
|
19
|
+
export function prefixValidationIssues(prefix, issues) {
|
|
20
|
+
return issues.map((issue) => ({
|
|
21
|
+
...issue,
|
|
22
|
+
path: issue.path.length === 0
|
|
23
|
+
? prefix
|
|
24
|
+
: issue.path === prefix || issue.path.startsWith(`${prefix}.`)
|
|
25
|
+
? issue.path
|
|
26
|
+
: `${prefix}.${issue.path}`,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
export function formatValidationIssues(issues) {
|
|
30
|
+
return issues
|
|
31
|
+
.map((issue) => ` [${issue.severity}] ${issue.path}: ${issue.message}`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
}
|
|
34
|
+
export function requireKeys(value, path, keys) {
|
|
2
35
|
const issues = [];
|
|
36
|
+
if (!isRecord(value)) {
|
|
37
|
+
pushValidationIssue(issues, path, `Expected an object, got ${describeValue(value)}.`);
|
|
38
|
+
return issues;
|
|
39
|
+
}
|
|
3
40
|
for (const key of keys) {
|
|
4
|
-
if (!(key in
|
|
5
|
-
issues
|
|
41
|
+
if (!(key in value)) {
|
|
42
|
+
pushValidationIssue(issues, path, `Missing required key: ${key}`);
|
|
6
43
|
}
|
|
7
44
|
}
|
|
8
45
|
return issues;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type
|
|
1
|
+
import { type SessionConfig } from "../types/sessionConfig.js";
|
|
2
|
+
import { type ValidationIssue } from "./basic.js";
|
|
3
3
|
export declare function validateSessionConfig(value: unknown): ValidationIssue[];
|
|
4
4
|
export declare function validateConfiguredProviderEnvironment(sessionConfig: SessionConfig, options?: {
|
|
5
5
|
commandExists?: (command: string) => boolean;
|
|
6
|
+
pathExists?: (commandPath: string) => boolean;
|
|
6
7
|
}): ValidationIssue[];
|
|
8
|
+
export { formatValidationIssues } from "./basic.js";
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"opencode",
|
|
8
|
-
"vscode-task",
|
|
9
|
-
]);
|
|
10
|
-
const VALID_UI_MODES = new Set(["headless", "visible"]);
|
|
2
|
+
import { accessSync, constants } from "node:fs";
|
|
3
|
+
import { PROVIDER_NAMES, SESSION_UI_MODES, } from "../types/sessionConfig.js";
|
|
4
|
+
import { isRecord, pushValidationIssue, } from "./basic.js";
|
|
5
|
+
const VALID_PROVIDERS = new Set(PROVIDER_NAMES);
|
|
6
|
+
const VALID_UI_MODES = new Set(SESSION_UI_MODES);
|
|
11
7
|
function pushIssue(issues, path, message) {
|
|
12
|
-
issues
|
|
13
|
-
}
|
|
14
|
-
function isRecord(value) {
|
|
15
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
pushValidationIssue(issues, path, message);
|
|
16
9
|
}
|
|
17
10
|
function validateStringArray(value, path, label, issues, options = {}) {
|
|
18
11
|
if (!Array.isArray(value)) {
|
|
@@ -74,6 +67,9 @@ function validateAgentProviderSection(value, path, issues) {
|
|
|
74
67
|
if (typeof value.command !== "string" || value.command.trim().length === 0) {
|
|
75
68
|
pushIssue(issues, `${path}.command`, "command must be a non-empty string when provided.");
|
|
76
69
|
}
|
|
70
|
+
else if (!isSupportedConfiguredCommand(value.command)) {
|
|
71
|
+
pushIssue(issues, `${path}.command`, "command must be a bare executable name or direct executable path. Put CLI flags in extra_args.");
|
|
72
|
+
}
|
|
77
73
|
}
|
|
78
74
|
if (value.extra_args !== undefined) {
|
|
79
75
|
validateStringArray(value.extra_args, `${path}.extra_args`, "extra_args", issues, { allowEmptyArray: true });
|
|
@@ -84,6 +80,43 @@ function commandExists(command) {
|
|
|
84
80
|
const result = spawnSync(lookupCommand, [command], { stdio: "ignore" });
|
|
85
81
|
return result.status === 0;
|
|
86
82
|
}
|
|
83
|
+
function configuredPathExists(commandPath) {
|
|
84
|
+
try {
|
|
85
|
+
accessSync(commandPath, constants.F_OK);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function startsWithPathPrefix(command) {
|
|
93
|
+
return (command.startsWith(".") ||
|
|
94
|
+
command.startsWith("/") ||
|
|
95
|
+
command.startsWith("\\\\") ||
|
|
96
|
+
/^[A-Za-z]:[\\/]/.test(command));
|
|
97
|
+
}
|
|
98
|
+
function containsForbiddenCommandSyntax(command) {
|
|
99
|
+
return /[\r\n"'`|&;<>]/.test(command);
|
|
100
|
+
}
|
|
101
|
+
function isBareExecutableName(command) {
|
|
102
|
+
return (command.length > 0 &&
|
|
103
|
+
!/\s/.test(command) &&
|
|
104
|
+
!containsForbiddenCommandSyntax(command) &&
|
|
105
|
+
!/[\\/]/.test(command) &&
|
|
106
|
+
!/^[A-Za-z]:/.test(command));
|
|
107
|
+
}
|
|
108
|
+
function isDirectExecutablePath(command) {
|
|
109
|
+
return (command.length > 0 &&
|
|
110
|
+
!containsForbiddenCommandSyntax(command) &&
|
|
111
|
+
startsWithPathPrefix(command));
|
|
112
|
+
}
|
|
113
|
+
function isSupportedConfiguredCommand(command) {
|
|
114
|
+
const trimmed = command.trim();
|
|
115
|
+
if (trimmed.length === 0 || trimmed !== command) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return isBareExecutableName(trimmed) || isDirectExecutablePath(trimmed);
|
|
119
|
+
}
|
|
87
120
|
export function validateSessionConfig(value) {
|
|
88
121
|
const issues = [];
|
|
89
122
|
if (value === undefined) {
|
|
@@ -122,18 +155,32 @@ export function validateSessionConfig(value) {
|
|
|
122
155
|
export function validateConfiguredProviderEnvironment(sessionConfig, options = {}) {
|
|
123
156
|
const issues = [];
|
|
124
157
|
const lookupCommand = options.commandExists ?? commandExists;
|
|
158
|
+
const lookupPath = options.pathExists ?? configuredPathExists;
|
|
125
159
|
const provider = sessionConfig.provider ?? "local-subprocess";
|
|
126
160
|
if (provider === "claude-code") {
|
|
127
161
|
const command = sessionConfig.claude_code?.command ?? "claude";
|
|
128
|
-
if (!lookupCommand(command)) {
|
|
162
|
+
if (isBareExecutableName(command) && !lookupCommand(command)) {
|
|
129
163
|
pushIssue(issues, "claude_code.command", `Configured claude-code executable was not found on PATH: ${command}.`);
|
|
130
164
|
}
|
|
165
|
+
else if (isDirectExecutablePath(command) && !lookupPath(command)) {
|
|
166
|
+
pushIssue(issues, "claude_code.command", `Configured claude-code executable path does not exist: ${command}.`);
|
|
167
|
+
}
|
|
168
|
+
else if (!isSupportedConfiguredCommand(command)) {
|
|
169
|
+
pushIssue(issues, "claude_code.command", "Configured claude-code command must be a bare executable name or direct path. Put CLI flags in extra_args.");
|
|
170
|
+
}
|
|
131
171
|
}
|
|
132
172
|
if (provider === "opencode") {
|
|
133
173
|
const command = sessionConfig.opencode?.command ?? "opencode";
|
|
134
|
-
if (!lookupCommand(command)) {
|
|
174
|
+
if (isBareExecutableName(command) && !lookupCommand(command)) {
|
|
135
175
|
pushIssue(issues, "opencode.command", `Configured opencode executable was not found on PATH: ${command}.`);
|
|
136
176
|
}
|
|
177
|
+
else if (isDirectExecutablePath(command) && !lookupPath(command)) {
|
|
178
|
+
pushIssue(issues, "opencode.command", `Configured opencode executable path does not exist: ${command}.`);
|
|
179
|
+
}
|
|
180
|
+
else if (!isSupportedConfiguredCommand(command)) {
|
|
181
|
+
pushIssue(issues, "opencode.command", "Configured opencode command must be a bare executable name or direct path. Put CLI flags in extra_args.");
|
|
182
|
+
}
|
|
137
183
|
}
|
|
138
184
|
return issues;
|
|
139
185
|
}
|
|
186
|
+
export { formatValidationIssues } from "./basic.js";
|