feat: 接入NapCat自动化MCP工具和CLI

This commit is contained in:
sunlei 2026-06-25 01:24:31 +08:00
parent c16a867c60
commit b767732fd3
9 changed files with 712 additions and 1 deletions

View File

@ -16,6 +16,7 @@
- 生成接口测试计划:接口改动后输出真实调用命令和统一返回结构断言。
- 生成业务链路测试计划:固化 Admin 登录、博客 CRUD、QQBot 扫码/自动回复、更新登录 SSE、FFLogs 命令、系统日志可视化、Web/Playground 回跳。
- 静态检查 NapCat 设备身份护栏:确认 API 仍保留 QQNT 可见 hostname、实体 OUI 风格 MAC、QQNT `machine-info` 写入、持久化 runtime dir 和 `DB_TIMEZONE` 默认值。
- 生成 NapCatQQ 上游同步与 runtime 发布自动化入口MCP/CLI 默认 dry-run覆盖 upstream audit、sync candidate review、runtime readiness、remote dev handoff 和 NAS Codex bootstrap plan除 upstream audit 在显式 `execute=true``useCodex=true` 时可运行 Codex 外,其余 Task 5 入口只返回 prompt/context/只读命令骨架。
- 生成卡点固化记录:把超时、卡进程、远程命令误写、重复失败整理成“问题点 / 稳定解法 / 后续入口 / 验证证据”,避免原样重试。
- 生成改动文档同步计划:按变更文件自动提示需要同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 入口。
- 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。
@ -40,6 +41,10 @@ pnpm run obsidian-context -- --module ktWorkflow
pnpm run obsidian-validate
pnpm run obsidian-sync
pnpm run workstream-closeout -- --title "发布闭环" --verification "Jenkins SUCCESS" --doc-sync "无需文档更新" --cleanup "cleanup-history dry-run deleted=0" --cleanup-final-deleted 0 --review "global-review findings=0" --superpowers-review "Superpowers reviewer completed; no Critical/Important findings" --problem "无新卡点" --solution "无新增稳定解法"
pnpm run napcat-upstream-audit -- --artifact-root .kt-workspace/test-artifacts/napcat-upstream-sync
pnpm run napcat-sync-candidate-review
pnpm run napcat-runtime-release-readiness
pnpm run nas-codex-bootstrap
pnpm run napcat-device-profile-check
pnpm run cleanup-history -- --dry-run
pnpm run cleanup-history -- --execute
@ -95,6 +100,11 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
| `kt_commit_plan` | 生成多仓库提交计划、建议 commit message 和检查项 |
| `kt_push_plan` | 生成多仓库推送计划和远程异常提醒 |
| `kt_business_test_plan` | 生成固化业务链路测试计划,包含 QQBot SSE、FFLogs 和系统日志 |
| `kt_napcat_upstream_audit` | 生成或执行 NapCatQQ upstream release 审计,默认 dry-run显式 `execute/useCodex` 后才运行 Codex |
| `kt_napcat_sync_candidate_review` | 生成 NapCat sync candidate review 的 prompt/context/只读命令骨架Task 5 不执行 Codex 或 Git 写操作 |
| `kt_napcat_runtime_release_readiness` | 生成 NapCat runtime release readiness 的 prompt/context/只读检查骨架,不自动构建或发布 |
| `kt_napcat_remote_dev_handoff` | 生成 NapCat 自动化远程开发 handoff 的 prompt/context 骨架,不创建线程或同步仓库 |
| `kt_nas_codex_bootstrap_plan` | 生成可信 NAS Codex bootstrap dry-run planTask 5 仅输出只读检查和占位计划 |
| `kt_napcat_device_profile_check` | 静态检查 API NapCat 设备身份护栏,覆盖 hostname、MAC、`machine-info`、runtime dir 和 DB timezone |
| `kt_blocker_resolution` | 生成或写入卡点固化记录,提醒停止原样重试 |
| `kt_remote_health_check` | 生成或执行远程只读健康检查命令 |
@ -113,6 +123,10 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pnpm run admin-login` | 使用可见 Edge 打开 Admin 登录页,填写账号密码,拖动滑块,保存登录态和截图。默认账号来自初始化数据 `admin/123456`,生产或个人账号用 `KT_ADMIN_USERNAME` / `KT_ADMIN_PASSWORD` 或 CLI 参数覆盖。 |
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、QQBot 插件 registry/controller smoke 未 mock 具体插件实现风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、API Pod SSH 日志读取超时及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
| `pnpm run napcat-upstream-audit` | 默认 dry-run 输出 NapCatQQ 上游 release 审计分类、只读 Git 命令和 artifact 路径;显式 `--execute --use-codex` 才运行 Codex。 |
| `pnpm run napcat-sync-candidate-review` | 输出 sync candidate review 的 prompt/context/只读命令骨架,不执行 Codex 或 Git 写操作。 |
| `pnpm run napcat-runtime-release-readiness` | 输出 runtime release readiness 的 prompt/context/只读检查骨架,不构建镜像、不调用 kubectl、不发布。 |
| `pnpm run nas-codex-bootstrap` | 输出可信 NAS Codex bootstrap dry-run plan真实安装、同步和 systemd 接入留给后续实现。 |
| `pnpm run napcat-device-profile-check` | 静态检查 API NapCat 设备身份护栏,防止回退到未知设备风险配置。 |
| `pnpm run deploy-observation` | 默认 dry-run 输出只读 NAS 观测命令;传 `--execute` 时执行 Jenkins/K8s/health/smoke 观测并写入 `.kt-workspace/test-artifacts/deploy-observation`。 |
| `pnpm run obsidian-context` | 输出 Obsidian 索引上下文;可传 `--module Admin`、`--query QQBot`、`--max-documents 10`。 |

View File

@ -10,6 +10,10 @@
"deploy-observation": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --deploy-observation",
"global-review": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --global-review",
"napcat-device-profile-check": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --napcat-device-profile-check",
"napcat-runtime-release-readiness": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --napcat-runtime-release-readiness",
"napcat-sync-candidate-review": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --napcat-sync-candidate-review",
"napcat-upstream-audit": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --napcat-upstream-audit",
"nas-codex-bootstrap": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --nas-codex-bootstrap",
"obsidian-context": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --obsidian-context",
"obsidian-sync": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --obsidian-sync",
"obsidian-validate": "node --import ./node_modules/tsx/dist/loader.mjs src/server.ts --obsidian-validate",

View File

@ -1,6 +1,10 @@
import type {
DeployObservationInput,
GlobalCodeReviewInput,
NapcatRemoteDevHandoffInput,
NapcatRuntimeReleaseReadinessInput,
NapcatSyncCandidateReviewInput,
NasCodexBootstrapPlanInput,
ObsidianContextInput,
ObsidianSyncInput,
ObsidianValidateInput,
@ -8,6 +12,7 @@ import type {
WorkstreamReusablePattern,
} from '../types.js';
import { workstreamReusablePatternValues } from './constants.js';
import type { NapcatUpstreamAuditInput } from '../tools/napcatAutomation.types.js';
function parseContentScanMode(argv: string[]): 'all' | 'changed' {
if (argv.includes('--content-scan-all') || argv.includes('--contentScanMode=all')) {
@ -62,6 +67,16 @@ function readOptionList(argv: string[], names: string[]): string[] {
.filter(Boolean);
}
/**
* Checks whether any supported boolean CLI flag is present in the raw argv list.
* @param argv - Full process argv including node, script, mode flag, and user options.
* @param names - Flag names to accept in dashed or camelCase form.
* @returns True when the caller provided one of the named flags.
*/
function hasFlag(argv: string[], names: string[]): boolean {
return names.some((name) => argv.includes(name));
}
export function parseWorkstreamCloseoutCliArgs(argv: string[]): WorkstreamCloseoutInput {
const patternSet = new Set<string>(workstreamReusablePatternValues);
const patterns = readOptionList(argv, ['--pattern', '--reusable-pattern'])
@ -123,6 +138,135 @@ export function parseDeployObservationCliArgs(argv: string[]): DeployObservation
};
}
/**
* Parses the NapCat upstream audit CLI mode into the deterministic audit builder input.
* @param argv - Full process argv from `src/server.ts`; `--execute` and `--use-codex` must both be explicit before Codex can run.
* @returns Upstream audit input with dry-run defaults for all optional metadata lists.
*/
export function parseNapcatUpstreamAuditCliArgs(
argv: string[],
): NapcatUpstreamAuditInput {
return {
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
codexHome: readOption(argv, ['--codex-home', '--codexHome']),
createCandidateBranch: hasFlag(argv, ['--create-candidate-branch', '--createCandidateBranch']),
dryMergeConflict: hasFlag(argv, ['--dry-merge-conflict', '--dryMergeConflict']),
execute: hasFlag(argv, ['--execute']),
forkBranch: readOption(argv, ['--fork-branch', '--forkBranch']),
forkPatchFiles: readOptionList(argv, [
'--fork-patch-file',
'--fork-patch-files',
'--forkPatchFile',
'--forkPatchFiles',
]),
forkRepo: readOption(argv, ['--fork-repo', '--forkRepo']),
lastAcceptedUpstreamBase: readOption(argv, [
'--last-accepted-upstream-base',
'--lastAcceptedUpstreamBase',
]),
outputSchemaPath: readOption(argv, ['--output-schema', '--outputSchemaPath']),
upstreamChangedFiles: readOptionList(argv, [
'--upstream-changed-file',
'--upstream-changed-files',
'--upstreamChangedFile',
'--upstreamChangedFiles',
]),
upstreamReleaseTag: readOption(argv, [
'--upstream-release-tag',
'--upstream-release-ref',
'--upstreamReleaseTag',
]),
upstreamRepo: readOption(argv, ['--upstream-repo', '--upstreamRepo']),
useCodex: hasFlag(argv, ['--use-codex', '--useCodex']),
workspaceRoot: readOption(argv, ['--workspace-root', '--workspaceRoot']),
};
}
/**
* Parses common dry-run NapCat prompt tool options shared by candidate review, readiness, and handoff modes.
* @param argv - Full process argv from a NapCat automation CLI script.
* @returns Prompt skeleton input with `execute` false unless explicitly requested by the caller.
*/
function parseNapcatPromptToolCliArgs(argv: string[]): NapcatSyncCandidateReviewInput {
return {
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
candidateBranch: readOption(argv, ['--candidate-branch', '--candidateBranch']),
execute: hasFlag(argv, ['--execute']),
forkBranch: readOption(argv, ['--fork-branch', '--forkBranch']),
forkRepo: readOption(argv, ['--fork-repo', '--forkRepo']),
lastAcceptedUpstreamBase: readOption(argv, [
'--last-accepted-upstream-base',
'--lastAcceptedUpstreamBase',
]),
upstreamReleaseTag: readOption(argv, [
'--upstream-release-tag',
'--upstream-release-ref',
'--upstreamReleaseTag',
]),
upstreamRepo: readOption(argv, ['--upstream-repo', '--upstreamRepo']),
useCodex: hasFlag(argv, ['--use-codex', '--useCodex']),
workspaceRoot: readOption(argv, ['--workspace-root', '--workspaceRoot']),
};
}
/**
* Parses the NapCat sync-candidate review CLI mode.
* @param argv - Full process argv from `pnpm run napcat-sync-candidate-review`.
* @returns Dry-run prompt skeleton input for human/Codex candidate review handoff.
*/
export function parseNapcatSyncCandidateReviewCliArgs(
argv: string[],
): NapcatSyncCandidateReviewInput {
return parseNapcatPromptToolCliArgs(argv);
}
/**
* Parses the NapCat runtime release readiness CLI mode.
* @param argv - Full process argv from `pnpm run napcat-runtime-release-readiness`.
* @returns Dry-run prompt skeleton input with image/profile hints and no default mutation.
*/
export function parseNapcatRuntimeReleaseReadinessCliArgs(
argv: string[],
): NapcatRuntimeReleaseReadinessInput {
return {
...parseNapcatPromptToolCliArgs(argv),
apiImageTag: readOption(argv, ['--api-image-tag', '--apiImageTag']),
napcatImageTag: readOption(argv, ['--napcat-image-tag', '--napcatImageTag']),
profile: readOption(argv, ['--profile']),
};
}
/**
* Parses the NapCat remote development handoff CLI mode.
* @param argv - Full process argv from `src/server.ts --napcat-remote-dev-handoff`.
* @returns Dry-run prompt skeleton input for a remote workspace handoff packet.
*/
export function parseNapcatRemoteDevHandoffCliArgs(
argv: string[],
): NapcatRemoteDevHandoffInput {
return {
...parseNapcatPromptToolCliArgs(argv),
targetHost: readOption(argv, ['--target-host', '--targetHost']),
};
}
/**
* Parses the NAS Codex bootstrap planning CLI mode.
* @param argv - Full process argv from `pnpm run nas-codex-bootstrap`.
* @returns Dry-run bootstrap plan input; `execute` is false unless the caller explicitly opts in.
*/
export function parseNasCodexBootstrapCliArgs(
argv: string[],
): NasCodexBootstrapPlanInput {
return {
artifactRoot: readOption(argv, ['--artifact-root', '--artifactRoot']),
codexHome: readOption(argv, ['--codex-home', '--codexHome']),
execute: hasFlag(argv, ['--execute']),
serviceName: readOption(argv, ['--service-name', '--serviceName']),
workspaceRoot: readOption(argv, ['--workspace-root', '--workspaceRoot']),
};
}
export function parseObsidianCliArgs(argv: string[]): ObsidianValidateInput {
return {
checkLayout: !argv.includes('--no-layout'),

View File

@ -69,6 +69,11 @@ export const registeredToolNames = [
'kt_commit_plan',
'kt_push_plan',
'kt_business_test_plan',
'kt_napcat_upstream_audit',
'kt_napcat_sync_candidate_review',
'kt_napcat_runtime_release_readiness',
'kt_napcat_remote_dev_handoff',
'kt_nas_codex_bootstrap_plan',
'kt_napcat_device_profile_check',
'kt_blocker_resolution',
'kt_remote_health_check',

View File

@ -22,6 +22,13 @@ import { buildVerificationPlan } from './tools/verification.js';
import { buildWorkstreamCloseout } from './tools/closeout.js';
import { buildChangeDocSync } from './tools/docSync.js';
import { buildDeployObservation } from './tools/deployObservation.js';
import {
buildNapcatRemoteDevHandoff,
buildNapcatRuntimeReleaseReadiness,
buildNapcatSyncCandidateReview,
buildNapcatUpstreamAudit,
buildNasCodexBootstrapPlan,
} from './tools/napcatAutomation.js';
import { buildCommitPlan, buildComponentWorkflow, buildDbSyncPlan, buildFinishTask, buildPushPlan, buildRemoteHealthCheck } from './tools/workflow.js';
import { workstreamReusablePatternValues } from './core/constants.js';
export function response(data: unknown): McpTextResponse {
@ -35,6 +42,11 @@ export function response(data: unknown): McpTextResponse {
};
}
/**
* Registers every ktWorkflow MCP tool against the provided MCP server instance.
* @param server - MCP SDK server that owns tool names, schemas, descriptions, and async handlers.
* @returns Nothing; handlers are attached to `server` for later stdio transport use.
*/
export function registerTools(server: McpServer): void {
server.registerTool(
'kt_read_context',
@ -370,6 +382,128 @@ export function registerTools(server: McpServer): void {
async (input) => response(buildBusinessTestPlan(input)),
);
server.registerTool(
'kt_napcat_upstream_audit',
{
description:
'生成或执行 NapCatQQ 上游 release 审计:默认 dry-run只读输出 hot zone、fork patch overlap、dry merge 和 Codex 审计入口execute/useCodex 均需显式开启才会运行 Codex。',
inputSchema: {
artifactRoot: z
.string()
.default('.kt-workspace/test-artifacts/napcat-upstream-audit'),
codexHome: z.string().optional(),
createCandidateBranch: z.boolean().default(false),
dryMergeConflict: z.boolean().default(false),
execute: z.boolean().default(false),
forkBranch: z.string().default('HEAD'),
forkPatchFiles: z.array(z.string()).default([]),
forkRepo: z.string().default('KTeam/NapCatQQ'),
lastAcceptedUpstreamBase: z.string().default('origin/main'),
outputSchemaPath: z.string().optional(),
upstreamChangedFiles: z.array(z.string()).default([]),
upstreamReleaseTag: z.string().default('upstream/main'),
upstreamRepo: z.string().default('NapNeko/NapCatQQ'),
useCodex: z.boolean().default(false),
workspaceRoot: z.string().default('/vol1/docker/kt-codex/workspace/KT'),
},
title: 'KT NapCat Upstream Audit',
},
async (input) => response(buildNapcatUpstreamAudit(input)),
);
server.registerTool(
'kt_napcat_sync_candidate_review',
{
description:
'生成 NapCatQQ sync candidate 人工/Codex 复审的 dry-run prompt、上下文和只读命令骨架Task 5 不执行 Codex 或 Git 写操作。',
inputSchema: {
artifactRoot: z
.string()
.default('.kt-workspace/test-artifacts/napcat-automation/sync-candidate-review'),
candidateBranch: z.string().default('napcat-upstream-sync/candidate'),
execute: z.boolean().default(false),
forkBranch: z.string().default('dev'),
forkRepo: z.string().default('KTeam/NapCatQQ'),
lastAcceptedUpstreamBase: z.string().default('origin/main'),
upstreamReleaseTag: z.string().default('upstream/main'),
upstreamRepo: z.string().default('NapNeko/NapCatQQ'),
useCodex: z.boolean().default(false),
workspaceRoot: z.string().default('/vol1/docker/kt-codex/workspace/KT'),
},
title: 'KT NapCat Sync Candidate Review',
},
async (input) => response(buildNapcatSyncCandidateReview(input)),
);
server.registerTool(
'kt_napcat_runtime_release_readiness',
{
description:
'生成 NapCat runtime release readiness dry-run prompt、上下文和只读检查骨架不自动构建镜像、不调用 kubectl、不发布。',
inputSchema: {
apiImageTag: z.string().default('main-<jenkins-build>'),
artifactRoot: z
.string()
.default('.kt-workspace/test-artifacts/napcat-automation/runtime-release-readiness'),
candidateBranch: z.string().default('napcat-upstream-sync/candidate'),
execute: z.boolean().default(false),
forkBranch: z.string().default('dev'),
forkRepo: z.string().default('KTeam/NapCatQQ'),
napcatImageTag: z.string().default('kt-napcat-desktop-cn:desktop-cn'),
profile: z.string().default('desktop-cn'),
upstreamReleaseTag: z.string().default('upstream/main'),
upstreamRepo: z.string().default('NapNeko/NapCatQQ'),
useCodex: z.boolean().default(false),
workspaceRoot: z.string().default('/vol1/docker/kt-codex/workspace/KT'),
},
title: 'KT NapCat Runtime Release Readiness',
},
async (input) => response(buildNapcatRuntimeReleaseReadiness(input)),
);
server.registerTool(
'kt_napcat_remote_dev_handoff',
{
description:
'生成 NapCat 自动化远程开发 handoff 的 dry-run prompt 和上下文骨架;不创建线程、不执行 Codex、不同步仓库。',
inputSchema: {
artifactRoot: z
.string()
.default('.kt-workspace/test-artifacts/napcat-automation/remote-dev-handoff'),
candidateBranch: z.string().default('napcat-upstream-sync/candidate'),
execute: z.boolean().default(false),
forkBranch: z.string().default('dev'),
forkRepo: z.string().default('KTeam/NapCatQQ'),
targetHost: z.string().default('nas'),
upstreamReleaseTag: z.string().default('upstream/main'),
upstreamRepo: z.string().default('NapNeko/NapCatQQ'),
useCodex: z.boolean().default(false),
workspaceRoot: z.string().default('/vol1/docker/kt-codex/workspace/KT'),
},
title: 'KT NapCat Remote Dev Handoff',
},
async (input) => response(buildNapcatRemoteDevHandoff(input)),
);
server.registerTool(
'kt_nas_codex_bootstrap_plan',
{
description:
'生成可信 NAS Codex 自动化环境 bootstrap 的 dry-run planTask 5 仅输出只读检查和占位计划,不安装、不同步、不写 systemd。',
inputSchema: {
artifactRoot: z
.string()
.default('.kt-workspace/test-artifacts/napcat-automation/nas-codex-bootstrap'),
codexHome: z.string().default('/vol1/docker/kt-codex/home/.codex'),
execute: z.boolean().default(false),
serviceName: z.string().default('kt-codex-automation'),
workspaceRoot: z.string().default('/vol1/docker/kt-codex/workspace/KT'),
},
title: 'KT NAS Codex Bootstrap Plan',
},
async (input) => response(buildNasCodexBootstrapPlan(input)),
);
server.registerTool(
'kt_napcat_device_profile_check',
{

View File

@ -2,8 +2,12 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
import path from "node:path";
import {
parseNasCodexBootstrapCliArgs,
parseDeployObservationCliArgs,
parseGlobalReviewCliArgs,
parseNapcatRuntimeReleaseReadinessCliArgs,
parseNapcatSyncCandidateReviewCliArgs,
parseNapcatUpstreamAuditCliArgs,
parseWorkstreamCloseoutCliArgs,
} from "./core/cli.js";
import { registeredToolNames } from "./core/constants.js";
@ -96,6 +100,51 @@ export async function runSelfTest(): Promise<void> {
if (!napcatAutomationReasonCodes.includes("HOT_ZONE_CHANGED")) {
throw new Error("NapCat reason-code list self-check failed");
}
const napcatCli = parseNapcatUpstreamAuditCliArgs([
"node",
"server",
"--napcat-upstream-audit",
"--execute",
"--use-codex",
"--artifact-root",
"/vol1/docker/kt-codex/artifacts/napcat-upstream-sync",
]);
if (!napcatCli.execute || !napcatCli.useCodex) {
throw new Error("NapCat upstream audit CLI parser self-check failed");
}
const napcatCandidateCli = parseNapcatSyncCandidateReviewCliArgs([
"node",
"server",
"--napcat-sync-candidate-review",
]);
const napcatRuntimeCli = parseNapcatRuntimeReleaseReadinessCliArgs([
"node",
"server",
"--napcat-runtime-release-readiness",
]);
const nasBootstrapCli = parseNasCodexBootstrapCliArgs([
"node",
"server",
"--nas-codex-bootstrap",
]);
if (
napcatCandidateCli.execute ||
napcatRuntimeCli.execute ||
nasBootstrapCli.execute
) {
throw new Error("NapCat auxiliary CLI dry-run default self-check failed");
}
for (const toolName of [
"kt_napcat_upstream_audit",
"kt_napcat_sync_candidate_review",
"kt_napcat_runtime_release_readiness",
"kt_napcat_remote_dev_handoff",
"kt_nas_codex_bootstrap_plan",
] as const) {
if (!registeredToolNames.includes(toolName)) {
throw new Error(`NapCat MCP tool registry self-check failed: ${toolName}`);
}
}
if (parseNapcatAutomationClassification("manual-review") !== "manual-review") {
throw new Error("NapCat classification parser self-check failed");
}

View File

@ -3,8 +3,13 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
parseNasCodexBootstrapCliArgs,
parseDeployObservationCliArgs,
parseGlobalReviewCliArgs,
parseNapcatRemoteDevHandoffCliArgs,
parseNapcatRuntimeReleaseReadinessCliArgs,
parseNapcatSyncCandidateReviewCliArgs,
parseNapcatUpstreamAuditCliArgs,
parseObsidianContextCliArgs,
parseObsidianCliArgs,
parseObsidianSyncCliArgs,
@ -18,6 +23,13 @@ import { buildGlobalCodeReview } from './tools/review.js';
import { registerTools } from './registerTools.js';
import { runSelfTest } from './selfTest.js';
import { buildNapcatDeviceProfileCheck } from './tools/testing.js';
import {
buildNapcatRemoteDevHandoff,
buildNapcatRuntimeReleaseReadiness,
buildNapcatSyncCandidateReview,
buildNapcatUpstreamAudit,
buildNasCodexBootstrapPlan,
} from './tools/napcatAutomation.js';
if (process.argv.includes('--cleanup-history')) {
console.log(JSON.stringify(cleanupHistoryArtifacts(parseCliCleanupArgs(process.argv)), null, 2));
@ -43,6 +55,38 @@ if (process.argv.includes('--cleanup-history')) {
2,
),
);
} else if (process.argv.includes('--napcat-upstream-audit')) {
console.log(
JSON.stringify(buildNapcatUpstreamAudit(parseNapcatUpstreamAuditCliArgs(process.argv)), null, 2),
);
} else if (process.argv.includes('--napcat-sync-candidate-review')) {
console.log(
JSON.stringify(
buildNapcatSyncCandidateReview(parseNapcatSyncCandidateReviewCliArgs(process.argv)),
null,
2,
),
);
} else if (process.argv.includes('--napcat-runtime-release-readiness')) {
console.log(
JSON.stringify(
buildNapcatRuntimeReleaseReadiness(parseNapcatRuntimeReleaseReadinessCliArgs(process.argv)),
null,
2,
),
);
} else if (process.argv.includes('--napcat-remote-dev-handoff')) {
console.log(
JSON.stringify(
buildNapcatRemoteDevHandoff(parseNapcatRemoteDevHandoffCliArgs(process.argv)),
null,
2,
),
);
} else if (process.argv.includes('--nas-codex-bootstrap')) {
console.log(
JSON.stringify(buildNasCodexBootstrapPlan(parseNasCodexBootstrapCliArgs(process.argv)), null, 2),
);
} else if (process.argv.includes('--self-test')) {
await runSelfTest();
} else if (process.argv.includes('--workstream-closeout')) {

View File

@ -3,8 +3,18 @@ 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 {
loadNapcatAutomationPrompt,
type NapcatAutomationPromptName,
} from './napcatAutomation.prompts.js';
import { workspaceRoot } from '../core/workspace.js';
import type {
NapcatPromptToolInput,
NapcatRemoteDevHandoffInput,
NapcatRuntimeReleaseReadinessInput,
NapcatSyncCandidateReviewInput,
NasCodexBootstrapPlanInput,
} from '../types.js';
import type {
NapcatAuditClassificationInput,
NapcatAuditClassificationResult,
@ -88,6 +98,279 @@ const hotZoneMatchers: Array<{ pattern: string; regex: RegExp }> = [
const napcatNasCodexArtifactRoot = '/vol1/docker/kt-codex/artifacts';
const napcatLocalArtifactRoot = '.kt-workspace/test-artifacts';
const napcatAutomationSkeletonArtifactRoot =
'.kt-workspace/test-artifacts/napcat-automation';
const defaultNapcatForkRepo = 'KTeam/NapCatQQ';
const defaultNapcatUpstreamRepo = 'NapNeko/NapCatQQ';
const defaultNapcatCandidateBranch = 'napcat-upstream-sync/candidate';
const defaultNapcatWorkspaceRoot = '/vol1/docker/kt-codex/workspace/KT';
interface NapcatPromptSkeletonConfig {
artifactSlug: string;
commands: NapcatAuditCommand[];
context: Record<string, unknown>;
input: NapcatPromptToolInput;
notes: string[];
promptName: NapcatAutomationPromptName;
title: string;
}
/**
* Resolves the artifact root shown by dry-run NapCat prompt skeleton tools.
* @param input - Prompt tool input carrying an optional artifactRoot override.
* @param artifactSlug - Stable child directory name for the specific automation tool.
* @returns The caller-provided artifact root, or the ktWorkflow test-artifact default for this tool.
*/
function resolveNapcatSkeletonArtifactRoot(
input: NapcatPromptToolInput,
artifactSlug: string,
): string {
return (
input.artifactRoot?.trim() ||
`${napcatAutomationSkeletonArtifactRoot}/${artifactSlug}`
);
}
/**
* Builds a deterministic dry-run prompt skeleton for Task 5 NapCat automation tools that are not allowed to mutate 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.
*/
function buildNapcatPromptSkeleton(config: NapcatPromptSkeletonConfig) {
const artifactRoot = resolveNapcatSkeletonArtifactRoot(
config.input,
config.artifactSlug,
);
const requestedExecute = config.input.execute === true;
return {
artifacts: [
{
path: joinNapcatArtifactPath(
artifactRoot,
`${config.artifactSlug}-prompt.md`,
),
type: 'markdown',
},
{
path: joinNapcatArtifactPath(
artifactRoot,
`${config.artifactSlug}-context.json`,
),
type: 'json',
},
],
commands: config.commands,
context: config.context,
execute: false,
notes: [
'Task 5 dry-run skeleton only; no Codex, git merge, git push, docker build, kubectl, or deploy command is executed.',
requestedExecute
? 'execute=true was requested, but this Task 5 skeleton intentionally remains non-mutating until the later implementation task.'
: 'execute defaults to false.',
...config.notes,
],
prompt: {
name: config.promptName,
text: loadNapcatAutomationPrompt(config.promptName).trim(),
},
requestedExecute,
summary: `${config.title} dry-run prompt skeleton generated.`,
useCodex: config.input.useCodex === true,
};
}
/**
* Builds a dry-run prompt skeleton for reviewing a NapCat upstream sync candidate branch.
* @param input - Candidate branch, fork/upstream refs, artifact root, and execution intent; execution is never performed in Task 5.
* @returns Prompt, context, artifacts, and read-only command hints for candidate review.
*/
export function buildNapcatSyncCandidateReview(
input: NapcatSyncCandidateReviewInput,
) {
const context = {
candidateBranch: input.candidateBranch ?? defaultNapcatCandidateBranch,
forkBranch: input.forkBranch ?? 'dev',
forkRepo: input.forkRepo ?? defaultNapcatForkRepo,
lastAcceptedUpstreamBase:
input.lastAcceptedUpstreamBase ?? 'LAST_ACCEPTED_UPSTREAM_BASE',
upstreamReleaseTag: input.upstreamReleaseTag ?? 'UPSTREAM_RELEASE_REF',
upstreamRepo: input.upstreamRepo ?? defaultNapcatUpstreamRepo,
workspaceRoot: input.workspaceRoot ?? defaultNapcatWorkspaceRoot,
};
return buildNapcatPromptSkeleton({
artifactSlug: 'sync-candidate-review',
commands: [
{
command: 'git status --short --branch',
description: 'Confirm the candidate worktree state before review.',
readOnly: true,
},
{
command:
'git log --oneline --decorate -n 20 "${CANDIDATE_BRANCH:-napcat-upstream-sync/candidate}"',
description: 'Inspect recent candidate commits without changing refs.',
readOnly: true,
},
{
command:
'git diff --stat "${LAST_ACCEPTED_UPSTREAM_BASE:-origin/main}..${CANDIDATE_BRANCH:-napcat-upstream-sync/candidate}"',
description: 'Summarize candidate file churn for human review.',
readOnly: true,
},
],
context,
input,
notes: [
'Use this skeleton after upstream audit classifies a candidate as safe enough for human review.',
],
promptName: 'sync-candidate-review',
title: 'NapCat sync candidate review',
});
}
/**
* Builds a dry-run prompt skeleton for checking NapCat runtime release readiness.
* @param input - Image/profile hints and repo refs used to prepare readiness context; execution is never performed in Task 5.
* @returns Prompt, context, artifacts, and bounded read-only command hints for readiness review.
*/
export function buildNapcatRuntimeReleaseReadiness(
input: NapcatRuntimeReleaseReadinessInput,
) {
const context = {
apiImageTag: input.apiImageTag ?? 'main-<jenkins-build>',
candidateBranch: input.candidateBranch ?? defaultNapcatCandidateBranch,
forkRepo: input.forkRepo ?? defaultNapcatForkRepo,
napcatImageTag: input.napcatImageTag ?? 'kt-napcat-desktop-cn:<profile>',
profile: input.profile ?? 'desktop-cn',
upstreamReleaseTag: input.upstreamReleaseTag ?? 'UPSTREAM_RELEASE_REF',
workspaceRoot: input.workspaceRoot ?? defaultNapcatWorkspaceRoot,
};
return buildNapcatPromptSkeleton({
artifactSlug: 'runtime-release-readiness',
commands: [
{
command: 'git status --short --branch',
description: 'Confirm local release metadata is not mixed with unrelated changes.',
readOnly: true,
},
{
command:
'docker image inspect "${NAPCAT_IMAGE_TAG:-kt-napcat-desktop-cn:desktop-cn}"',
description: 'Read the NAS-local NapCat image metadata without pulling or building.',
readOnly: true,
},
{
command:
'curl -fsS --max-time 8 "${API_HEALTH_URL:-http://127.0.0.1:48085/health/runtime}"',
description: 'Read the API runtime health endpoint before any release promotion.',
readOnly: true,
},
],
context,
input,
notes: [
'Deployment and Kubernetes evidence must be collected by the existing deploy-observation workflow after explicit human approval.',
],
promptName: 'runtime-release-readiness',
title: 'NapCat runtime release readiness',
});
}
/**
* Builds a dry-run prompt skeleton for handing NapCat automation work to a trusted remote Codex workspace.
* @param input - Fork/ref hints, remote target label, and artifact location for the handoff packet.
* @returns Prompt, context, artifacts, and read-only local command hints for a remote handoff.
*/
export function buildNapcatRemoteDevHandoff(
input: NapcatRemoteDevHandoffInput,
) {
const context = {
candidateBranch: input.candidateBranch ?? defaultNapcatCandidateBranch,
forkRepo: input.forkRepo ?? defaultNapcatForkRepo,
targetHost: input.targetHost ?? 'nas',
upstreamReleaseTag: input.upstreamReleaseTag ?? 'UPSTREAM_RELEASE_REF',
workspaceRoot: input.workspaceRoot ?? defaultNapcatWorkspaceRoot,
};
return buildNapcatPromptSkeleton({
artifactSlug: 'remote-dev-handoff',
commands: [
{
command: 'git rev-parse HEAD',
description: 'Capture the local ktWorkflow commit used to prepare the handoff.',
readOnly: true,
},
{
command: 'git status --short --branch',
description: 'Confirm no unrelated local state enters the handoff packet.',
readOnly: true,
},
],
context,
input,
notes: [
'The returned context is for a future remote session only; this tool does not spawn a subagent or run Codex.',
],
promptName: 'remote-dev-handoff',
title: 'NapCat remote dev handoff',
});
}
/**
* Builds a non-mutating NAS Codex bootstrap plan placeholder for later automation tasks.
* @param input - Trusted NAS workspace/Codex-home hints and optional service name; execution is intentionally not implemented in Task 5.
* @returns A dry-run bootstrap checklist and read-only verification commands.
*/
export function buildNasCodexBootstrapPlan(input: NasCodexBootstrapPlanInput) {
const artifactRoot =
input.artifactRoot?.trim() ||
`${napcatAutomationSkeletonArtifactRoot}/nas-codex-bootstrap`;
const context = {
codexHome: input.codexHome ?? '/vol1/docker/kt-codex/home/.codex',
serviceName: input.serviceName ?? 'kt-codex-automation',
workspaceRoot: input.workspaceRoot ?? defaultNapcatWorkspaceRoot,
};
const requestedExecute = input.execute === true;
return {
artifacts: [
{
path: joinNapcatArtifactPath(artifactRoot, 'nas-codex-bootstrap-plan.md'),
type: 'markdown',
},
{
path: joinNapcatArtifactPath(artifactRoot, 'nas-codex-bootstrap-context.json'),
type: 'json',
},
],
commands: [
{
command: 'ssh nas "command -v codex && codex --version"',
description: 'Read the trusted NAS Codex CLI version without installing or changing files.',
readOnly: true,
},
{
command:
'ssh nas "test -d /vol1/docker/kt-codex/workspace/KT && git -C /vol1/docker/kt-codex/workspace/KT status --short --branch"',
description: 'Read the trusted NAS KT workspace state without syncing or committing.',
readOnly: true,
},
],
context,
execute: false,
notes: [
'Task 5 only exposes a dry-run bootstrap plan. Task 6 must implement real NAS bootstrap logic before any mutation is allowed.',
requestedExecute
? 'execute=true was requested, but no bootstrap mutation is performed by this placeholder.'
: 'execute defaults to false.',
],
requestedExecute,
summary: 'NAS Codex bootstrap dry-run plan generated.',
};
}
/**
* Quotes a POSIX shell path argument for NAS-side Codex automation commands.

View File

@ -189,6 +189,40 @@ export interface NapcatDeviceProfileCheckInput {
project?: string;
}
export interface NapcatPromptToolInput {
artifactRoot?: string;
candidateBranch?: string;
execute?: boolean;
forkBranch?: string;
forkRepo?: string;
upstreamReleaseTag?: string;
upstreamRepo?: string;
useCodex?: boolean;
workspaceRoot?: string;
}
export interface NapcatSyncCandidateReviewInput extends NapcatPromptToolInput {
lastAcceptedUpstreamBase?: string;
}
export interface NapcatRuntimeReleaseReadinessInput extends NapcatPromptToolInput {
apiImageTag?: string;
napcatImageTag?: string;
profile?: string;
}
export interface NapcatRemoteDevHandoffInput extends NapcatPromptToolInput {
targetHost?: string;
}
export interface NasCodexBootstrapPlanInput {
artifactRoot?: string;
codexHome?: string;
execute?: boolean;
serviceName?: string;
workspaceRoot?: string;
}
export interface BlockerResolutionInput {
attempts?: number;
command?: string;