auditor-lambda 0.2.9 → 0.2.10

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/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 run the generated worker command to ingest them. 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."
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 { execSync } from "node:child_process";
1
+ import { join } from "node:path";
2
2
  import { isAuditExcludedStatus } from "../extractors/disposition.js";
3
- function tryRunConfiguredFormatter(root, command) {
4
- try {
5
- execSync(command, { cwd: root, stdio: "ignore", windowsHide: true });
6
- return true;
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, "npx prettier --write ."))
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, "npx eslint --fix ."))
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, "black .") ||
39
- tryRunConfiguredFormatter(root, "python -m black .")) {
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, "autopep8 --in-place --recursive .")) {
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, "gofmt -w ."))
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 { execSync } from "node:child_process";
1
+ import { join } from "node:path";
2
+ import { resolveNodeTool, runFirstAvailableCommand } from "./localCommands.js";
2
3
  function runTsc(root) {
3
4
  const results = [];
4
- try {
5
- execSync("npx tsc --noEmit", {
6
- cwd: root,
7
- stdio: "pipe",
8
- windowsHide: true,
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
- catch (error) {
12
- if (error.stdout) {
13
- const output = error.stdout.toString();
14
- const lines = output.split("\n");
15
- for (const line of lines) {
16
- const match = line.match(/^([^:]+)\((\d+),\d+\):\s+(error\s+TS\d+:.*)$/);
17
- if (match) {
18
- results.push({
19
- id: `tsc-${results.length}`,
20
- category: "correctness",
21
- severity: "error",
22
- path: match[1].replace(/\\/g, "/"),
23
- line_start: parseInt(match[2], 10),
24
- summary: match[3],
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
- try {
39
- execSync("npx eslint . --ext .ts,.js,.tsx,.jsx --format json", {
40
- cwd: root,
41
- stdio: "pipe",
42
- windowsHide: true,
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
- catch (error) {
46
- if (error.stdout) {
47
- try {
48
- const output = JSON.parse(error.stdout.toString());
49
- for (const fileResult of output) {
50
- for (const msg of fileResult.messages) {
51
- results.push({
52
- id: `eslint-${results.length}`,
53
- category: "maintainability",
54
- severity: msg.severity === 2 ? "error" : "warning",
55
- path: fileResult.filePath
56
- .replace(/\\/g, "/")
57
- .replace(root.replace(/\\/g, "/") + "/", ""),
58
- line_start: msg.line,
59
- summary: msg.message,
60
- rule: msg.ruleId || "eslint-error",
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
- else {
70
- process.stderr.write(`[syntax-resolution] eslint exited with no stdout; stderr: ${(error.stderr?.toString() ?? "").slice(0, 200)}\n`);
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. The supervisor launches fresh worker subprocesses that call the bounded `worker-run` entrypoint for deterministic stages.
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 the remaining work is genuinely audit-task review, `local-subprocess` stops in a terminal blocked handoff instead of pretending more automatic progress is available.
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
 
@@ -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 worker runs locally and stops in a terminal blocked state once the remaining work requires imported audit results or an interactive provider.
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.9",
3
+ "version": "0.2.10",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -131,6 +131,7 @@
131
131
  "type": "string",
132
132
  "enum": [
133
133
  "--results",
134
+ "--batch-results",
134
135
  "--updates",
135
136
  "--external-analyzer-results"
136
137
  ]