auditor-lambda 0.10.3 → 0.10.7

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 (183) hide show
  1. package/audit-code-wrapper-build.mjs +198 -0
  2. package/audit-code-wrapper-install-hosts.mjs +1140 -0
  3. package/audit-code-wrapper-io.mjs +155 -0
  4. package/audit-code-wrapper-legacy.mjs +125 -0
  5. package/audit-code-wrapper-lib.mjs +17 -1801
  6. package/audit-code-wrapper-opencode.mjs +256 -0
  7. package/dispatch/merge-results.mjs +5 -3
  8. package/dispatch/validate-result.mjs +2 -2
  9. package/dist/adapters/coverageSummary.js +6 -2
  10. package/dist/adapters/normalizeExternal.js +16 -1
  11. package/dist/adapters/npmAudit.js +20 -9
  12. package/dist/adapters/semgrep.js +26 -1
  13. package/dist/cli/advanceAuditCommand.d.ts +1 -0
  14. package/dist/cli/advanceAuditCommand.js +95 -0
  15. package/dist/cli/args.js +1 -2
  16. package/dist/cli/auditStep.js +2 -2
  17. package/dist/cli/cleanup.d.ts +11 -1
  18. package/dist/cli/cleanup.js +25 -5
  19. package/dist/cli/cleanupCommand.d.ts +1 -0
  20. package/dist/cli/cleanupCommand.js +24 -0
  21. package/dist/cli/dispatch.d.ts +55 -31
  22. package/dist/cli/dispatch.js +298 -241
  23. package/dist/cli/dispatchStatusCommand.d.ts +1 -0
  24. package/dist/cli/dispatchStatusCommand.js +68 -0
  25. package/dist/cli/explainTaskCommand.d.ts +1 -0
  26. package/dist/cli/explainTaskCommand.js +33 -0
  27. package/dist/cli/importExternalAnalyzerCommand.d.ts +1 -0
  28. package/dist/cli/importExternalAnalyzerCommand.js +20 -0
  29. package/dist/cli/ingestResultsCommand.d.ts +1 -0
  30. package/dist/cli/ingestResultsCommand.js +34 -0
  31. package/dist/cli/intakeCommand.d.ts +1 -0
  32. package/dist/cli/intakeCommand.js +17 -0
  33. package/dist/cli/lineIndex.js +19 -12
  34. package/dist/cli/nextStepCommand.d.ts +139 -0
  35. package/dist/cli/nextStepCommand.js +281 -232
  36. package/dist/cli/planCommand.d.ts +1 -0
  37. package/dist/cli/planCommand.js +16 -0
  38. package/dist/cli/prepareDispatchCommand.d.ts +1 -0
  39. package/dist/cli/prepareDispatchCommand.js +25 -0
  40. package/dist/cli/quotaCommand.d.ts +1 -0
  41. package/dist/cli/quotaCommand.js +56 -0
  42. package/dist/cli/requeueCommand.d.ts +1 -0
  43. package/dist/cli/requeueCommand.js +10 -0
  44. package/dist/cli/runToCompletion.js +451 -412
  45. package/dist/cli/sampleRunCommand.d.ts +1 -0
  46. package/dist/cli/sampleRunCommand.js +93 -0
  47. package/dist/cli/statusCommand.js +1 -1
  48. package/dist/cli/steps.js +4 -1
  49. package/dist/cli/submitPacketCommand.js +16 -15
  50. package/dist/cli/synthesizeCommand.d.ts +1 -0
  51. package/dist/cli/synthesizeCommand.js +15 -0
  52. package/dist/cli/updateRuntimeValidationCommand.d.ts +1 -0
  53. package/dist/cli/updateRuntimeValidationCommand.js +16 -0
  54. package/dist/cli/validateCommand.d.ts +1 -0
  55. package/dist/cli/validateCommand.js +41 -0
  56. package/dist/cli/validateResultCommand.d.ts +1 -0
  57. package/dist/cli/validateResultCommand.js +63 -0
  58. package/dist/cli/validateResultsCommand.d.ts +1 -0
  59. package/dist/cli/validateResultsCommand.js +31 -0
  60. package/dist/cli/workerRunCommand.d.ts +15 -1
  61. package/dist/cli/workerRunCommand.js +40 -4
  62. package/dist/cli.d.ts +3 -2
  63. package/dist/cli.js +21 -628
  64. package/dist/coverage.js +7 -3
  65. package/dist/extractors/analyzers/css.js +2 -2
  66. package/dist/extractors/analyzers/html.js +2 -2
  67. package/dist/extractors/analyzers/python.js +2 -2
  68. package/dist/extractors/analyzers/registry.js +17 -36
  69. package/dist/extractors/analyzers/treeSitter.d.ts +10 -1
  70. package/dist/extractors/analyzers/treeSitter.js +28 -6
  71. package/dist/extractors/analyzers/typescript.js +104 -85
  72. package/dist/extractors/browserExtension.js +4 -1
  73. package/dist/extractors/designAssessment.js +21 -21
  74. package/dist/extractors/fsIntake.js +34 -10
  75. package/dist/extractors/graph.js +17 -7
  76. package/dist/extractors/graphManifestEdges/cargo.d.ts +4 -0
  77. package/dist/extractors/graphManifestEdges/cargo.js +107 -0
  78. package/dist/extractors/graphManifestEdges/go.d.ts +5 -0
  79. package/dist/extractors/graphManifestEdges/go.js +151 -0
  80. package/dist/extractors/graphManifestEdges/index.d.ts +8 -0
  81. package/dist/extractors/graphManifestEdges/index.js +11 -0
  82. package/dist/extractors/graphManifestEdges/jsonc.d.ts +3 -0
  83. package/dist/extractors/graphManifestEdges/jsonc.js +97 -0
  84. package/dist/extractors/graphManifestEdges/maven.d.ts +3 -0
  85. package/dist/extractors/graphManifestEdges/maven.js +73 -0
  86. package/dist/extractors/graphManifestEdges/packageJson.d.ts +19 -0
  87. package/dist/extractors/graphManifestEdges/packageJson.js +204 -0
  88. package/dist/extractors/graphManifestEdges/pnpm.d.ts +2 -0
  89. package/dist/extractors/graphManifestEdges/pnpm.js +42 -0
  90. package/dist/extractors/graphManifestEdges/pyproject.d.ts +3 -0
  91. package/dist/extractors/graphManifestEdges/pyproject.js +83 -0
  92. package/dist/extractors/graphManifestEdges/toml.d.ts +4 -0
  93. package/dist/extractors/graphManifestEdges/toml.js +68 -0
  94. package/dist/extractors/graphManifestEdges/typescript.d.ts +3 -0
  95. package/dist/extractors/graphManifestEdges/typescript.js +56 -0
  96. package/dist/extractors/graphManifestEdges/workspace.d.ts +10 -0
  97. package/dist/extractors/graphManifestEdges/workspace.js +72 -0
  98. package/dist/extractors/graphManifestEdges/yaml.d.ts +3 -0
  99. package/dist/extractors/graphManifestEdges/yaml.js +59 -0
  100. package/dist/extractors/graphManifestEdges/yamlPaths.d.ts +4 -0
  101. package/dist/extractors/graphManifestEdges/yamlPaths.js +89 -0
  102. package/dist/extractors/graphPythonImports.js +4 -20
  103. package/dist/extractors/pathPatterns.js +3 -13
  104. package/dist/io/artifacts.d.ts +1 -1
  105. package/dist/io/artifacts.js +4 -1
  106. package/dist/io/runArtifacts.d.ts +8 -2
  107. package/dist/io/runArtifacts.js +103 -69
  108. package/dist/io/toolingManifest.js +2 -1
  109. package/dist/orchestrator/advance.js +36 -0
  110. package/dist/orchestrator/artifactFreshness.d.ts +1 -1
  111. package/dist/orchestrator/artifactFreshness.js +1 -1
  112. package/dist/orchestrator/artifactMetadata.js +5 -5
  113. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  114. package/dist/orchestrator/auditTaskUtils.js +8 -12
  115. package/dist/orchestrator/autoFixExecutor.js +40 -26
  116. package/dist/orchestrator/dependencyMap.js +1 -1
  117. package/dist/orchestrator/executorResult.d.ts +33 -0
  118. package/dist/orchestrator/executors.d.ts +7 -0
  119. package/dist/orchestrator/executors.js +24 -0
  120. package/dist/orchestrator/fileAnchors.js +42 -29
  121. package/dist/orchestrator/fileIntegrity.js +6 -1
  122. package/dist/orchestrator/flowCoverage.js +1 -2
  123. package/dist/orchestrator/flowPlanning.js +8 -4
  124. package/dist/orchestrator/graphEnrichmentExecutor.js +67 -45
  125. package/dist/orchestrator/ingestionExecutors.js +9 -1
  126. package/dist/orchestrator/intakeExecutors.d.ts +0 -4
  127. package/dist/orchestrator/intakeExecutors.js +24 -14
  128. package/dist/orchestrator/localCommands.d.ts +1 -0
  129. package/dist/orchestrator/localCommands.js +10 -17
  130. package/dist/orchestrator/nextStep.js +3 -1
  131. package/dist/orchestrator/requeueCommand.js +4 -0
  132. package/dist/orchestrator/reviewPacketGraph.js +50 -18
  133. package/dist/orchestrator/reviewPackets.js +10 -8
  134. package/dist/orchestrator/runtimeCommand.js +35 -7
  135. package/dist/orchestrator/runtimeValidationUpdate.js +6 -0
  136. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +3 -2
  137. package/dist/orchestrator/selectiveDeepening/lensVerification.js +44 -18
  138. package/dist/orchestrator/staleness.js +3 -3
  139. package/dist/orchestrator/state.js +1 -1
  140. package/dist/orchestrator/syntaxResolutionExecutor.js +17 -24
  141. package/dist/orchestrator/synthesisExecutors.js +1 -0
  142. package/dist/orchestrator/taskBuilder.js +5 -4
  143. package/dist/providers/claudeCodeProvider.js +4 -1
  144. package/dist/providers/opencodeProvider.js +4 -1
  145. package/dist/quota/discoveredLimits.js +3 -3
  146. package/dist/quota/headerExtraction.js +5 -2
  147. package/dist/quota/headerExtractors/claudeCodeHeaderExtractor.js +3 -0
  148. package/dist/quota/headerExtractors/index.js +3 -3
  149. package/dist/quota/index.d.ts +3 -1
  150. package/dist/quota/index.js +3 -0
  151. package/dist/reporting/findingRanks.d.ts +3 -0
  152. package/dist/reporting/findingRanks.js +24 -0
  153. package/dist/reporting/mergeFindings.js +1 -24
  154. package/dist/reporting/synthesis.d.ts +3 -1
  155. package/dist/reporting/synthesis.js +30 -6
  156. package/dist/reporting/synthesisNarrativePrompt.js +3 -0
  157. package/dist/reporting/workBlocks.js +1 -14
  158. package/dist/supervisor/operatorHandoff.js +2 -6
  159. package/dist/supervisor/runLedger.js +30 -41
  160. package/dist/types/activeDispatch.d.ts +31 -0
  161. package/dist/types/activeDispatch.js +2 -0
  162. package/dist/types.d.ts +21 -4
  163. package/dist/types.js +24 -16
  164. package/dist/validation/artifacts.js +3 -0
  165. package/dist/validation/auditResults.js +8 -2
  166. package/package.json +2 -2
  167. package/schemas/audit_findings.schema.json +5 -1
  168. package/schemas/audit_plan_metrics.schema.json +1 -1
  169. package/schemas/audit_result.schema.json +5 -6
  170. package/schemas/audit_task.schema.json +1 -4
  171. package/schemas/blind_spot_register.schema.json +1 -1
  172. package/schemas/coverage_matrix.schema.json +2 -8
  173. package/schemas/finding.schema.json +1 -16
  174. package/schemas/flow_coverage.schema.json +2 -8
  175. package/schemas/graph_bundle.schema.json +31 -0
  176. package/schemas/lens.schema.json +7 -0
  177. package/schemas/review_packets.schema.json +6 -17
  178. package/schemas/step_contract.schema.json +8 -2
  179. package/schemas/unit_manifest.schema.json +1 -4
  180. package/scripts/postinstall.mjs +3 -1
  181. package/skills/audit-code/audit-code.prompt.md +2 -3
  182. package/dist/extractors/graphManifestEdges.d.ts +0 -12
  183. package/dist/extractors/graphManifestEdges.js +0 -1135
@@ -0,0 +1,256 @@
1
+ export const OPENCODE_AUDIT_EDIT_PERMISSION = {
2
+ '*': 'ask',
3
+ '.audit-code/**': 'allow',
4
+ '.audit-artifacts/**': 'allow',
5
+ 'audit-report.md': 'allow',
6
+ };
7
+
8
+ export const OPENCODE_AUDIT_EXTERNAL_DIRECTORY_PERMISSION = { '*': 'allow' };
9
+
10
+ export const OPENCODE_AUDIT_BASH_PERMISSION = {
11
+ '*': 'allow',
12
+ 'audit-code run-to-completion*': 'deny',
13
+ 'audit-code synthesize*': 'deny',
14
+ 'audit-code cleanup*': 'deny',
15
+ 'audit-code requeue*': 'deny',
16
+ 'audit-code ingest-results*': 'deny',
17
+ '*dist*index.js* run-to-completion*': 'deny',
18
+ '*dist*index.js* synthesize*': 'deny',
19
+ '*dist*index.js* cleanup*': 'deny',
20
+ '*dist*index.js* requeue*': 'deny',
21
+ '*dist*index.js* ingest-results*': 'deny',
22
+ '*audit-code.mjs* run-to-completion*': 'deny',
23
+ '*audit-code.mjs* synthesize*': 'deny',
24
+ '*audit-code.mjs* cleanup*': 'deny',
25
+ '*audit-code.mjs* requeue*': 'deny',
26
+ '*audit-code.mjs* ingest-results*': 'deny',
27
+ 'audit-code': 'allow',
28
+ 'audit-code ensure*': 'allow',
29
+ 'audit-code next-step*': 'allow',
30
+ 'audit-code prepare-dispatch*': 'allow',
31
+ 'audit-code submit-packet*': 'allow',
32
+ 'audit-code merge-and-ingest*': 'allow',
33
+ 'audit-code validate*': 'allow',
34
+ '*audit-code.mjs': 'allow',
35
+ '*audit-code.mjs* ensure*': 'allow',
36
+ '*audit-code.mjs* next-step*': 'allow',
37
+ '*audit-code.mjs* prepare-dispatch*': 'allow',
38
+ '*audit-code.mjs* submit-packet*': 'allow',
39
+ '*audit-code.mjs* merge-and-ingest*': 'allow',
40
+ '*audit-code.mjs* worker-run*': 'allow',
41
+ '*audit-code.mjs* validate*': 'allow',
42
+ '*node* *auditor-lambda*dist*index.js* worker-run*': 'allow',
43
+ 'git status*': 'allow',
44
+ 'git diff*': 'allow',
45
+ 'grep *': 'allow',
46
+ 'Select-String *': 'allow',
47
+ 'rm *': 'deny',
48
+ };
49
+
50
+ function externalDirectoryPattern(path) {
51
+ return `${path.replace(/\\/g, '/').replace(/\/+$/u, '')}/**`;
52
+ }
53
+
54
+ function renderOpenCodeExternalDirectoryPermission() {
55
+ return { '*': 'allow' };
56
+ }
57
+
58
+ export function renderOpenCodePermissionConfig() {
59
+ return {
60
+ read: 'allow',
61
+ glob: 'allow',
62
+ grep: 'allow',
63
+ external_directory: renderOpenCodeExternalDirectoryPermission(),
64
+ edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
65
+ bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
66
+ };
67
+ }
68
+
69
+ export function renderOpenCodeProjectConfig(_root) {
70
+ const auditPermission = renderOpenCodePermissionConfig();
71
+ return {
72
+ $schema: 'https://opencode.ai/config.json',
73
+ permission: auditPermission,
74
+ agent: {
75
+ auditor: {
76
+ description:
77
+ 'Read-heavy audit orchestration agent for the /audit-code workflow.',
78
+ permission: {
79
+ ...auditPermission,
80
+ 'auditor_*': 'allow',
81
+ question: 'allow',
82
+ task: 'allow',
83
+ },
84
+ },
85
+ },
86
+ };
87
+ }
88
+
89
+ export function objectValue(value) {
90
+ return value && typeof value === 'object' && !Array.isArray(value)
91
+ ? value
92
+ : {};
93
+ }
94
+
95
+ export function mergeOpenCodePermissionRule(existingRule, generatedRule, managedRules = {}) {
96
+ if (generatedRule && typeof generatedRule === 'object' && !Array.isArray(generatedRule)) {
97
+ const generatedObject = generatedRule;
98
+ const merged = {};
99
+ const existingObject =
100
+ existingRule && typeof existingRule === 'object' && !Array.isArray(existingRule)
101
+ ? existingRule
102
+ : {};
103
+
104
+ if (typeof existingRule === 'string') {
105
+ merged['*'] = existingRule;
106
+ } else {
107
+ merged['*'] = existingObject['*'] ?? generatedObject['*'] ?? 'ask';
108
+ }
109
+
110
+ for (const [key, value] of Object.entries(generatedObject)) {
111
+ if (key !== '*') merged[key] = value;
112
+ }
113
+ for (const [key, value] of Object.entries(existingObject)) {
114
+ if (key !== '*') merged[key] = value;
115
+ }
116
+ for (const [key, value] of Object.entries(managedRules)) {
117
+ merged[key] = value;
118
+ }
119
+
120
+ return merged;
121
+ }
122
+
123
+ return existingRule ?? generatedRule;
124
+ }
125
+
126
+ export function mergeOpenCodePermissionConfig(existingPermission, generatedPermission) {
127
+ if (!existingPermission || typeof existingPermission !== 'object' || Array.isArray(existingPermission)) {
128
+ return generatedPermission;
129
+ }
130
+
131
+ return {
132
+ ...generatedPermission,
133
+ ...existingPermission,
134
+ read: generatedPermission.read,
135
+ glob: generatedPermission.glob,
136
+ grep: generatedPermission.grep,
137
+ external_directory: mergeOpenCodePermissionRule(
138
+ existingPermission.external_directory,
139
+ generatedPermission.external_directory,
140
+ OPENCODE_AUDIT_EXTERNAL_DIRECTORY_PERMISSION,
141
+ ),
142
+ edit: mergeOpenCodePermissionRule(
143
+ existingPermission.edit,
144
+ generatedPermission.edit,
145
+ OPENCODE_AUDIT_EDIT_PERMISSION,
146
+ ),
147
+ bash: mergeOpenCodePermissionRule(
148
+ existingPermission.bash,
149
+ generatedPermission.bash,
150
+ OPENCODE_AUDIT_BASH_PERMISSION,
151
+ ),
152
+ };
153
+ }
154
+
155
+ export function removeManagedOpenCodeCommand(commandConfig) {
156
+ const command = objectValue(commandConfig);
157
+ const { 'audit-code': _managedAuditCodeCommand, ...remaining } = command;
158
+ return remaining;
159
+ }
160
+
161
+ export function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
162
+ for (const tool of ['read', 'glob', 'grep']) {
163
+ if (permissionConfig?.[tool] !== 'allow') {
164
+ throw new Error(`OpenCode ${label}.${tool} must be allow. Run "audit-code install --host opencode".`);
165
+ }
166
+ }
167
+ const externalDirectory = permissionConfig?.external_directory;
168
+ if (!externalDirectory || typeof externalDirectory !== 'object' || Array.isArray(externalDirectory)) {
169
+ throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
170
+ }
171
+ if (externalDirectory['*'] !== 'allow') {
172
+ throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
173
+ }
174
+ const edit = permissionConfig?.edit;
175
+ const bash = permissionConfig?.bash;
176
+ if (!edit || typeof edit !== 'object' || Array.isArray(edit)) {
177
+ throw new Error(`OpenCode ${label}.edit must allow audit-owned file paths. Run "audit-code install --host opencode".`);
178
+ }
179
+ for (const pattern of ['.audit-code/**', '.audit-artifacts/**', 'audit-report.md']) {
180
+ if (edit[pattern] !== 'allow') {
181
+ throw new Error(`OpenCode ${label}.edit must allow ${pattern}. Run "audit-code install --host opencode".`);
182
+ }
183
+ }
184
+ if (!bash || typeof bash !== 'object' || Array.isArray(bash)) {
185
+ throw new Error(`OpenCode ${label}.bash must allow audit-code commands. Run "audit-code install --host opencode".`);
186
+ }
187
+ for (const pattern of [
188
+ 'audit-code',
189
+ 'audit-code ensure*',
190
+ 'audit-code next-step*',
191
+ 'audit-code prepare-dispatch*',
192
+ 'audit-code submit-packet*',
193
+ 'audit-code merge-and-ingest*',
194
+ '*audit-code.mjs',
195
+ '*audit-code.mjs* next-step*',
196
+ '*audit-code.mjs* submit-packet*',
197
+ '*audit-code.mjs* merge-and-ingest*',
198
+ '*audit-code.mjs* worker-run*',
199
+ '*node* *auditor-lambda*dist*index.js* worker-run*',
200
+ ]) {
201
+ if (bash[pattern] !== 'allow') {
202
+ throw new Error(`OpenCode ${label}.bash must allow ${pattern}. Run "audit-code install --host opencode".`);
203
+ }
204
+ }
205
+ for (const pattern of [
206
+ 'audit-code run-to-completion*',
207
+ 'audit-code synthesize*',
208
+ 'audit-code cleanup*',
209
+ 'audit-code requeue*',
210
+ 'audit-code ingest-results*',
211
+ '*dist*index.js* run-to-completion*',
212
+ '*dist*index.js* synthesize*',
213
+ '*dist*index.js* cleanup*',
214
+ '*dist*index.js* requeue*',
215
+ '*dist*index.js* ingest-results*',
216
+ '*audit-code.mjs* run-to-completion*',
217
+ '*audit-code.mjs* synthesize*',
218
+ '*audit-code.mjs* cleanup*',
219
+ '*audit-code.mjs* requeue*',
220
+ '*audit-code.mjs* ingest-results*',
221
+ ]) {
222
+ if (bash[pattern] !== 'deny') {
223
+ throw new Error(`OpenCode ${label}.bash must deny ${pattern}. Run "audit-code install --host opencode".`);
224
+ }
225
+ }
226
+ }
227
+
228
+ export function buildMergedOpenCodeProjectConfig(existing, root) {
229
+ const generated = renderOpenCodeProjectConfig(root);
230
+ const mergedMcp = objectValue(existing.mcp);
231
+ delete mergedMcp.auditor;
232
+ return {
233
+ ...existing,
234
+ $schema: existing.$schema ?? generated.$schema,
235
+ command: removeManagedOpenCodeCommand(existing.command),
236
+ mcp: mergedMcp,
237
+ permission: {
238
+ ...mergeOpenCodePermissionConfig(existing.permission, generated.permission),
239
+ external_directory: { '*': 'allow' },
240
+ },
241
+ agent: {
242
+ ...objectValue(existing.agent),
243
+ auditor: {
244
+ ...objectValue(objectValue(existing.agent).auditor),
245
+ ...generated.agent.auditor,
246
+ permission: {
247
+ ...mergeOpenCodePermissionConfig(
248
+ objectValue(objectValue(existing.agent).auditor).permission,
249
+ generated.agent.auditor.permission,
250
+ ),
251
+ external_directory: { '*': 'allow' },
252
+ },
253
+ },
254
+ },
255
+ };
256
+ }
@@ -27,8 +27,8 @@ if (existsSync(tasksPath)) {
27
27
  for (const task of tasks) {
28
28
  taskMap[task.task_id] = task;
29
29
  }
30
- } catch {
31
- // proceed without task context
30
+ } catch (e) {
31
+ process.stderr.write(`[warn] Could not read pending-audit-tasks.json; line-count validation will be skipped: ${e.message}\n`);
32
32
  }
33
33
  }
34
34
 
@@ -69,7 +69,9 @@ if (failing.length > 0) {
69
69
  writeFileSync(failedTasksPath, JSON.stringify(failing, null, 2));
70
70
  process.stderr.write(`${failing.length} task(s) failed validation and were excluded:\n`);
71
71
  for (const f of failing) {
72
- process.stderr.write(` ✗ ${f.task_id}: ${f.errors[0]}\n`);
72
+ for (const err of f.errors) {
73
+ process.stderr.write(` ✗ ${f.task_id}: ${err}\n`);
74
+ }
73
75
  }
74
76
  }
75
77
 
@@ -40,8 +40,8 @@ if (existsSync(tasksPath)) {
40
40
  try {
41
41
  const tasks = JSON.parse(readFileSync(tasksPath, "utf8"));
42
42
  task = tasks.find(t => t.task_id === taskId) ?? null;
43
- } catch {
44
- // proceed without task context
43
+ } catch (e) {
44
+ process.stderr.write(`[warn] Could not read pending-audit-tasks.json; line-count validation will be skipped: ${e.message}\n`);
45
45
  }
46
46
  }
47
47
 
@@ -1,11 +1,15 @@
1
1
  import { normalizeGenericExternalResults } from "./normalizeExternal.js";
2
+ /** Minimum acceptable line coverage percentage; files below this are flagged. */
3
+ const COVERAGE_THRESHOLD_LOW = 80;
4
+ /** Line coverage percentage below which severity is escalated to "high" (otherwise "medium"). */
5
+ const COVERAGE_SEVERITY_HIGH = 50;
2
6
  export function normalizeCoverageSummary(files) {
3
7
  return normalizeGenericExternalResults("coverage-summary", files
4
- .filter((file) => file.lines_pct < 80)
8
+ .filter((file) => file.lines_pct < COVERAGE_THRESHOLD_LOW)
5
9
  .map((file, index) => ({
6
10
  id: `coverage-${index + 1}`,
7
11
  category: "tests",
8
- severity: file.lines_pct < 50 ? "high" : "medium",
12
+ severity: file.lines_pct < COVERAGE_SEVERITY_HIGH ? "high" : "medium",
9
13
  path: file.path,
10
14
  summary: `Low line coverage: ${file.lines_pct}%${typeof file.branches_pct === "number" ? `, branch coverage ${file.branches_pct}%` : ""}.`,
11
15
  raw: file,
@@ -1,3 +1,18 @@
1
+ function normalizeExternalSeverity(value) {
2
+ switch (value?.toLowerCase()) {
3
+ case "critical": return "critical";
4
+ case "error":
5
+ case "high": return "high";
6
+ case "warning":
7
+ case "moderate":
8
+ case "medium": return "medium";
9
+ case "low": return "low";
10
+ case "info":
11
+ case "note":
12
+ case "hint": return "info";
13
+ default: return "info";
14
+ }
15
+ }
1
16
  export function normalizeGenericExternalResults(tool, items) {
2
17
  const valid = items.filter((item) => item.path && item.summary);
3
18
  const dropped = items.length - valid.length;
@@ -10,7 +25,7 @@ export function normalizeGenericExternalResults(tool, items) {
10
25
  results: valid.map((item, index) => ({
11
26
  id: item.id ?? `${tool}-${index + 1}`,
12
27
  category: item.category ?? "unknown",
13
- severity: item.severity ?? "unknown",
28
+ severity: normalizeExternalSeverity(item.severity),
14
29
  path: item.path,
15
30
  line_start: item.line_start,
16
31
  line_end: item.line_end,
@@ -1,12 +1,23 @@
1
1
  import { normalizeGenericExternalResults } from "./normalizeExternal.js";
2
+ const NPM_SEVERITY_MAP = {
3
+ critical: "critical",
4
+ high: "high",
5
+ moderate: "medium",
6
+ medium: "medium",
7
+ low: "low",
8
+ info: "info",
9
+ };
2
10
  export function normalizeNpmAuditJson(input) {
3
- return normalizeGenericExternalResults("npm-audit", Object.entries(input.vulnerabilities ?? {}).map(([pkg, vuln], index) => ({
4
- id: `npm-audit-${index}`,
5
- category: "dependency_risk",
6
- severity: vuln.severity ?? "unknown",
7
- path: "package-lock.json",
8
- summary: `Package ${pkg} has a ${vuln.severity ?? "unknown"} severity vulnerability in range ${vuln.range ?? "unknown"}.`,
9
- rule: pkg,
10
- raw: vuln,
11
- })));
11
+ return normalizeGenericExternalResults("npm-audit", Object.entries(input.vulnerabilities ?? {}).map(([pkg, vuln], index) => {
12
+ const severity = NPM_SEVERITY_MAP[vuln.severity ?? ""] ?? "low";
13
+ return {
14
+ id: `npm-audit-${index}`,
15
+ category: "dependency_risk",
16
+ severity,
17
+ path: "package-lock.json",
18
+ summary: `Package ${pkg} has a ${severity} severity vulnerability in range ${vuln.range ?? "unknown"}.`,
19
+ rule: pkg,
20
+ raw: vuln,
21
+ };
22
+ }));
12
23
  }
@@ -1,9 +1,34 @@
1
1
  import { normalizeGenericExternalResults } from "./normalizeExternal.js";
2
+ /**
3
+ * Maps semgrep's native uppercase severity strings to the lowercase enum values
4
+ * required by external_analyzer_results.schema.json. Case-insensitive lookup so
5
+ * any casing variant is handled uniformly.
6
+ *
7
+ * Semgrep → schema:
8
+ * CRITICAL → 'critical'
9
+ * ERROR → 'high'
10
+ * WARNING → 'medium'
11
+ * INFO → 'info'
12
+ *
13
+ * Any other / undefined value returns undefined so
14
+ * normalizeGenericExternalResults can apply its own fallback.
15
+ */
16
+ function normalizeSemgrepSeverity(raw) {
17
+ if (raw === undefined)
18
+ return undefined;
19
+ switch (raw.toUpperCase()) {
20
+ case "CRITICAL": return "critical";
21
+ case "ERROR": return "high";
22
+ case "WARNING": return "medium";
23
+ case "INFO": return "info";
24
+ default: return undefined;
25
+ }
26
+ }
2
27
  export function normalizeSemgrepJson(input) {
3
28
  return normalizeGenericExternalResults("semgrep", (input.results ?? []).map((result) => ({
4
29
  id: result.check_id,
5
30
  category: result.extra?.metadata?.category ?? "security",
6
- severity: result.extra?.severity,
31
+ severity: normalizeSemgrepSeverity(result.extra?.severity),
7
32
  path: result.path,
8
33
  line_start: result.start?.line,
9
34
  line_end: result.end?.line,
@@ -0,0 +1 @@
1
+ export declare function cmdAdvanceAudit(argv: string[]): Promise<void>;
@@ -0,0 +1,95 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { promoteFinalAuditReport } from "../io/artifacts.js";
3
+ import { clearDispatchFiles, ensureSupervisorDirs } from "../io/runArtifacts.js";
4
+ import { loadSessionConfig } from "../supervisor/sessionConfig.js";
5
+ import { resolveRunProviderName, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getFlag } from "./args.js";
6
+ import { runAuditStep, ingestBatchAuditResults } from "./auditStep.js";
7
+ import { emitEnvelope } from "./envelope.js";
8
+ import { persistConfigErrorHandoff } from "./reviewRun.js";
9
+ import { cleanupStaleArtifactsDir } from "./cleanup.js";
10
+ export async function cmdAdvanceAudit(argv) {
11
+ const root = getRootDir(argv);
12
+ warnIfNotGitRepo(root);
13
+ const artifactsDir = getArtifactsDir(argv);
14
+ await cleanupStaleArtifactsDir(artifactsDir);
15
+ await mkdir(artifactsDir, { recursive: true });
16
+ await ensureSupervisorDirs(artifactsDir);
17
+ let sessionConfig;
18
+ try {
19
+ sessionConfig = await loadSessionConfig(artifactsDir);
20
+ }
21
+ catch (error) {
22
+ await persistConfigErrorHandoff({
23
+ root,
24
+ artifactsDir,
25
+ progressSummary: error instanceof Error ? error.message : String(error),
26
+ });
27
+ throw error;
28
+ }
29
+ const providerName = resolveRunProviderName(argv, sessionConfig);
30
+ const batchResultsDir = getBatchResultsDir(argv);
31
+ if (batchResultsDir && getFlag(argv, "--results")) {
32
+ throw new Error("Use either --results <file> or --batch-results <dir>, not both.");
33
+ }
34
+ if (batchResultsDir) {
35
+ const result = await ingestBatchAuditResults({
36
+ root,
37
+ artifactsDir,
38
+ batchDir: batchResultsDir,
39
+ });
40
+ if (result.selected_executor !== "agent") {
41
+ await clearDispatchFiles(artifactsDir);
42
+ }
43
+ await emitEnvelope({
44
+ root,
45
+ artifactsDir,
46
+ bundle: result.bundle,
47
+ audit_state: result.audit_state,
48
+ selected_obligation: result.selected_obligation,
49
+ selected_executor: result.selected_executor,
50
+ progress_made: result.progress_made,
51
+ artifacts_written: result.artifacts_written,
52
+ progress_summary: result.progress_summary,
53
+ next_likely_step: result.next_likely_step,
54
+ providerName,
55
+ });
56
+ if (result.audit_state.status === "complete") {
57
+ await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
58
+ }
59
+ return;
60
+ }
61
+ const externalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
62
+ const result = await runAuditStep({
63
+ root,
64
+ artifactsDir,
65
+ preferredExecutor: getFlag(argv, "--preferred-executor") ??
66
+ (externalAnalyzerPath ? "external_analyzer_import_executor" : undefined),
67
+ auditResultsPath: getFlag(argv, "--results"),
68
+ runtimeUpdatesPath: getFlag(argv, "--updates"),
69
+ externalAnalyzerPath,
70
+ analyzers: sessionConfig.analyzers,
71
+ graphLlmEdgeReasoning: sessionConfig.graph?.llm_edge_reasoning,
72
+ since: getFlag(argv, "--since"),
73
+ opentoken: sessionConfig.opentoken?.enabled,
74
+ runLog: sessionConfig.observability?.run_log,
75
+ });
76
+ if (result.selected_executor !== "agent") {
77
+ await clearDispatchFiles(artifactsDir);
78
+ }
79
+ await emitEnvelope({
80
+ root,
81
+ artifactsDir,
82
+ bundle: result.updated_bundle,
83
+ audit_state: result.audit_state,
84
+ selected_obligation: result.selected_obligation,
85
+ selected_executor: result.selected_executor,
86
+ progress_made: result.progress_made,
87
+ artifacts_written: result.artifacts_written,
88
+ progress_summary: result.progress_summary,
89
+ next_likely_step: result.next_likely_step,
90
+ providerName,
91
+ });
92
+ if (result.audit_state.status === "complete") {
93
+ await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
94
+ }
95
+ }
package/dist/cli/args.js CHANGED
@@ -1,5 +1,4 @@
1
- import { existsSync } from "node:fs";
2
- import { createReadStream } from "node:fs";
1
+ import { existsSync, createReadStream } from "node:fs";
3
2
  import { readdir } from "node:fs/promises";
4
3
  import { Buffer } from "node:buffer";
5
4
  import { createHash } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  import { rename } from "node:fs/promises";
2
2
  import { basename, dirname, join } from "node:path";
3
- import { readJsonFile, RunLogger } from "@audit-tools/shared";
3
+ import { readJsonFile, RunLogger, withFsRetry } from "@audit-tools/shared";
4
4
  import { loadArtifactBundle, writeCoreArtifacts, } from "../io/artifacts.js";
5
5
  import { advanceAudit } from "../orchestrator/advance.js";
6
6
  import { deriveAuditState } from "../orchestrator/state.js";
@@ -16,7 +16,7 @@ async function maybeArchiveLegacyPendingResults(auditResultsPath) {
16
16
  }
17
17
  const archivedPath = join(dirname(auditResultsPath), `worker_results_submitted_${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
18
18
  try {
19
- await rename(auditResultsPath, archivedPath);
19
+ await withFsRetry(() => rename(auditResultsPath, archivedPath));
20
20
  return archivedPath;
21
21
  }
22
22
  catch (error) {
@@ -1 +1,11 @@
1
- export declare function cleanupStaleArtifactsDir(artifactsDir: string): Promise<void>;
1
+ import type { AuditState } from "../types/auditState.js";
2
+ export type CleanupOptions = {
3
+ force?: boolean;
4
+ dryRun?: boolean;
5
+ };
6
+ export type CleanupResult = {
7
+ action: "deleted" | "skipped" | "dry-run";
8
+ status: AuditState["status"] | "unknown";
9
+ reason?: string;
10
+ };
11
+ export declare function cleanupStaleArtifactsDir(artifactsDir: string, options?: CleanupOptions): Promise<CleanupResult>;
@@ -5,8 +5,14 @@ import { isFileMissingError, readJsonFile } from "@audit-tools/shared";
5
5
  // run completed or never started, its artifacts are safe to clear. A missing
6
6
  // state file means there is nothing to clean. Shared by run-to-completion
7
7
  // (clears before a fresh loop) and the cleanup command.
8
- export async function cleanupStaleArtifactsDir(artifactsDir) {
9
- let status;
8
+ //
9
+ // With options:
10
+ // force=true — delete even when status is active/blocked or state file is missing.
11
+ // dryRun=true — skip the actual rm call and return action='dry-run'.
12
+ // Both default to false; the no-args call from run-to-completion is unchanged.
13
+ export async function cleanupStaleArtifactsDir(artifactsDir, options = {}) {
14
+ const { force = false, dryRun = false } = options;
15
+ let status = "unknown";
10
16
  try {
11
17
  const state = await readJsonFile(join(artifactsDir, "audit_state.json"));
12
18
  status = state.status;
@@ -15,9 +21,23 @@ export async function cleanupStaleArtifactsDir(artifactsDir) {
15
21
  if (!isFileMissingError(error)) {
16
22
  throw error;
17
23
  }
18
- return;
24
+ // State file missing — status stays "unknown".
19
25
  }
20
- if (status === "complete" || status === "not_started") {
21
- await rm(artifactsDir, { recursive: true, force: true });
26
+ const eligibleWithoutForce = status === "complete" || status === "not_started";
27
+ const resumable = status === "active" || status === "blocked";
28
+ if (!eligibleWithoutForce && !force) {
29
+ // active/blocked — caller may want to resume; skip unless forced
30
+ if (resumable) {
31
+ const reason = `audit is ${status} and may be resumed — use --force to delete anyway`;
32
+ return { action: "skipped", status, reason };
33
+ }
34
+ // unknown (missing state file) — no-op by default; caller decides how to
35
+ // surface this (run-to-completion ignores it; cleanup command sets exitCode=1)
36
+ return { action: "skipped", status: "unknown" };
37
+ }
38
+ if (dryRun) {
39
+ return { action: "dry-run", status };
22
40
  }
41
+ await rm(artifactsDir, { recursive: true, force: true });
42
+ return { action: "deleted", status };
23
43
  }
@@ -0,0 +1 @@
1
+ export declare function cmdCleanup(argv: string[]): Promise<void>;
@@ -0,0 +1,24 @@
1
+ import { getArtifactsDir, hasFlag } from "./args.js";
2
+ import { cleanupStaleArtifactsDir } from "./cleanup.js";
3
+ export async function cmdCleanup(argv) {
4
+ const artifactsDir = getArtifactsDir(argv);
5
+ const dryRun = hasFlag(argv, "--dry-run");
6
+ const force = hasFlag(argv, "--force");
7
+ const result = await cleanupStaleArtifactsDir(artifactsDir, { force, dryRun });
8
+ if (result.action === "skipped") {
9
+ console.log(JSON.stringify({
10
+ artifacts_dir: artifactsDir,
11
+ action: "skipped",
12
+ reason: result.reason ?? "no audit_state.json found; artifacts may be from a crashed audit — use --force to delete anyway",
13
+ dry_run: dryRun,
14
+ }, null, 2));
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ console.log(JSON.stringify({
19
+ artifacts_dir: artifactsDir,
20
+ action: result.action,
21
+ status: result.status,
22
+ dry_run: dryRun,
23
+ }, null, 2));
24
+ }