From 30222fd85e4a263e10f49955a1b40052eb277224 Mon Sep 17 00:00:00 2001 From: sunlei Date: Thu, 25 Jun 2026 02:04:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=80=8F=E4=BC=A0NapCat=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8C=96=E5=91=BD=E4=BB=A4=E8=A6=86=E7=9B=96=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/selfTest.ts | 55 +++++++++++++++++++++++++++++++++++ src/tools/napcatAutomation.ts | 32 ++++++++++++++++---- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/selfTest.ts b/src/selfTest.ts index 57ae8ec..715d9e8 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -209,6 +209,61 @@ export async function runSelfTest(): Promise { ) { throw new Error("NAS Codex bootstrap default alignment self-check failed"); } + const napcatCandidateOverride = buildNapcatSyncCandidateReview({ + candidateBranch: "custom-candidate", + lastAcceptedUpstreamBase: "custom-base", + }); + const napcatCandidateOverrideCommandText = + napcatCandidateOverride.commands.map((item) => item.command).join("\n"); + if ( + !napcatCandidateOverrideCommandText.includes("custom-candidate") || + !napcatCandidateOverrideCommandText.includes("custom-base") || + napcatCandidateOverrideCommandText.includes( + napcatAutomationDefaults.candidateBranch, + ) || + napcatCandidateOverrideCommandText.includes( + napcatAutomationDefaults.lastAcceptedUpstreamBase, + ) + ) { + throw new Error("NapCat candidate command override self-check failed"); + } + const napcatRuntimeOverride = buildNapcatRuntimeReleaseReadiness({ + napcatImageTag: "custom-image", + }); + const napcatRuntimeOverrideCommandText = + napcatRuntimeOverride.commands.map((item) => item.command).join("\n"); + if ( + !napcatRuntimeOverrideCommandText.includes("custom-image") || + napcatRuntimeOverrideCommandText.includes( + napcatAutomationDefaults.runtimeImageTag, + ) + ) { + throw new Error("NapCat runtime command override self-check failed"); + } + const napcatAuditOverride = buildNapcatUpstreamAudit({ + dryMergeConflict: false, + forkBranch: "custom-fork", + forkPatchFiles: [], + lastAcceptedUpstreamBase: "custom-base", + upstreamChangedFiles: [], + upstreamReleaseTag: "custom-upstream", + }); + const napcatAuditOverrideCommandText = + napcatAuditOverride.commands.map((item) => item.command).join("\n"); + if ( + !napcatAuditOverrideCommandText.includes("custom-fork") || + !napcatAuditOverrideCommandText.includes("custom-base") || + !napcatAuditOverrideCommandText.includes("custom-upstream") || + napcatAuditOverrideCommandText.includes(napcatAutomationDefaults.forkBranch) || + napcatAuditOverrideCommandText.includes( + napcatAutomationDefaults.lastAcceptedUpstreamBase, + ) || + napcatAuditOverrideCommandText.includes( + napcatAutomationDefaults.upstreamReleaseRef, + ) + ) { + throw new Error("NapCat upstream audit command override self-check failed"); + } const packageJson = JSON.parse( readFileSync(resolveInsideRoot("mcp/ktWorkflow/package.json"), "utf8"), ) as { scripts?: Record }; diff --git a/src/tools/napcatAutomation.ts b/src/tools/napcatAutomation.ts index bcea7c5..c2eb6bb 100644 --- a/src/tools/napcatAutomation.ts +++ b/src/tools/napcatAutomation.ts @@ -221,13 +221,13 @@ export function buildNapcatSyncCandidateReview( }, { command: - `git log --oneline --decorate -n 20 "\${CANDIDATE_BRANCH:-${napcatAutomationDefaults.candidateBranch}}"`, + `git log --oneline --decorate -n 20 ${shellEnvFallback('CANDIDATE_BRANCH', context.candidateBranch)}`, description: 'Inspect recent candidate commits without changing refs.', readOnly: true, }, { command: - `git diff --stat "\${LAST_ACCEPTED_UPSTREAM_BASE:-${napcatAutomationDefaults.lastAcceptedUpstreamBase}}..\${CANDIDATE_BRANCH:-${napcatAutomationDefaults.candidateBranch}}"`, + `git diff --stat ${shellEnvFallback('LAST_ACCEPTED_UPSTREAM_BASE', context.lastAcceptedUpstreamBase)}..${shellEnvFallback('CANDIDATE_BRANCH', context.candidateBranch)}`, description: 'Summarize candidate file churn for human review.', readOnly: true, }, @@ -271,7 +271,7 @@ export function buildNapcatRuntimeReleaseReadiness( }, { command: - 'docker image inspect "${NAPCAT_IMAGE_TAG:-kt-napcat-desktop-cn:desktop-cn}"', + `docker image inspect ${shellEnvFallback('NAPCAT_IMAGE_TAG', context.napcatImageTag)}`, description: 'Read the NAS-local NapCat image metadata without pulling or building.', readOnly: true, }, @@ -395,6 +395,21 @@ function shellQuotePath(value: string): string { return `'${value.replace(/'/g, `'\\''`)}'`; } +/** + * Builds a double-quoted POSIX shell parameter expansion with a resolved fallback value. + * @param variableName - Environment variable name that may override the generated command at execution time. + * @param fallbackValue - Resolved builder context value used when the environment variable is unset. + * @returns A shell token such as `"${NAME:-fallback}"` with double-quote metacharacters escaped. + */ +function shellEnvFallback(variableName: string, fallbackValue: string): string { + const escapedFallback = fallbackValue + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\$/g, '\\$') + .replace(/`/g, '\\`'); + return `"\${${variableName}:-${escapedFallback}}"`; +} + /** * 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`. @@ -1022,6 +1037,11 @@ export function buildNapcatUpstreamAudit( ): NapcatUpstreamAuditResult { const classification = classifyNapcatUpstreamAudit(input); const artifactRoot = resolveNapcatArtifactRoot(input.artifactRoot); + const forkBranch = input.forkBranch ?? napcatAutomationDefaults.forkBranch; + const lastAcceptedUpstreamBase = + input.lastAcceptedUpstreamBase ?? napcatAutomationDefaults.lastAcceptedUpstreamBase; + const upstreamReleaseTag = + input.upstreamReleaseTag ?? napcatAutomationDefaults.upstreamReleaseRef; const commands: NapcatAuditCommand[] = [ { command: 'git status --short --branch', @@ -1042,19 +1062,19 @@ export function buildNapcatUpstreamAudit( }, { command: - `git diff --name-only "\${LAST_ACCEPTED_UPSTREAM_BASE:-${napcatAutomationDefaults.lastAcceptedUpstreamBase}}..\${UPSTREAM_RELEASE_REF:-${napcatAutomationDefaults.upstreamReleaseRef}}"`, + `git diff --name-only ${shellEnvFallback('LAST_ACCEPTED_UPSTREAM_BASE', lastAcceptedUpstreamBase)}..${shellEnvFallback('UPSTREAM_RELEASE_REF', upstreamReleaseTag)}`, description: 'List upstream release files changed since the last accepted base.', readOnly: true, }, { command: - `git diff --name-only "\${LAST_ACCEPTED_UPSTREAM_BASE:-${napcatAutomationDefaults.lastAcceptedUpstreamBase}}..\${FORK_BRANCH:-${napcatAutomationDefaults.forkBranch}}"`, + `git diff --name-only ${shellEnvFallback('LAST_ACCEPTED_UPSTREAM_BASE', lastAcceptedUpstreamBase)}..${shellEnvFallback('FORK_BRANCH', forkBranch)}`, description: 'List KT fork patch files for deterministic overlap checks.', readOnly: true, }, { command: - `git merge-tree "\${FORK_BRANCH:-${napcatAutomationDefaults.forkBranch}}" "\${UPSTREAM_RELEASE_REF:-${napcatAutomationDefaults.upstreamReleaseRef}}"`, + `git merge-tree ${shellEnvFallback('FORK_BRANCH', forkBranch)} ${shellEnvFallback('UPSTREAM_RELEASE_REF', upstreamReleaseTag)}`, description: 'Probe the dry merge shape without updating the worktree or refs.', readOnly: true, },