auditor-lambda 0.2.9 → 0.2.11
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 -0
- package/audit-code-wrapper-lib.mjs +20 -9
- package/dist/cli.js +122 -1
- package/dist/orchestrator/autoFixExecutor.js +32 -15
- package/dist/orchestrator/localCommands.d.ts +14 -0
- package/dist/orchestrator/localCommands.js +124 -0
- package/dist/orchestrator/syntaxResolutionExecutor.js +60 -59
- package/dist/supervisor/operatorHandoff.d.ts +1 -1
- package/dist/supervisor/operatorHandoff.js +8 -0
- package/docs/agent-integrations.md +2 -2
- package/docs/releasing.md +47 -0
- package/docs/session-config.md +3 -1
- package/package.json +7 -1
- package/schemas/audit-code-v1alpha1.schema.json +1 -0
package/README.md
CHANGED
|
@@ -141,9 +141,13 @@ async function newestMtimeMs(path) {
|
|
|
141
141
|
return newest;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
async function
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
export async function shouldBuildDistForPaths({
|
|
145
|
+
distEntryPath,
|
|
146
|
+
sourceRootPath,
|
|
147
|
+
tsconfigPath: tsconfigPathValue,
|
|
148
|
+
}) {
|
|
149
|
+
if (!(await fileExists(distEntryPath))) {
|
|
150
|
+
if (!(await fileExists(sourceRootPath)) || !(await fileExists(tsconfigPathValue))) {
|
|
147
151
|
throw new Error(
|
|
148
152
|
'Bundled dist is missing and source files are unavailable for rebuild.',
|
|
149
153
|
);
|
|
@@ -151,18 +155,25 @@ async function shouldBuildDist() {
|
|
|
151
155
|
return true;
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
if (!(await fileExists(
|
|
158
|
+
if (!(await fileExists(sourceRootPath)) || !(await fileExists(tsconfigPathValue))) {
|
|
155
159
|
return false;
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
const distMtime = (await stat(
|
|
159
|
-
const sourceMtime = await newestMtimeMs(
|
|
160
|
-
const tsconfigMtime = (await stat(
|
|
161
|
-
const
|
|
162
|
-
const newestInput = Math.max(sourceMtime, tsconfigMtime, packageJsonMtime);
|
|
162
|
+
const distMtime = (await stat(distEntryPath)).mtimeMs;
|
|
163
|
+
const sourceMtime = await newestMtimeMs(sourceRootPath);
|
|
164
|
+
const tsconfigMtime = (await stat(tsconfigPathValue)).mtimeMs;
|
|
165
|
+
const newestInput = Math.max(sourceMtime, tsconfigMtime);
|
|
163
166
|
return distMtime < newestInput;
|
|
164
167
|
}
|
|
165
168
|
|
|
169
|
+
async function shouldBuildDist() {
|
|
170
|
+
return await shouldBuildDistForPaths({
|
|
171
|
+
distEntryPath: distEntry,
|
|
172
|
+
sourceRootPath: sourceRoot,
|
|
173
|
+
tsconfigPath,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
async function releaseBuildLock(handle) {
|
|
167
178
|
try {
|
|
168
179
|
await handle?.close();
|
package/dist/cli.js
CHANGED
|
@@ -169,9 +169,12 @@ async function emitEnvelope(params) {
|
|
|
169
169
|
}
|
|
170
170
|
function buildManualReviewBlocker(providerName) {
|
|
171
171
|
return providerName === LOCAL_SUBPROCESS_PROVIDER_NAME
|
|
172
|
-
? "Automatic backend steps are exhausted. Remaining semantic review now belongs to the active conversation agent. Review the dispatched files, write structured audit results, and
|
|
172
|
+
? "Automatic backend steps are exhausted. Remaining semantic review now belongs to the active conversation agent. Review the dispatched files, write structured audit results, and ingest them with audit-code --results <file> or audit-code --batch-results <dir>. If you intentionally want a backend bridge instead, re-run audit-code with --provider auto, --provider claude-code, --provider opencode, --provider subprocess-template, or --provider vscode-task."
|
|
173
173
|
: "Automatic work is exhausted. Remaining audit tasks require explicit audit results or an interactive provider.";
|
|
174
174
|
}
|
|
175
|
+
function shouldRunInlineExecutor(selectedExecutor) {
|
|
176
|
+
return selectedExecutor !== null && selectedExecutor !== "agent";
|
|
177
|
+
}
|
|
175
178
|
function buildBlockedAuditState(params) {
|
|
176
179
|
return {
|
|
177
180
|
...params.state,
|
|
@@ -896,6 +899,124 @@ async function cmdRunToCompletion(argv) {
|
|
|
896
899
|
runCount += 1;
|
|
897
900
|
const runId = buildRunId(obligationId, runCount);
|
|
898
901
|
const paths = getRunPaths(artifactsDir, runId);
|
|
902
|
+
if (shouldRunInlineExecutor(preferredExecutor)) {
|
|
903
|
+
await clearDispatchFiles(artifactsDir);
|
|
904
|
+
const startedAt = new Date().toISOString();
|
|
905
|
+
let workerResult;
|
|
906
|
+
try {
|
|
907
|
+
const result = await runAuditStep({
|
|
908
|
+
root,
|
|
909
|
+
artifactsDir,
|
|
910
|
+
preferredExecutor,
|
|
911
|
+
auditResultsPath,
|
|
912
|
+
runtimeUpdatesPath,
|
|
913
|
+
externalAnalyzerPath,
|
|
914
|
+
});
|
|
915
|
+
workerResult = {
|
|
916
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
917
|
+
run_id: runId,
|
|
918
|
+
obligation_id: obligationId,
|
|
919
|
+
status: result.progress_made ? "completed" : "no_progress",
|
|
920
|
+
progress_made: result.progress_made,
|
|
921
|
+
selected_executor: result.selected_executor,
|
|
922
|
+
artifacts_written: result.artifacts_written,
|
|
923
|
+
summary: result.progress_summary,
|
|
924
|
+
next_likely_step: result.next_likely_step,
|
|
925
|
+
errors: [],
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
930
|
+
workerResult = {
|
|
931
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
932
|
+
run_id: runId,
|
|
933
|
+
obligation_id: obligationId,
|
|
934
|
+
status: "failed",
|
|
935
|
+
progress_made: false,
|
|
936
|
+
selected_executor: preferredExecutor,
|
|
937
|
+
artifacts_written: [],
|
|
938
|
+
summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
|
|
939
|
+
next_likely_step: decision.selected_obligation,
|
|
940
|
+
errors: [message],
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
await writeJsonFile(paths.resultPath, workerResult);
|
|
944
|
+
await writeJsonFile(paths.statusPath, {
|
|
945
|
+
run_id: runId,
|
|
946
|
+
status: workerResult.status,
|
|
947
|
+
execution_mode: "inline",
|
|
948
|
+
});
|
|
949
|
+
await appendRunLedgerEntry(artifactsDir, {
|
|
950
|
+
run_id: runId,
|
|
951
|
+
provider: provider.name,
|
|
952
|
+
obligation_id: obligationId,
|
|
953
|
+
selected_executor: workerResult.selected_executor,
|
|
954
|
+
status: workerResult.status,
|
|
955
|
+
started_at: startedAt,
|
|
956
|
+
ended_at: new Date().toISOString(),
|
|
957
|
+
result_path: paths.resultPath,
|
|
958
|
+
});
|
|
959
|
+
lastResult = workerResult;
|
|
960
|
+
if (workerResult.progress_made) {
|
|
961
|
+
anyProgress = true;
|
|
962
|
+
}
|
|
963
|
+
for (const artifact of workerResult.artifacts_written) {
|
|
964
|
+
artifactsWritten.add(artifact);
|
|
965
|
+
}
|
|
966
|
+
artifactsWritten.add("run-ledger.json");
|
|
967
|
+
if (externalAnalyzerPath)
|
|
968
|
+
pendingExternalAnalyzerPath = undefined;
|
|
969
|
+
if (auditResultsPath &&
|
|
970
|
+
pendingBatchAuditResults[0] === auditResultsPath &&
|
|
971
|
+
preferredExecutor === "result_ingestion_executor" &&
|
|
972
|
+
workerResult.status !== "failed" &&
|
|
973
|
+
workerResult.status !== "blocked") {
|
|
974
|
+
pendingBatchAuditResults.shift();
|
|
975
|
+
}
|
|
976
|
+
if (auditResultsPath)
|
|
977
|
+
pendingAuditResultsPath = undefined;
|
|
978
|
+
if (runtimeUpdatesPath)
|
|
979
|
+
pendingRuntimeUpdatesPath = undefined;
|
|
980
|
+
if (workerResult.status === "failed" ||
|
|
981
|
+
workerResult.status === "blocked" ||
|
|
982
|
+
workerResult.status === "no_progress") {
|
|
983
|
+
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
984
|
+
const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
|
|
985
|
+
const state = shouldBlock
|
|
986
|
+
? buildBlockedAuditState({
|
|
987
|
+
state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
|
|
988
|
+
obligationId: workerResult.obligation_id,
|
|
989
|
+
executor: workerResult.selected_executor,
|
|
990
|
+
blocker: buildWorkerFailureBlocker(workerResult),
|
|
991
|
+
})
|
|
992
|
+
: bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
|
|
993
|
+
if (shouldBlock) {
|
|
994
|
+
await writeCoreArtifacts(artifactsDir, {
|
|
995
|
+
...bundleAfter,
|
|
996
|
+
audit_state: state,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
await emitEnvelope({
|
|
1000
|
+
root,
|
|
1001
|
+
artifactsDir,
|
|
1002
|
+
bundle: shouldBlock
|
|
1003
|
+
? { ...bundleAfter, audit_state: state }
|
|
1004
|
+
: bundleAfter,
|
|
1005
|
+
audit_state: state,
|
|
1006
|
+
selected_obligation: workerResult.obligation_id,
|
|
1007
|
+
selected_executor: workerResult.selected_executor,
|
|
1008
|
+
progress_made: anyProgress,
|
|
1009
|
+
artifacts_written: Array.from(shouldBlock
|
|
1010
|
+
? new Set([...artifactsWritten, "audit_state.json"])
|
|
1011
|
+
: artifactsWritten),
|
|
1012
|
+
progress_summary: buildWorkerFailureBlocker(workerResult),
|
|
1013
|
+
next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
|
|
1014
|
+
providerName: provider.name,
|
|
1015
|
+
});
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
899
1020
|
const pendingAuditTasks = preferredExecutor === "agent"
|
|
900
1021
|
? buildPendingAuditTasks(bundle).slice(0, agentBatchSize)
|
|
901
1022
|
: undefined;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from "node:path";
|
|
2
2
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
catch (e) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
3
|
+
import { resolveNodeTool, runFirstAvailableCommand, } from "./localCommands.js";
|
|
4
|
+
function tryRunConfiguredFormatter(root, candidates) {
|
|
5
|
+
const result = runFirstAvailableCommand(root, candidates);
|
|
6
|
+
return result !== null && !result.error && result.exitCode === 0;
|
|
11
7
|
}
|
|
12
8
|
export function runAutoFixExecutor(bundle, root) {
|
|
13
9
|
if (!bundle.file_disposition) {
|
|
@@ -28,24 +24,45 @@ export function runAutoFixExecutor(bundle, root) {
|
|
|
28
24
|
extensions.has("js") ||
|
|
29
25
|
extensions.has("tsx") ||
|
|
30
26
|
extensions.has("jsx")) {
|
|
31
|
-
if (tryRunConfiguredFormatter(root,
|
|
27
|
+
if (tryRunConfiguredFormatter(root, [
|
|
28
|
+
...resolveNodeTool(root, join("node_modules", "prettier", "bin", "prettier.cjs"), ["--write", "."], "prettier --write ."),
|
|
29
|
+
{ command: "prettier", args: ["--write", "."], display: "prettier --write ." },
|
|
30
|
+
]))
|
|
32
31
|
executedTools.push("prettier");
|
|
33
|
-
if (tryRunConfiguredFormatter(root,
|
|
32
|
+
if (tryRunConfiguredFormatter(root, [
|
|
33
|
+
...resolveNodeTool(root, join("node_modules", "eslint", "bin", "eslint.js"), ["--fix", "."], "eslint --fix ."),
|
|
34
|
+
{ command: "eslint", args: ["--fix", "."], display: "eslint --fix ." },
|
|
35
|
+
]))
|
|
34
36
|
executedTools.push("eslint");
|
|
35
37
|
}
|
|
36
38
|
// Python
|
|
37
39
|
if (extensions.has("py")) {
|
|
38
|
-
if (tryRunConfiguredFormatter(root,
|
|
39
|
-
|
|
40
|
+
if (tryRunConfiguredFormatter(root, [
|
|
41
|
+
{ command: "black", args: ["."], display: "black ." },
|
|
42
|
+
{ command: "python", args: ["-m", "black", "."], display: "python -m black ." },
|
|
43
|
+
])) {
|
|
40
44
|
executedTools.push("black");
|
|
41
45
|
}
|
|
42
|
-
if (tryRunConfiguredFormatter(root,
|
|
46
|
+
if (tryRunConfiguredFormatter(root, [
|
|
47
|
+
{
|
|
48
|
+
command: "autopep8",
|
|
49
|
+
args: ["--in-place", "--recursive", "."],
|
|
50
|
+
display: "autopep8 --in-place --recursive .",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
command: "python",
|
|
54
|
+
args: ["-m", "autopep8", "--in-place", "--recursive", "."],
|
|
55
|
+
display: "python -m autopep8 --in-place --recursive .",
|
|
56
|
+
},
|
|
57
|
+
])) {
|
|
43
58
|
executedTools.push("autopep8");
|
|
44
59
|
}
|
|
45
60
|
}
|
|
46
61
|
// Go
|
|
47
62
|
if (extensions.has("go")) {
|
|
48
|
-
if (tryRunConfiguredFormatter(root,
|
|
63
|
+
if (tryRunConfiguredFormatter(root, [
|
|
64
|
+
{ command: "gofmt", args: ["-w", "."], display: "gofmt -w ." },
|
|
65
|
+
]))
|
|
49
66
|
executedTools.push("gofmt");
|
|
50
67
|
}
|
|
51
68
|
const resultsArtifact = {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface LocalCommandCandidate {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
display?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LocalCommandResult {
|
|
7
|
+
candidate: LocalCommandCandidate;
|
|
8
|
+
exitCode: number | null;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
error?: Error;
|
|
12
|
+
}
|
|
13
|
+
export declare function runFirstAvailableCommand(root: string, candidates: LocalCommandCandidate[]): LocalCommandResult | null;
|
|
14
|
+
export declare function resolveNodeTool(root: string, relativePath: string, args: string[], display: string): LocalCommandCandidate[];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
4
|
+
function isWindowsBatchCommand(path) {
|
|
5
|
+
return process.platform === "win32" && /\.(cmd|bat)$/i.test(path);
|
|
6
|
+
}
|
|
7
|
+
function quoteForCmd(arg) {
|
|
8
|
+
if (arg.length === 0)
|
|
9
|
+
return '""';
|
|
10
|
+
if (!/[\s"]/u.test(arg))
|
|
11
|
+
return arg;
|
|
12
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
13
|
+
}
|
|
14
|
+
function toSpawnTuple(candidate) {
|
|
15
|
+
if (!isWindowsBatchCommand(candidate.command)) {
|
|
16
|
+
return {
|
|
17
|
+
command: candidate.command,
|
|
18
|
+
args: candidate.args,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
command: process.env.ComSpec ?? "cmd.exe",
|
|
23
|
+
args: [
|
|
24
|
+
"/d",
|
|
25
|
+
"/s",
|
|
26
|
+
"/c",
|
|
27
|
+
[candidate.command, ...candidate.args].map(quoteForCmd).join(" "),
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function resolveFromPath(command) {
|
|
32
|
+
if (command.trim().length === 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (command.includes("\\") ||
|
|
36
|
+
command.includes("/") ||
|
|
37
|
+
isAbsolute(command)) {
|
|
38
|
+
return existsSync(command) ? command : null;
|
|
39
|
+
}
|
|
40
|
+
const pathValue = process.env.PATH ?? "";
|
|
41
|
+
const pathEntries = pathValue
|
|
42
|
+
.split(delimiter)
|
|
43
|
+
.map((entry) => entry.trim())
|
|
44
|
+
.filter((entry) => entry.length > 0);
|
|
45
|
+
const extensions = process.platform === "win32"
|
|
46
|
+
? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
47
|
+
.split(";")
|
|
48
|
+
.map((ext) => ext.toLowerCase())
|
|
49
|
+
: [""];
|
|
50
|
+
for (const dir of pathEntries) {
|
|
51
|
+
const directPath = join(dir, command);
|
|
52
|
+
if (existsSync(directPath)) {
|
|
53
|
+
return directPath;
|
|
54
|
+
}
|
|
55
|
+
for (const ext of extensions) {
|
|
56
|
+
const candidatePath = join(dir, `${command}${ext}`);
|
|
57
|
+
if (existsSync(candidatePath)) {
|
|
58
|
+
return candidatePath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function resolveCandidate(root, candidate) {
|
|
65
|
+
if (candidate.command === process.execPath) {
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
const resolvedPath = resolveFromPath(candidate.command);
|
|
69
|
+
if (resolvedPath) {
|
|
70
|
+
return {
|
|
71
|
+
...candidate,
|
|
72
|
+
command: resolvedPath,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const repoLocalPath = join(root, candidate.command);
|
|
76
|
+
if (existsSync(repoLocalPath)) {
|
|
77
|
+
return {
|
|
78
|
+
...candidate,
|
|
79
|
+
command: repoLocalPath,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
export function runFirstAvailableCommand(root, candidates) {
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const resolved = resolveCandidate(root, candidate);
|
|
87
|
+
if (!resolved) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const spawnTarget = toSpawnTuple(resolved);
|
|
91
|
+
const result = spawnSync(spawnTarget.command, spawnTarget.args, {
|
|
92
|
+
cwd: root,
|
|
93
|
+
env: process.env,
|
|
94
|
+
encoding: "utf8",
|
|
95
|
+
windowsHide: true,
|
|
96
|
+
stdio: "pipe",
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
candidate: {
|
|
100
|
+
...resolved,
|
|
101
|
+
display: candidate.display ?? [candidate.command, ...candidate.args].join(" "),
|
|
102
|
+
},
|
|
103
|
+
exitCode: result.status,
|
|
104
|
+
stdout: result.stdout ?? "",
|
|
105
|
+
stderr: result.stderr ?? "",
|
|
106
|
+
error: result.error
|
|
107
|
+
? new Error(result.error.message, { cause: result.error })
|
|
108
|
+
: undefined,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
export function resolveNodeTool(root, relativePath, args, display) {
|
|
114
|
+
const localToolPath = join(root, relativePath);
|
|
115
|
+
const candidates = [];
|
|
116
|
+
if (existsSync(localToolPath)) {
|
|
117
|
+
candidates.push({
|
|
118
|
+
command: process.execPath,
|
|
119
|
+
args: [localToolPath, ...args],
|
|
120
|
+
display,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return candidates;
|
|
124
|
+
}
|
|
@@ -1,74 +1,75 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { resolveNodeTool, runFirstAvailableCommand } from "./localCommands.js";
|
|
2
3
|
function runTsc(root) {
|
|
3
4
|
const results = [];
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const command = runFirstAvailableCommand(root, [
|
|
6
|
+
...resolveNodeTool(root, join("node_modules", "typescript", "bin", "tsc"), ["--noEmit"], "tsc --noEmit"),
|
|
7
|
+
{ command: "tsc", args: ["--noEmit"], display: "tsc --noEmit" },
|
|
8
|
+
]);
|
|
9
|
+
if (!command || command.error) {
|
|
10
|
+
return results;
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rule: "tsc",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
process.stderr.write(`[syntax-resolution] tsc exited with no stdout; stderr: ${(error.stderr?.toString() ?? "").slice(0, 200)}\n`);
|
|
12
|
+
const output = [command.stdout, command.stderr].filter(Boolean).join("\n");
|
|
13
|
+
const lines = output.split("\n");
|
|
14
|
+
for (const line of lines) {
|
|
15
|
+
const match = line.match(/^([^:]+)\((\d+),\d+\):\s+(error\s+TS\d+:.*)$/);
|
|
16
|
+
if (match) {
|
|
17
|
+
results.push({
|
|
18
|
+
id: `tsc-${results.length}`,
|
|
19
|
+
category: "correctness",
|
|
20
|
+
severity: "error",
|
|
21
|
+
path: match[1].replace(/\\/g, "/"),
|
|
22
|
+
line_start: parseInt(match[2], 10),
|
|
23
|
+
summary: match[3],
|
|
24
|
+
rule: "tsc",
|
|
25
|
+
});
|
|
32
26
|
}
|
|
33
27
|
}
|
|
28
|
+
if (command.exitCode === 0 && output.trim().length === 0) {
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
if (results.length === 0 && output.trim().length > 0) {
|
|
32
|
+
process.stderr.write(`[syntax-resolution] tsc output could not be parsed: ${output.slice(0, 200)}\n`);
|
|
33
|
+
}
|
|
34
34
|
return results;
|
|
35
35
|
}
|
|
36
36
|
function runEslint(root) {
|
|
37
37
|
const results = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const command = runFirstAvailableCommand(root, [
|
|
39
|
+
...resolveNodeTool(root, join("node_modules", "eslint", "bin", "eslint.js"), [".", "--ext", ".ts,.js,.tsx,.jsx", "--format", "json"], "eslint . --ext .ts,.js,.tsx,.jsx --format json"),
|
|
40
|
+
{
|
|
41
|
+
command: "eslint",
|
|
42
|
+
args: [".", "--ext", ".ts,.js,.tsx,.jsx", "--format", "json"],
|
|
43
|
+
display: "eslint . --ext .ts,.js,.tsx,.jsx --format json",
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
if (!command || command.error) {
|
|
47
|
+
return results;
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
catch (e) {
|
|
66
|
-
process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${(error.stdout?.toString() ?? "").slice(0, 200)}\n`);
|
|
49
|
+
const output = [command.stdout, command.stderr].filter(Boolean).join("\n").trim();
|
|
50
|
+
if (output.length === 0) {
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(output);
|
|
55
|
+
for (const fileResult of parsed) {
|
|
56
|
+
for (const msg of fileResult.messages) {
|
|
57
|
+
results.push({
|
|
58
|
+
id: `eslint-${results.length}`,
|
|
59
|
+
category: "maintainability",
|
|
60
|
+
severity: msg.severity === 2 ? "error" : "warning",
|
|
61
|
+
path: fileResult.filePath
|
|
62
|
+
.replace(/\\/g, "/")
|
|
63
|
+
.replace(root.replace(/\\/g, "/") + "/", ""),
|
|
64
|
+
line_start: msg.line,
|
|
65
|
+
summary: msg.message,
|
|
66
|
+
rule: msg.ruleId || "eslint-error",
|
|
67
|
+
});
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
process.stderr.write(`[syntax-resolution] eslint output could not be parsed: ${output.slice(0, 200)}\n`);
|
|
72
73
|
}
|
|
73
74
|
return results;
|
|
74
75
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
2
2
|
import type { AuditState, AuditTopLevelStatus } from "../types/auditState.js";
|
|
3
3
|
export interface AuditCodeHandoffInput {
|
|
4
|
-
flag: "--results" | "--updates" | "--external-analyzer-results";
|
|
4
|
+
flag: "--results" | "--batch-results" | "--updates" | "--external-analyzer-results";
|
|
5
5
|
suggested_path: string;
|
|
6
6
|
description: string;
|
|
7
7
|
}
|
|
@@ -66,6 +66,11 @@ function buildSuggestedInputs(artifactsDir, status, isConfigError) {
|
|
|
66
66
|
suggested_path: join(incomingDir, "audit-results.json"),
|
|
67
67
|
description: "Import structured audit-review results after manual or provider-assisted review finishes.",
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
flag: "--batch-results",
|
|
71
|
+
suggested_path: join(incomingDir, "audit-results-batch"),
|
|
72
|
+
description: "Import a directory of per-batch audit result files when the conversation agent reviews multiple tasks before ingestion.",
|
|
73
|
+
},
|
|
69
74
|
{
|
|
70
75
|
flag: "--updates",
|
|
71
76
|
suggested_path: join(incomingDir, "runtime-validation-updates.json"),
|
|
@@ -176,6 +181,9 @@ export function buildAuditCodeHandoff(params) {
|
|
|
176
181
|
export async function writeAuditCodeHandoffArtifacts(handoff) {
|
|
177
182
|
try {
|
|
178
183
|
await mkdir(handoff.artifact_paths.incoming_dir, { recursive: true });
|
|
184
|
+
await mkdir(join(handoff.artifact_paths.incoming_dir, "audit-results-batch"), {
|
|
185
|
+
recursive: true,
|
|
186
|
+
});
|
|
179
187
|
await writeJsonFile(handoff.artifact_paths.operator_handoff_json, handoff);
|
|
180
188
|
await writeFile(handoff.artifact_paths.operator_handoff_markdown, renderMarkdown(handoff), "utf8");
|
|
181
189
|
}
|
|
@@ -172,9 +172,9 @@ Use it when the current host cannot keep review inside the active conversation,
|
|
|
172
172
|
|
|
173
173
|
Use when you want the supervisor to stay entirely local.
|
|
174
174
|
|
|
175
|
-
This requires no external agent CLI.
|
|
175
|
+
This requires no external agent CLI. Deterministic executors run in-process during normal wrapper runs, and the supervisor only stops once the remaining work is genuinely semantic review.
|
|
176
176
|
|
|
177
|
-
When
|
|
177
|
+
When that review boundary is reached, `local-subprocess` stops in a terminal blocked handoff instead of pretending more automatic progress is available. Use `--results <file>` for a single batch or `--batch-results <dir>` when the active conversation agent reviewed multiple task batches before ingestion.
|
|
178
178
|
|
|
179
179
|
This is the safest default backend when the repository is already available locally.
|
|
180
180
|
|
package/docs/releasing.md
CHANGED
|
@@ -41,6 +41,53 @@ npm publish --dry-run
|
|
|
41
41
|
|
|
42
42
|
The local dry run is still valuable even though the real publish happens in GitHub Actions. It proves the packed artifact, lifecycle hooks, and publish metadata before you spend a release on a broken workflow run.
|
|
43
43
|
|
|
44
|
+
## Version bump helpers
|
|
45
|
+
|
|
46
|
+
You do not need to look up the current version before cutting a release bump.
|
|
47
|
+
|
|
48
|
+
From the repository root:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run release:patch
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
That delegates the increment to `npm version patch`, updates `package.json` and `package-lock.json`, and creates the matching release commit and git tag in the existing repository format:
|
|
55
|
+
|
|
56
|
+
Under the hood, the helper uses `npm version patch --no-git-tag-version` and then creates the release commit and annotated git tag explicitly so the workflow stays compatible with current npm CLI behavior.
|
|
57
|
+
|
|
58
|
+
- commit: `release: vX.Y.Z`
|
|
59
|
+
- tag: `vX.Y.Z`
|
|
60
|
+
|
|
61
|
+
The repository also exposes:
|
|
62
|
+
|
|
63
|
+
- `npm run release:minor`
|
|
64
|
+
- `npm run release:major`
|
|
65
|
+
|
|
66
|
+
After the version bump, push `main` and the new tag, then publish through the GitHub Actions trusted publishing flow.
|
|
67
|
+
|
|
68
|
+
## One-command release publish
|
|
69
|
+
|
|
70
|
+
When you want the repository to do the full maintainer flow in one command, use:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm run release:patch:publish
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That command:
|
|
77
|
+
|
|
78
|
+
1. verifies the git worktree is clean and you are on `main`
|
|
79
|
+
2. runs `npm run verify:release`
|
|
80
|
+
3. bumps the version, commits `package.json` and `package-lock.json`, and creates the annotated release tag
|
|
81
|
+
4. pushes `main` and the new release tag
|
|
82
|
+
5. creates the matching GitHub Release
|
|
83
|
+
6. waits for `publish-package.yml` to publish through Trusted Publishing
|
|
84
|
+
7. confirms the new npm version resolves from the registry before exiting
|
|
85
|
+
|
|
86
|
+
Minor and major variants are also available:
|
|
87
|
+
|
|
88
|
+
- `npm run release:minor:publish`
|
|
89
|
+
- `npm run release:major:publish`
|
|
90
|
+
|
|
44
91
|
## Supported Node lines
|
|
45
92
|
|
|
46
93
|
Routine CI currently exercises the repository on Node `20` and Node `22`.
|
package/docs/session-config.md
CHANGED
|
@@ -112,7 +112,9 @@ This keeps the current default stable while still allowing best-effort cross-edi
|
|
|
112
112
|
|
|
113
113
|
No extra config is required.
|
|
114
114
|
|
|
115
|
-
This mode covers deterministic
|
|
115
|
+
This mode covers deterministic audit execution locally and stops in a terminal blocked state once the remaining work requires imported audit results or an interactive provider.
|
|
116
|
+
|
|
117
|
+
When the active conversation agent reviews multiple task batches before ingestion, prefer `audit-code --batch-results <dir>` over ad hoc artifact edits.
|
|
116
118
|
|
|
117
119
|
This remains the safest fallback default while the semantic-review workflow is being realigned.
|
|
118
120
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auditor-lambda",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Portable hybrid code-auditing framework for arbitrary repositories.",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"build": "tsc -p tsconfig.json",
|
|
23
23
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
24
24
|
"test": "npm run build && node --test tests/*.test.mjs",
|
|
25
|
+
"release:patch": "node scripts/release-and-publish.mjs patch --bump-only",
|
|
26
|
+
"release:minor": "node scripts/release-and-publish.mjs minor --bump-only",
|
|
27
|
+
"release:major": "node scripts/release-and-publish.mjs major --bump-only",
|
|
28
|
+
"release:patch:publish": "node scripts/release-and-publish.mjs patch",
|
|
29
|
+
"release:minor:publish": "node scripts/release-and-publish.mjs minor",
|
|
30
|
+
"release:major:publish": "node scripts/release-and-publish.mjs major",
|
|
25
31
|
"verify:release": "npm run check && npm test && npm run smoke:linked-audit-code && npm run smoke:packaged-audit-code",
|
|
26
32
|
"smoke:linked-audit-code": "node scripts/smoke-linked-audit-code.mjs",
|
|
27
33
|
"smoke:packaged-audit-code": "node scripts/smoke-packaged-audit-code.mjs",
|