feat: 封装NapCat Codex审计执行器
This commit is contained in:
parent
99408d3cec
commit
3f7ed99aec
@ -1,4 +1,4 @@
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
@ -41,6 +41,7 @@ import {
|
||||
upstreamAuditOutputSchema,
|
||||
} from "./tools/napcatAutomation.types.js";
|
||||
import {
|
||||
buildNapcatCodexExecCommand,
|
||||
buildNapcatUpstreamAudit,
|
||||
classifyNapcatUpstreamAudit,
|
||||
} from "./tools/napcatAutomation.js";
|
||||
@ -119,6 +120,66 @@ export async function runSelfTest(): Promise<void> {
|
||||
"NapCat upstream audit dry-run artifact plan self-check failed",
|
||||
);
|
||||
}
|
||||
const codexCommand = buildNapcatCodexExecCommand({
|
||||
artifactRoot: "/vol1/docker/kt-codex/artifacts/smoke",
|
||||
contextPacketPath:
|
||||
"/vol1/docker/kt-codex/artifacts/smoke/context-packet.md",
|
||||
outputSchemaPath:
|
||||
"/vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow/prompts/napcat/schemas/upstream-audit.schema.json",
|
||||
promptName: "upstream-audit",
|
||||
workspaceRoot: "/vol1/docker/kt-codex/workspace/KT",
|
||||
});
|
||||
if (!codexCommand.includes("--ask-for-approval never")) {
|
||||
throw new Error("NapCat Codex runner approval policy self-check failed");
|
||||
}
|
||||
for (const expected of [
|
||||
"CODEX_HOME='/vol1/docker/kt-codex/home/.codex'",
|
||||
"--cd '/vol1/docker/kt-codex/workspace/KT'",
|
||||
"--profile-v2 automation",
|
||||
"--sandbox workspace-write",
|
||||
"--json",
|
||||
"--ephemeral",
|
||||
"--output-schema '/vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow/prompts/napcat/schemas/upstream-audit.schema.json'",
|
||||
"--output-last-message '/vol1/docker/kt-codex/artifacts/smoke/codex-upstream-audit.md'",
|
||||
"\"$(cat '/vol1/docker/kt-codex/artifacts/smoke/context-packet.md')\"",
|
||||
]) {
|
||||
if (!codexCommand.includes(expected)) {
|
||||
throw new Error(`NapCat Codex runner command missing: ${expected}`);
|
||||
}
|
||||
}
|
||||
if (codexCommand.includes("git push") || codexCommand.includes("kubectl")) {
|
||||
throw new Error("NapCat Codex runner mutation guard self-check failed");
|
||||
}
|
||||
const codexExecutionArtifactRoot = resolveInsideRoot(
|
||||
".kt-workspace/test-artifacts/napcat-codex-runner-self-test",
|
||||
);
|
||||
const codexInvalidSchemaAudit = buildNapcatUpstreamAudit({
|
||||
artifactRoot: codexExecutionArtifactRoot,
|
||||
codexExecutor: () => ({
|
||||
ok: true,
|
||||
stderr: "",
|
||||
stdout: "{\"classification\":\"surprising\"}\n",
|
||||
}),
|
||||
dryMergeConflict: false,
|
||||
execute: true,
|
||||
forkPatchFiles: [],
|
||||
upstreamChangedFiles: [],
|
||||
useCodex: true,
|
||||
});
|
||||
if (
|
||||
codexInvalidSchemaAudit.classification !== "blocked" ||
|
||||
!codexInvalidSchemaAudit.reasonCodes.includes("CODEX_SCHEMA_INVALID")
|
||||
) {
|
||||
throw new Error("NapCat Codex invalid schema fallback self-check failed");
|
||||
}
|
||||
if (
|
||||
!existsSync(
|
||||
path.join(codexExecutionArtifactRoot, "codex-upstream-audit.jsonl"),
|
||||
) ||
|
||||
!existsSync(path.join(codexExecutionArtifactRoot, "context-packet.md"))
|
||||
) {
|
||||
throw new Error("NapCat Codex artifact capture self-check failed");
|
||||
}
|
||||
const dryRunAuditCommands: string[] = [];
|
||||
for (const command of dryRunAudit.commands) {
|
||||
if (!command.readOnly) {
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { upstreamAuditOutputSchema } from './napcatAutomation.types.js';
|
||||
import { loadNapcatAutomationPrompt } from './napcatAutomation.prompts.js';
|
||||
import type {
|
||||
NapcatAuditClassificationInput,
|
||||
NapcatAuditClassificationResult,
|
||||
NapcatAuditCommand,
|
||||
NapcatAutomationReasonCode,
|
||||
NapcatCodexExecCommandInput,
|
||||
NapcatCodexExecResult,
|
||||
NapcatCodexExecutor,
|
||||
NapcatUpstreamAuditInput,
|
||||
NapcatUpstreamAuditResult,
|
||||
} from './napcatAutomation.types.js';
|
||||
|
||||
@ -75,6 +85,330 @@ const hotZoneMatchers: Array<{ pattern: string; regex: RegExp }> = [
|
||||
{ pattern: 'vite*.ts', regex: /(?:^|\/)vite[^/]*\.ts$/i },
|
||||
];
|
||||
|
||||
/**
|
||||
* Quotes a POSIX shell path argument for NAS-side Codex automation commands.
|
||||
* @param value - Absolute or repo-relative filesystem path that will be interpreted by a Linux shell.
|
||||
* @returns A single-quoted shell token with embedded single quotes escaped.
|
||||
*/
|
||||
function shellQuotePath(value: string): string {
|
||||
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the default trusted Codex home from the NAS workspace root layout.
|
||||
* @param workspaceRoot - Linux workspace root passed to `codex exec --cd`, usually `/vol1/docker/kt-codex/workspace/KT`.
|
||||
* @returns The colocated Codex home path, or the stabilized NAS default when the workspace root does not match the expected layout.
|
||||
*/
|
||||
function deriveCodexHome(workspaceRoot: string): string {
|
||||
const marker = '/workspace/';
|
||||
const markerIndex = workspaceRoot.indexOf(marker);
|
||||
if (markerIndex >= 0) {
|
||||
return path.posix.join(workspaceRoot.slice(0, markerIndex), 'home/.codex');
|
||||
}
|
||||
return '/vol1/docker/kt-codex/home/.codex';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the bounded Codex CLI command used by NapCat upstream audit automation.
|
||||
* @param input - Linux paths for artifact capture, the context packet prompt, JSON schema validation, and the trusted KT workspace root; `codexHome` may override the derived NAS default.
|
||||
* @returns A POSIX shell command that runs `codex exec` with workspace-write sandboxing, never approval, JSONL output, and last-message artifact capture.
|
||||
*/
|
||||
export function buildNapcatCodexExecCommand(
|
||||
input: NapcatCodexExecCommandInput,
|
||||
): string {
|
||||
const outputLastMessagePath = path.posix.join(
|
||||
input.artifactRoot,
|
||||
`codex-${input.promptName}.md`,
|
||||
);
|
||||
const codexHome = input.codexHome ?? deriveCodexHome(input.workspaceRoot);
|
||||
|
||||
return [
|
||||
`CODEX_HOME=${shellQuotePath(codexHome)}`,
|
||||
'codex exec',
|
||||
`--cd ${shellQuotePath(input.workspaceRoot)}`,
|
||||
'--profile-v2 automation',
|
||||
'--sandbox workspace-write',
|
||||
'--ask-for-approval never',
|
||||
'--json',
|
||||
'--ephemeral',
|
||||
`--output-schema ${shellQuotePath(input.outputSchemaPath)}`,
|
||||
`--output-last-message ${shellQuotePath(outputLastMessagePath)}`,
|
||||
`"$(cat ${shellQuotePath(input.contextPacketPath)})"`,
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a POSIX-style artifact child path while preserving deterministic command text for NAS execution.
|
||||
* @param artifactRoot - Artifact directory supplied by automation input or the default KT test-artifact root.
|
||||
* @param fileName - File name to place directly under the artifact root.
|
||||
* @returns A stable path string that can be used by both local self-tests and Linux-side command construction.
|
||||
*/
|
||||
function joinNapcatArtifactPath(artifactRoot: string, fileName: string): string {
|
||||
return path.posix.join(artifactRoot, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the artifact root for dry-run and execute-mode NapCat upstream audit outputs.
|
||||
* @param artifactRoot - Optional caller-provided artifact directory; when omitted the standard KT test-artifact folder is used.
|
||||
* @returns A non-empty artifact root path.
|
||||
*/
|
||||
function resolveNapcatArtifactRoot(artifactRoot?: string): string {
|
||||
return (
|
||||
artifactRoot?.trim() ||
|
||||
'.kt-workspace/test-artifacts/napcat-upstream-audit'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the Linux workspace root used by the NAS Codex automation runner.
|
||||
* @param workspaceRoot - Optional trusted KT workspace root override.
|
||||
* @returns The configured workspace root or the stabilized NAS kt-codex workspace path.
|
||||
*/
|
||||
function resolveNapcatCodexWorkspaceRoot(workspaceRoot?: string): string {
|
||||
return workspaceRoot?.trim() || '/vol1/docker/kt-codex/workspace/KT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the JSON schema path passed to `codex exec --output-schema`.
|
||||
* @param input - Upstream audit input containing an optional schema path and workspace root.
|
||||
* @returns The caller-provided schema path or the source-controlled upstream-audit schema under the trusted workspace root.
|
||||
*/
|
||||
function resolveNapcatCodexOutputSchemaPath(
|
||||
input: NapcatUpstreamAuditInput,
|
||||
): string {
|
||||
if (input.outputSchemaPath?.trim()) {
|
||||
return input.outputSchemaPath.trim();
|
||||
}
|
||||
return path.posix.join(
|
||||
resolveNapcatCodexWorkspaceRoot(input.workspaceRoot),
|
||||
'mcp/ktWorkflow/prompts/napcat/schemas/upstream-audit.schema.json',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a deterministic Markdown context packet for Codex upstream audit reasoning.
|
||||
* @param input - NapCat upstream audit metadata and optional repo/ref hints; values are serialized as context only and must not be executed.
|
||||
* @param classification - Deterministic local classifier output that Codex can compare against upstream risk evidence.
|
||||
* @returns Markdown prompt content with the source-controlled prompt, sanitized metadata, and explicit no-mutation constraints.
|
||||
*/
|
||||
function buildNapcatCodexContextPacket(
|
||||
input: NapcatUpstreamAuditInput,
|
||||
classification: NapcatAuditClassificationResult,
|
||||
): string {
|
||||
const promptText = loadNapcatAutomationPrompt('upstream-audit').trim();
|
||||
const context = {
|
||||
classification,
|
||||
constraints: {
|
||||
forbiddenCommands: ['git push', 'kubectl', 'docker build', 'deploy'],
|
||||
outputSchema: 'upstream-audit.schema.json',
|
||||
},
|
||||
input: {
|
||||
createCandidateBranch: input.createCandidateBranch === true,
|
||||
dryMergeConflict: input.dryMergeConflict,
|
||||
forkBranch: input.forkBranch,
|
||||
forkPatchFiles: input.forkPatchFiles,
|
||||
forkRepo: redactUrlCredentials(input.forkRepo),
|
||||
lastAcceptedUpstreamBase: input.lastAcceptedUpstreamBase,
|
||||
upstreamChangedFiles: input.upstreamChangedFiles,
|
||||
upstreamReleaseTag: input.upstreamReleaseTag,
|
||||
upstreamRepo: redactUrlCredentials(input.upstreamRepo),
|
||||
},
|
||||
schemaVersion: 1,
|
||||
};
|
||||
|
||||
return [
|
||||
'# NapCat Upstream Audit Codex Context',
|
||||
'',
|
||||
'## Source Prompt',
|
||||
'',
|
||||
promptText,
|
||||
'',
|
||||
'## Context',
|
||||
'',
|
||||
'```json',
|
||||
JSON.stringify(context, null, 2),
|
||||
'```',
|
||||
'',
|
||||
'## Safety',
|
||||
'',
|
||||
'- Do not edit files.',
|
||||
'- Do not run git push, kubectl, docker build, deploy, or release mutation commands.',
|
||||
'- Return only JSON that matches the supplied output schema.',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redacts credentials from URL-shaped repository hints before they enter context artifacts.
|
||||
* @param value - Optional repo URL or short repository identifier from automation input.
|
||||
* @returns The original value when no URL credentials are present; otherwise the same URL with userinfo removed.
|
||||
*/
|
||||
function redactUrlCredentials(value?: string): string | undefined {
|
||||
if (!value) return undefined;
|
||||
return value.replace(/:\/\/[^/@\s]+@/g, '://');
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the Codex CLI command with the task-required ten minute timeout.
|
||||
* @param command - POSIX shell command produced by `buildNapcatCodexExecCommand`.
|
||||
* @returns Captured stdout/stderr and an ok flag; failures are returned so callers can preserve artifacts and classify as blocked.
|
||||
*/
|
||||
function executeNapcatCodexCommand(command: string): NapcatCodexExecResult {
|
||||
try {
|
||||
const stdout = execSync(command, {
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 10 * 1024 * 1024,
|
||||
shell: process.platform === 'win32' ? undefined : '/bin/bash',
|
||||
timeout: 10 * 60 * 1000,
|
||||
});
|
||||
return { ok: true, stderr: '', stdout };
|
||||
} catch (error) {
|
||||
const execError = error as {
|
||||
stderr?: Buffer | string;
|
||||
stdout?: Buffer | string;
|
||||
};
|
||||
return {
|
||||
ok: false,
|
||||
stderr: bufferLikeToString(execError.stderr),
|
||||
stdout: bufferLikeToString(execError.stdout),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts optional exec output into UTF-8 text for artifact preservation.
|
||||
* @param value - Buffer or string captured from a child-process result.
|
||||
* @returns String output, or an empty string when the stream was not captured.
|
||||
*/
|
||||
function bufferLikeToString(value?: Buffer | string): string {
|
||||
if (!value) return '';
|
||||
return Buffer.isBuffer(value) ? value.toString('utf8') : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Codex stdout and last-message content into the strict upstream audit schema.
|
||||
* @param stdout - JSONL stdout emitted by `codex exec --json`; the final schema payload may be a raw line or nested text field.
|
||||
* @param lastMessage - Optional content from `--output-last-message` when Codex wrote one.
|
||||
* @returns Parsed classification result, or null when no schema-valid payload can be found.
|
||||
*/
|
||||
function parseNapcatCodexAuditOutput(
|
||||
stdout: string,
|
||||
lastMessage: string,
|
||||
): NapcatAuditClassificationResult | null {
|
||||
const candidates = [
|
||||
...stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
.reverse(),
|
||||
lastMessage,
|
||||
stdout,
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const parsed = parseNapcatCodexAuditCandidate(candidate);
|
||||
if (parsed) return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse one raw Codex output candidate, including nested string payloads.
|
||||
* @param candidate - Raw JSON, JSONL line, markdown fenced JSON, or nested Codex event text.
|
||||
* @returns A schema-valid upstream audit result, or null when the candidate does not contain one.
|
||||
*/
|
||||
function parseNapcatCodexAuditCandidate(
|
||||
candidate: string,
|
||||
): NapcatAuditClassificationResult | null {
|
||||
const trimmed = candidate.trim();
|
||||
if (!trimmed) return null;
|
||||
|
||||
for (const jsonText of collectJsonTextCandidates(trimmed)) {
|
||||
try {
|
||||
const value = JSON.parse(jsonText) as unknown;
|
||||
const direct = upstreamAuditOutputSchema.safeParse(value);
|
||||
if (direct.success) return direct.data;
|
||||
|
||||
const nested = parseNestedCodexAuditCandidate(value);
|
||||
if (nested) return nested;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts direct and fenced JSON snippets from Codex text output.
|
||||
* @param value - Raw Codex event text or last-message Markdown.
|
||||
* @returns Candidate JSON strings in most-specific-first order.
|
||||
*/
|
||||
function collectJsonTextCandidates(value: string): string[] {
|
||||
const candidates = [value];
|
||||
const fencedJson = value.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]?.trim();
|
||||
if (fencedJson) {
|
||||
candidates.unshift(fencedJson);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches common Codex JSONL event shapes for a nested schema payload string.
|
||||
* @param value - Parsed JSONL event or raw nested JSON value from Codex stdout.
|
||||
* @returns A schema-valid upstream audit result, or null when no nested payload matches.
|
||||
*/
|
||||
function parseNestedCodexAuditCandidate(
|
||||
value: unknown,
|
||||
): NapcatAuditClassificationResult | null {
|
||||
if (!value || typeof value !== 'object') return null;
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of [...value].reverse()) {
|
||||
const parsed = parseNestedCodexAuditCandidate(item);
|
||||
if (parsed) return parsed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const record = value as Record<string, unknown>;
|
||||
for (const key of ['content', 'message', 'output', 'text', 'lastMessage']) {
|
||||
const nestedValue = record[key];
|
||||
if (typeof nestedValue === 'string') {
|
||||
const parsed = parseNapcatCodexAuditCandidate(nestedValue);
|
||||
if (parsed) return parsed;
|
||||
} else if (nestedValue && typeof nestedValue === 'object') {
|
||||
const parsed = parseNestedCodexAuditCandidate(nestedValue);
|
||||
if (parsed) return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the blocked result used when Codex execution or schema parsing fails.
|
||||
* @param artifacts - Artifact paths already written or reserved for the failed execution.
|
||||
* @param commands - Command metadata returned to callers for auditability.
|
||||
* @returns A blocked upstream audit result with the `CODEX_SCHEMA_INVALID` reason code.
|
||||
*/
|
||||
function buildCodexSchemaInvalidResult(
|
||||
artifacts: NapcatUpstreamAuditResult['artifacts'],
|
||||
commands: NapcatAuditCommand[],
|
||||
): NapcatUpstreamAuditResult {
|
||||
return {
|
||||
artifacts,
|
||||
classification: 'blocked',
|
||||
commands,
|
||||
execute: true,
|
||||
hotZonePatterns: [...napcatUpstreamHotZonePatterns],
|
||||
reasonCodes: ['CODEX_SCHEMA_INVALID'],
|
||||
recommendedAction: 'block',
|
||||
summary:
|
||||
'Codex upstream audit execution did not produce schema-valid JSON; inspect context, JSONL stdout, and last-message artifacts before continuing.',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether Git audit metadata contains a repo-relative path that the classifier can trust.
|
||||
* @param value - Raw path string from fork/upstream Git diff collectors; empty entries are allowed because collectors may emit blank lines.
|
||||
@ -289,14 +623,15 @@ export function classifyNapcatUpstreamAudit(
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a read-only upstream audit plan for NapCatQQ release sync automation.
|
||||
* @param input - Same deterministic classifier input supplied by the dry-run collector; values are embedded only in the returned classification, never executed.
|
||||
* @returns Dry-run commands and artifact paths for a future collector, with no Git merge/push, Docker, Kubernetes, or release mutations.
|
||||
* Builds or executes a NapCatQQ upstream audit plan.
|
||||
* @param input - Deterministic collector metadata plus optional Codex automation settings; execute mode writes context/stdout artifacts and runs the bounded Codex command only when both `execute` and `useCodex` are true.
|
||||
* @returns Dry-run classification and commands, or Codex schema output with artifact paths; failures are blocked with `CODEX_SCHEMA_INVALID` and preserved artifacts.
|
||||
*/
|
||||
export function buildNapcatUpstreamAudit(
|
||||
input: NapcatAuditClassificationInput,
|
||||
input: NapcatUpstreamAuditInput,
|
||||
): NapcatUpstreamAuditResult {
|
||||
const classification = classifyNapcatUpstreamAudit(input);
|
||||
const artifactRoot = resolveNapcatArtifactRoot(input.artifactRoot);
|
||||
const commands: NapcatAuditCommand[] = [
|
||||
{
|
||||
command: 'git status --short --branch',
|
||||
@ -335,19 +670,99 @@ export function buildNapcatUpstreamAudit(
|
||||
},
|
||||
];
|
||||
|
||||
if (input.execute === true && input.useCodex === true) {
|
||||
const promptName = input.promptName ?? 'upstream-audit';
|
||||
const contextPacketPath = joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
'context-packet.md',
|
||||
);
|
||||
const jsonlPath = joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
'codex-upstream-audit.jsonl',
|
||||
);
|
||||
const lastMessagePath = joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
`codex-${promptName}.md`,
|
||||
);
|
||||
const outputSchemaPath = resolveNapcatCodexOutputSchemaPath(input);
|
||||
const workspaceRoot = resolveNapcatCodexWorkspaceRoot(input.workspaceRoot);
|
||||
const codexCommand = buildNapcatCodexExecCommand({
|
||||
artifactRoot,
|
||||
codexHome: input.codexHome,
|
||||
contextPacketPath,
|
||||
outputSchemaPath,
|
||||
promptName,
|
||||
workspaceRoot,
|
||||
});
|
||||
const codexArtifacts: NapcatUpstreamAuditResult['artifacts'] = [
|
||||
{ path: contextPacketPath, type: 'context' },
|
||||
{ path: jsonlPath, type: 'jsonl' },
|
||||
{ path: lastMessagePath, type: 'markdown' },
|
||||
];
|
||||
const codexCommands: NapcatAuditCommand[] = [
|
||||
{
|
||||
command: codexCommand,
|
||||
description:
|
||||
'Run Codex CLI upstream audit with bounded sandboxing and capture JSONL/last-message artifacts.',
|
||||
readOnly: false,
|
||||
},
|
||||
];
|
||||
|
||||
mkdirSync(artifactRoot, { recursive: true });
|
||||
writeFileSync(
|
||||
contextPacketPath,
|
||||
buildNapcatCodexContextPacket(input, classification),
|
||||
'utf8',
|
||||
);
|
||||
writeFileSync(lastMessagePath, '', 'utf8');
|
||||
|
||||
let codexResult: NapcatCodexExecResult;
|
||||
try {
|
||||
const executor: NapcatCodexExecutor =
|
||||
input.codexExecutor ?? executeNapcatCodexCommand;
|
||||
codexResult = executor(codexCommand);
|
||||
} catch (error) {
|
||||
codexResult = {
|
||||
ok: false,
|
||||
stderr: String(error),
|
||||
stdout: '',
|
||||
};
|
||||
}
|
||||
|
||||
writeFileSync(jsonlPath, codexResult.stdout, 'utf8');
|
||||
const lastMessage = existsSync(lastMessagePath)
|
||||
? readFileSync(lastMessagePath, 'utf8')
|
||||
: '';
|
||||
const parsed = codexResult.ok
|
||||
? parseNapcatCodexAuditOutput(codexResult.stdout, lastMessage)
|
||||
: null;
|
||||
|
||||
if (!parsed) {
|
||||
return buildCodexSchemaInvalidResult(codexArtifacts, codexCommands);
|
||||
}
|
||||
|
||||
return {
|
||||
...parsed,
|
||||
artifacts: codexArtifacts,
|
||||
commands: codexCommands,
|
||||
execute: true,
|
||||
hotZonePatterns: [...napcatUpstreamHotZonePatterns],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...classification,
|
||||
artifacts: [
|
||||
{
|
||||
path: '.kt-workspace/test-artifacts/napcat-upstream-audit/context.md',
|
||||
path: joinNapcatArtifactPath(artifactRoot, 'context.md'),
|
||||
type: 'markdown',
|
||||
},
|
||||
{
|
||||
path: '.kt-workspace/test-artifacts/napcat-upstream-audit/classification.json',
|
||||
path: joinNapcatArtifactPath(artifactRoot, 'classification.json'),
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
path: '.kt-workspace/test-artifacts/napcat-upstream-audit/files.json',
|
||||
path: joinNapcatArtifactPath(artifactRoot, 'files.json'),
|
||||
type: 'json',
|
||||
},
|
||||
],
|
||||
|
||||
@ -56,16 +56,41 @@ export interface NapcatAuditCommand {
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface NapcatUpstreamAuditInput {
|
||||
export interface NapcatCodexExecCommandInput {
|
||||
artifactRoot: string;
|
||||
codexHome?: string;
|
||||
contextPacketPath: string;
|
||||
outputSchemaPath: string;
|
||||
promptName: 'upstream-audit';
|
||||
workspaceRoot: string;
|
||||
}
|
||||
|
||||
export interface NapcatCodexExecResult {
|
||||
ok: boolean;
|
||||
stderr: string;
|
||||
stdout: string;
|
||||
}
|
||||
|
||||
export type NapcatCodexExecutor = (
|
||||
command: string,
|
||||
) => NapcatCodexExecResult;
|
||||
|
||||
export interface NapcatUpstreamAuditInput
|
||||
extends NapcatAuditClassificationInput {
|
||||
artifactRoot?: string;
|
||||
codexExecutor?: NapcatCodexExecutor;
|
||||
codexHome?: string;
|
||||
createCandidateBranch?: boolean;
|
||||
execute?: boolean;
|
||||
forkBranch?: string;
|
||||
forkRepo?: string;
|
||||
lastAcceptedUpstreamBase?: string;
|
||||
outputSchemaPath?: string;
|
||||
promptName?: 'upstream-audit';
|
||||
upstreamReleaseTag?: string;
|
||||
upstreamRepo?: string;
|
||||
useCodex?: boolean;
|
||||
workspaceRoot?: string;
|
||||
}
|
||||
|
||||
export interface NapcatAutomationArtifact {
|
||||
@ -85,7 +110,7 @@ export interface NapcatUpstreamAuditResult
|
||||
extends NapcatAuditClassificationResult {
|
||||
artifacts: NapcatAutomationArtifact[];
|
||||
commands: NapcatAuditCommand[];
|
||||
execute: false;
|
||||
execute: boolean;
|
||||
hotZonePatterns: string[];
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user