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
package/README.md
CHANGED
|
@@ -30,8 +30,9 @@ npm install -g auditor-lambda
|
|
|
30
30
|
|
|
31
31
|
That makes `audit-code` available on `PATH`. During package install, the package
|
|
32
32
|
also writes user-level command/skill assets for hosts we can seed safely, including
|
|
33
|
-
the Claude command file, the global Codex skill bundle
|
|
34
|
-
slash command entry in
|
|
33
|
+
the Claude command file, the global Codex skill bundle with `audit-code` display
|
|
34
|
+
metadata, and the global OpenCode slash command entry in
|
|
35
|
+
`~/.config/opencode/opencode.json`.
|
|
35
36
|
|
|
36
37
|
After that, invoke `/audit-code` in a supported host. The prompt self-bootstraps
|
|
37
38
|
the current repository by running:
|
|
@@ -533,8 +533,59 @@ function renderCodexAutomationRecipe() {
|
|
|
533
533
|
].join('\n');
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
+
const OPENCODE_AUDIT_EDIT_PERMISSION = {
|
|
537
|
+
'*': 'ask',
|
|
538
|
+
'.audit-code/**': 'allow',
|
|
539
|
+
'.audit-artifacts/**': 'allow',
|
|
540
|
+
'audit-report.md': 'allow',
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const OPENCODE_AUDIT_BASH_PERMISSION = {
|
|
544
|
+
'*': 'ask',
|
|
545
|
+
'audit-code run-to-completion*': 'deny',
|
|
546
|
+
'audit-code synthesize*': 'deny',
|
|
547
|
+
'audit-code cleanup*': 'deny',
|
|
548
|
+
'audit-code requeue*': 'deny',
|
|
549
|
+
'audit-code ingest-results*': 'deny',
|
|
550
|
+
'*audit-code.mjs* run-to-completion*': 'deny',
|
|
551
|
+
'*audit-code.mjs* synthesize*': 'deny',
|
|
552
|
+
'*audit-code.mjs* cleanup*': 'deny',
|
|
553
|
+
'*audit-code.mjs* requeue*': 'deny',
|
|
554
|
+
'*audit-code.mjs* ingest-results*': 'deny',
|
|
555
|
+
'audit-code': 'allow',
|
|
556
|
+
'audit-code ensure*': 'allow',
|
|
557
|
+
'audit-code prepare-dispatch*': 'allow',
|
|
558
|
+
'audit-code submit-packet*': 'allow',
|
|
559
|
+
'audit-code merge-and-ingest*': 'allow',
|
|
560
|
+
'audit-code validate*': 'allow',
|
|
561
|
+
'*audit-code.mjs': 'allow',
|
|
562
|
+
'*audit-code.mjs* ensure*': 'allow',
|
|
563
|
+
'*audit-code.mjs* prepare-dispatch*': 'allow',
|
|
564
|
+
'*audit-code.mjs* submit-packet*': 'allow',
|
|
565
|
+
'*audit-code.mjs* merge-and-ingest*': 'allow',
|
|
566
|
+
'*audit-code.mjs* worker-run*': 'allow',
|
|
567
|
+
'*audit-code.mjs* validate*': 'allow',
|
|
568
|
+
'node* .audit-code/install/run-mcp-server.mjs*': 'allow',
|
|
569
|
+
'node* ./.audit-code/install/run-mcp-server.mjs*': 'allow',
|
|
570
|
+
'git status*': 'allow',
|
|
571
|
+
'git diff*': 'allow',
|
|
572
|
+
'grep *': 'allow',
|
|
573
|
+
'rm *': 'deny',
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
function renderOpenCodePermissionConfig() {
|
|
577
|
+
return {
|
|
578
|
+
read: 'allow',
|
|
579
|
+
glob: 'allow',
|
|
580
|
+
grep: 'allow',
|
|
581
|
+
edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
|
|
582
|
+
bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
536
586
|
function renderOpenCodeProjectConfig(root, promptBody) {
|
|
537
587
|
const launcher = replaceBackslashes(toRepoRelativePath(root, join(root, '.audit-code', 'install', MCP_LAUNCHER_FILENAME)));
|
|
588
|
+
const auditPermission = renderOpenCodePermissionConfig();
|
|
538
589
|
return {
|
|
539
590
|
$schema: 'https://opencode.ai/config.json',
|
|
540
591
|
command: {
|
|
@@ -553,19 +604,7 @@ function renderOpenCodeProjectConfig(root, promptBody) {
|
|
|
553
604
|
timeout: 10000,
|
|
554
605
|
},
|
|
555
606
|
},
|
|
556
|
-
permission:
|
|
557
|
-
read: 'allow',
|
|
558
|
-
glob: 'allow',
|
|
559
|
-
grep: 'allow',
|
|
560
|
-
edit: 'ask',
|
|
561
|
-
bash: {
|
|
562
|
-
'*': 'ask',
|
|
563
|
-
'git status*': 'allow',
|
|
564
|
-
'git diff*': 'allow',
|
|
565
|
-
'grep *': 'allow',
|
|
566
|
-
'rm *': 'deny',
|
|
567
|
-
},
|
|
568
|
-
},
|
|
607
|
+
permission: auditPermission,
|
|
569
608
|
agent: {
|
|
570
609
|
auditor: {
|
|
571
610
|
description:
|
|
@@ -574,14 +613,8 @@ function renderOpenCodeProjectConfig(root, promptBody) {
|
|
|
574
613
|
'auditor*': true,
|
|
575
614
|
},
|
|
576
615
|
permission: {
|
|
577
|
-
edit:
|
|
578
|
-
bash: {
|
|
579
|
-
'*': 'ask',
|
|
580
|
-
'git status*': 'allow',
|
|
581
|
-
'git diff*': 'allow',
|
|
582
|
-
'grep *': 'allow',
|
|
583
|
-
'rm *': 'deny',
|
|
584
|
-
},
|
|
616
|
+
edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
|
|
617
|
+
bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
|
|
585
618
|
question: 'allow',
|
|
586
619
|
},
|
|
587
620
|
},
|
|
@@ -607,6 +640,99 @@ function objectValue(value) {
|
|
|
607
640
|
: {};
|
|
608
641
|
}
|
|
609
642
|
|
|
643
|
+
function mergeOpenCodePermissionRule(existingRule, generatedRule, managedRules = {}) {
|
|
644
|
+
if (generatedRule && typeof generatedRule === 'object' && !Array.isArray(generatedRule)) {
|
|
645
|
+
const generatedObject = generatedRule;
|
|
646
|
+
const merged = {};
|
|
647
|
+
const existingObject =
|
|
648
|
+
existingRule && typeof existingRule === 'object' && !Array.isArray(existingRule)
|
|
649
|
+
? existingRule
|
|
650
|
+
: {};
|
|
651
|
+
|
|
652
|
+
if (typeof existingRule === 'string') {
|
|
653
|
+
merged['*'] = existingRule;
|
|
654
|
+
} else {
|
|
655
|
+
merged['*'] = existingObject['*'] ?? generatedObject['*'] ?? 'ask';
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
for (const [key, value] of Object.entries(generatedObject)) {
|
|
659
|
+
if (key !== '*') merged[key] = value;
|
|
660
|
+
}
|
|
661
|
+
for (const [key, value] of Object.entries(existingObject)) {
|
|
662
|
+
if (key !== '*') merged[key] = value;
|
|
663
|
+
}
|
|
664
|
+
for (const [key, value] of Object.entries(managedRules)) {
|
|
665
|
+
merged[key] = value;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return merged;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return existingRule ?? generatedRule;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function mergeOpenCodePermissionConfig(existingPermission, generatedPermission) {
|
|
675
|
+
if (!existingPermission || typeof existingPermission !== 'object' || Array.isArray(existingPermission)) {
|
|
676
|
+
return generatedPermission;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
...generatedPermission,
|
|
681
|
+
...existingPermission,
|
|
682
|
+
edit: mergeOpenCodePermissionRule(
|
|
683
|
+
existingPermission.edit,
|
|
684
|
+
generatedPermission.edit,
|
|
685
|
+
OPENCODE_AUDIT_EDIT_PERMISSION,
|
|
686
|
+
),
|
|
687
|
+
bash: mergeOpenCodePermissionRule(
|
|
688
|
+
existingPermission.bash,
|
|
689
|
+
generatedPermission.bash,
|
|
690
|
+
OPENCODE_AUDIT_BASH_PERMISSION,
|
|
691
|
+
),
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
|
|
696
|
+
const edit = permissionConfig?.edit;
|
|
697
|
+
const bash = permissionConfig?.bash;
|
|
698
|
+
if (!edit || typeof edit !== 'object' || Array.isArray(edit)) {
|
|
699
|
+
throw new Error(`OpenCode ${label}.edit must allow audit-owned file paths. Run "audit-code install --host opencode".`);
|
|
700
|
+
}
|
|
701
|
+
for (const pattern of ['.audit-code/**', '.audit-artifacts/**', 'audit-report.md']) {
|
|
702
|
+
if (edit[pattern] !== 'allow') {
|
|
703
|
+
throw new Error(`OpenCode ${label}.edit must allow ${pattern}. Run "audit-code install --host opencode".`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!bash || typeof bash !== 'object' || Array.isArray(bash)) {
|
|
707
|
+
throw new Error(`OpenCode ${label}.bash must allow audit-code commands. Run "audit-code install --host opencode".`);
|
|
708
|
+
}
|
|
709
|
+
for (const pattern of [
|
|
710
|
+
'audit-code',
|
|
711
|
+
'audit-code ensure*',
|
|
712
|
+
'audit-code prepare-dispatch*',
|
|
713
|
+
'audit-code submit-packet*',
|
|
714
|
+
'audit-code merge-and-ingest*',
|
|
715
|
+
'*audit-code.mjs',
|
|
716
|
+
'*audit-code.mjs* submit-packet*',
|
|
717
|
+
'*audit-code.mjs* merge-and-ingest*',
|
|
718
|
+
'node* .audit-code/install/run-mcp-server.mjs*',
|
|
719
|
+
]) {
|
|
720
|
+
if (bash[pattern] !== 'allow') {
|
|
721
|
+
throw new Error(`OpenCode ${label}.bash must allow ${pattern}. Run "audit-code install --host opencode".`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
for (const pattern of [
|
|
725
|
+
'audit-code run-to-completion*',
|
|
726
|
+
'audit-code synthesize*',
|
|
727
|
+
'*audit-code.mjs* run-to-completion*',
|
|
728
|
+
'*audit-code.mjs* synthesize*',
|
|
729
|
+
]) {
|
|
730
|
+
if (bash[pattern] !== 'deny') {
|
|
731
|
+
throw new Error(`OpenCode ${label}.bash must deny ${pattern}. Run "audit-code install --host opencode".`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
610
736
|
function buildMergedOpenCodeProjectConfig(existing, root, promptBody) {
|
|
611
737
|
const generated = renderOpenCodeProjectConfig(root, promptBody);
|
|
612
738
|
return {
|
|
@@ -620,13 +746,17 @@ function buildMergedOpenCodeProjectConfig(existing, root, promptBody) {
|
|
|
620
746
|
...objectValue(existing.mcp),
|
|
621
747
|
auditor: generated.mcp.auditor,
|
|
622
748
|
},
|
|
623
|
-
permission:
|
|
624
|
-
existing.permission && typeof existing.permission === 'object' && !Array.isArray(existing.permission)
|
|
625
|
-
? existing.permission
|
|
626
|
-
: generated.permission,
|
|
749
|
+
permission: mergeOpenCodePermissionConfig(existing.permission, generated.permission),
|
|
627
750
|
agent: {
|
|
628
751
|
...objectValue(existing.agent),
|
|
629
|
-
auditor:
|
|
752
|
+
auditor: {
|
|
753
|
+
...objectValue(objectValue(existing.agent).auditor),
|
|
754
|
+
...generated.agent.auditor,
|
|
755
|
+
permission: mergeOpenCodePermissionConfig(
|
|
756
|
+
objectValue(objectValue(existing.agent).auditor).permission,
|
|
757
|
+
generated.agent.auditor.permission,
|
|
758
|
+
),
|
|
759
|
+
},
|
|
630
760
|
},
|
|
631
761
|
};
|
|
632
762
|
}
|
|
@@ -1822,8 +1952,10 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
1822
1952
|
if (commandConfig.template !== sourceBody.trimStart()) {
|
|
1823
1953
|
throw new Error('OpenCode config command["audit-code"].template is out of sync with the source prompt. Run "audit-code install".');
|
|
1824
1954
|
}
|
|
1955
|
+
assertOpenCodeAuditPermissionConfig(config?.permission, 'permission');
|
|
1956
|
+
assertOpenCodeAuditPermissionConfig(config?.agent?.auditor?.permission, 'agent.auditor.permission');
|
|
1825
1957
|
return {
|
|
1826
|
-
summary: 'OpenCode project config has MCP server
|
|
1958
|
+
summary: 'OpenCode project config has MCP server, /audit-code slash command, and audit permissions.',
|
|
1827
1959
|
path: assetPaths.opencodeConfigPath,
|
|
1828
1960
|
};
|
|
1829
1961
|
});
|
|
@@ -2001,6 +2133,12 @@ async function detectBootstrapRefreshReason(root, host) {
|
|
|
2001
2133
|
if (opencodeConfig?.command?.['audit-code']?.template !== sourcePromptBody.trimStart()) {
|
|
2002
2134
|
return 'stale_host_asset:opencode:config_command';
|
|
2003
2135
|
}
|
|
2136
|
+
try {
|
|
2137
|
+
assertOpenCodeAuditPermissionConfig(opencodeConfig?.permission, 'permission');
|
|
2138
|
+
assertOpenCodeAuditPermissionConfig(opencodeConfig?.agent?.auditor?.permission, 'agent.auditor.permission');
|
|
2139
|
+
} catch {
|
|
2140
|
+
return 'stale_host_asset:opencode:permissions';
|
|
2141
|
+
}
|
|
2004
2142
|
if (await fileExists(join(root, '.opencode', 'commands', 'audit-code.md'))) {
|
|
2005
2143
|
return 'stale_host_asset:opencode:legacy_command_file';
|
|
2006
2144
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
@@ -218,8 +218,8 @@ async function emitEnvelope(params) {
|
|
|
218
218
|
}
|
|
219
219
|
function buildManualReviewBlocker(providerName) {
|
|
220
220
|
return providerName === LOCAL_SUBPROCESS_PROVIDER_NAME
|
|
221
|
-
? "Ready for LLM review.
|
|
222
|
-
"
|
|
221
|
+
? "Ready for LLM semantic review. If the host exposes a callable subagent tool, prepare dispatch and fan out packets. " +
|
|
222
|
+
"If not, use single-task fallback: review only the first pending task, write one AuditResult to the run audit-results path, execute worker_command, then stop."
|
|
223
223
|
: "Audit blocked: waiting for manual audit results or interactive provider configuration.";
|
|
224
224
|
}
|
|
225
225
|
function shouldRunInlineExecutor(selectedExecutor) {
|
|
@@ -308,30 +308,6 @@ async function listBatchResultFiles(batchDir) {
|
|
|
308
308
|
}
|
|
309
309
|
return files;
|
|
310
310
|
}
|
|
311
|
-
const PROJECT_SIGNALS = [
|
|
312
|
-
"package.json",
|
|
313
|
-
"go.mod",
|
|
314
|
-
"Cargo.toml",
|
|
315
|
-
"pom.xml",
|
|
316
|
-
"build.gradle",
|
|
317
|
-
"pyproject.toml",
|
|
318
|
-
"setup.py",
|
|
319
|
-
"setup.cfg",
|
|
320
|
-
"Makefile",
|
|
321
|
-
"CMakeLists.txt",
|
|
322
|
-
];
|
|
323
|
-
async function detectProjectRoot(root) {
|
|
324
|
-
for (const signal of PROJECT_SIGNALS) {
|
|
325
|
-
try {
|
|
326
|
-
await access(join(root, signal));
|
|
327
|
-
return signal;
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
// not found, try next
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
311
|
function buildPendingAuditTasks(bundle) {
|
|
336
312
|
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
337
313
|
const pendingTasks = (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
|
|
@@ -716,35 +692,6 @@ async function cmdRunToCompletion(argv) {
|
|
|
716
692
|
let pendingBatchAuditResults = batchResultsDir
|
|
717
693
|
? await listBatchResultFiles(batchResultsDir)
|
|
718
694
|
: [];
|
|
719
|
-
const earlyBundle = await loadArtifactBundle(artifactsDir);
|
|
720
|
-
if (!earlyBundle.unit_manifest) {
|
|
721
|
-
const foundSignal = await detectProjectRoot(root);
|
|
722
|
-
if (!foundSignal) {
|
|
723
|
-
const blocker = `No recognisable project signals found in ${root}. Expected one of: ${PROJECT_SIGNALS.join(", ")}. Check that --root points to the repository root, not a subdirectory or an unrelated path.`;
|
|
724
|
-
const earlyState = deriveAuditState(earlyBundle);
|
|
725
|
-
const blockedState = buildBlockedAuditState({
|
|
726
|
-
state: earlyState,
|
|
727
|
-
obligationId: null,
|
|
728
|
-
executor: null,
|
|
729
|
-
blocker,
|
|
730
|
-
});
|
|
731
|
-
await emitEnvelope({
|
|
732
|
-
root,
|
|
733
|
-
artifactsDir,
|
|
734
|
-
bundle: { ...earlyBundle, audit_state: blockedState },
|
|
735
|
-
audit_state: blockedState,
|
|
736
|
-
selected_obligation: null,
|
|
737
|
-
selected_executor: null,
|
|
738
|
-
progress_made: false,
|
|
739
|
-
artifacts_written: [],
|
|
740
|
-
progress_summary: blocker,
|
|
741
|
-
next_likely_step: null,
|
|
742
|
-
providerName: provider.name,
|
|
743
|
-
isConfigError: true,
|
|
744
|
-
});
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
695
|
let pendingAuditResultsPath = getFlag(argv, "--results");
|
|
749
696
|
let pendingRuntimeUpdatesPath = getFlag(argv, "--updates");
|
|
750
697
|
let pendingExternalAnalyzerPath = getFlag(argv, "--external-analyzer-results");
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Lens, RepoManifest } from "../types.js";
|
|
2
|
+
import type { FileDisposition } from "../types/disposition.js";
|
|
3
|
+
import type { GraphBundle, GraphEdge } from "../types/graph.js";
|
|
4
|
+
import type { SurfaceRecord } from "../types/surfaces.js";
|
|
5
|
+
export declare const BROWSER_EXTENSION_HEURISTIC_NOTE = "Chrome extension manifest and HTML asset references were resolved deterministically from local paths; verify unusual dynamic registration manually.";
|
|
6
|
+
export declare function isBrowserExtensionManifestPath(path: string): boolean;
|
|
7
|
+
export declare function extractChromeExtensionManifestEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|
|
8
|
+
export declare function extractHtmlResourceEdges(fromPath: string, content: string, pathLookup: Map<string, string>): GraphEdge[];
|
|
9
|
+
export declare function hasBrowserExtensionManifestFile(repoManifest: RepoManifest): boolean;
|
|
10
|
+
export declare function deriveBrowserExtensionLensesForPath(path: string): Lens[];
|
|
11
|
+
export declare function inferBrowserExtensionUnitKind(path: string): string | undefined;
|
|
12
|
+
export declare function buildBrowserExtensionSurfacesFromGraph(graphBundle: GraphBundle | undefined, disposition?: FileDisposition): SurfaceRecord[];
|
|
13
|
+
export declare function chromeExtensionRiskSignalsForManifest(content: string): string[];
|