auditor-lambda 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -9
- package/audit-code-wrapper-lib.mjs +19 -920
- package/dist/cli/auditStep.d.ts +1 -33
- package/dist/cli/dispatch.d.ts +47 -0
- package/dist/cli/dispatch.js +116 -1
- package/dist/cli/mergeAndIngestCommand.js +10 -0
- package/dist/cli/nextStepCommand.js +3 -1
- package/dist/cli/prompts.d.ts +2 -0
- package/dist/cli/prompts.js +9 -0
- package/dist/cli/semanticReviewStep.js +12 -1
- package/dist/cli/steps.d.ts +15 -0
- package/dist/cli.js +1 -8
- package/dist/io/artifacts.d.ts +9 -1
- package/dist/io/artifacts.js +7 -0
- package/dist/io/runArtifacts.d.ts +14 -0
- package/dist/io/runArtifacts.js +23 -0
- package/dist/orchestrator/designReviewPrompt.d.ts +4 -1
- package/dist/orchestrator/designReviewPrompt.js +43 -2
- package/dist/orchestrator/executorResult.d.ts +25 -0
- package/dist/orchestrator/intakeExecutors.d.ts +19 -1
- package/dist/orchestrator/intakeExecutors.js +89 -3
- package/dist/orchestrator/nextStep.d.ts +1 -0
- package/dist/orchestrator/nextStep.js +1 -1
- package/dist/orchestrator/state.js +8 -1
- package/dist/providers/constants.d.ts +1 -1
- package/dist/providers/constants.js +1 -1
- package/dist/reporting/synthesis.d.ts +8 -0
- package/dist/reporting/synthesis.js +16 -1
- package/dist/supervisor/operatorHandoff.js +2 -0
- package/dist/types/auditScope.d.ts +16 -2
- package/dist/validation/sessionConfig.js +35 -0
- package/docs/contracts.md +0 -16
- package/docs/operator-guide.md +6 -8
- package/package.json +1 -1
- package/schemas/audit_findings.schema.json +1 -0
- package/scripts/postinstall.mjs +0 -174
- package/skills/audit-code/SKILL.md +17 -1
- package/skills/audit-code/audit-code.prompt.md +25 -0
- package/dist/mcp/server.d.ts +0 -72
- package/dist/mcp/server.js +0 -765
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolved audit scope, emitted by the intake executor so the conversation-first
|
|
4
|
+
* loader can echo what is about to be audited (and gate on confirmation when a
|
|
5
|
+
* mis-scope smell suggests the user targeted the wrong directory).
|
|
6
|
+
*/
|
|
7
|
+
export interface ScopeSummary {
|
|
8
|
+
/** Absolute path of the resolved repository root being audited. */
|
|
9
|
+
repo_root: string;
|
|
10
|
+
/** Count of files that will actually be audited (after disposition filtering). */
|
|
11
|
+
auditable_file_count: number;
|
|
12
|
+
/** Whether `repo_root` sits inside a git working tree. */
|
|
13
|
+
git_available: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Zero or more human-readable warnings that the resolved root may be the wrong
|
|
16
|
+
* target (e.g. a non-git subdirectory whose ancestor is a repo, or a workspace
|
|
17
|
+
* member of a parent monorepo). Empty when the scope looks correct.
|
|
18
|
+
*/
|
|
19
|
+
mis_scope_smells: string[];
|
|
20
|
+
}
|
|
2
21
|
/**
|
|
3
22
|
* Uniform result of running one audit executor: the updated artifact bundle, the
|
|
4
23
|
* artifact filenames it wrote (which drive metadata/staleness bookkeeping in
|
|
@@ -9,4 +28,10 @@ export interface ExecutorRunResult {
|
|
|
9
28
|
updated: ArtifactBundle;
|
|
10
29
|
artifacts_written: string[];
|
|
11
30
|
progress_summary: string;
|
|
31
|
+
/**
|
|
32
|
+
* Optional resolved-scope summary. Only the intake executor sets this; it lets
|
|
33
|
+
* the loader echo the audit target and gate on confirmation when
|
|
34
|
+
* `mis_scope_smells` is non-empty.
|
|
35
|
+
*/
|
|
36
|
+
scope_summary?: ScopeSummary;
|
|
12
37
|
}
|
|
@@ -1,3 +1,21 @@
|
|
|
1
1
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
2
2
|
import type { ExecutorRunResult } from "./executorResult.js";
|
|
3
|
-
|
|
3
|
+
/** Prefix used to carry the scope summary inside `progress_summary` for hosts
|
|
4
|
+
* that read the step's progress text rather than the `scope_summary.json`
|
|
5
|
+
* artifact. The loader extracts everything after this marker as JSON. */
|
|
6
|
+
export declare const SCOPE_SUMMARY_PREFIX = "SCOPE_SUMMARY:";
|
|
7
|
+
/**
|
|
8
|
+
* Detect signals that the resolved audit root may be the *wrong* directory.
|
|
9
|
+
* Two heuristics, returned as zero or more human-readable warnings:
|
|
10
|
+
*
|
|
11
|
+
* a. No-git-but-ancestor-is-repo — `root` is not a git repo but some ancestor
|
|
12
|
+
* directory is (you probably targeted a subdirectory instead of the repo
|
|
13
|
+
* root).
|
|
14
|
+
* b. Workspace-member — `root` has a `package.json` with a `name`, and its
|
|
15
|
+
* parent has a `package.json` declaring `workspaces` (you probably want to
|
|
16
|
+
* audit from the monorepo root).
|
|
17
|
+
*
|
|
18
|
+
* Returns an empty array when the scope looks correct. Never throws.
|
|
19
|
+
*/
|
|
20
|
+
export declare function detectMisScopeSmells(root: string): string[];
|
|
21
|
+
export declare function runIntakeExecutor(bundle: ArtifactBundle, root: string, artifactsDir?: string): Promise<ExecutorRunResult>;
|
|
@@ -1,7 +1,70 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { isGitRepo, writeJsonFile } from "@audit-tools/shared";
|
|
1
4
|
import { buildFileDisposition, isAuditExcludedStatus, } from "../extractors/disposition.js";
|
|
2
5
|
import { buildRepoManifestFromFs } from "../extractors/fsIntake.js";
|
|
3
6
|
import { loadIgnoreFile } from "../extractors/ignore.js";
|
|
4
|
-
|
|
7
|
+
/** Prefix used to carry the scope summary inside `progress_summary` for hosts
|
|
8
|
+
* that read the step's progress text rather than the `scope_summary.json`
|
|
9
|
+
* artifact. The loader extracts everything after this marker as JSON. */
|
|
10
|
+
export const SCOPE_SUMMARY_PREFIX = "SCOPE_SUMMARY:";
|
|
11
|
+
/**
|
|
12
|
+
* Detect signals that the resolved audit root may be the *wrong* directory.
|
|
13
|
+
* Two heuristics, returned as zero or more human-readable warnings:
|
|
14
|
+
*
|
|
15
|
+
* a. No-git-but-ancestor-is-repo — `root` is not a git repo but some ancestor
|
|
16
|
+
* directory is (you probably targeted a subdirectory instead of the repo
|
|
17
|
+
* root).
|
|
18
|
+
* b. Workspace-member — `root` has a `package.json` with a `name`, and its
|
|
19
|
+
* parent has a `package.json` declaring `workspaces` (you probably want to
|
|
20
|
+
* audit from the monorepo root).
|
|
21
|
+
*
|
|
22
|
+
* Returns an empty array when the scope looks correct. Never throws.
|
|
23
|
+
*/
|
|
24
|
+
export function detectMisScopeSmells(root) {
|
|
25
|
+
const smells = [];
|
|
26
|
+
// (a) No .git here, but an ancestor is a git repository.
|
|
27
|
+
if (!isGitRepo(root)) {
|
|
28
|
+
let current = dirname(root);
|
|
29
|
+
let previous = root;
|
|
30
|
+
while (current && current !== previous) {
|
|
31
|
+
if (existsSync(join(current, ".git"))) {
|
|
32
|
+
smells.push(`root has no .git but ancestor '${current}' is a git repository — you may have targeted a subdirectory instead of the repo root`);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
previous = current;
|
|
36
|
+
current = dirname(current);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// (b) Workspace member of a parent monorepo.
|
|
40
|
+
const rootPkg = readPackageJson(root);
|
|
41
|
+
if (rootPkg && rootPkg.name !== undefined) {
|
|
42
|
+
const parent = dirname(root);
|
|
43
|
+
if (parent && parent !== root) {
|
|
44
|
+
const parentPkg = readPackageJson(parent);
|
|
45
|
+
if (parentPkg && parentPkg.workspaces !== undefined) {
|
|
46
|
+
smells.push(`root appears to be a workspace member of a parent monorepo at '${parent}' — consider auditing from the monorepo root instead`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return smells;
|
|
51
|
+
}
|
|
52
|
+
function readPackageJson(dir) {
|
|
53
|
+
const path = join(dir, "package.json");
|
|
54
|
+
if (!existsSync(path))
|
|
55
|
+
return undefined;
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
58
|
+
return parsed && typeof parsed === "object"
|
|
59
|
+
? parsed
|
|
60
|
+
: undefined;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Missing or malformed package.json — treat as absent for smell purposes.
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function runIntakeExecutor(bundle, root, artifactsDir) {
|
|
5
68
|
const ignore = await loadIgnoreFile(root);
|
|
6
69
|
const repoManifest = await buildRepoManifestFromFs({
|
|
7
70
|
root,
|
|
@@ -13,13 +76,36 @@ export async function runIntakeExecutor(bundle, root) {
|
|
|
13
76
|
if (auditableCount === 0) {
|
|
14
77
|
throw new Error(`No auditable files found in ${root}. The repository may be empty, generated-only, documentation-only, or filtered by .auditorignore.`);
|
|
15
78
|
}
|
|
79
|
+
const scopeSummary = {
|
|
80
|
+
repo_root: root,
|
|
81
|
+
auditable_file_count: auditableCount,
|
|
82
|
+
git_available: isGitRepo(root),
|
|
83
|
+
mis_scope_smells: detectMisScopeSmells(root),
|
|
84
|
+
};
|
|
85
|
+
const artifactsWritten = ["repo_manifest.json", "file_disposition.json"];
|
|
86
|
+
// Persist the scope summary alongside the other intake artifacts when we know
|
|
87
|
+
// where the artifacts directory is. The typed `scope_summary` field and the
|
|
88
|
+
// progress_summary marker below carry the same data for hosts that don't read
|
|
89
|
+
// the file directly.
|
|
90
|
+
if (artifactsDir) {
|
|
91
|
+
await writeJsonFile(join(artifactsDir, "scope_summary.json"), scopeSummary);
|
|
92
|
+
artifactsWritten.push("scope_summary.json");
|
|
93
|
+
}
|
|
94
|
+
const progressSummary = `${SCOPE_SUMMARY_PREFIX}${JSON.stringify(scopeSummary)}\n` +
|
|
95
|
+
`Created intake artifacts for ${repoManifest.files.length} files ` +
|
|
96
|
+
`(${auditableCount} auditable). Scope: ${root}, git: ${scopeSummary.git_available ? "yes" : "no"}` +
|
|
97
|
+
(scopeSummary.mis_scope_smells.length > 0
|
|
98
|
+
? `; ${scopeSummary.mis_scope_smells.length} mis-scope warning(s)`
|
|
99
|
+
: "") +
|
|
100
|
+
".";
|
|
16
101
|
return {
|
|
17
102
|
updated: {
|
|
18
103
|
...bundle,
|
|
19
104
|
repo_manifest: repoManifest,
|
|
20
105
|
file_disposition: disposition,
|
|
21
106
|
},
|
|
22
|
-
artifacts_written:
|
|
23
|
-
progress_summary:
|
|
107
|
+
artifacts_written: artifactsWritten,
|
|
108
|
+
progress_summary: progressSummary,
|
|
109
|
+
scope_summary: scopeSummary,
|
|
24
110
|
};
|
|
25
111
|
}
|
|
@@ -6,5 +6,6 @@ export interface NextStepDecision {
|
|
|
6
6
|
selected_executor: string | null;
|
|
7
7
|
reason: string;
|
|
8
8
|
}
|
|
9
|
+
export declare const PRIORITY: string[];
|
|
9
10
|
export declare function findObligation(obligations: AuditObligation[]): AuditObligation | undefined;
|
|
10
11
|
export declare function decideNextStep(bundle: ArtifactBundle): NextStepDecision;
|
|
@@ -46,7 +46,14 @@ export function deriveAuditState(bundle) {
|
|
|
46
46
|
"requeue_tasks.json",
|
|
47
47
|
], planningReady)));
|
|
48
48
|
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
49
|
-
|
|
49
|
+
// Tasks deferred by a budget cap (FINDING-013) will never have results, so
|
|
50
|
+
// they must be excluded from the completion check — otherwise the obligation
|
|
51
|
+
// loops forever under a budget. Absent active_dispatch => empty set => the
|
|
52
|
+
// logic is unchanged (all tasks must be complete).
|
|
53
|
+
const deferredTaskIds = new Set(bundle.active_dispatch?.deferred_task_ids ?? []);
|
|
54
|
+
const hasPendingAuditTasks = bundle.audit_tasks?.some((task) => task.status !== "complete" &&
|
|
55
|
+
!completedTaskIds.has(task.task_id) &&
|
|
56
|
+
!deferredTaskIds.has(task.task_id)) ?? false;
|
|
50
57
|
if (hasPendingAuditTasks) {
|
|
51
58
|
obligations.push(obligation("audit_tasks_completed", "missing"));
|
|
52
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LOCAL_SUBPROCESS_PROVIDER_NAME } from "@audit-tools/shared";
|
|
1
|
+
export { LOCAL_SUBPROCESS_PROVIDER_NAME, CODEX_PROVIDER_NAME, ANTIGRAVITY_PROVIDER_NAME, } from "@audit-tools/shared";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { LOCAL_SUBPROCESS_PROVIDER_NAME } from "@audit-tools/shared";
|
|
1
|
+
export { LOCAL_SUBPROCESS_PROVIDER_NAME, CODEX_PROVIDER_NAME, ANTIGRAVITY_PROVIDER_NAME, } from "@audit-tools/shared";
|
|
@@ -28,6 +28,14 @@ export interface AuditReportSummary {
|
|
|
28
28
|
audited_file_count: number;
|
|
29
29
|
excluded_file_count: number;
|
|
30
30
|
runtime_validation_status_breakdown: Record<string, number>;
|
|
31
|
+
/**
|
|
32
|
+
* Distinct count of tasks/files NOT audited because a packet budget cap
|
|
33
|
+
* (FINDING-013) deferred them — kept separate from `excluded_file_count`
|
|
34
|
+
* (non-auditable files), since a budget skip is an honest partial-coverage
|
|
35
|
+
* signal, not an exclusion. Optional so the shared `AuditFindingsSummary`
|
|
36
|
+
* (which omits it) stays assignable to this render shape; defaults to 0.
|
|
37
|
+
*/
|
|
38
|
+
budget_deferred_task_count?: number;
|
|
31
39
|
}
|
|
32
40
|
export interface AuditReportModel {
|
|
33
41
|
summary: AuditReportSummary;
|
|
@@ -25,6 +25,8 @@ function coverageSummary(coverage) {
|
|
|
25
25
|
return {
|
|
26
26
|
audited_file_count: files.filter((file) => file.audit_status === "complete").length,
|
|
27
27
|
excluded_file_count: files.filter((file) => file.audit_status === "excluded").length,
|
|
28
|
+
// Distinct from excluded: files a budget cap deferred (status set by scope).
|
|
29
|
+
budget_deferred_task_count: files.filter((file) => file.audit_status === "budget_deferred").length,
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
32
|
function formatSeverityList(summary) {
|
|
@@ -50,6 +52,7 @@ export function buildAuditReportModel(params) {
|
|
|
50
52
|
severity_breakdown: severityBreakdown(findings),
|
|
51
53
|
audited_file_count: coverage.audited_file_count,
|
|
52
54
|
excluded_file_count: coverage.excluded_file_count,
|
|
55
|
+
budget_deferred_task_count: coverage.budget_deferred_task_count,
|
|
53
56
|
runtime_validation_status_breakdown: runtimeStatusBreakdown(params.runtimeValidationReport),
|
|
54
57
|
},
|
|
55
58
|
findings,
|
|
@@ -117,7 +120,11 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
117
120
|
if (report.executive_summary && report.executive_summary.trim().length > 0) {
|
|
118
121
|
lines.push("## Executive Summary", "", report.executive_summary.trim(), "");
|
|
119
122
|
}
|
|
120
|
-
lines.push("## Summary", "", `- Findings: ${report.summary.finding_count}`, `- Work blocks: ${report.summary.work_block_count}`, `- Severity breakdown: ${formatSeverityList(report.summary.severity_breakdown)}`, `- Fully audited files: ${report.summary.audited_file_count}`, `- Excluded non-auditable files: ${report.summary.excluded_file_count}`,
|
|
123
|
+
lines.push("## Summary", "", `- Findings: ${report.summary.finding_count}`, `- Work blocks: ${report.summary.work_block_count}`, `- Severity breakdown: ${formatSeverityList(report.summary.severity_breakdown)}`, `- Fully audited files: ${report.summary.audited_file_count}`, `- Excluded non-auditable files: ${report.summary.excluded_file_count}`, ...((report.summary.budget_deferred_task_count ?? 0) > 0
|
|
124
|
+
? [
|
|
125
|
+
`- Not audited (budget): ${report.summary.budget_deferred_task_count} task(s) skipped by packet budget cap`,
|
|
126
|
+
]
|
|
127
|
+
: []), "");
|
|
121
128
|
if (report.top_risks && report.top_risks.length > 0) {
|
|
122
129
|
lines.push("## Top Risks", "");
|
|
123
130
|
for (const risk of report.top_risks) {
|
|
@@ -186,6 +193,14 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
186
193
|
lines.push("", scope.dropped_note);
|
|
187
194
|
}
|
|
188
195
|
}
|
|
196
|
+
else if (scope && scope.mode === "budget") {
|
|
197
|
+
lines.push(`**Partial audit (budget cap).** This run dispatched only the top-${scope.budget?.max_files ?? "K"} packet(s); ` +
|
|
198
|
+
`${scope.deferred_packet_count ?? 0} packet(s) covering ${scope.deferred_task_ids?.length ?? 0} task(s) were deferred and NOT audited. ` +
|
|
199
|
+
`Findings above reflect only the audited subset. **A full audit is advised before release.**`);
|
|
200
|
+
if (scope.dropped_note) {
|
|
201
|
+
lines.push("", scope.dropped_note);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
189
204
|
else {
|
|
190
205
|
lines.push("This report is deterministic output from the completed audit. Non-auditable files were excluded from scope before task generation.");
|
|
191
206
|
}
|
|
@@ -24,9 +24,11 @@ const NON_PENDING_OBLIGATION_STATES = new Set([
|
|
|
24
24
|
const INTERACTIVE_PROVIDER_OPTIONS = [
|
|
25
25
|
"auto",
|
|
26
26
|
"claude-code",
|
|
27
|
+
"codex",
|
|
27
28
|
"opencode",
|
|
28
29
|
"subprocess-template",
|
|
29
30
|
"vscode-task",
|
|
31
|
+
"antigravity",
|
|
30
32
|
];
|
|
31
33
|
function quoteShellPath(filePath) {
|
|
32
34
|
// The handoff renders a single shell argument, so the snippet only needs
|
|
@@ -19,8 +19,12 @@ export interface AuditScopeBudget {
|
|
|
19
19
|
max_files: number;
|
|
20
20
|
}
|
|
21
21
|
export interface AuditScopeManifest {
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* `full` audits every auditable file; `delta` scopes to a changed
|
|
24
|
+
* neighbourhood; `budget` dispatches only the top-K review packets under a
|
|
25
|
+
* `max_packets` cap and defers the rest.
|
|
26
|
+
*/
|
|
27
|
+
mode: "full" | "delta" | "budget";
|
|
24
28
|
/** Git ref/SHA the delta was measured against; `null` in full mode. */
|
|
25
29
|
since: string | null;
|
|
26
30
|
/**
|
|
@@ -40,4 +44,14 @@ export interface AuditScopeManifest {
|
|
|
40
44
|
* requested `--since` could not be honoured and the run fell back to full.
|
|
41
45
|
*/
|
|
42
46
|
dropped_note?: string;
|
|
47
|
+
/**
|
|
48
|
+
* When `mode === 'budget'`: the number of review packets that were NOT
|
|
49
|
+
* dispatched due to the `max_packets` cap. Present only in budget mode.
|
|
50
|
+
*/
|
|
51
|
+
deferred_packet_count?: number;
|
|
52
|
+
/**
|
|
53
|
+
* When `mode === 'budget'`: the task_ids skipped due to the budget cap.
|
|
54
|
+
* Present only in budget mode.
|
|
55
|
+
*/
|
|
56
|
+
deferred_task_ids?: string[];
|
|
43
57
|
}
|
|
@@ -164,7 +164,9 @@ export function validateSessionConfig(value) {
|
|
|
164
164
|
}
|
|
165
165
|
validateTemplateProviderSection(value.subprocess_template, "subprocess_template", issues, provider === "subprocess-template");
|
|
166
166
|
validateTemplateProviderSection(value.vscode_task, "vscode_task", issues, provider === "vscode-task");
|
|
167
|
+
validateTemplateProviderSection(value.antigravity, "antigravity", issues, provider === "antigravity");
|
|
167
168
|
validateAgentProviderSection(value.claude_code, "claude_code", issues);
|
|
169
|
+
validateAgentProviderSection(value.codex, "codex", issues);
|
|
168
170
|
validateAgentProviderSection(value.opencode, "opencode", issues);
|
|
169
171
|
if (value.synthesis !== undefined) {
|
|
170
172
|
if (!isRecord(value.synthesis)) {
|
|
@@ -175,6 +177,27 @@ export function validateSessionConfig(value) {
|
|
|
175
177
|
pushIssue(issues, "synthesis.narrative", "synthesis.narrative must be a boolean when provided.");
|
|
176
178
|
}
|
|
177
179
|
}
|
|
180
|
+
if (value.dispatch !== undefined) {
|
|
181
|
+
if (!isRecord(value.dispatch)) {
|
|
182
|
+
pushIssue(issues, "dispatch", "dispatch must be a JSON object.");
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (value.dispatch.canary !== undefined &&
|
|
186
|
+
typeof value.dispatch.canary !== "boolean") {
|
|
187
|
+
pushIssue(issues, "dispatch.canary", "dispatch.canary must be a boolean when provided.");
|
|
188
|
+
}
|
|
189
|
+
if (value.dispatch.confirm_threshold !== undefined &&
|
|
190
|
+
(!Number.isInteger(value.dispatch.confirm_threshold) ||
|
|
191
|
+
Number(value.dispatch.confirm_threshold) < 0)) {
|
|
192
|
+
pushIssue(issues, "dispatch.confirm_threshold", "dispatch.confirm_threshold must be a non-negative integer when provided.");
|
|
193
|
+
}
|
|
194
|
+
if (value.dispatch.max_packets !== undefined &&
|
|
195
|
+
(!Number.isInteger(value.dispatch.max_packets) ||
|
|
196
|
+
Number(value.dispatch.max_packets) < 0)) {
|
|
197
|
+
pushIssue(issues, "dispatch.max_packets", "dispatch.max_packets must be a non-negative integer when provided.");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
178
201
|
if (value.analyzers !== undefined) {
|
|
179
202
|
if (!isRecord(value.analyzers)) {
|
|
180
203
|
pushIssue(issues, "analyzers", "analyzers must be a JSON object mapping analyzer id to a setting.");
|
|
@@ -219,6 +242,18 @@ export async function validateConfiguredProviderEnvironment(sessionConfig, optio
|
|
|
219
242
|
pushIssue(issues, "opencode.command", "Configured opencode command must be a bare executable name or direct path. Put CLI flags in extra_args.");
|
|
220
243
|
}
|
|
221
244
|
}
|
|
245
|
+
if (provider === "codex") {
|
|
246
|
+
const command = sessionConfig.codex?.command ?? "codex";
|
|
247
|
+
if (isBareExecutableName(command) && !(await lookupCommand(command))) {
|
|
248
|
+
pushIssue(issues, "codex.command", `Configured codex executable was not found on PATH: ${command}.`);
|
|
249
|
+
}
|
|
250
|
+
else if (isDirectExecutablePath(command) && !lookupPath(command)) {
|
|
251
|
+
pushIssue(issues, "codex.command", `Configured codex executable path does not exist: ${command}.`);
|
|
252
|
+
}
|
|
253
|
+
else if (!isSupportedConfiguredCommand(command)) {
|
|
254
|
+
pushIssue(issues, "codex.command", "Configured codex command must be a bare executable name or direct path. Put CLI flags in extra_args.");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
222
257
|
return issues;
|
|
223
258
|
}
|
|
224
259
|
export { formatValidationIssues } from "@audit-tools/shared";
|
package/docs/contracts.md
CHANGED
|
@@ -214,22 +214,6 @@ Review packets may expose graph-derived context for workers:
|
|
|
214
214
|
- `quality` metrics for cohesion, internal edges, boundary edges, and
|
|
215
215
|
unexplained files
|
|
216
216
|
|
|
217
|
-
## MCP contract
|
|
218
|
-
|
|
219
|
-
The local MCP server exposes:
|
|
220
|
-
|
|
221
|
-
- `start_audit`
|
|
222
|
-
- `get_status`
|
|
223
|
-
- `continue_audit`
|
|
224
|
-
- `explain_task`
|
|
225
|
-
- `validate_artifacts`
|
|
226
|
-
- `import_results`
|
|
227
|
-
- `import_runtime_updates`
|
|
228
|
-
|
|
229
|
-
It also exposes resources for current artifacts, operator handoff, install
|
|
230
|
-
guidance, and the current report. MCP consumers should prefer the tool and
|
|
231
|
-
resource contracts over reading internal files directly.
|
|
232
|
-
|
|
233
217
|
## Guided recovery
|
|
234
218
|
|
|
235
219
|
Failure responses should distinguish:
|
package/docs/operator-guide.md
CHANGED
|
@@ -43,9 +43,9 @@ Host-specific files may include:
|
|
|
43
43
|
|
|
44
44
|
- Codex: managed `AGENTS.md` fallback guidance
|
|
45
45
|
- Claude Desktop: project template, remote MCP connector, local MCP bundle
|
|
46
|
-
- OpenCode: `opencode.json` with auditor
|
|
46
|
+
- OpenCode: `opencode.json` with auditor agent and permission wiring; the `/audit-code` command is global npm-installed state
|
|
47
47
|
- VS Code/Copilot: prompt, custom agent, instructions, and `.vscode/mcp.json`
|
|
48
|
-
- Antigravity: planning-mode
|
|
48
|
+
- Antigravity: planning-mode guidance
|
|
49
49
|
|
|
50
50
|
Use `.audit-code/install/GETTING-STARTED.md` as the repo-local handoff after
|
|
51
51
|
bootstrap.
|
|
@@ -61,18 +61,17 @@ repo-local `AGENTS.md` fallback guidance. The installed skill includes
|
|
|
61
61
|
`agents/openai.yaml` metadata so Codex can keep the slash-list display aligned
|
|
62
62
|
with the canonical `/audit-code` spelling.
|
|
63
63
|
|
|
64
|
-
Claude Desktop is treated as
|
|
64
|
+
Claude Desktop is treated as a bundle-install host. Use the generated project
|
|
65
65
|
template and local bundle artifacts when installing the integration.
|
|
66
66
|
|
|
67
67
|
OpenCode uses the global command seeded by `npm install -g auditor-lambda`.
|
|
68
68
|
The generated project `opencode.json` should not define `command["audit-code"]`;
|
|
69
|
-
it only wires the auditor
|
|
69
|
+
it only wires the auditor agent and project permissions. VS Code uses
|
|
70
70
|
repo-local prompt and MCP configuration files.
|
|
71
71
|
|
|
72
72
|
Antigravity should be treated as a workflow-and-artifacts host until it has a
|
|
73
|
-
stable project-local config surface. Use generated planning-mode guidance
|
|
74
|
-
|
|
75
|
-
terminal when needed.
|
|
73
|
+
stable project-local config surface. Use generated planning-mode guidance
|
|
74
|
+
or the backend fallback from an Antigravity-managed terminal when needed.
|
|
76
75
|
|
|
77
76
|
Manual prompt-import hosts can use:
|
|
78
77
|
|
|
@@ -110,7 +109,6 @@ audit-code --external-analyzer-results /path/to/external_analyzer_results.json
|
|
|
110
109
|
audit-code explain-task <task_id>
|
|
111
110
|
audit-code validate
|
|
112
111
|
audit-code cleanup
|
|
113
|
-
audit-code mcp
|
|
114
112
|
```
|
|
115
113
|
|
|
116
114
|
`audit-code next-step` is the backend-rendered step engine used by the
|
package/package.json
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"audited_file_count": { "type": "integer", "minimum": 0 },
|
|
28
28
|
"excluded_file_count": { "type": "integer", "minimum": 0 },
|
|
29
|
+
"budget_deferred_task_count": { "type": "integer", "minimum": 0 },
|
|
29
30
|
"runtime_validation_status_breakdown": {
|
|
30
31
|
"type": "object",
|
|
31
32
|
"additionalProperties": { "type": "integer", "minimum": 0 }
|