diff --git a/README.md b/README.md index 4befb3a..f1d8013 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 的短轮询、API Pod SSH 日志读取超时和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。 +- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、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 的短轮询、API Pod SSH 日志读取超时及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 | +| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、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 b384b88..8761cf9 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -162,6 +162,11 @@ export async function runSelfTest(): Promise { "private async waitForPasswordLoginStatus(container) {", " latestStatus = await this.getLoginStatus(container, true);", "}", + "async status(sessionId) {", + " if (session.captchaUrl) {", + " return this.failCaptchaLogin(session, container, status.loginError);", + " }", + "}", "private async tryPasswordRelogin(session, container) {", " await this.containerService.ensureRuntimeLoginEnv(container, { loginPassword: 'x' });", " session.lastRestartedAt = Date.now();", @@ -178,6 +183,7 @@ export async function runSelfTest(): Promise { "qqbot-napcat-captcha-error-throws-before-pending", "qqbot-napcat-captcha-log-single-read", "qqbot-napcat-captcha-status-before-log-url", + "qqbot-napcat-captcha-status-clears-pending", "qqbot-napcat-captcha-log-window-late", ]) { if (!qqbotCaptchaFlowFindings.some((item) => item.category === category)) { @@ -195,6 +201,13 @@ export async function runSelfTest(): Promise { " await this.detectPasswordCaptchaUrl(container, sinceMs);", " }", "}", + "async status(sessionId) {", + " if (session.captchaUrl) {", + " if (this.isPasswordCaptchaStillRequired(status)) {", + " return this.keepPasswordCaptchaPending(session, session.captchaUrl, status.loginError);", + " }", + " }", + "}", "private async detectPasswordCaptchaUrl(container, sinceMs, allowTailFallback = true) {", " await this.containerService.detectRuntimeCaptchaUrl(container, sinceMs);", " await this.containerService.detectRuntimeCaptchaUrl(container);", @@ -222,7 +235,8 @@ export async function runSelfTest(): Promise { qqbotCaptchaFlowValidFindings.some( (item) => item.category === "qqbot-napcat-captcha-log-window-late" || - item.category === "qqbot-napcat-captcha-status-before-log-url", + item.category === "qqbot-napcat-captcha-status-before-log-url" || + item.category === "qqbot-napcat-captcha-status-clears-pending", ) ) { throw new Error("QQBot captcha flow valid self-check produced false positive"); diff --git a/src/tools/review.ts b/src/tools/review.ts index ab029fa..70f42fb 100644 --- a/src/tools/review.ts +++ b/src/tools/review.ts @@ -386,11 +386,16 @@ export function findQqbotNapcatCaptchaFlowFindings( const waitCaptchaLine = lines.findIndex((line) => /private\s+async\s+waitForPasswordCaptchaUrl\s*\(/.test(line), ); + const statusLine = lines.findIndex((line) => + /async\s+status\s*\(\s*sessionId/.test(line), + ); const passwordReloginLine = lines.findIndex((line) => /private\s+async\s+tryPasswordRelogin\s*\(/.test(line), ); const waitBody = waitLine >= 0 ? lines.slice(waitLine, waitLine + 90).join("\n") : ""; + const statusBody = + statusLine >= 0 ? lines.slice(statusLine, statusLine + 90).join("\n") : ""; const passwordReloginBody = passwordReloginLine >= 0 ? lines.slice(passwordReloginLine, passwordReloginLine + 140).join("\n") @@ -483,6 +488,23 @@ export function findQqbotNapcatCaptchaFlowFindings( }); } + if ( + statusBody.includes("session.captchaUrl") && + !statusBody.includes("isPasswordCaptchaStillRequired(status)") + ) { + findings.push({ + category: "qqbot-napcat-captcha-status-clears-pending", + file: context.file, + line: statusLine + 1, + level: "P2", + message: + "NapCat 验证码 pending 后,状态轮询可能把仍需验证码的无 URL 状态误判为失败并清掉 captchaUrl。", + project: context.project, + suggestion: + "status() 在 session 已有 captchaUrl 且当前状态仍提示需要验证码/安全验证时,应继续 keepPasswordCaptchaPending,而不是 failCaptchaLogin。", + }); + } + const logAnchorMatch = passwordReloginBody.match( /const\s+([A-Za-z0-9_]*LogSinceMs)\s*=\s*Date\.now\(\)[\s\S]*?ensureRuntimeLoginEnv/, );