feat(commander): add GPT prompt boundary lint

This commit is contained in:
Codex
2026-05-23 13:24:15 +00:00
parent e2646763c0
commit 77a8c6b878
10 changed files with 434 additions and 5 deletions
+4
View File
@@ -257,6 +257,10 @@ async function main(): Promise<void> {
if (top === "commander") {
const result = runCommanderCommand(args.slice(1));
if (sub === "prompt-lint") {
emitJson(commandName, result, true);
return;
}
const ok = (result as { ok?: unknown }).ok !== false;
emitJson(commandName, result, ok);
if (!ok) process.exitCode = 1;
@@ -0,0 +1,149 @@
import { spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { lintCommanderPrompt } from "./src/commander-prompt-lint";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function asRecord(value: unknown, label: string): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
return value as JsonRecord;
}
function asStringArray(value: unknown, label: string): string[] {
assertCondition(Array.isArray(value) && value.every((item) => typeof item === "string"), `${label} must be string array`, value);
return value as string[];
}
function runCli(args: string[], stdin?: string): { status: number | null; stdout: string; stderr: string; envelope: JsonRecord } {
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
cwd: process.cwd(),
input: stdin,
encoding: "utf8",
maxBuffer: 4 * 1024 * 1024,
});
assertCondition(String(result.stdout || "").trim().length > 0, `command produced no stdout: ${args.join(" ")}`, {
status: result.status,
stderr: String(result.stderr || ""),
});
return {
status: result.status,
stdout: String(result.stdout || ""),
stderr: String(result.stderr || ""),
envelope: asRecord(JSON.parse(String(result.stdout || "")) as unknown, "cli envelope"),
};
}
function dataOf(envelope: JsonRecord): JsonRecord {
return asRecord(envelope.data, "data");
}
const completePrompt = `
UniDesk#20 #118 / commander prompt boundary lint
You are a D601 Code Queue GPT-5.5 runner. Work in UniDesk only.
PR and Git authorization:
- You may create and update a head branch and PR, rebase/update from origin/master, resolve conflicts, and self-merge/close the ordinary PR when checks pass and the task boundary is satisfied.
Artifact authorization:
- You may use repo-owned CI/CD, publish, or equivalent controlled build paths to build/publish DEV images or artifacts, and must report commit, image tag, digest, artifact report, and validation evidence.
Rollout boundary:
- DEV deploy apply, rollout, and live health verification are owned by the host commander unless this prompt explicitly contains ROLLOUT_OK.
- Without explicit ROLLOUT_OK, do not acquire the DEV CD lock, run deploy apply, run rollout restart, or compete with host commander live verification.
Forbidden:
- No PROD mutation, no reading or printing secrets/tokens/credentials, no manual database/DB writes, and no destructive rollback.
`;
const incompletePromptWithSecret = `
UniDesk#20 #118
Task: implement the lint. token=ghp_prompt_lint_contract_secret
Please make code changes and tests.
`;
const lint = lintCommanderPrompt(completePrompt);
assertCondition(lint.ok === true, "complete GPT-5.5 PR prompt should pass lint", lint);
assertCondition(lint.missingClauses.length === 0, "complete prompt should have no missing clauses", lint);
assertCondition(lint.suggestedPatchSnippet === "", "passing prompt should not include patch snippet", lint);
assertCondition(lint.policy.advisoryOnly === true, "lint must be advisory only", lint);
assertCondition(lint.policy.changesCodexSubmitDefault === false, "lint must not change codex submit default", lint);
assertCondition(JSON.stringify(lint).includes("promptShape"), "lint should expose prompt shape metadata", lint);
assertCondition(!JSON.stringify(lint).includes("self-merge/close the ordinary PR"), "direct lint result must not echo full prompt", lint);
const failingLint = lintCommanderPrompt(incompletePromptWithSecret);
assertCondition(failingLint.ok === false, "incomplete prompt should fail lint", failingLint);
assertCondition(failingLint.riskLevel === "high", "incomplete prompt should be high risk", failingLint);
for (const expected of [
"pr-self-merge-rebase-authorization",
"artifact-build-publish-authorization",
"host-owned-dev-rollout",
"runner-rollout-forbidden-without-rollout-ok",
"prod-secret-db-rollback-boundary",
]) {
assertCondition(failingLint.missingClauses.includes(expected), `missing expected clause id ${expected}`, failingLint);
}
assertCondition(failingLint.suggestedPatchSnippet.includes("ROLLOUT_OK"), "snippet should mention ROLLOUT_OK", failingLint);
assertCondition(!JSON.stringify(failingLint).includes("ghp_prompt_lint_contract_secret"), "lint output must not echo secret-like prompt text", failingLint);
const tmp = mkdtempSync(join(tmpdir(), "host-codex-commander-prompt-lint-"));
try {
const promptFile = join(tmp, "prompt.md");
writeFileSync(promptFile, incompletePromptWithSecret, "utf8");
const fileRun = runCli(["commander", "prompt-lint", "--kind", "gpt55-pr", "--prompt-file", promptFile]);
assertCondition(fileRun.status === 0, "commander prompt-lint is advisory and should exit 0 even when lint ok=false", fileRun);
assertCondition(fileRun.envelope.ok === true, "CLI envelope should remain ok for advisory lint result", fileRun.envelope);
const fileData = dataOf(fileRun.envelope);
assertCondition(fileData.ok === false, "lint data ok should reflect missing clauses", fileData);
assertCondition(asStringArray(fileData.missingClauses, "missingClauses").includes("host-owned-dev-rollout"), "file lint should report rollout clause", fileData);
assertCondition(String(fileData.suggestedPatchSnippet || "").includes("DEV deploy apply"), "file lint should include bounded patch snippet", fileData);
assertCondition(!fileRun.stdout.includes("ghp_prompt_lint_contract_secret"), "file lint stdout must not echo prompt secret", fileRun.stdout);
const stdinRun = runCli(["commander", "prompt-lint", "--kind", "gpt55-pr", "--stdin"], completePrompt);
assertCondition(stdinRun.status === 0 && stdinRun.envelope.ok === true, "stdin lint should exit successfully", stdinRun.envelope);
const stdinData = dataOf(stdinRun.envelope);
assertCondition(stdinData.ok === true, "stdin lint data should pass for complete prompt", stdinData);
assertCondition(asStringArray(stdinData.missingClauses, "missingClauses").length === 0, "stdin lint should have no missing clauses", stdinData);
assertCondition(!stdinRun.stdout.includes("Artifact authorization:"), "stdin lint must not echo full prompt", stdinRun.stdout);
} finally {
rmSync(tmp, { recursive: true, force: true });
}
const submitDryRun = runCli(["codex", "submit", "--prompt-stdin", "--queue", "prompt-lint-contract", "--dry-run"], incompletePromptWithSecret);
assertCondition(submitDryRun.status === 0 && submitDryRun.envelope.ok === true, "codex submit --dry-run should not be gated by commander prompt-lint", submitDryRun.envelope);
const submitData = dataOf(submitDryRun.envelope);
assertCondition(asRecord(submitData.request, "submit request").prompt !== undefined, "submit dry-run should keep its existing prompt review behavior", submitData);
const helpRun = runCli(["commander", "--help"]);
assertCondition(helpRun.status === 0 && helpRun.envelope.ok === true, "commander help should succeed", helpRun.envelope);
const helpData = dataOf(helpRun.envelope);
assertCondition(asStringArray(helpData.usage, "help usage").some((line) => line.includes("commander prompt-lint")), "commander help should list prompt-lint", helpData);
assertCondition(asRecord(helpData.promptLint, "promptLint").gate === "advisory-only; not a business PR gate and not a Code Queue submit admission change", "help should document advisory-only gate", helpData);
const doc = readFileSync("docs/reference/host-codex-commander.md", "utf8");
for (const snippet of [
"commander prompt-lint --kind gpt55-pr",
"missingClauses",
"suggestedPatchSnippet",
"不是业务 PR 门禁",
]) {
assertCondition(doc.includes(snippet), `reference doc missing snippet: ${snippet}`);
}
process.stdout.write(`${JSON.stringify({
ok: true,
checks: [
"complete GPT-5.5 PR prompt passes commander prompt-lint",
"missing PR/artifact/DEV rollout/ROLLOUT_OK/PROD-secret-DB-rollback clauses are reported as high risk",
"prompt-lint supports --prompt-file and --stdin",
"prompt-lint output does not echo full prompt or secret-like prompt text",
"commander prompt-lint remains advisory and does not gate codex submit --dry-run",
"commander help and host commander reference document the advisory lint entry",
],
}, null, 2)}\n`);
+6
View File
@@ -28,11 +28,13 @@ const syntaxFiles = [
"scripts/src/e2e.ts",
"scripts/src/help.ts",
"scripts/src/commander.ts",
"scripts/src/commander-prompt-lint.ts",
"scripts/src/recovery-guardrails.ts",
"scripts/src/server-cleanup.ts",
"scripts/src/remote.ts",
"scripts/host-codex-commander-contract-test.ts",
"scripts/host-codex-commander-no-daemon-smoke-contract-test.ts",
"scripts/host-codex-commander-prompt-lint-contract-test.ts",
"scripts/host-codex-commander-skeleton-contract-test.ts",
"scripts/auth-broker-contract-test.ts",
"scripts/code-queue-cli-disclosure-contract-test.ts",
@@ -335,6 +337,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("src/components/microservices/host-codex-commander/src/redaction.ts"),
fileItem("src/components/microservices/host-codex-commander/src/state.ts"),
fileItem("src/components/microservices/code-queue-mgr/src/prompt-observation.ts"),
fileItem("scripts/src/commander-prompt-lint.ts"),
fileItem("scripts/src/deploy.ts"),
fileItem("scripts/code-queue-issue3-regression-test.ts"),
fileItem("scripts/code-queue-liveness-diagnostics-test.ts"),
@@ -355,6 +358,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("scripts/code-queue-commander-view-contract-test.ts"),
fileItem("scripts/host-codex-commander-skeleton-contract-test.ts"),
fileItem("scripts/host-codex-commander-no-daemon-smoke-contract-test.ts"),
fileItem("scripts/host-codex-commander-prompt-lint-contract-test.ts"),
fileItem("scripts/provider-runner-triage-contract-test.ts"),
fileItem("scripts/ssh-argv-guidance-contract-test.ts"),
fileItem("scripts/src/provider-triage.ts"),
@@ -408,6 +412,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("code-queue:commander-view-contract", ["bun", "scripts/code-queue-commander-view-contract-test.ts"], 30_000));
items.push(commandItem("host-codex-commander:skeleton-contract", ["bun", "scripts/host-codex-commander-skeleton-contract-test.ts"], 30_000));
items.push(commandItem("host-codex-commander:no-daemon-smoke-contract", ["bun", "scripts/host-codex-commander-no-daemon-smoke-contract-test.ts"], 30_000));
items.push(commandItem("host-codex-commander:prompt-lint-contract", ["bun", "scripts/host-codex-commander-prompt-lint-contract-test.ts"], 30_000));
items.push(commandItem("provider:runner-triage-contract", ["bun", "scripts/provider-runner-triage-contract-test.ts"], 30_000));
items.push(commandItem("ssh:argv-guidance-contract", ["bun", "scripts/ssh-argv-guidance-contract-test.ts"], 30_000));
items.push(commandItem("deploy:artifact-matrix-contract", ["bun", "scripts/deploy-artifact-matrix-contract-test.ts"], 90_000));
@@ -449,6 +454,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("code-queue:commander-view-contract", "Code Queue commander view contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("host-codex-commander:skeleton-contract", "host Codex commander skeleton contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("host-codex-commander:no-daemon-smoke-contract", "host Codex commander no-daemon smoke contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("host-codex-commander:prompt-lint-contract", "host Codex commander prompt boundary lint contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("provider:runner-triage-contract", "Provider runner triage contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("ssh:argv-guidance-contract", "SSH argv guidance and failure hint contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("deploy:artifact-matrix-contract", "deploy artifact matrix contract is opt-in with script checks", "--scripts-typecheck or --full"));
+245
View File
@@ -0,0 +1,245 @@
import { readFileSync } from "node:fs";
export type CommanderPromptLintKind = "gpt55-pr";
export type CommanderPromptLintRiskLevel = "low" | "medium" | "high";
interface ClauseCheck {
id: string;
label: string;
required: boolean;
matched: boolean;
severity: "medium" | "high";
suggestion: string;
}
export interface CommanderPromptLintResult {
ok: boolean;
kind: CommanderPromptLintKind;
missingClauses: string[];
riskLevel: CommanderPromptLintRiskLevel;
suggestedPatchSnippet: string;
promptShape: {
chars: number;
lines: number;
textEchoed: false;
};
policy: {
advisoryOnly: true;
mutatesScheduler: false;
changesCodexSubmitDefault: false;
printsPromptText: false;
supportedInputs: Array<"--stdin" | "--prompt-file">;
reference: string;
};
}
interface ParsedPromptLintArgs {
kind: CommanderPromptLintKind;
prompt: string;
}
const gpt55PrSnippet = [
"GPT-5.5 runner boundary:",
"- You may create/update the branch and PR, resolve conflicts, rebase/update from the target branch, and self-merge/close an ordinary PR when checks pass and the task boundary is satisfied.",
"- You may use repo-owned CI/CD, publish, or equivalent controlled build paths to build/publish DEV images or artifacts, and must report commit, image tag, digest, artifact report, and validation evidence.",
"- DEV deploy apply, rollout, and live health verification are owned by the host commander unless this prompt explicitly contains ROLLOUT_OK.",
"- Without explicit ROLLOUT_OK, do not acquire the DEV CD lock, run deploy apply, perform rollout/restart, or compete with host commander live verification.",
"- Forbidden: PROD mutation, reading or printing secrets, manual database writes, destructive rollback, Code Queue backend/scheduler restart, or interrupt/cancel running tasks unless explicitly authorized.",
].join("\n");
function hasFlag(args: string[], flag: string): boolean {
return args.includes(flag);
}
function optionValue(args: string[], names: string[]): string | undefined {
for (const name of names) {
const index = args.indexOf(name);
if (index === -1) continue;
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`);
return value;
}
return undefined;
}
function assertKnownOptions(args: string[]): void {
const flags = new Set(["--stdin"]);
const valueOptions = new Set(["--kind", "--prompt-file", "--file"]);
for (let index = 0; index < args.length; index += 1) {
const arg = args[index] ?? "";
if (!arg.startsWith("--")) throw new Error(`commander prompt-lint does not accept positional prompt text; use --stdin or --prompt-file`);
if (flags.has(arg)) continue;
if (valueOptions.has(arg)) {
index += 1;
if (index >= args.length) throw new Error(`${arg} requires a value`);
continue;
}
throw new Error(`unknown commander prompt-lint option: ${arg}`);
}
}
function parseKind(raw: string | undefined): CommanderPromptLintKind {
const kind = raw ?? "gpt55-pr";
if (kind !== "gpt55-pr") throw new Error(`unsupported commander prompt-lint kind: ${kind}`);
return kind;
}
function readPromptFromArgs(args: string[]): string {
const promptFile = optionValue(args, ["--prompt-file", "--file"]);
const promptStdin = hasFlag(args, "--stdin");
const sources = [promptFile !== undefined, promptStdin].filter(Boolean).length;
if (sources !== 1) throw new Error("commander prompt-lint requires exactly one prompt source: --prompt-file or --stdin");
const prompt = promptFile !== undefined
? (promptFile === "-" ? readFileSync(0, "utf8") : readFileSync(promptFile, "utf8"))
: readFileSync(0, "utf8");
if (prompt.trim().length === 0) throw new Error("commander prompt-lint prompt must not be empty");
return prompt;
}
export function parseCommanderPromptLintArgs(args: string[]): ParsedPromptLintArgs {
assertKnownOptions(args);
return {
kind: parseKind(optionValue(args, ["--kind"])),
prompt: readPromptFromArgs(args),
};
}
function any(patterns: RegExp[], prompt: string): boolean {
return patterns.some((pattern) => pattern.test(prompt));
}
function rolloutOkPresent(prompt: string): boolean {
return /\bROLLOUT_OK\b/u.test(prompt);
}
function gpt55PrClauseChecks(prompt: string): ClauseCheck[] {
const prAuthorization = any([
/\bPR\b[^\n]{0,120}(?:create|update|push|branch|merge|close|rebase|conflict)/iu,
/(?:||push||||rebase|)[^\n]{0,80}\bPR\b/iu,
], prompt) && any([
/self[- ]?(?:merge|close)|(?:||)||/iu,
/merge\/close|\//iu,
], prompt) && any([
/\brebase\b|\bupdate\b||(?:|base|master|)/iu,
], prompt);
const artifactAuthorization = any([
/\b(?:build|publish)\b[^\n]{0,80}\b(?:artifact|image|DEV image||)\b/iu,
/\b(?:artifact|image||)\b[^\n]{0,80}\b(?:build|publish|tag|digest||)\b/iu,
/(?:|)[^\n]{0,80}(?:||artifact|image)/iu,
], prompt) && any([
/\b(?:tag|digest|artifact report|image tag)\b/iu,
/(?:\s*tag|digest||artifact report)/iu,
], prompt);
const hostOwnsDevRollout = any([
/DEV[^\n]{0,120}(?:deploy apply|rollout|live health|live verification)[^\n]{0,120}(?:host commander|commander|host|||)/iu,
/(?:host commander|commander|host|)[^\n]{0,120}DEV[^\n]{0,120}(?:deploy apply|rollout|live health|live verification||线|)/iu,
/DEV[^\n]{0,80}(?:|线|rollout|)[^\n]{0,80}(?:host|commander||)/iu,
], prompt);
const forbidsRunnerRolloutUnlessOk = any([
/unless[^\n]{0,80}\bROLLOUT_OK\b/iu,
/[^\n]{0,60}\bROLLOUT_OK\b[^\n]{0,80}(?:|||do not|don't|must not)/iu,
/\bROLLOUT_OK\b[^\n]{0,80}(?:|unless|only if|explicit)/iu,
], prompt) && any([
/(?:do not|don't|must not|||)[^\n]{0,100}(?:DEV CD lock|deploy apply|rollout|live health|rollout restart||)/iu,
/(?:DEV CD lock|deploy apply|rollout|live health|rollout restart||)[^\n]{0,100}(?:do not|don't|must not|||)/iu,
], prompt);
const prodForbidden = any([
/(?:|||must not|do not|don't|\bno\b|forbid|forbidden)[^\n]{0,80}(?:PROD|prod|production|)/iu,
/(?:PROD|prod|production|)[^\n]{0,80}(?:|||must not|do not|don't|\bno\b|forbid|forbidden|not allowed)/iu,
], prompt);
const secretForbidden = any([
/(?:|||must not|do not|don't|\bno\b|forbid|forbidden)[^\n]{0,80}(?:secret|token|credential||)/iu,
/(?:secret|token|credential||)[^\n]{0,80}(?:|||must not|do not|don't|\bno\b|forbid|forbidden|not allowed||)/iu,
], prompt);
const dbForbidden = any([
/(?:|||must not|do not|don't|\bno\b|forbid|forbidden)[^\n]{0,80}(?:database|DB|)/iu,
/(?:database|DB|)[^\n]{0,80}(?:manual||patch|write|||||must not|do not|don't|\bno\b)/iu,
], prompt);
const rollbackForbidden = any([
/(?:|||must not|do not|don't|\bno\b|forbid|forbidden)[^\n]{0,80}(?:destructive rollback||)/iu,
/(?:destructive rollback|)[^\n]{0,80}(?:|||must not|do not|don't|\bno\b|forbid|forbidden|not allowed)/iu,
], prompt);
return [
{
id: "pr-self-merge-rebase-authorization",
label: "PR/自合并/rebase/update 授权文本",
required: true,
matched: prAuthorization,
severity: "high",
suggestion: "明确授权 runner 创建/更新 PR、rebase/update/解决冲突,并在普通 PR 满足任务边界和检查通过时自合并/关闭。",
},
{
id: "artifact-build-publish-authorization",
label: "build/publish artifact 授权文本",
required: true,
matched: artifactAuthorization,
severity: "high",
suggestion: "明确授权使用 repo-owned CI/CD 或受控构建路径发布 DEV image/artifact,并要求回报 tag、digest 和 artifact report。",
},
{
id: "host-owned-dev-rollout",
label: "DEV deploy apply/rollout/live verification 由 host 统一执行文本",
required: true,
matched: hostOwnsDevRollout,
severity: "high",
suggestion: "写明 DEV deploy apply、rollout 和 live health verification 默认由 host commander 统一执行。",
},
{
id: "runner-rollout-forbidden-without-rollout-ok",
label: "未显式 ROLLOUT_OK 时禁止 runner rollout",
required: true,
matched: forbidsRunnerRolloutUnlessOk,
severity: "high",
suggestion: "写明未显式包含 ROLLOUT_OK 时,runner 不得抢 DEV CD lock、deploy apply、rollout 或 live verification。",
},
{
id: "prod-secret-db-rollback-boundary",
label: "PROD/secret/DB/破坏性回滚边界",
required: true,
matched: prodForbidden && secretForbidden && dbForbidden && rollbackForbidden,
severity: "high",
suggestion: "同时禁止 PROD mutation、密钥读取/打印、数据库手工写入和破坏性回滚。",
},
];
}
export function lintCommanderPrompt(prompt: string, kind: CommanderPromptLintKind = "gpt55-pr"): CommanderPromptLintResult {
const clauses = gpt55PrClauseChecks(prompt);
const missingClauses = clauses.filter((clause) => clause.required && !clause.matched).map((clause) => clause.id);
const highMissing = clauses.some((clause) => clause.severity === "high" && !clause.matched);
const rolloutOk = rolloutOkPresent(prompt);
const riskLevel: CommanderPromptLintRiskLevel = missingClauses.length === 0
? rolloutOk ? "medium" : "low"
: highMissing ? "high" : "medium";
return {
ok: missingClauses.length === 0,
kind,
missingClauses,
riskLevel,
suggestedPatchSnippet: missingClauses.length === 0 ? "" : gpt55PrSnippet,
promptShape: {
chars: prompt.length,
lines: prompt.split(/\r\n|\r|\n/u).length,
textEchoed: false,
},
policy: {
advisoryOnly: true,
mutatesScheduler: false,
changesCodexSubmitDefault: false,
printsPromptText: false,
supportedInputs: ["--stdin", "--prompt-file"],
reference: "docs/reference/host-codex-commander.md",
},
};
}
export function runCommanderPromptLintCommand(args: string[]): CommanderPromptLintResult {
const options = parseCommanderPromptLintArgs(args);
return lintCommanderPrompt(options.prompt, options.kind);
}
+3
View File
@@ -1,6 +1,7 @@
import { buildCommanderApprovalNotificationDraft, commanderApprovalNotificationPathUnavailable } from "../../src/components/microservices/host-codex-commander/src/approval-notification";
import { commanderContract as hostCommanderContract, commanderHighRiskActions as highRiskActions } from "../../src/components/microservices/host-codex-commander/src/contract";
import { redactText } from "../../src/components/microservices/host-codex-commander/src/redaction";
import { runCommanderPromptLintCommand } from "./commander-prompt-lint";
const requiredDryRunMessage = "This host Codex commander skeleton only supports dry-run planning; live daemon/control operations are not implemented.";
@@ -36,6 +37,7 @@ function commanderHelp(): Record<string, unknown> {
"bun scripts/cli.ts commander plan --dry-run [--session-id id]",
"bun scripts/cli.ts commander smoke --dry-run [--session-id id]",
"bun scripts/cli.ts commander approval request --action <action> --dry-run [--reason text] [--task-id id]",
"bun scripts/cli.ts commander prompt-lint --kind gpt55-pr (--prompt-file <file>|--stdin)",
],
highRiskActions,
reference: "docs/reference/host-codex-commander.md",
@@ -494,6 +496,7 @@ export function runCommanderCommand(args: string[]): Record<string, unknown> {
if (sub === "plan") return commanderPlan(args.slice(1));
if (sub === "smoke") return commanderSmoke(args.slice(1));
if (sub === "approval" && second === "request") return commanderApprovalRequest(args.slice(2));
if (sub === "prompt-lint") return runCommanderPromptLintCommand(args.slice(1)) as unknown as Record<string, unknown>;
return {
ok: false,
error: "unsupported-command",
+12 -3
View File
@@ -47,7 +47,7 @@ export function rootHelp(): unknown {
{ command: "artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service", description: "Manage the D601 host-managed CNCF Distribution registry and run pull-only artifact CD for supported services, including D601 direct, k3s-managed, and code-queue dev-only consumers." },
{ command: "auth-broker contract|health --dry-run|credential-request --dry-run|pr-preflight --dry-run", description: "Inspect the P0 Rust auth broker and CLI adapter contract without reading token values, writing GitHub, or starting services." },
{ command: "gh preflight|auth|issue|pr", description: "Run safe GitHub issue and PR CRUD/lifecycle operations through REST with body-file update replace/append, comment delete, token diagnostics, PR closeout preflight, hard delete unsupported, and merge blocked." },
{ command: "commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run", description: "Host Codex commander skeleton contract, no-daemon smoke plan, and dry-run approval preview; generates <=200 char Chinese ClaudeQQ drafts plus backend-core proxy command without live bridges or message sends." },
{ command: "commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run|prompt-lint --kind gpt55-pr", description: "Host Codex commander skeleton contract, no-daemon smoke plan, dry-run approval preview, and advisory GPT-5.5 PR prompt boundary lint without live bridges, message sends, or submit gating." },
{ command: "code-agent-sandbox", description: "Independent Code Agent Sandbox service skeleton for adapter, mode, and credential-boundary diagnostics." },
{ command: "schedule list|get|runs|run|retry-run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run <id> supports --wait-ms N and retry-run reuses the failed run's schedule." },
{ command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." },
@@ -213,22 +213,31 @@ function providerHelp(): unknown {
function commanderHelp(): unknown {
return {
command: "commander contract|plan|smoke|approval",
command: "commander contract|plan|smoke|approval|prompt-lint",
output: "json",
usage: [
"bun scripts/cli.ts commander contract",
"bun scripts/cli.ts commander plan --dry-run [--session-id id]",
"bun scripts/cli.ts commander smoke --dry-run [--session-id id]",
"bun scripts/cli.ts commander approval request --action <action> --dry-run [--reason text] [--task-id id]",
"bun scripts/cli.ts commander prompt-lint --kind gpt55-pr (--prompt-file <file>|--stdin)",
],
description: "Inspect the local host Codex commander skeleton contract, dry-run planner, no-daemon smoke validation plan, state helpers, trace summary aggregator, and approval draft preview with a backend-core ClaudeQQ proxy command.",
description: "Inspect the local host Codex commander skeleton contract, dry-run planner, no-daemon smoke validation plan, state helpers, trace summary aggregator, approval draft preview, and advisory GPT-5.5 PR prompt boundary lint.",
boundary: [
"the current skeleton is local-only and never attaches to live bridges",
"dry-run commands never open SSH, PTY, or stdio bridges",
"high-risk actions only produce a <=200 char Chinese ClaudeQQ approval draft and notification-path-unavailable blocker",
"authorized future sends must use backend-core /api/microservices/claudeqq/proxy, not local skill or powershell paths",
"prompt-lint is commander advisory output and does not change codex submit default behavior",
"token and secret values must never be printed",
],
promptLint: {
command: "bun scripts/cli.ts commander prompt-lint --kind gpt55-pr --prompt-file <path>",
stdin: "cat prompt.md | bun scripts/cli.ts commander prompt-lint --kind gpt55-pr --stdin",
outputFields: ["ok", "missingClauses", "riskLevel", "suggestedPatchSnippet"],
fullPromptEchoed: false,
gate: "advisory-only; not a business PR gate and not a Code Queue submit admission change",
},
reference: "docs/reference/host-codex-commander.md",
};
}