diff --git a/README.md b/README.md index 7cd4509..4befb3a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ - 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。 - 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。 - 检查 env 策略和变更风险:区分后端真实 env 与前端客户端 `.env*`,提醒锁文件、核心表格组件、API 时间序列化 KtDateTime 列/DTO 装饰器入口、部署链路和 Vue TSX 插槽写法等高风险改动。 -- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。 +- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、API Pod SSH 日志读取超时和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。 - 生成或写入 `TASKS.md` 最近记录:默认 `dryRun=true`,确认后再落盘。 - 生成提交前检查清单:校验 KT commit message 约定。 @@ -106,7 +106,7 @@ 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 重型服务单测导入风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 | +| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、API Pod SSH 日志读取超时及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 | | `pnpm run obsidian-context` | 输出 Obsidian 索引上下文;可传 `--module Admin`、`--query QQBot`、`--max-documents 10`。 | | `pnpm run obsidian-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 | | `pnpm run obsidian-sync` | 审计 Obsidian 工作流入口是否连通,并联动执行 validate。 | diff --git a/src/selfTest.ts b/src/selfTest.ts index f952c0a..b384b88 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -227,6 +227,51 @@ export async function runSelfTest(): Promise { ) { throw new Error("QQBot captcha flow valid self-check produced false positive"); } + const qqbotCaptchaLogTimeoutFindings = findQqbotNapcatCaptchaFlowFindings( + [ + "async detectRuntimeCaptchaUrl(runtime, sinceMs) {", + " const result = await this.runProcess(", + " 'ssh',", + " [...this.getSshArgs(), 'sh -s'],", + " script,", + " undefined,", + " this.getRuntimeCheckTimeoutMs(),", + " );", + "}", + ], + { + file: "src/qqbot/napcat/qqbot-napcat-container.service.ts", + project: "Node/kt-template-online-api", + }, + ); + if ( + !qqbotCaptchaLogTimeoutFindings.some( + (item) => item.category === "qqbot-napcat-captcha-log-timeout-too-short", + ) + ) { + throw new Error("QQBot captcha log timeout self-check failed"); + } + const qqbotCaptchaLogTimeoutValidFindings = + findQqbotNapcatCaptchaFlowFindings( + [ + "async detectRuntimeCaptchaUrl(runtime, sinceMs) {", + " const result = await this.runProcess(", + " 'ssh',", + " [...this.getSshArgs(), 'sh -s'],", + " script,", + " undefined,", + " this.getCaptchaLogReadTimeoutMs(),", + " );", + "}", + ], + { + file: "src/qqbot/napcat/qqbot-napcat-container.service.ts", + project: "Node/kt-template-online-api", + }, + ); + if (qqbotCaptchaLogTimeoutValidFindings.length) { + throw new Error("QQBot captcha log timeout valid self-check false positive"); + } const reviewCliParser = { dashedAll: parseGlobalReviewCliArgs([ diff --git a/src/tools/review.ts b/src/tools/review.ts index 038bb08..ab029fa 100644 --- a/src/tools/review.ts +++ b/src/tools/review.ts @@ -336,6 +336,35 @@ export function findQqbotNapcatCaptchaFlowFindings( }, ): ReviewFinding[] { const normalized = context.file.replaceAll("\\", "/"); + if ( + context.project === "Node/kt-template-online-api" && + normalized === "src/qqbot/napcat/qqbot-napcat-container.service.ts" + ) { + const detectLine = lines.findIndex((line) => + /async\s+detectRuntimeCaptchaUrl\s*\(/.test(line), + ); + const detectBody = + detectLine >= 0 ? lines.slice(detectLine, detectLine + 45).join("\n") : ""; + const findings: ReviewFinding[] = []; + if ( + detectBody && + (detectBody.includes("this.getRuntimeCheckTimeoutMs()") || + !detectBody.includes("this.getCaptchaLogReadTimeoutMs()")) + ) { + findings.push({ + category: "qqbot-napcat-captcha-log-timeout-too-short", + file: context.file, + line: detectLine + 1, + level: "P2", + message: + "NapCat 验证码日志读取复用了 5 秒运行态快检超时,API Pod 到 NAS 的 SSH 读日志可能被提前杀掉。", + project: context.project, + suggestion: + "detectRuntimeCaptchaUrl 使用独立的验证码日志读取超时,默认不要低于 15 秒,避免 proofWaterUrl 已存在但被 SSH 超时吞掉。", + }); + } + return findings; + } if ( context.project !== "Node/kt-template-online-api" || normalized !== "src/qqbot/account/qqbot-napcat-login.service.ts" diff --git a/src/tools/workflow.ts b/src/tools/workflow.ts index 0afc5b0..ef4cd3c 100644 --- a/src/tools/workflow.ts +++ b/src/tools/workflow.ts @@ -623,6 +623,7 @@ export function buildComponentWorkflow( "密码登录触发验证码时保持 scan session pending,Admin 通过 TencentCaptcha 拿 ticket/randstr/sid,再由 API 调用同一 NapCat 容器 /api/QQLogin/CaptchaLogin", "密码登录验证码日志窗口必须从 NAPCAT_QUICK_PASSWORD 环境重建前开始,覆盖重建容器和后续 restart,避免 proofWaterUrl 被清理前漏抓", "如果验证码状态先于 Docker 日志 URL 到达,必须短窗口轮询当前容器日志拿 proofWaterUrl,再进入清理或失败兜底", + "API Pod SSH 读取验证码 Docker 日志不能复用 5 秒运行态快检超时,默认读取超时不得低于 15 秒", "验证码 pending、cancel、expire、success 和失败终态都必须清理临时密码环境变量,清理失败要阻断成功或二维码兜底", "SSE 返回 step/status/qrcode/error", "容器重启只在必要时触发,优先直接通信 NapCat API", @@ -633,6 +634,7 @@ export function buildComponentWorkflow( "docker restart 后 WebUI CheckLoginStatus/GetQQLoginInfo 验证 isLogin/online/uin;失败时检查 NapCat 日志是否为无历史登录记录、密码登录失败或需扫码验证", "密码验证码触发时用 watcher 或 scan/status 证明 API 返回 captcha pending 且不暴露完整 proofWaterUrl", "验证码状态先到但 URL 滞后时,Jest 需要证明短轮询后仍保持 captcha pending,不进入二维码或 error", + "NAS 本地日志已有验证码但 API 仍无 captchaUrl 时,必须检查 API Pod SSH 日志读取是否超时", "scan/events 能看到 quick-login-start/quick-login-fallback/password-login-start/password-login-fallback/重置/生成二维码/等待扫码/成功或失败", "目标 Jest 覆盖 quick 成功、password 成功、password 失败进二维码、验证码 CaptchaLogin 回传、密文保存、容器 env 注入和清理失败阻断", "手动刷新不超时",