auditor-lambda 0.3.18 → 0.3.20
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 +3 -2
- package/audit-code-wrapper-lib.mjs +165 -27
- package/dist/cli.js +3 -56
- package/dist/extractors/browserExtension.d.ts +13 -0
- package/dist/extractors/browserExtension.js +378 -0
- package/dist/extractors/disposition.js +8 -1
- package/dist/extractors/fsIntake.js +1 -0
- package/dist/extractors/graph.js +6 -0
- package/dist/extractors/pathPatterns.js +15 -1
- package/dist/extractors/surfaces.d.ts +4 -1
- package/dist/extractors/surfaces.js +15 -2
- package/dist/io/runArtifacts.js +48 -0
- package/dist/orchestrator/autoFixExecutor.js +41 -10
- package/dist/orchestrator/internalExecutors.js +6 -2
- package/dist/orchestrator/planning.js +5 -1
- package/dist/orchestrator/syntaxResolutionExecutor.js +10 -1
- package/dist/orchestrator/unitBuilder.d.ts +3 -1
- package/dist/orchestrator/unitBuilder.js +19 -4
- package/dist/supervisor/operatorHandoff.js +9 -2
- package/docs/operator-guide.md +3 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +141 -1
- package/skills/audit-code/SKILL.md +5 -0
- package/skills/audit-code/agents/openai.yaml +4 -0
- package/skills/audit-code/audit-code.prompt.md +41 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { buildFileDisposition } from "../extractors/disposition.js";
|
|
2
|
+
import { buildFileDisposition, isAuditExcludedStatus, } from "../extractors/disposition.js";
|
|
3
3
|
import { buildGraphBundle, buildGraphBundleFromFs, } from "../extractors/graph.js";
|
|
4
4
|
import { buildCriticalFlowManifest } from "../extractors/flows.js";
|
|
5
5
|
import { buildRiskRegister } from "../extractors/risk.js";
|
|
@@ -123,6 +123,10 @@ export async function runIntakeExecutor(bundle, root) {
|
|
|
123
123
|
hash_files: false,
|
|
124
124
|
});
|
|
125
125
|
const disposition = buildFileDisposition(repoManifest);
|
|
126
|
+
const auditableCount = disposition.files.filter((file) => !isAuditExcludedStatus(file.status)).length;
|
|
127
|
+
if (auditableCount === 0) {
|
|
128
|
+
throw new Error(`No auditable files found in ${root}. The repository may be empty, generated-only, documentation-only, or filtered by .auditorignore.`);
|
|
129
|
+
}
|
|
126
130
|
return {
|
|
127
131
|
updated: {
|
|
128
132
|
...bundle,
|
|
@@ -140,7 +144,6 @@ export async function runStructureExecutor(bundle, root) {
|
|
|
140
144
|
const externalAnalyzerResults = bundle.external_analyzer_results;
|
|
141
145
|
const disposition = bundle.file_disposition ?? buildFileDisposition(bundle.repo_manifest);
|
|
142
146
|
const unitManifest = buildUnitManifest(bundle.repo_manifest, disposition);
|
|
143
|
-
const surfaceManifest = buildSurfaceManifest(bundle.repo_manifest, disposition);
|
|
144
147
|
const graphBundle = root
|
|
145
148
|
? await buildGraphBundleFromFs(bundle.repo_manifest, root, disposition, {
|
|
146
149
|
externalAnalyzerResults,
|
|
@@ -148,6 +151,7 @@ export async function runStructureExecutor(bundle, root) {
|
|
|
148
151
|
: buildGraphBundle(bundle.repo_manifest, disposition, {
|
|
149
152
|
externalAnalyzerResults,
|
|
150
153
|
});
|
|
154
|
+
const surfaceManifest = buildSurfaceManifest(bundle.repo_manifest, disposition, { graphBundle });
|
|
151
155
|
const criticalFlows = buildCriticalFlowManifest(bundle.repo_manifest, surfaceManifest, disposition);
|
|
152
156
|
const riskRegister = buildRiskRegister(unitManifest, criticalFlows, externalAnalyzerResults);
|
|
153
157
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { applyUnitCoverage, createCoverageMatrix, markExcludedPath, } from "../coverage.js";
|
|
2
2
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
|
+
import { hasBrowserExtensionManifestFile } from "../extractors/browserExtension.js";
|
|
3
4
|
import { deriveRequiredLensesForPath } from "./unitBuilder.js";
|
|
4
5
|
const CATEGORY_LENS_TABLE = [
|
|
5
6
|
[["security", "secret"], ["security", "correctness"]],
|
|
@@ -47,6 +48,7 @@ function applyAnalyzerCoverage(coverage, externalAnalyzerResults) {
|
|
|
47
48
|
}
|
|
48
49
|
export function initializeCoverageFromPlan(repoManifest, unitManifest, disposition, externalAnalyzerResults) {
|
|
49
50
|
const coverage = createCoverageMatrix(repoManifest.files.map((file) => file.path));
|
|
51
|
+
const isBrowserExtensionProject = hasBrowserExtensionManifestFile(repoManifest);
|
|
50
52
|
const dispositionMap = new Map(disposition.files.map((item) => [item.path, item.status]));
|
|
51
53
|
for (const file of repoManifest.files) {
|
|
52
54
|
const status = dispositionMap.get(file.path);
|
|
@@ -66,7 +68,9 @@ export function initializeCoverageFromPlan(repoManifest, unitManifest, dispositi
|
|
|
66
68
|
}
|
|
67
69
|
for (const file of repoManifest.files) {
|
|
68
70
|
const unitIds = unitIdsByPath.get(file.path) ?? [];
|
|
69
|
-
const requiredLenses = deriveRequiredLensesForPath(file.path
|
|
71
|
+
const requiredLenses = deriveRequiredLensesForPath(file.path, {
|
|
72
|
+
isBrowserExtensionProject,
|
|
73
|
+
});
|
|
70
74
|
for (const unitId of unitIds) {
|
|
71
75
|
applyUnitCoverage(coverage, file.path, unitId, requiredLenses);
|
|
72
76
|
}
|
|
@@ -15,6 +15,14 @@ const ESLINT_CONFIG_FILES = [
|
|
|
15
15
|
".eslintrc.yml",
|
|
16
16
|
".eslintrc.yaml",
|
|
17
17
|
];
|
|
18
|
+
const TSCONFIG_FILES = [
|
|
19
|
+
"tsconfig.json",
|
|
20
|
+
"tsconfig.build.json",
|
|
21
|
+
"jsconfig.json",
|
|
22
|
+
];
|
|
23
|
+
function hasTypeScriptConfig(root) {
|
|
24
|
+
return TSCONFIG_FILES.some((file) => existsSync(join(root, file)));
|
|
25
|
+
}
|
|
18
26
|
function hasEslintConfig(root) {
|
|
19
27
|
if (ESLINT_CONFIG_FILES.some((file) => existsSync(join(root, file)))) {
|
|
20
28
|
return true;
|
|
@@ -109,7 +117,8 @@ function runEslint(root) {
|
|
|
109
117
|
}
|
|
110
118
|
export function runSyntaxResolutionExecutor(bundle, root) {
|
|
111
119
|
const items = [];
|
|
112
|
-
if (
|
|
120
|
+
if (hasTypeScriptConfig(root) &&
|
|
121
|
+
bundle.file_disposition?.files.some((f) => f.path.endsWith(".ts"))) {
|
|
113
122
|
items.push(...runTsc(root));
|
|
114
123
|
}
|
|
115
124
|
if (bundle.file_disposition?.files.some((f) => f.path.endsWith(".ts") || f.path.endsWith(".js"))) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Lens, RepoManifest, UnitManifest } from "../types.js";
|
|
2
2
|
import type { FileDisposition } from "../types/disposition.js";
|
|
3
3
|
export declare const LENS_ORDER: Lens[];
|
|
4
|
-
export declare function deriveRequiredLensesForPath(path: string
|
|
4
|
+
export declare function deriveRequiredLensesForPath(path: string, options?: {
|
|
5
|
+
isBrowserExtensionProject?: boolean;
|
|
6
|
+
}): Lens[];
|
|
5
7
|
export declare function buildUnitManifest(repoManifest: RepoManifest, disposition?: FileDisposition): UnitManifest;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deriveBrowserExtensionLensesForPath, hasBrowserExtensionManifestFile, inferBrowserExtensionUnitKind, } from "../extractors/browserExtension.js";
|
|
1
2
|
import { bucketFile } from "../extractors/bucketing.js";
|
|
2
3
|
import { isAuditExcludedStatus } from "../extractors/disposition.js";
|
|
3
4
|
import { pathTokens, normalizeExtractorPath } from "../extractors/pathPatterns.js";
|
|
@@ -14,7 +15,13 @@ const LENS_MAP = {
|
|
|
14
15
|
generated_vendor: ["maintainability"],
|
|
15
16
|
unknown: ["correctness"],
|
|
16
17
|
};
|
|
17
|
-
function inferUnitKind(path) {
|
|
18
|
+
function inferUnitKind(path, isBrowserExtensionProject = false) {
|
|
19
|
+
if (isBrowserExtensionProject) {
|
|
20
|
+
const extensionKind = inferBrowserExtensionUnitKind(path);
|
|
21
|
+
if (extensionKind) {
|
|
22
|
+
return extensionKind;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
18
25
|
const normalized = path.toLowerCase();
|
|
19
26
|
if (normalized.startsWith("apps/") || normalized.startsWith("services/"))
|
|
20
27
|
return "service";
|
|
@@ -94,7 +101,7 @@ function applyExtensionLensGuards(path, lenses) {
|
|
|
94
101
|
}
|
|
95
102
|
return lenses;
|
|
96
103
|
}
|
|
97
|
-
export function deriveRequiredLensesForPath(path) {
|
|
104
|
+
export function deriveRequiredLensesForPath(path, options = {}) {
|
|
98
105
|
const assignment = bucketFile(path);
|
|
99
106
|
const required = new Set();
|
|
100
107
|
for (const bucket of assignment.buckets) {
|
|
@@ -102,6 +109,11 @@ export function deriveRequiredLensesForPath(path) {
|
|
|
102
109
|
required.add(lens);
|
|
103
110
|
}
|
|
104
111
|
}
|
|
112
|
+
if (options.isBrowserExtensionProject) {
|
|
113
|
+
for (const lens of deriveBrowserExtensionLensesForPath(path)) {
|
|
114
|
+
required.add(lens);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
105
117
|
return applyExtensionLensGuards(path, sortLenses(required));
|
|
106
118
|
}
|
|
107
119
|
function inferCriticalFlows(files, requiredLenses) {
|
|
@@ -128,13 +140,14 @@ function inferCriticalFlows(files, requiredLenses) {
|
|
|
128
140
|
}
|
|
129
141
|
export function buildUnitManifest(repoManifest, disposition) {
|
|
130
142
|
const units = new Map();
|
|
143
|
+
const isBrowserExtensionProject = hasBrowserExtensionManifestFile(repoManifest);
|
|
131
144
|
const dispositionMap = new Map(disposition?.files.map((item) => [item.path, item.status]) ?? []);
|
|
132
145
|
for (const file of repoManifest.files) {
|
|
133
146
|
const status = dispositionMap.get(file.path);
|
|
134
147
|
if (file.excluded || (status && isAuditExcludedStatus(status))) {
|
|
135
148
|
continue;
|
|
136
149
|
}
|
|
137
|
-
const kind = inferUnitKind(file.path);
|
|
150
|
+
const kind = inferUnitKind(file.path, isBrowserExtensionProject);
|
|
138
151
|
const unitId = inferUnitId(file.path, kind);
|
|
139
152
|
const existing = units.get(unitId) ?? {
|
|
140
153
|
unit_id: unitId,
|
|
@@ -148,7 +161,9 @@ export function buildUnitManifest(repoManifest, disposition) {
|
|
|
148
161
|
}
|
|
149
162
|
const assignment = bucketFile(file.path);
|
|
150
163
|
const required = new Set(existing.required_lenses);
|
|
151
|
-
for (const lens of deriveRequiredLensesForPath(file.path
|
|
164
|
+
for (const lens of deriveRequiredLensesForPath(file.path, {
|
|
165
|
+
isBrowserExtensionProject,
|
|
166
|
+
})) {
|
|
152
167
|
required.add(lens);
|
|
153
168
|
}
|
|
154
169
|
existing.required_lenses = sortLenses(required);
|
|
@@ -11,6 +11,8 @@ const RUN_LEDGER_FILENAME = "run-ledger.json";
|
|
|
11
11
|
const CURRENT_TASK_FILENAME = "current-task.json";
|
|
12
12
|
const CURRENT_PROMPT_FILENAME = "current-prompt.md";
|
|
13
13
|
const CURRENT_TASKS_FILENAME = "current-tasks.json";
|
|
14
|
+
const CURRENT_SINGLE_TASK_FILENAME = "current-single-task.json";
|
|
15
|
+
const CURRENT_SINGLE_TASK_PROMPT_FILENAME = "current-single-task-prompt.md";
|
|
14
16
|
const AUDIT_TASKS_FILENAME = "audit_tasks.json";
|
|
15
17
|
const RUNTIME_VALIDATION_TASKS_FILENAME = "runtime_validation_tasks.json";
|
|
16
18
|
const BLOCKED_STATUS = "blocked";
|
|
@@ -118,10 +120,10 @@ function buildInteractiveProviderHint(status, providerName, sessionConfigPath, i
|
|
|
118
120
|
return null;
|
|
119
121
|
}
|
|
120
122
|
if (isConfigError) {
|
|
121
|
-
return `Configuration error: Verify --root points to
|
|
123
|
+
return `Configuration error: Verify --root points to the intended repository root and that the tree contains auditable files.`;
|
|
122
124
|
}
|
|
123
125
|
const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
|
|
124
|
-
return `Provider: ${providerLabel}. For automatic LLM review, configure an interactive provider in ${sessionConfigPath}.`;
|
|
126
|
+
return `Provider: ${providerLabel}. This is a deterministic semantic-review handoff, not a failed audit. Use host subagents when the active toolset provides them; otherwise use the single-task fallback and stop after the worker command. For automatic LLM review, configure an interactive provider in ${sessionConfigPath}; that is only needed for backend-launched review.`;
|
|
125
127
|
}
|
|
126
128
|
function renderMarkdown(handoff) {
|
|
127
129
|
const lines = [
|
|
@@ -167,6 +169,9 @@ function renderMarkdown(handoff) {
|
|
|
167
169
|
for (const command of handoff.suggested_commands) {
|
|
168
170
|
lines.push(`- ${command}`);
|
|
169
171
|
}
|
|
172
|
+
if (handoff.active_review_run) {
|
|
173
|
+
lines.push("- Use packet dispatch commands only when the conversation host exposes a callable subagent tool; otherwise follow the single-task fallback.");
|
|
174
|
+
}
|
|
170
175
|
}
|
|
171
176
|
if (handoff.active_review_run) {
|
|
172
177
|
lines.push("", "Active review run:");
|
|
@@ -237,6 +242,8 @@ export function buildAuditCodeHandoff(params) {
|
|
|
237
242
|
handoff.file_map = {
|
|
238
243
|
current_task: artifactPaths.current_task,
|
|
239
244
|
current_prompt: artifactPaths.current_prompt,
|
|
245
|
+
single_task: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_FILENAME),
|
|
246
|
+
single_task_prompt: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_PROMPT_FILENAME),
|
|
240
247
|
dispatch_plan: join(params.artifactsDir, "runs", params.activeReviewRun.run_id, "dispatch-plan.json"),
|
|
241
248
|
audit_results: params.activeReviewRun.audit_results_path,
|
|
242
249
|
final_report: join(params.root, "audit-report.md"),
|
package/docs/operator-guide.md
CHANGED
|
@@ -57,7 +57,9 @@ ChatGPT-style project conversations are the intended product surface. Use
|
|
|
57
57
|
default context.
|
|
58
58
|
|
|
59
59
|
Codex should normally use the global skill seeded by the npm install plus
|
|
60
|
-
repo-local `AGENTS.md` fallback guidance.
|
|
60
|
+
repo-local `AGENTS.md` fallback guidance. The installed skill includes
|
|
61
|
+
`agents/openai.yaml` metadata so Codex can keep the slash-list display aligned
|
|
62
|
+
with the canonical `/audit-code` spelling.
|
|
61
63
|
|
|
62
64
|
Claude Desktop is treated as an MCP-first host. Use the generated project
|
|
63
65
|
template and local bundle artifacts when installing the integration.
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
const pkgRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
8
|
const promptSourceFile = join(pkgRoot, 'skills', 'audit-code', 'audit-code.prompt.md');
|
|
9
9
|
const skillSourceFile = join(pkgRoot, 'skills', 'audit-code', 'SKILL.md');
|
|
10
|
+
const codexOpenAiAgentSourceFile = join(pkgRoot, 'skills', 'audit-code', 'agents', 'openai.yaml');
|
|
10
11
|
|
|
11
12
|
function readRequiredSource(path, label) {
|
|
12
13
|
if (!existsSync(path)) {
|
|
@@ -18,6 +19,15 @@ function readRequiredSource(path, label) {
|
|
|
18
19
|
return readFileSync(path);
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
function readOptionalSource(path, label) {
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
console.warn(`audit-code: ${label} source not found at ${path} - skipping optional install`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return readFileSync(path);
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
function writeGeneratedFile(path, content) {
|
|
22
32
|
const action = existsSync(path) ? 'updated' : 'installed';
|
|
23
33
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -32,8 +42,118 @@ function splitFrontmatter(text) {
|
|
|
32
42
|
return { body: normalized.slice(match[0].length) };
|
|
33
43
|
}
|
|
34
44
|
|
|
45
|
+
const OPENCODE_AUDIT_EDIT_PERMISSION = {
|
|
46
|
+
'*': 'ask',
|
|
47
|
+
'.audit-code/**': 'allow',
|
|
48
|
+
'.audit-artifacts/**': 'allow',
|
|
49
|
+
'audit-report.md': 'allow',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const OPENCODE_AUDIT_BASH_PERMISSION = {
|
|
53
|
+
'*': 'ask',
|
|
54
|
+
'audit-code run-to-completion*': 'deny',
|
|
55
|
+
'audit-code synthesize*': 'deny',
|
|
56
|
+
'audit-code cleanup*': 'deny',
|
|
57
|
+
'audit-code requeue*': 'deny',
|
|
58
|
+
'audit-code ingest-results*': 'deny',
|
|
59
|
+
'*audit-code.mjs* run-to-completion*': 'deny',
|
|
60
|
+
'*audit-code.mjs* synthesize*': 'deny',
|
|
61
|
+
'*audit-code.mjs* cleanup*': 'deny',
|
|
62
|
+
'*audit-code.mjs* requeue*': 'deny',
|
|
63
|
+
'*audit-code.mjs* ingest-results*': 'deny',
|
|
64
|
+
'audit-code': 'allow',
|
|
65
|
+
'audit-code ensure*': 'allow',
|
|
66
|
+
'audit-code prepare-dispatch*': 'allow',
|
|
67
|
+
'audit-code submit-packet*': 'allow',
|
|
68
|
+
'audit-code merge-and-ingest*': 'allow',
|
|
69
|
+
'audit-code validate*': 'allow',
|
|
70
|
+
'*audit-code.mjs': 'allow',
|
|
71
|
+
'*audit-code.mjs* ensure*': 'allow',
|
|
72
|
+
'*audit-code.mjs* prepare-dispatch*': 'allow',
|
|
73
|
+
'*audit-code.mjs* submit-packet*': 'allow',
|
|
74
|
+
'*audit-code.mjs* merge-and-ingest*': 'allow',
|
|
75
|
+
'*audit-code.mjs* worker-run*': 'allow',
|
|
76
|
+
'*audit-code.mjs* validate*': 'allow',
|
|
77
|
+
'node* .audit-code/install/run-mcp-server.mjs*': 'allow',
|
|
78
|
+
'node* ./.audit-code/install/run-mcp-server.mjs*': 'allow',
|
|
79
|
+
'git status*': 'allow',
|
|
80
|
+
'git diff*': 'allow',
|
|
81
|
+
'grep *': 'allow',
|
|
82
|
+
'rm *': 'deny',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function objectValue(value) {
|
|
86
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
87
|
+
? value
|
|
88
|
+
: {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function mergeOpenCodePermissionRule(existingRule, generatedRule, managedRules = {}) {
|
|
92
|
+
if (generatedRule && typeof generatedRule === 'object' && !Array.isArray(generatedRule)) {
|
|
93
|
+
const generatedObject = generatedRule;
|
|
94
|
+
const merged = {};
|
|
95
|
+
const existingObject =
|
|
96
|
+
existingRule && typeof existingRule === 'object' && !Array.isArray(existingRule)
|
|
97
|
+
? existingRule
|
|
98
|
+
: {};
|
|
99
|
+
|
|
100
|
+
if (typeof existingRule === 'string') {
|
|
101
|
+
merged['*'] = existingRule;
|
|
102
|
+
} else {
|
|
103
|
+
merged['*'] = existingObject['*'] ?? generatedObject['*'] ?? 'ask';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [key, value] of Object.entries(generatedObject)) {
|
|
107
|
+
if (key !== '*') merged[key] = value;
|
|
108
|
+
}
|
|
109
|
+
for (const [key, value] of Object.entries(existingObject)) {
|
|
110
|
+
if (key !== '*') merged[key] = value;
|
|
111
|
+
}
|
|
112
|
+
for (const [key, value] of Object.entries(managedRules)) {
|
|
113
|
+
merged[key] = value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return existingRule ?? generatedRule;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function mergeOpenCodePermissionConfig(existingPermission, generatedPermission) {
|
|
123
|
+
if (!existingPermission || typeof existingPermission !== 'object' || Array.isArray(existingPermission)) {
|
|
124
|
+
return generatedPermission;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...generatedPermission,
|
|
129
|
+
...existingPermission,
|
|
130
|
+
edit: mergeOpenCodePermissionRule(
|
|
131
|
+
existingPermission.edit,
|
|
132
|
+
generatedPermission.edit,
|
|
133
|
+
OPENCODE_AUDIT_EDIT_PERMISSION,
|
|
134
|
+
),
|
|
135
|
+
bash: mergeOpenCodePermissionRule(
|
|
136
|
+
existingPermission.bash,
|
|
137
|
+
generatedPermission.bash,
|
|
138
|
+
OPENCODE_AUDIT_BASH_PERMISSION,
|
|
139
|
+
),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderOpenCodePermissionConfig() {
|
|
144
|
+
return {
|
|
145
|
+
read: 'allow',
|
|
146
|
+
glob: 'allow',
|
|
147
|
+
grep: 'allow',
|
|
148
|
+
edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
|
|
149
|
+
bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
35
153
|
function mergeOpenCodeGlobalConfig(existing, promptBody) {
|
|
36
154
|
const parsed = existing ? JSON.parse(existing) : {};
|
|
155
|
+
const auditPermission = renderOpenCodePermissionConfig();
|
|
156
|
+
const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
|
|
37
157
|
return {
|
|
38
158
|
...parsed,
|
|
39
159
|
command: {
|
|
@@ -47,12 +167,18 @@ function mergeOpenCodeGlobalConfig(existing, promptBody) {
|
|
|
47
167
|
subtask: false,
|
|
48
168
|
},
|
|
49
169
|
},
|
|
170
|
+
permission: mergeOpenCodePermissionConfig(parsed.permission, auditPermission),
|
|
50
171
|
agent: {
|
|
51
172
|
...(parsed.agent && typeof parsed.agent === 'object' && !Array.isArray(parsed.agent)
|
|
52
173
|
? parsed.agent
|
|
53
174
|
: {}),
|
|
54
175
|
auditor: {
|
|
176
|
+
...existingAuditor,
|
|
55
177
|
description: 'Read-heavy audit orchestration agent for the /audit-code workflow.',
|
|
178
|
+
permission: mergeOpenCodePermissionConfig(
|
|
179
|
+
existingAuditor.permission,
|
|
180
|
+
auditPermission,
|
|
181
|
+
),
|
|
56
182
|
},
|
|
57
183
|
},
|
|
58
184
|
};
|
|
@@ -75,23 +201,37 @@ if (!promptSource || !skillSource) {
|
|
|
75
201
|
}
|
|
76
202
|
|
|
77
203
|
const promptBody = splitFrontmatter(promptSource.toString('utf8')).body;
|
|
204
|
+
const codexOpenAiAgentSource = readOptionalSource(codexOpenAiAgentSourceFile, 'Codex skill UI metadata');
|
|
78
205
|
|
|
79
206
|
const installs = [
|
|
80
207
|
{
|
|
81
208
|
label: 'Claude command',
|
|
82
209
|
path: join(homedir(), '.claude', 'commands', 'audit-code.md'),
|
|
210
|
+
sourcePath: promptSourceFile,
|
|
83
211
|
content: promptSource,
|
|
84
212
|
},
|
|
85
213
|
{
|
|
86
214
|
label: 'Codex skill',
|
|
87
215
|
path: join(homedir(), '.codex', 'skills', 'audit-code', 'SKILL.md'),
|
|
216
|
+
sourcePath: skillSourceFile,
|
|
88
217
|
content: skillSource,
|
|
89
218
|
},
|
|
90
219
|
{
|
|
91
220
|
label: 'Codex prompt',
|
|
92
221
|
path: join(homedir(), '.codex', 'skills', 'audit-code', 'audit-code.prompt.md'),
|
|
222
|
+
sourcePath: promptSourceFile,
|
|
93
223
|
content: promptSource,
|
|
94
224
|
},
|
|
225
|
+
...(codexOpenAiAgentSource
|
|
226
|
+
? [
|
|
227
|
+
{
|
|
228
|
+
label: 'Codex skill UI metadata',
|
|
229
|
+
path: join(homedir(), '.codex', 'skills', 'audit-code', 'agents', 'openai.yaml'),
|
|
230
|
+
sourcePath: codexOpenAiAgentSourceFile,
|
|
231
|
+
content: codexOpenAiAgentSource,
|
|
232
|
+
},
|
|
233
|
+
]
|
|
234
|
+
: []),
|
|
95
235
|
];
|
|
96
236
|
|
|
97
237
|
for (const install of installs) {
|
|
@@ -101,7 +241,7 @@ for (const install of installs) {
|
|
|
101
241
|
} catch (err) {
|
|
102
242
|
console.warn(`audit-code: could not install global ${install.label} (${err.message})`);
|
|
103
243
|
console.warn(` To install manually, copy from:`);
|
|
104
|
-
console.warn(` ${install.
|
|
244
|
+
console.warn(` ${install.sourcePath}`);
|
|
105
245
|
console.warn(` to:`);
|
|
106
246
|
console.warn(` ${install.path}`);
|
|
107
247
|
}
|
|
@@ -27,6 +27,11 @@ dispatch.
|
|
|
27
27
|
If the host cannot delegate to subagents, the conversation orchestrator may
|
|
28
28
|
complete exactly one assigned review task, ingest it through the provided backend
|
|
29
29
|
command, then stop so the user can rerun `/audit-code` from fresh context.
|
|
30
|
+
In that fallback path it should not prepare packet dispatch, probe alternate
|
|
31
|
+
backend subcommands, synthesize reports, or choose a smaller task; the first
|
|
32
|
+
pending task and the exact worker command are the boundary.
|
|
33
|
+
The backend writes a deterministic single-task fallback prompt for that case so
|
|
34
|
+
the orchestrator does not need to infer the first task from a broad batch prompt.
|
|
30
35
|
|
|
31
36
|
Subagent fan-out belongs to the host agent runtime rather than to repo-local
|
|
32
37
|
backend provider settings.
|
|
@@ -25,12 +25,21 @@ and ingest results mechanically.
|
|
|
25
25
|
a backend command fails and the error explicitly requires diagnosis.
|
|
26
26
|
- Do not inspect individual subagent result files after dispatch. Validation
|
|
27
27
|
and ingestion are backend responsibilities.
|
|
28
|
+
- Do not inspect the backend command catalog or try alternate subcommands to
|
|
29
|
+
bypass a blocked semantic-review handoff. In particular, do not run
|
|
30
|
+
`run-to-completion`, `synthesize`, `cleanup`, `requeue`, or direct
|
|
31
|
+
`ingest-results` while following this directive.
|
|
32
|
+
- A report under `.audit-artifacts/` is not a completion signal while
|
|
33
|
+
`audit_state.status` is `"blocked"`. Present a report only after Step 5.
|
|
28
34
|
- CRITICAL: Do not use your `Read` tool to read `entry.prompt_path` or JSON schemas into your own context window. The subagent will read them. Pass the path literally.
|
|
29
35
|
- Prefer subagent dispatch for semantic review whenever the host exposes an
|
|
30
36
|
Agent/subagent tool.
|
|
31
37
|
- Treat the user's `/audit-code` request as explicit authorization to launch
|
|
32
38
|
review subagents in parallel. Do not ask for a separate delegation request
|
|
33
39
|
before using available Agent/subagent tools.
|
|
40
|
+
- Decide subagent support from the active toolset, not from shell commands or
|
|
41
|
+
backend provider names. A shell command named `agent`, an MCP prompt, or a
|
|
42
|
+
`local-subprocess` provider is not a host subagent facility.
|
|
34
43
|
- Do not use `browser_subagent` for semantic review of source code unless the
|
|
35
44
|
task explicitly requires browser-based validation.
|
|
36
45
|
- If the host cannot dispatch subagents, complete exactly one assigned review
|
|
@@ -86,7 +95,11 @@ If status is `"blocked"` for semantic review, continue to Step 2.
|
|
|
86
95
|
|
|
87
96
|
## Step 2 - Dispatch Review Work
|
|
88
97
|
|
|
89
|
-
|
|
98
|
+
Use this step only when the active toolset exposes a callable host subagent
|
|
99
|
+
facility such as `Agent`, `Task`, or an equivalent built-in delegation tool.
|
|
100
|
+
Do not try to discover subagent support by running shell commands.
|
|
101
|
+
|
|
102
|
+
When that callable subagent facility exists, prepare a dispatch plan by default:
|
|
90
103
|
|
|
91
104
|
```bash
|
|
92
105
|
audit-code prepare-dispatch --run-id <run_id> --artifacts-dir <artifacts_dir>
|
|
@@ -132,21 +145,43 @@ error. Do not improvise manual merging or state edits.
|
|
|
132
145
|
|
|
133
146
|
Loop back to Step 1.
|
|
134
147
|
|
|
148
|
+
If no callable host subagent facility exists, or a delegation attempt fails
|
|
149
|
+
because the host does not provide such a tool, go directly to Step 3. Do not run
|
|
150
|
+
`prepare-dispatch`, do not inspect generated packet prompts, and do not try
|
|
151
|
+
alternate backend commands.
|
|
152
|
+
|
|
135
153
|
## Step 3 - Single-Task Fallback
|
|
136
154
|
|
|
137
155
|
Use this path only when the host cannot dispatch subagents.
|
|
138
156
|
|
|
139
|
-
|
|
140
|
-
|
|
157
|
+
Allowed backend command in this step: the exact `worker_command` from the task
|
|
158
|
+
file, after you have written the single-task result. Do not run `audit-code`,
|
|
159
|
+
`run-to-completion`, `prepare-dispatch`, `merge-and-ingest`, `synthesize`,
|
|
160
|
+
`validate`, or any other backend command as a substitute for the fallback.
|
|
161
|
+
|
|
162
|
+
Read the generated single-task fallback prompt at
|
|
163
|
+
`handoff.file_map.single_task_prompt` when present, otherwise
|
|
164
|
+
`.audit-artifacts/dispatch/current-single-task-prompt.md`. That file is
|
|
165
|
+
deterministically narrowed to the first pending task. If it is unavailable, read
|
|
166
|
+
the current review prompt named by `handoff.active_review_run.prompt_path` or
|
|
167
|
+
`.audit-artifacts/dispatch/current-prompt.md`, plus the matching task file
|
|
141
168
|
needed to find `audit_results_path` and `worker_command`.
|
|
142
169
|
|
|
143
170
|
Complete exactly one assigned review task. If a batch file lists multiple tasks,
|
|
144
|
-
choose the first pending task
|
|
145
|
-
|
|
171
|
+
choose the first pending task by array order only; do not substitute a smaller
|
|
172
|
+
or easier task. If that first task covers a large file, use targeted reads and
|
|
173
|
+
searches within its assigned files instead of abandoning it. Read only that
|
|
174
|
+
task's assigned files. Write one valid `AuditResult` object, wrapped in a JSON
|
|
175
|
+
array, to `audit_results_path`.
|
|
176
|
+
|
|
177
|
+
If the current review prompt says to produce results for every listed task, the
|
|
178
|
+
single-task fallback overrides that wording for the top-level orchestrator:
|
|
179
|
+
produce exactly one result for the first pending task only.
|
|
146
180
|
|
|
147
181
|
Run the exact `worker_command` from the task file. Then stop and summarize that
|
|
148
182
|
one bounded step. Do not loop into another semantic review task in the same
|
|
149
|
-
conversation turn.
|
|
183
|
+
conversation turn. Do not re-check audit state or read an audit report after the
|
|
184
|
+
worker command.
|
|
150
185
|
|
|
151
186
|
## Step 4 - Backend Failure Handling
|
|
152
187
|
|