auditor-lambda 0.3.19 → 0.3.21
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 +15 -4
- package/audit-code-wrapper-lib.mjs +248 -56
- package/dist/cli.js +578 -12
- package/dist/io/runArtifacts.js +48 -0
- package/dist/prompts/renderWorkerPrompt.js +1 -0
- package/dist/supervisor/operatorHandoff.js +10 -7
- package/docs/contracts.md +23 -1
- package/docs/operator-guide.md +14 -5
- package/docs/product.md +4 -3
- package/package.json +1 -1
- package/scripts/postinstall.mjs +174 -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 +15 -135
package/dist/io/runArtifacts.js
CHANGED
|
@@ -10,6 +10,8 @@ const findingSchemaPath = join(packageRoot, "schemas", "finding.schema.json");
|
|
|
10
10
|
const CURRENT_TASK_FILENAME = "current-task.json";
|
|
11
11
|
const CURRENT_PROMPT_FILENAME = "current-prompt.md";
|
|
12
12
|
const CURRENT_TASKS_FILENAME = "current-tasks.json";
|
|
13
|
+
const CURRENT_SINGLE_TASK_FILENAME = "current-single-task.json";
|
|
14
|
+
const CURRENT_SINGLE_TASK_PROMPT_FILENAME = "current-single-task-prompt.md";
|
|
13
15
|
const CURRENT_SCHEMA_FILENAME = "audit-result.schema.json";
|
|
14
16
|
const CURRENT_RESULTS_SCHEMA_FILENAME = "audit-results.schema.json";
|
|
15
17
|
const CURRENT_FINDING_SCHEMA_FILENAME = "finding.schema.json";
|
|
@@ -63,6 +65,49 @@ async function writeDispatchSchemaFiles(artifactsDir) {
|
|
|
63
65
|
await writeFile(join(dispatchDir, CURRENT_RESULTS_SCHEMA_FILENAME), await readFile(auditResultsSchemaPath, "utf8"), "utf8");
|
|
64
66
|
await writeFile(join(dispatchDir, CURRENT_FINDING_SCHEMA_FILENAME), await readFile(findingSchemaPath, "utf8"), "utf8");
|
|
65
67
|
}
|
|
68
|
+
function renderSingleTaskFallbackPrompt(task, auditTask) {
|
|
69
|
+
const commandArgv = JSON.stringify(task.worker_command);
|
|
70
|
+
const lineCounts = auditTask.file_paths
|
|
71
|
+
.map((path) => `- ${path}: ${auditTask.file_line_counts?.[path] ?? 0} lines`)
|
|
72
|
+
.join("\n");
|
|
73
|
+
return [
|
|
74
|
+
"# audit-code single-task fallback",
|
|
75
|
+
"",
|
|
76
|
+
"Use this file only when the conversation host cannot dispatch subagents.",
|
|
77
|
+
"This prompt is generated deterministically from the first pending task.",
|
|
78
|
+
"",
|
|
79
|
+
`run_id: ${task.run_id}`,
|
|
80
|
+
`task_id: ${auditTask.task_id}`,
|
|
81
|
+
`unit_id: ${auditTask.unit_id}`,
|
|
82
|
+
`pass_id: ${auditTask.pass_id}`,
|
|
83
|
+
`lens: ${auditTask.lens}`,
|
|
84
|
+
`rationale: ${auditTask.rationale}`,
|
|
85
|
+
"",
|
|
86
|
+
"Assigned files and line counts:",
|
|
87
|
+
lineCounts,
|
|
88
|
+
"",
|
|
89
|
+
"Instructions:",
|
|
90
|
+
"1. Read only the assigned files above.",
|
|
91
|
+
"2. Produce exactly one AuditResult object for task_id above, wrapped in a JSON array.",
|
|
92
|
+
"3. Write that JSON array to audit_results_path.",
|
|
93
|
+
"4. Run worker_command exactly, then stop without checking audit state or reading a report.",
|
|
94
|
+
"",
|
|
95
|
+
`audit_results_path: ${task.audit_results_path}`,
|
|
96
|
+
`worker_command: ${commandArgv}`,
|
|
97
|
+
"",
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
100
|
+
async function writeSingleTaskFallbackFiles(artifactsDir, task, currentTasks) {
|
|
101
|
+
if (task.preferred_executor !== "agent" ||
|
|
102
|
+
!task.audit_results_path ||
|
|
103
|
+
!currentTasks ||
|
|
104
|
+
currentTasks.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const firstTask = currentTasks[0];
|
|
108
|
+
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_SINGLE_TASK_FILENAME), firstTask);
|
|
109
|
+
await writeFile(join(artifactsDir, "dispatch", CURRENT_SINGLE_TASK_PROMPT_FILENAME), renderSingleTaskFallbackPrompt(task, firstTask), "utf8");
|
|
110
|
+
}
|
|
66
111
|
export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, currentTasks, options = {}) {
|
|
67
112
|
await mkdir(paths.runDir, { recursive: true });
|
|
68
113
|
await writeJsonFile(paths.taskPath, task);
|
|
@@ -78,6 +123,7 @@ export async function writeWorkerTaskFiles(task, prompt, paths, artifactsDir, cu
|
|
|
78
123
|
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASK_FILENAME), task);
|
|
79
124
|
await writeFile(join(artifactsDir, "dispatch", CURRENT_PROMPT_FILENAME), prompt, "utf8");
|
|
80
125
|
await writeJsonFile(join(artifactsDir, "dispatch", CURRENT_TASKS_FILENAME), currentTasks ?? []);
|
|
126
|
+
await writeSingleTaskFallbackFiles(artifactsDir, task, currentTasks);
|
|
81
127
|
await writeDispatchSchemaFiles(artifactsDir);
|
|
82
128
|
}
|
|
83
129
|
export async function writeDispatchBatchFiles(artifactsDir, runs, currentTasks) {
|
|
@@ -121,6 +167,8 @@ export async function clearDispatchFiles(artifactsDir) {
|
|
|
121
167
|
CURRENT_TASK_FILENAME,
|
|
122
168
|
CURRENT_PROMPT_FILENAME,
|
|
123
169
|
CURRENT_TASKS_FILENAME,
|
|
170
|
+
CURRENT_SINGLE_TASK_FILENAME,
|
|
171
|
+
CURRENT_SINGLE_TASK_PROMPT_FILENAME,
|
|
124
172
|
CURRENT_SCHEMA_FILENAME,
|
|
125
173
|
CURRENT_RESULTS_SCHEMA_FILENAME,
|
|
126
174
|
CURRENT_FINDING_SCHEMA_FILENAME,
|
|
@@ -12,6 +12,7 @@ export function renderWorkerPrompt(task) {
|
|
|
12
12
|
`Read: ${tasksPath}`,
|
|
13
13
|
"Scope: review only the tasks listed in the Read file. Do not add tasks,",
|
|
14
14
|
"edit source files, remediate findings, run unrelated audits, or write result_path.",
|
|
15
|
+
"Prefer host Read and Grep tools for source inspection. On native Windows, do not use Unix pipelines like `grep ... | head`; if shell search is unavoidable, use `Select-String` as a fallback.",
|
|
15
16
|
"For each listed task: read the assigned file_paths under the specified lens,",
|
|
16
17
|
"using targeted reads/searches where they give complete enough evidence without loading unrelated context,",
|
|
17
18
|
"and emit exactly one AuditResult object with:",
|
|
@@ -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";
|
|
@@ -103,9 +105,7 @@ function buildSuggestedCommands(artifactsDir, suggestedInputs, status, activeRev
|
|
|
103
105
|
return [
|
|
104
106
|
renderShellCommand([
|
|
105
107
|
"audit-code",
|
|
106
|
-
"
|
|
107
|
-
"--run-id",
|
|
108
|
-
activeReviewRun.run_id,
|
|
108
|
+
"next-step",
|
|
109
109
|
"--artifacts-dir",
|
|
110
110
|
artifactsDir,
|
|
111
111
|
]),
|
|
@@ -121,7 +121,7 @@ function buildInteractiveProviderHint(status, providerName, sessionConfigPath, i
|
|
|
121
121
|
return `Configuration error: Verify --root points to the intended repository root and that the tree contains auditable files.`;
|
|
122
122
|
}
|
|
123
123
|
const providerLabel = providerName ?? LOCAL_SUBPROCESS_PROVIDER_NAME;
|
|
124
|
-
return `Provider: ${providerLabel}. For automatic LLM review, configure an interactive provider in ${sessionConfigPath}.`;
|
|
124
|
+
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
125
|
}
|
|
126
126
|
function renderMarkdown(handoff) {
|
|
127
127
|
const lines = [
|
|
@@ -167,6 +167,9 @@ function renderMarkdown(handoff) {
|
|
|
167
167
|
for (const command of handoff.suggested_commands) {
|
|
168
168
|
lines.push(`- ${command}`);
|
|
169
169
|
}
|
|
170
|
+
if (handoff.active_review_run) {
|
|
171
|
+
lines.push("- Use next-step so the backend renders either packet dispatch or single-task fallback after the host reports capabilities.");
|
|
172
|
+
}
|
|
170
173
|
}
|
|
171
174
|
if (handoff.active_review_run) {
|
|
172
175
|
lines.push("", "Active review run:");
|
|
@@ -228,15 +231,15 @@ export function buildAuditCodeHandoff(params) {
|
|
|
228
231
|
if (params.state.status === BLOCKED_STATUS && params.activeReviewRun) {
|
|
229
232
|
handoff.quick_start = renderShellCommand([
|
|
230
233
|
"audit-code",
|
|
231
|
-
"
|
|
232
|
-
"--run-id",
|
|
233
|
-
params.activeReviewRun.run_id,
|
|
234
|
+
"next-step",
|
|
234
235
|
"--artifacts-dir",
|
|
235
236
|
params.artifactsDir,
|
|
236
237
|
]);
|
|
237
238
|
handoff.file_map = {
|
|
238
239
|
current_task: artifactPaths.current_task,
|
|
239
240
|
current_prompt: artifactPaths.current_prompt,
|
|
241
|
+
single_task: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_FILENAME),
|
|
242
|
+
single_task_prompt: join(params.artifactsDir, "dispatch", CURRENT_SINGLE_TASK_PROMPT_FILENAME),
|
|
240
243
|
dispatch_plan: join(params.artifactsDir, "runs", params.activeReviewRun.run_id, "dispatch-plan.json"),
|
|
241
244
|
audit_results: params.activeReviewRun.audit_results_path,
|
|
242
245
|
final_report: join(params.root, "audit-report.md"),
|
package/docs/contracts.md
CHANGED
|
@@ -77,6 +77,23 @@ The backend stores resumable artifacts under `.audit-artifacts/`, including:
|
|
|
77
77
|
Consumers should treat these as versioned JSON artifacts and validate them with
|
|
78
78
|
`audit-code validate` rather than inferring state from filenames alone.
|
|
79
79
|
|
|
80
|
+
## Step artifacts
|
|
81
|
+
|
|
82
|
+
The conversation-first `/audit-code` prompt is a loader. It runs
|
|
83
|
+
`audit-code next-step` and then follows only the returned step prompt. The
|
|
84
|
+
backend writes the current step contract to:
|
|
85
|
+
|
|
86
|
+
- `<artifacts_dir>/steps/current-step.json`
|
|
87
|
+
- `<artifacts_dir>/steps/current-prompt.md`
|
|
88
|
+
|
|
89
|
+
`current-step.json` uses `contract_version: "audit-code-step/v1alpha1"` and
|
|
90
|
+
includes `step_kind`, `prompt_path`, `status`, `run_id`, `allowed_commands`,
|
|
91
|
+
`stop_condition`, `repo_root`, `artifacts_dir`, and relevant `artifact_paths`.
|
|
92
|
+
|
|
93
|
+
When semantic review is blocked, `next-step` first emits a `capability_check`.
|
|
94
|
+
After the host reports `--host-can-dispatch-subagents true|false`, the backend
|
|
95
|
+
renders exactly one review path: packet dispatch or the single-task fallback.
|
|
96
|
+
|
|
80
97
|
## Dispatch packets
|
|
81
98
|
|
|
82
99
|
Packet dispatch preserves the existing `AuditTask` and `AuditResult`
|
|
@@ -92,13 +109,18 @@ Planning artifacts are shaped by:
|
|
|
92
109
|
Normal packet flow:
|
|
93
110
|
|
|
94
111
|
```text
|
|
95
|
-
audit-code
|
|
112
|
+
audit-code next-step --host-can-dispatch-subagents true
|
|
113
|
+
backend prepares dispatch-plan.json
|
|
96
114
|
conversation launches one worker per dispatch-plan entry
|
|
97
115
|
worker reads entry.prompt_path
|
|
98
116
|
worker submits AuditResult[] through submit-packet
|
|
99
117
|
audit-code merge-and-ingest --run-id <run_id> --artifacts-dir <artifacts_dir>
|
|
100
118
|
```
|
|
101
119
|
|
|
120
|
+
`audit-code prepare-dispatch --run-id <run_id> --artifacts-dir
|
|
121
|
+
<artifacts_dir>` remains available for compatibility and tests, but generic
|
|
122
|
+
handoff fields point users and prompts to `next-step`.
|
|
123
|
+
|
|
102
124
|
Packet artifacts:
|
|
103
125
|
|
|
104
126
|
- `<artifacts_dir>/runs/<run_id>/dispatch-plan.json`
|
package/docs/operator-guide.md
CHANGED
|
@@ -43,7 +43,7 @@ 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 `/audit-code`
|
|
46
|
+
- OpenCode: `opencode.json` with auditor MCP server 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
48
|
- Antigravity: planning-mode and MCP-oriented guidance
|
|
49
49
|
|
|
@@ -57,14 +57,17 @@ 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.
|
|
64
66
|
|
|
65
|
-
OpenCode uses
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
OpenCode uses the global command seeded by `npm install -g auditor-lambda`.
|
|
68
|
+
The generated project `opencode.json` should not define `command["audit-code"]`;
|
|
69
|
+
it only wires the auditor MCP server and project permissions. VS Code uses
|
|
70
|
+
repo-local prompt and MCP configuration files.
|
|
68
71
|
|
|
69
72
|
Antigravity should be treated as a workflow-and-artifacts host until it has a
|
|
70
73
|
stable project-local config surface. Use generated planning-mode guidance,
|
|
@@ -98,6 +101,7 @@ The wrapper:
|
|
|
98
101
|
Useful fallback commands:
|
|
99
102
|
|
|
100
103
|
```bash
|
|
104
|
+
audit-code next-step
|
|
101
105
|
audit-code --single-step
|
|
102
106
|
audit-code --results /path/to/audit_results.json
|
|
103
107
|
audit-code --batch-results /path/to/results-dir
|
|
@@ -109,6 +113,11 @@ audit-code cleanup
|
|
|
109
113
|
audit-code mcp
|
|
110
114
|
```
|
|
111
115
|
|
|
116
|
+
`audit-code next-step` is the backend-rendered step engine used by the
|
|
117
|
+
conversation prompt. It writes `.audit-artifacts/steps/current-step.json` and
|
|
118
|
+
`.audit-artifacts/steps/current-prompt.md`, then the host should follow only
|
|
119
|
+
that prompt.
|
|
120
|
+
|
|
112
121
|
`audit-code validate` checks artifact shape, cross-artifact consistency,
|
|
113
122
|
session config, and explicit provider readiness.
|
|
114
123
|
|
package/docs/product.md
CHANGED
|
@@ -148,9 +148,10 @@ Readiness should be judged through three checks:
|
|
|
148
148
|
- field-trial quality: run real repositories through planning, validate
|
|
149
149
|
artifacts, and use `audit_plan_metrics.json` to track packet count, weak
|
|
150
150
|
packet count, average cohesion, merge edge kinds, and weak-packet samples
|
|
151
|
-
- full-loop behavior: prove `
|
|
152
|
-
`submit-packet`, `merge-and-ingest`, selective deepening,
|
|
153
|
-
and final `audit-report.md` promotion in at least one
|
|
151
|
+
- full-loop behavior: prove `next-step` capability routing, packet dispatch,
|
|
152
|
+
worker review, `submit-packet`, `merge-and-ingest`, selective deepening,
|
|
153
|
+
runtime validation, and final `audit-report.md` promotion in at least one
|
|
154
|
+
real host flow
|
|
154
155
|
- release hygiene: keep `npm run verify:release`, linked smoke, packaged
|
|
155
156
|
smoke, tarball preview, and Trusted Publishing green from a clean checkout
|
|
156
157
|
|
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,151 @@ 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
|
+
'*dist*index.js* run-to-completion*': 'deny',
|
|
60
|
+
'*dist*index.js* synthesize*': 'deny',
|
|
61
|
+
'*dist*index.js* cleanup*': 'deny',
|
|
62
|
+
'*dist*index.js* requeue*': 'deny',
|
|
63
|
+
'*dist*index.js* ingest-results*': 'deny',
|
|
64
|
+
'*audit-code.mjs* run-to-completion*': 'deny',
|
|
65
|
+
'*audit-code.mjs* synthesize*': 'deny',
|
|
66
|
+
'*audit-code.mjs* cleanup*': 'deny',
|
|
67
|
+
'*audit-code.mjs* requeue*': 'deny',
|
|
68
|
+
'*audit-code.mjs* ingest-results*': 'deny',
|
|
69
|
+
'audit-code': 'allow',
|
|
70
|
+
'audit-code ensure*': 'allow',
|
|
71
|
+
'audit-code next-step*': 'allow',
|
|
72
|
+
'audit-code prepare-dispatch*': 'allow',
|
|
73
|
+
'audit-code submit-packet*': 'allow',
|
|
74
|
+
'audit-code merge-and-ingest*': 'allow',
|
|
75
|
+
'audit-code validate*': 'allow',
|
|
76
|
+
'*audit-code.mjs': 'allow',
|
|
77
|
+
'*audit-code.mjs* ensure*': 'allow',
|
|
78
|
+
'*audit-code.mjs* next-step*': 'allow',
|
|
79
|
+
'*audit-code.mjs* prepare-dispatch*': 'allow',
|
|
80
|
+
'*audit-code.mjs* submit-packet*': 'allow',
|
|
81
|
+
'*audit-code.mjs* merge-and-ingest*': 'allow',
|
|
82
|
+
'*audit-code.mjs* worker-run*': 'allow',
|
|
83
|
+
'*audit-code.mjs* validate*': 'allow',
|
|
84
|
+
'*node* *auditor-lambda*dist*index.js* worker-run*': 'allow',
|
|
85
|
+
'node* .audit-code/install/run-mcp-server.mjs*': 'allow',
|
|
86
|
+
'node* ./.audit-code/install/run-mcp-server.mjs*': 'allow',
|
|
87
|
+
'git status*': 'allow',
|
|
88
|
+
'git diff*': 'allow',
|
|
89
|
+
'grep *': 'allow',
|
|
90
|
+
'Select-String *': 'allow',
|
|
91
|
+
'rm *': 'deny',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function replaceBackslashes(value) {
|
|
95
|
+
return value.replace(/\\/g, '/');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function externalDirectoryPattern(path) {
|
|
99
|
+
return `${replaceBackslashes(path).replace(/\/+$/u, '')}/**`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function renderOpenCodeExternalDirectoryPermission() {
|
|
103
|
+
return {
|
|
104
|
+
[externalDirectoryPattern(pkgRoot)]: 'allow',
|
|
105
|
+
[externalDirectoryPattern(dirname(process.execPath))]: 'allow',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function objectValue(value) {
|
|
110
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
111
|
+
? value
|
|
112
|
+
: {};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function mergeOpenCodePermissionRule(existingRule, generatedRule, managedRules = {}) {
|
|
116
|
+
if (generatedRule && typeof generatedRule === 'object' && !Array.isArray(generatedRule)) {
|
|
117
|
+
const generatedObject = generatedRule;
|
|
118
|
+
const merged = {};
|
|
119
|
+
const existingObject =
|
|
120
|
+
existingRule && typeof existingRule === 'object' && !Array.isArray(existingRule)
|
|
121
|
+
? existingRule
|
|
122
|
+
: {};
|
|
123
|
+
|
|
124
|
+
if (typeof existingRule === 'string') {
|
|
125
|
+
merged['*'] = existingRule;
|
|
126
|
+
} else {
|
|
127
|
+
merged['*'] = existingObject['*'] ?? generatedObject['*'] ?? 'ask';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const [key, value] of Object.entries(generatedObject)) {
|
|
131
|
+
if (key !== '*') merged[key] = value;
|
|
132
|
+
}
|
|
133
|
+
for (const [key, value] of Object.entries(existingObject)) {
|
|
134
|
+
if (key !== '*') merged[key] = value;
|
|
135
|
+
}
|
|
136
|
+
for (const [key, value] of Object.entries(managedRules)) {
|
|
137
|
+
merged[key] = value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return merged;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return existingRule ?? generatedRule;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function mergeOpenCodePermissionConfig(existingPermission, generatedPermission) {
|
|
147
|
+
if (!existingPermission || typeof existingPermission !== 'object' || Array.isArray(existingPermission)) {
|
|
148
|
+
return generatedPermission;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...generatedPermission,
|
|
153
|
+
...existingPermission,
|
|
154
|
+
read: generatedPermission.read,
|
|
155
|
+
glob: generatedPermission.glob,
|
|
156
|
+
grep: generatedPermission.grep,
|
|
157
|
+
external_directory: mergeOpenCodePermissionRule(
|
|
158
|
+
existingPermission.external_directory,
|
|
159
|
+
generatedPermission.external_directory,
|
|
160
|
+
generatedPermission.external_directory,
|
|
161
|
+
),
|
|
162
|
+
edit: mergeOpenCodePermissionRule(
|
|
163
|
+
existingPermission.edit,
|
|
164
|
+
generatedPermission.edit,
|
|
165
|
+
OPENCODE_AUDIT_EDIT_PERMISSION,
|
|
166
|
+
),
|
|
167
|
+
bash: mergeOpenCodePermissionRule(
|
|
168
|
+
existingPermission.bash,
|
|
169
|
+
generatedPermission.bash,
|
|
170
|
+
OPENCODE_AUDIT_BASH_PERMISSION,
|
|
171
|
+
),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function renderOpenCodePermissionConfig() {
|
|
176
|
+
return {
|
|
177
|
+
read: 'allow',
|
|
178
|
+
glob: 'allow',
|
|
179
|
+
grep: 'allow',
|
|
180
|
+
external_directory: renderOpenCodeExternalDirectoryPermission(),
|
|
181
|
+
edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
|
|
182
|
+
bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
35
186
|
function mergeOpenCodeGlobalConfig(existing, promptBody) {
|
|
36
187
|
const parsed = existing ? JSON.parse(existing) : {};
|
|
188
|
+
const auditPermission = renderOpenCodePermissionConfig();
|
|
189
|
+
const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
|
|
37
190
|
return {
|
|
38
191
|
...parsed,
|
|
39
192
|
command: {
|
|
@@ -47,12 +200,18 @@ function mergeOpenCodeGlobalConfig(existing, promptBody) {
|
|
|
47
200
|
subtask: false,
|
|
48
201
|
},
|
|
49
202
|
},
|
|
203
|
+
permission: mergeOpenCodePermissionConfig(parsed.permission, auditPermission),
|
|
50
204
|
agent: {
|
|
51
205
|
...(parsed.agent && typeof parsed.agent === 'object' && !Array.isArray(parsed.agent)
|
|
52
206
|
? parsed.agent
|
|
53
207
|
: {}),
|
|
54
208
|
auditor: {
|
|
209
|
+
...existingAuditor,
|
|
55
210
|
description: 'Read-heavy audit orchestration agent for the /audit-code workflow.',
|
|
211
|
+
permission: mergeOpenCodePermissionConfig(
|
|
212
|
+
existingAuditor.permission,
|
|
213
|
+
auditPermission,
|
|
214
|
+
),
|
|
56
215
|
},
|
|
57
216
|
},
|
|
58
217
|
};
|
|
@@ -75,23 +234,37 @@ if (!promptSource || !skillSource) {
|
|
|
75
234
|
}
|
|
76
235
|
|
|
77
236
|
const promptBody = splitFrontmatter(promptSource.toString('utf8')).body;
|
|
237
|
+
const codexOpenAiAgentSource = readOptionalSource(codexOpenAiAgentSourceFile, 'Codex skill UI metadata');
|
|
78
238
|
|
|
79
239
|
const installs = [
|
|
80
240
|
{
|
|
81
241
|
label: 'Claude command',
|
|
82
242
|
path: join(homedir(), '.claude', 'commands', 'audit-code.md'),
|
|
243
|
+
sourcePath: promptSourceFile,
|
|
83
244
|
content: promptSource,
|
|
84
245
|
},
|
|
85
246
|
{
|
|
86
247
|
label: 'Codex skill',
|
|
87
248
|
path: join(homedir(), '.codex', 'skills', 'audit-code', 'SKILL.md'),
|
|
249
|
+
sourcePath: skillSourceFile,
|
|
88
250
|
content: skillSource,
|
|
89
251
|
},
|
|
90
252
|
{
|
|
91
253
|
label: 'Codex prompt',
|
|
92
254
|
path: join(homedir(), '.codex', 'skills', 'audit-code', 'audit-code.prompt.md'),
|
|
255
|
+
sourcePath: promptSourceFile,
|
|
93
256
|
content: promptSource,
|
|
94
257
|
},
|
|
258
|
+
...(codexOpenAiAgentSource
|
|
259
|
+
? [
|
|
260
|
+
{
|
|
261
|
+
label: 'Codex skill UI metadata',
|
|
262
|
+
path: join(homedir(), '.codex', 'skills', 'audit-code', 'agents', 'openai.yaml'),
|
|
263
|
+
sourcePath: codexOpenAiAgentSourceFile,
|
|
264
|
+
content: codexOpenAiAgentSource,
|
|
265
|
+
},
|
|
266
|
+
]
|
|
267
|
+
: []),
|
|
95
268
|
];
|
|
96
269
|
|
|
97
270
|
for (const install of installs) {
|
|
@@ -101,7 +274,7 @@ for (const install of installs) {
|
|
|
101
274
|
} catch (err) {
|
|
102
275
|
console.warn(`audit-code: could not install global ${install.label} (${err.message})`);
|
|
103
276
|
console.warn(` To install manually, copy from:`);
|
|
104
|
-
console.warn(` ${install.
|
|
277
|
+
console.warn(` ${install.sourcePath}`);
|
|
105
278
|
console.warn(` to:`);
|
|
106
279
|
console.warn(` ${install.path}`);
|
|
107
280
|
}
|
|
@@ -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.
|