fix: 修复NapCat自动流水线闭环
This commit is contained in:
parent
9eae9cc9a0
commit
65807e8ef3
@ -1,5 +1,5 @@
|
||||
pipeline {
|
||||
agent any
|
||||
agent { label 'kt-node-agent' }
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '30'))
|
||||
@ -25,6 +25,7 @@ pipeline {
|
||||
KT_WORKSPACE_ROOT = '/vol1/docker/kt-codex/workspace/KT'
|
||||
KT_WORKFLOW_DIR = '/vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow'
|
||||
KT_ARTIFACT_ROOT = '/vol1/docker/kt-codex/artifacts'
|
||||
KT_WORKFLOW_RUNNER_IMAGE = 'kt-jenkins-node-agent:current'
|
||||
}
|
||||
|
||||
stages {
|
||||
@ -32,12 +33,26 @@ pipeline {
|
||||
steps {
|
||||
sh '''
|
||||
set -e
|
||||
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-runtime-release-readiness -- \
|
||||
--artifact-root "$KT_ARTIFACT_ROOT/napcat-runtime-release" \
|
||||
--fork-branch "$NAPCAT_FORK_REF" \
|
||||
--upstream-release-tag "$UPSTREAM_RELEASE_TAG" \
|
||||
--napcat-image-tag "$QQBOT_NAPCAT_IMAGE_OVERRIDE" \
|
||||
--profile "$QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION_OVERRIDE"
|
||||
docker run --rm \
|
||||
-e KT_WORKFLOW_DIR="$KT_WORKFLOW_DIR" \
|
||||
-e KT_ARTIFACT_ROOT="$KT_ARTIFACT_ROOT" \
|
||||
-e NAPCAT_FORK_REF="$NAPCAT_FORK_REF" \
|
||||
-e UPSTREAM_RELEASE_TAG="$UPSTREAM_RELEASE_TAG" \
|
||||
-e QQBOT_NAPCAT_IMAGE_OVERRIDE="$QQBOT_NAPCAT_IMAGE_OVERRIDE" \
|
||||
-e QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION_OVERRIDE="$QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION_OVERRIDE" \
|
||||
-e COREPACK_HOME=/vol1/docker/kt-codex/runtime/corepack \
|
||||
-v /vol1/docker/kt-codex:/vol1/docker/kt-codex \
|
||||
--entrypoint bash "$KT_WORKFLOW_RUNNER_IMAGE" -lc '
|
||||
set -e
|
||||
source /vol1/docker/kt-codex/runtime/nvm/nvm.sh
|
||||
corepack prepare pnpm@10.28.2 --activate
|
||||
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-runtime-release-readiness -- \
|
||||
--artifact-root "$KT_ARTIFACT_ROOT/napcat-runtime-release" \
|
||||
--fork-branch "$NAPCAT_FORK_REF" \
|
||||
--upstream-release-tag "$UPSTREAM_RELEASE_TAG" \
|
||||
--napcat-image-tag "$QQBOT_NAPCAT_IMAGE_OVERRIDE" \
|
||||
--profile "$QQBOT_NAPCAT_DESKTOP_PROFILE_VERSION_OVERRIDE"
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
@ -93,11 +108,19 @@ pipeline {
|
||||
always {
|
||||
sh '''
|
||||
set +e
|
||||
rm -rf "$WORKSPACE/artifacts"
|
||||
mkdir -p "$WORKSPACE/artifacts/napcat-runtime-release"
|
||||
cp -a "$KT_ARTIFACT_ROOT/napcat-runtime-release/." "$WORKSPACE/artifacts/napcat-runtime-release/" 2>/dev/null || true
|
||||
docker run --rm \
|
||||
-e KT_ARTIFACT_ROOT="$KT_ARTIFACT_ROOT" \
|
||||
-e WORKSPACE="$WORKSPACE" \
|
||||
-v /vol1/docker/kt-codex:/vol1/docker/kt-codex \
|
||||
-v kt-node-agent-workdir:/home/jenkins/agent \
|
||||
--entrypoint bash "$KT_WORKFLOW_RUNNER_IMAGE" -lc '
|
||||
set +e
|
||||
rm -rf "$WORKSPACE/artifacts"
|
||||
mkdir -p "$WORKSPACE/artifacts/napcat-runtime-release"
|
||||
cp -a "$KT_ARTIFACT_ROOT/napcat-runtime-release/." "$WORKSPACE/artifacts/napcat-runtime-release/" 2>/dev/null || true
|
||||
'
|
||||
'''
|
||||
archiveArtifacts artifacts: 'artifacts/napcat-runtime-release/**/*', allowEmptyArchive: true, fingerprint: false
|
||||
archiveArtifacts artifacts: 'artifacts/napcat-runtime-release/**', allowEmptyArchive: true, fingerprint: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
pipeline {
|
||||
agent any
|
||||
agent { label 'kt-node-agent' }
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '30'))
|
||||
@ -17,6 +17,7 @@ pipeline {
|
||||
environment {
|
||||
KT_WORKFLOW_DIR = '/vol1/docker/kt-codex/workspace/KT/mcp/ktWorkflow'
|
||||
KT_ARTIFACT_ROOT = '/vol1/docker/kt-codex/artifacts'
|
||||
KT_WORKFLOW_RUNNER_IMAGE = 'kt-jenkins-node-agent:current'
|
||||
}
|
||||
|
||||
stages {
|
||||
@ -24,8 +25,19 @@ pipeline {
|
||||
steps {
|
||||
sh '''
|
||||
set -e
|
||||
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-upstream-audit -- \
|
||||
--artifact-root "$KT_ARTIFACT_ROOT/napcat-upstream-sync/dry-run"
|
||||
docker run --rm \
|
||||
-e KT_WORKFLOW_DIR="$KT_WORKFLOW_DIR" \
|
||||
-e KT_ARTIFACT_ROOT="$KT_ARTIFACT_ROOT" \
|
||||
-e CODEX_HOME=/vol1/docker/kt-codex/home/.codex \
|
||||
-e COREPACK_HOME=/vol1/docker/kt-codex/runtime/corepack \
|
||||
-v /vol1/docker/kt-codex:/vol1/docker/kt-codex \
|
||||
--entrypoint bash "$KT_WORKFLOW_RUNNER_IMAGE" -lc '
|
||||
set -e
|
||||
source /vol1/docker/kt-codex/runtime/nvm/nvm.sh
|
||||
corepack prepare pnpm@10.28.2 --activate
|
||||
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-upstream-audit -- \
|
||||
--artifact-root "$KT_ARTIFACT_ROOT/napcat-upstream-sync/dry-run"
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
@ -42,10 +54,21 @@ pipeline {
|
||||
}
|
||||
sh '''
|
||||
set -e
|
||||
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-upstream-audit -- \
|
||||
--execute \
|
||||
--use-codex \
|
||||
--artifact-root "$KT_ARTIFACT_ROOT/napcat-upstream-sync/codex"
|
||||
docker run --rm \
|
||||
-e KT_WORKFLOW_DIR="$KT_WORKFLOW_DIR" \
|
||||
-e KT_ARTIFACT_ROOT="$KT_ARTIFACT_ROOT" \
|
||||
-e CODEX_HOME=/vol1/docker/kt-codex/home/.codex \
|
||||
-e COREPACK_HOME=/vol1/docker/kt-codex/runtime/corepack \
|
||||
-v /vol1/docker/kt-codex:/vol1/docker/kt-codex \
|
||||
--entrypoint bash "$KT_WORKFLOW_RUNNER_IMAGE" -lc '
|
||||
set -e
|
||||
source /vol1/docker/kt-codex/runtime/nvm/nvm.sh
|
||||
corepack prepare pnpm@10.28.2 --activate
|
||||
pnpm --dir "$KT_WORKFLOW_DIR" run napcat-upstream-audit -- \
|
||||
--execute \
|
||||
--use-codex \
|
||||
--artifact-root "$KT_ARTIFACT_ROOT/napcat-upstream-sync/codex"
|
||||
'
|
||||
'''
|
||||
}
|
||||
}
|
||||
@ -55,11 +78,19 @@ pipeline {
|
||||
always {
|
||||
sh '''
|
||||
set +e
|
||||
rm -rf "$WORKSPACE/artifacts"
|
||||
mkdir -p "$WORKSPACE/artifacts/napcat-upstream-sync"
|
||||
cp -a "$KT_ARTIFACT_ROOT/napcat-upstream-sync/." "$WORKSPACE/artifacts/napcat-upstream-sync/" 2>/dev/null || true
|
||||
docker run --rm \
|
||||
-e KT_ARTIFACT_ROOT="$KT_ARTIFACT_ROOT" \
|
||||
-e WORKSPACE="$WORKSPACE" \
|
||||
-v /vol1/docker/kt-codex:/vol1/docker/kt-codex \
|
||||
-v kt-node-agent-workdir:/home/jenkins/agent \
|
||||
--entrypoint bash "$KT_WORKFLOW_RUNNER_IMAGE" -lc '
|
||||
set +e
|
||||
rm -rf "$WORKSPACE/artifacts"
|
||||
mkdir -p "$WORKSPACE/artifacts/napcat-upstream-sync"
|
||||
cp -a "$KT_ARTIFACT_ROOT/napcat-upstream-sync/." "$WORKSPACE/artifacts/napcat-upstream-sync/" 2>/dev/null || true
|
||||
'
|
||||
'''
|
||||
archiveArtifacts artifacts: 'artifacts/napcat-upstream-sync/**/*', allowEmptyArchive: true, fingerprint: false
|
||||
archiveArtifacts artifacts: 'artifacts/napcat-upstream-sync/**', allowEmptyArchive: true, fingerprint: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +54,7 @@ import {
|
||||
buildNasCodexBootstrapPlan,
|
||||
classifyNapcatUpstreamAudit,
|
||||
napcatAutomationDefaults,
|
||||
shouldFailNapcatAutomationCli,
|
||||
} from "./tools/napcatAutomation.js";
|
||||
import { registerTools } from "./registerTools.js";
|
||||
import {
|
||||
@ -119,6 +120,22 @@ export async function runSelfTest(): Promise<void> {
|
||||
if (!napcatCli.execute || !napcatCli.useCodex) {
|
||||
throw new Error("NapCat upstream audit CLI parser self-check failed");
|
||||
}
|
||||
if (
|
||||
!shouldFailNapcatAutomationCli({
|
||||
classification: "blocked",
|
||||
recommendedAction: "block",
|
||||
}) ||
|
||||
shouldFailNapcatAutomationCli({
|
||||
classification: "manual-review",
|
||||
recommendedAction: "request-human-review",
|
||||
}) ||
|
||||
shouldFailNapcatAutomationCli({
|
||||
classification: "safe-candidate",
|
||||
recommendedAction: "create-candidate-branch",
|
||||
})
|
||||
) {
|
||||
throw new Error("NapCat automation CLI blocked gate self-check failed");
|
||||
}
|
||||
if (
|
||||
napcatCli.artifactRoot !==
|
||||
"/vol1/docker/kt-codex/artifacts/napcat-upstream-sync"
|
||||
@ -154,9 +171,17 @@ export async function runSelfTest(): Promise<void> {
|
||||
throw new Error("NapCat auxiliary CLI dry-run default self-check failed");
|
||||
}
|
||||
const napcatCandidateDefaults = buildNapcatSyncCandidateReview({}).context;
|
||||
const napcatRuntimeDefaults = buildNapcatRuntimeReleaseReadiness({}).context;
|
||||
const napcatRuntimeReadinessDefaults = buildNapcatRuntimeReleaseReadiness({});
|
||||
const napcatRuntimeDefaults = napcatRuntimeReadinessDefaults.context;
|
||||
const napcatRemoteDefaults = buildNapcatRemoteDevHandoff({}).context;
|
||||
const nasBootstrapDefaults = buildNasCodexBootstrapPlan({}).context;
|
||||
for (const artifact of napcatRuntimeReadinessDefaults.artifacts) {
|
||||
if (!existsSync(artifact.path)) {
|
||||
throw new Error(
|
||||
`NapCat runtime readiness artifact write self-check failed: ${artifact.path}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
napcatCandidateDefaults.candidateBranch !==
|
||||
napcatAutomationDefaults.candidateBranch ||
|
||||
@ -477,7 +502,7 @@ export async function runSelfTest(): Promise<void> {
|
||||
"RUN_CODEX_WORKSPACE_WRITE_AUDIT",
|
||||
"buildDiscarder(logRotator",
|
||||
"disableConcurrentBuilds()",
|
||||
"archiveArtifacts artifacts: 'artifacts/napcat-upstream-sync/**/*'",
|
||||
"archiveArtifacts artifacts: 'artifacts/napcat-upstream-sync/**'",
|
||||
]) {
|
||||
if (!upstreamSyncJenkinsTemplate.includes(expected)) {
|
||||
throw new Error(`NapCat upstream Jenkins template missing: ${expected}`);
|
||||
@ -549,6 +574,62 @@ export async function runSelfTest(): Promise<void> {
|
||||
"NapCat upstream Jenkins template unsafe default audit self-check failed",
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Verifies that a NapCat Jenkins template targets the NAS Jenkins node that can run shell work.
|
||||
* @param template - Full Jenkinsfile text that Jenkins will execute.
|
||||
* @param name - Human-readable template name for the self-test failure.
|
||||
* @returns Nothing; throws when the template can stay queued on the exclusive agent topology.
|
||||
*/
|
||||
const assertNapCatJenkinsUsesNasAgent = (
|
||||
template: string,
|
||||
name: string,
|
||||
): void => {
|
||||
if (!template.includes("agent { label 'kt-node-agent' }")) {
|
||||
throw new Error(
|
||||
`${name} Jenkins template must target kt-node-agent instead of agent any`,
|
||||
);
|
||||
}
|
||||
};
|
||||
assertNapCatJenkinsUsesNasAgent(
|
||||
upstreamSyncJenkinsTemplate,
|
||||
"NapCat upstream",
|
||||
);
|
||||
assertNapCatJenkinsUsesNasAgent(
|
||||
runtimeReleaseJenkinsTemplate,
|
||||
"NapCat runtime",
|
||||
);
|
||||
/**
|
||||
* Verifies that a NapCat Jenkins template runs ktWorkflow inside a helper container with NAS mounts.
|
||||
* @param template - Full Jenkinsfile text that Jenkins will execute.
|
||||
* @param name - Human-readable template name for the self-test failure.
|
||||
* @returns Nothing; throws when the template assumes `/vol1` exists inside `kt-node-agent`.
|
||||
*/
|
||||
const assertNapCatJenkinsUsesWorkflowRunner = (
|
||||
template: string,
|
||||
name: string,
|
||||
): void => {
|
||||
for (const expected of [
|
||||
"KT_WORKFLOW_RUNNER_IMAGE",
|
||||
"docker run --rm",
|
||||
"/vol1/docker/kt-codex:/vol1/docker/kt-codex",
|
||||
"COREPACK_HOME=/vol1/docker/kt-codex/runtime/corepack",
|
||||
"corepack prepare pnpm@10.28.2 --activate",
|
||||
]) {
|
||||
if (!template.includes(expected)) {
|
||||
throw new Error(
|
||||
`${name} Jenkins template must run ktWorkflow through the NAS helper container: ${expected}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
assertNapCatJenkinsUsesWorkflowRunner(
|
||||
upstreamSyncJenkinsTemplate,
|
||||
"NapCat upstream",
|
||||
);
|
||||
assertNapCatJenkinsUsesWorkflowRunner(
|
||||
runtimeReleaseJenkinsTemplate,
|
||||
"NapCat runtime",
|
||||
);
|
||||
for (const forbidden of ["git merge", "git push", "docker build", "kubectl"]) {
|
||||
if (upstreamSyncJenkinsTemplate.includes(forbidden)) {
|
||||
throw new Error(
|
||||
@ -563,7 +644,7 @@ export async function runSelfTest(): Promise<void> {
|
||||
"PROMOTE_KT_TEMPLATE_API_MAIN",
|
||||
"buildDiscarder(logRotator",
|
||||
"disableConcurrentBuilds()",
|
||||
"archiveArtifacts artifacts: 'artifacts/napcat-runtime-release/**/*'",
|
||||
"archiveArtifacts artifacts: 'artifacts/napcat-runtime-release/**'",
|
||||
]) {
|
||||
if (!runtimeReleaseJenkinsTemplate.includes(expected)) {
|
||||
throw new Error(`NapCat runtime Jenkins template missing: ${expected}`);
|
||||
|
||||
@ -29,8 +29,24 @@ import {
|
||||
buildNapcatSyncCandidateReview,
|
||||
buildNapcatUpstreamAudit,
|
||||
buildNasCodexBootstrapPlan,
|
||||
shouldFailNapcatAutomationCli,
|
||||
} from './tools/napcatAutomation.js';
|
||||
|
||||
/**
|
||||
* Prints a NapCat automation result and turns blocked classifications into a failing CLI exit code.
|
||||
* @param result - Structured NapCat automation result that must remain visible on stdout for Jenkins artifacts and manual diagnostics.
|
||||
* @returns Nothing; writes JSON evidence and mutates `process.exitCode` when the result blocks safe continuation.
|
||||
*/
|
||||
function printNapcatAutomationCliResult(result: {
|
||||
classification?: unknown;
|
||||
recommendedAction?: unknown;
|
||||
}): void {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
if (shouldFailNapcatAutomationCli(result)) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv.includes('--cleanup-history')) {
|
||||
console.log(JSON.stringify(cleanupHistoryArtifacts(parseCliCleanupArgs(process.argv)), null, 2));
|
||||
} else if (process.argv.includes('--global-review')) {
|
||||
@ -56,8 +72,8 @@ if (process.argv.includes('--cleanup-history')) {
|
||||
),
|
||||
);
|
||||
} else if (process.argv.includes('--napcat-upstream-audit')) {
|
||||
console.log(
|
||||
JSON.stringify(buildNapcatUpstreamAudit(parseNapcatUpstreamAuditCliArgs(process.argv)), null, 2),
|
||||
printNapcatAutomationCliResult(
|
||||
buildNapcatUpstreamAudit(parseNapcatUpstreamAuditCliArgs(process.argv)),
|
||||
);
|
||||
} else if (process.argv.includes('--napcat-sync-candidate-review')) {
|
||||
console.log(
|
||||
|
||||
@ -117,6 +117,18 @@ export const napcatAutomationDefaults = {
|
||||
workspaceRoot: '/vol1/docker/kt-codex/workspace/KT',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Decides whether a NapCat automation CLI invocation must fail the hosting pipeline after printing JSON evidence.
|
||||
* @param result - Structured ktWorkflow result from a NapCat automation command; only terminal block states represent infrastructure or safety failures.
|
||||
* @returns True when Jenkins or another CLI caller must stop the pipeline instead of showing a green build with blocked evidence.
|
||||
*/
|
||||
export function shouldFailNapcatAutomationCli(result: {
|
||||
classification?: unknown;
|
||||
recommendedAction?: unknown;
|
||||
}): boolean {
|
||||
return result.classification === 'blocked' || result.recommendedAction === 'block';
|
||||
}
|
||||
|
||||
interface NapcatPromptSkeletonConfig {
|
||||
artifactSlug: string;
|
||||
commands: NapcatAuditCommand[];
|
||||
@ -144,9 +156,9 @@ function resolveNapcatSkeletonArtifactRoot(
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a deterministic dry-run prompt skeleton for Task 5 NapCat automation tools that are not allowed to mutate state yet.
|
||||
* Builds a deterministic dry-run prompt skeleton for Task 5 NapCat automation tools that are not allowed to mutate release state yet.
|
||||
* @param config - Prompt name, context payload, read-only command hints, and caller execution intent.
|
||||
* @returns A structured prompt/context/command package that never runs Codex or mutating release commands.
|
||||
* @returns A structured prompt/context/command package; writes prompt and context artifacts for Jenkins archiving but never runs Codex or release mutation commands.
|
||||
*/
|
||||
function buildNapcatPromptSkeleton(config: NapcatPromptSkeletonConfig) {
|
||||
const artifactRoot = resolveNapcatSkeletonArtifactRoot(
|
||||
@ -154,21 +166,32 @@ function buildNapcatPromptSkeleton(config: NapcatPromptSkeletonConfig) {
|
||||
config.artifactSlug,
|
||||
);
|
||||
const requestedExecute = config.input.execute === true;
|
||||
const promptText = loadNapcatAutomationPrompt(config.promptName).trim();
|
||||
const promptPath = joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
`${config.artifactSlug}-prompt.md`,
|
||||
);
|
||||
const contextPath = joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
`${config.artifactSlug}-context.json`,
|
||||
);
|
||||
|
||||
mkdirSync(artifactRoot, { recursive: true });
|
||||
writeFileSync(promptPath, promptText, 'utf8');
|
||||
writeFileSync(
|
||||
contextPath,
|
||||
JSON.stringify(config.context, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return {
|
||||
artifacts: [
|
||||
{
|
||||
path: joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
`${config.artifactSlug}-prompt.md`,
|
||||
),
|
||||
path: promptPath,
|
||||
type: 'markdown',
|
||||
},
|
||||
{
|
||||
path: joinNapcatArtifactPath(
|
||||
artifactRoot,
|
||||
`${config.artifactSlug}-context.json`,
|
||||
),
|
||||
path: contextPath,
|
||||
type: 'json',
|
||||
},
|
||||
],
|
||||
@ -184,7 +207,7 @@ function buildNapcatPromptSkeleton(config: NapcatPromptSkeletonConfig) {
|
||||
],
|
||||
prompt: {
|
||||
name: config.promptName,
|
||||
text: loadNapcatAutomationPrompt(config.promptName).trim(),
|
||||
text: promptText,
|
||||
},
|
||||
requestedExecute,
|
||||
summary: `${config.title} dry-run prompt skeleton generated.`,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user