auditor-lambda 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 (199) hide show
  1. package/README.md +173 -0
  2. package/audit-code-wrapper-lib.mjs +905 -0
  3. package/audit-code.mjs +13 -0
  4. package/dist/adapters/coverageSummary.d.ts +8 -0
  5. package/dist/adapters/coverageSummary.js +13 -0
  6. package/dist/adapters/eslint.d.ts +13 -0
  7. package/dist/adapters/eslint.js +21 -0
  8. package/dist/adapters/normalizeExternal.d.ts +12 -0
  9. package/dist/adapters/normalizeExternal.js +19 -0
  10. package/dist/adapters/npmAudit.d.ts +15 -0
  11. package/dist/adapters/npmAudit.js +12 -0
  12. package/dist/adapters/semgrep.d.ts +22 -0
  13. package/dist/adapters/semgrep.js +14 -0
  14. package/dist/cli.d.ts +1 -0
  15. package/dist/cli.js +724 -0
  16. package/dist/coverage.d.ts +11 -0
  17. package/dist/coverage.js +102 -0
  18. package/dist/extractors/bucketing.d.ts +7 -0
  19. package/dist/extractors/bucketing.js +72 -0
  20. package/dist/extractors/disposition.d.ts +4 -0
  21. package/dist/extractors/disposition.js +41 -0
  22. package/dist/extractors/fileInventory.d.ts +7 -0
  23. package/dist/extractors/fileInventory.js +44 -0
  24. package/dist/extractors/flows.d.ts +5 -0
  25. package/dist/extractors/flows.js +125 -0
  26. package/dist/extractors/fsIntake.d.ts +8 -0
  27. package/dist/extractors/fsIntake.js +66 -0
  28. package/dist/extractors/graph.d.ts +4 -0
  29. package/dist/extractors/graph.js +46 -0
  30. package/dist/extractors/ignore.d.ts +1 -0
  31. package/dist/extractors/ignore.js +17 -0
  32. package/dist/extractors/risk.d.ts +5 -0
  33. package/dist/extractors/risk.js +45 -0
  34. package/dist/extractors/surfaces.d.ts +4 -0
  35. package/dist/extractors/surfaces.js +40 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/io/artifacts.d.ts +38 -0
  39. package/dist/io/artifacts.js +100 -0
  40. package/dist/io/json.d.ts +8 -0
  41. package/dist/io/json.js +96 -0
  42. package/dist/io/runArtifacts.d.ts +14 -0
  43. package/dist/io/runArtifacts.js +37 -0
  44. package/dist/orchestrator/advance.d.ts +24 -0
  45. package/dist/orchestrator/advance.js +104 -0
  46. package/dist/orchestrator/artifactMetadata.d.ts +4 -0
  47. package/dist/orchestrator/artifactMetadata.js +111 -0
  48. package/dist/orchestrator/autoFixExecutor.d.ts +3 -0
  49. package/dist/orchestrator/autoFixExecutor.js +63 -0
  50. package/dist/orchestrator/chunking.d.ts +5 -0
  51. package/dist/orchestrator/chunking.js +13 -0
  52. package/dist/orchestrator/dependencyMap.d.ts +1 -0
  53. package/dist/orchestrator/dependencyMap.js +82 -0
  54. package/dist/orchestrator/executors.d.ts +6 -0
  55. package/dist/orchestrator/executors.js +52 -0
  56. package/dist/orchestrator/flowCoverage.d.ts +4 -0
  57. package/dist/orchestrator/flowCoverage.js +44 -0
  58. package/dist/orchestrator/flowPlanning.d.ts +3 -0
  59. package/dist/orchestrator/flowPlanning.js +42 -0
  60. package/dist/orchestrator/flowRequeue.d.ts +5 -0
  61. package/dist/orchestrator/flowRequeue.js +58 -0
  62. package/dist/orchestrator/internalExecutors.d.ts +16 -0
  63. package/dist/orchestrator/internalExecutors.js +212 -0
  64. package/dist/orchestrator/nextStep.d.ts +9 -0
  65. package/dist/orchestrator/nextStep.js +44 -0
  66. package/dist/orchestrator/planning.d.ts +4 -0
  67. package/dist/orchestrator/planning.js +62 -0
  68. package/dist/orchestrator/requeue.d.ts +3 -0
  69. package/dist/orchestrator/requeue.js +25 -0
  70. package/dist/orchestrator/requeueCommand.d.ts +10 -0
  71. package/dist/orchestrator/requeueCommand.js +27 -0
  72. package/dist/orchestrator/resultIngestion.d.ts +2 -0
  73. package/dist/orchestrator/resultIngestion.js +13 -0
  74. package/dist/orchestrator/runtimeValidation.d.ts +7 -0
  75. package/dist/orchestrator/runtimeValidation.js +103 -0
  76. package/dist/orchestrator/runtimeValidationUpdate.d.ts +2 -0
  77. package/dist/orchestrator/runtimeValidationUpdate.js +52 -0
  78. package/dist/orchestrator/staleness.d.ts +2 -0
  79. package/dist/orchestrator/staleness.js +83 -0
  80. package/dist/orchestrator/state.d.ts +3 -0
  81. package/dist/orchestrator/state.js +85 -0
  82. package/dist/orchestrator/syntaxResolutionExecutor.d.ts +3 -0
  83. package/dist/orchestrator/syntaxResolutionExecutor.js +99 -0
  84. package/dist/orchestrator/taskBuilder.d.ts +12 -0
  85. package/dist/orchestrator/taskBuilder.js +154 -0
  86. package/dist/orchestrator/unitBuilder.d.ts +3 -0
  87. package/dist/orchestrator/unitBuilder.js +145 -0
  88. package/dist/orchestrator.d.ts +6 -0
  89. package/dist/orchestrator.js +33 -0
  90. package/dist/prompts/renderWorkerPrompt.d.ts +2 -0
  91. package/dist/prompts/renderWorkerPrompt.js +19 -0
  92. package/dist/providers/claudeCodeProvider.d.ts +8 -0
  93. package/dist/providers/claudeCodeProvider.js +20 -0
  94. package/dist/providers/index.d.ts +7 -0
  95. package/dist/providers/index.js +77 -0
  96. package/dist/providers/localSubprocessProvider.d.ts +5 -0
  97. package/dist/providers/localSubprocessProvider.js +13 -0
  98. package/dist/providers/opencodeProvider.d.ts +8 -0
  99. package/dist/providers/opencodeProvider.js +15 -0
  100. package/dist/providers/spawnLoggedCommand.d.ts +2 -0
  101. package/dist/providers/spawnLoggedCommand.js +48 -0
  102. package/dist/providers/subprocessTemplateProvider.d.ts +8 -0
  103. package/dist/providers/subprocessTemplateProvider.js +41 -0
  104. package/dist/providers/types.d.ts +22 -0
  105. package/dist/providers/types.js +1 -0
  106. package/dist/providers/vscodeTaskProvider.d.ts +8 -0
  107. package/dist/providers/vscodeTaskProvider.js +14 -0
  108. package/dist/reporting/mergeFindings.d.ts +4 -0
  109. package/dist/reporting/mergeFindings.js +136 -0
  110. package/dist/reporting/rootCause.d.ts +11 -0
  111. package/dist/reporting/rootCause.js +69 -0
  112. package/dist/reporting/synthesis.d.ts +21 -0
  113. package/dist/reporting/synthesis.js +55 -0
  114. package/dist/supervisor/operatorHandoff.d.ts +37 -0
  115. package/dist/supervisor/operatorHandoff.js +144 -0
  116. package/dist/supervisor/runLedger.d.ts +3 -0
  117. package/dist/supervisor/runLedger.js +17 -0
  118. package/dist/supervisor/sessionConfig.d.ts +4 -0
  119. package/dist/supervisor/sessionConfig.js +26 -0
  120. package/dist/types/artifactMetadata.d.ts +8 -0
  121. package/dist/types/artifactMetadata.js +1 -0
  122. package/dist/types/auditState.d.ts +14 -0
  123. package/dist/types/auditState.js +1 -0
  124. package/dist/types/disposition.d.ts +9 -0
  125. package/dist/types/disposition.js +1 -0
  126. package/dist/types/externalAnalyzer.d.ts +16 -0
  127. package/dist/types/externalAnalyzer.js +1 -0
  128. package/dist/types/flowCoverage.d.ts +11 -0
  129. package/dist/types/flowCoverage.js +1 -0
  130. package/dist/types/flows.d.ts +11 -0
  131. package/dist/types/flows.js +1 -0
  132. package/dist/types/graph.d.ts +18 -0
  133. package/dist/types/graph.js +1 -0
  134. package/dist/types/risk.d.ts +9 -0
  135. package/dist/types/risk.js +1 -0
  136. package/dist/types/runLedger.d.ts +13 -0
  137. package/dist/types/runLedger.js +1 -0
  138. package/dist/types/runtimeValidation.d.ts +22 -0
  139. package/dist/types/runtimeValidation.js +1 -0
  140. package/dist/types/sessionConfig.d.ts +27 -0
  141. package/dist/types/sessionConfig.js +1 -0
  142. package/dist/types/surfaces.d.ts +11 -0
  143. package/dist/types/surfaces.js +1 -0
  144. package/dist/types/workerResult.d.ts +13 -0
  145. package/dist/types/workerResult.js +1 -0
  146. package/dist/types/workerSession.d.ts +13 -0
  147. package/dist/types/workerSession.js +1 -0
  148. package/dist/types.d.ts +104 -0
  149. package/dist/types.js +1 -0
  150. package/dist/validation/artifacts.d.ts +3 -0
  151. package/dist/validation/artifacts.js +191 -0
  152. package/dist/validation/basic.d.ts +5 -0
  153. package/dist/validation/basic.js +9 -0
  154. package/dist/validation/sessionConfig.d.ts +6 -0
  155. package/dist/validation/sessionConfig.js +139 -0
  156. package/docs/agent-integrations.md +237 -0
  157. package/docs/agent-roles.md +69 -0
  158. package/docs/architecture.md +90 -0
  159. package/docs/artifacts.md +69 -0
  160. package/docs/bootstrap-install.md +79 -0
  161. package/docs/contract.md +140 -0
  162. package/docs/github-copilot.md +50 -0
  163. package/docs/model-selection.md +86 -0
  164. package/docs/next-steps.md +161 -0
  165. package/docs/packaging.md +88 -0
  166. package/docs/pipeline.md +152 -0
  167. package/docs/product-direction.md +111 -0
  168. package/docs/production-launch-bar.md +83 -0
  169. package/docs/production-readiness.md +52 -0
  170. package/docs/repo-layout.md +30 -0
  171. package/docs/run-flow.md +49 -0
  172. package/docs/session-config.md +232 -0
  173. package/docs/supervisor.md +83 -0
  174. package/docs/usage.md +172 -0
  175. package/docs/windows-setup.md +146 -0
  176. package/package.json +56 -0
  177. package/schemas/audit-code-v1alpha1.schema.json +191 -0
  178. package/schemas/audit_result.schema.json +48 -0
  179. package/schemas/audit_state.schema.json +36 -0
  180. package/schemas/audit_task.schema.json +49 -0
  181. package/schemas/blind_spot_register.schema.json +40 -0
  182. package/schemas/coverage_matrix.schema.json +50 -0
  183. package/schemas/critical_flows.schema.json +38 -0
  184. package/schemas/external_analyzer_results.schema.json +31 -0
  185. package/schemas/file_disposition.schema.json +33 -0
  186. package/schemas/finding.schema.json +62 -0
  187. package/schemas/flow_coverage.schema.json +44 -0
  188. package/schemas/graph_bundle.schema.json +55 -0
  189. package/schemas/merged_findings.schema.json +14 -0
  190. package/schemas/repo_manifest.schema.json +37 -0
  191. package/schemas/risk_register.schema.json +30 -0
  192. package/schemas/root_cause_clusters.schema.json +31 -0
  193. package/schemas/runtime_validation_report.schema.json +34 -0
  194. package/schemas/runtime_validation_tasks.schema.json +36 -0
  195. package/schemas/surface_manifest.schema.json +32 -0
  196. package/schemas/synthesis_report.schema.json +61 -0
  197. package/schemas/unit_manifest.schema.json +36 -0
  198. package/skills/audit-code/SKILL.md +54 -0
  199. package/skills/audit-code/audit-code.prompt.md +66 -0
@@ -0,0 +1,18 @@
1
+ export interface GraphEdge {
2
+ from: string;
3
+ to: string;
4
+ kind?: string;
5
+ }
6
+ export interface RouteEdge {
7
+ path: string;
8
+ handler: string;
9
+ method?: string;
10
+ }
11
+ export interface GraphBundle {
12
+ graphs: {
13
+ imports?: GraphEdge[];
14
+ calls?: GraphEdge[];
15
+ routes?: RouteEdge[];
16
+ [key: string]: unknown;
17
+ };
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export interface RiskItem {
2
+ unit_id: string;
3
+ risk_score: number;
4
+ signals: string[];
5
+ notes?: string[];
6
+ }
7
+ export interface RiskRegister {
8
+ items: RiskItem[];
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface RunLedgerEntry {
2
+ run_id: string;
3
+ provider: string;
4
+ obligation_id: string | null;
5
+ selected_executor: string | null;
6
+ status: string;
7
+ started_at: string;
8
+ ended_at: string;
9
+ result_path: string;
10
+ }
11
+ export interface RunLedger {
12
+ runs: RunLedgerEntry[];
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ export interface RuntimeValidationTask {
2
+ id: string;
3
+ kind: string;
4
+ target_paths: string[];
5
+ reason: string;
6
+ priority: "high" | "medium" | "low";
7
+ suggested_checks?: string[];
8
+ source_artifacts?: string[];
9
+ }
10
+ export interface RuntimeValidationTaskManifest {
11
+ tasks: RuntimeValidationTask[];
12
+ }
13
+ export interface RuntimeValidationResult {
14
+ task_id: string;
15
+ status: "pending" | "confirmed" | "not_confirmed" | "inconclusive";
16
+ summary: string;
17
+ evidence?: string[];
18
+ notes?: string[];
19
+ }
20
+ export interface RuntimeValidationReport {
21
+ results: RuntimeValidationResult[];
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ export type ProviderName = "auto" | "local-subprocess" | "subprocess-template" | "claude-code" | "opencode" | "vscode-task";
2
+ export type ResolvedProviderName = Exclude<ProviderName, "auto">;
3
+ export interface SubprocessTemplateConfig {
4
+ command_template: string[];
5
+ env?: Record<string, string>;
6
+ }
7
+ export interface ClaudeCodeConfig {
8
+ command?: string;
9
+ extra_args?: string[];
10
+ }
11
+ export interface OpenCodeConfig {
12
+ command?: string;
13
+ extra_args?: string[];
14
+ }
15
+ export interface VSCodeTaskConfig {
16
+ command_template: string[];
17
+ env?: Record<string, string>;
18
+ }
19
+ export interface SessionConfig {
20
+ provider?: ProviderName;
21
+ timeout_ms?: number;
22
+ ui_mode?: "visible" | "headless";
23
+ subprocess_template?: SubprocessTemplateConfig;
24
+ claude_code?: ClaudeCodeConfig;
25
+ opencode?: OpenCodeConfig;
26
+ vscode_task?: VSCodeTaskConfig;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ export interface SurfaceRecord {
2
+ id: string;
3
+ kind: string;
4
+ entrypoint: string;
5
+ exposure?: string;
6
+ methods?: string[];
7
+ notes?: string[];
8
+ }
9
+ export interface SurfaceManifest {
10
+ surfaces: SurfaceRecord[];
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export type WorkerResultStatus = "completed" | "blocked" | "failed" | "no_progress";
2
+ export interface WorkerResult {
3
+ contract_version: "audit-code-worker-result/v1alpha1";
4
+ run_id: string;
5
+ obligation_id: string | null;
6
+ status: WorkerResultStatus;
7
+ progress_made: boolean;
8
+ selected_executor: string | null;
9
+ artifacts_written: string[];
10
+ summary: string;
11
+ next_likely_step: string | null;
12
+ errors: string[];
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export interface WorkerTask {
2
+ contract_version: "audit-code-worker/v1alpha1";
3
+ run_id: string;
4
+ repo_root: string;
5
+ artifacts_dir: string;
6
+ obligation_id: string | null;
7
+ preferred_executor: string;
8
+ result_path: string;
9
+ worker_command: string[];
10
+ audit_results_path?: string;
11
+ runtime_updates_path?: string;
12
+ external_analyzer_results_path?: string;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,104 @@
1
+ export type Lens = "correctness" | "architecture" | "maintainability" | "security" | "reliability" | "performance" | "data_integrity" | "tests" | "operability" | "config_deployment";
2
+ export interface FileRecord {
3
+ path: string;
4
+ language: string;
5
+ size_bytes: number;
6
+ hash?: string;
7
+ excluded?: boolean;
8
+ exclusion_reason?: string;
9
+ }
10
+ export interface RepoManifest {
11
+ repository: {
12
+ name: string;
13
+ root?: string;
14
+ default_branch?: string;
15
+ };
16
+ generated_at: string;
17
+ files: FileRecord[];
18
+ }
19
+ export interface AuditUnit {
20
+ unit_id: string;
21
+ name: string;
22
+ kind?: string;
23
+ files: string[];
24
+ risk_score?: number;
25
+ required_lenses: Lens[];
26
+ critical_flows?: string[];
27
+ }
28
+ export interface UnitManifest {
29
+ units: AuditUnit[];
30
+ }
31
+ export interface ReviewedLineRange {
32
+ path: string;
33
+ start: number;
34
+ end: number;
35
+ pass_id: string;
36
+ lens?: Lens;
37
+ agent_role?: string;
38
+ }
39
+ export interface CoverageFileRecord {
40
+ path: string;
41
+ unit_ids: string[];
42
+ classification_status: string;
43
+ audit_status: string;
44
+ required_lenses: Lens[];
45
+ completed_lenses: Lens[];
46
+ reviewed_line_ranges: ReviewedLineRange[];
47
+ }
48
+ export interface CoverageMatrix {
49
+ files: CoverageFileRecord[];
50
+ }
51
+ export interface AuditTask {
52
+ task_id: string;
53
+ unit_id: string;
54
+ pass_id: string;
55
+ lens: Lens;
56
+ file_paths: string[];
57
+ line_ranges?: Array<{
58
+ path: string;
59
+ start: number;
60
+ end: number;
61
+ }>;
62
+ inputs?: Record<string, string>;
63
+ rationale: string;
64
+ priority?: "high" | "medium" | "low";
65
+ tags?: string[];
66
+ }
67
+ export interface Finding {
68
+ id: string;
69
+ title: string;
70
+ category: string;
71
+ severity: "critical" | "high" | "medium" | "low" | "info";
72
+ confidence: "high" | "medium" | "low";
73
+ lens: Lens;
74
+ summary: string;
75
+ affected_files: Array<{
76
+ path: string;
77
+ line_start?: number;
78
+ line_end?: number;
79
+ symbol?: string;
80
+ }>;
81
+ impact?: string;
82
+ likelihood?: string;
83
+ evidence?: string[];
84
+ reproduction?: string[];
85
+ remediation?: string[];
86
+ systemic?: boolean;
87
+ related_findings?: string[];
88
+ }
89
+ export interface AuditResult {
90
+ task_id: string;
91
+ unit_id: string;
92
+ pass_id: string;
93
+ lens: Lens;
94
+ agent_role?: string;
95
+ reviewed_ranges: Array<{
96
+ path: string;
97
+ start: number;
98
+ end: number;
99
+ }>;
100
+ findings: Finding[];
101
+ notes?: string[];
102
+ requires_followup?: boolean;
103
+ followup_tasks?: string[];
104
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { ArtifactBundle } from "../io/artifacts.js";
2
+ import type { ValidationIssue } from "./basic.js";
3
+ export declare function validateArtifactBundle(bundle: ArtifactBundle): ValidationIssue[];
@@ -0,0 +1,191 @@
1
+ import { requireKeys } from "./basic.js";
2
+ function pushIssue(issues, path, message) {
3
+ issues.push({ path, message });
4
+ }
5
+ export function validateArtifactBundle(bundle) {
6
+ const issues = [];
7
+ if (bundle.repo_manifest) {
8
+ issues.push(...requireKeys(bundle.repo_manifest, "repo_manifest", ["repository", "generated_at", "files"]));
9
+ }
10
+ if (bundle.unit_manifest) {
11
+ issues.push(...requireKeys(bundle.unit_manifest, "unit_manifest", ["units"]));
12
+ }
13
+ if (bundle.coverage_matrix) {
14
+ issues.push(...requireKeys(bundle.coverage_matrix, "coverage_matrix", ["files"]));
15
+ }
16
+ if (bundle.graph_bundle) {
17
+ issues.push(...requireKeys(bundle.graph_bundle, "graph_bundle", ["graphs"]));
18
+ }
19
+ if (bundle.surface_manifest) {
20
+ issues.push(...requireKeys(bundle.surface_manifest, "surface_manifest", ["surfaces"]));
21
+ }
22
+ if (bundle.critical_flows) {
23
+ issues.push(...requireKeys(bundle.critical_flows, "critical_flows", ["flows"]));
24
+ }
25
+ if (bundle.flow_coverage) {
26
+ issues.push(...requireKeys(bundle.flow_coverage, "flow_coverage", ["flows"]));
27
+ }
28
+ if (bundle.risk_register) {
29
+ issues.push(...requireKeys(bundle.risk_register, "risk_register", ["items"]));
30
+ }
31
+ if (bundle.runtime_validation_tasks) {
32
+ issues.push(...requireKeys(bundle.runtime_validation_tasks, "runtime_validation_tasks", ["tasks"]));
33
+ }
34
+ if (bundle.runtime_validation_report) {
35
+ issues.push(...requireKeys(bundle.runtime_validation_report, "runtime_validation_report", ["results"]));
36
+ }
37
+ if (bundle.external_analyzer_results) {
38
+ issues.push(...requireKeys(bundle.external_analyzer_results, "external_analyzer_results", ["tool", "results"]));
39
+ }
40
+ const repoPaths = new Set(bundle.repo_manifest?.files.map((file) => file.path) ?? []);
41
+ const dispositionMap = new Map(bundle.file_disposition?.files.map((item) => [item.path, item.status]) ??
42
+ []);
43
+ const unitIds = new Set(bundle.unit_manifest?.units.map((unit) => unit.unit_id) ?? []);
44
+ const flowIds = new Set(bundle.critical_flows?.flows.map((flow) => flow.id) ?? []);
45
+ const runtimeTaskIds = new Set(bundle.runtime_validation_tasks?.tasks.map((task) => task.id) ?? []);
46
+ if (bundle.repo_manifest && bundle.coverage_matrix) {
47
+ const coveragePaths = new Set(bundle.coverage_matrix.files.map((file) => file.path));
48
+ for (const path of repoPaths) {
49
+ if (!coveragePaths.has(path)) {
50
+ pushIssue(issues, "coverage_matrix", `Missing coverage entry for ${path}`);
51
+ }
52
+ }
53
+ }
54
+ if (bundle.repo_manifest && bundle.file_disposition) {
55
+ const dispositionPaths = new Set(bundle.file_disposition.files.map((file) => file.path));
56
+ for (const path of repoPaths) {
57
+ if (!dispositionPaths.has(path)) {
58
+ pushIssue(issues, "file_disposition", `Missing disposition entry for ${path}`);
59
+ }
60
+ }
61
+ }
62
+ if (bundle.unit_manifest) {
63
+ for (const unit of bundle.unit_manifest.units) {
64
+ if (unit.files.length === 0) {
65
+ pushIssue(issues, `unit_manifest:${unit.unit_id}`, "Unit has no files");
66
+ }
67
+ if (unit.required_lenses.length === 0) {
68
+ pushIssue(issues, `unit_manifest:${unit.unit_id}`, "Unit has no required lenses");
69
+ }
70
+ for (const path of unit.files) {
71
+ if (!repoPaths.has(path)) {
72
+ pushIssue(issues, `unit_manifest:${unit.unit_id}`, `Unit references unknown file ${path}`);
73
+ }
74
+ const disposition = dispositionMap.get(path);
75
+ if (disposition && disposition !== "included") {
76
+ pushIssue(issues, `unit_manifest:${unit.unit_id}`, `Unit includes non-included file ${path} with disposition ${disposition}`);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ if (bundle.coverage_matrix && bundle.unit_manifest) {
82
+ for (const file of bundle.coverage_matrix.files) {
83
+ if (!repoPaths.has(file.path)) {
84
+ pushIssue(issues, "coverage_matrix", `Coverage contains unknown file ${file.path}`);
85
+ }
86
+ for (const unitId of file.unit_ids) {
87
+ if (!unitIds.has(unitId)) {
88
+ pushIssue(issues, `coverage_matrix:${file.path}`, `Coverage references unknown unit ${unitId}`);
89
+ }
90
+ }
91
+ const disposition = dispositionMap.get(file.path);
92
+ if (disposition &&
93
+ disposition !== "included" &&
94
+ file.audit_status !== "excluded") {
95
+ pushIssue(issues, `coverage_matrix:${file.path}`, `Non-included file should be excluded in coverage; found status ${file.audit_status}`);
96
+ }
97
+ for (const lens of file.completed_lenses) {
98
+ if (!file.required_lenses.includes(lens) &&
99
+ file.audit_status !== "excluded") {
100
+ pushIssue(issues, `coverage_matrix:${file.path}`, `Completed lens ${lens} is not listed in required_lenses`);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ if (bundle.critical_flows) {
106
+ for (const flow of bundle.critical_flows.flows) {
107
+ if (flow.paths.length === 0) {
108
+ pushIssue(issues, `critical_flows:${flow.id}`, "Flow has no paths");
109
+ }
110
+ if (flow.entrypoints.length === 0) {
111
+ pushIssue(issues, `critical_flows:${flow.id}`, "Flow has no entrypoints");
112
+ }
113
+ for (const path of flow.paths) {
114
+ if (!repoPaths.has(path)) {
115
+ pushIssue(issues, `critical_flows:${flow.id}`, `Flow references unknown file ${path}`);
116
+ }
117
+ const disposition = dispositionMap.get(path);
118
+ if (disposition && disposition !== "included") {
119
+ pushIssue(issues, `critical_flows:${flow.id}`, `Flow includes non-included file ${path} with disposition ${disposition}`);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ if (bundle.flow_coverage && bundle.critical_flows) {
125
+ for (const flow of bundle.flow_coverage.flows) {
126
+ if (!flowIds.has(flow.flow_id)) {
127
+ pushIssue(issues, `flow_coverage:${flow.flow_id}`, `Flow coverage references unknown flow ${flow.flow_id}`);
128
+ }
129
+ for (const lens of flow.completed_lenses) {
130
+ if (!flow.required_lenses.includes(lens)) {
131
+ pushIssue(issues, `flow_coverage:${flow.flow_id}`, `Completed lens ${lens} is not in required_lenses`);
132
+ }
133
+ }
134
+ const expectedStatus = flow.required_lenses.length > 0 &&
135
+ flow.required_lenses.every((lens) => flow.completed_lenses.includes(lens))
136
+ ? "complete"
137
+ : flow.completed_lenses.length > 0
138
+ ? "partial"
139
+ : "pending";
140
+ if (flow.status !== expectedStatus) {
141
+ pushIssue(issues, `flow_coverage:${flow.flow_id}`, `Flow status ${flow.status} does not match expected ${expectedStatus}`);
142
+ }
143
+ }
144
+ }
145
+ if (bundle.risk_register && bundle.unit_manifest) {
146
+ const riskUnitIds = new Set(bundle.risk_register.items.map((item) => item.unit_id));
147
+ for (const unit of bundle.unit_manifest.units) {
148
+ if (!riskUnitIds.has(unit.unit_id)) {
149
+ pushIssue(issues, "risk_register", `Missing risk entry for unit ${unit.unit_id}`);
150
+ }
151
+ }
152
+ }
153
+ if (bundle.surface_manifest) {
154
+ for (const surface of bundle.surface_manifest.surfaces) {
155
+ if (!repoPaths.has(surface.entrypoint)) {
156
+ pushIssue(issues, `surface_manifest:${surface.id}`, `Surface references unknown entrypoint ${surface.entrypoint}`);
157
+ }
158
+ const disposition = dispositionMap.get(surface.entrypoint);
159
+ if (disposition && disposition !== "included") {
160
+ pushIssue(issues, `surface_manifest:${surface.id}`, `Surface entrypoint ${surface.entrypoint} is not included`);
161
+ }
162
+ }
163
+ }
164
+ if (bundle.runtime_validation_tasks) {
165
+ for (const task of bundle.runtime_validation_tasks.tasks) {
166
+ if (task.target_paths.length === 0) {
167
+ pushIssue(issues, `runtime_validation_tasks:${task.id}`, "Runtime validation task has no target paths");
168
+ }
169
+ for (const path of task.target_paths) {
170
+ if (!repoPaths.has(path)) {
171
+ pushIssue(issues, `runtime_validation_tasks:${task.id}`, `Runtime validation task references unknown path ${path}`);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ if (bundle.runtime_validation_report) {
177
+ for (const result of bundle.runtime_validation_report.results) {
178
+ if (!runtimeTaskIds.has(result.task_id)) {
179
+ pushIssue(issues, `runtime_validation_report:${result.task_id}`, `Runtime validation result references unknown task ${result.task_id}`);
180
+ }
181
+ }
182
+ }
183
+ if (bundle.external_analyzer_results) {
184
+ for (const item of bundle.external_analyzer_results.results) {
185
+ if (!repoPaths.has(item.path) && bundle.repo_manifest) {
186
+ pushIssue(issues, `external_analyzer_results:${item.id}`, `External analyzer result references unknown path ${item.path}`);
187
+ }
188
+ }
189
+ }
190
+ return issues;
191
+ }
@@ -0,0 +1,5 @@
1
+ export interface ValidationIssue {
2
+ path: string;
3
+ message: string;
4
+ }
5
+ export declare function requireKeys(record: Record<string, unknown>, path: string, keys: string[]): ValidationIssue[];
@@ -0,0 +1,9 @@
1
+ export function requireKeys(record, path, keys) {
2
+ const issues = [];
3
+ for (const key of keys) {
4
+ if (!(key in record)) {
5
+ issues.push({ path, message: `Missing required key: ${key}` });
6
+ }
7
+ }
8
+ return issues;
9
+ }
@@ -0,0 +1,6 @@
1
+ import type { SessionConfig } from "../types/sessionConfig.js";
2
+ import type { ValidationIssue } from "./basic.js";
3
+ export declare function validateSessionConfig(value: unknown): ValidationIssue[];
4
+ export declare function validateConfiguredProviderEnvironment(sessionConfig: SessionConfig, options?: {
5
+ commandExists?: (command: string) => boolean;
6
+ }): ValidationIssue[];
@@ -0,0 +1,139 @@
1
+ import { spawnSync } from "node:child_process";
2
+ const VALID_PROVIDERS = new Set([
3
+ "auto",
4
+ "local-subprocess",
5
+ "subprocess-template",
6
+ "claude-code",
7
+ "opencode",
8
+ "vscode-task",
9
+ ]);
10
+ const VALID_UI_MODES = new Set(["headless", "visible"]);
11
+ function pushIssue(issues, path, message) {
12
+ issues.push({ path, message });
13
+ }
14
+ function isRecord(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ function validateStringArray(value, path, label, issues, options = {}) {
18
+ if (!Array.isArray(value)) {
19
+ pushIssue(issues, path, `${label} must be an array of strings.`);
20
+ return;
21
+ }
22
+ if (!options.allowEmptyArray && value.length === 0) {
23
+ pushIssue(issues, path, `${label} must not be empty.`);
24
+ }
25
+ for (const [index, item] of value.entries()) {
26
+ if (typeof item !== "string" || item.trim().length === 0) {
27
+ pushIssue(issues, `${path}[${index}]`, `${label} entries must be non-empty strings.`);
28
+ }
29
+ }
30
+ }
31
+ function validateEnvOverlay(value, path, issues) {
32
+ if (!isRecord(value)) {
33
+ pushIssue(issues, path, "env must be an object of string values.");
34
+ return;
35
+ }
36
+ for (const [key, entry] of Object.entries(value)) {
37
+ if (typeof entry !== "string") {
38
+ pushIssue(issues, `${path}.${key}`, "Environment override values must be strings.");
39
+ }
40
+ }
41
+ }
42
+ function validateTemplateProviderSection(value, path, issues, required) {
43
+ if (value === undefined) {
44
+ if (required) {
45
+ pushIssue(issues, path, "Provider requires this config section with a non-empty command_template.");
46
+ }
47
+ return;
48
+ }
49
+ if (!isRecord(value)) {
50
+ pushIssue(issues, path, "Provider config must be a JSON object.");
51
+ return;
52
+ }
53
+ if (value.command_template === undefined) {
54
+ if (required) {
55
+ pushIssue(issues, `${path}.command_template`, "command_template is required for this provider.");
56
+ }
57
+ }
58
+ else {
59
+ validateStringArray(value.command_template, `${path}.command_template`, "command_template", issues);
60
+ }
61
+ if (value.env !== undefined) {
62
+ validateEnvOverlay(value.env, `${path}.env`, issues);
63
+ }
64
+ }
65
+ function validateAgentProviderSection(value, path, issues) {
66
+ if (value === undefined) {
67
+ return;
68
+ }
69
+ if (!isRecord(value)) {
70
+ pushIssue(issues, path, "Provider config must be a JSON object.");
71
+ return;
72
+ }
73
+ if (value.command !== undefined) {
74
+ if (typeof value.command !== "string" || value.command.trim().length === 0) {
75
+ pushIssue(issues, `${path}.command`, "command must be a non-empty string when provided.");
76
+ }
77
+ }
78
+ if (value.extra_args !== undefined) {
79
+ validateStringArray(value.extra_args, `${path}.extra_args`, "extra_args", issues, { allowEmptyArray: true });
80
+ }
81
+ }
82
+ function commandExists(command) {
83
+ const lookupCommand = process.platform === "win32" ? "where" : "which";
84
+ const result = spawnSync(lookupCommand, [command], { stdio: "ignore" });
85
+ return result.status === 0;
86
+ }
87
+ export function validateSessionConfig(value) {
88
+ const issues = [];
89
+ if (value === undefined) {
90
+ return issues;
91
+ }
92
+ if (!isRecord(value)) {
93
+ pushIssue(issues, "session_config", "Session config must be a JSON object.");
94
+ return issues;
95
+ }
96
+ const provider = value.provider;
97
+ if (provider !== undefined) {
98
+ if (typeof provider !== "string") {
99
+ pushIssue(issues, "provider", "provider must be a string.");
100
+ }
101
+ else if (!VALID_PROVIDERS.has(provider)) {
102
+ pushIssue(issues, "provider", `Unsupported provider "${provider}". Expected one of: ${Array.from(VALID_PROVIDERS).join(", ")}.`);
103
+ }
104
+ }
105
+ const timeoutMs = value.timeout_ms;
106
+ if (timeoutMs !== undefined &&
107
+ (!Number.isInteger(timeoutMs) || Number(timeoutMs) <= 0)) {
108
+ pushIssue(issues, "timeout_ms", "timeout_ms must be a positive integer number of milliseconds.");
109
+ }
110
+ const uiMode = value.ui_mode;
111
+ if (uiMode !== undefined) {
112
+ if (typeof uiMode !== "string" || !VALID_UI_MODES.has(uiMode)) {
113
+ pushIssue(issues, "ui_mode", `ui_mode must be one of: ${Array.from(VALID_UI_MODES).join(", ")}.`);
114
+ }
115
+ }
116
+ validateTemplateProviderSection(value.subprocess_template, "subprocess_template", issues, provider === "subprocess-template");
117
+ validateTemplateProviderSection(value.vscode_task, "vscode_task", issues, provider === "vscode-task");
118
+ validateAgentProviderSection(value.claude_code, "claude_code", issues);
119
+ validateAgentProviderSection(value.opencode, "opencode", issues);
120
+ return issues;
121
+ }
122
+ export function validateConfiguredProviderEnvironment(sessionConfig, options = {}) {
123
+ const issues = [];
124
+ const lookupCommand = options.commandExists ?? commandExists;
125
+ const provider = sessionConfig.provider ?? "local-subprocess";
126
+ if (provider === "claude-code") {
127
+ const command = sessionConfig.claude_code?.command ?? "claude";
128
+ if (!lookupCommand(command)) {
129
+ pushIssue(issues, "claude_code.command", `Configured claude-code executable was not found on PATH: ${command}.`);
130
+ }
131
+ }
132
+ if (provider === "opencode") {
133
+ const command = sessionConfig.opencode?.command ?? "opencode";
134
+ if (!lookupCommand(command)) {
135
+ pushIssue(issues, "opencode.command", `Configured opencode executable was not found on PATH: ${command}.`);
136
+ }
137
+ }
138
+ return issues;
139
+ }