MEMORY.md 14 KB

zsjz-ai 项目长期记忆

本机环境怪癖(每次会话都会遇到)

  • bash 工具可用(2026-09-24 起实测 ls/cat/grep/sed/find/rm/cp/comm/diff 都正常); 早期会话的「shim 报 dirname not found」已不复现。仍优先用 Glob/Grep/Read 读文件。
  • PowerShell 工具的 stdout 不回显:要拿输出必须落盘再读。落盘务必用 ... | Out-File -FilePath <f> -Encoding utf8;> 会写成 UTF-16,Read 工具会当二进制拒绝。
  • Remove-Item 被 safe-delete 钩子拦截 → 删文件用 [System.IO.File]::Delete('绝对路径');bash rm -f 可以。
  • 仓库根 = 主仓库 E:\workspace\zsjz-ai(git common dir);当前 worktree 是 C:\Users\cc\WorkBuddy\Worktrees\zsjz-ai\dev-3-*。worktree 只有被跟踪的文件, 被 gitignore 的本地目录不会带过来(见下),缺东西时去主仓库拷。

worktree 首次可用的初始化(2026-09-24 踩实)

  1. frontend/ 是 pnpm 项目:pnpm install 会因 esbuild postinstall 报 spawnSync node.exe EBUSY(沙箱里孙进程 spawn node 被拦),而且失败后 node_modules/.bin 不会生成,于是 pnpm type:check / build 全报「不是内部或外部命令」。 → 解法:pnpm install --ignore-scripts。esbuild 真二进制在 @esbuild/win32-x64, postinstall 只是 --version 校验,跳过无害(实测 require('esbuild').version 正常)。
  2. ai-electron/ 是 npm 项目(只有 package-lock.json,node_modules 是扁平结构、无 .pnpm): 不要用 pnpm 装(会因 esbuild@^0.28.0 解析失败 —— 镜像元数据里 latest 还是 0.27.4)。 正确姿势:从主仓库拷 ai-electron/package-lock.json(被 gitignore 了),再 npm install --ignore-scripts --no-audit --no-fund(约 23s / 710 包)。 校验命令:npx tsc --noEmit(= npm run typecheck)、npm test(vitest,91 用例)、 npm run build-electron(产出 public/electron/main.js)。
  3. 被 gitignore 误伤、worktree 里没有、缺了直接构建失败的目录(从主仓库 cp -r 过来,不会污染 git status):
    • ai-electron/frontend/build/(根 .gitignore:22 的 build/ 命中;vite.config.ts 要 ./build)
    • ai-electron/frontend/src/case/views/data/(ai-electron/.gitignore:7 的 data/ 命中, 把源码目录也误伤了;路由 routes/index.ts 直接引用,51 个文件)
    • ai-electron/package-lock.json
  4. 其余 5600+ 条「主仓库有、worktree 没有」都是运行时垃圾(data/、out/、logs/),不用管。

前端(ai-electron/frontend/)

  • 技术栈:Vue 3 + <script setup lang="ts"> + ant-design-vue + vite + less + pnpm。
  • 常用命令(cwd 一律在 ai-frontend/):
    • 构建:pnpm build(= vite build --mode production,不做类型检查,约 2 分钟)
    • 类型:pnpm type:check(= vue-tsc --noEmit --skipLibCheck,约 25 秒)
  • pnpm type:check 本来就过不了:仓库有约 89 条既有报错,集中在 src/call/、src/trans/、src/graph/、src/otg/、src/person/ 等无关文件。 判断自己有没有写坏,看的是「报错文件里有没有自己动的文件」,不是退出码。
  • tsconfig.json:isolatedModules: true,无 verbatimModuleSyntax;noUnusedLocals: false(所以多余的 import 不报错)。
  • 暗色主题的既有缺陷:vite-plugin-theme-vite3 生成的 dist/assets/app-antd-dark-theme-style.*.css 里,自定义属性的 -- 前缀被抹掉 (搜 --codex-bg / --ai-text 都是 0 处,只有 codex-bg / ai-text)。 即暗色模式下 --ai-* / --codex-* / --jeesite-* 都不会被覆盖,页面沿用浅色 :root 值 (antd 类名那部分正常)。全项目同等影响,v2.1 之前就存在;修它要动构建期主题插件,未动。
  • 构建时那条 WARN warnings when minifying css: Invalid character(s) '@charset UTF-8"; 是同一个 主题插件的产物拼接问题,同样与业务代码无关。
  • 路由 / 别名:@/* → src/*;路由表在 src/core/router/routes/index.ts。
  • less 全局变量靠 build/theme/modifyVars.ts 里的 hack: 'true; @import (reference) "src/core/design/var/index.less";' 注入, 所以任意 .vue style 块和任意 .less 文件都能直接用 @modal-header-bg-color 这类变量, SFC 的 style 抽成独立 .less 再 @import 回来是安全的。
  • ant-design-vue 的 Modal / Popover / Select 会 teleport 到 body,样式必须全局(非 scoped), 靠 wrapClassName + 全局类名命中。

新增页面 / 菜单入口机制(纯前端,不走后端)

  • 菜单不在后端:来自本地 src/core/store/modules/menu.json(permission.ts 直接 import 它当 RootRoute)。 加页面 = 往对应父分组 children 里塞一条(path/component/url 三者同值,name 形如 ViewsXxxYyy,id 用递增号)。
  • 视图映射在 src/core/router/helper/routeHelper.ts 的 explicitDynamicViewMap(显式登记优先于 glob 兜底), 格式:'/xxx/yyy': () => import('@/xxx/views/yyy/index.vue')。
  • 页面外壳沿用 .ai-page 那套反冲手法:width: calc(100% + 20px); margin: 0 -10px;,留 2px 灰底 gutter。

Codex 数据分析新包(src/codex/,2026-09-24 起)

  • 需求:全新包、不复用 src/ai/ 聊天代码、所有文件 codex 前缀(含视图入口 views/codexAnalysis/codexAnalysis.vue)。
  • 计划文档:仓库根 codex-analysis-plan.md(v1.0.0 → v1.9.0,逐版本停下 review)。
  • v1.0.0 ~ v1.9.0 已全部落地(骨架 / 会话 / 流式对话 / 富内容块 / 图表关系图 / 过程与审批 / 产物 / 报告导出 / 恢复与诊断 / 打磨)。前端 31 个文件全 codex 前缀;主进程新增 codexArtifactService.ts + codexArtifactCtl.ts(root 白名单落盘 + 路径穿越校验 + 只读不删), codexCtl.ts 增量:threadStart 返回 cwd、turnRun 登记可选 cwd、新增 defaultWorkspace。
  • 关键实现约定:事件按 threadId 缓存(同 id 覆盖、isDelta 追加);turn/completed 决定回合结局; 围栏协议 echarts/graph/table/json/report,解析失败回退成代码块;导出 md/html/xlsx/长图(png); HTML 报告里的图表靠 utils/codexVisualRegistry.ts(内容哈希 → 图片提供者)内嵌真实图片。
  • 工作目录优先级(v2.0.5 起):会话 cwd → 该案件的手动覆盖(cwdOverrides) → 案件工作空间 → 客户端默认。 案件工作空间 = <user.dir>/QingJian/workspace/<案件ID>,由后端 CaseInfo.workspacePath 回传 (@TableField(exist = false),CaseInfoService.fillWorkspacePath 在 open/current/list 返回前置值)。
  • 验证口径:前端 pnpm type:check(89 条基线、本包 0 报错)+ pnpm build(exit 0); 主进程 npx tsc --noEmit + npm test(91 用例)+ npm run build-electron; 后端 mvn -o -DskipTests compile(仓库根,多模块,ai-server 是子模块)。
  • 基座事实:主进程 electron/controller/codexCtl.ts + service/codex/* 已全量可用;IPC 封装现在 唯一实现在 src/codex/api/codexApi.ts,src/ai/api/codexApi.ts 只剩一行 export *。渲染唯一输入是 AgentEvent(映射见 service/codex/eventMapper.ts)。
  • 协议参考:官方 https://developers.openai.com/codex/app-server;本地 schema 全集在 E:\workspace\ai\Noobi.ai\schemas\codex\v2\*.json。布局可参考 Noobi.ai(React),但样式必须用本项目主题。

cleaning 页拆分工程(src/case/views/data/cleaning.vue)

当前进度(2026-09-18):12029 → 944 行(−92%),30+ 新文件,type:check 0 报错 + build 通过。 依赖顺序(单向、无环):useCaseDataCleaningShared(底层状态)→ useCleaningRules / useFieldMapping / useRecognitionRules / useTemplateLibrary → useCaseDataCleaningActions(叶子动作域,放在最后所以能直接用上游产物)。 剩余(压到 ≤400):① 把顶层 watch([...]) 移进 composable(watcher 在 composable 内注册仍绑定页面实例)→ 21 个退回声明可搬走; ② 清未使用 import(匹配标识符要允许 ... 展开);③ useCaseDataCleaningActions(1310) 可按域再拆成 useFileTree / usePreviewData / useColumnOps。

  • 消环的唯一正确姿势:底层共享状态收进唯一一个 shared composable,其余域全部单向依赖它(+ 已有 handle); 叶子域一定要放在所有上游之后。不要用「多个平级 composable 互相注入」——底层状态同时被多方需要,会形成真实的环, 拓扑排序无解;破环退化成空壳 composable 就是垃圾。
  • 生成器可复用件(写这类脚本直接照搬):

    • 声明解析用「找下一个顶层起点」,顶层起点要包含 watch( / provide( / onMounted( 等非声明的顶层语句,否则上一个声明的范围会吞掉它们
    • 模板取词:{{ }} + : / v- / @ / ref 绑定值 + 标签名;表达式剥掉 '…'/"…" 但保留 ${...} 插值
    • 抽出的 .ts 补 export;跨文件相对路径按新层级重算;deps 类型显式写 interface(否则类型循环 → Object.entries(any) → [string, unknown][])
    • 接线模式:页面在 setup 中实例化 composable + 同名解构(页面代码/模板零改动),provide 放在 setup 末尾; wiring 插在「最后一个依赖声明」之后(锚点是依赖声明的最后一行)
  • 方案文档:仓库根 cleaning-refactor-plan.md。已拍板:路由不动(cleaning.vue 留作入口)、 通信走 provide/inject + 领域 composable、样式保持全局 less 非 scoped、业务逻辑一律不许动、 每阶段停下来 review。

  • 目录:cleaning/{types,constants,context}.ts + cleaning/{utils,composables,components,styles}/

  • 沟通模式(Phase 3 起确定的主力模式,后续阶段照这个来):

    • 领域逻辑放 composables/useXxx.ts,由页面在 setup 里实例化
    • 页面用同名解构接住返回值(const { a, b } = useXxx(...))→ 页面原有代码与模板零改动
    • 页面把 useXxx 实例放进 provide(CleaningContextKey, { ..., xxx }),子组件从 context 取
    • provide() 放在 setup 末尾(避开 const 声明顺序的 TDZ);composable 的调用位置 = 「最后一个依赖声明之后」
    • composable 的 deps 必须显式声明类型(写 interface),不要让 TS 从实参反推 —— 页面里有 decl 的签名依赖返回值时 会形成类型循环,导致 deps 退化成 Record<string, any>,而 Object.entries(<any>) 会产出 [string, unknown][] 引发一片 unknown 报错
  • 验证必须 type:check + build 双跑:vue-tsc 抓不到 Vue 模板标签不平衡, 抽块边界写错(多删一个 </div>)时 type:check 是 0 报错、pnpm build 才报 Element is missing end tag。 抽模板块时边界要取「配对的 </div> 行」,不要凭缩进猜(这个文件缩进本身不严格)。

  • 已踩实的坑:

    • 抽出的 .ts 必须补 export(无 import/export 的 .ts 会被 TS 当全局脚本 → TS2306)
    • 抽出文件引用原页面的相对路径 import 时,要按新层级重算(@/ 别名例外)
    • 页面级 let/var 标量不能注入:跨文件是按值快照,两边各改一份(请求序号守卫之类会失效)
    • 声明解析要用「找下一个顶层起点」,不要用括号深度(多行泛型 ref<\n Record<...> 会让深度中途归零、把声明切一半)
    • 模板取词要保留 ${...} 插值,只剥 '…' / "…"(整段 strip 会把 `is-${x}` 里的 x 抹掉)
    • 判定「setup 顶层立即求值」时只扫 wiring 位置之前的行(末尾的 provide(...) 也是顶层语句,会假阳性); watch([...refs]) 整条都算立即求值;「可搬集合 ↔ wiring 位置」要迭代到收敛
    • 算「页面还要解构哪些名字」时,扫描范围 = 整份模板 minus 被抽走的块(别漏掉保留在页面里的部分,比如面板里的匹配条/外层 wrapper)
    • 依赖若是页面里 const { x } = someComposable; 解构出来的,类型要写成 ReturnType<typeof someComposable>['x'](否则退化成 any → Object.entries(any) → unknown); 依赖类型文本里出现的类型名(Ref<TemplateTab> 里的 TemplateTab)也要补 import
  • 验证「重构没改业务逻辑」的可靠做法:拿 git show HEAD:<原文件> 当基准,和「当前入口 + 抽出目录整棵树」做 逐行多集合比对(归一化:去 export 前缀 / 折叠空白 / 去行尾 ,;,跳过注释与 import), 判定条件 = 原始每行在当前树里的出现次数 ≥ 原始次数。本项目已验证 script 丢失/被改动 = 0。 注意:扫描范围只圈自己那一棵(别把同目录其它页面算进来)、SFC 的 template 结束点是 <script 不是 <style、 取原始文件用 node execSync(...,{encoding:'buffer'})(PowerShell > 会写成 UTF-16,且 cmd /c 被拦)。

用户偏好

  • 直接干活,不要反复审计。
  • 业务逻辑不许动:重构只做搬家/抽取,逐行原样搬运。
  • 每个阶段停下来给 review,确认后才进下一阶段。
  • 大文件重构:先给方案文档(会被认真审),方案里要列清决策点。
  • 别生成一堆垃圾文档,编译/构建通过就是完成标准。
  • 不要留临时脚本、临时日志在仓库里,做完自己清理。