auditor-lambda 0.8.0 → 0.9.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 (98) hide show
  1. package/audit-code-wrapper-lib.mjs +149 -129
  2. package/dist/adapters/normalizeExternal.js +6 -3
  3. package/dist/cli/args.d.ts +0 -1
  4. package/dist/cli/args.js +0 -6
  5. package/dist/cli/dispatch.js +3 -2
  6. package/dist/cli/lineIndex.js +4 -1
  7. package/dist/cli/mergeAndIngestCommand.d.ts +1 -0
  8. package/dist/cli/mergeAndIngestCommand.js +219 -0
  9. package/dist/cli/nextStepCommand.js +5 -1
  10. package/dist/cli/runToCompletion.d.ts +9 -0
  11. package/dist/cli/runToCompletion.js +655 -480
  12. package/dist/cli/statusCommand.d.ts +1 -0
  13. package/dist/cli/statusCommand.js +113 -0
  14. package/dist/cli/submitPacketCommand.d.ts +1 -0
  15. package/dist/cli/submitPacketCommand.js +155 -0
  16. package/dist/cli/workerResult.d.ts +1 -1
  17. package/dist/cli/workerRunCommand.d.ts +1 -0
  18. package/dist/cli/workerRunCommand.js +88 -0
  19. package/dist/cli.js +14 -563
  20. package/dist/extractors/analyzers/sql.js +4 -1
  21. package/dist/extractors/analyzers/treeSitter.js +29 -15
  22. package/dist/extractors/analyzers/typescript.js +10 -8
  23. package/dist/extractors/designAssessment.js +43 -24
  24. package/dist/extractors/graph.js +139 -73
  25. package/dist/extractors/pathPatterns.js +17 -5
  26. package/dist/io/runArtifactTypes.d.ts +18 -0
  27. package/dist/io/runArtifactTypes.js +1 -0
  28. package/dist/io/runArtifacts.d.ts +2 -18
  29. package/dist/io/runArtifacts.js +14 -3
  30. package/dist/mcp/server.js +9 -0
  31. package/dist/orchestrator/advance.js +37 -22
  32. package/dist/orchestrator/artifactFreshness.js +2 -2
  33. package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
  34. package/dist/orchestrator/autoFixExecutor.js +16 -8
  35. package/dist/orchestrator/dependencyMap.d.ts +1 -1
  36. package/dist/orchestrator/dependencyMap.js +7 -1
  37. package/dist/orchestrator/fileAnchors.js +14 -3
  38. package/dist/orchestrator/flowCoverage.js +1 -0
  39. package/dist/orchestrator/flowRequeue.js +4 -1
  40. package/dist/orchestrator/{internalExecutors.d.ts → ingestionExecutors.d.ts} +0 -6
  41. package/dist/orchestrator/ingestionExecutors.js +237 -0
  42. package/dist/orchestrator/intakeExecutors.d.ts +3 -0
  43. package/dist/orchestrator/intakeExecutors.js +25 -0
  44. package/dist/orchestrator/planningExecutors.d.ts +4 -0
  45. package/dist/orchestrator/planningExecutors.js +95 -0
  46. package/dist/orchestrator/runtimeCommand.js +7 -15
  47. package/dist/orchestrator/selectiveDeepening/conflict.d.ts +8 -0
  48. package/dist/orchestrator/selectiveDeepening/conflict.js +71 -0
  49. package/dist/orchestrator/selectiveDeepening/findingFollowup.d.ts +10 -0
  50. package/dist/orchestrator/selectiveDeepening/findingFollowup.js +52 -0
  51. package/dist/orchestrator/selectiveDeepening/highRiskClean.d.ts +7 -0
  52. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +44 -0
  53. package/dist/orchestrator/selectiveDeepening/index.d.ts +18 -0
  54. package/dist/orchestrator/selectiveDeepening/index.js +128 -0
  55. package/dist/orchestrator/selectiveDeepening/lensVerification.d.ts +12 -0
  56. package/dist/orchestrator/selectiveDeepening/lensVerification.js +242 -0
  57. package/dist/orchestrator/selectiveDeepening/runtimeValidation.d.ts +13 -0
  58. package/dist/orchestrator/selectiveDeepening/runtimeValidation.js +57 -0
  59. package/dist/orchestrator/selectiveDeepening/shared.d.ts +45 -0
  60. package/dist/orchestrator/selectiveDeepening/shared.js +128 -0
  61. package/dist/orchestrator/selectiveDeepening/stewardFollowup.d.ts +6 -0
  62. package/dist/orchestrator/selectiveDeepening/stewardFollowup.js +72 -0
  63. package/dist/orchestrator/selectiveDeepening.d.ts +2 -20
  64. package/dist/orchestrator/selectiveDeepening.js +6 -760
  65. package/dist/orchestrator/staleness.js +3 -3
  66. package/dist/orchestrator/structureExecutors.d.ts +5 -0
  67. package/dist/orchestrator/structureExecutors.js +94 -0
  68. package/dist/orchestrator/taskBuilder.d.ts +2 -2
  69. package/dist/orchestrator/taskBuilder.js +101 -82
  70. package/dist/providers/index.d.ts +7 -0
  71. package/dist/providers/index.js +14 -95
  72. package/dist/quota/discoveredLimits.d.ts +1 -0
  73. package/dist/quota/discoveredLimits.js +7 -1
  74. package/dist/quota/index.d.ts +0 -2
  75. package/dist/quota/index.js +1 -2
  76. package/dist/reporting/workBlocks.js +7 -4
  77. package/dist/types/reviewPlanning.d.ts +23 -16
  78. package/dist/validation/auditResults.js +97 -95
  79. package/dist/validation/sessionConfig.d.ts +2 -2
  80. package/dist/validation/sessionConfig.js +14 -7
  81. package/package.json +3 -2
  82. package/schemas/audit_findings.schema.json +3 -3
  83. package/schemas/critical_flows.schema.json +3 -2
  84. package/schemas/dispatch_quota.schema.json +1 -1
  85. package/schemas/graph_bundle.schema.json +1 -1
  86. package/schemas/review_packets.schema.json +1 -1
  87. package/schemas/step_contract.schema.json +80 -0
  88. package/scripts/postinstall.mjs +19 -2
  89. package/skills/audit-code/opencode-command-template.txt +3 -3
  90. package/dist/orchestrator/internalExecutors.js +0 -424
  91. package/dist/providers/localSubprocessProvider.d.ts +0 -9
  92. package/dist/providers/localSubprocessProvider.js +0 -18
  93. package/dist/providers/subprocessTemplateProvider.d.ts +0 -8
  94. package/dist/providers/subprocessTemplateProvider.js +0 -59
  95. package/dist/providers/vscodeTaskProvider.d.ts +0 -7
  96. package/dist/providers/vscodeTaskProvider.js +0 -14
  97. package/dist/quota/probe.d.ts +0 -10
  98. package/dist/quota/probe.js +0 -18
@@ -1,4 +1,4 @@
1
- import { describeValue, formatValidationIssues, isRecord, } from "@audit-tools/shared";
1
+ import { describeValue, formatValidationIssues, isRecord, VALID_LENSES, VALID_SEVERITIES, VALID_CONFIDENCES, } from "@audit-tools/shared";
2
2
  export function normalizeCoveragePath(path) {
3
3
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
4
4
  }
@@ -11,23 +11,11 @@ const REQUIRED_FINDING_FIELDS = [
11
11
  "lens",
12
12
  "summary",
13
13
  ];
14
- const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low", "info"]);
15
- const VALID_CONFIDENCES = new Set(["high", "medium", "low"]);
14
+ // Severity / confidence / lens validity now come from the canonical shared
15
+ // vocabulary (`@audit-tools/shared`); previously each was re-defined here and
16
+ // drifted from the shared Lens / FindingSeverity / FindingConfidence types.
16
17
  const VALID_PRIORITIES = new Set(["high", "medium", "low"]);
17
18
  const LENS_VERIFICATION_TAG = "lens_verification";
18
- const VALID_LENSES = new Set([
19
- "correctness",
20
- "architecture",
21
- "maintainability",
22
- "security",
23
- "reliability",
24
- "performance",
25
- "data_integrity",
26
- "tests",
27
- "operability",
28
- "config_deployment",
29
- "observability",
30
- ]);
31
19
  function pushIssue(issues, params) {
32
20
  issues.push({
33
21
  ...params,
@@ -77,20 +65,12 @@ function validateExpectedStringField(value, label, expected, taskId, resultIndex
77
65
  });
78
66
  }
79
67
  }
80
- function validateFinding(finding, label, taskId, resultIndex) {
81
- const issues = [];
82
- if (!isRecord(finding)) {
83
- pushIssue(issues, {
84
- result_index: resultIndex,
85
- task_id: taskId,
86
- field: label,
87
- message: `${label} must be an object, got ${describeValue(finding)}.`,
88
- });
89
- return issues;
90
- }
68
+ function validateFindingRequiredFields(finding, label, taskId, resultIndex, issues) {
91
69
  for (const field of REQUIRED_FINDING_FIELDS) {
92
70
  validateRequiredStringField(finding[field], `${label}.${field}`, taskId, resultIndex, issues);
93
71
  }
72
+ }
73
+ function validateFindingEnums(finding, label, taskId, resultIndex, issues) {
94
74
  if (typeof finding.severity === "string" &&
95
75
  !VALID_SEVERITIES.has(finding.severity)) {
96
76
  pushIssue(issues, {
@@ -117,6 +97,8 @@ function validateFinding(finding, label, taskId, resultIndex) {
117
97
  message: `Invalid lens '${finding.lens}'. Must be one of: ${[...VALID_LENSES].join(", ")}.`,
118
98
  });
119
99
  }
100
+ }
101
+ function validateAffectedFiles(finding, label, taskId, resultIndex, issues) {
120
102
  const affectedFiles = finding.affected_files;
121
103
  if (!Array.isArray(affectedFiles) || affectedFiles.length === 0) {
122
104
  pushIssue(issues, {
@@ -125,57 +107,58 @@ function validateFinding(finding, label, taskId, resultIndex) {
125
107
  field: `${label}.affected_files`,
126
108
  message: "affected_files must be a non-empty array.",
127
109
  });
110
+ return;
128
111
  }
129
- else {
130
- for (let k = 0; k < affectedFiles.length; k++) {
131
- const item = affectedFiles[k];
132
- if (!isRecord(item)) {
133
- pushIssue(issues, {
134
- result_index: resultIndex,
135
- task_id: taskId,
136
- field: `${label}.affected_files[${k}]`,
137
- message: `affected_files[${k}] must be an object, got ${describeValue(item)}.`,
138
- });
139
- continue;
140
- }
141
- if (!isNonEmptyString(item.path)) {
142
- pushIssue(issues, {
143
- result_index: resultIndex,
144
- task_id: taskId,
145
- field: `${label}.affected_files[${k}].path`,
146
- message: "affected_files entry has an empty path.",
147
- });
148
- }
149
- if (item.line_start !== undefined &&
150
- !Number.isInteger(item.line_start)) {
151
- pushIssue(issues, {
152
- result_index: resultIndex,
153
- task_id: taskId,
154
- field: `${label}.affected_files[${k}].line_start`,
155
- message: `affected_files[${k}].line_start must be an integer, got ${describeValue(item.line_start)}.`,
156
- });
157
- }
158
- if (item.line_end !== undefined &&
159
- !Number.isInteger(item.line_end)) {
160
- pushIssue(issues, {
161
- result_index: resultIndex,
162
- task_id: taskId,
163
- field: `${label}.affected_files[${k}].line_end`,
164
- message: `affected_files[${k}].line_end must be an integer, got ${describeValue(item.line_end)}.`,
165
- });
166
- }
167
- if (Number.isInteger(item.line_start) &&
168
- Number.isInteger(item.line_end) &&
169
- Number(item.line_start) > Number(item.line_end)) {
170
- pushIssue(issues, {
171
- result_index: resultIndex,
172
- task_id: taskId,
173
- field: `${label}.affected_files[${k}]`,
174
- message: "affected_files line_start must be less than or equal to line_end.",
175
- });
176
- }
112
+ for (let k = 0; k < affectedFiles.length; k++) {
113
+ const item = affectedFiles[k];
114
+ if (!isRecord(item)) {
115
+ pushIssue(issues, {
116
+ result_index: resultIndex,
117
+ task_id: taskId,
118
+ field: `${label}.affected_files[${k}]`,
119
+ message: `affected_files[${k}] must be an object, got ${describeValue(item)}.`,
120
+ });
121
+ continue;
122
+ }
123
+ if (!isNonEmptyString(item.path)) {
124
+ pushIssue(issues, {
125
+ result_index: resultIndex,
126
+ task_id: taskId,
127
+ field: `${label}.affected_files[${k}].path`,
128
+ message: "affected_files entry has an empty path.",
129
+ });
130
+ }
131
+ if (item.line_start !== undefined &&
132
+ !Number.isInteger(item.line_start)) {
133
+ pushIssue(issues, {
134
+ result_index: resultIndex,
135
+ task_id: taskId,
136
+ field: `${label}.affected_files[${k}].line_start`,
137
+ message: `affected_files[${k}].line_start must be an integer, got ${describeValue(item.line_start)}.`,
138
+ });
139
+ }
140
+ if (item.line_end !== undefined &&
141
+ !Number.isInteger(item.line_end)) {
142
+ pushIssue(issues, {
143
+ result_index: resultIndex,
144
+ task_id: taskId,
145
+ field: `${label}.affected_files[${k}].line_end`,
146
+ message: `affected_files[${k}].line_end must be an integer, got ${describeValue(item.line_end)}.`,
147
+ });
148
+ }
149
+ if (Number.isInteger(item.line_start) &&
150
+ Number.isInteger(item.line_end) &&
151
+ Number(item.line_start) > Number(item.line_end)) {
152
+ pushIssue(issues, {
153
+ result_index: resultIndex,
154
+ task_id: taskId,
155
+ field: `${label}.affected_files[${k}]`,
156
+ message: "affected_files line_start must be less than or equal to line_end.",
157
+ });
177
158
  }
178
159
  }
160
+ }
161
+ function validateEvidence(finding, label, taskId, resultIndex, issues) {
179
162
  const evidence = finding.evidence;
180
163
  if (!Array.isArray(evidence) || evidence.length === 0) {
181
164
  pushIssue(issues, {
@@ -184,33 +167,52 @@ function validateFinding(finding, label, taskId, resultIndex) {
184
167
  field: `${label}.evidence`,
185
168
  message: "evidence is empty — provide an array of plain strings such as \"src/foo.ts:42 - variable overwritten before use\".",
186
169
  });
170
+ return;
187
171
  }
188
- else {
189
- let hasSubstantiveEntry = false;
190
- for (let k = 0; k < evidence.length; k++) {
191
- const entry = evidence[k];
192
- if (typeof entry !== "string") {
193
- pushIssue(issues, {
194
- result_index: resultIndex,
195
- task_id: taskId,
196
- field: `${label}.evidence[${k}]`,
197
- message: `evidence[${k}] must be a string, got ${describeValue(entry)}.`,
198
- });
199
- continue;
200
- }
201
- if (entry.trim().length > 0) {
202
- hasSubstantiveEntry = true;
203
- }
204
- }
205
- if (!hasSubstantiveEntry) {
172
+ let hasSubstantiveEntry = false;
173
+ for (let k = 0; k < evidence.length; k++) {
174
+ const entry = evidence[k];
175
+ if (typeof entry !== "string") {
206
176
  pushIssue(issues, {
207
177
  result_index: resultIndex,
208
178
  task_id: taskId,
209
- field: `${label}.evidence`,
210
- message: "All evidence entries are empty strings.",
179
+ field: `${label}.evidence[${k}]`,
180
+ message: `evidence[${k}] must be a string, got ${describeValue(entry)}.`,
211
181
  });
182
+ continue;
183
+ }
184
+ if (entry.trim().length > 0) {
185
+ hasSubstantiveEntry = true;
212
186
  }
213
187
  }
188
+ if (!hasSubstantiveEntry) {
189
+ pushIssue(issues, {
190
+ result_index: resultIndex,
191
+ task_id: taskId,
192
+ field: `${label}.evidence`,
193
+ message: "All evidence entries are empty strings.",
194
+ });
195
+ }
196
+ }
197
+ // Thin orchestrator: validates one finding by delegating to the per-concern
198
+ // validators (object-shape, required strings, enums, affected_files, evidence),
199
+ // each of which pushes into the shared issues array. Behavior and messages are
200
+ // identical to the former single 165-line function.
201
+ function validateFinding(finding, label, taskId, resultIndex) {
202
+ const issues = [];
203
+ if (!isRecord(finding)) {
204
+ pushIssue(issues, {
205
+ result_index: resultIndex,
206
+ task_id: taskId,
207
+ field: label,
208
+ message: `${label} must be an object, got ${describeValue(finding)}.`,
209
+ });
210
+ return issues;
211
+ }
212
+ validateFindingRequiredFields(finding, label, taskId, resultIndex, issues);
213
+ validateFindingEnums(finding, label, taskId, resultIndex, issues);
214
+ validateAffectedFiles(finding, label, taskId, resultIndex, issues);
215
+ validateEvidence(finding, label, taskId, resultIndex, issues);
214
216
  return issues;
215
217
  }
216
218
  function validateOptionalStringArray(value, label, taskId, resultIndex, issues) {
@@ -1,7 +1,7 @@
1
1
  import { type SessionConfig, type ValidationIssue } from "@audit-tools/shared";
2
2
  export declare function validateSessionConfig(value: unknown): ValidationIssue[];
3
3
  export declare function validateConfiguredProviderEnvironment(sessionConfig: SessionConfig, options?: {
4
- commandExists?: (command: string) => boolean;
4
+ commandExists?: (command: string) => Promise<boolean>;
5
5
  pathExists?: (commandPath: string) => boolean;
6
- }): ValidationIssue[];
6
+ }): Promise<ValidationIssue[]>;
7
7
  export { formatValidationIssues } from "@audit-tools/shared";
@@ -1,6 +1,8 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { exec } from "node:child_process";
2
2
  import { accessSync, constants } from "node:fs";
3
+ import { promisify } from "node:util";
3
4
  import { ANALYZER_SETTINGS, PROVIDER_NAMES, SESSION_UI_MODES, isRecord, pushValidationIssue, } from "@audit-tools/shared";
5
+ const execAsync = promisify(exec);
4
6
  const VALID_PROVIDERS = new Set(PROVIDER_NAMES);
5
7
  const VALID_UI_MODES = new Set(SESSION_UI_MODES);
6
8
  const VALID_ANALYZER_SETTINGS = new Set(ANALYZER_SETTINGS);
@@ -80,10 +82,15 @@ function validateAgentProviderSection(value, path, issues) {
80
82
  pushIssue(issues, `${path}.dangerously_skip_permissions`, "dangerously_skip_permissions must be a boolean when provided.");
81
83
  }
82
84
  }
83
- function commandExists(command) {
85
+ async function commandExists(command) {
84
86
  const lookupCommand = process.platform === "win32" ? "where" : "which";
85
- const result = spawnSync(lookupCommand, [command], { stdio: "ignore" });
86
- return result.status === 0;
87
+ try {
88
+ await execAsync(`${lookupCommand} ${command}`);
89
+ return true;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
87
94
  }
88
95
  function configuredPathExists(commandPath) {
89
96
  try {
@@ -183,14 +190,14 @@ export function validateSessionConfig(value) {
183
190
  }
184
191
  return issues;
185
192
  }
186
- export function validateConfiguredProviderEnvironment(sessionConfig, options = {}) {
193
+ export async function validateConfiguredProviderEnvironment(sessionConfig, options = {}) {
187
194
  const issues = [];
188
195
  const lookupCommand = options.commandExists ?? commandExists;
189
196
  const lookupPath = options.pathExists ?? configuredPathExists;
190
197
  const provider = sessionConfig.provider ?? "local-subprocess";
191
198
  if (provider === "claude-code") {
192
199
  const command = sessionConfig.claude_code?.command ?? "claude";
193
- if (isBareExecutableName(command) && !lookupCommand(command)) {
200
+ if (isBareExecutableName(command) && !(await lookupCommand(command))) {
194
201
  pushIssue(issues, "claude_code.command", `Configured claude-code executable was not found on PATH: ${command}.`);
195
202
  }
196
203
  else if (isDirectExecutablePath(command) && !lookupPath(command)) {
@@ -202,7 +209,7 @@ export function validateConfiguredProviderEnvironment(sessionConfig, options = {
202
209
  }
203
210
  if (provider === "opencode") {
204
211
  const command = sessionConfig.opencode?.command ?? "opencode";
205
- if (isBareExecutableName(command) && !lookupCommand(command)) {
212
+ if (isBareExecutableName(command) && !(await lookupCommand(command))) {
206
213
  pushIssue(issues, "opencode.command", `Configured opencode executable was not found on PATH: ${command}.`);
207
214
  }
208
215
  else if (isDirectExecutablePath(command) && !lookupPath(command)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "update-languages": "node scripts/update-languages.mjs",
24
24
  "build": "tsc -p tsconfig.json",
25
25
  "check": "tsc -p tsconfig.json --noEmit",
26
- "test": "npm run build && node --test tests/*.test.mjs",
26
+ "test": "npm run build && node --import tsx/esm --test tests/*.test.mjs",
27
27
  "release:patch": "node scripts/release-and-publish.mjs patch --bump-only",
28
28
  "release:minor": "node scripts/release-and-publish.mjs minor --bump-only",
29
29
  "release:major": "node scripts/release-and-publish.mjs major --bump-only",
@@ -73,6 +73,7 @@
73
73
  "ajv": "^8.17.1",
74
74
  "linguist-languages": "^9.3.2",
75
75
  "tree-sitter-wasms": "^0.1.13",
76
+ "tsx": "^4.19.0",
76
77
  "typescript": "^5.9.2",
77
78
  "web-tree-sitter": "^0.25.10"
78
79
  }
@@ -57,7 +57,7 @@
57
57
  "enum": ["critical", "high", "medium", "low", "info"]
58
58
  },
59
59
  "confidence": { "type": "string", "enum": ["high", "medium", "low"] },
60
- "lens": { "type": "string", "minLength": 1 },
60
+ "lens": { "type": "string", "enum": ["correctness", "architecture", "maintainability", "security", "reliability", "performance", "data_integrity", "tests", "operability", "config_deployment", "observability"] },
61
61
  "summary": { "type": "string" },
62
62
  "affected_files": {
63
63
  "type": "array",
@@ -78,9 +78,9 @@
78
78
  "impact": { "type": "string" },
79
79
  "likelihood": { "type": "string" },
80
80
  "evidence": { "type": "array", "minItems": 1, "items": { "type": "string" } },
81
- "reproduction": { "type": "array", "items": { "type": "string" } },
81
+ "reproduction": { "type": "array", "minItems": 1, "items": { "type": "string" } },
82
82
  "systemic": { "type": "boolean" },
83
- "related_findings": { "type": "array", "items": { "type": "string" } },
83
+ "related_findings": { "type": "array", "minItems": 1, "items": { "type": "string" } },
84
84
  "theme_id": { "type": "string" }
85
85
  },
86
86
  "additionalProperties": false
@@ -33,8 +33,9 @@
33
33
  "items": { "type": "string" }
34
34
  },
35
35
  "confidence": {
36
- "type": "string",
37
- "enum": ["high", "low"]
36
+ "type": "number",
37
+ "minimum": 0,
38
+ "maximum": 1
38
39
  }
39
40
  },
40
41
  "additionalProperties": false
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "audit-code-dispatch-quota/v1alpha2",
4
4
  "title": "DispatchQuota",
5
5
  "description": "Quota schedule for a prepare-dispatch run. Written beside dispatch-plan.json. Hosts must launch at most wave_size packets per wave, then re-read this file before the next wave to pick up any updated limits.",
@@ -115,7 +115,7 @@
115
115
  }
116
116
  }
117
117
  },
118
- "additionalProperties": true
118
+ "additionalProperties": false
119
119
  },
120
120
  "analyzers_used": {
121
121
  "type": "array",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "graphEdge": {
31
31
  "type": "object",
32
- "required": ["from", "to", "confidence"],
32
+ "required": ["from", "to"],
33
33
  "properties": {
34
34
  "from": { "type": "string" },
35
35
  "to": { "type": "string" },
@@ -0,0 +1,80 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "step_contract.schema.json",
4
+ "title": "Audit Code Step Contract",
5
+ "description": "The step contract written to steps/current-step.json by audit-code. Describes one bounded step for the host agent to execute.",
6
+ "type": "object",
7
+ "required": [
8
+ "contract_version",
9
+ "step_kind",
10
+ "prompt_path",
11
+ "status",
12
+ "run_id",
13
+ "allowed_commands",
14
+ "stop_condition",
15
+ "repo_root",
16
+ "artifacts_dir",
17
+ "artifact_paths"
18
+ ],
19
+ "properties": {
20
+ "contract_version": { "const": "audit-code-step/v1alpha1" },
21
+ "step_kind": {
22
+ "type": "string",
23
+ "enum": [
24
+ "dispatch_review",
25
+ "single_task_fallback",
26
+ "design_review",
27
+ "analyzer_install",
28
+ "edge_reasoning",
29
+ "edge_reasoning_dispatch",
30
+ "synthesis_narrative",
31
+ "present_report",
32
+ "blocked"
33
+ ]
34
+ },
35
+ "status": {
36
+ "type": "string",
37
+ "enum": ["ready", "blocked", "complete"]
38
+ },
39
+ "prompt_path": { "type": "string" },
40
+ "run_id": { "type": ["string", "null"] },
41
+ "progress": {
42
+ "type": "object",
43
+ "required": ["summary"],
44
+ "properties": {
45
+ "summary": { "type": "string" },
46
+ "pending_packets": { "type": "integer" },
47
+ "pending_tasks": { "type": "integer" },
48
+ "completed_tasks": { "type": "integer" },
49
+ "wave_size": { "type": "integer" }
50
+ },
51
+ "additionalProperties": false
52
+ },
53
+ "allowed_commands": {
54
+ "type": "array",
55
+ "items": { "type": "string" }
56
+ },
57
+ "allowed_mcp_tools": {
58
+ "type": "array",
59
+ "items": { "type": "string" }
60
+ },
61
+ "stop_condition": { "type": "string" },
62
+ "repo_root": { "type": "string" },
63
+ "artifacts_dir": { "type": "string" },
64
+ "artifact_paths": {
65
+ "type": "object",
66
+ "additionalProperties": { "type": ["string", "null"] }
67
+ },
68
+ "access": {
69
+ "type": "object",
70
+ "required": ["read_paths", "write_paths"],
71
+ "properties": {
72
+ "read_paths": { "type": "array", "items": { "type": "string" } },
73
+ "write_paths": { "type": "array", "items": { "type": "string" } },
74
+ "forbidden_patterns": { "type": "array", "items": { "type": "string" } }
75
+ },
76
+ "additionalProperties": false
77
+ }
78
+ },
79
+ "additionalProperties": false
80
+ }
@@ -284,7 +284,6 @@ function mergeOpenCodeGlobalConfig(existing) {
284
284
  const parsed = existing ? JSON.parse(existing) : {};
285
285
  const auditPermission = renderOpenCodePermissionConfig();
286
286
  const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
287
- const nodeExecPath = replaceBackslashes(process.execPath);
288
287
  const pkgEntrypoint = replaceBackslashes(join(pkgRoot, 'audit-code.mjs'));
289
288
  return {
290
289
  ...parsed,
@@ -303,7 +302,7 @@ function mergeOpenCodeGlobalConfig(existing) {
303
302
  ...objectValue(parsed.mcp),
304
303
  auditor: {
305
304
  type: 'local',
306
- command: [nodeExecPath, pkgEntrypoint, 'mcp'],
305
+ command: ['node', pkgEntrypoint, 'mcp'],
307
306
  enabled: true,
308
307
  timeout: 10000,
309
308
  },
@@ -380,6 +379,10 @@ if (!promptSource || !skillSource) {
380
379
 
381
380
  const codexOpenAiAgentSource = readOptionalSource(codexOpenAiAgentSourceFile, 'Codex skill UI metadata');
382
381
 
382
+ const postinstallStart = Date.now();
383
+ let succeeded = 0;
384
+ let failed = 0;
385
+
383
386
  const installs = [
384
387
  {
385
388
  label: 'Claude command',
@@ -415,12 +418,14 @@ for (const install of installs) {
415
418
  try {
416
419
  const action = writeGeneratedFile(install.path, install.content);
417
420
  console.log(`audit-code: ${action} global ${install.label} at ${install.path}`);
421
+ succeeded++;
418
422
  } catch (err) {
419
423
  console.warn(`audit-code: could not install global ${install.label} (${err.message})`);
420
424
  console.warn(` To install manually, copy from:`);
421
425
  console.warn(` ${install.sourcePath}`);
422
426
  console.warn(` to:`);
423
427
  console.warn(` ${install.path}`);
428
+ failed++;
424
429
  }
425
430
  }
426
431
 
@@ -429,8 +434,10 @@ const globalMcpLauncherPath = join(homedir(), '.audit-code', 'run-mcp-server.mjs
429
434
  try {
430
435
  const action = writeGeneratedFile(globalMcpLauncherPath, Buffer.from(renderGlobalMcpLauncher(pkgRoot)));
431
436
  console.log(`audit-code: ${action} global MCP launcher at ${globalMcpLauncherPath}`);
437
+ succeeded++;
432
438
  } catch (err) {
433
439
  console.warn(`audit-code: could not install global MCP launcher (${err.message})`);
440
+ failed++;
434
441
  }
435
442
 
436
443
  // Install OpenCode global command and MCP via merged config
@@ -440,10 +447,12 @@ try {
440
447
  mergeOpenCodeGlobalConfig(existing),
441
448
  );
442
449
  console.log(`audit-code: ${action} global OpenCode config in ${opencodeGlobalConfig}`);
450
+ succeeded++;
443
451
  } catch (err) {
444
452
  console.warn(`audit-code: could not install global OpenCode config (${err.message})`);
445
453
  console.warn(` To install manually, add the mcp.auditor and command["audit-code"] entries to:`);
446
454
  console.warn(` ${opencodeGlobalConfig}`);
455
+ failed++;
447
456
  }
448
457
 
449
458
  // Install Antigravity plugin (global skill for Gemini IDE / Antigravity Hub)
@@ -460,8 +469,10 @@ try {
460
469
 
461
470
  const skillAction = writeGeneratedFile(antigravityPluginSkillPath, skillSource);
462
471
  console.log(`audit-code: ${skillAction} Antigravity plugin skill at ${antigravityPluginSkillPath}`);
472
+ succeeded++;
463
473
  } catch (err) {
464
474
  console.warn(`audit-code: could not install Antigravity plugin (${err.message})`);
475
+ failed++;
465
476
  }
466
477
 
467
478
  // Install Claude Desktop plugin so /audit-code appears in the slash-command menu
@@ -497,9 +508,11 @@ try {
497
508
  console.log(`audit-code: ${skillAction} Claude Desktop plugin skill at ${claudePluginSkillPath}`);
498
509
 
499
510
  console.log(`audit-code: restart Claude Desktop for /audit-code to appear in the slash-command menu`);
511
+ succeeded++;
500
512
  } catch (err) {
501
513
  console.warn(`audit-code: could not install Claude Desktop plugin (${err.message})`);
502
514
  console.warn(` Plugin directory: ${claudePluginDir}`);
515
+ failed++;
503
516
  }
504
517
 
505
518
  // Register auditor MCP server with Claude Desktop so /audit-code appears in its slash-command menu
@@ -511,9 +524,13 @@ try {
511
524
  console.log(`audit-code: ${action} Claude Desktop MCP server entry in ${claudeDesktopConfig}`);
512
525
  console.log(`audit-code: restart Claude Desktop for /audit-code to appear`);
513
526
  console.log(`audit-code: to target a specific repo, set AUDIT_CODE_REPO_ROOT in Claude Desktop's MCP env settings`);
527
+ succeeded++;
514
528
  } catch (err) {
515
529
  console.warn(`audit-code: could not update Claude Desktop config (${err.message})`);
516
530
  console.warn(` To register manually, add "mcpServers.auditor" to:`);
517
531
  console.warn(` ${claudeDesktopConfig}`);
518
532
  console.warn(` with command "node" and args ["${replaceBackslashes(globalMcpLauncherPath)}"]`);
533
+ failed++;
519
534
  }
535
+
536
+ console.log(`audit-code: postinstall complete — ${succeeded} succeeded, ${failed} failed (${Date.now() - postinstallStart}ms)`);
@@ -3,11 +3,11 @@
3
3
  Use `audit-code next-step` as the primary interface to the audit workflow.
4
4
 
5
5
  1. Run `audit-code next-step` directly when shell access is available.
6
- 2. If MCP is your only available interface, call `auditor_start_audit` or `auditor_continue_audit`; both return the same one-step contract.
6
+ 2. If MCP is your only available interface, call `start_audit` or `continue_audit`; both return the same one-step contract.
7
7
  3. Read `prompt_content` in the response and follow it.
8
- 4. When a step completes (not blocked), run `audit-code next-step` again or call `auditor_continue_audit` as the compatibility adapter.
8
+ 4. When a step completes (not blocked), run `audit-code next-step` again or call `continue_audit` as the compatibility adapter.
9
9
  5. Stop when the step instructions say to stop.
10
10
 
11
11
  Use the `task` tool or equivalent for subagent dispatch when the step tells you to fan out review work.
12
12
 
13
- If neither shell access nor `auditor_start_audit` is available, stop and report that no next-step interface is connected.
13
+ If neither shell access nor `start_audit` is available, stop and report that no next-step interface is connected.