diff --git a/README.md b/README.md index 7fa2777..141b678 100644 --- a/README.md +++ b/README.md @@ -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 plan,Task 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`。 | diff --git a/package.json b/package.json index 3b24262..a592dfa 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/core/cli.ts b/src/core/cli.ts index 3f53ba6..76e10a6 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -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(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'), diff --git a/src/core/constants.ts b/src/core/constants.ts index dec1985..57bf3fd 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -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', diff --git a/src/registerTools.ts b/src/registerTools.ts index c76c52b..cbb66c9 100644 --- a/src/registerTools.ts +++ b/src/registerTools.ts @@ -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-'), + 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 plan;Task 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', { diff --git a/src/selfTest.ts b/src/selfTest.ts index e62d7d1..9164817 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -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 { 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"); } diff --git a/src/server.ts b/src/server.ts index ee0719d..bec1655 100644 --- a/src/server.ts +++ b/src/server.ts @@ -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')) { diff --git a/src/tools/napcatAutomation.ts b/src/tools/napcatAutomation.ts index 892d6d9..31fd767 100644 --- a/src/tools/napcatAutomation.ts +++ b/src/tools/napcatAutomation.ts @@ -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; + 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-', + candidateBranch: input.candidateBranch ?? defaultNapcatCandidateBranch, + forkRepo: input.forkRepo ?? defaultNapcatForkRepo, + napcatImageTag: input.napcatImageTag ?? 'kt-napcat-desktop-cn:', + 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. diff --git a/src/types.ts b/src/types.ts index 8dd0c18..d8be3f1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -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;