diff --git a/.gitignore b/.gitignore index 0ef1819..94de4ed 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,8 @@ Thumbs.db # WorkBuddy 自动化任务运行数据(本地运行时生成,不提交) .workbuddy/automations/ # 工作台运行日志与 CDP 自检截图(本地产物,不提交) -**/mcn-work-shop/server.log -**/mcn-work-shop/shot-*.png +**/mcn-workshop/server.log +**/mcn-workshop/shot-*.png # 工作台本地数据库(含账号真实数据,不入库;根目录 0 字节残留同规则) mcn-plugin.db **/mcn-plugin.db diff --git a/.workbuddy/memory/2026-09-29.md b/.workbuddy/memory/2026-09-29.md index 8e1bebd..b97a179 100644 --- a/.workbuddy/memory/2026-09-29.md +++ b/.workbuddy/memory/2026-09-29.md @@ -368,3 +368,150 @@ codebuddy -p → EADDRINUSE 后卡死,超时零输出 ### 清理 本轮 91 个临时文件(`_*.py`/`_*.log`/`_*.txt`/`_debug/` 等,均在根目录与 mcn-work-shop 下)已全部分批删除。 + +--- + +## 18:45-19:30 A 方案落地完成 + 联网搜索接入(实测全通过) + +### ✅ 结果:工作台 AI 链路已切到本地 CLI,可用 +用户提供 API Key 后,端到端全部跑通: +`/api/ai/status` → ready:true / `?probe=1` → ok:true / `/api/ai/clarify`(SSE) → 正常出内容且格式合规(`==OPTS==/==REQ==` 完整)。 + +### 🔐 凭据存放(重要,防泄密) +- **`mcn-work-shop/config.json` 是 git 跟踪文件** → ⛔ 绝不能把 key 写进去。 +- 凭据落在**仓库之外**:`%USERPROFILE%\.workbuddy\mcn-work-shop\ai-cli.json`(`{"apiKey":"..."}`)。 +- 解析优先级:`config.apiKey/authToken` > **凭据文件** > 环境变量 `CODEBUDDY_API_KEY/AUTH_TOKEN`。 +- `server.js` 新增 `cliBaseOpts()` 统一构造调用参数;新增环境变量覆盖开关 **`MCN_AI_MODEL`**(不改配置做模型 A/B)。 + +### ⭐⭐ doubao 不可用(钉死结论) +- `custom:doubao-seed-2-1-pro-260628` → **500**;`custom:doubao-seed-2-0-lite-260428` → **400 `Custom model [...] service info not found`**。 +- CLI `--help` 的模型列表是**二进制静态清单,≠ 已开通**。桌面产品配置 48 个模型里没 doubao;`workbuddy.db` 历史只用过 `deepseek-v4.1-flash`/`hy4-preview`。 +- **但 `custom:` 系并非全不可用** —— 同租户下有一大批已开通的自定义槽位。 + +### ⭐⭐ `custom:custom-model-*` 槽位 → 真实模型名映射(实测逐个跑出) +| 槽位 | 真名 | +|:--|:--| +| `a1` / `a2` / `a3` / `a4` | Claude-Opus-4.7 / Sonnet-4.6 / Opus-4.6 / **Opus-4.8** | +| `b1-standard` / `b1-priority` | GPT-5.4-Standard / GPT-5.4-Priority | +| `b3-priority` | GPT-5.2-Priority | +| `b4-standard` / `b4-priority` | GPT-5.1-Standard / GPT-5.1-Priority | +| `b5-standard` / `b5-priority` | **GPT-5.5-Standard / GPT-5.5-Priority** | +| `c2-flash` | Gemini-3.5-Flash-1 | + +> **取证方法**(可复用):CLI 日志 `%TEMP%\mcn-cli-home\logs\<日期>\*.log` 里会有 +> `"requestModelId":"custom:...","requestModelName":"Claude-Opus-4.8"` 配对 → 正则一次提取全部映射。 +> 直接问模型「你是谁」不可靠(a1~a4 会统一答「我是AI智能编程助手」)。 + +### ⭐⭐ 三个性能杠杆(实测数据,都已在代码里落地) +| 杠杆 | 做法 | 效果 | +|:--|:--|:--| +| **工具面收敛** | `--tools "WebSearch,WebFetch"` | prompt **21,776 → 4,859 tokens(-78%)**,**47.6s → 15.0s** | +| **推理档位** | `--effort low`(新增,`minimal\|low\|medium\|high\|xhigh\|max`) | 首字 **61.7s → 19.7s** | +| **换模型** | `deepseek-v4-pro` 46.2s → `custom:custom-model-a4` 18.5s | 快 2.5 倍 | + +- ⚠️ **`[INIT tools]` 事件列的是工具注册表全量,不是启用集** —— 判断 `--tools` 是否生效要看 `usage.input_tokens`,别被事件列表误导(本轮差点误判成"白名单没生效")。 +- 附带安全收益:工具面收敛后模型**物理上碰不到文件系统**(Bash/Read/Write/Edit 全不在白名单)。 + +### ⭐ 联网搜索:CLI 原生自带,开箱可用 +- `--tools` 里保留 `WebSearch,WebFetch` 即可;实测真的发起 `WebSearch {"query":...,"freshness":"d1"}`。 +- **但不能常开也不能常关** → 用 `CLI_SEARCH_HINT` 作 `--append-system-prompt` 注入判据: + 该搜(今天/近期的外部事实、热点、平台规则、事实核查)/不该搜(创作讨论、需求澄清、脚本撰写)/ + **搜索预算硬上限 2 次**(1 WebSearch + 必要 1 WebFetch),搜到即答禁止反复搜。 +- 教训:第一版提示词只写"最多搜 2 轮",模型实际连搜 4 次把耗时推到 3 分钟 → 改成显式预算 + `maxTurns: 6` 硬顶。 + +### ⭐ 两个文本污染(必须处理) +1. **报错当回答**:未认证时 CLI 把报错走文本通道发出 → `isCliBoilerplate()` 拦截。 +2. **工具调用前的过程话术**(本轮新发现):模型搜前先吐一句 + `I'll search for ... before responding.` → 被当正文显示。 + **修法**:`runCli` 记录每次 `tool_use` 的位置(`toolMarks`),**只保留最后一次工具调用之后的文本**; + 因为这段已流式推给前端,`server.js` 成功分支比对 `r.text !== acc` 时补发 `{replace}` 整体覆盖。 + (前端 `data-pages.js` L653-672 对未知事件类型是 `continue` 安全跳过 → 加事件类型不破坏兼容。) + +### ⭐ 本轮新识别并清掉的宿主干扰 +- **宿主 MCP 注入**:`CODEBUDDY_MCP_CONFIG` 会在子 CLI 里自动挂上 `weixinpay` + `sheetagent` 两个 MCP, + 纯浪费启动时间还可能被误调 → `buildChildEnv` 删该变量 + 传 `--strict-mcp-config --mcp-config '{"mcpServers":{}}'`。 + 实测 `[INIT mcp]` 从 2 个变成 `[]`。 +- 另删 `CODEBUDDY_GATEWAY_PASSWORD`/`CODEBUDDY_GATEWAY_AUTH`/`WORKBUDDY_PAC_RPC_*`。 + +### 当前配置 +`config.json → ai`:`backend:"cli"`,`model:"custom:custom-model-a4"`(Claude-Opus-4.8), +`effort:"low"`,`search:true`,`tools:""`,`strictMcp:true`,`maxTurns:6`。 + +### 遗留 / 待办 +- **需求打磨单轮耗时仍 60-120s**(首字已降到 ~20s)。总耗时主要在模型推理+输出,属模型特性;若要更快可再换 `b5-standard`(GPT-5.5) 或加 `effort:minimal`。 +- **前端 `sendTurn` 未实测**(本轮只测了后端 SSE;协议未变理论零改动)。 +- 真实账号 + 参考选题走**完整 AI 创作链路**(S1-S11)仍未验证。 +- `flash`/`minimal` 档位对产出质量的影响未评估。 + +--- + +## 19:32-19:40 模型切换:GPT-5.5(用户拍板) + +用户选定 **GPT-5.5**,配置已改为 `custom:custom-model-b5-standard`。 + +### 实测对比(端到端 `/api/ai/clarify`,同一测试用例) +| 模型 | 普通创作问答 | 问热点(带搜索) | 格式合规 | +|:--|:--|:--|:--| +| **GPT-5.5-Standard**(当前) | **42.2s** | 137.2s | ✅ | +| Claude-Opus-4.8 | 105.5s | 139.6s | ✅ | +| DeepSeek-V4-Pro | 119.5s | 151.2s | ✅ | + +- **普通问答快 2.5 倍**(42.2s vs 105.5s)—— 这是最常用的路径,收益最大。 +- **触发搜索后各模型都是 130-150s** → 瓶颈转移到搜索本身,不是模型。 + ∴ `CLI_SEARCH_HINT` 把搜索预算压到 ≤2 次是对的,这是搜索场景唯一有效的提速手段。 +- 首字延迟:普通问答 ~20s;搜索场景 ~67s(因为叙述被切掉了,搜索期间用户只看到"正在思考"占位)。 + +### b5 两个变体的区别 +`custom:custom-model-b5-standard`(标准档,已选)/`custom:custom-model-b5-priority`(优先档, +资源紧张时优先路由,通常积分倍率更高)。b1/b3/b4 同族也是这套 -standard/-priority 后缀。 + +### 本轮验证方法(可复用) +测试脚本自检三件事,比只看耗时更有价值: +1. `==OPTS==` / `==REQ==` 双标记是否齐全(格式合规,前端要靠它解析候选与需求清单) +2. 英文过程话术残留(正则扫整行纯英文 ≥25 字符的段落) +3. 首字延迟 + 总耗时分开记(首字反映推理档位,总耗时反映输出+搜索) + +### 当前最终配置 +`config.json → ai`:`backend:"cli"`,`model:"custom:custom-model-b5-standard"`, +`effort:"low"`,`search:true`,`tools:""`,`strictMcp:true`,`maxTurns:6`; +凭据在 `%USERPROFILE%\.workbuddy\mcn-work-shop\ai-cli.json`。 + +--- + +## 19:40-19:50 切换 deepseek-v4.1-flash + 全流程跑通验证 + +用户要求「改为用 deepseek v4.1 flash 把流程跑通」。配置已改 `custom:custom-model-b5-standard` → **`deepseek-v4.1-flash`**。 + +### ✅ 全流程四环节实测全绿 +| 环节 | 结果 | +|:--|:--| +| `GET /api/ai/status` | backend=cli · model=deepseek-v4.1-flash · ready=**true** | +| `?probe=1` | ok=**true** · 17.0s · 返回「成功」 | +| `POST /api/ai/clarify`(stream) | 371 字 · `==OPTS==/==REQ==` 合规 ✅ · 无英文残留 ✅ · 51.7s(首字 49.7s) | +| `POST /api/run` → 轮询 | queued(5s) → running(30s) → **done**(50s) · `model_id` 落库 = deepseek-v4.1-flash ✅ | + +### ⭐⭐ 关键架构发现:完整流程 = 两段,模型来源互不相干 +| 环节 | 接口 | 谁在跑 | 模型来源 | +|:--|:--|:--|:--| +| 需求打磨(弹窗对话) | `POST /api/ai/clarify` | **本地 CLI**(本方案) | `config.json → ai.cli.model` | +| 提交创作任务(S1-S11 生成脚本) | `POST /api/run` | **WorkBuddy 宿主**(写 automation,桌面端调度器执行) | **`sessions` 表最近活跃「手动会话」的 `model`**,兜底 `auto` | + +- 🔴 **改 `ai.cli.model` 不影响创作任务的模型。** +- 创作任务模型是「跟随用户在 WorkBuddy UI 最近选过的模型」→ 从工作台侧看是**浮动的、不确定的**。 +- 取证 SQL:`SELECT model_id, COUNT(*) FROM automations GROUP BY model_id ORDER BY 2 DESC;` + 本机实测分布:`hy4-preview` **30** 条 / `deepseek-v4.1-flash` **6** 条(我们这次会话是 flash,所以新任务落到 flash)。 +- **想让创作固定用某模型**:在 UI 把当前会话模型切到它;或改造 `/api/run` 支持显式 `model_id`(属行为变更,未做)。 + +### `deepseek-v4.1-flash` 的实测速度(澄清一个反直觉点) +- 首字 **49.7s**,总 51.7s;对比 GPT-5.5 首字 20.0s/总 42.2s。 +- **"flash" 不等于首字更快** —— 它仍是推理型模型;但总耗时接近。选它主要看积分成本。 +- 三个模型(deepseek-v4.1-flash / GPT-5.5 / Opus-4.8)**格式合规都 ✅、都无英文残留**,质量层面可用性一致。 + +### 顺带确认的死代码 +- **`POST /api/ai/topics` 是遗留死代码** —— 前端从不调用(选题列表走 `GET /api/dsh/topics`)。 + 它仍写死 Dify,未配 `DIFY_MCN_CYLG_KEY` 会 500,但**不影响真实流程**。已记入文档 §11。 + +### 清理 +- 自检 automation `automation-1790682160345`(名为「链路自检·deepseek-v4.1-flash(可删除)」) + 已用 `automation_update mode=delete` 走正规通道删除(未用 sqlite 直改)。 +- 临时脚本 `_t_flash.py` 已删。 diff --git a/.workbuddy/memory/2026-10-08.md b/.workbuddy/memory/2026-10-08.md new file mode 100644 index 0000000..714ffa0 --- /dev/null +++ b/.workbuddy/memory/2026-10-08.md @@ -0,0 +1,165 @@ +# 2026-10-08 + +## 会话技能加载 + 项目记忆去重 + +- 按用户指令加载 `session-mechanism`:四类必读已读(`00-动手前必过.md` / `rules.md` / `manifest.md` / `01-文档索引.md`)+ SKILL.md 第一屏(触发句、第 0 步加载门槛、十条禁令、自决策白名单、现状地图)。 +- 内化判据:**边界内自决策**(技术选型/实现路径/命名/调参/部署/排查/版本/兼容降级/文档技术内容);**只提报三类**(功能语义分叉/红线门禁/超边界:花钱·对外承诺·要凭据)。 +- 项目 `MEMORY.md` 去重重组为 v4(17,841 → 17,315 B):新增 §0 两条指针(工作台 AI 通路 `docs/AI链路-本地CLI接入.md`、技能侧 WeKnora 调用);§2.1 补 09-29 AI 通路四条硬红线+两段流程模型来源;§4.2/§4.3 降级为「存根+硬红线」;清历史流水。 + +### 未决 +- ⚠️ `MEMORY.md` 体积仍会被注入截断(阈值约 10–12 KB)。解法二选一:① 维持单文件(细节自动可见,但注入截断)② 拆专题文件(不截断,但细节失去自动注入)。**无客观优劣 ⇒ 待用户定**。 + +## 工作台「更新榜单数据」按钮排查(功能正常,是可见性问题) + +- 现象:用户点击后「没有会话执行」。 +- 取证(四处读数):宿主库 `automations` 有该 once 任务(ACTIVE、`next_run_at` 已过期、`model=hy4-preview`、`skills=["mcn-data-insight"]`);`automation_runs.status=IN_PROGRESS` + `automation_runtime_state.running=1`(⚠️ `running_conversation_id=null` 是常态,不代表没起会话);`sessions` 已建后台会话 `8a085794…`(bg=1,10:07:23);会话 jsonl 283 KB 且 mtime 持续更新(10:14),工具调用 30 Bash/14 Read/4 Write ⇒ **任务真的在跑**。`/api/dsh/ranking-update` 返回 `files=104`、`lastUpdateAt` 10:13:51 ⇒ 数据已在落盘。 +- 真因两条(都与抓取逻辑无关):① 会话 `cwd = …\mcn-work-shop` ⇒ 落在侧边栏 **mcn-work-shop 空间分组**,不在用户当前空间,需切组才可见(这是设计,非 bug);② 工作台服务作为会话后台任务**被回收** ⇒ 前端轮询 `/api/run/status` 持续抛错。 +- 修复:`public/data-pages.js` `updateRankingData()` 轮询加失败计数 —— 连续 3 次查询失败即提示「工作台服务已断开,任务仍在后台执行」并结束轮询(原逻辑静默 `continue` 到 15 分钟超时,用户零感知)。 +- ⚠️ **排查方法论沉淀**:判「后台任务有没有在执行」的正确取證链 = `automations` → `automation_runs` → `automation_runtime_state` → `sessions` → 会话 jsonl 的 mtime/大小。⛔ 别只看 `running_conversation_id`(常为 null,会误判成没执行)。 + +## 工作台目录统一改名 `mcn-work-shop` → `mcn-workshop` + +- 动因(用户):「分组已改为 mcn-workshop,需要全部替换;执行会话任务时需要按分组名称显示在会话列表」。 + 🔑 **机制**:WorkBuddy 侧边栏空间分组名 = 会话 `cwd` 的 **basename** ⇒ **只改字符串无效,必须真改名目录**。 +- 执行五步:① 停服务释放句柄(否则 rename 必 `WinError 5`)→ ② `mv mcn-work-shop mcn-workshop`(成功)→ ③ 临时脚本全仓替换 **`mcn-work-shop`→`mcn-workshop`:37 文件 / 121 处**;⛔ 按**沿革句规矩跳过** `.workbuddy/memory/YYYY-MM-DD.md`(16 个历史日志保留当时目录名史实)→ ④ 修 2 处被替换改坏语义的注释(`server.js` L1265 / L1269,原「旧 mcn-workshop/新建 mcn-workshop」被换成同词,已补回三代路径)→ ⑤ 重启验证:`app.js`/`data-pages.js` 200,`data-pages.js` 含 `pollFail` 修复。 +- ⚠️ **事实澄清(易踩)**:`D:\AI技能\mcn-workshop` **不是工作台**,是 09-01 路径拼接 bug 留下的**垃圾目录**(3 个 0 字节文件 + `outputs`)。工作台本体一直在仓库内 `…\V1.0\mcn-workshop`。sessions 表取证:该垃圾目录下**零会话**;未删除会话中仅 1 条 mcn-work 相关,cwd 仍是旧 `…\mcn-work-shop`。 +- 净效果:新建后台会话 cwd = `…\V1.0\mcn-workshop` ⇒ 侧边栏分组显示 **`mcn-workshop`** ✅ +- 残留:`project/…/subskills/mcn-data-insight/scripts/__pycache__/*.pyc`(二进制缓存含旧串,会自行重生成,无需处理)。 + +### 追加澄清(用户真实意图,⛔ 别再理解成"目录改名") +- 用户要的不是改目录名,而是:**工作台提交 WorkBuddy 定时任务后,产生的 AI 会话应归属哪个「空间分组」**。 +- 机制:分组名 = 会话 `cwd` 的**目录名**;工作台侧由 `server.js` 的 `SESSION_CWD`(当前 = 工作台自身目录)决定 ⇒ **改目录名只是让分组名变成 `mcn-workshop` 的手段**,不是目的本身。 +- ⚠️ **存量不掉头**:10:07 那条榜单更新任务 cwd 仍是旧 `…\mcn-work-shop` ⇒ 显示在**旧**分组;改名后**新建**的任务才落到 `mcn-workshop`。清理用的 `SESSION_CWD_PATTERN='%mcn-work%'` 覆盖新旧三代,不受影响。 +- 待用户定(功能语义分叉):① 维持工作台专属分组 ② 归入用户当前主项目分组 ③ 自定义独立分组(需目录真实存在且在 workspaces 登记,08-27 即此法)。可选项:把 `SESSION_CWD` 提升为 `config.json` 可配字段,换分组只改配置不动代码。 + +## 后台任务进度可见(用户:「我需要看到 才知道执行是否正常」) + +- 真需求:不是分组放哪,而是**在工作台内直接看到执行状态**,判断在跑还是卡住、做了什么。 +- 后端 `server.js` `/api/run/status` 增强:automation → 关联它拉起的后台会话(`sessions.is_background_automation=1` 且创建时间落在任务创建 −15s~+300s)→ 读该会话 jsonl **尾部 256 KB** → 新增字段 + `elapsedMs` / `sessionId` / `sessionTitle` / `lastAction`(最近一段文本,截断 160 字)/ `toolCalls`(按工具名计数)/ `updatedAgoMs` / `stalled`(>90 s 无新输出)/ `logBytes`。 + - ⭐ 会话日志路径换算(可复用):cwd → projects 目录名 = **盘符小写 + 分隔符全转 `-`**(`escapeCwdForProjects()`);基目录 `/projects/<转义名>/.jsonl`。 + - ⚠️ `stalled` 只在 `state=running` 时有意义(已完成任务日志当然不再写入,会恒 true)。 +- 前端新增 `window.TaskProgress`(实现在 `public/app.js`,样式 `style.css` 的 `.tp-*`):右下角常驻面板 = 状态/已运行时长/工具调用/最近动作/停滞警告。**四处轮询点全接入**:`app.js executeTask`、`data-pages.js` 的榜单更新/批量更新账号/视频解析。终态保留 8 秒自动收起,运行超时则保留供继续观察。 +- 验证:三文件 `node --check` 全过;服务起来后 `app.js` 含 6 处、`data-pages.js` 含 9 处 `TaskProgress`,`style.css` 含 `.tp-panel`;状态接口返回完整进度字段。 + +### ✅ 分组归属定案:就用 `mcn-workshop` 文件夹对应的分组(已实测) +- 用户拍板:任务会话归到 **`mcn-workshop` 文件夹对应的那个分组**。 +- 实测(提交轻量自检任务取证):新会话 `cwd = …\V1.0\mcn-workshop` ✅;对照 10:07 那条旧榜单任务仍是 `…\mcn-work-shop`(**两者在侧边栏分属不同分组**,属正常存量,不用管)。 +- 结论:**不需要再改代码** —— `SESSION_CWD = ROOT`,目录改名后自动生效。自检任务已删排期。 +- ⚠️ 认知要点:侧边栏分组按 **完整 cwd** 聚合,不是按文件夹名合并 ⇒ 同名不同路径会是两个分组。 + +### ⚠️ 更正(12:2x):分组靠 cwd **命中已打开的工作区**,光改目录名不够 +- 上一条只对了一半:目录改名后会话 cwd 是**仓库内** `…\V1.0\mcn-workshop`,**不是用户的工作区** ⇒ 仍显示在「**未分组任务**」区域(用户实测反馈)。 +- 用户纠正:`D:\AI技能\mcn-workshop` **才是那个工作区**。我此前仅凭目录里只有 3 个 0 字节垃圾文件就判它"是垃圾目录"——**没查登记表就下结论**,违反「判状态先问程序自己」。 +- 🔑 **真机制**:侧边栏分组 = 会话 cwd **命中已打开过的工作区**才显示为命名分组;没命中 ⇒ 落「未分组任务」。 + - 取证:`workspaces` 表仅 7 行且全是 `C:\Users\maidou\WorkBuddy\<时间戳>`,**不含**任何 `D:\AI技能\*` ⇒ 分组依据不是这张表,而是按会话 cwd 对已打开工作区的聚合。 +- 修法(配置化,⛔ 不写死):`load-config.js` 增 `sessionCwd`(默认 `''` = 用 ROOT)→ `server.js` `SESSION_CWD = String(CFG.sessionCwd||'').trim() || ROOT` → `config.json` 设 `"sessionCwd": "D:\\AI技能\\mcn-workshop"`。**换分组只改配置,不动代码。** +- 实测:新会话 cwd = `D:/AI技能/mcn-workshop` ✅;任务在该工作区执行正常(done,结果"成功",73 秒)。对照 12:23 那条仍在仓库内路径 ⇒ 两组并存,旧的会随过期清理消失。 +- ✅ 13:06 **二次模拟请求复验一致**(用户指示:不测真实功能,只发模拟请求看分组):写入 `cwds=["D:\\AI技能\\mcn-workshop"]`,会话 cwd 同样命中 ⇒ 配置化改法稳定。两次自检排期均已删,会话留在目标工作区供用户肉眼确认。 + +## 素材库补采:批 65 收尾 + 批 66/67 完成(30 卡) + +- 承 09-29 进度:批 65(细节专业 15 卡)已 apply 完成,细节专业 40→55。 +- **批 66(细节专业 15 卡)**:取材 31748 姜乘澜底妆 / 17511 笑笑易 / 19014 俊希做菜 / 24821 烟道逃生 / 25090 姜乘澜画眼线。✅ **本批 5 条原生即教程/实操题材**,与细节专业天然契合(不像批 65 从非专业题材硬提)。apply:接受 15/退回 0,问法改写 15 张平均 +32 字。细节专业 55→70。 +- **批 67(自嘲反差 15 卡)**:取材 26484 路之坑爹感恩宴 / 31816 直男五合一洗护 / 31970 李普不离谱 / 21500 桃气小周买饭 / 26411 老弟受难日记。含 1 张**元层面自黑**卡(31970 全家崩溃时推销泡面)。apply:接受 15/退回 0,问法改写 15 张平均 +12 字。自嘲反差 56→71。 +- 两批均走完整链路:`写 md → _tmp_bNN_spec.py 生成 spec → meta.json → build-pending → cat PROMPT_polish_v3.md + build-pending 组装 prompt → 派 doubao-seed-2-1-pro → _wNN.py 校验落盘 → apply-pending`。 + +### 本轮新踩坑 +- ⚠️ **MCP `myai-mcp-production` 登录态会过期**:批 66 拉第 4 条时报「认证失败」。解法:先 `auth_status` 确认 → `feishu_login` 重授权(拉起浏览器)→ 重试即通。**连续拉多条详情时中途要留意**。 +- ⚠️ **spec 脚本里别夹英文单词**:批 66 的 neg 句误写「等摊主 processing」,批量产出会污染语料。写完 spec 脚本前先自查。 + +### 当前缺口(修正后口径,2026-10-08 10:50) +| 标签 | 现有 | 缺口 | 候选 | +|:--|--:|--:|--:| +| 食材极致 | 8 | 92 | ⛔ 0 | +| 预见式服务 | 21 | 79 | ⛔ 2 | +| 感官沉浸 | 59 | 41 | 14 | +| 细节专业 | 70 | 30 | 22 | +| 自嘲反差 | 71 | 29 | 34 | +| 视觉冲击 | 76 | 24 | 10 | + +达标:品质对比 110 / 氛围沉浸 106 / 反差 217 / 反常识 128 / 价值观冲击 108。 +- ⛔ **卡点未解**:`食材极致`(缺口 92)与 `预见式服务`(缺口 79)候选池近乎空,占剩余缺口一半以上。需换关键词或换捞法才能推进。 +- 暂存批累计 24 批(43–52、54–67,缺 53)。 + +## 补采续跑:批 68(感官沉浸 15 卡)/批 69(细节专业 9 卡) + +**批 68 · 感官沉浸(33289 子杭自驾318 ×6 / 31773 萝卜乔乔英国留学回国 ×6 / 30090 姐弟双向送礼 ×3)** +- apply 结果:**接受 15/退回 0,问法改写 15 张,平均 +19 字**。感官沉浸 59 → 74。 +- ⭐ **两条来源 0 卡,已在 md 写明判据**:`28012`(电焊工试戏)男主嘴配的「滋滋」是**谐音梗的音效**而非真实声音质感,事件主角是"身在曹营心在焊" → 归 `反常识`/`自嘲反差`;`28415`(男朋友的算计)踩碎眼镜/摔筷子/拍桌的动静是**发疯甩锅的附产品** → 归 `反差`/`价值观冲击`。 +- 感官通道分布齐全(冷/缺氧失眠/冰雹砸车/撕羊腿/热水澡/久坐腰酸 · 热瓶/苦到脸垮/中药味/红米肠/赶机喘/长途疲惫 · 中暑虚脱/猛灌水/红糖蛋滋啦)。 + +**批 69 · 细节专业(28328 海鲜蒸汽 ×4 / 37316 巧克力棒手作 ×5)= 9 卡** +- apply 结果:**接受 9/退回 0,问法改写 9 张,平均 +20 字**。细节专业 70 → 79。 +- ⭐ **本批 3 条来源 0 卡,坚持不凑数**(沿用批 56 口径): + - `20461`(机票骗局)—— 骗子的"专业"是**话术与骗术设计**(伪装客服、真退票取信、连环索要卡号),不落在"这一下换普通人来做就做不到位"的操作细节上 → `反差`/`反常识`。**这是细节专业最容易误判的一类:话术专业 ≠ 操作专业。** + - `24820`(烟道脱困)—— 主角是**作死翻车与体力消耗**,片尾"专业人士、专业场地、专业操作"是**反讽** → `自嘲反差`/`难度极限`;且同账号同题材 `24821` 已在批 66 取过两卡,本条脱困手法同构,不重复入库。 + - `22163`(轮椅游渔岛)—— 倒拖轮椅过软沙滩属**体力付出** → `难度极限`;其余为情侣互坑 → `反差`/`自嘲反差`。 +- ⭐ **取批命中率下降的信号**:细节专业候选池看着有 22 条,但主队列取 5 条只有 2 条可用(命中率 40%)。后续该标签每批卡数会低于 15,属正常,不要为了凑 15 张硬塞。 + +**新增踩坑与复用做法** +- ✅ **`_wNN.py` 用 sed 复用**(`sed -e 's/_raw68/_raw69/g' -e 's/pending_0068/pending_0069/g' -e 's/^N = 15/N = 9/' _w68.py > _w69.py`)—— 卡数变化时记得同步改 `N`。本轮连续 3 批(67/68/69)一次通过,`bad: []`。 +- ✅ **超长详情自动落盘**:`33289` 返回 88,150 字符超限 → 工具落盘到 `D:\.workbuddy\projects\d-AI技能-mcn-short-video\\tool-results\*.txt`,用 Read 直接读(该文件只有 117 行,一次读完)。 +- ⚠️ 派模型改用「让 Agent 自己 Read prompt 文件 → 自己 Write 落盘 `_rawNN.txt`」——比让模型把 JSON 回吐到对话再手工落盘更省上下文,连续 3 批稳定。 + +**当前缺口(批 69 后)**:自嘲反差 29(候选 34)|感官沉浸 26(候选 14)|视觉冲击 24(候选 10)|细节专业 21(候选 22)。食材极致 92/预见式服务 79 仍挂起(用户 2026-10-08 决策:先不处理)。 + +## 批 70(自嘲反差 15 卡)· 本轮第 5 批 → 复核点 + +**结果**:接受 15/退回 0,问法改写 15 张,平均 **+27 字**(前几批 +19/+20,本批问法增厚更充分)。自嘲反差 71→**86**(缺口 14)。 +**来源**:35376 唐轩乌龙×6 | 35301 丁浩普信男×6 | 28146 妈妈硬核推理×3。**5 条视频里 2 条 0 卡**(33593、21923)。 + +**⭐ 自嘲反差口径新增形态「姿态过载」**(本批确立): +- 定义:主角自己把一件小事升格到远超其体量的规格,且全程一本正经(妈妈把女儿回家晚做成刑侦审讯:北风4级/100米/11分37秒 vs 6点12分39秒)。 +- 判据仍落回一句话:**笑点落在主角自己身上**。妈妈是主角、推理的荒谬感由她自己一本正经地制造 → 收。 +- ⛔ 反向凡尔赛揭晓(语数英 100/98/100 + 兑现平板)→ 笑点在预期被颠覆 → `反常识`;儿子 O 型嘴石化 → 笑点落在旁观者受击 → `反差`(世界参差)。均不收。 + +**两条 0 卡判据(写入 meta)**: +- **33593(张开父子的下场)**:家族护短复仇爽剧,爽点落在反派父子被制裁上,主角阵营全程降维打击,无一处笑点落在自己身上 → `反差`/`价值观冲击`。其「悲痛段落硬塞洗面奶口播」虽属元层面自黑,但批 67 已收同构卡(C1 全家崩溃时推销泡面)→ 不重复入库。 +- **21923(消防设施瘫痪的代价)**:真实事件改编的沉重社会悲剧(烟花引火→消防栓被锁→接头拧不上→母亲丧生→儿子一夜白头),全片无笑点 → `价值观冲击`。 +- ⭐ **通用判据沉淀**:**沉重社会悲剧类一律不进自嘲反差**;**元层面自黑要查是否已收同构卡**(批 67 已占一类)。 + +**其他弃卡判据**:35376「你那算盘珠子都崩我脸上了」是吐槽他人 → `反差`;「我没吃过我妈包的包子」与「我妈不会做饭」是同一场乌龙的两端 → 分作「立 flag」与「揭盅」两卡,不合并(同批 67 B2/B3/B4 细分做法)。35301 防晒植入段属商业植入;丽丽怒骂并入 B4 不单列。 + +**本轮 5 批(66–70)合计 69 卡**:66=15 | 67=15(自嘲反差)| 68=15(感官沉浸)| 69=9(细节专业,不凑数)| 70=15(自嘲反差)。 +**当前缺口(批 70 后)**:感官沉浸 **26**(候选 14)→ 视觉冲击 24(10)→ 细节专业 21(22)→ 自嘲反差 14(34)。食材极致 92/预见式服务 79 挂起。 +**体检**:`5_tag_coverage.py` 卡片总数 1017(源文件 69 个),12 维无自造标签;⛔ 零覆盖仅 `难度极限`(用户决策不处理),⚠️ 偏少仅 `食材极致`(8,挂起)。 + +**复核抽查**:批 70 卡 6(哭着嚼坚果上供)润色生效——台词前置 + 破折号拍点(「我以后再也不养小仓鼠了,我对不起你」——唐轩把相框平放在桌上…),锁定 4 字段零改动,无编造。spec 回写 sim=3/neg=2、tag 单一、src 3 个,全部符合。 + +**⭐ 入库状态核查(用户问「素材都同步到 wekonra 了吗」→ 答案:没有,两层原因)** +- **流程层**:按暂存模式,补采只落 `batches/pending_import/`,统一入库必须跑 `13_import_pending.py --run`。截至批 70,**27 批 397 卡全部堆在暂存区,一条都没进库**(`13_import_pending.py --help` 盘点:27 批/397 卡/异常 0)。`batches/*.md` 41 批(早期跑过 `2_import_batch.py` 的)是唯一进过库的部分。 +- **环境层**:`WeKnora-app` 容器 **Exited (127)**,最后一条日志停在 **2026-09-29 23:53**(health 200)→ 31607 端口不通,连"库里现有多少条"都查不了。同项目其余容器(frontend 24719 / docreader / postgres / redis)都在跑。**整个 10-08 的补采从未连过库。** +- **部署信息**:镜像 `wechatopenai/weknora-app:latest`,restart `unless-stopped`,compose 工作目录标签 `/home/maidou/weknora`(WSL 路径,本机 `docker ps -a` 可见)。拉起命令 `docker start WeKnora-app`;若仍 127 需查 compose 的 command/entrypoint。 +- **回炉链路同样未完成**:「删旧条目 + 重导 + 检索终验」都还没做;回炉批 31–33 也未跑完。 +- ⚠️ 排查口诀补充:**问「同步了吗」时先查两处**——① `13_import_pending.py --help` 看暂存盘点 ② `docker ps -a` 看 WeKnora-app 是否 Up。两者都要看,只查一处会误判。 + +**⭐ 规则核对:入库前必须补全 question(用户 2026-10-08 追问「是否遵循」→ 核查 27 批结论)** +- **规则依据(能查到的最强出处)**:RUNBOOK §步骤 6「写 spec.json(检索问法)」=流水线必经步骤;**真正的动机在 RUNBOOK 红线**:「追加相似问不重建索引(`POST /faq/entries/{id}/similar-questions` 返回 200 但向量分数按位不变)→ **要改问法只能删 + 重导**」。所以「入库前补全」不是形式要求,而是**事后补的代价=整批删+重导**。 +- ⚠️ **未找到该规则的原始对话出处**:`conversation_search` 两次 0 命中,本地 memory / RUNBOOK 也没有「入库前要补全 question」这句原话。**已按「规范 + 红线」等价认定,未擅自当作既有明文规则引用。** +- **核查结论(27 批全量)**:全部有 spec.json、`std` 全部非空 → **形式上 100% 遵循**。但有 **2 批偏离现行规范**: + | 批 | 偏离 | 范围 | + |:--|:--|:--| + | **43** | `neg=3` 条(现行应为 2),且内容是**关键词短语**(「白酒带货 / 餐厅推荐 / 商务礼仪培训」)不是问句 | 15/15 卡 | + | **48** | `sim=2` 条(现行应为 3) | 15/15 卡 | + 其余 25 批:sim=3(批 47 起)或 sim=6(批 44/45/46,合旧规范)/neg=2,全部合规。 +- ⚠️ **文档与执行脱节(待用户裁定)**:RUNBOOK §步骤 6 明文写 **sim「口语问法 4–6 条」**,但**批 47 起实际统一为 3 条**(`_wNN.py` 校验也写死 `!= 3`)。要么改文档、要么把 sim 补回 ≥4 条。 +- ⭐ **核查脚本口诀**:`glob('batches/pending_import/batch_*.spec.json')` 逐批 `Counter(len(x['sim']))` + `Counter(len(x['neg']))` 打分布表,一眼看出条数偏离;**别用 md 的 ```text 计数 //2 算卡数**(会算成一半,以 spec 长度为准)。 + +**⭐ Docker WeKnora 启动失败排查(2026-10-08 · 已修复)** +- **根因(不是应用崩溃,是容器 init 前的 bind mount 失败)**:`docker inspect WeKnora-app` 的 `.State.Error` 给出原文—— + `OCI runtime create failed: runc create failed: ... error mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu-24.04/4f4eee8c…" to rootfs at "/app/config/config.yaml": not a directory: Are you trying to mount a directory onto a file (or vice-versa)?` +- **链路**:compose 写 `./config/config.yaml:/app/config/config.yaml`(文件→文件,写法正确,源文件 `/home/maidou/weknora/config/config.yaml` 确实存在且是 6545 B 的文件)→ 但 **Docker Desktop 的 bind-mount 代理层**(WSL 9P 中转,路径被哈希成 `4f4eee8c…`)把源**识别成了目录** → 挂载失败 → 容器连 init 都没进 → **Exit 127**(entrypoint `./scripts/docker-entrypoint.sh` 根本没执行)。 +- **时间线**:StartedAt 09-28 01:45,FinishedAt **10-08 02:51** —— 正常跑了 10 天,今天凌晨有一次重启尝试(Docker Desktop/WSL 层变动)时代理层状态不对而失败。restart `unless-stopped` 不会救「启动失败」,所以一直停在 Exited。 +- ✅ **修复:`docker start WeKnora-app` 一次即恢复**(`Up (healthy)`,31607 通)→ 属**代理层临时状态问题**,重试可解。复发时的升级路径:重启 Docker Desktop → `docker compose up -d` 重建容器(会换新的代理哈希)。治本方向:单文件 bind mount 跨 WSL 本就脆弱,可改 `docker cp` 或 volume。 +- ⭐ **排查口诀:容器 Exited(127) 先看 `docker inspect --format '{{.State.Error}}'`**,不要只看 `docker logs`(日志只到上次正常运行,看不出启动失败原因)。本次日志最后一条停在 09-29 23:53 health 200,完全看不出问题,是 inspect 的 State.Error 直接给出根因。 + +**库内实测(服务恢复后)** +- FAQ 总数 **631**(分页抖动,去重拉取 624)。**问法 100% 齐全**:无 std 0 / 无 sim 0 / 无 neg 0。 +- sim 条数分布 `{5:312, 6:222, 3:46, 2:30, 4:14}`、neg `{2:542, 3:82}` —— 历史口径混着(旧 5/6 条、现 3 条、少量 2 条)。 +- 库内 tag_name 分布:反差 172/反常识 117/价值观冲击 93/视觉冲击 67/自嘲反差 44/细节专业 32/感官沉浸 26/氛围沉浸 24/品质对比 19/预见式服务 19/食材极致 9/难度极限 2 → **与本地统计一致,即已入库 = 早期 41 批;暂存 27 批 397 卡确实未进。** +- ⚠️ **接口结构坑**:FAQ 列表返回是 **`data.data`**(`{data:{total,page,page_size,data:[...]}}`)两层嵌套,只取一层会得到空列表;条目标签字段是 **`tag_name`**(不是 `tag`/`tags`);`page_size=total` 一次拉全会返回空 → 必须分页 100 逐页 + 按 id 去重。 + +**新增踩坑** +- ⚠️ `_tmp_bNN_spec.py` 里卡片名提取必须 **`.replace("###","")`**:`head.split("|")[0].strip()` 会带 `### ` 前缀 → `assert name in Q` 报 `AssertionError: ### 吃包子吃出丧母感`。批 70 踩到一次,加 replace 后通过。 +- ✅ prompt 组装字节数核对:模板 7,683 + 载荷 34,647 = 42,330(`wc -c` 可验),比批 68 记录的 15,772 大是因当时统计口径不同,**以 `wc -c` 为准**。 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 9532ec7..ee9e9e0 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -1,28 +1,40 @@ -# MCN 短视频项目长期记忆(2026-09-28 精简重写 v3) +# MCN 短视频项目长期记忆(v4 · 2026-10-08 去重精简) -> 原则:**只放「索引 + 红线 + 踩坑」**。凡已在文件里可查的(SKILL.md / references / RUNBOOK / 工作台规范)只留指针。 +> 原则:**只放「索引 + 红线 + 踩坑」**。凡文件里可查的(SKILL.md / references / RUNBOOK / 工作台 docs)只留指针,不抄内容。历史流水已清。 ## 0. 权威源指针 | 主题 | 权威文件 | |:--|:--| -| 技能全部口径 | `<仓库>/project/短视频脚本创作/V1.0/`(SKILL.md + references/references-add) | -| 路径/环境 | `references-add/路径配置.md` | +| 技能全部口径 | `<仓库>/project/短视频脚本创作/V1.0/`(SKILL.md + references + references-add) | +| 路径 / 环境 | `references-add/路径配置.md` | | 留人密度 | `知识库/03_框架节奏/04_节奏控时叙事套路.md` §1.3 三级留人点体系 | | 12 维标签 / 镜头 / 画面风格 | `素材库/分块定义/00_通用维度_分块定义.md` | | 事件卡 10 字段模板 | `素材库/分块定义/00_模板-各库分块模板.md` §极致事件 | | 赛道词表 | `知识库/标签库/赛道标签.md`(S01–S27 × 8 叙事形态 × 三级垂类) | -| 卡片批量生产 / 回炉 | `tools/weknora-ingest/RUNBOOK.md`(自包含手册,**§2.4 召回实测 / §2.5 回炉 SOP / §2.6 触发率**) | -| 工作台 UI | `mcn-work-shop/docs/工作台视觉交互规范.md` | +| 卡片批量生产 / 回炉 | `tools/weknora-ingest/RUNBOOK.md`(§2.4 召回 / §2.5 回炉 SOP / §2.6 触发率) | +| 工作台 UI | `mcn-workshop/docs/工作台视觉交互规范.md` | +| 工作台 AI 通路 | `mcn-workshop/docs/AI链路-本地CLI接入.md` | +| 技能侧 WeKnora 调用 | 技能 `references/接口调用/WeKnora_极致事件检索.md` | ## 1. 仓库与环境 - 本地 `D:\AI技能\mcn-short-video`;远程 `git@work.alotbuy.com:maogeigei/mcn-short-video.git`。 - 技能软链 `~/.workbuddy/skills/短视频工作台` = junction → `project\短视频脚本创作\V1.0\`;失效判据 children=0 → node `unlinkSync` + PowerShell `New-Item -ItemType Junction`。 - 环境判定(禁写死盘符):路径含 `.dsh` → dsh(**只读**);否则 `git rev-parse --show-toplevel` 成功=开发机 / 失败=用户环境。 - 产物落盘:桌面 `MCNSkill项目/{账号}/`。 -- 技能版本:**只改 V1.0**;Lite1.0(无 subskills)默认不动;变更流程 = 方案→确认→执行→同步副本。 +- 版本:**只改 V1.0**;Lite1.0(无 subskills)默认不动;变更流程 = 方案→确认→执行→同步副本。 ## 2. 子技能要点 -- **MCN工作台(Web)** `V1.0/mcn-work-shop/`:零依赖 Node,端口 8900。AI 任务 `POST /api/run`(写宿主 automation,once + `next_run_at`=now+5s)→ 轮询 `/api/run/status`;**创作/改写 prompt 必带 `SKILL_HINT_CREATE` 前缀**。配置单源 `config.json`;`.cjs` 无扩展 require 会 MODULE_NOT_FOUND → 配置模块用 `.js`;静态资源根映射,`/public/xxx` 必 404。 + +### 2.1 MCN工作台(Web)`V1.0/mcn-workshop/` +- 零依赖 Node,端口 8900。任务 `POST /api/run`(写宿主 automation,once + `now+5s`)→ 轮询 `/api/run/status`;**创作/改写 prompt 必带 `SKILL_HINT_CREATE` 前缀**。配置单源 `config.json`;`.cjs` 无扩展 require → MODULE_NOT_FOUND(配置模块用 `.js`);静态资源根映射,`/public/xxx` 必 404。 +- **AI 通路(09-29 起)**:`ai.backend` = `cli`(本地 CodeBuddy CLI,stdio 无端口)|`dify`(旧链路兜底)。**细节全在 §0 权威文档,改动前必读**。四条硬红线: + - 🔴 **凭据**:`config.json` 是 git 跟踪文件 → key 只放 `%USERPROFILE%\.workbuddy\mcn-workshop\ai-cli.json`(仓库外,不被技能同步复制)。 + - 🔴 **`SERVER__PORT` 必须删** —— CLI(含 `-p` 模式)内部起 HTTP server,继承宿主端口即 `EADDRINUSE` **静默卡死**,且 HTTP 层(health/info/sessions)看起来完全正常。**最容易误判的坑**。 + - 🔴 **两段流程模型来源不同**:`/api/ai/clarify`(需求打磨)走**本地 CLI**(`ai.cli.model`);`/api/run`(提交创作)走**宿主调度器**,模型取 `sessions` 表最近活跃**手动会话**的 `model`(兜底 `auto`)⇒ **改 `ai.cli.model` 不影响创作任务模型**。 + - 🔴 **`custom:` 模型按租户开通**:本机 Key 租户**无 doubao**(500 / `service info not found`);`custom-model-*` 真名从 `%TEMP%\mcn-cli-home\logs\**\*.log` 的 `requestModelId`/`requestModelName` 配对提取(问模型「你是谁」不可靠)。 +- 其余备忘:性能三杠杆 `--tools "WebSearch,WebFetch"`(prompt -78%)/ `--effort low`(首字 61.7s→19.7s)/ 换模型;⚠️ `[INIT tools]` 是**注册表全量**,判生效看 `usage.input_tokens`;`/api/ai/topics` 是**死代码**;SSE 协议 `{delta}/{replace}/{error}/[DONE]`,cli 与 dify 一致(前端零改动)。 + +### 2.2 其他子技能 - **mcn-dou-analysis**:达人表 15 列、TOP6、≥60 条停、29 赛道枚举、浏览器 9223 + 标签页复用、**身份标识禁从 DB 回填**;产物 `POST /api/import/account` 幂等写 `mcn-plugin.db`。 - **mcn-data-insight**:11 个红狐子技能,榜单即查即缓存。红狐周期 code=3203 → 账号周榜最新可用期恒为**上一周**(非 bug)。 - **mcn-video-prompt**:F1 反推图/F2 反推视频/F3 生图/F4 分镜;格式优先级 = 用户格式 > 默认 6 层;llm-wiki 必须下沉读原始条目。 @@ -31,12 +43,11 @@ ## 3. 创作红线 - 三态:⚡自动(静默只落最终物)/🤝共创(+确认门)/🛠开发(+全落盘);产物链 05→06→07 禁跳步;硬编码禁止(P0)。 - 脚本必须走 S1-S11,禁会话内直写;开场钩子 3-7s,硬上限 10s。 -- **留人点**:L1 极致触点 15-20s / L2 留人点 **每 10 秒 ≥1** / L3 增量点 3-5s。判据 = 观众产生明确「为什么」(抛问题/打破预期/说到自己/给承诺,≥1 种);纯陈述/纯动作/重复前文不算。自检 = ⌈时长÷10⌉。**禁止混层**。类型名沿用五类机制,**禁自造枚举**。 +- **留人点**:L1 极致触点 15-20s / L2 留人点 **每 10 秒 ≥1** / L3 增量点 3-5s。判据 = 观众产生明确「为什么」(抛问题/打破预期/说到自己/给承诺,≥1 种);纯陈述/纯动作/重复前文不算。自检 = ⌈时长÷10⌉。**禁止混层**,类型名沿用五类机制,**禁自造枚举**。 - **三层定位**:素材库=存素材/极致事件=素材的内容层属性/知识库=讲方法。**事件卡模板与条目落在素材库侧**,`知识库/05_极致事件/` 只放类型层与判据。 - **事件卡 10 字段(顺序固定)**:`极致内容 → 内容含义 → 极致触点 → 事件标签 → 极致类型 → 创作手法 → 镜头语言 → 画面风格 → 复用场景 → 适用场景`。卡片头 `> 所属库:{NN 库名 | 横切未定}` 不占字段位。 - 三易混字段:**极致内容**=画面内容+氛围+质感(客观可拍);**内容含义**=`[期待A]—被X打破→[结果B]`;**极致触点**=**效果侧两项:用户心理 / 共鸣**(机制词归标签/类型,效果词不复用标签词)。 - - **事件标签 ≥2 值,主标签后缀「(主)」**;「事件标签」≠ 8 库「极致标签」。 - - 创作手法含**铺垫量级 短≤3s / 中 3-10s / 长>10s**;翻车样本同等入库。**极致触点 ≠ 留人点**。 + - **事件标签 ≥2 值,主标签后缀「(主)」**;「事件标签」≠ 8 库「极致标签」。创作手法含**铺垫量级 短≤3s / 中 3-10s / 长>10s**;翻车样本同等入库。**极致触点 ≠ 留人点**。 - **复用场景 ≠ 适用场景**:适用场景=原生在哪被调用(从哪来);复用场景=事件结构能搬到哪(到哪去),取值 `{赛道·叙事形态·段落}`,**禁自造赛道名**。 - **素材条目形态铁律**:`### {名称}` + `- **字段**:值` 平铺列表;**禁写成多节表格报告**。 - 解析逆向:以 `analysis` 场次表为准(±1-2s 偏移);`极限维度` 是**上游自造词,禁直录**,三步归一:剥落差→类型层→落 12 维。 @@ -44,7 +55,7 @@ ## 4. 检索层:WeKnora + 卡片流水线 - 容器:`MCN极致事件素材库`(FAQ 型,`e6772e41-7b72-499d-b46c-e5ca7c9d1740`,`127.0.0.1:31607`,key 见 `D:\.workbuddy\mcp.json`)。**执行前必读 RUNBOOK**。 - ⛔ **接口四铁律**:① 只走 `POST /faq/entries` 批量通道(单条 `/faq/entry` 建的检索不到);② 改相似问必须 DELETE + 重导;③ 检索必显式 `vector_threshold: 0.5`(默认 0.7);④ dry_run 异步且占通道,须轮询 completed。 -- ⛔⛔ **检索参数名铁律**:数量参数名 = **`match_count`**;写 `top_k`/`limit`/`size`/`count`/`k` 会**静默忽略并恒返回 10 条**(不报错)。`vector_threshold` **必须显式传**(省略 → 返回 0 条)。阈值只做事后过滤,**不能提升质量**。分数间隔**不可判优**(top1~top10 首尾仅差 0.047)→ 靠**人工复筛落差结构同构性**。 +- ⛔⛔ **检索参数名铁律**:数量参数 = **`match_count`**;写 `top_k`/`limit`/`size`/`count`/`k` 会**静默忽略并恒返回 10 条**(不报错)。`vector_threshold` **必须显式传**(省略 → 返回 0 条)。阈值只事后过滤,**不能提升质量**;分数间隔**不可判优**(top1~top10 首尾仅差 0.047)→ 靠**人工复筛落差结构同构性**。 - ⛔ **删 FAQ 条目**:`DELETE /knowledge-bases/{KB}/faq/entries` + body `{"ids":[int64,...]}`;`DELETE /faq/entries/{id}` 是 404。 - ⛔ **FAQ 列表分页抖动**:`page_size=100` 页边界会重复。解法 = 先 `page_size=1` 拿 `total` → 再 `page_size={total}` 一次拉全。 - 其他:FAQ 检索恒为纯向量;条目**单标签**;`CustomMetadata` 不参与过滤;`auto_tag` 必须关。素材卡走 FAQ 型(文档型封顶 9/11)。**规则类文本不进检索层**。 @@ -55,36 +66,27 @@ ### 4.1 补采自动化与标签治理 - 自动化 `MCN极致事件素材库·定向补采(每小时3批·暂存模式)`,id `c4d5eae8-e2ff-427a-99aa-d027d354bc19`,`FREQ=HOURLY;INTERVAL=1`,ACTIVE。**rrule 不支持分钟级** → 单次触发连跑 3 批。 -- 脚本:`4_sensory_candidates.py`(感官候选)/`5_tag_coverage.py`(12 标签体检)/`6_target_candidates.py`(定向筛,含 `NEGMAP`)/`7_dispatch_next.py`(**调度器**:算缺口+取批命令+SKIP/PRIORITY)。取批三入口:主队列/`--sensory`/`--target <标签> N`。 +- 脚本:`4_sensory_candidates`(感官候选)/`5_tag_coverage`(12 标签体检)/`6_target_candidates`(定向筛,含 `NEGMAP`)/`7_dispatch_next`(**调度器**:算缺口+取批命令+SKIP/PRIORITY)。取批三入口:主队列/`--sensory`/`--target <标签> N`。 - **目标「每标签 ≥100 条」**:**严格主角判据** + **食材优先** + **难度极限暂不处理**。缺口跑 `7_dispatch_next.py`,**勿写死**。 - ⭐ **「食材极致」严格主角判据 3 条**:① 部位知识型 ② 验鲜解剖全流程型 ③ 形态重组型。 - ⭐⭐ **暂存模式(防返工关键机制)**:补采只产 `batches/pending_import/{batch_00NN.md,.spec.json,.meta.json}`,**禁跑 `2_import_batch.py`/`3_done_batch.py`、禁改 `state.json`**。统一入库 = **`13_import_pending.py --dry|--run|--sync`**。**安全结论**:回炉删除按 `polish_plan.json` 冻结的 526 id → 新采卡不会被误删;唯一真风险 = 重跑 `9_polish_plan.py`(已禁)。 -### 4.2 卡片回炉 + v3 双任务 SOP — 详见 RUNBOOK §2.5 -- 链路:`8_backup_all.py` → `9_polish_plan.py` → `10_polish_batch.py payload|apply ` → 删旧 + 重导 → `11_diff_report.py`。 +### 4.2 卡片回炉 + v3 SOP(细节 → RUNBOOK §2.5) +- 链路 `8_backup_all` → `9_polish_plan` → `10_polish_batch payload|apply ` → 删旧 + 重导 → `11_diff_report`。 - **字段契约**:可润色 6(极致内容/内容含义/极致触点/创作手法/镜头语言/画面风格)/**锁定 4**(事件标签/极致类型/复用场景/适用场景)/可改写问法 3 组。 - **三重校验**:① 字段集合一致 ② 骨架完整 ③ 行数不变。任一失败 → 该卡退回。 -- ⭐ **v2「吸引力优先」**:判据 = **内容是否更短视频化**。三特征:破折号 +24%/口语化连接词 +28/强动作动词 +32%。三改动 = **台词前置/破折号拍点/动词升级**。 -- ⭐ **约束强度对照**:堆「禁止…」严约束 → 改动率仅 1%;宽约束 → 46%。走宽约束。 -- ⭐ **v3 第二任务:问法增厚**:`std` 保留骨架补画面、`sim` 条数严格相等、`neg` 保持负例属性。**v3.1 绝对边界**:增厚 = **搬运原文已有信息**。自检口诀:「问法里每一个具体信息点,都必须能在该卡 6 个可润色字段 + 事件标签里找到出处;找不到的删掉。」 -- ⭐ **`validate()` 返回 5 元组** `(ok, reasons, cleaned, q, autofix)`;**std 前缀校验 = autofix 语义**(原文无前缀时模型误加 → 自动剥离,不退回)。 -- ⭐ **`fiction_check` 防误报**:引号字符类匹配弯/直引号;比对须归一化。实测残留告警全为误报 → 只告警不拦截。 -- ⭐⭐ **暂存批 v3 链路(已跑通)**:`10_polish_batch.py build-pending ` → `Agent(model="doubao-seed-2-1-pro")` 喂 `PROMPT_polish_v3.md`+载荷 → 存 `out/polish_result/pending_00NN.json` → `apply-pending `。 - - ⚠️ **`apply-pending` 必须在同一次执行内跑完**:曾出现「已生成 pending_00NN.json 但未 apply」→ 续跑须先补 apply(批次 48 实测)。 -- 工具:`12_attractiveness.py`、`11_diff_report.py`、`14_form_audit.py`、`15_sensory_recall.py`(含 `extract_name()`)、`16/17_question_ab.py`、`18/18b_question_validate(.r).py`。 +- ⭐ **v2「吸引力优先」**:判据 = **内容是否更短视频化**;三改动 = 台词前置/破折号拍点/动词升级。⭐ **宽约束**(严约束改动率仅 1% / 宽 46%)。 +- ⭐ **v3 问法增厚**:`std` 保留骨架补画面、`sim` 条数严格相等、`neg` 保持负例属性。**绝对边界 = 搬运原文已有信息**(每个信息点须能在该卡 6 个可润色字段 + 事件标签找到出处)。 +- 🔴 **暂存批 v3 链路**:`build-pending ` → 喂 `PROMPT_polish_v3.md` + 载荷 → 存 `out/polish_result/pending_00NN.json` → `apply-pending `;**`apply-pending` 必须同一次执行内跑完**(曾漏跑,续跑须先补,批次 48 实测)。 +- 其他:`validate()` 返 5 元组 `(ok,reasons,cleaned,q,autofix)`,std 前缀不符走 autofix(剥离、不退回);`fiction_check` 弯/直引号须归一化,只告警不拦截;工具 `11_diff_report`/`12_attractiveness`/`14_form_audit`/`15_sensory_recall`/`16-18*_question_*`。 -### 4.3 ⭐⭐ 技能侧 WeKnora 调用(权威源 = 技能 `references/接口调用/WeKnora_极致事件检索.md`) -- 🔴 **素材召回触发率 ≈ 0(端到端实测 ×2 路径)**;**口径(用户确立,优先)**:架构 = 「**流程不主动召回素材,知识库按需被取**」= 设计意图,**不改**。`hybrid_search` 0 次 ≠ bug。判据只有两条:① 模型**真能写出桥段** → 跳过 = ✅;② **写不出却仍不检索** = ❌。 - - 实测:4 会话 191 次工具调用、召回 0 次;模型自述「中间步骤已在上下文按序完成,自动模式后台静默」→ 整段脑内跳过。 - - **⭐ 素材库两个作用位**:**① 给事件「填空」**(S7 写不出桥段时取机制样本)+ **② 给台词「润色」**(S9 取原话/微反应/意外)。**两者是同一条链上下游**:S7 触发 ≈ 0 → S9 的 `{material_cards}` 恒空 → ② 退化。**∴ ② 失效是 ① 的直接后果**(②修好①自动恢复,改动最小)。 - - 文件位置:① `7_生成短视频大纲.md` §极致触点素材检索;② `9_生成短视频脚本.md` L12 + L101-113 §3.1。 - - **结论**:不动架构;解「"按需"判据太软」→ 推荐 **C1 换外部可验证判据**("通用 vs 专属"可验证,不像"我能不能写")。⛔ 作废「删除跳过条款 → 默认必检」。 - - 会话记录 `D:/.workbuddy/projects//.jsonl`;工具 `20_trace_recall.py`。工作台「提交创作」是**两段式**(`#reqCreate` 只弹确认,真提交在 `button[data-ok]`)。 -- ✅ **确定有效 = 参数纠正**:`match_count` 实测省 69% 上下文;`vector_threshold` 必显式传。 -- ⚠️ **收益有限 = 问法改写**:反查法旧 R@1 34/35 vs 新 35/35 → **差异在噪声内**。价值 = **信息量保障**,不是提升召回率。⛔ 禁宣称"召回率 97%→100%"。 -- 🔴 **真实瓶颈 = 素材覆盖**:需求法暴露 3/5 真实节点零命中 → 问法再优化也召不回不存在的素材。 -- 🔬 **验证方法论铁律**:测「检索优化」→ ⛔ 禁用「凭空需求」法;✅ 必用**「反查法」**。 -- ⚠️ **信息保真审计口径**:真判据 = **角色台词零丢失 + 数字零丢失 + 可拍动作/物品全在**。 +### 4.3 技能侧 WeKnora 调用(权威源 = 技能 `references/接口调用/WeKnora_极致事件检索.md`) +- 🔴 **素材召回触发率 ≈ 0(端到端实测 ×2 路径)**;**用户口径(优先)**:架构 = 「**流程不主动召回素材,知识库按需被取**」= 设计意图,**不改**。`hybrid_search` 0 次 ≠ bug。判据仅两条:① 模型**真能写出桥段** → 跳过 = ✅;② **写不出却仍不检索** = ❌。 +- ⭐ **素材库两个作用位**:① 给事件「填空」(S7 写不出桥段时取机制样本)② 给台词「润色」(S9 取原话/微反应/意外)。**同一链上下游**:S7 触发≈0 → S9 `{material_cards}` 恒空 → ② 退化;**∴ ② 失效是 ① 的直接后果**(修①即自动恢复)。文件位:`7_生成短视频大纲.md` §极致触点素材检索;`9_生成短视频脚本.md` L12 + L101-113。 +- **结论**:不动架构;解「按需判据太软」→ 推荐 **C1 换外部可验证判据**("通用 vs 专属"可验证)。⛔ 作废「删跳过条款 → 默认必检」。 +- ✅ **确定有效**:参数纠正(`match_count` 省 69% 上下文;`vector_threshold` 必显式传)。⚠️ **收益有限**:问法改写(反查法 R@1 34/35→35/35,**差异在噪声内**;⛔ 禁宣称"召回率 97%→100%")。🔴 **真实瓶颈 = 素材覆盖**(3/5 真实节点零命中)。 +- 🔬 **验证方法论铁律**:测检索优化 → ⛔ 禁用「凭空需求」法;✅ 必用**「反查法」**。⚠️ 信息保真审计口径 = **角色台词零丢失 + 数字零丢失 + 可拍动作/物品全在**。 +- 杂项:会话记录 `D:/.workbuddy/projects//.jsonl`;工具 `20_trace_recall.py`;工作台「提交创作」两段式(`#reqCreate` 弹确认,真提交在 `button[data-ok]`)。 ## 5. 麦芽平台采集(脚本在 `tools/`) - ⭐ **接口直取,禁 DOM 翻页**:`GET /api/scriptwriting/shortVideo/hot?pageNum&pageSize&sortType&startLikeNum`(pageSize=50 最快)。 @@ -110,14 +112,11 @@ - ChatGPT 投材料(`tools/chatgpt_ask.py`):**长材料走附件上传**;完成判定 = 无 stop 按钮且文本连续 4 次不变。 ## 8. 工作台 UI 定稿要点 -> 完整铁律查 **`mcn-work-shop/docs/工作台视觉交互规范.md`**(权威源)。 +> 完整铁律查 **`mcn-workshop/docs/工作台视觉交互规范.md`**(权威源)。 - 榜单四榜末列恒「功能」= `inDb ? 查看 : 关注`;热门榜与点赞榜字段体系不同 → 跨榜逻辑双向回退。 - 选题列表由 `rewrite_id` 驱动:点标题=详情;「查看脚本」=`openScriptModal({rewriteId})`;「编辑继续」=带 id UPDATE;「复制」=不带 id 新增。 ## 9. 历史时间线(压缩) - 08 月:素材库扁平化 8 类型;赛道 8→27;三态创作;Lite1.0;`.dsh` 只读;工作台立项。 -- 09-10 仓库迁移 + 环境判定|09-14 全量技能审计(P0=0/P1=8/P2=7)|09-16 工作台 AI 创作改版 + UI 规范定稿。 -- 09-21~22 极致事件定义与三层定位裁决、事件卡 10 字段定稿。 -- 09-23 WeKnora 检索层建成(FAQ 容器 + 阈值 0.5);留人点三级体系定稿。 -- 09-23~24 卡片流水线化(RUNBOOK + 脚本 1/2/3)。 -- 09-28 定向补采自动化 + 卡片回炉(v2 吸引力优先 + v3 问法增厚)+ 补采切暂存模式;检索参数名纠正。 +- 09-10 仓库迁移 + 环境判定|09-14 全量技能审计(P0=0/P1=8/P2=7)|09-16 工作台 AI 创作改版 + UI 规范定稿|09-21~22 极致事件定义与三层定位裁决、事件卡 10 字段定稿|09-23 WeKnora 检索层建成 + 留人点三级体系定稿|09-23~24 卡片流水线化|09-28 定向补采自动化 + 卡片回炉(v2/v3)+ 补采切暂存模式 + 检索参数名纠正。 +- 09-29 AI 通路改 `cli`(本地 CodeBuddy CLI)→ 端到端跑通(status/probe/clarify/run),模型 `deepseek-v4.1-flash`;权威文档 `docs/AI链路-本地CLI接入.md`。 diff --git a/.workbuddy/skills/taste-skill/SKILL.md b/.workbuddy/skills/taste-skill/SKILL.md deleted file mode 100644 index b72132f..0000000 --- a/.workbuddy/skills/taste-skill/SKILL.md +++ /dev/null @@ -1,1206 +0,0 @@ ---- -name: design-taste-frontend -description: Anti-slop frontend skill for landing pages, portfolios, and redesigns. The agent reads the brief, infers the right design direction, and ships interfaces that do not look templated. Real design systems when applicable, audit-first on redesigns, strict pre-flight check. ---- - -# tasteskill: Anti-Slop Frontend Skill - -> Landing pages, portfolios, and redesigns. Not dashboards, not data tables, not multi-step product UI. -> Every rule below is **contextual**. None of it fires automatically. First read the brief, then pull only what fits. - ---- - -## 0. BRIEF INFERENCE (Read the Room Before Anything Else) - -Before touching code or tweaking dials, **infer what the user actually wants**. Most LLM design output is bad because the model jumps to a default aesthetic instead of reading the room. - -### 0.A Read these signals first -1. **Page kind** - landing (SaaS / consumer / agency / event), portfolio (dev / designer / creative studio), redesign (preserve vs overhaul), editorial / blog. -2. **Vibe words** the user used - "minimalist", "calm", "Linear-style", "Awwwards", "brutalist", "premium consumer", "Apple-y", "playful", "serious B2B", "editorial", "agency-y", "glassy", "dark tech". -3. **Reference signals** - URLs they linked, screenshots they pasted, products they named, brands they're competing with. -4. **Audience** - B2B procurement panel vs. design-conscious consumer vs. recruiter scanning a portfolio. The audience picks the aesthetic, not your taste. -5. **Brand assets that already exist** - logo, color, type, photography. For redesigns, these are starting material, not optional input (see Section 11). -6. **Quiet constraints** - accessibility-first audiences, public-sector, regulated industries, trust-first commerce, kids' products. These constraints OVERRIDE aesthetic preference. - -### 0.B Output a one-line "Design Read" before generating -Before any code, state in one line: **"Reading this as: \ for \, with a \ language, leaning toward \."** - -Example reads: -- *"Reading this as: B2B SaaS landing for technical buyers, with a Linear-style minimalist language, leaning toward Tailwind utilities + Geist + restrained motion."* -- *"Reading this as: solo designer portfolio for hiring managers, with an editorial / kinetic-type language, leaning toward native CSS + scroll-driven animation + custom typography."* -- *"Reading this as: redesign of a public-sector service site, with a trust-first language, leaning toward GOV.UK Frontend or USWDS."* - -### 0.C If the brief is ambiguous, ask one question, do not guess -Ask exactly **one** clarifying question - never a multi-question dump - and only when the design read genuinely diverges. Example: *"Should this feel closer to Linear-clean or Awwwards-experimental?"* - -If you can confidently infer from context, **do not ask**. Just declare the design read and proceed. - -### 0.D Anti-Default Discipline -Do not default to: AI-purple gradients, centered hero over dark mesh, three equal feature cards, generic glassmorphism on everything, infinite-loop micro-animations everywhere, Inter + slate-900. These are the LLM defaults. Reach past them deliberately based on the design read. - ---- - -## 1. THE THREE DIALS (Core Configuration) - -After the design read, set three dials. Every layout, motion, and density decision below is gated by these. - -* **`DESIGN_VARIANCE: 8`** - 1 = Perfect Symmetry, 10 = Artsy Chaos -* **`MOTION_INTENSITY: 6`** - 1 = Static, 10 = Cinematic / Physics -* **`VISUAL_DENSITY: 4`** - 1 = Art Gallery / Airy, 10 = Cockpit / Packed Data - -**Baseline:** `8 / 6 / 4`. Use these unless the design read overrides them. Do not ask the user to edit this file - overrides happen conversationally. - -### 1.A Dial Inference (design read → dial values) -| Signal | VARIANCE | MOTION | DENSITY | -|---|---|---|---| -| "minimalist / clean / calm / editorial / Linear-style" | 5-6 | 3-4 | 2-3 | -| "premium consumer / Apple-y / luxury / brand" | 7-8 | 5-7 | 3-4 | -| "playful / wild / Dribbble / Awwwards / experimental / agency" | 9-10 | 8-10 | 3-4 | -| "landing page / portfolio / marketing site (default)" | 7-9 | 6-8 | 3-5 | -| "trust-first / public-sector / regulated / accessibility-critical" | 3-4 | 2-3 | 4-5 | -| "redesign - preserve" | match existing | +1 | match existing | -| "redesign - overhaul" | +2 | +2 | match existing | - -### 1.B Use-Case Presets -| Use case | VARIANCE | MOTION | DENSITY | -|---|---|---|---| -| Landing (SaaS, mainstream) | 7 | 6 | 4 | -| Landing (Agency / creative) | 9 | 8 | 3 | -| Landing (Premium consumer) | 7 | 6 | 3 | -| Portfolio (Designer / studio) | 8 | 7 | 3 | -| Portfolio (Developer) | 6 | 5 | 4 | -| Editorial / Blog | 6 | 4 | 3 | -| Public-sector service | 3 | 2 | 5 | -| Redesign - preserve | match | match+1 | match | -| Redesign - overhaul | +2 | +2 | match | - -### 1.C How the Dials Drive Output -Use these (or user-overridden values) as global variables. Cross-references throughout this document refer to these exact variable names - never invent aliases like `LAYOUT_VARIANCE` or `ANIM_LEVEL`. - ---- - -## 2. BRIEF → DESIGN SYSTEM MAP - -Once you have the design read (Section 0) and dials (Section 1), pick the right foundation. Do not invent CSS for things that have an official package. Do not pretend an aesthetic trend is an official system. - -### 2.A When to reach for a real design system (use official packages) -| Brief reads as… | Reach for | Why | -|---|---|---| -| Microsoft / enterprise SaaS / dashboards | `@fluentui/react-components` or `@fluentui/web-components` | Official Fluent UI, Microsoft tokens, accessibility done | -| Google-ish UI, Material-flavored product | `@material/web` + Material 3 tokens | Official, theme-able via Material Theming | -| IBM-style B2B / enterprise analytics | `@carbon/react` + `@carbon/styles` | Official Carbon, mature data-density patterns | -| Shopify app surfaces | `polaris.js` web components / Polaris React | Required for Shopify admin UI | -| Atlassian / Jira-style product | `@atlaskit/*` + `@atlaskit/tokens` | Official Atlassian DS | -| GitHub-style devtool / community page | `@primer/css` or `@primer/react-brand` | Official Primer; Brand variant for marketing | -| Public-sector UK service | `govuk-frontend` | Legally / regulatorily expected | -| US public-sector / trust-first | `uswds` | Same | -| Fast local-business / agency MVP | Bootstrap 5.3 | Boring, fast, works | -| Modern accessible React foundation | `@radix-ui/themes` | Primitives + polished theme | -| Modern SaaS where you own the components | shadcn/ui (`npx shadcn@latest add ...`) | You own the code, easy to customise; never ship default state | -| Tailwind-based modern SaaS / AI marketing | Tailwind v4 utilities + `dark:` variant | Default for indie + small team builds | - -**Honesty rule:** if the brief reads as one of the systems above, install and use the **official** package. Do not recreate its CSS by hand. Do not import a system's tokens but then override 90% of them. - -**One system per project.** Do not mix Fluent React with Carbon in the same tree. Do not import shadcn/ui components into a Material 3 app. - -### 2.B When the brief is an aesthetic, not a system -For these directions, there is **no single official package**. Build with native CSS + Tailwind + a maintained component library. Be honest in code comments about what is borrowed inspiration vs. official material. - -| Aesthetic | Honest implementation | -|---|---| -| Glassmorphism / "frosted glass" | `backdrop-filter`, layered borders, highlight overlays. Provide solid-fill fallback for `prefers-reduced-transparency`. | -| Bento (Apple-style tile grids) | CSS Grid with mixed cell sizes. No single library owns this. | -| Brutalism | Native CSS, monospace, raw borders. No library. | -| Editorial / magazine | Serif type, asymmetric grid, generous whitespace. No library. | -| Dark tech / hacker | Mono + accent neon, terminal motifs. No library. | -| Aurora / mesh gradients | SVG or layered radial gradients. No library. | -| Kinetic typography | Native CSS animations, scroll-driven animations, GSAP for hijacks. No library. | -| **Apple Liquid Glass** | Apple documents this for Apple platforms only. **There is no official `liquid-glass.css`.** Web implementations are approximations using `backdrop-filter` + layered borders + highlights. Label clearly as approximation. | - ---- - -## 3. DEFAULT ARCHITECTURE & CONVENTIONS - -Unless the design read picks a real design system (Section 2.A), these are the defaults: - -### 3.A Stack -* **Framework:** React or Next.js. Default to Server Components (RSC). - * **RSC SAFETY:** Global state works ONLY in Client Components. In Next.js, wrap providers in a `"use client"` component. - * **INTERACTIVITY ISOLATION:** Any component using Motion, scroll listeners, or pointer physics MUST be an isolated leaf with `'use client'` at the top. Server Components render static layouts only. -* **Styling:** **Tailwind v4** (default). Tailwind v3 only if the existing project demands it. - * For v4: do NOT use `tailwindcss` plugin in `postcss.config.js`. Use `@tailwindcss/postcss` or the Vite plugin. -* **Animation:** **Motion** (the library formerly known as Framer Motion). Import from `motion/react` (`import { motion } from "motion/react"`). The `framer-motion` package still works as a legacy alias - prefer `motion/react` in new code. -* **Fonts:** Always use `next/font` (Next.js) or self-host with `@font-face` + `font-display: swap`. Never link Google Fonts via `` in production. - -### 3.B State -* Local `useState` / `useReducer` for isolated UI. -* Global state ONLY for deep prop-drilling avoidance - Zustand, Jotai, or React context. -* **NEVER** use `useState` to track continuous values driven by user input (mouse position, scroll progress, pointer physics, magnetic hover). Use Motion's `useMotionValue` / `useTransform` / `useScroll`. `useState` re-renders the React tree on every change and collapses on mobile. - -### 3.C Icons -* **Allowed libraries (priority order):** `@phosphor-icons/react`, `hugeicons-react`, `@radix-ui/react-icons`, `@tabler/icons-react`. -* **Discouraged:** `lucide-react`. Acceptable only when the user explicitly asks for it or the project already depends on it. -* **NEVER hand-roll SVG icons.** If a glyph is missing, install a second library or compose from primitives - do not draw icon paths from scratch. -* **One family per project.** Do not mix Phosphor with Lucide in the same component tree. -* **Standardize `strokeWidth` globally** (e.g. `1.5` or `2.0`). - -### 3.D Emoji Policy -Discouraged by default in code, markup, and visible text. Replace symbols with icon-library glyphs. **Override:** allow emojis only when the user explicitly asks for a playful / chat-style / social-native vibe - and even then use them sparingly with intent. - -### 3.E Responsiveness & Layout Mechanics -* Standardize breakpoints (`sm 640`, `md 768`, `lg 1024`, `xl 1280`, `2xl 1536`). -* Contain page layouts using `max-w-[1400px] mx-auto` or `max-w-7xl`. -* **Viewport Stability:** NEVER use `h-screen` for full-height Hero sections. ALWAYS use `min-h-[100dvh]` to prevent layout jumping on mobile (iOS Safari address bar). -* **Grid over Flex-Math:** NEVER use complex flexbox percentage math (`w-[calc(33%-1rem)]`). ALWAYS use CSS Grid (`grid grid-cols-1 md:grid-cols-3 gap-6`). - -### 3.F Dependency Verification (mandatory) -Before importing ANY 3rd-party library, check `package.json`. If the package is missing, output the install command first. **Never** assume a library exists. - ---- - -## 4. DESIGN ENGINEERING DIRECTIVES (Bias Correction) - -LLMs default to clichés. Override these defaults proactively. Each rule has a context-aware override path. - -### 4.1 Typography -* **Display / Headlines:** Default `text-4xl md:text-6xl tracking-tighter leading-none`. -* **Body / Paragraphs:** Default `text-base text-gray-600 leading-relaxed max-w-[65ch]`. -* **Sans font choice:** - * **Discouraged as default:** `Inter`. Pick `Geist`, `Outfit`, `Cabinet Grotesk`, `Satoshi`, or a brand-appropriate serif first. - * **Override:** Inter is acceptable when the user explicitly asks for a neutral / standard / Linear-style feel, or when the brief is a public-sector / accessibility-first site. -* **Pairings to know:** `Geist` + `Geist Mono`, `Satoshi` + `JetBrains Mono`, `Cabinet Grotesk` + `Inter Tight`, `GT America` + `IBM Plex Mono`. - -* **SERIF DISCIPLINE (VERY DISCOURAGED AS DEFAULT):** - * Serif is **very discouraged as the default font for any project.** "It feels creative / premium / editorial" is NOT a reason to reach for serif. The agent's default mental model that "creative brief = serif" is the single most-tested AI tell in production rounds. - * **Serif is only acceptable when ONE of these is explicitly true:** - - The brand brief literally names a serif font, OR - - The aesthetic family is genuinely editorial / luxury / publication / manuscript / heritage / vintage AND you can articulate why this specific serif fits this specific brand - * For everything else (creative agency, design studio, modern brand, premium consumer, portfolio, lifestyle), **default sans-serif display** (Geist Display, ABC Diatype, Söhne Breit, Cabinet Grotesk Display, Migra Sans, GT Walsheim, Inter Display, PP Neue Montreal). Sans display fonts are not "boring" — they are the default for the same reason black is the default in fashion. - * **EMPHASIS RULE (related):** When you want to emphasize a word within a headline (the kinetic "and `spatial` design" type move), use **italic or bold of the SAME font**. Do NOT inject a random serif word into a sans headline (or vice versa) just to add visual interest. Mixed-family emphasis is amateur. Italic/bold emphasis in the same family is the right move. - * **Specifically BANNED as defaults:** `Fraunces` and `Instrument_Serif` (the two LLM-favorite display serifs). - * **If a serif is justified** (rare, per the above), rotate from this pool, do NOT reuse the same serif across consecutive projects: PP Editorial New, GT Sectra Display, Cardinal Grotesque, Reckless Neue, Tiempos Headline, Recoleta, Cormorant Garamond, Playfair Display, EB Garamond, IvyPresto, Migra, Editorial Old, Saol Display, Söhne Breit Kursiv, Domaine Display, Canela, Schnyder, Tobias, NB Architekt, ITC Galliard. - -* **ITALIC DESCENDER CLEARANCE (mandatory):** When italic is used in display type and the word contains a descender letter (`y g j p q`), `leading-[1]` or `leading-none` will clip the descender. Use `leading-[1.1]` minimum and add `pb-1` or `mb-1` reserve on the wrapping element. Audit every italic word in display headlines before shipping. - -### 4.2 Color Calibration -* Max 1 accent color. Saturation < 80% by default. -* **THE LILA RULE:** The "AI Purple / Blue glow" aesthetic is discouraged as a default. No automatic purple button glows, no random neon gradients. Use neutral bases (Zinc / Slate / Stone) with high-contrast singular accents (Emerald, Electric Blue, Deep Rose, Burnt Orange, etc.). -* **Override:** if the brand or brief explicitly asks for purple / violet / lila, embrace it. But execute with intent: consistent palette, harmonised neutrals, restrained gradients. Not generic AI gradient slop. -* **One palette per project.** Do not fluctuate between warm and cool grays within the same project. -* **COLOR CONSISTENCY LOCK (mandatory):** Once an accent color is chosen for a page, it is used on the WHOLE page. A warm-grey site does not suddenly get a blue CTA in section 7. A rose-accented site does not get a teal status badge in the footer. Pick one accent, lock it, audit every component before shipping. - -* **PREMIUM-CONSUMER PALETTE BAN (mandatory, second-most-recurring AI-tell):** - * For premium-consumer briefs (cookware, wellness, artisan, luxury, heritage craft, DTC home goods, etc.) the LLM default is **warm beige/cream + brass/clay/oxblood/ochre + espresso/ink dark text**. Concretely banned hex families as default backgrounds and accents: - - Backgrounds: `#f5f1ea`, `#f7f5f1`, `#fbf8f1`, `#efeae0`, `#ece6db`, `#faf7f1`, `#e8dfcb` (all "warm paper / cream / chalk / bone") - - Accents: `#b08947`, `#b6553a`, `#9a2436`, `#9c6e2a`, `#bc7c3a`, `#7d5621` (all "brass / clay / oxblood / ochre") - - Text: `#1a1714`, `#1a1814`, `#1b1814` (all "espresso / warm near-black") - * This palette is BANNED as the default reach for premium-consumer briefs. Every premium-consumer site you have ever shipped uses this exact palette. The brand becomes invisible. - * **Default alternatives (rotate, do not reuse):** - - **Cold Luxury:** silver-grey + chrome + smoke (think Tesla, Apple Watch Hermes-without-the-leather) - - **Forest:** deep green + bone + amber accent (think Filson, Patagonia premium) - - **Black and Tan:** true off-black + warm tan, sharp contrast, no beige - - **Cobalt + Cream:** saturated blue against a single neutral, no brass - - **Terracotta + Slate:** warm rust against cool grey, no brass - - **Olive + Brick + Paper:** muted olive plus brick-red accent - - **Pure monochrome + single saturated pop:** off-white + off-black + one bright accent (electric blue, emerald, hot pink, etc.) - * **Palette-rotation rule:** if the previous premium-consumer project you generated used the beige+brass family, this one MUST use a different family. Do not ship the same warm-craft palette twice in a row. - * **Override:** the beige+brass+espresso palette is acceptable ONLY when the brand brief explicitly names those colors, or when the brand identity is genuinely vintage / artisan / warm-craft AND you can articulate why this specific palette fits this specific brand. Default-reaching for it because "this is a cookware brief" is banned. - -### 4.3 Layout Diversification -* **ANTI-CENTER BIAS:** Centered Hero / H1 sections are avoided when `DESIGN_VARIANCE > 4`. Force "Split Screen" (50/50), "Left-aligned content / right-aligned asset", "Asymmetric white-space", or scroll-pinned structures. -* **Override:** centered hero is OK for editorial / manifesto / launch-announcement briefs where the message itself is the design. - -### 4.4 Materiality, Shadows, Cards -* Use cards ONLY when elevation communicates real hierarchy. Otherwise group with `border-t`, `divide-y`, or negative space. -* When a shadow is used, tint it to the background hue. No pure-black drop shadows on light backgrounds. -* For `VISUAL_DENSITY > 7`: generic card containers are banned. Data metrics breathe in plain layout. -* **SHAPE CONSISTENCY LOCK (mandatory):** Pick ONE corner-radius scale for the page and stick to it. Options: all-sharp (radius 0), all-soft (radius 12-16px), all-pill (full radius for interactive). Mixed systems are allowed only when there is a documented rule (e.g. "buttons are full-pill, cards are 16px, inputs are 8px") and that rule is followed everywhere. Round buttons in a square layout, or square cards on a pill-button page, is broken design. - -### 4.5 Interactive UI States -LLMs default to "static successful state only." Always implement full cycles: -* **Loading:** Skeletal loaders matching the final layout's shape. Avoid generic circular spinners. -* **Empty States:** Beautifully composed; indicate how to populate. -* **Error States:** Clear, inline (forms), or contextual (toasts only for transient). -* **Tactile Feedback:** On `:active`, use `-translate-y-[1px]` or `scale-[0.98]` to simulate a physical push. -* **BUTTON CONTRAST CHECK (mandatory, a11y):** Before shipping any button, verify the button text is readable against the button background. White button + white text, `bg-white` CTA with `text-white` label, transparent button against the page background with no border → all banned. Audit every CTA: contrast ratio WCAG AA min (4.5:1 for body, 3:1 for large text 18px+). Same rule applies to ghost buttons over photographic backgrounds (use a backdrop, scrim, or stroke). -* **CTA BUTTON WRAP BAN (mandatory):** Button text MUST fit on one line at desktop. If a label like "VIEW SELECTED WORK" wraps to 2 or 3 lines, the button is broken. Fix by EITHER shortening the label (3 words max for primary CTAs, ideally 1-2) OR widening the button (do not artificially constrain `max-width` on CTAs). Wrapped CTAs at desktop are a Pre-Flight Fail. -* **NO DUPLICATE CTA INTENT (mandatory):** Two CTAs with the same intent on one page is a Pre-Flight Fail. Examples of same intent: "Get in touch" + "Contact us" + "Let's talk" + "Start a project" + "Start something" + "Reach out" = all "contact" intent → pick ONE label and use it everywhere on the page (nav, hero, footer). Same for "Try free" + "Get started" + "Sign up free" (all "signup" intent) and "View work" + "See selected work" + "Browse projects" (all "portfolio" intent). One label per intent. -* **FORM CONTRAST CHECK (mandatory, a11y):** Form inputs, placeholder text, focus rings, helper text, and error text all pass WCAG AA contrast against the section background. Light placeholders on a near-white form, white form on white page section, form labels grayer than 4.5:1 contrast → all banned. Audit every form before shipping. - -### 4.6 Data & Form Patterns -* Label ABOVE input. Helper text optional but present in markup. Error text BELOW input. Standard `gap-2` for input blocks. -* No placeholder-as-label. Ever. - -### 4.7 Layout Discipline (Hard Rules. Failing any of these is shipping broken work) - -* **Hero MUST fit in the initial viewport.** Headline max 2 lines on desktop, subtext max **20 words** AND max 3-4 lines, CTAs visible without scroll. If the copy is too long: reduce font scale OR cut copy. If you cannot describe the value-prop in 20 words of subtext, the value-prop is unclear, not the rule too tight. Never let the hero overflow and force scroll to find the CTA. -* **Hero font-scale discipline.** Plan font size and image size *together*. If the hero asset is large and the headline is more than 6 words, do not start at `text-7xl/text-8xl`. Default sensible range: `text-4xl md:text-5xl lg:text-6xl` for most heroes; `text-6xl md:text-7xl` only when the headline is 3-5 words. A 4-line hero headline is always a font-size error, never a copy-length error. -* **HERO TOP PADDING CAP (mandatory):** Hero top padding max `pt-24` (≈6rem) at desktop. More than that means the hero content floats halfway down the viewport and reads as a layout bug, not as intentional space. If your hero needs more breathing room, increase font scale or asset size, not top padding. -* **HERO STACK DISCIPLINE (max 4 text elements).** The hero is a single moment, not a feature list. Allowed text elements, max 4 in total: - 1. Eyebrow (small uppercase label) OR brand strip OR neither - pick zero or one - 2. Headline (max 2 lines, see above) - 3. Subtext (max 20 words, max 4 lines) - 4. CTAs (1 primary + max 1 secondary) - - **BANNED in the hero:** tiny tagline below CTAs ("Works with GitHub, GitLab, and self-hosted Git"), trust micro-strip ("Used by engineering teams at..."), pricing teaser ("Free for solo, $10/user for teams"), feature bullet list, social-proof avatar row. All of those move to dedicated sections directly below the hero. - - If you have an eyebrow AND a tagline below CTAs in the same hero, drop the tagline. If you have a brand strip AND a tagline, drop the tagline. One small text element per hero, max. -* **"Used by" / "Trusted by" logo wall belongs UNDER the hero, never inside it.** The hero is for the value prop and primary CTA. The logo wall is a separate section directly below. Do not stuff trust logos into the same flex row as the hero copy. -* **Navigation MUST render on a single line on desktop.** If items don't fit at `lg` (1024px), condense labels, drop secondary items, or move to a hamburger. A two-line nav at desktop is broken design. -* **Navigation height cap: 80px max desktop, default 64-72px.** No huge "agency" nav bars that eat 15% of the viewport. -* **Bento grids MUST have rhythm, not one-sided repetition.** Do not stack 6 left-image / right-text rows. Vary the composition: alternate full-width feature rows, asymmetric tile sizes, vertical breaks. -* **BENTO CELL COUNT RULE (mandatory):** A bento grid has EXACTLY as many cells as you have content for. 3 items → 3 cells (1+2 split, or 2+1, or asymmetric trio). 5 items → 5 cells (2+3, 3+2, hero+4, etc.). If your grid has an empty cell in the middle or at the end, you planned wrong. Re-shape the grid; do not paste a blank tile. -* **Section-Layout-Repetition Ban.** Once you use a layout family for a section (e.g., 3-column-image-cards, full-width-quote, split-text-image), that family can appear at most ONCE on the page. "Selected commissions" must not look like "What we do." A landing page with 8 sections must use at least 4 different layout families. -* **ZIGZAG ALTERNATION CAP (mandatory).** Alternating "left-image + right-text" then "left-text + right-image" zigzag layout = banal. Max 2 sections in a row with this image+text-split pattern. The 3rd consecutive image+text split is a Pre-Flight Fail. Break the pattern with a full-width section, a vertical-stack section, a bento grid, a marquee, or a different layout family. -* **EYEBROW RESTRAINT (mandatory, the #1 violated rule in production tests).** An "eyebrow" is the small uppercase wide-tracking label sitting above a section headline (e.g. `FOUR COLORWAYS`, `SELECTED WORK`, `THE HARDWARE`, `Git-native task management`). Typical CSS signature: `text-[11px] uppercase tracking-[0.18em]`, `font-mono text-[10.5px] uppercase tracking-[0.22em]`. Every AI-built site puts an eyebrow above EVERY section header, producing the same templated rhythm. Hard rule: - - **Maximum 1 eyebrow per 3 sections.** Hero counts as 1. So a page with 9 sections may use at most 3 eyebrows total. - - If section A has an eyebrow, the next 2 sections cannot have one. - - **Pre-Flight Check is mechanical:** count instances of `uppercase tracking` (or similar small-caps mono labels above headlines) across all section components. If count > ceil(sectionCount / 3), the output fails. - - **What to do instead of an eyebrow:** drop it entirely. The headline alone is enough. If you need to categorize a section, the section's location on the page already categorizes it; no label needed. -* **SPLIT-HEADER BAN (mandatory).** The pattern "left big headline + right small explainer paragraph" as a section header (left col-span-7/8, right col-span-4/5 with a small body paragraph floating in the right column) is **banned as default**. Sections should have ONE focused message. If you genuinely need both a headline and an explainer paragraph, stack them vertically (headline on top, body below, max-width 65ch). Reach for the split-header pattern only when there is a real compositional reason (e.g., the right column carries a visual or interactive element, not just filler text). -* **Bento Background Diversity (mandatory).** Bento and feature-grid sections cannot be 6 white-on-white cards with text inside. At least 2-3 cells in any multi-cell grid need real visual variation: a real image, a brand-appropriate gradient (not AI-purple), a pattern, a tinted background. A cream-on-cream bento with only typography inside reads as boring AI default, even when the rest of the page is good. -* **Mobile collapse must be explicit per section.** For every multi-column layout, declare the `< 768px` fallback in the same component. No "it'll work, Tailwind handles it" assumptions. - -### 4.8 Image & Visual Asset Strategy - -Landing pages and portfolios are **visual products**. Text-only pages with fake-screenshot divs are slop. - -**Priority order for visual assets:** -1. **Image-generation tool first.** If ANY image-gen tool is available in the environment (`generate_image`, MCP image tool, IDE-integrated gen, OpenAI image tools, etc.) you MUST use it to create section-specific assets: hero photography, product shots, texture backgrounds, mood images. Generate at the right aspect ratio for the section. Do not skip this step because hand-rolled CSS feels faster. -2. **Real web images second.** When no gen tool is available, use real photography sources. Acceptable defaults: - * `https://picsum.photos/seed/{descriptive-seed}/{w}/{h}` for placeholder photography (seed should describe the section, e.g. `marrow-cookware-kitchen`) - * Actual stock or brand URLs when the brief provides them - * Open-license sources (Unsplash via direct URL, Pexels) if explicitly allowed -3. **Last resort: tell the user.** If neither is possible, do NOT fill the page with hand-rolled SVG illustrations or div-based "fake screenshots." Instead, leave clearly-labeled placeholder slots (``) and at the end of the response say: *"This page needs real images at: \[list of placements\]. Please generate or provide them."* - -**Even minimalist sites need real images.** A pure-text page is not minimalism. It is incomplete work. Even an editorial Linear-style site needs at least 2-3 real images (hero, one product/lifestyle shot, one supporting image). Generate B&W minimalist photography if the brief is restrained; do not skip images entirely because the dial is low. - -**Real company logos for social proof.** When the brief calls for a "Trusted by / Used by / Customers" logo wall, do NOT default to plain text wordmarks (`Acme Co` styled in a row). Use real SVG logos: -* **Source: Simple Icons** (`https://cdn.simpleicons.org/{slug}/ffffff` for any color, or `simple-icons` npm package). Covers most known brands. -* **Alternative: devicon** for tech-stack logos (`@svgr/cli` or CDN). -* **Make-up the brand name? Then make-up an SVG mark too.** Generate a simple monogram (one letter in a circle, two-letter ligature, abstract glyph) rendered as an inline `` matching the page style. Plain text wordmarks for invented brand names look generic. -* **Always** ensure logos render in both light and dark mode (white-on-dark, black-on-light, or single-color theme variable). -* **LOGO-ONLY rule (mandatory):** logo wall = logos and nothing else. Do NOT print industry / category labels below each logo (no `Vercel` + `hosting` underneath, no `Stripe` + `payments`, no `Cloudflare` + `infra`). The logo is the credibility, the label adds nothing the user does not already know. Optional: brand name as alt-text for screen readers, optional link to the brand's site. That is it. - -**Hand-rolled illustrations:** -* SVG icons from libraries: fine (see Section 3.C). -* Hand-rolled decorative SVGs (custom illustrations, logos, marks): **strongly discouraged**, never as default. Acceptable only when: - - The brief explicitly calls for it ("draw me an SVG logo") - - It's a single, simple geometric mark (a square, a circle, a wordmark in display type) - - You're confident in the output quality - -**Div-based fake screenshots are banned.** A "hand-built product preview" rendered with `
` rectangles, fake task lists, fake dashboards, fake terminal windows is a Tell. If you need to show a product: -* Use a real screenshot URL if one exists -* Generate one via image tool -* Use a real component preview (an actual mini-version of the UI inside the page) -* Or skip the preview entirely and use editorial photography - -**Hero needs a real visual.** Text + gradient blob is not a hero - it's a placeholder. - -### 4.9 Content Density - -Landing pages live on the **first impression**, not the full read. Cut ruthlessly. - -* **Default content shape per section:** short headline (≤ 8 words) + short sub-paragraph (≤ 25 words) + one visual asset OR one CTA. Anything more must be justified by the section's job. -* **No data-dump sections.** A 20-row publication table, a 30-row award list, a giant pricing matrix on a marketing page = wrong layout. Use: - - Top 3-5 highlights + "View full list" link - - Marquee / carousel for breadth - - Different page entirely if the data is the product -* **Long lists need a different UI component, not a longer list.** Default `
    ` with bullets / `divide-y` rows is the lazy choice. If you have > 5 items, reach for one of these instead: - - 2-column split with grouped items - - Card grid with image + label per item - - Tabs / accordion if items are categorisable - - Horizontal scroll-snap pills - - Carousel for breadth-heavy lists (testimonials, logos, capabilities) - - Marquee for "lots-of-things-that-don't-need-individual-attention" - A spec sheet with 10 rows + a hairline under every row is the WORST default. Either group rows into 2-3 chunks with sparse dividers, or move to a card-per-spec layout. -* **Spec sheets specifically (the Marrow-cookware pattern).** A long product specification table with `border-b` on every row is the AI default for cookware / hardware / apparel / artisan-goods briefs. Banned. Concrete alternatives: - - **2-col card grid:** each spec gets its own card with the spec name, the value (large display number), and a one-line "why it matters" body. Cards arranged 2-col on desktop, 1-col mobile. - - **Scroll-snap horizontal pills:** each spec is a pill, user can flick through. - - **Grouped chunks:** group 10 specs into 3 logical clusters (e.g. "Materials", "Cooking", "Warranty"), each cluster gets ONE soft divider and a cluster heading. - - **Featured-vs-rest:** 3-4 hero specs visualised as large display tiles, the rest collapsed under a "View full specifications" disclosure. - -* **COPY SELF-AUDIT (mandatory before ship):** Before declaring any task done, re-read every visible string on the page (headlines, subheads, eyebrows, button labels, body copy, captions, alt text, footer text, error messages). Flag any string that is: - - **Grammatically broken** ("free on its past", "two plans but one is honest", "to put it on the table" out of context) - - **Has unclear referents** ("we plan to stay that way" without prior context) - - **Sounds like AI hallucination** (cute-but-wrong wordplay, forced metaphors that don't track, "elegant nothing" phrases) - - **Reads like an LLM trying to sound thoughtful** (passive-aggressive humility, fake-craftsman labels, mock-poetic micro-meta) - Rewrite every flagged string. If unsure whether a string makes sense, replace it with a plain functional sentence. AI-generated cute copy is worse than boring copy. -* **Fake-precise numbers are flagged.** Numbers like `92%`, `4.1×`, `48k`, `5.8 mm`, `13.4 lb` either: - - Come from real data (brief, brand guidelines, public metrics) - fine - - Are explicitly labeled as mock (``, "example", "sample data") - fine - - Are AI-invented spec aesthetics - banned. Don't fake engineering precision the brand doesn't claim. -* **One copy register per page.** Don't mix technical mono ("47 tasks · 0.6 ctx-switches/day"), editorial prose, and marketing punch in the same composition unless the brand voice explicitly calls for it. - -### 4.10 Quotes & Testimonials - -* **Max 3 lines** of quote body. Never 6. If the original quote is longer → cut it. A landing-page quote is a snippet, not the full review. -* For very small font sizes (e.g. footer-style testimonials), the line cap can stretch slightly. Spirit: "fits in a glance." -* **No em-dashes inside the quote text** as design flourish (long pauses, kinetic em-dashes, em-dash-bullets). See Section 9.G - em-dash is completely banned. -* Attribution: name + role + (optionally) company. Never name only ("- Sarah"). -* Quote marks: use real typographic quotes ( " " ) or none at all. Not straight ASCII ( " ). - -### 4.11 Page Theme Lock (Light / Dark Mode Consistency) - -The page has ONE theme. Sections do not invert. - -* If the page is dark mode, ALL sections are dark mode. No light-mode-warm-paper section sandwiched between dark sections (or vice versa). The user must not feel they walked into a different website mid-scroll. -* The exception: if the brief explicitly calls for a "Color Block Story" or "Theme Switch on Scroll" device AND that is a deliberate composition (one full theme switch with a strong transition, not random alternation), it is allowed once per page. -* Default behaviour: pick light, dark, or auto (`prefers-color-scheme`) at the page level and lock it. Section-level background tints within the same theme family are fine (`bg-zinc-950` next to `bg-zinc-900`); flipping to `bg-amber-50` in the middle of a `bg-zinc-950` page is broken. -* When using a design system with built-in theming (Radix Themes, shadcn/ui with ``), set the theme ONCE in `layout.tsx` or the page root. Do not let individual sections override. - ---- - -## 5. CONTEXT-AWARE PROACTIVITY - -These are tools, not defaults. Use them when the design read calls for them. **None of these fire automatically.** - -* **Liquid Glass / Glassmorphism:** Appropriate for premium consumer, Apple-adjacent, luxury brand, or media-overlay vibes. Inappropriate for dashboards, public-sector, or "boring B2B." When used, go beyond `backdrop-blur`: add a 1px inner border (`border-white/10`) and a subtle inner shadow (`shadow-[inset_0_1px_0_rgba(255,255,255,0.1)]`) for physical edge refraction. Provide a solid-fill fallback under `prefers-reduced-transparency`. -* **Magnetic Micro-physics:** Use when `MOTION_INTENSITY > 5` AND the brief reads premium / playful / agency. Implement EXCLUSIVELY with Motion's `useMotionValue` / `useTransform` outside the React render cycle. Never `useState`. See Section 3.B. -* **Perpetual Micro-Interactions** (Pulse, Typewriter, Float, Shimmer, Carousel): Use when `MOTION_INTENSITY > 5` AND the section actively benefits from motion (status indicators, live feeds, AI-feel). **Not every card needs an infinite loop.** If a section is informational, leave it still. Apply Spring Physics (`type: "spring", stiffness: 100, damping: 20`) - no linear easing. -* **"Motion claimed, motion shown."** If `MOTION_INTENSITY > 4`, the page must actually move: entry transitions on hero, scroll-reveal on key sections, hover physics on CTAs, at minimum. A static page that claims `MOTION_INTENSITY: 7` is broken. Conversely, if you cannot ship working motion in the available scope, drop the dial to 3 and ship a clean static page. Never half-build motion that breaks (cut-off ScrollTriggers, jumpy enters, missing cleanups). -* **MOTION MUST BE MOTIVATED (mandatory).** Before adding any animation, ask: "what does this animation communicate?" Valid answers: hierarchy (drawing attention to the right thing), storytelling (revealing content in sequence that matches a narrative), feedback (acknowledging a user action), state transition (showing something changed). Invalid answer: "it looked cool". GSAP everywhere because GSAP is available is amateur. Each ScrollTrigger, each marquee, each pinned section needs a reason. If you cannot articulate the reason in one sentence, drop the animation. -* **MARQUEE MAX-ONE-PER-PAGE (mandatory).** Horizontal scrolling text marquees ("logos endlessly scrolling", "manifesto scrolling sideways", "kinetic word strip") are appropriate at most ONCE per page. Two or more marquees on the same page reads as lazy filler. Pick the one section where the marquee actually serves the content; the others get a different layout. -* **GSAP Sticky-Stack Pattern (when scroll-stack is used).** A "card stack on scroll" must be a REAL sticky-stack, not a sequential reveal list. See Section 5.A below for the canonical code skeleton. Common failure: trigger fires halfway through scroll instead of pinning at viewport top. Fix: `start: "top top"` not `start: "top center"` or `"top 80%"`. -* **GSAP Horizontal-Pan Pattern (when horizontal scroll-hijack is used).** See Section 5.B below for the canonical skeleton. Common failure: animation starts before the section is pinned, so the user sees half a slide. Same fix: `start: "top top"`, pin the wrapper, scrub the inner track. - -### 5.A Sticky-Stack - Canonical Skeleton - -```tsx -"use client"; -import { useRef, useEffect } from "react"; -import { gsap } from "gsap"; -import { ScrollTrigger } from "gsap/ScrollTrigger"; -import { useReducedMotion } from "motion/react"; - -gsap.registerPlugin(ScrollTrigger); - -export function StickyStack({ cards }: { cards: React.ReactNode[] }) { - const ref = useRef(null); - const reduce = useReducedMotion(); - - useEffect(() => { - if (reduce || !ref.current) return; - const ctx = gsap.context(() => { - const cardEls = gsap.utils.toArray(".stack-card"); - cardEls.forEach((card, i) => { - if (i === cardEls.length - 1) return; - ScrollTrigger.create({ - trigger: card, - start: "top top", // pin at viewport top - endTrigger: cardEls[cardEls.length - 1], - end: "top top", - pin: true, - pinSpacing: false, - }); - gsap.to(card, { - scale: 0.92, - opacity: 0.55, - ease: "none", - scrollTrigger: { - trigger: cardEls[i + 1], - start: "top bottom", - end: "top top", - scrub: true, - }, - }); - }); - }, ref); - return () => ctx.revert(); - }, [reduce]); - - return ( -
    - {cards.map((card, i) => ( -
    - {card} -
    - ))} -
    - ); -} -``` - -Critical points: `start: "top top"`, `pin: true`, every card except the last is pinned, the scale/opacity transform is driven by the NEXT card's scroll trigger (so previous card shrinks as next one arrives). - -### 5.B Horizontal-Pan - Canonical Skeleton - -```tsx -"use client"; -import { useRef, useEffect } from "react"; -import { gsap } from "gsap"; -import { ScrollTrigger } from "gsap/ScrollTrigger"; -import { useReducedMotion } from "motion/react"; - -gsap.registerPlugin(ScrollTrigger); - -export function HorizontalPan({ children }: { children: React.ReactNode }) { - const wrap = useRef(null); - const track = useRef(null); - const reduce = useReducedMotion(); - - useEffect(() => { - if (reduce || !wrap.current || !track.current) return; - const ctx = gsap.context(() => { - const distance = track.current!.scrollWidth - window.innerWidth; - gsap.to(track.current, { - x: -distance, - ease: "none", - scrollTrigger: { - trigger: wrap.current, - start: "top top", // pin starts when section top hits viewport top - end: () => `+=${distance}`, // scroll distance = track width minus viewport - pin: true, - scrub: 1, - invalidateOnRefresh: true, - }, - }); - }, wrap); - return () => ctx.revert(); - }, [reduce]); - - return ( -
    -
    - {children} -
    -
    - ); -} -``` - -Critical points: `start: "top top"`, `pin: true`, `end: "+=${distance}"` (scroll length = horizontal travel needed), `scrub: 1`. The wrapper is pinned, the inner track slides horizontally as the user scrolls vertically. - -### 5.C Scroll-Reveal Stagger - Canonical Skeleton (lighter alternative) - -For simple "items appear as they enter viewport" (no pinning), prefer Motion's `whileInView` over GSAP - lighter, no ScrollTrigger needed: - -```tsx -"use client"; -import { motion, useReducedMotion } from "motion/react"; - -export function RevealStagger({ items }: { items: string[] }) { - const reduce = useReducedMotion(); - return ( -
      - {items.map((item, i) => ( - - {item} - - ))} -
    - ); -} -``` - -Use this for: feature lists, testimonial grids, logo walls, anything that just needs "enter on scroll." Save GSAP for actual pin/scrub work. - -### 5.D Forbidden Animation Patterns - -* **`window.addEventListener("scroll", ...)`** is banned. It runs on every scroll frame, jank-prone, no batching. Use Motion's `useScroll()`, GSAP's `ScrollTrigger`, IntersectionObserver, or CSS `scroll-driven animations` (`animation-timeline: view()`). -* **Custom scroll progress calculations using `window.scrollY`** in React state. Same reason. Re-renders on every frame. -* **`requestAnimationFrame` loops that touch React state.** Use motion values (`useMotionValue` + `useTransform`) instead. -* **Layout Transitions:** Use Motion's `layout` and `layoutId` props for visible state changes (re-ordering lists, expanding modals, shared elements between routes). Do not wrap static content in `layout` props "for safety" - it costs measurement work. -* **Staggered Orchestration:** Use `staggerChildren` (Motion) or CSS cascade (`animation-delay: calc(var(--index) * 100ms)`) for reveal moments where sequence matters. For `staggerChildren`, parent (`variants`) and children MUST share the same Client Component tree. - ---- - -## 6. PERFORMANCE & ACCESSIBILITY GUARDRAILS - -### 6.A Hardware Acceleration -* Animate ONLY `transform` and `opacity`. Never animate `top`, `left`, `width`, `height`. -* Use `will-change: transform` sparingly - only on elements that will actually animate. - -### 6.B Reduced Motion (mandatory) -* **Any motion above `MOTION_INTENSITY > 3` MUST honor `prefers-reduced-motion`.** This is non-negotiable. -* In Motion: wrap with `useReducedMotion()` and degrade to static. -* In CSS: gate animations behind `@media (prefers-reduced-motion: no-preference)` or provide an override block under `@media (prefers-reduced-motion: reduce)` that disables. -* Infinite loops, parallax, scroll-hijack, and magnetic physics MUST collapse to static / instant under reduced motion. - -### 6.C Dark Mode (mandatory for any consumer-facing page) -* Design for **both modes from the start**. Never ship light-only or dark-only without explicit user instruction. -* Use Tailwind `dark:` variant OR CSS variables for tokens. Pick one strategy per project. -* **Do not prescribe specific dark-mode colors here.** The brief decides. Maintain visual hierarchy, brand identity, and WCAG AA contrast (AAA for body) across both modes. -* Respect `prefers-color-scheme: dark`. Default to system preference unless the brand insists on one mode. - -### 6.D Core Web Vitals Targets -* **LCP** < 2.5s. Hero image must be `next/image priority` or preloaded. -* **INP** < 200ms. Heavy work off main thread. -* **CLS** < 0.1. Reserve space for images, fonts, embeds. -* Run Lighthouse before declaring a page done. - -### 6.E DOM Cost -* Apply grain / noise filters EXCLUSIVELY to fixed, `pointer-events-none` pseudo-elements (e.g., `fixed inset-0 z-[60] pointer-events-none`). NEVER on scrolling containers - continuous GPU repaints destroy mobile FPS. -* Be aware of bundle size. Motion is not tiny. Three.js is large. Lazy-load anything that's not above-the-fold. - -### 6.F Z-Index Restraint -NEVER spam arbitrary `z-50` or `z-10`. Use z-index strictly for systemic layer contexts (sticky navbars, modals, overlays, grain). Document the z-index scale in a project constants file. - ---- - -## 7. DIAL DEFINITIONS (Technical Reference) - -### DESIGN_VARIANCE (Level 1-10) -* **1-3 (Predictable):** Symmetrical CSS Grid (12-col, equal fr-units), equal paddings, centered alignment. -* **4-7 (Offset):** `margin-top: -2rem` overlaps, varied image aspect ratios (4:3 next to 16:9), left-aligned headers over center-aligned data. -* **8-10 (Asymmetric):** Masonry layouts, CSS Grid with fractional units (`grid-template-columns: 2fr 1fr 1fr`), massive empty zones (`padding-left: 20vw`). -* **MOBILE OVERRIDE:** For levels 4-10, asymmetric layouts above `md:` MUST collapse to strict single-column (`w-full`, `px-4`, `py-8`) on viewports `< 768px`. - -### MOTION_INTENSITY (Level 1-10) -* **1-3 (Static):** No automatic animations. CSS `:hover` and `:active` states only. `prefers-reduced-motion` is the default mode anyway. -* **4-7 (Fluid CSS):** `transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1)`. `animation-delay` cascades for load-ins. Focus on `transform` and `opacity`. -* **8-10 (Advanced Choreography):** Complex scroll-triggered reveals, parallax, scroll-driven animation (CSS `animation-timeline` or GSAP ScrollTrigger). Use Motion hooks. **NEVER use `window.addEventListener('scroll')`** - it is a hard ban, not a "prefer-not." See Section 5.D for the allowed alternatives. - -### VISUAL_DENSITY (Level 1-10) -* **1-3 (Art Gallery):** Lots of white space. Huge section gaps (`py-32` to `py-48`). Expensive, clean. -* **4-7 (Daily App):** Standard web app spacing (`py-16` to `py-24`). -* **8-10 (Cockpit):** Tight paddings. No card boxes; 1px lines separate data. Mandatory: `font-mono` for all numbers. - ---- - -## 8. DARK MODE PROTOCOL - -Dual-mode by default. Never assume light-only unless the brief is print-emulating editorial. - -### 8.A Token Strategy (pick one, stick to it) -* **Tailwind `dark:` variant** (default for utility-first projects): every color utility paired with its dark variant (`bg-white dark:bg-zinc-950`, `text-gray-900 dark:text-gray-100`). -* **CSS variables** (for shadcn/ui, Radix Themes, or component libraries with theming): define semantic tokens (`--surface`, `--surface-elevated`, `--text-primary`, `--accent`) and swap values under `[data-theme="dark"]` or `@media (prefers-color-scheme: dark)`. - -### 8.B Do Not Prescribe Specific Colors Here -The brief and brand decide. This skill enforces only: -* **Contrast** - WCAG AA minimum for body text, AAA target for hero copy. -* **Hierarchy parity** - visual hierarchy that works in light must work in dark. If a CTA pops in light, it pops in dark. -* **Brand fidelity** - primary brand color stays recognisable. Don't desaturate the brand into a dark mode. -* **No pure `#000000` and no pure `#ffffff`** - use off-black (zinc-950, near-black warm gray) and off-white. Pure values kill depth. - -### 8.C Default Mode -Respect `prefers-color-scheme` unless the brand insists. Add a manual toggle if either mode would lose key brand expression. - -### 8.D Test in Both Modes Before Finishing -Open the page in both modes during development. Do not ship a page you've only seen in one mode. - ---- - -## 9. AI TELLS (Forbidden Patterns) - -Avoid these signatures unless the brief explicitly asks for them. - -### 9.A Visual & CSS -* **NO neon / outer glows** by default. Use inner borders or subtle tinted shadows. -* **NO pure black (`#000000`).** Off-black, zinc-950, or charcoal. -* **NO oversaturated accents.** Desaturate to blend with neutrals. -* **NO excessive gradient text** for large headers. -* **NO custom mouse cursors.** Outdated, accessibility-hostile, perf-hostile. - -### 9.B Typography -* **AVOID Inter as default.** See Section 4.1. Override path exists. -* **NO oversized H1s** that just scream. Control hierarchy with weight + color, not raw scale. -* **Serif constraints:** Serif for editorial / luxury / publication. Not for dashboards. - -### 9.C Layout & Spacing -* **Mathematically perfect** padding and margins. No floating elements with awkward gaps. -* **NO 3-column equal feature cards.** The generic "three identical cards horizontally" feature row is banned. Use 2-column zig-zag, asymmetric grid, scroll-pinned, or horizontal-scroll alternative. - -### 9.D Content & Data ("Jane Doe" Effect) -* **NO generic names.** "John Doe", "Sarah Chan", "Jack Su" → use creative, realistic, locale-appropriate names. -* **NO generic avatars.** No SVG "egg" or Lucide user icons → use believable photo placeholders or specific styling. -* **NO fake-perfect numbers.** Avoid `99.99%`, `50%`, `1234567`. Use organic, messy data (`47.2%`, `+1 (312) 847-1928`). -* **NO startup-slop brand names.** "Acme", "Nexus", "SmartFlow", "Cloudly" → invent contextual, premium names that sound real. -* **NO filler verbs.** "Elevate", "Seamless", "Unleash", "Next-Gen", "Revolutionize" → concrete verbs only. - -### 9.E External Resources & Components -* **NO hand-rolled SVG icons.** Use Phosphor / HugeIcons / Radix / Tabler. Lucide on explicit request only. -* **Hand-rolled decorative SVGs strongly discouraged** as default (see Section 4.8). -* **NO div-based fake screenshots.** Never build a fake product UI out of `
    ` rectangles to simulate a screenshot. Use real images, generated images, or skip the preview. -* **NO broken Unsplash links.** Use `https://picsum.photos/seed/{descriptive-string}/{w}/{h}`, or generated photo placeholders, or actual assets. -* **shadcn/ui customization:** Allowed, but NEVER in default state. Customize radii, colors, shadows, typography to the project aesthetic. -* **Production-Ready Cleanliness:** Code visually clean, memorable, meticulously refined. - -### 9.F Production-Test Tells (banned outright) - -These patterns came out of real LLM-generated landing-page tests. They are the signatures the model defaults to when it tries to "look designed." Treat them as hard bans unless the brief explicitly calls for one. - -**Hero & top-of-page** -* **NO version labels in the hero.** `V0.6`, `v2.0`, `BETA`, `INVITE-ONLY PREVIEW`, `EARLY ACCESS`, `ALPHA` - banned as default eyebrows. Only acceptable when the brief is explicitly about a product launch / preview status. -* **NO "Brand · No. 01"-style sub-eyebrows.** "Marrow · No. 01 · The 6-quart" type micro-meta lines. Skip them. - -**Section numbering & micro-labels** -* **NO section-number eyebrows.** `00 / INDEX`, `001 · Capabilities`, `002 · Featured commission`, `06 · how it works`, `05 · The honest table` - banned. Eyebrows should name the topic in plain language, not enumerate. -* **NO `01 / 4`-style pagination on images or bento tiles.** If the user can count, they don't need the label. -* **NO `Scroll · 001 Capabilities`-style scroll cues.** A simple arrow or "Scroll" is enough; no section-number prefix. -* **NO "Index of Work, 2018 - 2026"-style range labels** as eyebrows. Just say what the section is. - -**Separators & dots** -* **The middle-dot (`·`) is rationed.** Maximum 1 per line in metadata strips. Do NOT use it as the default separator for everything ("foo · bar · baz · qux · quux"). If you need a separator family, prefer line breaks, hairlines, or columns. -* **NO decorative colored status dots on every list/nav/badge.** A colored dot before "ONE Q4 SLOT OPEN" or before every nav link, or every task row - banned by default. Acceptable only when the dot conveys actual semantic state (a server status, an availability flag) and is used sparingly. - -**Em-dashes & typography flourishes** -* **NO em-dash (`—`) as a design element OR anywhere else.** See Section 9.G below for the complete, non-negotiable ban. The em-dash character is forbidden in headlines, eyebrows, pills, body copy, quotes, attribution, captions, button text, and alt text. Use the regular hyphen (`-`). -* **NO `
    `-broken-and-italicized headlines** as a default "design move." "for thirty\*years.*" type splits. Headlines should read naturally first, get clever only when the brief demands it. -* **NO vertical rotated text** ("INDEX OF WORK, 2018 - 2026" rotated 90°). Agency-portfolio cliché. Use it only when the brief is explicitly agency / Awwwards / experimental AND it serves a real composition purpose. -* **NO crosshair / hairline grid lines as decoration.** Vertical and horizontal lines drawn just to make the page "feel designed" - banned. Use them only when they organize real content. - -**Fake product previews** -* **NO div-based fake product UI in the hero** (fake task list, fake terminal, fake dashboard built from styled divs). It is the #1 LLM-design Tell. Use a real screenshot, a generated image, a real component preview, or none at all. -* **NO fake version footers** ("v0.6.2-rc.1", "last sync 4s ago · main") inside fake screenshots. Adds nothing, screams AI. - -**Marketing-copy Tells** -* **NO "Quietly in use at" / "Quietly trusted by"** social-proof headers. Use natural language: "Trusted by", "Used at", "Customers include", or skip the heading entirely if the logos speak. -* **NO "From the field" / "Field notes" / "Currently on the bench" / "On our desks" / "Loose plates" style poetic labels** on quote, blog, or sidebar sections. Reads as performative-craftsman. Use plain functional labels ("Testimonials", "Latest writing", "Now working on") or skip the label. -* **NO "We respect the French ones"-style** mock-humble industry-references in body copy. Cute and AI-y. -* **NO weather / locale strips** ("LIS 14:23 · 18°C") in headers/footers unless the brief is explicitly about a place / time-zone-distributed studio. -* **NO micro-meta-sentences under eyebrows.** Sentences like *"Each of these is a feature we ship today, not a roadmap promise. The list will stay short on purpose."* sitting under a section heading are clutter. Eyebrow + Headline + Body is enough. -* **NO generic step labels.** "Stage 1 / Stage 2 / Stage 3", "Step 1 / Step 2 / Step 3", "Phase 01 / Phase 02 / Phase 03", "Pass One / Pass Two / Pass Three". Banned. The actual step content is the label. If you must show progression, use the verb-noun directly ("Install", "Configure", "Ship") not "Stage 1: Install". - -**Pills, labels and version stamps** -* **NO pills/labels/tags overlaid on images.** No `` overlays on photos with tags like `Brand · 02`, `PLATE · BRAND`, `Field notes - journal`. Either let the image speak alone, or add a caption directly below (outside the image). -* **NO photo-credit captions as decoration.** Strings like `Field study no. 12 · Ines Caetano`, `Plate 03 · House archive`, `Frame XII · 35mm` under stock/picsum images are pretentious. Photo credit is allowed ONLY when there is a real photographer being credited for a real photo (with permission). Otherwise: skip the caption or use a one-line functional caption ("The 6-quart, in Sage."). -* **NO version footers on marketing pages.** Footer strings like `v1.4.2`, `Build 0048`, `last sync 4s ago · main` are CLI / devtool fixtures, not landing-page content. Banned on marketing/landing/portfolio pages. -* **NO "Reservation 412 of 800"-style live-stock counters** as decoration. Only if the brief is explicitly a limited-run waitlist with real data. - -**Decoration text strips** -* **NO decoration text strip at hero bottom.** Patterns like `BRAND. MOTION. SPATIAL.`, `TYPE / FORM / MOTION`, `DESIGN · BUILD · SHIP`, `ESTD. 2018 · LISBON · BRAND. MOTION. SPATIAL.` as a small mono-caps strip across the bottom of the hero are an agency-portfolio cliché. Banned by default. Only acceptable when the strip carries real, navigable links (sticky bottom nav) or real status info (cookie banner, build info on a docs site). -* **NO floating top-right sub-text in section headings.** Pattern: section has a giant left-aligned headline; in the top-right corner of the same section header there is a small explainer paragraph floating with no clear alignment to anything else. That floater is the Tell. Either put the sub-text directly under the headline, or build a clean 2-column header (left: headline, right: aligned body), but not a tiny corner paragraph. - -**Lists, dividers and scoring** -* **NO `border-t` + `border-b` on every row of a long list / spec table.** Pick one (bottom-border between rows OR top-border above the group) and use it sparsely. A 10-row spec table with hairlines under each row is the laziest layout - see Section 4.9 for alternative UI components. -* **NO scoring/progress bars with filled background tracks** as comparison visuals. If you need to show "X out of Y" comparisons, prefer a number + small icon, or a tiny inline bar WITHOUT a background track. Big filled `bg-zinc-200` tracks with a partial fill on top are dashboard-UI clutter on a landing page. - -**Locale, time, scroll cues** -* **Locale / city-name / time / weather strips are banned for 99% of briefs.** "Lisbon, working with founders" in the hero, "1200-690 Lisbon, Portugal" in the footer, "Lisbon 14:23 · 18°C" in the nav. These are agency-portfolio decoration tells. Allowed ONLY when: the brief explicitly describes a globally-distributed studio with timezone-relevant work, OR a travel-focused brand, OR a real-world physical venue. A single contact-address mention in the footer is fine; an atmospheric locale strip is not. -* **Scroll cues are banned.** `Scroll`, `↓ scroll`, `Scroll to explore`, `Scroll to walk through it`, animated mouse-wheel icons. If the user has not scrolled yet, they are looking at the hero. They know what scroll is. The bottom of the viewport does not need a label. -* **ZERO decorative status dots by default.** A coloured dot before nav items, before list rows, before badges, before status labels is a Tell. Only acceptable when conveying real semantic state (a live indicator on actual server status, a live availability flag) and limited to one per page section. - -### 9.G EM-DASH BAN (the single most-violated Tell) - -**Em-dash (`—`) is COMPLETELY banned.** It is the LLM's signature stylistic crutch and it is the #1 visual Tell in production tests. There is no "limited use" allowance, no "natural language frequency" allowance, no "in body copy is fine" allowance. None. - -* **Banned in headlines.** Use a period or a comma. -* **Banned in eyebrows / labels / pills / button text / image captions / nav items.** Replace with line breaks, columns, or hairlines. -* **Banned in body copy.** Restructure the sentence: two sentences with a period, OR a comma, OR parentheses, OR a colon. -* **Banned in quote attribution.** Use a normal hyphen with spaces (` - `) or a line break + smaller-weight name. -* **Banned in en-dash form too (`–`) when used as a separator.** Date ranges (`2018-2026`) use a hyphen. Number ranges (`€40-80k`) use a hyphen. - -The ONLY permitted dash characters on the page are: -* Regular hyphen `-` (for compound words, ranges, line dividers in markup) -* Minus sign in math (`-5°C`) - -If your output contains a single `—` or `–` anywhere visible to the user, the output fails the Pre-Flight Check and must be rewritten. - -This rule is non-negotiable. The agent has historically ignored em-dash limits when phrased as "use sparingly." The phrasing here is binary: zero em-dashes. - ---- - -## 10. REFERENCE VOCABULARY (Pattern Names the Agent Should Know) - -This is a vocabulary, not a library. The agent should KNOW these pattern names to communicate about them, design with them in mind, and reach for them when the design read calls for them. **Implementations and code sketches live in the Block Library (Section 12), which is populated iteratively.** - -### Hero Paradigms -* **Asymmetric Split Hero** - Text on one side, asset on the other, generous white space. -* **Editorial Manifesto Hero** - Large type, no asset, almost-poster. -* **Video / Media Mask Hero** - Type cut out as mask over video background. -* **Kinetic-Type Hero** - Animated typography as the primary visual. -* **Curtain-Reveal Hero** - Hero parts on scroll like a curtain. -* **Scroll-Pinned Hero** - Hero stays pinned while content scrolls behind. - -### Navigation & Menus -* **Mac OS Dock Magnification** - Edge nav, icons scale fluidly on hover. -* **Magnetic Button** - Pulls toward cursor. -* **Gooey Menu** - Sub-items detach like viscous liquid. -* **Dynamic Island** - Morphing pill for status / alerts. -* **Contextual Radial Menu** - Circular menu expanding at click point. -* **Floating Speed Dial** - FAB springing into curved secondary actions. -* **Mega Menu Reveal** - Full-screen dropdown, stagger-fade content. - -### Layout & Grids -* **Bento Grid** - Asymmetric tile grouping (Apple Control Center). -* **Masonry Layout** - Staggered grid, no fixed row height. -* **Chroma Grid** - Borders / tiles with subtle animating gradients. -* **Split-Screen Scroll** - Two halves sliding in opposite directions. -* **Sticky-Stack Sections** - Sections that pin and stack on scroll. - -### Cards & Containers -* **Parallax Tilt Card** - 3D tilt tracking mouse coordinates. -* **Spotlight Border Card** - Borders illuminate under cursor. -* **Glassmorphism Panel** - Frosted glass with inner refraction. -* **Holographic Foil Card** - Iridescent rainbow shift on hover. -* **Tinder Swipe Stack** - Physical card stack, swipe-away. -* **Morphing Modal** - Button expands into its own dialog. - -### Scroll Animations -* **Sticky Scroll Stack** - Cards stick and physically stack. -* **Horizontal Scroll Hijack** - Vertical scroll → horizontal pan. -* **Locomotive / Sequence Scroll** - Video / 3D sequence tied to scrollbar. -* **Zoom Parallax** - Central background image zooming on scroll. -* **Scroll Progress Path** - SVG line drawing along scroll. -* **Liquid Swipe Transition** - Page transition like viscous liquid. - -### Galleries & Media -* **Dome Gallery** - 3D panoramic gallery. -* **Coverflow Carousel** - 3D carousel with angled edges. -* **Drag-to-Pan Grid** - Boundless draggable canvas. -* **Accordion Image Slider** - Narrow strips expanding on hover. -* **Hover Image Trail** - Mouse leaves popping image trail. -* **Glitch Effect Image** - RGB-channel shift on hover. - -### Typography & Text -* **Kinetic Marquee** - Endless text bands reversing on scroll. -* **Text Mask Reveal** - Massive type as transparent window to video. -* **Text Scramble Effect** - Matrix-style decoding on load / hover. -* **Circular Text Path** - Text curving along spinning circle. -* **Gradient Stroke Animation** - Outlined text with running gradient. -* **Kinetic Typography Grid** - Letters dodging the cursor. - -### Micro-Interactions & Effects -* **Particle Explosion Button** - CTA shatters into particles on success. -* **Liquid Pull-to-Refresh** - Reload indicator like detaching droplets. -* **Skeleton Shimmer** - Shifting light reflection across placeholders. -* **Directional Hover-Aware Button** - Fill enters from cursor's exact side. -* **Ripple Click Effect** - Wave from click coordinates. -* **Animated SVG Line Drawing** - Vectors drawing themselves in real time. -* **Mesh Gradient Background** - Organic lava-lamp blobs. -* **Lens Blur Depth** - Background UI blurred to focus foreground action. - -### Animation Library Choice -* **Motion (`motion/react`)** - default for UI / Bento / state-change motion. -* **GSAP + ScrollTrigger** - for full-page scrolltelling and scroll hijacks. Isolate in dedicated leaf components with `useEffect` cleanup. -* **Three.js / WebGL** - for canvas backgrounds and 3D scenes. Same isolation rule. -* **NEVER mix GSAP / Three.js with Motion in the same component tree.** They fight over the same frames. - ---- - -## 11. REDESIGN PROTOCOL - -This skill handles **greenfield builds AND redesigns**. Misclassifying the mode is the single biggest source of bad redesign output. - -### 11.A Detect the Mode (first action) -* **Greenfield** - no existing site, or full overhaul approved. Dial baseline from Section 1. -* **Redesign - Preserve** - modernise without breaking the brand. Audit first, extract brand tokens, evolve gradually. -* **Redesign - Overhaul** - new visual language on top of existing content. Treat as greenfield for visuals; preserve content and IA. - -If ambiguous, ask **once**: *"Should this redesign preserve the existing brand, or are we starting visually from scratch?"* - -### 11.B Audit Before Touching -Document the current state before proposing changes: -* **Brand tokens** - primary / accent colors, type stack, logo treatment, radii. -* **Information architecture** - page tree, primary nav, key conversion paths. -* **Content blocks** - what exists, what's doing work, what's filler. -* **Patterns to preserve** - signature interactions, recognisable hero, copy voice. -* **Patterns to retire** - AI-slop tells, broken layouts, dead links, generic stock imagery, perf traps. -* **Dial reading of the existing site** - infer current `DESIGN_VARIANCE` / `MOTION_INTENSITY` / `VISUAL_DENSITY`. That's your starting point, not the baseline. -* **SEO baseline** - current ranking pages, meta titles, structured data, OG cards. **SEO migration is the #1 redesign risk.** - -### 11.C Preservation Rules -* **Do not change information architecture** unless asked. Keep page slugs, anchor IDs, primary nav labels stable for SEO and muscle memory. -* **Extract brand colors before applying Section 4.2.** A brand that is already purple stays purple - apply the LILA RULE's override. -* **Preserve copy voice** unless asked for a rewrite. Visual modernisation ≠ content rewrite. -* **Honor existing accessibility wins.** Do not regress focus states, alt text, keyboard nav, contrast. -* **Respect existing analytics events.** Do not rename buttons, form fields, section IDs that downstream tracking depends on. - -### 11.D Modernisation Levers (priority order) -Apply in order - stop when the brief is satisfied: -1. **Typography refresh** - biggest visual lift per unit of risk. -2. **Spacing & rhythm** - increase section padding, fix vertical rhythm. -3. **Color recalibration** - desaturate, unify neutrals, keep brand accent. -4. **Motion layer** - add `MOTION_INTENSITY`-appropriate micro-interactions to existing components. -5. **Hero & key-section recomposition** - restructure top-of-funnel using Section 10 vocabulary. -6. **Full block replacement** - only when the existing block is unsalvageable. - -### 11.E Decision Tree: Targeted Evolution vs Full Redesign -* IA, content, and SEO sound → **targeted evolution** (Levers 1-4). ~70% of value at ~40% of risk. -* Visual debt is structural (broken IA, no design system, broken mobile) → **full redesign** with strict content preservation. -* Brand itself is changing → **greenfield**. - -### 11.F What Never Changes Silently -Never modify without explicit user approval: -* URL structure / route slugs. -* Primary nav labels. -* Form field names or order (breaks analytics + autofill). -* Brand logo or wordmark. -* Existing legal / consent / cookie copy. - ---- - -## 12. THE BLOCK LIBRARY (Contract - Implementations Land Here Iteratively) - -The Reference Vocabulary (Section 10) names patterns. The Block Library implements them with real props, real motion specs, and real code sketches. - -**Status:** schema defined here. Blocks will be added iteratively. Do not freelance new blocks without following this schema. - -### 12.A File Location -``` -skills/taste-skill/blocks/ - hero/ - asymmetric-split.md - editorial-manifesto.md - kinetic-type.md - ... - feature/ - bento-grid.md - sticky-scroll-stack.md - zig-zag.md - ... - social-proof/ - pricing/ - cta/ - footer/ - navigation/ - portfolio/ - transition/ -``` - -### 12.B Required Frontmatter -```yaml ---- -name: asymmetric-split-hero -category: hero -dial_compatibility: - variance: [6, 10] - motion: [3, 10] - density: [2, 5] -when_to_use: "Landing pages with one strong asset and one strong message. Default hero for SaaS, agency, premium consumer." -not_for: "Editorial / manifesto launches where the message IS the design." -stack: ["react", "next", "tailwind", "motion"] ---- -``` - -### 12.C Required Body Sections -1. **Visual sketch** - short ASCII or description of the layout. -2. **Props API** - the component's interface. -3. **Code sketch** - minimal working implementation (Server Component default, Client island for motion). -4. **Mobile fallback** - explicit collapse rules for `< 768px`. -5. **Motion variants** - one variant per `MOTION_INTENSITY` band (1-3, 4-7, 8-10). Reduced-motion fallback explicit. -6. **Dark-mode notes** - token strategy specific to this block. -7. **Anti-patterns** - common ways this block goes wrong. -8. **References** - links to real examples in production. - -### 12.D Block-Library Discipline -* One block per file. No multi-block files. -* Every block must work standalone (drop it into a page, it renders). -* Every block must pass the Pre-Flight Check (Section 14). -* Blocks that depend on a design system from Section 2.A live under `blocks//--.md` (e.g. `feature/bento-grid--material.md`). - ---- - -## 13. OUT OF SCOPE - -This skill is NOT for: -* Dashboards / dense product UI / admin panels (use Fluent, Carbon, Atlassian, or Polaris from Section 2.A). -* Data tables (use TanStack Table or AG Grid). -* Multi-step forms / wizards (use Form-specific patterns; this skill won't make them better). -* Code editors (use Monaco / CodeMirror with their official skinning). -* Native mobile (use Apple HIG / Material directly). -* Realtime collab UIs (presence, cursors, OT-aware - different problem class). - -If the brief is one of the above, **say so explicitly**, point to the right tool, and only apply this skill's marketing-page / about-page / landing-page parts to the surfaces where they apply. - ---- - -## 14. FINAL PRE-FLIGHT CHECK - -Run this matrix before outputting code. This is the last filter. - -**THIS IS NOT OPTIONAL. Run every box. If any box fails, the output is not done.** - -- [ ] **Brief inference** declared (Section 0.B one-liner)? -- [ ] **Dial values** explicit and reasoned from the brief, not silently using baseline? -- [ ] **Design system** chosen from Section 2 if applicable, or aesthetic labeled honestly? -- [ ] **Redesign mode** detected and audit performed (if applicable, Section 11)? -- [ ] **ZERO em-dashes (`—`) anywhere on the page.** Headlines, eyebrows, pills, body, quotes, attribution, captions, buttons, alt text. Zero. (Section 9.G - non-negotiable.) -- [ ] **Page Theme Lock**: ONE theme (light, dark, or auto) for the whole page. No section flips to inverted mode mid-page (Section 4.11)? -- [ ] **Color Consistency Lock**: one accent color used identically across all sections (Section 4.2)? -- [ ] **Shape Consistency Lock**: one corner-radius system applied consistently (Section 4.4)? -- [ ] **Button Contrast Check**: every CTA text is readable against its background (no white-on-white, WCAG AA 4.5:1)? -- [ ] **CTA Button Wrap**: no CTA label wraps to 2+ lines at desktop? -- [ ] **Form Contrast Check**: form inputs, placeholders, focus rings, labels all pass WCAG AA against the section background? -- [ ] **Serif discipline**: if a serif is used, it is NOT Fraunces or Instrument_Serif (or it is, with explicit brand justification)? Different serif from your previous project? -- [ ] **Premium-consumer palette check**: if the brief is premium-consumer (cookware / wellness / artisan / luxury), the palette is NOT the AI-default beige+brass+oxblood+espresso family? Different family from your previous premium-consumer project? -- [ ] **Italic descender clearance**: every italic word with `y g j p q` has `leading-[1.1]` min + `pb-1` reserve? -- [ ] **Hero fits the viewport**: headline ≤ 2 lines, subtext ≤ 20 words AND ≤ 4 lines, CTA visible without scroll, font scale planned around image? -- [ ] **Hero top padding**: max `pt-24` at desktop, hero content does not float halfway down the viewport? -- [ ] **Hero stack discipline**: max 4 text elements in hero (eyebrow OR brand strip, headline, subtext, CTAs)? No tiny tagline below CTAs, no trust micro-strip in hero? -- [ ] **EYEBROW COUNT (mechanical)**: count instances of `uppercase tracking` micro-labels above section headlines across all components. Count ≤ ceil(sectionCount / 3)? Hero counts as 1. -- [ ] **Split-Header Ban**: no "left big headline + right small explainer paragraph" pattern as a section header (vertical stack instead)? -- [ ] **Zigzag Alternation Cap**: no 3+ consecutive sections with the same image+text-split layout? -- [ ] **No Duplicate CTA Intent**: no two CTAs with the same intent ("Get in touch" + "Let's talk" both on page = Fail)? -- [ ] **Logo wall = logo only**: no industry / category labels printed below logos? -- [ ] **Bento Background Diversity**: at least 2-3 bento cells have real visual variation (image, gradient, pattern), not all white-on-white text cards? -- [ ] **"Used by / Trusted by" logo wall** lives UNDER the hero, not inside it, uses REAL SVG logos (Simple Icons / devicon) or generated SVG marks, NOT plain text wordmarks? -- [ ] **Copy Self-Audit**: every visible string re-read, no grammatically-broken or AI-hallucinated phrases ("free on its past" type) shipped? -- [ ] **Motion motivated**: every animation can be justified in one sentence (hierarchy / storytelling / feedback / state transition), no GSAP-for-show? -- [ ] **Marquee max-one-per-page**: no two horizontal marquees on the same page? -- [ ] **Navigation on ONE line** at desktop, height ≤ 80px? -- [ ] **Section-Layout-Repetition** check: no two sections share the same layout family (at least 4 different families across 8 sections)? -- [ ] **Bento has rhythm AND exact cell count** (N items → N cells, no empty cells in middle or at end)? -- [ ] **Long lists use the right UI component** (not default `
      ` with `divide-y` for > 5 items - see Section 4.9 alternatives)? -- [ ] **Real images used** (gen-tool first, then Picsum-seed, then explicit placeholder slots) - NO div-based fake screenshots, NO hand-rolled decorative SVGs, NO pure-text minimalism? -- [ ] **No pills/labels overlaid on images** (no `Plate · Brand`, no `Field notes - journal`)? -- [ ] **No photo-credit captions as decoration** (`Field study no. 12 · Ines Caetano`)? -- [ ] **No version footers** (`v1.4.2`, `Build 0048`) on marketing pages? -- [ ] **No micro-meta-sentences** under eyebrows ("Each of these is a feature we ship today...")? -- [ ] **No decoration text strip at hero bottom** (`BRAND. MOTION. SPATIAL.`)? -- [ ] **No floating top-right sub-text** in section headings? -- [ ] **No scoring/progress bars with filled background tracks** as comparison visuals? -- [ ] **No locale / city-name / time / weather strips** unless brief is genuinely globally-distributed or place-focused? -- [ ] **No scroll cues** (`Scroll`, `↓ scroll`, `Scroll to explore`)? -- [ ] **No version labels in hero** (V0.6, BETA, INVITE-ONLY) unless the brief is a launch? -- [ ] **No section-numbering eyebrows** (`00 / INDEX`, `001 · Capabilities`, `06 · how it works`)? -- [ ] **No decorative dots** (zero by default, only for real semantic state)? -- [ ] **No `border-t` + `border-b` on every row** of long lists / spec tables? -- [ ] **Content density** sane: no 20-row data tables, no fake-precise specs without justification, ≤ 25-word sub-paragraphs by default? -- [ ] **Quotes ≤ 3 lines** of body, attribution clean (no em-dash)? -- [ ] **Motion claimed = motion shown**: if `MOTION_INTENSITY > 4`, page actually animates, not just claimed? -- [ ] **GSAP sticky-stack / horizontal-pan** implemented per Section 5.A / 5.B canonical skeleton (`start: "top top"`, `pin: true`, correct scrub)? -- [ ] **No `window.addEventListener('scroll')`** - using Motion `useScroll()` / ScrollTrigger / IntersectionObserver / CSS scroll-driven animations only? -- [ ] **Reduced motion** wrapped for everything `MOTION_INTENSITY > 3`? -- [ ] **Dark mode** tokens defined and tested in both modes? -- [ ] **Mobile collapse** explicit (`w-full`, `px-4`, `max-w-7xl mx-auto`) for high-variance layouts? -- [ ] **Viewport stability**: `min-h-[100dvh]`, never `h-screen`? -- [ ] **`useEffect` animations** have strict cleanup functions? -- [ ] **Empty / loading / error** states provided? -- [ ] **Cards omitted** in favor of spacing where possible? -- [ ] **Icons** from an allowed library only (Phosphor / HugeIcons / Radix / Tabler), no hand-rolled SVG paths? -- [ ] **Motion** isolated in client-leaf components with `'use client'` at the top, memoized? -- [ ] **No AI Tells** from Section 9 (Inter as default, AI-purple, three-equal cards, Jane Doe, Acme, "Quietly in use at")? -- [ ] **Core Web Vitals** plausibly hit (LCP < 2.5s, INP < 200ms, CLS < 0.1)? -- [ ] **One design system** per project (no Material + shadcn mixed)? - -If a single checkbox cannot be honestly ticked, the page is not done. Fix it before delivering. - ---- - -# APPENDICES - Real Source-Backed Reference Material - -The sections below are vendored reference content. They give the agent real install commands, real canonical doc links, and real working starter snippets for each design system named in Section 2. Use them to ground decisions in production reality, not training-data fiction. - -## Appendix A - Install Commands per Design System - -```bash -# Material Web (Material 3) -npm install @material/web - -# Fluent UI React (v9) -npm install @fluentui/react-components - -# Fluent UI Web Components (framework-free) -npm install @fluentui/web-components @fluentui/tokens - -# IBM Carbon -npm install @carbon/react @carbon/styles - -# Radix Themes -npm install @radix-ui/themes - -# shadcn/ui (open code, owned components) -npx shadcn@latest init -npx shadcn@latest add button card badge separator input - -# Primer CSS (GitHub product/devtool UI) -npm install --save @primer/css - -# Primer Brand (GitHub marketing UI) -npm install @primer/react-brand - -# GOV.UK Frontend -npm install govuk-frontend - -# USWDS (US Web Design System) -npm install uswds - -# Atlassian Design System (Atlaskit) -yarn add @atlaskit/css-reset @atlaskit/tokens @atlaskit/button @atlaskit/badge @atlaskit/section-message @atlaskit/card - -# Bootstrap 5.3 -npm install bootstrap - -# Shopify Polaris Web Components (Shopify apps only) -# Add this to your app HTML head: -# -# -``` - -## Appendix B - Canonical Sources (read these before reinventing) - -### Material Web -- https://github.com/material-components/material-web -- https://material-web.dev/theming/material-theming/ -- https://m3.material.io/develop/web - -### Fluent UI -- https://fluent2.microsoft.design/get-started/develop -- https://fluent2.microsoft.design/components/web/react/ -- https://github.com/microsoft/fluentui -- https://learn.microsoft.com/en-us/fluent-ui/web-components/ - -### Carbon -- https://carbondesignsystem.com/ -- https://github.com/carbon-design-system/carbon -- https://carbondesignsystem.com/developing/react-tutorial/overview/ -- https://carbondesignsystem.com/developing/web-components-tutorial/overview/ - -### Shopify Polaris -- https://shopify.dev/docs/api/app-home/web-components -- https://github.com/Shopify/polaris-react -- https://polaris-react.shopify.com/components - -### Atlassian -- https://atlassian.design/get-started/develop -- https://atlassian.design/components/button/examples -- https://atlaskit.atlassian.com/packages/design-system/button/example/disabled -- https://atlassian.design/tokens/design-tokens - -### Primer -- https://primer.style/ -- https://github.com/primer/css -- https://github.com/primer/brand - -### GOV.UK -- https://design-system.service.gov.uk/components/button/ -- https://design-system.service.gov.uk/styles/layout/ -- https://github.com/alphagov/govuk-frontend - -### USWDS -- https://designsystem.digital.gov/documentation/developers/ -- https://designsystem.digital.gov/components/button/ -- https://designsystem.digital.gov/components/card/ -- https://github.com/uswds/uswds - -### Bootstrap -- https://getbootstrap.com/docs/5.3/layout/grid/ -- https://getbootstrap.com/docs/5.3/components/card/ - -### Tailwind -- https://tailwindcss.com/docs/dark-mode -- https://tailwindcss.com/blog/tailwindcss-v4 - -### Radix -- https://www.radix-ui.com/themes/docs/components/theme -- https://www.radix-ui.com/themes/docs/components/card -- https://github.com/radix-ui/themes - -### shadcn/ui -- https://ui.shadcn.com/docs -- https://ui.shadcn.com/docs/components/card -- https://github.com/shadcn-ui/ui - -### Native CSS / W3C standards -- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/backdrop-filter -- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme -- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion -- https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout -- https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll-driven_animations -- https://drafts.csswg.org/scroll-animations-1/ - -### Apple Liquid Glass (Apple platforms only) -- https://developer.apple.com/design/human-interface-guidelines/materials -- https://developer.apple.com/documentation/TechnologyOverviews/liquid-glass -- https://developer.apple.com/documentation/TechnologyOverviews/adopting-liquid-glass -- https://developer.apple.com/documentation/SwiftUI/Material - ---- - -## Appendix C - Apple Liquid Glass: Honest Web Approximation - -Do **not** treat random CSS snippets as official Apple Liquid Glass. - -### What is official -Apple documents Liquid Glass inside Apple's Human Interface Guidelines and Developer Documentation for **Apple platforms**. It is a dynamic material used across Apple platform UI. Apple's native implementation belongs to Apple platform APIs and system components, **not a public web CSS package**. - -Relevant official docs: -- Apple Human Interface Guidelines → Materials -- Apple Developer Documentation → Liquid Glass -- Apple Developer Documentation → Adopting Liquid Glass -- SwiftUI → Material - -### What is NOT official -There is no `liquid-glass.css` from Apple for normal websites. - -A web approximation can use: -- `backdrop-filter` -- transparent backgrounds -- layered borders -- highlight overlays -- gradients -- motion -- strong contrast fallbacks - -But that is **web glassmorphism / frosted-glass approximation**, not official Apple Liquid Glass. Label it as such in comments. - -### Safer web approximation skeleton - -```css -.liquid-glass-web-approx { - position: relative; - isolation: isolate; - overflow: hidden; - border-radius: 999px; - border: 1px solid rgb(255 255 255 / .32); - background: - linear-gradient(135deg, rgb(255 255 255 / .30), rgb(255 255 255 / .08)), - rgb(255 255 255 / .12); - backdrop-filter: blur(24px) saturate(180%) contrast(1.05); - -webkit-backdrop-filter: blur(24px) saturate(180%) contrast(1.05); - box-shadow: - inset 0 1px 0 rgb(255 255 255 / .48), - inset 0 -1px 0 rgb(255 255 255 / .12), - 0 18px 60px rgb(0 0 0 / .18); -} - -.liquid-glass-web-approx::before { - content: ""; - position: absolute; - inset: 0; - z-index: -1; - border-radius: inherit; - background: - radial-gradient(circle at 20% 0%, rgb(255 255 255 / .55), transparent 34%), - linear-gradient(90deg, rgb(255 255 255 / .18), transparent 42%, rgb(255 255 255 / .14)); - pointer-events: none; -} - -.liquid-glass-web-approx::after { - content: ""; - position: absolute; - inset: 1px; - border-radius: inherit; - border: 1px solid rgb(255 255 255 / .14); - pointer-events: none; -} - -@media (prefers-color-scheme: dark) { - .liquid-glass-web-approx { - border-color: rgb(255 255 255 / .18); - background: - linear-gradient(135deg, rgb(255 255 255 / .16), rgb(255 255 255 / .04)), - rgb(15 23 42 / .42); - box-shadow: - inset 0 1px 0 rgb(255 255 255 / .22), - 0 18px 60px rgb(0 0 0 / .42); - } -} - -@media (prefers-reduced-transparency: reduce) { - .liquid-glass-web-approx { - background: rgb(255 255 255 / .96); - backdrop-filter: none; - -webkit-backdrop-filter: none; - } -} -``` - -**Important:** `prefers-reduced-transparency` has uneven browser support; test it. Always provide enough contrast even without blur. - ---- - -**End of appendices.** Install commands above are reality anchors. The Apple Liquid Glass skeleton is a labeled approximation, not an Apple-issued package. For canonical docs per design system, consult the system's official docs (links in Section 2 plus Appendix B). diff --git a/.workbuddy/test-cases/2026-08-31_俊希全流程.md b/.workbuddy/test-cases/2026-08-31_俊希全流程.md index 2c29755..599a62a 100644 --- a/.workbuddy/test-cases/2026-08-31_俊希全流程.md +++ b/.workbuddy/test-cases/2026-08-31_俊希全流程.md @@ -53,7 +53,7 @@ |-------|-------|---------|-----------| | 项目文件夹(桌面 `MCNSkill项目/俊希/`) | 账号设定/视频表格/脚本 | **仅 `提示词01/`**(分镜产出,属短视频分镜提示词技能) | **否**(本链路产出全新创建;提示词01 保留) | | 达人账号表.xlsx | 俊希行 | **无** | 否(首次登记 70014) | -| 工作台 mcn-plugin.db(`…\mcn-work-shop\`,自有副本) | hot_accounts 俊希 | **有**(id=3,含完整账号信息 + content 账号设定文本、douyin_id=JJX0827、sec_uid、278.7万粉、source_dir=`D:\dshworkspace\账号解析\俊希` 为**旧路径残留**,目录已不存在) | **否**(见下判定) | +| 工作台 mcn-plugin.db(`…\mcn-workshop\`,自有副本) | hot_accounts 俊希 | **有**(id=3,含完整账号信息 + content 账号设定文本、douyin_id=JJX0827、sec_uid、278.7万粉、source_dir=`D:\dshworkspace\账号解析\俊希` 为**旧路径残留**,目录已不存在) | **否**(见下判定) | | 同上 | account_videos 俊希 | **有 108 条**(source_dir=`D:\dshworkspace\抖音账号分析\俊希` 旧路径) | 否 | | 同上 | account_persona 俊希 | **无**(5 行均为 1627-1630 测试占位,无 account_id=3) | 否 | | 同上 | rewrite_log 俊希 | **有 3 条**(2026-08-19 AI 写脚本记录,id=12/15/16,topic 含完整选题) | 否 | @@ -65,7 +65,7 @@ 1. **文件层面**:桌面 `MCNSkill项目/俊希/` 无本链路产出 → **无需清除**,全新创建;已有产出时(有就刷新)→ 覆盖更新 `短视频表格.xlsx`、新增/更新设定与脚本,**不删除旧产出**(保留可追溯) 2. **数据库层面(工作台 mcn-plugin.db 自有副本)**:俊希有历史数据但**不影响本测试**——关键判定 `account_persona` 无俊希 → 工作台 `confirmAiScript` 的 hasPersona=false → 弹窗走「先进行账号设定分析」分支,**首次场景成立**(hot_accounts.content 的旧设定文本不会被界面读取,界面只读 account_persona)。因此**无需清除**;若追求「完全干净」需用户明确授权后选择性清除(hot_accounts id=3 及其 videos/rewrite_log 行),且只动工作台自有副本 3. **dsh 原库**:**红线只读,绝不写**(清理不适用) -4. **工作台副本与 dsh 原库的数据同步**:工作台读自有副本(`mcn-work-shop/mcn-plugin.db`),与 `~/.dsh/mcn-plugin.db` 隔离;测试新增数据(rewrite_log 第 4 条等)只落工作台副本,不影响 dsh 原库 +4. **工作台副本与 dsh 原库的数据同步**:工作台读自有副本(`mcn-workshop/mcn-plugin.db`),与 `~/.dsh/mcn-plugin.db` 隔离;测试新增数据(rewrite_log 第 4 条等)只落工作台副本,不影响 dsh 原库 ### 清除操作(如用户要求执行,需先备份) @@ -153,7 +153,7 @@ ### TC-09 工作台 AI 参考最新视频选题(**界面模拟点击**) -> **本环节用可执行脚本模拟真实点击测试**,脚本:`mcn-work-shop/cdp-test-junxi.mjs` +> **本环节用可执行脚本模拟真实点击测试**,脚本:`mcn-workshop/cdp-test-junxi.mjs` > 用法:`node cdp-test-junxi.mjs`(安全模式:验证到选题生成,不触发任务);`node cdp-test-junxi.mjs --confirm`(继续确认执行,真实触发创作任务) > 机制:连接浏览器 9223 调试实例 → 新开标签页打开 `#/accounts` → `Runtime.evaluate` 模拟点击 → 轮询弹窗/AI 选题 → 自动关闭标签页(不干扰用户页面)。脚本复用 cdp-verify-*.mjs 的 CDP 模式。 @@ -248,7 +248,7 @@ - `MCNSkill项目/俊希/` 现有仅 `提示词01/` 子目录(分镜产出,属短视频分镜提示词技能),无账号分析产出 - **工作台 mcn-plugin.db 俊希历史数据**:hot_accounts id=3(完整账号信息 + content 设定文本,source_dir=`D:\dshworkspace\账号解析\俊希` **旧路径已不存在**)、account_videos 108 条(source_dir=`D:\dshworkspace\抖音账号分析\俊希` 旧路径)、rewrite_log 3 条(2026-08-19,海鲜大餐主题);**account_persona 无俊希** → hasPersona=false → 弹窗走「先进行账号设定分析」分支,首次场景成立,无需清除 - dsh 原库(~/.dsh/mcn-plugin.db)俊希数据与副本一致(.dsh 只读红线不写) -- **界面模拟点击测试脚本**:`mcn-work-shop/cdp-test-junxi.mjs`(2026-08-31 实测通过:账号列表定位俊希 PASS / AI写脚本弹窗结构 PASS / AI 选题 3 个方向 PASS);注意 `.modal` 选择器会被 index.html 隐藏的 `#settingsModal` 干扰,须限定 `:not([hidden])` 且含 `#aiTopicGen` +- **界面模拟点击测试脚本**:`mcn-workshop/cdp-test-junxi.mjs`(2026-08-31 实测通过:账号列表定位俊希 PASS / AI写脚本弹窗结构 PASS / AI 选题 3 个方向 PASS);注意 `.modal` 选择器会被 index.html 隐藏的 `#settingsModal` 干扰,须限定 `:not([hidden])` 且含 `#aiTopicGen` - MCP 解析单条视频需等待 3-5 分钟;已解析过的视频可直接查询 - 工作台 #/reviews「AI脚本诊断」复盘按钮明确按 **mcn-script-review(脚本复盘)技能**执行(本用例主链路不含复盘,如需回归可追加) - 产出根目录:开发机 → 桌面 `MCNSkill项目/{达人昵称}/`(`references/路径配置.md` 权威) diff --git a/P1修复方案_V1.0_20260914.md b/P1修复方案_V1.0_20260914.md index 0fe9b4e..94c1bfa 100644 --- a/P1修复方案_V1.0_20260914.md +++ b/P1修复方案_V1.0_20260914.md @@ -60,8 +60,8 @@ ### P1-2 | 契约表漏登记 2 个 AI 入口 **现象** -- `mcn-work-shop/docs/AI会话任务输入输出对照.md`:入口表只有 1–9 行(9 为 clarify,非任务);L127 声明「前缀常量 = CREATE/EXTRACT/STORYBOARD/REVIEW + ACCOUNT」;检查清单第 1 项同样只列 5 个。 -- 实际 `mcn-work-shop/public/data-pages.js` 还有两个入口: +- `mcn-workshop/docs/AI会话任务输入输出对照.md`:入口表只有 1–9 行(9 为 clarify,非任务);L127 声明「前缀常量 = CREATE/EXTRACT/STORYBOARD/REVIEW + ACCOUNT」;检查清单第 1 项同样只列 5 个。 +- 实际 `mcn-workshop/public/data-pages.js` 还有两个入口: | 入口 | 触发点 | 前缀常量 | skills | label | 代码位置 | |------|------|------|------|------|------| @@ -78,10 +78,10 @@ | 2 | `对照.md:127` 关键细节 | 常量清单补全为 `SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ANALYZE/VIDEO_PARSE/ACCOUNT`,并注明**权威来源 = data-pages.js / app.js 顶部实际声明**(行号会漂,故不给行号,只给文件) | | 3 | `对照.md:137` 检查清单第 1 项 | 前缀对应关系补 `分析=ANALYZE、视频解析=VIDEO_PARSE` | | 4 | `对照.md` 新增入口检查清单 | 追加第 8 项:`[ ] 表码对齐:本次新增/改动的入口已回填本表入口表行(含 skills / 前缀常量 / 代码位置),并核对 server.js BROWSER_HINT 正则不被误命中` | -| 5 | `SKILL.md:81` | `(SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ACCOUNT,分别对应 创作/提炼/分镜/复盘/账号抓取)` → `(SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ANALYZE/VIDEO_PARSE/ACCOUNT,分别对应 创作/提炼/分镜/复盘/账号数据分析/视频解析/账号抓取;完整清单以 mcn-work-shop/docs/AI会话任务输入输出对照.md 为准)` | +| 5 | `SKILL.md:81` | `(SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ACCOUNT,分别对应 创作/提炼/分镜/复盘/账号抓取)` → `(SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ANALYZE/VIDEO_PARSE/ACCOUNT,分别对应 创作/提炼/分镜/复盘/账号数据分析/视频解析/账号抓取;完整清单以 mcn-workshop/docs/AI会话任务输入输出对照.md 为准)` | **验证**: -1. `grep -c "SKILL_HINT_" mcn-work-shop/public/data-pages.js mcn-work-shop/public/app.js` 得到的常量名集合 = 对照表清单,一一对应无缺; +1. `grep -c "SKILL_HINT_" mcn-workshop/public/data-pages.js mcn-workshop/public/app.js` 得到的常量名集合 = 对照表清单,一一对应无缺; 2. 逐个入口 grep `skills:` 与表内 skills 列一致。 **风险**:低。纯文档补登记,不改代码逻辑。 @@ -133,7 +133,7 @@ **注意**:`5_生成短视频选题.md:192`「S6 用「本场钩子」五类留人机制」与 S6 场次表列名「本场钩子」是**正确用法**,不动。 -**验证**:`grep -rn "开篇钩子\|钩子预设\|钩子策略" SKILL.md references/ mcn-work-shop/` → 仅允许命中 `references-add/变更日志.md`(历史归档)。 +**验证**:`grep -rn "开篇钩子\|钩子预设\|钩子策略" SKILL.md references/ mcn-workshop/` → 仅允许命中 `references-add/变更日志.md`(历史归档)。 --- @@ -179,7 +179,7 @@ | # | 位置 | 动作 | |:--:|------|------| -| 1 | `references/接口调用/本地接口调用规范.md` | **重写**为《工作台接口调用规范》:① 基地址 — 从 `mcn-work-shop/config.json` 的 `port` 读(默认 8900,`server.js` 支持 argv 覆盖),禁写死端口;② 写库接口 — `POST /api/import/account`,body `{"account":"{达人昵称}"}`,落库映射见 dou-analysis SKILL.md:160;③ 保留「大文本用 Python urllib + `json.dumps(ensure_ascii=False)` 直连,禁 PowerShell `ConvertTo-Json`」这条铁律;④ 接口清单**不另抄一份**,指向 `mcn-work-shop/docs/AI会话任务输入输出对照.md` + `server.js` 路由为权威 | +| 1 | `references/接口调用/本地接口调用规范.md` | **重写**为《工作台接口调用规范》:① 基地址 — 从 `mcn-workshop/config.json` 的 `port` 读(默认 8900,`server.js` 支持 argv 覆盖),禁写死端口;② 写库接口 — `POST /api/import/account`,body `{"account":"{达人昵称}"}`,落库映射见 dou-analysis SKILL.md:160;③ 保留「大文本用 Python urllib + `json.dumps(ensure_ascii=False)` 直连,禁 PowerShell `ConvertTo-Json`」这条铁律;④ 接口清单**不另抄一份**,指向 `mcn-workshop/docs/AI会话任务输入输出对照.md` + `server.js` 路由为权威 | | 2 | `references/铁律避坑规则/账号设定执行避坑.md:12` | 探测方法改为「用 `GET http://127.0.0.1:<工作台端口>/api/dsh/stats` 探连通,不用写接口探测」 | | 3 | `references/铁律避坑规则/账号设定执行避坑.md:25` | 沉淀描述改为「新建 `references/接口调用/本地接口调用规范.md`,记录工作台接口基地址判定、写库接口与请求构造方式」 | | 4 | `feature/05_解析视频.md:55` | 整句改为「解析结果对应「原视频解析」数据:落盘后调用工作台 `POST /api/import/account`(body `{"account":"{达人昵称}"}`)把 `content.json` 补录进 `account_video_source` 并提取选题(topic);工作台未启动时跳过(不影响本地保存)。」 | diff --git a/flova短剧创作界面分析_20260907.md b/flova短剧创作界面分析_20260907.md index 226e8b2..b7e1ec5 100644 --- a/flova短剧创作界面分析_20260907.md +++ b/flova短剧创作界面分析_20260907.md @@ -69,7 +69,7 @@ ### 4. 交互特征小结(对本项目最有参考价值) | 特征 | flova 实现 | 借鉴点 | |------|-----------|--------| -| 双栏创作 | 左画布右对话,**对话永驻不打断** | mcn-work-shop 的 AI 弹窗可改为右侧常驻面板 | +| 双栏创作 | 左画布右对话,**对话永驻不打断** | mcn-workshop 的 AI 弹窗可改为右侧常驻面板 | | 创作规范注入 | 对话首轮自动载入 Skill("Skill已完成"状态行) | 用户选定 Skill 后第一步就声明规范,后续全部按规范执行 | | 分步引导 | AI 每次只确认 1 个参数,确认完再问下一个 | 选题→时长→对白→形象→关系→框架→剧本 的渐进式锁定 | | AI 提供选项 | 回复末尾出 2-4 个**可点选方向**,用户一键续接 | 降低输入成本、保持共创节奏 | diff --git a/project/短视频脚本创作/V1.0/SKILL.md b/project/短视频脚本创作/V1.0/SKILL.md index 4324202..f3bc814 100644 --- a/project/短视频脚本创作/V1.0/SKILL.md +++ b/project/短视频脚本创作/V1.0/SKILL.md @@ -64,21 +64,21 @@ agent_created: true 用户说「打开工作台 / 打开MCN工作台」时: -1. 后台启动本地服务:`node <本技能目录>/mcn-work-shop/server.js`(零依赖 Node,无需安装包;Windows 也可双击 `start.bat`) +1. 后台启动本地服务:`node <本技能目录>/mcn-workshop/server.js`(零依赖 Node,无需安装包;Windows 也可双击 `start.bat`) 2. 用内置浏览器打开 `http://localhost:8900`。**注意端口**:本机 8899 可能被 `MiPCAudio.exe`(小米 PC 管家)占用系统级 0.0.0.0:8899 且杀后自愈,工作台默认端口已迁移为 **8900**;如需指定端口用 `node server.js <端口>`(`PORT_BASE = parseInt(process.argv[2],10) || 8900`) 3. 工作台采用 hash 路由,页面结构: - **首页(#/)**:数据功能卡片(抖音周榜/热门账号/AI写脚本/AI写作复盘)+ 5 大技能功能块(问法点击复制)+ 三种创作模式 - **📈 抖音周榜(#/ranking)**:读桌面 `MCNSkill项目\三方数据\红狐数据\抖音榜单\*.json`(运行时解析真实桌面、兼容 OneDrive 重定向;环境变量 `MCN_RANKING_DIR` 可覆盖),赛道筛选 + 日期切换 + 数值排序 - - **👥 热门账号(#/accounts)**:读工作台自有数据库副本 `mcn-work-shop/mcn-plugin.db`(只读),分页/搜索/赛道/类型筛选,账号点击进详情 + - **👥 热门账号(#/accounts)**:读工作台自有数据库副本 `mcn-workshop/mcn-plugin.db`(只读),分页/搜索/赛道/类型筛选,账号点击进详情 - **账号详情(#/account/:id)**:信息卡 + 账号信息/账号设定/数据分析 3 tab + 视频列表分页;解析/改写/提炼按钮直接提交 AI 任务执行 - **视频详情(#/video/:id)**:信息卡 + 视频脚本/视频拆解/视频分析 3 tab - **✍️ AI写脚本(#/rewrites)**:爆款视频列表 + 源脚本 vs AI脚本双栏对比 + 分镜提示词分块渲染 - **🧪 AI写作复盘(#/reviews)**:评分维度列表 + 复盘详情(评分条/问题清单/报告) - **📁 产出浏览(#/files)**:默认读桌面 `MCNSkill项目/`,支持 ⚙️ 设置添加其他项目地址;md/json 在线预览 -4. **数据边界**:数据库读工作台自有副本 `mcn-work-shop/mcn-plugin.db`(**dsh 侧已整套下线(09-10):`~/.dsh/mcn-plugin.db` 与 `D:\dshworkspace` 均不存在**,工作台 DB 为唯一读写副本);周榜读桌面 `MCNSkill项目\三方数据\红狐数据\抖音榜单\*.json` 文件 -5. **AI 任务触发(Automation 方案)**:各数据页「导入/创作/解析/改写/提炼/复盘」按钮点击后 → 工作台后端 `POST /api/run` → 向 `~/.workbuddy/workbuddy.db` 的 `automations` 表写入一条**一次性任务**(`schedule_type='once'`、**`scheduled_at=now+5s`(带秒)且显式写 `next_run_at=now+5000`**、`cwds=[<工作台目录>]`(由 `server.js` 从 `ROOT` 动态推导,值 = `<技能根>/mcn-work-shop`,**禁止写死盘符**))→ WorkBuddy 客户端调度器按 `next_run_at` 扫描拾取(周期≤30s)→ 创建 AI 会话执行 → **会话归入 `mcn-work-shop` 空间分组**(左侧会话栏按 cwd 分组,可在该分组查看任务执行过程与结果)。参考 dsh 插件 `dsh-plugin-mcn` 的 `/mcn/api/creative` 模式(固定会话 followup 触发);前端 5 秒防连点。**调度机制/状态机/并发上限/排查命令详见同目录 `自动化任务调度机制.md`(09-01 实测定稿,勿再按旧认知 `scheduled_at=now` 直写——会因 next_run_at 为空而卡死)** +4. **数据边界**:数据库读工作台自有副本 `mcn-workshop/mcn-plugin.db`(**dsh 侧已整套下线(09-10):`~/.dsh/mcn-plugin.db` 与 `D:\dshworkspace` 均不存在**,工作台 DB 为唯一读写副本);周榜读桌面 `MCNSkill项目\三方数据\红狐数据\抖音榜单\*.json` 文件 +5. **AI 任务触发(Automation 方案)**:各数据页「导入/创作/解析/改写/提炼/复盘」按钮点击后 → 工作台后端 `POST /api/run` → 向 `~/.workbuddy/workbuddy.db` 的 `automations` 表写入一条**一次性任务**(`schedule_type='once'`、**`scheduled_at=now+5s`(带秒)且显式写 `next_run_at=now+5000`**、`cwds=[<工作台目录>]`(由 `server.js` 从 `ROOT` 动态推导,值 = `<技能根>/mcn-workshop`,**禁止写死盘符**))→ WorkBuddy 客户端调度器按 `next_run_at` 扫描拾取(周期≤30s)→ 创建 AI 会话执行 → **会话归入 `mcn-workshop` 空间分组**(左侧会话栏按 cwd 分组,可在该分组查看任务执行过程与结果)。参考 dsh 插件 `dsh-plugin-mcn` 的 `/mcn/api/creative` 模式(固定会话 followup 触发);前端 5 秒防连点。**调度机制/状态机/并发上限/排查命令详见同目录 `自动化任务调度机制.md`(09-01 实测定稿,勿再按旧认知 `scheduled_at=now` 直写——会因 next_run_at 为空而卡死)** 6. **工作台任务执行方式铁律(P0,09-02 固化)**:凡工作台场景的**创作/改写/重写/复盘/解析**类任务(含"重新执行历史任务"),必须通过 `POST /api/run` 提交 AI 会话任务执行(automation 触发 → 独立 AI 会话按技能 S1-S11 执行 → 写库 rewrite_log/入库),**禁止在会话内直接产出脚本草稿**(不会入库、不在左侧会话栏可见、不走技能会话,等于"影子脚本")。**重新执行历史任务 = 复用原任务的 prompt(人设卡+视频选题+创作要求+落库字段)+ `skills:['短视频工作台']` 提交新任务**——新任务自动加载当前技能版本(含最新优化),无需手工搬内容。执行方式判定细则见「关键规则 〇·执行方式判定」。 -7. **AI 任务入口技能契约(P0,09-02 固化)**:工作台每个触发 AI 会话任务的入口都必须遵守「功能入口 × 技能调用契约」——任务 prompt = **固定技能声明前缀常量**(SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ACCOUNT,分别对应 创作/提炼/分镜/复盘/账号抓取)+ **业务正文**,并传对应 `skills` 数组(参数级技能挂载)。**新增/修改任何入口时先读 `mcn-work-shop/docs/AI会话任务输入输出对照.md`(契约总表 + 新增入口检查清单 7 项),再动手**——防裸 prompt 跳步(09-02 三入口缺约束根因)、防技能声明混入业务串、防非抓取任务命中 server.js BROWSER_HINT 正则被误注入浏览器锁。 +7. **AI 任务入口技能契约(P0,09-02 固化)**:工作台每个触发 AI 会话任务的入口都必须遵守「功能入口 × 技能调用契约」——任务 prompt = **固定技能声明前缀常量**(SKILL_HINT_CREATE/EXTRACT/STORYBOARD/REVIEW/ACCOUNT,分别对应 创作/提炼/分镜/复盘/账号抓取)+ **业务正文**,并传对应 `skills` 数组(参数级技能挂载)。**新增/修改任何入口时先读 `mcn-workshop/docs/AI会话任务输入输出对照.md`(契约总表 + 新增入口检查清单 7 项),再动手**——防裸 prompt 跳步(09-02 三入口缺约束根因)、防技能声明混入业务串、防非抓取任务命中 server.js BROWSER_HINT 正则被误注入浏览器锁。 8. 本功能仅 V1.0 源技能环境提供,**不同步 dsh/Lite 副本** 9. **改完必重开页面(硬性约定)**:对工作台任何页面/代码改动完成后,**最后一步必须用 present_files 重新打开对应页面**(带 hash 直接定位,如 `http://localhost:8900/#/accounts`),让用户在 WorkBuddy 内置浏览器立即看到最新效果。原因:内置浏览器无 CDP 调试端口(9222/9224 等均无响应),无法模拟 F5;重开页面是唯一可靠刷新方式。此约定对工作台所有 UI 改动强制生效 @@ -150,7 +150,7 @@ agent_created: true - **技能本体**:`subskills/lieflat-charts/`(相对本文件;含 SKILL.md + catalog.md + templates/ 各 gallery,随源仓库分发) - **选型规则**:先读 `catalog.md` 按数据形状锁定图型编号,再打开 `templates/` 对应 gallery 的真实实现复制骨架;默认顺序 Lupi Editorial → Lupi Basics → Glance(工作台场景数据量小/需快读时可直用 Glance) - **视觉 token**:`mono-tokens.js`(纸 #F0EFEB / 墨 #1C1C1A / 灰 #8F8E88 / 网格 #DEDDD6),同一交付只锁一种色系 -- **ECharts 依赖**:已本地化至工作台 `mcn-work-shop/public/vendor/echarts.min.js`(零 CDN 依赖,离线可用);引用样例见工作台账号详情页视频列表上方「点赞/评论趋势折线图」(G8 Rainfall 骨架:双 grid、双序列随时间、tipLight tooltip、quarticOut 入场) +- **ECharts 依赖**:已本地化至工作台 `mcn-workshop/public/vendor/echarts.min.js`(零 CDN 依赖,离线可用);引用样例见工作台账号详情页视频列表上方「点赞/评论趋势折线图」(G8 Rainfall 骨架:双 grid、双序列随时间、tipLight tooltip、quarticOut 入场) - **使用限制**:库外新造是最后手段(须继承最接近 gallery 模板的视觉语法);不得拼接多个模板造混合图型 --- @@ -338,7 +338,7 @@ S1 意图识别 → S2 需求完善 → [G0 分析确认] > 判定口诀:**"这个产物要不要进工作台(rewrite_log/入库/左侧会话栏)?"**——要进,就走任务;不进,才可会话内产出。 > 重新执行历史任务 = 复用原任务 prompt(从 automations 表取)+ `skills` 参数提交新任务,任务自动应用当前技能版本。 -> **组装 prompt/选择 skills 时遵守「功能入口 × 技能调用契约」**(工作台章节第 7 条 + `mcn-work-shop/docs/AI会话任务输入输出对照.md`)——任务必须带技能前缀常量(SKILL_HINT_*)+ 对应 `skills` 数组。 +> **组装 prompt/选择 skills 时遵守「功能入口 × 技能调用契约」**(工作台章节第 7 条 + `mcn-workshop/docs/AI会话任务输入输出对照.md`)——任务必须带技能前缀常量(SKILL_HINT_*)+ 对应 `skills` 数组。 ### 〇·1、方案与执行分离(P0) @@ -397,7 +397,7 @@ Skill部署到非原始开发路径时,所有用户新增的创作流程/接 ├── SKILL.md ← 入口文件(流程引擎+规则摘要+子技能索引+目录索引) ├── 帮助文档.md ← 使用指南+触发示例+快速启动模板 ├── scripts/ ← MCP配置 + Dify接口脚本 -├── mcn-work-shop/ ← 本地 Web 工作台(server.js + public/,零依赖 Node) +├── mcn-workshop/ ← 本地 Web 工作台(server.js + public/,零依赖 Node) ├── subskills/ ← 6 个子技能(browser-harness / lieflat-charts / mcn-data-insight / mcn-dou-analysis / mcn-script-review / mcn-video-prompt,相对路径引用见「子技能索引」) ├── references/ ← 标准库 │ ├── 创作流程规范.md ← 确认门/格式/路径/硬编码 规则权威源 diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/AI链路-本地CLI接入.md b/project/短视频脚本创作/V1.0/mcn-work-shop/docs/AI链路-本地CLI接入.md deleted file mode 100644 index ae0a858..0000000 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/AI链路-本地CLI接入.md +++ /dev/null @@ -1,159 +0,0 @@ -# AI 链路:本地 CodeBuddy CLI 接入(A 方案) - -> 2026-09-29 落地。工作台的 AI 能力(需求打磨 / 创作)除 Dify 外,新增**本地 CodeBuddy CLI** 通路。 -> 实现文件:`cli-backend.js`(后端)+ `server.js` 的 `/api/ai/clarify`、`/api/ai/status`。 - ---- - -## 1. 一句话 - -把「工作台 → 模型」从 **HTTP 调 Dify** 换成 **spawn 一个 `codebuddy` 子进程、用 stdio 通信**。 -不涉及任何本地端口,与 WorkBuddy 桌面程序完全解耦。 - ---- - -## 2. 为什么不是「直接用宿主那个 CLI」 - -排查中确认了三条硬结论,决定了必须自备凭据: - -| 结论 | 证据 | -|:--|:--| -| 宿主 gateway 端口/密码**每轮轮换**,且无处落盘 | 同机同天实测 `12134 → 13116 → 10261`;`D:/.workbuddy/` 下无 worker/registry 文件 | -| 唯一发现通道是环境变量,且**只存在于 WorkBuddy 派生的进程树内** | `CODEBUDDY_SERVICE_PROXY_URL=http://127.0.0.1:/...`,用户双击 `start.bat` 起的工作台拿不到 | -| WorkBuddy **故意**不让子进程继承凭据 | 用 4 个环境变量把凭据交给 CLI,CLI 读完**立即 `delete`**;凭据不落明文盘 | - -→ 所以走官方文档给的非交互认证通道:`CODEBUDDY_API_KEY` / `CODEBUDDY_AUTH_TOKEN`。 - ---- - -## 3. 配置步骤 - -### 步骤 1:拿 API Key - -| 版本 | 地址 | -|:--|:--| -| **中国版**(本机是这版,`CODEBUDDY_INTERNET_ENVIRONMENT=internal`) | https://copilot.tencent.com/profile/ | -| 国际版 | https://www.codebuddy.ai/profile/keys | -| iOA | https://tencent.sso.copilot.tencent.com/profile/keys | - -### 步骤 2:填进 `config.json` - -```json -"ai": { - "backend": "cli", - "cli": { - "path": "", - "model": "custom:doubao-seed-2-1-pro-260628", - "apiKey": "<你的 API Key>", - "authToken": "", - "internetEnvironment": "internal", - "permissionMode": "", - "timeoutMs": 300000 - } -} -``` - -只改两个值即可:`backend` → `"cli"`,`apiKey` → 你的 key。 - -### 步骤 3:自检 - -```bash -# 只报告配置(零消耗) -curl "http://localhost:8900/api/ai/status" - -# 真跑一次极短提示,验证端到端(消耗一次调用) -curl "http://localhost:8900/api/ai/status?probe=1" -``` - -期望看到 `"ready": true` 且 `probe.ok = true`。若 `ready: false` → key 没填对; -若 `probe.error` 里出现 `Authentication required` → key 无效或 `internetEnvironment` 版本错了。 - ---- - -## 4. 字段说明 - -| 字段 | 说明 | -|:--|:--| -| `ai.backend` | `"cli"` 走本地 CLI;`"dify"` 走原链路。可用环境变量 `MCN_AI_BACKEND` 临时覆盖(不改文件、不重启) | -| `ai.cli.path` | 留空 → 自动探测:`CODEBUDDY_CLI_PATH` 环境变量 → WorkBuddy 自带 CLI → `PATH` 里的 `codebuddy` | -| `ai.cli.model` | 模型 ID。可用值见 `codebuddy --model` 帮助(含 `custom:doubao-seed-2-1-pro-260628`) | -| `ai.cli.apiKey` | 留空 → 读环境变量 `CODEBUDDY_API_KEY` | -| `ai.cli.authToken` | 留空 → 读环境变量 `CODEBUDDY_AUTH_TOKEN`。**优先级最高**(官方:`AUTH_TOKEN` > `apiKeyHelper` > `API_KEY`) | -| `ai.cli.internetEnvironment` | 中国版 `internal`;iOA `ioa`;国际版留空 | -| `ai.cli.timeoutMs` | 单次调用超时,默认 300000(5 分钟) | - ---- - -## 5. 实现里必须保留的三条防护(改动前务必读) - -`cli-backend.js` 的 `buildChildEnv()` 做了三件事,**每一条都是实测踩出来的坑**: - -### 5.1 删掉 `SERVER__PORT` / `SERVER__HOST` - -`codebuddy`(**连 `-p` 打印模式也**)内部会起一个 HTTP server: - -```js -listen(parseInt(process.env.SERVER__PORT) || config.get("cell.server", { port: 3000 }).port) -``` - -在 WorkBuddy 进程树内启动时,会继承宿主给自己设的 `SERVER__PORT` → 去 listen 宿主已占用的端口 → -`EADDRINUSE` → **进程静默卡死**(HTTP 层看似正常,只有 agent 执行环节挂掉,极具迷惑性)。 - -官方自己在派生子进程时也是这么做的:`delete el.SERVER__PORT, delete el.SERVER__HOST`。 - -### 5.2 强制 `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` - -`bin/codebuddy` 这个 launcher 按环境变量选 bundle: - -| 环境变量 | 命中产物 | -|:--|:--| -| `CODEBUDDY_FORCE_LITE_WB_BUNDLE=1`(宿主注入,**优先级最高**) | `codebuddy-lite-wb.mjs`(WorkBuddy 特供精简包) | -| `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` | `codebuddy-headless.js`(官方 headless,**我们要的**) | -| 含 `--print` | 自动 headless | -| 都没有 | `dist/codebuddy`(**本安装里不存在** → `MODULE_NOT_FOUND`) | - -不显式清除 wb 特供包分流,会误用给宿主定制的产物。 - -### 5.3 清掉 `NODE_OPTIONS` 与会话上下文变量 - -宿主会注入 `NODE_OPTIONS=--require=...node-language-shim.cjs`、`CODEBUDDY_SESSION_*`、 -`CODEBUDDY_SERVICE_PROXY_URL`、`BAGGAGE` 等,子进程继承会串味。一并清掉,并把 -`CODEBUDDY_CONFIG_DIR` 指向独立目录(默认 `/mcn-cli-home`),避免污染 WorkBuddy 的配置与会话历史。 - ---- - -## 6. 输出协议 - -工作台对前端只暴露一套 SSE 协议,`cli` 与 `dify` 两条后端产出完全一致,**前端无需感知差异**: - -| 事件 | 含义 | -|:--|:--| -| `{"delta": "..."}` | 文本增量,直接追加渲染 | -| `{"replace": "..."}` | 整体覆盖(终态校准 / 出错但已有产出) | -| `{"error": "..."}` | 错误(附带可操作的中文提示) | -| `data: [DONE]` | 流结束 | - -CLI 侧的事件解析(`--output-format stream-json --include-partial-messages`): - -| CLI 事件 | 处理 | -|:--|:--| -| `type:"system", subtype:"init"` | 取 `session_id` | -| `type:"stream_event"` → `event.content_block_delta` → `delta.type==="text_delta"` | 取 `delta.text` 作为增量 | -| `delta.type==="thinking_delta"` | 思考增量(可选回调) | -| `type:"assistant"` | 未产生增量流时的整段文本兜底 | -| `type:"result"` | 终态。`is_error:true` 时真实原因在 **`errors[]`**,正文作废 | - -> ⚠️ 未认证时,CLI 会把报错文本**当成 assistant 文本**发出来。 -> `isCliBoilerplate()` 负责识别并拦截,避免把英文报错显示成"AI 的回答"。 - ---- - -## 7. 排障速查 - -| 现象 | 原因 | 处理 | -|:--|:--|:--| -| 进程卡死无输出 | `SERVER__PORT` 未清除 → EADDRINUSE | 确认 `buildChildEnv` 的 delete 生效 | -| `Cannot find module '../dist/codebuddy'` | bundle 分流未命中 | 设 `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` | -| `Authentication required. Please use /login` | 无凭据 | 按 §3 填 key | -| `ready:false` 但 key 已填 | `internetEnvironment` 版本不对 | 中国版必须是 `internal` | -| HTTP 层全正常、agent 却不干活 | 典型的 EADDRINUSE 假象 | 看 §5.1 | diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/2026-09-10.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-09-10.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/2026-09-10.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-09-10.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/2026-09-24.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-09-24.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/2026-09-24.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-09-24.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/2026-09-28.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-09-28.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/2026-09-28.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-09-28.md diff --git a/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-10-08.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-10-08.md new file mode 100644 index 0000000..90b7952 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/2026-10-08.md @@ -0,0 +1,9 @@ +# 2026-10-08 工作日志 + +## 榜单数据更新(自动化 automation-1791425215326) + +- 按 mcn-data-insight「榜单数据落盘规范」抓取热点数据四榜最新一期,落盘目录:`C:\Users\maidou\Desktop\MCNSkill项目\三方数据\红狐数据\抖音榜单\` +- 写入 8 个文件:账号日榜 1(数据期 2026-10-06)、账号周榜 5 赛道(数据期 2026-09-28~10-04)、视频热榜 1(周标号 2026-09-28)、点赞榜 1(周标号 2026-09-28),各 50 条 +- 实际 API 调用 8 次(hot1 + likes1 + 日榜1 + 周榜5),无缓存命中(上一期:日榜 09-26、周榜 09-07、视频/点赞 09-21) +- 运行要点:`fetch_week_ranks.py` 需 cd 到 scripts 目录用 `D:/miniconda3/python.exe` 跑;`fetch_rank.py` 先落临时目录再按 JSON 内 dateStart 重命名到正式文件名 +- 注意:Bash `mktemp` 得到的是 /tmp 路径,传给 Python 前需 `cygpath -w` 转 Windows 路径,否则 FileNotFoundError diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1789027006296/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1789027006296/memory.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1789027006296/memory.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1789027006296/memory.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790238145517/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790238145517/memory.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790238145517/memory.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790238145517/memory.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790567007119/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790567007119/memory.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790567007119/memory.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790567007119/memory.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790585785553/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790585785553/memory.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790585785553/memory.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790585785553/memory.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790587091698/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790587091698/memory.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/.workbuddy/memory/automations/automation-1790587091698/memory.md rename to project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790587091698/memory.md diff --git a/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790682160345/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790682160345/memory.md new file mode 100644 index 0000000..d5b992b --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1790682160345/memory.md @@ -0,0 +1,13 @@ +# automation-1790682160345 执行记录 + +## 任务性质 +链路自检(健康检查)任务。指令:仅回复「链路正常」四字。 + +## 历史执行 +| 时间 | 结果 | 说明 | +| --- | --- | --- | +| 2026-09-29 19:43 | 成功 | 定时任务正常触发并返回,链路通畅。首次执行,无历史记录文件,已初始化本文件。 | + +## 备注 +- 该任务不产出任何交付文件,无需 present_files。 +- 只做链路连通性验证:定时调度 → 会话唤起 → 模型响应 全链路可达。 diff --git a/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1791425215326/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1791425215326/memory.md new file mode 100644 index 0000000..1141927 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1791425215326/memory.md @@ -0,0 +1,12 @@ +# automation-1791425215326 · 榜单数据更新(热点数据四榜落盘) + +## 执行约定(复用) +- 落盘目录:`C:\Users\maidou\Desktop\MCNSkill项目\三方数据\红狐数据\抖音榜单\`(桌面未 OneDrive 重定向;env `MCN_RANKING_DIR` 可覆盖) +- 四榜:账号日榜(全品类 1 次)/账号周榜(5 赛道:生活vlog 小剧场 亲子 美食 旅行)/视频热榜/点赞榜(各 1 次,不传赛道) +- 幂等:调用前先 ls 目标子目录,同榜名+赛道+数据日期已存在则跳过 +- Python 用 `D:/miniconda3/python.exe`;`fetch_week_ranks.py` 在 scripts 目录执行;`fetch_rank.py` 先写临时目录,按 JSON 内 dateStart 定正式文件名 + +## 执行历史 +| 运行日期 | 数据期 | 写入 | API 调用 | 备注 | +|---|---|---|---|---| +| 2026-10-08 | 日榜 2026-10-06;周榜/热榜/点赞 2026-09-28 | 8 个文件(各 50 条) | 8 次 | 该自动化首次记录执行;上一期缓存为日榜 09-26 / 周榜 09-07 / 视频·点赞 09-21,全部未命中故全量抓取 | diff --git a/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1791433363165/memory.md b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1791433363165/memory.md new file mode 100644 index 0000000..1b660d5 --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-workshop/.workbuddy/memory/automations/automation-1791433363165/memory.md @@ -0,0 +1,6 @@ +# automation-1791433363165 执行记录 + +## 2026-10-08 12:23 +- 任务:按用户要求只回复两个字「成功」。 +- 结果:已按字面要求输出「成功」,无文件产出。 +- 备注:本次为首次执行,无历史记录。 diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/build-create-prompts.cjs b/project/短视频脚本创作/V1.0/mcn-workshop/build-create-prompts.cjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/build-create-prompts.cjs rename to project/短视频脚本创作/V1.0/mcn-workshop/build-create-prompts.cjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-align-videoinfo.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-align-videoinfo.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-align-videoinfo.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-align-videoinfo.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-diag-videoinfo.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-diag-videoinfo.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-diag-videoinfo.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-diag-videoinfo.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-measure-cols.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-measure-cols.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-measure-cols.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-measure-cols.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-measure-cols2.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-measure-cols2.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-measure-cols2.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-measure-cols2.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-measure-cols3.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-measure-cols3.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-measure-cols3.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-measure-cols3.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-shot-wangweisi.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-shot-wangweisi.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-shot-wangweisi.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-shot-wangweisi.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-test-junxi.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-test-junxi.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-test-junxi.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-test-junxi.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-addr.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-addr.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-addr.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-addr.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-aicreate.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-aicreate.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-aicreate.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-aicreate.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-batch-e2e.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-batch-e2e.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-batch-e2e.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-batch-e2e.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-batch.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-batch.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-batch.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-batch.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-btnwhite.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-btnwhite.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-btnwhite.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-btnwhite.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-collect.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-collect.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-collect.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-collect.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-footer.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-footer.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-footer.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-footer.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-gensb.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-gensb.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-gensb.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-gensb.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-homestats.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-homestats.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-homestats.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-homestats.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-import.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-import.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-import.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-import.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-infocard-title.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-infocard-title.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-infocard-title.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-infocard-title.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-noall.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-noall.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-noall.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-noall.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-numalign.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-numalign.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-numalign.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-numalign.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-oneway2.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-oneway2.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-oneway2.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-oneway2.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personacard.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-personacard.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personacard.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-personacard.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personapad.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-personapad.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-personapad.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-personapad.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r27.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r27.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r27.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r27.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r28.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r28.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r28.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r28.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r29.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r29.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r29.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r29.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r30.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r30.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r30.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r30.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r59.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r59.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r59.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r59.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r64-acc.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r64-acc.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r64-acc.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r64-acc.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r64-del.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r64-del.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r64-del.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r64-del.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r64.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r64.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r64.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r64.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r65.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r65.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r65.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r65.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r67.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r67.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r67.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r67.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r68.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r68.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r68.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r68.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r68b.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r68b.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r68b.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r68b.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r69.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r69.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r69.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r69.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r70.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r70.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-r70.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-r70.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranknarrow.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-ranknarrow.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranknarrow.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-ranknarrow.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankpreview.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rankpreview.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankpreview.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rankpreview.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranktabs.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-ranktabs.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-ranktabs.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-ranktabs.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankwide.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rankwide.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rankwide.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rankwide.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rename.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rename.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rename.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rename.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rosegold.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rosegold.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-rosegold.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-rosegold.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-srcbtn1600.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-srcbtn1600.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-srcbtn1600.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-srcbtn1600.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tabstyle.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-tabstyle.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tabstyle.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-tabstyle.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tag-last.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-tag-last.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-tag-last.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-tag-last.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-title-link.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-title-link.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-title-link.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-title-link.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-titlebar.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-titlebar.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-titlebar.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-titlebar.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-typebadge.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-typebadge.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-typebadge.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-typebadge.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-videoinfo.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-videoinfo.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-videoinfo.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-videoinfo.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-videoinfo2.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-videoinfo2.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-verify-videoinfo2.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-verify-videoinfo2.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-wheel.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-wheel.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel2.mjs b/project/短视频脚本创作/V1.0/mcn-workshop/cdp-wheel2.mjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cdp-wheel2.mjs rename to project/短视频脚本创作/V1.0/mcn-workshop/cdp-wheel2.mjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/check-run-status.cjs b/project/短视频脚本创作/V1.0/mcn-workshop/check-run-status.cjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/check-run-status.cjs rename to project/短视频脚本创作/V1.0/mcn-workshop/check-run-status.cjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/cli-backend.js b/project/短视频脚本创作/V1.0/mcn-workshop/cli-backend.js similarity index 64% rename from project/短视频脚本创作/V1.0/mcn-work-shop/cli-backend.js rename to project/短视频脚本创作/V1.0/mcn-workshop/cli-backend.js index 97e841e..f46d0e8 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/cli-backend.js +++ b/project/短视频脚本创作/V1.0/mcn-workshop/cli-backend.js @@ -69,6 +69,50 @@ function resolveCli(explicitPath) { return null; } +// ---------------------------------------------------------------- 凭据解析 + +// 凭据默认落点:**刻意放在仓库之外**。 +// config.json 是 git 跟踪文件,绝不能让 key 落进去;本目录也不会被技能同步脚本复制。 +function defaultCredentialFile() { + return path.join(os.homedir(), '.workbuddy', 'mcn-workshop', 'ai-cli.json'); +} + +// 读凭据文件:{"apiKey":"...","authToken":"..."} +function readCredentialFile(p) { + if (!p) return {}; + try { + if (!fs.existsSync(p)) return {}; + const j = JSON.parse(fs.readFileSync(p, 'utf8')); + return { + apiKey: typeof j.apiKey === 'string' ? j.apiKey.trim() : '', + authToken: typeof j.authToken === 'string' ? j.authToken.trim() : '', + file: p, + }; + } catch (e) { return {}; } +} + +/** + * 凭据优先级(高 → 低): + * 1) 调用方显式传入(config.json 的 ai.cli.apiKey / authToken) + * 2) 凭据文件(默认 ~/.workbuddy/mcn-workshop/ai-cli.json) + * 3) 环境变量 CODEBUDDY_API_KEY / CODEBUDDY_AUTH_TOKEN + * 返回 { apiKey, authToken, source } + */ +function resolveCredentials(o) { + o = o || {}; + const f = readCredentialFile(o.credentialFile || defaultCredentialFile()); + const apiKey = o.apiKey || f.apiKey || process.env.CODEBUDDY_API_KEY || ''; + const authToken = o.authToken || f.authToken || process.env.CODEBUDDY_AUTH_TOKEN || ''; + let source = '(未配置)'; + if (o.authToken) source = 'config:authToken'; + else if (o.apiKey) source = 'config:apiKey'; + else if (f.authToken) source = 'file:authToken ' + f.file; + else if (f.apiKey) source = 'file:apiKey ' + f.file; + else if (process.env.CODEBUDDY_AUTH_TOKEN) source = 'env:CODEBUDDY_AUTH_TOKEN'; + else if (process.env.CODEBUDDY_API_KEY) source = 'env:CODEBUDDY_API_KEY'; + return { apiKey, authToken, source, credentialFile: f.file || defaultCredentialFile() }; +} + // ---------------------------------------------------------------- 环境构造 // 把「宿主 WorkBuddy 注入的、会干扰子 CLI 的」环境变量剔除,并注入凭据 @@ -86,6 +130,9 @@ function buildChildEnv(opts) { for (const k of Object.keys(env)) { if (k.startsWith('CODEBUDDY_SESSION_') || k.startsWith('CODEBUDDY_CONVERSATION_') || k === 'CODEBUDDY_TOOL_CALL_ID' || k === 'CODEBUDDY_SERVICE_PROXY_URL' || + k === 'CODEBUDDY_MCP_CONFIG' || // 宿主 MCP(weixinpay/sheetagent…)我们不要 + k === 'CODEBUDDY_GATEWAY_PASSWORD' || k === 'CODEBUDDY_GATEWAY_AUTH' || + k === 'WORKBUDDY_PAC_RPC_TOKEN' || k === 'WORKBUDDY_PAC_RPC_SOCKET' || k === 'SANDBOX_CENTER_IPC_ADDRESS' || k === 'BAGGAGE' || k === 'BASH_ENV') { delete env[k]; } @@ -97,12 +144,17 @@ function buildChildEnv(opts) { env.CODEBUDDY_SAFE_DELETE_ENABLED = '0'; // 工作台场景不需要工具删除保护 env.CODEBUDDY_GATEWAY_DISABLE_API_DOCS = '1'; env.CODEBUDDY_DISABLE_TELEMETRY = '1'; + // 官方建议:纯 -p 单发不支持后台任务,开着会返回明确错误 + env.CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS = '1'; - // 【坑 3】凭据:独立进程必须自备 - const key = opts.apiKey || process.env.CODEBUDDY_API_KEY || ''; - const token = opts.authToken || process.env.CODEBUDDY_AUTH_TOKEN || ''; - if (token) env.CODEBUDDY_AUTH_TOKEN = token; // 优先级最高 - if (key) env.CODEBUDDY_API_KEY = key; + // 【坑 3】凭据:独立进程必须自备。 + // 注意:宿主也会用同名变量注入凭据且读后即删——必须先把继承来的残留清掉,再写我们自己的, + // 否则可能带着一个「空壳/过期」的值进入子进程。 + delete env.CODEBUDDY_API_KEY; + delete env.CODEBUDDY_AUTH_TOKEN; + const cred = resolveCredentials(opts); + if (cred.authToken) env.CODEBUDDY_AUTH_TOKEN = cred.authToken; // 优先级最高 + if (cred.apiKey) env.CODEBUDDY_API_KEY = cred.apiKey; if (opts.internetEnvironment) env.CODEBUDDY_INTERNET_ENVIRONMENT = opts.internetEnvironment; if (opts.baseUrl) env.CODEBUDY_BASE_URL = opts.baseUrl; @@ -123,11 +175,17 @@ function buildChildEnv(opts) { * @param {string} o.cwd 工作目录(决定 CLI 能看到哪些项目文件) * @param {string} [o.homeDir] CLI 的配置/数据目录(默认 /mcn-cli-home) * @param {string} [o.model] 模型 ID - * @param {string} [o.apiKey] CODEBUDDY_API_KEY + * @param {string} [o.apiKey] CODEBUDDY_API_KEY(优先级最高,一般留空走凭据文件) * @param {string} [o.authToken] CODEBUDDY_AUTH_TOKEN + * @param {string} [o.credentialFile] 凭据文件路径,默认 ~/.workbuddy/mcn-workshop/ai-cli.json * @param {string} [o.internetEnvironment] internal / ioa / 留空 * @param {string} [o.cliPath] 显式指定 CLI 路径 * @param {number} [o.timeoutMs] + * @param {boolean} [o.search] 是否允许联网搜索(默认 true)。true → 工具面收敛为 WebSearch+WebFetch + * @param {string} [o.tools] 显式工具白名单:'default' | 'none' | 'WebSearch,WebFetch'(留空=按 search 推导) + * @param {boolean} [o.strictMcp] 是否屏蔽宿主 MCP(默认 true) + * @param {string} [o.appendSystemPrompt] 追加系统提示(如搜索使用策略) + * @param {number} [o.maxTurns] 最大 agent 轮数(0=不限) * @param {function} o.onDelta (text) => void 文本增量 * @param {function} [o.onThink] (text) => void 思考增量(可选) * @param {function} [o.onEvent] (obj) => void 全部原始事件(可选,调试用) @@ -154,6 +212,33 @@ function runCli(o) { ...(o.permissionMode ? ['--permission-mode', o.permissionMode] : []), ]; + // ---- 工具面收敛(既提速,也是安全边界)---- + // 工作台的 AI 只负责「产出文本」,prompt 里已自带全部上下文(账号设定/需求/历史), + // 不需要读写文件、跑命令。默认只给联网搜索两个工具: + // · 启动快(不必加载 Bash/Edit/Write/... 的工具 schema) + // · 模型物理上碰不到本机文件系统 + // o.search === false → 放开默认工具面,但明确禁用联网搜索 + let tools = o.tools; + if (tools === undefined || tools === null || tools === '') { + tools = (o.search === false) ? 'default' : 'WebSearch,WebFetch'; + } + if (tools === 'none') args.push('--tools', ''); + else if (tools && tools !== 'default') args.push('--tools', tools); + if (o.search === false) args.push('--disallowedTools', 'WebSearch,WebFetch'); + + // ---- 屏蔽宿主 MCP ---- + // 宿主会把 CODEBUDDY_MCP_CONFIG 注入进程树,里面挂着 weixinpay / sheetagent 等。 + // 工作台场景一个都用不上,加载它们纯属浪费启动时间,还可能被模型误调。 + if (o.strictMcp !== false) { + args.push('--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}'); + } + + // 推理开销:把首字延迟从几十秒压到十几秒的关键开关(minimal|low|medium|high|xhigh|max) + if (o.effort) args.push('--effort', o.effort); + + if (o.appendSystemPrompt) args.push('--append-system-prompt', o.appendSystemPrompt); + if (parseInt(o.maxTurns, 10) > 0) args.push('--max-turns', String(parseInt(o.maxTurns, 10))); + const env = buildChildEnv({ homeDir, apiKey: o.apiKey, @@ -183,6 +268,7 @@ function runCli(o) { } let full = ''; + let streamed = ''; // 已经通过 onDelta 推给前端的内容(用于终态比对) let stderr = ''; let sessionId = ''; let usage = null; @@ -190,6 +276,11 @@ function runCli(o) { let errText = ''; let sawDelta = false; let buf = ''; + // 每次工具调用的发生位置(= 当时的 full 长度)。 + // 根因:模型在发起 WebSearch 前常先吐一句过程话术(如 + // "I'll search for ... before responding."),它不是答案。 + // 只保留「最后一次工具调用之后」的文本作为正文。 + const toolMarks = []; const handleLine = (line) => { line = line.trim(); @@ -206,6 +297,12 @@ function runCli(o) { // 文本增量:stream_event → content_block_delta → text_delta if (ev.type === 'stream_event' && ev.event) { const e2 = ev.event; + // 工具调用开始的边界(用于切掉调用前的过程话术) + if (e2.type === 'content_block_start' && e2.content_block && + (e2.content_block.type === 'tool_use' || e2.content_block.name)) { + toolMarks.push(full.length); + return; + } if (e2.type === 'content_block_delta' && e2.delta) { if (e2.delta.type === 'text_delta' && e2.delta.text) { const prev = full; @@ -219,6 +316,7 @@ function runCli(o) { // 已误判过一次的话,本段的累积都作废 if (isCliBoilerplate(prev)) { full = ''; errText = errText || prev; return; } sawDelta = true; + streamed += e2.delta.text; if (o.onDelta) { try { o.onDelta(e2.delta.text); } catch (e) {} } } else if (e2.delta.type === 'thinking_delta' && e2.delta.thinking && o.onThink) { try { o.onThink(e2.delta.thinking); } catch (e) {} @@ -229,6 +327,10 @@ function runCli(o) { // 兜底:未开启/未产生增量流时,整段文本在 assistant 消息里 if (ev.type === 'assistant' && ev.message && Array.isArray(ev.message.content)) { + // 工具调用边界(未走增量流时,tool_use 只会出现在 assistant 消息里) + for (const c of ev.message.content) { + if (c && c.type === 'tool_use') toolMarks.push(full.length); + } if (!sawDelta) { const t = ev.message.content .filter((c) => c && c.type === 'text' && typeof c.text === 'string') @@ -236,6 +338,7 @@ function runCli(o) { if (t && !full) { if (isCliBoilerplate(t)) { errText = errText || t; return; } full = t; + streamed += t; if (o.onDelta) { try { o.onDelta(t); } catch (e) {} } } } @@ -283,18 +386,22 @@ function runCli(o) { child.on('close', (code) => { clearTimeout(timer); if (buf.trim()) handleLine(buf); - const text = full || resultText; - if (aborted) return resolve({ ok: false, text, error: 'CLI 执行超时或被取消', sessionId, usage }); + // 切掉「最后一次工具调用之前」的过程话术:那部分不是答案。 + // (例:模型先输出 "I'll search for ... before responding." 再发起 WebSearch) + const cut = toolMarks.length ? toolMarks[toolMarks.length - 1] : 0; + const answer = (cut > 0 && cut < full.length) ? full.slice(cut).trim() : full; + const text = answer || resultText; + if (aborted) return resolve({ ok: false, text, streamed, error: 'CLI 执行超时或被取消', sessionId, usage }); // 事件里明确报了错(如未认证)→ 优先按错误返回 if (errText) { const raw = errText; - return resolve({ ok: false, text, error: messageForCliError(raw) || raw, sessionId, usage, rawStderr: raw }); + return resolve({ ok: false, text, streamed, error: messageForCliError(raw) || raw, sessionId, usage, rawStderr: raw }); } if (code !== 0 && !text) { const msg = (stderr || '').trim().split('\n').filter(Boolean).slice(-3).join(' | '); - return resolve({ ok: false, text, error: messageForCliError(msg) || ('CLI 退出码 ' + code), sessionId, usage, rawStderr: msg }); + return resolve({ ok: false, text, streamed, error: messageForCliError(msg) || ('CLI 退出码 ' + code), sessionId, usage, rawStderr: msg }); } - resolve({ ok: true, text, sessionId, usage }); + resolve({ ok: true, text, streamed, sessionId, usage }); }); }); } @@ -324,4 +431,4 @@ function messageForCliError(msg) { return msg; } -module.exports = { runCli, resolveCli, buildChildEnv }; +module.exports = { runCli, resolveCli, buildChildEnv, resolveCredentials, defaultCredentialFile }; diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/config.json b/project/短视频脚本创作/V1.0/mcn-workshop/config.json similarity index 54% rename from project/短视频脚本创作/V1.0/mcn-work-shop/config.json rename to project/短视频脚本创作/V1.0/mcn-workshop/config.json index 419f960..c2ff62b 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/config.json +++ b/project/短视频脚本创作/V1.0/mcn-workshop/config.json @@ -4,17 +4,25 @@ "outputRoot": "MCNSkill项目", "businessDataDir": ["MCNSkill项目", "三方数据", "红狐数据", "抖音榜单"], "extraRoots": [], + "sessionCwd": "D:\\AI技能\\mcn-workshop", "ai": { - "backend": "dify", + "backend": "cli", "cli": { "path": "", - "model": "custom:doubao-seed-2-1-pro-260628", + "model": "deepseek-v4.1-flash", + "effort": "low", "apiKey": "", "authToken": "", + "credentialFile": "", "internetEnvironment": "internal", "permissionMode": "", - "timeoutMs": 300000 + "timeoutMs": 300000, + "search": true, + "tools": "", + "strictMcp": true, + "appendSystemPrompt": "", + "maxTurns": 6 } } } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/AI会话任务输入输出对照.md b/project/短视频脚本创作/V1.0/mcn-workshop/docs/AI会话任务输入输出对照.md similarity index 96% rename from project/短视频脚本创作/V1.0/mcn-work-shop/docs/AI会话任务输入输出对照.md rename to project/短视频脚本创作/V1.0/mcn-workshop/docs/AI会话任务输入输出对照.md index f0a6a91..836490d 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/AI会话任务输入输出对照.md +++ b/project/短视频脚本创作/V1.0/mcn-workshop/docs/AI会话任务输入输出对照.md @@ -6,7 +6,7 @@ ## 触发链路(所有入口共用) -按钮点击 → 确认弹窗(confirmRun / confirmAiScript / confirmSaveAccount)→ `executeTask()` → `POST /api/run`(server.js 写 automations 表,scheduledAt=now+5s + next_run_at=now+5000,`skills` 数组→`skills_json` 参数级技能挂载)→ 客户端调度器拾取 → 左侧会话栏「mcn-work-shop」空间后台会话执行(cwd = 工作台目录,由 `server.js` 从 `ROOT` 动态推导)→ 轮询 `/api/run/status`(done/error/review)→ 完成刷新页面。 +按钮点击 → 确认弹窗(confirmRun / confirmAiScript / confirmSaveAccount)→ `executeTask()` → `POST /api/run`(server.js 写 automations 表,scheduledAt=now+5s + next_run_at=now+5000,`skills` 数组→`skills_json` 参数级技能挂载)→ 客户端调度器拾取 → 左侧会话栏「mcn-workshop」空间后台会话执行(cwd = 工作台目录,由 `server.js` 从 `ROOT` 动态推导)→ 轮询 `/api/run/status`(done/error/review)→ 完成刷新页面。 ## 功能入口 × 技能调用契约总表 @@ -115,7 +115,7 @@ | 候选 | `==OPTS==` | 每行一个候选,格式「骨架词——具体展开」(≤45 字、无编号);结合本条创意/人设落地,禁干巴术语 | 解析为气泡下内嵌按钮行,点选=以整行文本发一轮 | | 需求 | `==REQ==` | 8 行清单:内容形式/一句话故事/开场钩子/故事爆点/故事爽点/故事框架/人物关系/其他要求(空项留空) | 解析回填需求框字段 | -**System Prompt 已抽为文件**:`mcn-work-shop/prompts/clarify.md`(==PROMPT== 与 ==CHANGELOG== 之间为正文,头尾说明不发模型)。**迭代 prompt 只改该文件**,server.js 启动按 mtime 缓存读取、占位符注入(`{{accountName}}/{{personaText}}/{{contentText}}/{{attachText}}/{{reqMdText}}/{{histText}}/{{turnText}}`),不再改 server.js 字符串。 +**System Prompt 已抽为文件**:`mcn-workshop/prompts/clarify.md`(==PROMPT== 与 ==CHANGELOG== 之间为正文,头尾说明不发模型)。**迭代 prompt 只改该文件**,server.js 启动按 mtime 缓存读取、占位符注入(`{{accountName}}/{{personaText}}/{{contentText}}/{{attachText}}/{{reqMdText}}/{{histText}}/{{turnText}}`),不再改 server.js 字符串。 **关键规则(clarify.md 内固化)**: - 候选骨架受控词表:内容形式(剧情/Vlog/口播/**视觉短片**)、故事框架(钩子前置/悬念反转/递进铺垫/先抑后扬)、开场钩子(悬念提问/反差画面/直击痛点/身份共鸣/利益承诺)、剧情人物关系(一次一层);每项必须结合本条创意给「具体形态」 diff --git a/project/短视频脚本创作/V1.0/mcn-workshop/docs/AI链路-本地CLI接入.md b/project/短视频脚本创作/V1.0/mcn-workshop/docs/AI链路-本地CLI接入.md new file mode 100644 index 0000000..65173dc --- /dev/null +++ b/project/短视频脚本创作/V1.0/mcn-workshop/docs/AI链路-本地CLI接入.md @@ -0,0 +1,283 @@ +# AI 链路:本地 CodeBuddy CLI 接入(A 方案) + +> 2026-09-29 落地并实测通过。工作台的 AI 能力(需求打磨 / 创作)除 Dify 外,新增**本地 CodeBuddy CLI** 通路。 +> 实现:`cli-backend.js`(后端模块)+ `server.js` 的 `/api/ai/clarify`、`/api/ai/status`。 + +--- + +## 1. 一句话 + +把「工作台 → 模型」从 **HTTP 调 Dify** 换成 **spawn 一个 `codebuddy` 子进程、用 stdio 通信**。 +**不涉及任何本地端口**,与 WorkBuddy 桌面程序完全解耦。 + +--- + +## 2. 为什么必须自备凭据(别再挖宿主那条路) + +排查中钉死了三条硬结论: + +| 结论 | 证据 | +|:--|:--| +| 宿主 gateway 端口/密码**每轮轮换** | 同机同天实测端口 `12134 → 13116 → 10261`;密码同步换 | +| 唯一发现通道只存在于 WorkBuddy 派生进程树内 | `CODEBUDDY_SERVICE_PROXY_URL`;用户双击 `start.bat` 起的工作台拿不到 | +| WorkBuddy **故意**不让子进程继承凭据 | 4 个环境变量交付 → CLI 读完**立即 `delete`**;凭据不落明文盘 | + +→ 走官方文档给的非交互认证通道:`CODEBUDDY_API_KEY` / `CODEBUDDY_AUTH_TOKEN`。 + +--- + +## 3. 配置步骤 + +### 步骤 1:拿 API Key + +| 版本 | 地址 | +|:--|:--| +| **中国版**(本机是这版,`internetEnvironment=internal`) | https://copilot.tencent.com/profile/ | +| 国际版 | https://www.codebuddy.ai/profile/keys | +| iOA | https://tencent.sso.copilot.tencent.com/profile/keys | + +### 步骤 2:把 Key 放进凭据文件(⛔ 不要写进 config.json) + +**`config.json` 是 git 跟踪文件,写进去会提交到仓库。** + +默认凭据文件(仓库之外,不会被技能同步脚本复制): + +``` +%USERPROFILE%\.workbuddy\mcn-workshop\ai-cli.json +``` + +内容: + +```json +{ "apiKey": "ck_xxxxxxxx._xxxxxxxx" } +``` + +也可用 `authToken` 字段(优先级更高)。或直接设环境变量 `CODEBUDDY_API_KEY`。 + +### 步骤 3:自检 + +```bash +curl "http://localhost:8900/api/ai/status" # 只报告配置,零消耗 +curl "http://localhost:8900/api/ai/status?probe=1" # 真跑一次极短提示 +``` + +期望 `"ready": true` 且 `probe.ok = true`。 + +--- + +## 4. 字段说明(`config.json` → `ai`) + +| 字段 | 默认 | 说明 | +|:--|:--|:--| +| `backend` | `cli` | `"cli"` 走本地 CLI;`"dify"` 走原链路。环境变量 `MCN_AI_BACKEND` 可临时覆盖 | +| `cli.path` | `''` | 留空自动探测:`CODEBUDDY_CLI_PATH` → WorkBuddy 自带 CLI → `PATH` | +| `cli.model` | `deepseek-v4.1-flash` | **只影响 `/api/ai/clarify`**(需求打磨)。环境变量 `MCN_AI_MODEL` 可临时覆盖。⚠️ 创作任务的模型另见 §11 | +| `cli.effort` | `low` | 推理档位 `minimal\|low\|medium\|high\|xhigh\|max`。**首字延迟第一大杠杆** | +| `cli.search` | `true` | 是否允许联网搜索(见 §7) | +| `cli.tools` | `''` | 工具白名单。留空=按 `search` 推导;`none`=全禁;也可写 `WebSearch,WebFetch` | +| `cli.strictMcp` | `true` | 屏蔽宿主 MCP(weixinpay / sheetagent 等) | +| `cli.maxTurns` | `6` | agent 轮数硬上限,防搜索失控跑飞 | +| `cli.appendSystemPrompt` | `''` | 追加系统提示;留空时自动注入内置搜索策略(`CLI_SEARCH_HINT`) | +| `cli.credentialFile` | `''` | 凭据文件路径;留空走默认位置 | +| `cli.apiKey` / `cli.authToken` | `''` | 优先级最高,**但别填**(会被 git 记录) | +| `cli.timeoutMs` | `300000` | 单次调用超时 | + +--- + +## 5. 三条必须保留的防护(改代码前务必读) + +`cli-backend.js` 的 `buildChildEnv()` 做了三件事,每条都是实测踩出来的坑: + +### 5.1 删掉 `SERVER__PORT` / `SERVER__HOST` + +`codebuddy`(**连 `-p` 打印模式也**)内部会起一个 HTTP server: + +```js +listen(parseInt(process.env.SERVER__PORT) || config.get("cell.server", { port: 3000 }).port) +``` + +在 WorkBuddy 进程树内启动 → 继承宿主的 `SERVER__PORT` → `EADDRINUSE` → **进程静默卡死**。 +迷惑性极强:HTTP 层(health / info / sessions)全正常,只有 agent 执行环节挂, +极易被误判成「prewarm 占端口」。 + +官方自己也这么处理:`delete el.SERVER__PORT, delete el.SERVER__HOST`。 + +### 5.2 强制 `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` + +`bin/codebuddy` launcher 按环境变量选 bundle: + +| 环境变量 | 命中产物 | +|:--|:--| +| `CODEBUDDY_FORCE_LITE_WB_BUNDLE=1`(宿主注入,**优先级最高**) | `codebuddy-lite-wb.mjs`(WorkBuddy 特供精简包) | +| `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` | `codebuddy-headless.js`(**我们要的**) | +| 含 `--print` | 自动 headless | +| 都没有 | `dist/codebuddy` → **本安装不存在**,报 `MODULE_NOT_FOUND` | + +### 5.3 清掉宿主注入的会话上下文 + +`NODE_OPTIONS`(node-language-shim)、`CODEBUDDY_SESSION_*`、`CODEBUDDY_MCP_CONFIG`、 +`CODEBUDDY_GATEWAY_PASSWORD`、`BAGGAGE` 等一并删除,并把 `CODEBUDDY_CONFIG_DIR` +指向独立目录(默认 `/mcn-cli-home`),避免污染 WorkBuddy 自身配置。 + +--- + +## 6. 性能:三个真正的杠杆(实测数据) + +| 杠杆 | 做法 | 效果 | +|:--|:--|:--| +| **① 工具面收敛** | `--tools "WebSearch,WebFetch"` | prompt 从 **21,776 → 4,859 tokens(-78%)**,耗时 **47.6s → 15.0s** | +| **② 推理档位** | `--effort low` | 首字延迟 **61.7s → 19.7s** | +| **③ 换模型** | 见 §9 | `deepseek-v4-pro` 46.2s vs `Claude-Opus-4.8` 18.5s(同一提示词) | + +> ⚠️ `[INIT tools]` 事件里列的是**工具注册表全量**,不是实际启用集。 +> 判断 `--tools` 是否生效要看 `usage.input_tokens`,别被事件列表误导。 + +**额外收益**:工具面收敛后模型**物理上碰不到文件系统**(`Bash`/`Read`/`Write`/`Edit` 全不在白名单), +这是免费拿到的一道安全边界。 + +--- + +## 7. 联网搜索 + +CLI **原生带** `WebSearch` / `WebFetch`,开箱可用,无需额外配置。实测: + +``` +[TOOL_USE] WebSearch {"query":"今天热点新闻","freshness":"d1"} +``` + +但搜索会把耗时推到 1–2 分钟,所以**不能简单地常开或常关**,而是给模型一条明确的判据。 +`server.js` 的 `CLI_SEARCH_HINT` 会作为 `--append-system-prompt` 注入: + +- **该搜**:回答依赖「今天/近期」的外部事实(当下热点、平台规则与趋势、行业数据、事实核查) +- **不该搜**:创作讨论、需求澄清、脚本撰写、文案打磨 → 一律不搜 +- **搜索预算**:最多 1 次 WebSearch(+ 必要 1 次 WebFetch),硬上限 2 次;搜到即答,禁止反复搜 +- 失败/超时直接凭已有信息作答,不重试 + +想完全关闭:`"search": false`(会自动改用 `--disallowedTools WebSearch,WebFetch`)。 + +--- + +## 8. 输出协议 + +工作台对前端只暴露一套 SSE 协议,`cli` 与 `dify` 两条后端产出完全一致,**前端无需感知差异**: + +| 事件 | 含义 | +|:--|:--| +| `{"delta": "..."}` | 文本增量,直接追加渲染 | +| `{"replace": "..."}` | 整体覆盖(终态校准) | +| `{"error": "..."}` | 错误(附可操作中文提示) | +| `data: [DONE]` | 流结束 | + +CLI 侧事件解析(`--output-format stream-json --include-partial-messages`): + +| CLI 事件 | 处理 | +|:--|:--| +| `type:"system", subtype:"init"` | 取 `session_id` | +| `stream_event` → `content_block_delta` → `delta.type==="text_delta"` | 取 `delta.text` 作为增量 | +| `stream_event` → `content_block_start`(tool_use) | 记录**工具调用边界**,见下 | +| `type:"assistant"` | 未产生增量流时的整段兜底 | +| `type:"result"` | 终态。`is_error:true` 时真实原因在 **`errors[]`**,正文作废 | + +### 两个必须处理的文本污染 + +1. **报错被当成回答**:未认证时 CLI 把报错文本走文本通道发出来 → + `isCliBoilerplate()` 识别拦截,避免把英文报错显示成「AI 的回答」。 +2. **工具调用前的过程话术**:模型发起搜索前常先吐一句 + `I'll search for ... before responding.`,它不是答案。 + → `runCli` 记录每次 `tool_use` 的位置 `toolMarks`,**只保留最后一次工具调用之后的文本**; + 由于这段已被流式推给前端,`server.js` 在成功分支比对 `r.text !== acc` 时补发 `{replace}` 整体覆盖。 + +--- + +## 9. 实测可用模型清单(本机 API Key 租户) + +`--model` 的 `--help` 列表是**二进制里的静态清单,不等于已开通**。 +下表是逐个真跑出来的结果: + +| 模型 ID | 真实名称 | 实测 | +|:--|:--|:--| +| `deepseek-v4.1-flash` | DeepSeek-V4.1-Flash | ✅ **当前默认**(`clarify`) | +| `custom:custom-model-b5-standard` | GPT-5.5-Standard | ✅ 次选 | +| `custom:custom-model-b5-priority` | GPT-5.5-Priority | ✅ 同族「优先档」(资源紧张时优先路由,通常积分倍率更高) | +| `custom:custom-model-a4` | Claude-Opus-4.8 | ✅ | +| `custom:custom-model-b1-standard` / `-priority` | GPT-5.4-Standard / -Priority | ✅ | +| `custom:custom-model-b3-priority` | GPT-5.2-Priority | ✅ | +| `custom:custom-model-b4-standard` / `-priority` | GPT-5.1-Standard / -Priority | ✅ | +| `custom:custom-model-a1` | Claude-Opus-4.7 | ✅ | +| `custom:custom-model-a2` | Claude-Sonnet-4.6 | ✅ | +| `custom:custom-model-a3` | Claude-Opus-4.6 | ✅ | +| `custom:custom-model-c2-flash` | Gemini-3.5-Flash-1 | ✅ | +| `auto` / `hy3` / `glm-5.3` / `deepseek-v4-pro` | Auto / 混元3 / GLM-5.3 / DeepSeek-V4-Pro | ✅(deepseek 慢 3 倍) | +| `custom:doubao-seed-2-1-pro-260628` | — | ❌ **500**,未开通 | +| `custom:doubao-seed-2-0-lite-260428` | — | ❌ **400 `service info not found`** | + +> **doubao 系在本机不可用**:既不在桌面产品配置的 48 个模型里,也不在该 API Key 租户的服务清单里。 +> CLI 的静态清单里出现它,只是「名单上有、没开通」。 + +### 速度对比 A:短提示词(约 130 字输出,含 ~10s CLI 启动开销) + +| 模型 | 耗时 | +|:--|:--| +| GPT-5.5-Standard | 15.7s | +| Claude-Opus-4.8 | 18.5s | +| Kimi-K2.5 | 21.7s | +| GLM-5.3 / GLM-5.3-Flash | 22.5s / 23.6s | +| **DeepSeek-V4-Pro** | **46.2s** 🐢 | + +### 速度对比 B:端到端走 `/api/ai/clarify` 真实需求打磨(含账号设定 + 完整模板) + +| 模型 | 普通创作问答 | 问热点(触发搜索) | `==OPTS==/==REQ==` 合规 | +|:--|:--|:--|:--| +| **GPT-5.5-Standard**(当前) | **42.2s** | 137.2s | ✅ | +| Claude-Opus-4.8 | 105.5s | 139.6s | ✅ | +| DeepSeek-V4-Pro | 119.5s | 151.2s | ✅ | + +> 结论:**普通问答差距最大(2.5 倍)**;一旦触发联网搜索,各模型都要 130–150s, +> 瓶颈转移到搜索本身 → 这就是 `CLI_SEARCH_HINT` 要把搜索预算压到 ≤2 次的原因。 + +--- + +## 10. 排障速查 + +| 现象 | 原因 | 处理 | +|:--|:--|:--| +| 进程卡死无输出 | `SERVER__PORT` 未清除 → EADDRINUSE | 确认 §5.1 的 delete 生效 | +| `Cannot find module '../dist/codebuddy'` | bundle 分流未命中 | 设 `CODEBUDDY_FORCE_HEADLESS_BUNDLE=1` | +| `Authentication required. Please use /login` | 无凭据 | 按 §3 配 key | +| `ready:false` 但 key 已填 | `internetEnvironment` 版本不对 | 中国版必须是 `internal` | +| `500 internal server error` / `400 Custom model [...] service info not found` | 该模型未在当前 Key 租户开通 | 换 §9 表内 ✅ 的模型 | +| 首字要等 60s+ | 推理档位过高 | `"effort": "low"` | +| 回答里混进英文过程话术 | 工具调用前的叙述 | §8 的 `toolMarks` 切分(已内置) | +| HTTP 层全正常、agent 却不干活 | 典型 EADDRINUSE 假象 | 看 §5.1 | + +--- + +## 11. 完整流程=两段,模型来源不同(⚠️ 容易搞混) + +工作台「AI 创作」实际只有两个后端调用: + +| 环节 | 接口 | 谁在跑 | 模型从哪来 | +|:--|:--|:--|:--| +| **需求打磨**(弹窗内对话) | `POST /api/ai/clarify` | **本方案**:本地 CLI 子进程 | `config.json → ai.cli.model` | +| **提交创作任务**(生成脚本 S1-S11) | `POST /api/run` | **WorkBuddy 宿主**:写 automation,由桌面端调度器执行 | **`sessions` 表里最近活跃的「手动会话」的 `model`**,兜底 `auto` | + +> 🔴 **两者互不相干。** 改 `ai.cli.model` **不会**影响创作任务用的模型。 +> 创作任务的模型是「跟随用户在 WorkBuddy UI 里最近选过的模型」——即浮动的, +> 从工作台侧看**不确定**。查证方法: +> ```sql +> SELECT model_id, COUNT(*) FROM automations GROUP BY model_id ORDER BY 2 DESC; +> ``` +> 本机实测历史分布:`hy4-preview` 30 条 / `deepseek-v4.1-flash` 6 条。 +> 想让创作任务固定用某个模型,需要在 UI 里把当前会话模型切到它(或改造 `/api/run` 支持显式 `model_id`)。 + +### 完整链路自检结果(2026-09-29,model = deepseek-v4.1-flash) + +| 环节 | 结果 | +|:--|:--| +| `GET /api/ai/status` | backend=`cli` · model=`deepseek-v4.1-flash` · ready=**true** | +| `GET /api/ai/status?probe=1` | ok=**true** · 17.0s · 返回「成功」 | +| `POST /api/ai/clarify`(stream) | 371 字 · `==OPTS==/==REQ==` 合规 **✅** · 无英文残留 **✅** · 51.7s | +| `POST /api/run` → 轮询状态 | queued(5s) → running(30s) → **done**(50s) · `model_id` 落库 = `deepseek-v4.1-flash` **✅** | + +> 注:`/api/ai/topics` 是**遗留死代码** —— 前端从不调用它(选题列表走 `/api/dsh/topics`)。 +> 它仍写死 Dify,未配 `DIFY_MCN_CYLG_KEY` 时会 500,但**不影响真实流程**。 diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/对话式选题完善_用户场景案例.md b/project/短视频脚本创作/V1.0/mcn-workshop/docs/对话式选题完善_用户场景案例.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/docs/对话式选题完善_用户场景案例.md rename to project/短视频脚本创作/V1.0/mcn-workshop/docs/对话式选题完善_用户场景案例.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/工作台视觉交互规范.md b/project/短视频脚本创作/V1.0/mcn-workshop/docs/工作台视觉交互规范.md similarity index 99% rename from project/短视频脚本创作/V1.0/mcn-work-shop/docs/工作台视觉交互规范.md rename to project/短视频脚本创作/V1.0/mcn-workshop/docs/工作台视觉交互规范.md index bac13cc..be54735 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/docs/工作台视觉交互规范.md +++ b/project/短视频脚本创作/V1.0/mcn-workshop/docs/工作台视觉交互规范.md @@ -1,6 +1,6 @@ # 短视频工作台 · 视觉与交互规范(最终态) -> **定位**:本文件是工作台前端(`mcn-work-shop/`)视觉与交互的**唯一权威**——设计 token、布局框架、**逐页最终态**、组件规格、交互链路、已知坑。 +> **定位**:本文件是工作台前端(`mcn-workshop/`)视觉与交互的**唯一权威**——设计 token、布局框架、**逐页最终态**、组件规格、交互链路、已知坑。 > **用法**:新增页面 / 改样式 / 改交互前先读本文件;改完**回写本文件**(含文末变更记录),避免同类坑重复踩。 > **技术基线**:零依赖 Node 服务 + 原生 JS(无框架无构建);`public/` 下 `index.html` / `style.css` / `app.js` / `data-pages.js` / `help-guide.js`。 > **最后更新**:2026-09-16(本轮把此前的 `工作台UI规范.md` 合并升级为本文件并重命名为「视觉交互规范」,内容为**迭代后的最终态**) diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js b/project/短视频脚本创作/V1.0/mcn-workshop/dsh-data.js similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/dsh-data.js rename to project/短视频脚本创作/V1.0/mcn-workshop/dsh-data.js diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/get-port.js b/project/短视频脚本创作/V1.0/mcn-workshop/get-port.js similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/get-port.js rename to project/短视频脚本创作/V1.0/mcn-workshop/get-port.js diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/load-config.js b/project/短视频脚本创作/V1.0/mcn-workshop/load-config.js similarity index 55% rename from project/短视频脚本创作/V1.0/mcn-work-shop/load-config.js rename to project/短视频脚本创作/V1.0/mcn-workshop/load-config.js index 825de63..84dec6a 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/load-config.js +++ b/project/短视频脚本创作/V1.0/mcn-workshop/load-config.js @@ -11,6 +11,10 @@ const DEFAULTS = { outputRoot: 'MCNSkill项目', businessDataDir: ['MCNSkill项目', '三方数据', '红狐数据', '抖音榜单'], extraRoots: [], + // 10-08:工作台提交的 AI 任务会话归属哪个工作区 —— 侧边栏分组按会话 cwd 聚合, + // cwd 不是已打开过的工作区就会落到「未分组任务」。 + // 留空 → 用工作台自身目录(ROOT);填绝对路径 → 会话归到那个工作区(改分组只改这里,不动代码)。 + sessionCwd: '', // 09-29 AI 通路配置:backend 决定走哪条链路 // "cli" 本地 CodeBuddy CLI 进程(stdio,无端口;需自备 CODEBUDDY_API_KEY) // "dify" Dify chat-messages(原链路,保留兜底) @@ -19,11 +23,23 @@ const DEFAULTS = { cli: { path: '', // 留空 → 自动探测(环境变量 CODEBUDDY_CLI_PATH > WorkBuddy 自带 CLI > PATH) model: 'custom:doubao-seed-2-1-pro-260628', - apiKey: '', // 留空 → 读环境变量 CODEBUDDY_API_KEY - authToken: '', // 留空 → 读环境变量 CODEBUDDY_AUTH_TOKEN(优先级最高) + apiKey: '', // 一般留空:config.json 是 git 跟踪文件,key 别写这里 + authToken: '', // 同上 + // 凭据文件(JSON:{"apiKey":"..."} 或 {"authToken":"..."})。 + // 留空 → 默认 ~/.workbuddy/mcn-workshop/ai-cli.json(刻意放仓库外,不会被技能同步复制) + credentialFile: '', internetEnvironment: 'internal', // 中国版=internal,iOA=ioa,国际版留空 permissionMode: '', timeoutMs: 300000, + // 推理开销档位:minimal|low|medium|high|xhigh|max(留空=模型默认)。 + // 这是首字延迟的第一大杠杆:reasoning 模型默认档位会让首字等到 60s+。 + effort: 'low', + // 联网搜索:true → 工具面收敛为 WebSearch+WebFetch(启动快,且模型碰不到文件系统) + search: true, + tools: '', // 显式覆盖工具白名单:'' = 按 search 推导;'none' = 全禁 + strictMcp: true, // 屏蔽宿主 MCP(weixinpay/sheetagent 等) + appendSystemPrompt: '', // 追加系统提示 + maxTurns: 6, // agent 轮数硬上限(防搜索/工具调用失控跑飞) }, }, }; diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/poll-create-tasks.cjs b/project/短视频脚本创作/V1.0/mcn-workshop/poll-create-tasks.cjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/poll-create-tasks.cjs rename to project/短视频脚本创作/V1.0/mcn-workshop/poll-create-tasks.cjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/poll-review-tasks.cjs b/project/短视频脚本创作/V1.0/mcn-workshop/poll-review-tasks.cjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/poll-review-tasks.cjs rename to project/短视频脚本创作/V1.0/mcn-workshop/poll-review-tasks.cjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/probe-asar-skills.cjs b/project/短视频脚本创作/V1.0/mcn-workshop/probe-asar-skills.cjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/probe-asar-skills.cjs rename to project/短视频脚本创作/V1.0/mcn-workshop/probe-asar-skills.cjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/prompts/clarify.md b/project/短视频脚本创作/V1.0/mcn-workshop/prompts/clarify.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/prompts/clarify.md rename to project/短视频脚本创作/V1.0/mcn-workshop/prompts/clarify.md diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js b/project/短视频脚本创作/V1.0/mcn-workshop/public/app.js similarity index 93% rename from project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js rename to project/短视频脚本创作/V1.0/mcn-workshop/public/app.js index b2d0e60..29d1def 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/app.js +++ b/project/短视频脚本创作/V1.0/mcn-workshop/public/app.js @@ -70,7 +70,8 @@ async function executeTask(opts) { }); if (d.error) throw new Error(d.error); taskId = d.id; - showToast('✅ 任务已提交,请在左侧会话栏查看执行'); + showToast('✅ 任务已提交,执行进度见右下角面板'); + TaskProgress.show(opts.label || '工作台任务'); if (opts.onSubmit) { try { opts.onSubmit(taskId); } catch (e) {} } } catch (e) { showToast('❌ 任务提交失败: ' + e.message); @@ -82,6 +83,7 @@ async function executeTask(opts) { while (Date.now() < deadline) { await new Promise((r) => setTimeout(r, 5000)); try { s = await api('/api/run/status?id=' + encodeURIComponent(taskId)); } catch (e) { continue; } + if (s) TaskProgress.update(s, opts.label || '工作台任务'); if (!s || s.state === 'notfound') { finished = true; finish(s || null); break; } if (s.state === 'done') { finished = true; finish(s); showToast('✅ 任务执行完成,页面已自动刷新'); refreshCurrentPage(); break; } if (s.state === 'error') { finished = true; finish(s); showToast('❌ 任务执行失败' + (s.error ? ':' + s.error : '')); break; } @@ -90,7 +92,9 @@ async function executeTask(opts) { } restore(); if (!ended) finish(null); - if (!finished) showToast('⏳ 任务仍在执行,请到左侧会话栏查看进度'); + if (!finished) showToast('⏳ 任务仍在执行,右下角面板可继续观察进度'); + // 终态保留 8 秒展示结果摘要(状态/工具调用/最近动作)后自动收起;未完成则保留面板供继续观察 + if (finished) setTimeout(() => TaskProgress.hide(), 8000); } // 通用确认弹窗:确认后执行 AI 任务(opts: title/desc/target/url/prompt/label/btn/afterText) @@ -263,6 +267,58 @@ function showToast(msg) { _toastTimer = setTimeout(() => t.classList.remove('show'), 1800); } +/* ================= 任务进度面板(10-08 新增) ================= + 后台任务原本只弹一句「已提交」,用户无法判断到底在跑还是卡住、做了什么。 + 本面板把 /api/run/status 的进度(状态/耗时/工具调用/最近动作/是否停滞)持续显示, + 不必再去左侧会话栏翻找。用法:TaskProgress.show(标题) → 轮询中 TaskProgress.update(s) → TaskProgress.hide() */ +const TaskProgress = (function () { + let el = null; + const fmtDur = (ms) => { + if (ms == null) return '—'; + const s = Math.max(0, Math.round(Number(ms) / 1000)); + if (s < 60) return s + ' 秒'; + return Math.floor(s / 60) + ' 分 ' + (s % 60) + ' 秒'; + }; + const STATE_TXT = { queued: '排队中', pending: '等待调度', running: '执行中', done: '已完成', error: '执行失败', review: '待确认', notfound: '任务已清理' }; + function ensure() { + if (el && document.body.contains(el)) return el; + el = document.createElement('div'); + el.className = 'tp-panel'; + document.body.appendChild(el); + return el; + } + function render(title, s) { + const box = ensure(); + const st = (s && s.state) || 'queued'; + const dotCls = st === 'running' ? 'run' : (st === 'done' ? 'done' : (st === 'error' ? 'err' : '')); + const tc = (s && s.toolCalls) ? s.toolCalls : {}; + const tools = Object.keys(tc).map((k) => '' + esc(k) + ' ×' + tc[k] + '').join(''); + const stalled = st === 'running' && !!(s && s.stalled); + const ago = (s && s.updatedAgoMs != null) ? fmtDur(s.updatedAgoMs) : null; + box.innerHTML = + '
      ' + + '
      ' + esc(title || (s && s.sessionTitle) || 'AI 任务') + '
      ' + + '
      ' + + '
      ' + + '
      状态' + esc(STATE_TXT[st] || st) + + (s && s.elapsedMs != null ? '(已运行 ' + fmtDur(s.elapsedMs) + ')' : '') + '
      ' + + (tools ? '
      工具
      ' + tools + '
      ' : '') + + (s && s.lastAction ? '
      最近动作
      ' + esc(s.lastAction) + '
      ' : '') + + (ago && st === 'running' ? '
      活跃' + + (stalled ? '已 ' : '') + esc(ago) + '前有输出
      ' : '') + + (stalled ? '
      ⚠️ 超过 90 秒没有新输出,可能卡住或等待中
      ' : '') + + (s && s.error ? '
      错误:' + esc(String(s.error)) + '
      ' : '') + + '
      '; + const btn = box.querySelector('.tp-close'); + if (btn) btn.onclick = hide; + } + function show(title, s) { render(title, s || null); } + function update(s, title) { render(title, s); } + function hide() { if (el && el.parentNode) el.parentNode.removeChild(el); el = null; } + return { show, update, hide }; +})(); +window.TaskProgress = TaskProgress; + /* ================= 迷你 Markdown 渲染器 ================= */ function inlineMd(s) { let out = esc(s); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js b/project/短视频脚本创作/V1.0/mcn-workshop/public/data-pages.js similarity index 99% rename from project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js rename to project/短视频脚本创作/V1.0/mcn-workshop/public/data-pages.js index 8b1415f..2cf0563 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/data-pages.js +++ b/project/短视频脚本创作/V1.0/mcn-workshop/public/data-pages.js @@ -1056,19 +1056,32 @@ async function updateRankingData(view, btn) { body: JSON.stringify({ prompt: rankPrompt, force: !!inCooldown }), }); if (!r || r.ok === false) throw new Error((r && r.error) || '任务提交失败'); - showToast('✅ 榜单更新任务已提交,请到左侧会话栏查看执行'); + showToast('✅ 榜单更新任务已提交,执行进度见右下角面板'); + TaskProgress.show('更新榜单数据'); // 4) 轮询任务状态(最长 15 分钟;期间按钮保持 busy) + // 10-08 修复:工作台服务被回收时 api() 会持续抛错,原逻辑静默 continue 直到 15 分钟超时、用户零感知 + // ⇒ 连续 3 次查询失败即判定服务已断开,提示并结束轮询(任务仍在后台跑,只是工作台看不到进度) const deadline = Date.now() + 15 * 60 * 1000; let state = 'running'; + let pollFail = 0; while (Date.now() < deadline) { await new Promise((res) => setTimeout(res, 5000)); let s = null; - try { s = await api('/api/run/status?id=' + encodeURIComponent(r.id)); } catch (e) { continue; } + try { s = await api('/api/run/status?id=' + encodeURIComponent(r.id)); pollFail = 0; if (s) TaskProgress.update(s, '更新榜单数据'); } + catch (e) { + if (++pollFail >= 3) { + restore(); + showToast('⚠️ 工作台服务已断开(连续 3 次查询失败);任务仍在后台执行,重启工作台后请到左侧会话栏查看进度'); + return; + } + continue; + } if (!s || s.state === 'notfound') { state = 'done'; break; } if (s.state === 'done') { state = 'done'; break; } if (s.state === 'error' || s.state === 'review') { state = s.state; break; } } restore(); + if (state !== 'running') setTimeout(() => TaskProgress.hide(), 8000); if (state === 'done') { showToast('✅ 榜单数据已更新,正在刷新…'); // 5) 整页重建:丢弃 _reload(同闭包 render 不清 meta 缓存)→ router 重新进入渲染 → 拉到最新数据期 @@ -1216,12 +1229,15 @@ function updateFollowedAccounts(btn) { }); if (r.error) throw new Error(r.error); const taskId = r.id; + const taskTitle = '更新账号·' + name; + TaskProgress.show(taskTitle); const deadline = Date.now() + 20 * 60 * 1000; let state = 'running'; while (Date.now() < deadline) { await new Promise((res) => setTimeout(res, 5000)); let s = null; try { s = await api('/api/run/status?id=' + encodeURIComponent(taskId)); } catch (e) { continue; } + if (s) TaskProgress.update(s, taskTitle); if (!s || s.state === 'notfound') { state = 'done'; break; } if (s.state === 'done') { state = 'done'; break; } if (s.state === 'error' || s.state === 'review') { state = s.state; break; } @@ -1231,6 +1247,7 @@ function updateFollowedAccounts(btn) { } catch (e) { failed.push(name + '(' + e.message + ')'); } } restore(); + TaskProgress.hide(); if (btn) btn.blur(); showToast(done.length ? `✅ 已更新 ${done.length} 个关注账号${failed.length ? `;${failed.length} 个未完成:${failed.join('、')}` : ''}` : '❌ 更新未完成:' + failed.join('、')); refreshCurrentPage(); @@ -1950,12 +1967,15 @@ function readVideoTaskRec(videoId) { /* 刷新后恢复轮询:跟随该视频解析任务直到终态 → 清理记录 → 提示并刷新本页按钮态 */ async function watchVideoParseTask(videoId, taskId, reload) { let s = null; + TaskProgress.show('视频解析'); const deadline = Date.now() + 35 * 60 * 1000; while (Date.now() < deadline) { await new Promise((r) => setTimeout(r, 5000)); try { s = await api('/api/run/status?id=' + encodeURIComponent(taskId)); } catch (e) { continue; } + if (s) TaskProgress.update(s, '视频解析'); if (!s || s.state === 'notfound' || s.state === 'done' || s.state === 'error' || s.state === 'review') break; } + setTimeout(() => TaskProgress.hide(), 8000); try { localStorage.removeItem(VIDEO_TASK_KEY(videoId)); } catch (e) {} if (s && s.state === 'done') showToast('✅ 视频解析完成,已自动刷新本页'); else if (s && s.state === 'error') showToast('❌ 视频解析失败' + (s.error ? ':' + s.error : '') + '(任务已结束,可重新点击解析)'); diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/help-guide.js b/project/短视频脚本创作/V1.0/mcn-workshop/public/help-guide.js similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/public/help-guide.js rename to project/短视频脚本创作/V1.0/mcn-workshop/public/help-guide.js diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html b/project/短视频脚本创作/V1.0/mcn-workshop/public/index.html similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/public/index.html rename to project/短视频脚本创作/V1.0/mcn-workshop/public/index.html diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css b/project/短视频脚本创作/V1.0/mcn-workshop/public/style.css similarity index 97% rename from project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css rename to project/短视频脚本创作/V1.0/mcn-workshop/public/style.css index 23cdf96..e69c12d 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/public/style.css +++ b/project/短视频脚本创作/V1.0/mcn-workshop/public/style.css @@ -880,3 +880,28 @@ td.td-title { max-width: 240px; } .g-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; } .g-actions .btn-sm { padding: 4px 12px; font-size: 14px; } .g-actions .ghost:disabled { opacity: .5; cursor: default; } + +/* ===== 任务进度面板(10-08 新增:后台 AI 任务执行过程可见) ===== */ +.tp-panel { + position: fixed; right: 18px; bottom: 18px; z-index: 9999; + width: 380px; max-width: calc(100vw - 36px); + background: var(--panel); border: 1px solid var(--border); border-radius: 12px; + box-shadow: 0 8px 28px rgba(0,0,0,.16); overflow: hidden; + font-size: 13.5px; color: var(--text); +} +.tp-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--border); background: var(--primary-soft); } +.tp-title { font-weight: 700; font-size: 14px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.tp-close { border: none; background: transparent; color: var(--dim); cursor: pointer; font-size: 16px; line-height: 1; padding: 2px 4px; } +.tp-body { padding: 10px 12px; } +.tp-row { display: flex; gap: 8px; margin-bottom: 6px; } +.tp-row .k { color: var(--dim); flex: 0 0 62px; } +.tp-row .v { flex: 1; word-break: break-all; } +.tp-tools { display: flex; flex-wrap: wrap; gap: 6px; } +.tp-tools span { background: #f0f2f5; border-radius: 20px; padding: 2px 9px; font-size: 12px; font-variant-numeric: tabular-nums; } +.tp-act { max-height: 92px; overflow: auto; background: #fafbfc; border: 1px solid var(--border); border-radius: 8px; padding: 8px 9px; color: var(--dim); font-size: 12.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; } +.tp-warn { margin-top: 8px; padding: 7px 9px; border-radius: 8px; background: #fff3e6; color: #b3541e; font-size: 12.5px; font-weight: 600; } +.tp-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); flex: 0 0 8px; } +.tp-dot.run { animation: tp-pulse 1.1s ease-in-out infinite; } +.tp-dot.done { background: #2f9e44; } +.tp-dot.err { background: var(--danger); } +@keyframes tp-pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/public/vendor/echarts.min.js b/project/短视频脚本创作/V1.0/mcn-workshop/public/vendor/echarts.min.js similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/public/vendor/echarts.min.js rename to project/短视频脚本创作/V1.0/mcn-workshop/public/vendor/echarts.min.js diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/scripts/attachment-parser.js b/project/短视频脚本创作/V1.0/mcn-workshop/scripts/attachment-parser.js similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/scripts/attachment-parser.js rename to project/短视频脚本创作/V1.0/mcn-workshop/scripts/attachment-parser.js diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/scripts/export_video_map.py b/project/短视频脚本创作/V1.0/mcn-workshop/scripts/export_video_map.py similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/scripts/export_video_map.py rename to project/短视频脚本创作/V1.0/mcn-workshop/scripts/export_video_map.py diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js b/project/短视频脚本创作/V1.0/mcn-workshop/server.js similarity index 90% rename from project/短视频脚本创作/V1.0/mcn-work-shop/server.js rename to project/短视频脚本创作/V1.0/mcn-workshop/server.js index 87e09aa..3f01278 100644 --- a/project/短视频脚本创作/V1.0/mcn-work-shop/server.js +++ b/project/短视频脚本创作/V1.0/mcn-workshop/server.js @@ -13,7 +13,7 @@ const dsh = require('./dsh-data'); const { parseAttachment } = require('./scripts/attachment-parser'); const { loadConfig } = require('./load-config'); // 09-29 新增:本地 CodeBuddy CLI 后端(stdio 直连,替代 Dify;见 cli-backend.js 头部说明) -const { runCli, resolveCli } = require('./cli-backend'); +const { runCli, resolveCli, resolveCredentials } = require('./cli-backend'); // 09-01 默认端口由 8899 改为 8900:本机 8899 被小米 PC 管家 MiPCAudio.exe 系统服务占用(0.0.0.0 监听且自动复活), // 此时 127.0.0.1:8899 绑定不生效 → 默认直接起在 8900;仍可 `node server.js <端口>` 显式换端口 @@ -34,10 +34,46 @@ function aiBackend() { return String((CFG.ai && CFG.ai.backend) || 'dify').toLowerCase(); } +// 09-29 CLI 后端的联网搜索策略(追加到系统提示)。 +// 根因:CLI 自带 WebSearch/WebFetch,但默认不会主动用;而每次都搜又会把首字延迟从十几秒拖到几十秒。 +// 所以给模型一条明确的「什么时候才搜」的判据,而不是简单开关。 +const CLI_SEARCH_HINT = + '你具备联网搜索能力(WebSearch / WebFetch),但搜索很慢,要严格按判据使用。' + + '【该搜】回答依赖「今天/近期」的外部事实时:当下热点、平台规则与趋势、行业数据、事实核查。' + + '【不该搜】创作讨论、需求澄清、脚本撰写、文案打磨——这些靠你的知识直接答,一律不搜。' + + '【搜索预算】中文关键词;最多 1 次 WebSearch;仅当搜索结果指向必须打开的页面时才追加 1 次 WebFetch;' + + '工具调用总数硬上限 2 次。搜到即答,禁止反复搜或换词重搜。' + + '搜索失败或超时就直接凭已有信息作答,不要重试。'; + +// 由 config.json 的 ai.cli 段构造一次 CLI 调用参数(clarify / 自检 / 后续其它入口共用,避免多处硬编码) +function cliBaseOpts(over) { + const c = (CFG.ai && CFG.ai.cli) || {}; + const searchOn = c.search !== false; + return Object.assign({ + cwd: ROOT, + // MCN_AI_MODEL 可临时覆盖模型(用于不重启、不改配置的 A/B 对比与排障) + model: String(process.env.MCN_AI_MODEL || '').trim() || c.model, + apiKey: c.apiKey, + authToken: c.authToken, + credentialFile: c.credentialFile, + internetEnvironment: c.internetEnvironment, + permissionMode: c.permissionMode, + cliPath: c.path, + timeoutMs: Math.max(30000, parseInt(c.timeoutMs, 10) || 300000), + effort: c.effort || '', + search: searchOn, + tools: c.tools, + strictMcp: c.strictMcp !== false, + maxTurns: c.maxTurns, + appendSystemPrompt: c.appendSystemPrompt || (searchOn ? CLI_SEARCH_HINT : ''), + }, over || {}); +} + // 09-10 路径治本(配套「仓库特征判定」):工作台运行时路径一律从 ROOT 动态推导,禁止写死盘符。 // 根因:原写死盘符指向仓库外的固定目录,仓库迁移后该目录不存在 → 浏览器锁失效、会话 cwd 悬空。 const BROWSER_LOCK_FILE = path.join(ROOT, '.browser-lock'); // 浏览器互斥锁文件(与工作台同目录) -const SESSION_CWD = ROOT; // 工作台触发的 AI 会话 cwd(分组名 = mcn-work-shop) +// 10-08:默认工作台自身目录;config.json 的 sessionCwd 可指定别的工作区(分组随之改变) +const SESSION_CWD = String(CFG.sessionCwd || '').trim() || ROOT; // 09-07:prompt 模板文件化(prompts/*.md)——规则迭代只改文件不碰代码;文件缺失/损坏回退 null,调用方自行兜底。 // 模板正文取 ==PROMPT== 与 ==CHANGELOG== 之间(头部说明/文末变更记录不发给模型) @@ -359,7 +395,7 @@ function createOnceAutomation(name, prompt, skillsArr, connectorIdsArr) { try { const now = Date.now(); const id = 'automation-' + now; - const cwd = SESSION_CWD; // 工作台触发的会话归入 mcn-work-shop 空间分组(由 ROOT 动态推导) + const cwd = SESSION_CWD; // 工作台触发的会话归入 mcn-workshop 空间分组(由 ROOT 动态推导) const d = new Date(now + 5 * 1000); const pad = (n) => String(n).padStart(2, '0'); const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; @@ -401,6 +437,55 @@ function rankLastTaskState() { } catch (e) { return null; } } +// ---------- 任务进度可见(10-08 新增:用户「我需要看到 才知道执行的是否正常」)---------- +// 后台任务只返回 running/done 太粗:用户无法判断"在真跑"还是"卡住了"。 +// 解法:automation → 关联它拉起的后台会话 → 读该会话 jsonl 尾部 → 产出 +// 已运行时长 / 工具调用统计 / 最近一段动作 / 会话最后写入距今(判断是否停滞) + +// 会话 cwd → WorkBuddy projects 目录名(实测:`D:\a\b\V1.0\mcn-workshop` → `d-a-b-V1.0-mcn-workshop`) +function escapeCwdForProjects(cwd) { + const s = String(cwd || '').replace(/\\/g, '/'); + const m = s.match(/^([A-Za-z]):\/(.+)$/); + if (!m) return null; + return m[1].toLowerCase() + '-' + m[2].replace(/\//g, '-'); +} + +const TOOL_NAME_RE = /"name":"(Bash|Read|Write|Edit|Grep|Glob|WebSearch|WebFetch|WebFetch2|Skill|Task|Agent|TodoWrite|ExitPlanMode|mcp__[A-Za-z0-9_]+)"/g; + +function readSessionProgress(sessionId, cwd) { + const out = { lastAction: '', toolCalls: {}, updatedAgoMs: null, stalled: false, logBytes: 0 }; + try { + const esc = escapeCwdForProjects(cwd); + if (!esc) return out; + const f = path.join(os.homedir(), '.workbuddy', 'projects', esc, sessionId + '.jsonl'); + if (!fs.existsSync(f)) return out; + const st = fs.statSync(f); + out.logBytes = st.size; + out.updatedAgoMs = Date.now() - Number(st.mtimeMs); + // 90 秒没再写入即视为停滞(模型正常推进时几乎每秒都在写日志) + out.stalled = out.updatedAgoMs > 90000; + if (st.size <= 0) return out; + const readLen = Math.min(st.size, 256 * 1024); + const fd = fs.openSync(f, 'r'); + const buf = Buffer.alloc(readLen); + fs.readSync(fd, buf, 0, readLen, st.size - readLen); + fs.closeSync(fd); + const txt = buf.toString('utf8'); + let m; + TOOL_NAME_RE.lastIndex = 0; + while ((m = TOOL_NAME_RE.exec(txt))) out.toolCalls[m[1]] = (out.toolCalls[m[1]] || 0) + 1; + const texts = []; + const re2 = /"text":"((?:[^"\\]|\\.){4,})"/g; + while ((m = re2.exec(txt))) { + let s = m[1]; + try { s = JSON.parse('"' + s + '"'); } catch (e) { /* 解析失败保留原串 */ } + if (s && String(s).trim()) texts.push(String(s).trim()); + } + if (texts.length) out.lastAction = texts[texts.length - 1].replace(/\s+/g, ' ').slice(0, 160); + } catch (e) { /* 读不到不影响状态判定 */ } + return out; +} + // ---- 静态文件 / API ---- const server = http.createServer((req, res) => { const u = new URL(req.url, 'http://localhost'); @@ -777,7 +862,7 @@ const server = http.createServer((req, res) => { const db = new DatabaseSync(process.env.WORKBUDDY_DB || path.join(os.homedir(), '.workbuddy', 'workbuddy.db')); const now = Date.now(); const id = 'automation-' + now; - const cwd = SESSION_CWD; // 工作台触发的会话归入 mcn-work-shop 空间分组(由 ROOT 动态推导) + const cwd = SESSION_CWD; // 工作台触发的会话归入 mcn-workshop 空间分组(由 ROOT 动态推导) const d = new Date(Date.now() + 5 * 1000); // 未来 5 秒(08-31 根因:客户端只对未来 scheduledAt 补算 next_run_at;写 now=过去时间→不补算→调度器扫不到→卡死) const pad = (n) => String(n).padStart(2, '0'); const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; @@ -809,10 +894,17 @@ const server = http.createServer((req, res) => { try { const { DatabaseSync } = require('node:sqlite'); const db = new DatabaseSync(process.env.WORKBUDDY_DB || path.join(os.homedir(), '.workbuddy', 'workbuddy.db'), { readOnly: true }); - const auto = db.prepare('SELECT status, deleted_at, last_run_at FROM automations WHERE id=?').get(id); + const auto = db.prepare('SELECT status, deleted_at, last_run_at, created_at FROM automations WHERE id=?').get(id); if (!auto || auto.deleted_at) { db.close(); return sendJSON(200, { ok: true, id, state: 'notfound' }); } const st = db.prepare('SELECT running, last_error FROM automation_runtime_state WHERE automation_id=?').get(id); const runs = db.prepare("SELECT status FROM automation_runs WHERE automation_id=? ORDER BY created_at DESC LIMIT 1").get(id); + // 10-08:关联本任务拉起的后台会话(任务创建后 5 分钟内第一条自动化会话),读其日志产出进度 + const t0 = Number(auto.created_at) || 0; + const sess = t0 ? db.prepare( + 'SELECT id, title, cwd FROM sessions WHERE is_background_automation=1 AND created_at >= ? AND created_at <= ? ORDER BY created_at ASC LIMIT 1' + ).get(t0 - 15000, t0 + 300000) : null; + const prog = sess ? readSessionProgress(sess.id, sess.cwd) : null; + const elapsedMs = t0 ? Date.now() - t0 : null; db.close(); let state = 'queued'; if (st && Number(st.running) === 1) state = 'running'; @@ -824,7 +916,17 @@ const server = http.createServer((req, res) => { else if (['ERROR', 'CANCELLED', 'FAILED', 'INTERRUPTED'].includes(s)) state = 'error'; else state = 'pending'; // ACCEPTED / QUEUED / PENDING 等 = 排队等待执行 } - return sendJSON(200, { ok: true, id, state, error: (st && st.last_error) || null }); + return sendJSON(200, { + ok: true, id, state, error: (st && st.last_error) || null, + elapsedMs, + sessionId: (sess && sess.id) || null, + sessionTitle: (sess && sess.title) || null, + lastAction: (prog && prog.lastAction) || '', + toolCalls: (prog && prog.toolCalls) || {}, + updatedAgoMs: (prog && prog.updatedAgoMs) != null ? prog.updatedAgoMs : null, + stalled: !!(prog && prog.stalled), + logBytes: (prog && prog.logBytes) || 0, + }); } catch (e) { return sendErr(500, '状态查询失败: ' + e.message); } @@ -888,7 +990,7 @@ const server = http.createServer((req, res) => { resp = await fetch(difyUrl + '/chat-messages', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' }, - body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'blocking', user: 'mcn-work-shop' }), + body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'blocking', user: 'mcn-workshop' }), signal: ctrl.signal, }); } finally { clearTimeout(timer); } @@ -915,10 +1017,7 @@ const server = http.createServer((req, res) => { if (!t) return ''; return t.length <= 8 ? '***' : t.slice(0, 6) + '***' + t.slice(-4); }; - const envKey = process.env.CODEBUDDY_API_KEY || ''; - const envTok = process.env.CODEBUDDY_AUTH_TOKEN || ''; - const effKey = c.apiKey || envKey; - const effTok = c.authToken || envTok; + const cred = resolveCredentials({ apiKey: c.apiKey, authToken: c.authToken, credentialFile: c.credentialFile }); const info = { backend, cli: { @@ -926,10 +1025,11 @@ const server = http.createServer((req, res) => { pathConfigured: !!c.path, model: c.model || '', internetEnvironment: c.internetEnvironment || '', - credential: effTok ? 'CODEBUDDY_AUTH_TOKEN(' + mask(effTok) + ')' - : effKey ? 'CODEBUDDY_API_KEY(' + mask(effKey) + ')' - : '(未配置)', - ready: !!(effTok || effKey), + credential: cred.source + (cred.apiKey || cred.authToken + ? ' (' + mask(cred.authToken || cred.apiKey) + ')' : ''), + credentialFile: cred.credentialFile, + credentialFileExists: (() => { try { return fs.existsSync(cred.credentialFile); } catch (e) { return false; } })(), + ready: !!(cred.authToken || cred.apiKey), }, dify: { url: String(process.env.DIFY_URL || 'https://mydify.youmanvideo.com/v1'), @@ -946,12 +1046,7 @@ const server = http.createServer((req, res) => { if (url.searchParams.get('probe') === '1' && backend === 'cli') { const t0 = Date.now(); try { - const r = await runCli({ - prompt: '只回复两个字:成功', cwd: ROOT, model: c.model, - apiKey: c.apiKey, authToken: c.authToken, - internetEnvironment: c.internetEnvironment, cliPath: c.path, - timeoutMs: 120000, - }); + const r = await runCli(cliBaseOpts({ prompt: '只回复两个字:成功', timeoutMs: 120000 })); info.probe = { ok: r.ok, ms: Date.now() - t0, text: (r.text || '').slice(0, 100), @@ -1030,18 +1125,7 @@ const server = http.createServer((req, res) => { // dify = 原链路(下方保留,未改动) // 两条链路对外都产出同一套 SSE 协议:{delta} / {replace} / {error},前端无需感知差异。 if (aiBackend() === 'cli') { - const c = (CFG.ai && CFG.ai.cli) || {}; - const base = { - prompt: query, - cwd: ROOT, - model: c.model, - apiKey: c.apiKey, - authToken: c.authToken, - internetEnvironment: c.internetEnvironment, - permissionMode: c.permissionMode, - cliPath: c.path, - timeoutMs: Math.max(30000, parseInt(c.timeoutMs, 10) || 300000), - }; + const base = cliBaseOpts({ prompt: query }); if (stream) { res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', @@ -1063,6 +1147,10 @@ const server = http.createServer((req, res) => { // 已产出的内容优先保留(模型中途失败 / 超时场景) if (acc && r.text && r.text !== acc) write({ replace: r.text }); write({ error: r.error || 'CLI 执行失败' }); + } else if (r.text && r.text !== acc) { + // 成功但「流过的内容」≠「终态正文」:典型是模型在联网搜索前先吐了一句过程话术, + // 后端已把工具调用前的部分切掉 → 这里用权威正文整体覆盖(前端原生支持 replace) + write({ replace: r.text }); } log('AI 需求打磨(cli,stream): ' + accountName + ' | ' + acc.length + '字' + (r.sessionId ? ' | session ' + r.sessionId : '') + (r.ok ? '' : ' | 失败: ' + r.error)); @@ -1092,7 +1180,7 @@ const server = http.createServer((req, res) => { const difyResp = await fetch(difyUrl + '/chat-messages', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' }, - body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'streaming', user: 'mcn-work-shop' }), + body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'streaming', user: 'mcn-workshop' }), signal: ctrl.signal, }); if (!difyResp.ok || !difyResp.body) { @@ -1171,7 +1259,7 @@ const server = http.createServer((req, res) => { difyResp = await fetch(difyUrl + '/chat-messages', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' }, - body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'blocking', user: 'mcn-work-shop' }), + body: JSON.stringify({ inputs: { is_think: '0', is_online: '0', model: process.env.DIFY_MCN_MODEL || 'gemini' }, query, response_mode: 'blocking', user: 'mcn-workshop' }), signal: ctrl.signal, }); } finally { clearTimeout(timer); } @@ -1238,14 +1326,14 @@ const server = http.createServer((req, res) => { }); }); -// ---- 会话自动清理:mcn-work-shop 空间超 6 小时无活动的已结束会话自动清除 ---- +// ---- 会话自动清理:mcn-workshop 空间超 6 小时无活动的已结束会话自动清除 ---- // 09-01 用户需求:工作台触发(cwd = 工作台目录 SESSION_CWD)的 AI 会话任务,超过 6 小时自动清除 -// 09-10 路径治本:cwd 由写死盘符改为 ROOT 动态推导(<技能根>/mcn-work-shop); -// 清理模式放宽为 '%mcn-work%',同时覆盖历史会话(旧 mcn-workshop)与新建会话(mcn-work-shop) +// 09-10 路径治本:cwd 由写死盘符改为 ROOT 动态推导(<技能根>/mcn-workshop); +// 清理模式放宽为 '%mcn-work%',同时覆盖历史会话(早期 D:\AgentSkill\mcn-workshop、迁移期 …\mcn-work-shop)与新建会话(…\V1.0\mcn-workshop) // 判定:cwd 含 mcn-work + status=completed(执行中一律不删)+ 未删过 + 最后活动时间超过 6 小时 // 方式:软删除(置 deleted_at),客户端左侧会话栏按 deleted_at 过滤即不再显示;可逆安全 const SESSION_TTL_MS = 6 * 3600 * 1000; // 6 小时 -const SESSION_CWD_PATTERN = '%mcn-work%'; // 兼容 SESSION_CWD(mcn-work-shop)与历史 cwd(mcn-workshop) +const SESSION_CWD_PATTERN = '%mcn-work%'; // 覆盖当前 SESSION_CWD(…\V1.0\mcn-workshop)与历史 cwd(早期 D:\AgentSkill\mcn-workshop、迁移期 …\mcn-work-shop) const SESSION_CLEAN_INTERVAL_MS = 60 * 60 * 1000; // 每小时检查一次 function cleanupExpiredSessions() { @@ -1262,7 +1350,7 @@ function cleanupExpiredSessions() { AND COALESCE(last_activity_at, updated_at, created_at, 0) < ? `).run(now, SESSION_CWD_PATTERN, cutoff); db.close(); - if (Number(r.changes) > 0) log(`会话自动清理:已清除 mcn-work-shop 超 6 小时无活动的已完成会话 ${r.changes} 条`); + if (Number(r.changes) > 0) log(`会话自动清理:已清除 mcn-workshop 超 6 小时无活动的已完成会话 ${r.changes} 条`); } catch (e) { console.error('会话自动清理失败:', e.message); } diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/start.bat b/project/短视频脚本创作/V1.0/mcn-workshop/start.bat similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/start.bat rename to project/短视频脚本创作/V1.0/mcn-workshop/start.bat diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/submit-review-tasks.cjs b/project/短视频脚本创作/V1.0/mcn-workshop/submit-review-tasks.cjs similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/submit-review-tasks.cjs rename to project/短视频脚本创作/V1.0/mcn-workshop/submit-review-tasks.cjs diff --git a/project/短视频脚本创作/V1.0/mcn-work-shop/自动化任务调度机制.md b/project/短视频脚本创作/V1.0/mcn-workshop/自动化任务调度机制.md similarity index 100% rename from project/短视频脚本创作/V1.0/mcn-work-shop/自动化任务调度机制.md rename to project/短视频脚本创作/V1.0/mcn-workshop/自动化任务调度机制.md diff --git a/project/短视频脚本创作/V1.0/references-add/变更日志.md b/project/短视频脚本创作/V1.0/references-add/变更日志.md index 4fe7204..23ffc97 100644 --- a/project/短视频脚本创作/V1.0/references-add/变更日志.md +++ b/project/短视频脚本创作/V1.0/references-add/变更日志.md @@ -129,11 +129,11 @@ ## 2026-09-10(工作台配置收敛:端口/DB/目录单源化,代码层统一引用 config.json) -- **触发**:用户问「这些设定能否用配置,所有文件和脚本都引用,避免有调整时到处修改」。确认范围:只收敛代码层(不动文档/技能规范),配置放 `mcn-work-shop/config.json`。 +- **触发**:用户问「这些设定能否用配置,所有文件和脚本都引用,避免有调整时到处修改」。确认范围:只收敛代码层(不动文档/技能规范),配置放 `mcn-workshop/config.json`。 - **目标**:工作台散落在 server.js / dsh-data.js / 4 个辅助脚本 / 前端 data-pages.js / start.bat 里的硬编码设定(端口 8900、DB 名 mcn-plugin.db、产出根 MCNSkill项目、榜单目录),统一收进 config.json,代码层一律从配置读,调整只需改 config.json 一处。 - **新增**: - - `mcn-work-shop/config.json`:单一配置源(port / dbName / outputRoot / businessDataDir / extraRoots) - - `mcn-work-shop/load-config.js`:配置读取唯一入口 + 默认值唯一来源(config 缺失/损坏回退 DEFAULTS) + - `mcn-workshop/config.json`:单一配置源(port / dbName / outputRoot / businessDataDir / extraRoots) + - `mcn-workshop/load-config.js`:配置读取唯一入口 + 默认值唯一来源(config 缺失/损坏回退 DEFAULTS) - **改动**: - `server.js`:`require('./load-config')` 取 CFG;`PORT_BASE = argv[2] || CFG.port`(argv 端口仍优先);`DEFAULT_ROOT` / `path.join(ROOT, CFG.dbName)` 从 CFG 读;`saveConfig` 合并写盘保留其它字段;错误提示「mcn-plugin.db」→ CFG.dbName 动态 - `dsh-data.js`:删本地 loadCfg,改用 loadConfig;`DSH_DB = path.join(__dirname, CFG.dbName)`;榜单目录 `path.join(...CFG.businessDataDir)` @@ -152,7 +152,7 @@ - **判定层**:`V1.0/references-add/路径配置.md`、`Lite1.0/references-add/路径配置.md`、`subskills/mcn-dou-analysis/references/路径配置.md`、`subskills/mcn-video-prompt/references-add/路径配置.md`(判定表/执行规则/解析规则/变更记录,v3) - **SKILL 层**:`V1.0/SKILL.md`(榜单读取 71/78、外部技能依赖 dsh 副本删除、产出路径表述)、`Lite1.0/SKILL.md`、`subskills/mcn-dou-analysis/SKILL.md`(42/44/51/52/58/59/82/95 行)、`subskills/mcn-video-prompt/SKILL.md`(502/505/531-539)、`subskills/mcn-data-insight/SKILL.md`(90-96/116 行) - **标准库**:5 份 `references/规则/路径引用规范.md`(「三种机器」→「两种机器」)、`references/创作流程规范.md`、`references/创作流程/{11_脚本检查和诊断,4_账号设定解析和确认}.md`、`references/接口调用/{MCP_工具调用规范,自动化任务清理规范}.md` - - **数据源层**:`subskills/mcn-data-insight/scripts/fetch_week_ranks.py`(删 `.dsh` 分支代码)、`mcn-work-shop/dsh-data.js`(删 `isDshDeploy` 分支 + 注释)、`mcn-work-shop/public/data-pages.js`(SKILL_HINT_RANKING + 写库注释)、`subskills/mcn-data-insight/subskills/{douyin-rise-ranking,douyin-weekly-surge,douyin-content-surge,douyin-daily-hot}/SKILL.md`、`references/规则/抖音数据规则.md` + - **数据源层**:`subskills/mcn-data-insight/scripts/fetch_week_ranks.py`(删 `.dsh` 分支代码)、`mcn-workshop/dsh-data.js`(删 `isDshDeploy` 分支 + 注释)、`mcn-workshop/public/data-pages.js`(SKILL_HINT_RANKING + 写库注释)、`subskills/mcn-data-insight/subskills/{douyin-rise-ranking,douyin-weekly-surge,douyin-content-surge,douyin-daily-hot}/SKILL.md`、`references/规则/抖音数据规则.md` - **账号分析层**:`references/账号数据分析方法.md`、`references/feature/{01_获取账号信息,03_提炼账号设定,04_分析视频数据,06_生成账号设定卡片}.md`、`references/铁律避坑规则/账号信息获取执行避坑.md`(删「dsh 原库」)、`references/浏览器搜索抖音账号操作规范.md`(159/175 行 browser-harness dsh 部署环境) - **README**:`references-add/README.md`(两处)、`subskills/mcn-dou-analysis/references-add/README.md`、`subskills/mcn-video-prompt/references-add/README.md`、`subskills/mcn-video-prompt/references/README.md` - **残留核对**:全树扫描 `.dsh`/`dshworkspace`/`dsh 部署`/`dsh 环境`/`dsh_MCNProject`/`②dsh`/`③用户环境`——**当前有效规则零残留**,剩余命中全部为豁免三类:①变更记录/变更日志历史行;②「09-10 起 dsh 环境已废弃」说明文字;③`路径引用规范.md` 用户级路径示例 `{主目录}/.dsh/...`(DSH 工具配置目录,非部署环境)。工作台重启验证 HTTP 200,`dsh-data.js`/`fetch_week_ranks.py` 语法校验通过。 @@ -172,8 +172,8 @@ - **MCP 文档**:`references/接口调用/MCP_工具调用规范.md`、`subskills/mcn-dou-analysis/references/接口调用/MCP_工具调用规范.md`(开发/非开发环境判定) - **Lite1.0**:`SKILL.md` 外部技能依赖段落(见下) - **真实读写路径修复(B 类 8 处)**: - - `mcn-work-shop/server.js`:新增 `BROWSER_LOCK_FILE = path.join(ROOT, '.browser-lock')`、`SESSION_CWD = ROOT`;2 处 `const cwd` 与 `BROWSER_LOCK` 改用常量;`SESSION_CWD_PATTERN` 由 `'%mcn-workshop%'` 放宽为 `'%mcn-work%'`(**同时兼容历史会话 mcn-workshop 与新建会话 mcn-work-shop**,避免 6 小时清理对旧会话失效) - - `mcn-work-shop/build-create-prompts.cjs` / `submit-review-tasks.cjs`:prompt 内 DB 绝对路径 → 改用文件内既有 `DB` 常量插值 + - `mcn-workshop/server.js`:新增 `BROWSER_LOCK_FILE = path.join(ROOT, '.browser-lock')`、`SESSION_CWD = ROOT`;2 处 `const cwd` 与 `BROWSER_LOCK` 改用常量;`SESSION_CWD_PATTERN` 由 `'%mcn-workshop%'` 放宽为 `'%mcn-work%'`(**同时兼容历史会话 mcn-workshop 与新建会话 mcn-workshop**,避免 6 小时清理对旧会话失效) + - `mcn-workshop/build-create-prompts.cjs` / `submit-review-tasks.cjs`:prompt 内 DB 绝对路径 → 改用文件内既有 `DB` 常量插值 - `V1.0/SKILL.md` 第 5 条:文档里的运行时 `cwds=["D:\AgentSkill\mcn-workshop"]` → 改为 `<工作台目录>`(由 server.js 从 ROOT 动态推导) - **Lite1.0 外部技能依赖修正(09-02 结构变更补同步)**:原段落仍写「三环境两步判定 + `D:\AgentSkill\mcn-dou-analysis` 外部路径」,既失效又未同步 09-02「mcn-dou-analysis 并入 `subskills/`」变更。修正为:**Lite1.0 目录下无 `subskills/`(实测),未内置该技能**;需要账号解析改用完整版「短视频工作台」V1.0,或先把技能同步进 `Lite1.0/subskills/` 后按相对路径 `subskills/mcn-dou-analysis/SKILL.md` 调用;禁止盘符绝对路径定位。 - **BROWSER_LOCK 契约同步**:`subskills/mcn-dou-analysis/references/浏览器搜索抖音账号操作规范.md` 5.6 节锁文件路径由 8 处盘符硬编码 → 改为相对 `.browser-lock`(工作台触发的会话 cwd 即工作台目录),并注明以工作台注入的绝对路径为准。 @@ -188,7 +188,7 @@ - **D·视觉脚本 → D·视觉短片(全量)**:S9 格式列举(SKILL.md)、S6 内容形式映射表(F04 测评种草/F07 纯视觉短片)、S9 正文 6 处、创作流程规范 3 处、03_框架节奏/07_脚本格式选择指南 8 处——与工作台需求框/用户既定术语对齐,消除「纯视觉短片→D·视觉脚本」混用。 - **预设钩子 → 开场钩子(全量)**:S5 选题方案字段(含 10 种开场方式引用)+ S7 大纲回扣语义 + 04_爆款开场/00_口径说明——对外统称「开场钩子」(=S6 场1 段名、工作台需求框 hook 字段同名同义),S5 阶段语义由「预设」改为直接以开场钩子表达。 -- **需求框-技能衔接注明(mcn-work-shop/prompts/clarify.md)**:内容形式 4 项为粗选方向,落创作时细化为 S6 口径(口播→教程/知识分享/盘点,视觉短片→测评/沉浸式);故事框架四词(钩子前置/悬念反转/递进铺垫/先抑后扬)=达人倾向词,由 S6 用叙事形态 F01-F08 + 结构模板(三段式/五段式)定型。 +- **需求框-技能衔接注明(mcn-workshop/prompts/clarify.md)**:内容形式 4 项为粗选方向,落创作时细化为 S6 口径(口播→教程/知识分享/盘点,视觉短片→测评/沉浸式);故事框架四词(钩子前置/悬念反转/递进铺垫/先抑后扬)=达人倾向词,由 S6 用叙事形态 F01-F08 + 结构模板(三段式/五段式)定型。 - **规则沉淀(09-07)**:新增 references/规则/概念分层与源头表述规范.md(先分层再落笔:策略/实现/容器各写源头正文;禁止 X=Y/括注跨文档打补丁,改源头正文;同族术语对外一个总称、改名一次全链路;含开场钩子家族案例);SKILL.md 红线摘要区新增 🔴 索引行;项目级 技能审计检查清单.md 一致性维新增第 11/12 条(概念分层、源头表述自洽),编号重排 1→15,使用方法补层级先于枚举。 - **开场钩子分层表述源头化(5_选题 / 6_框架 / 04_爆款开场·00_)**:三个源头正文自洽——开场方式(04_ 库 10 种标准分类)由 S5 依据用户要求/账号结构性符号/对标拆解(S3)选定并写入选题方案;S6 场次表「本场钩子」=五类留人机制(悬念提问/反差画面/直击痛点/身份共鸣/利益承诺),场1 的本场钩子用于把 S5 已选开场方式实现为镜头与台词。消除 S5「钩子类型」一词双指(表头改「开场方式」、方案字段行改为单一开场方式成分);不再以对照式/括注维护,概念各归其位。 @@ -228,7 +228,7 @@ ## 2026-09-02(工作台 AI 任务契约与执行铁律) -- 工作台新增第 7 条「AI 任务入口技能契约(P0)」:所有 AI 会话任务入口须遵守功能入口×技能调用契约(prompt=技能前缀常量+业务正文+skills 参数级挂载),新增/修改入口前先读 `mcn-work-shop/docs/AI会话任务输入输出对照.md`(含新增入口检查清单 7 项)——防裸 prompt 跳步/技能声明混入业务串/BROWSER_HINT 误注入;原第 7/8 条顺延为 8/9。 +- 工作台新增第 7 条「AI 任务入口技能契约(P0)」:所有 AI 会话任务入口须遵守功能入口×技能调用契约(prompt=技能前缀常量+业务正文+skills 参数级挂载),新增/修改入口前先读 `mcn-workshop/docs/AI会话任务输入输出对照.md`(含新增入口检查清单 7 项)——防裸 prompt 跳步/技能声明混入业务串/BROWSER_HINT 误注入;原第 7/8 条顺延为 8/9。 - 工作台任务执行方式铁律固化:创作/改写/复盘必须走 `POST /api/run` + skills 参数,禁会话内手写草稿。 - 调度机制实测定稿 + 端口 8899→8900。 diff --git a/project/短视频脚本创作/V1.0/references/规则/路径引用规范.md b/project/短视频脚本创作/V1.0/references/规则/路径引用规范.md index 8456923..811d222 100644 --- a/project/短视频脚本创作/V1.0/references/规则/路径引用规范.md +++ b/project/短视频脚本创作/V1.0/references/规则/路径引用规范.md @@ -33,7 +33,7 @@ | 场景 | 说明 | |------|------| -| 仅开发机工具 | 不随技能分发的目录(如 `mcn-work-shop/`),内部可保留开发机路径 | +| 仅开发机工具 | 不随技能分发的目录(如 `mcn-workshop/`),内部可保留开发机路径 | | 环境判别/路径配置文档 | `路径配置.md`、SKILL.md 环境判定节——锚点 git 检出特征是**规则内容**不是引用,保留 | | 示例/教学文本 | `C:/Videos/demo.mp4`、`C:/path/to/file` 等明确示例(`path/to`、`demo` 字样) | | 历史日志 | `.workbuddy/memory/` 既有日志保留原样,不改写历史 | diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/references/规则/路径引用规范.md b/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/references/规则/路径引用规范.md index cbb0466..9f9ca2b 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/references/规则/路径引用规范.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/references/规则/路径引用规范.md @@ -33,7 +33,7 @@ | 场景 | 说明 | |------|------| -| 仅开发机工具 | 不随技能分发的目录(如 `mcn-work-shop/`),内部可保留开发机路径 | +| 仅开发机工具 | 不随技能分发的目录(如 `mcn-workshop/`),内部可保留开发机路径 | | 环境判别/路径配置文档 | `路径配置.md`、SKILL.md 环境判定节——锚点 git 检出特征是**规则内容**不是引用,保留 | | 示例/教学文本 | `C:/Videos/demo.mp4`、`C:/path/to/file` 等明确示例(`path/to`、`demo` 字样) | | 历史日志 | `.workbuddy/memory/` 既有日志保留原样,不改写历史 | diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/scripts/fetch_week_ranks.py b/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/scripts/fetch_week_ranks.py index 5cce75d..03198d1 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/scripts/fetch_week_ranks.py +++ b/project/短视频脚本创作/V1.0/subskills/mcn-data-insight/scripts/fetch_week_ranks.py @@ -39,7 +39,7 @@ from urllib.error import HTTPError, URLError LIKES_API = "https://redfox.hk/story/api/dy/search/likesRank" SURGE_API = "https://redfox.hk/story/api/dy/search/hotContentRank" -# 子目录(与 mcn-work-shop/dsh-data.js rankSubDir 对应) +# 子目录(与 mcn-workshop/dsh-data.js rankSubDir 对应) SUB_DIR = {"hot": "视频热榜", "likes": "点赞榜"} # 文件名前缀(与账号周榜 抖音周榜_{赛道}_{周一}.json 区分) FILE_PREFIX = {"hot": "抖音周榜_视频热榜", "likes": "抖音周榜_点赞榜"} @@ -50,7 +50,7 @@ CATEGORY_ALL = "全部" def resolve_ranking_dir(): - """与 mcn-work-shop/dsh-data.js resolveRankingDir 同款:env → 桌面 抖音榜单(09-10 起无 dsh 分支)""" + """与 mcn-workshop/dsh-data.js resolveRankingDir 同款:env → 桌面 抖音榜单(09-10 起无 dsh 分支)""" env = os.environ.get("MCN_RANKING_DIR", "").strip() if env: return env diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/SKILL.md b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/SKILL.md index 80b8b10..0a85b4b 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/SKILL.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/SKILL.md @@ -157,7 +157,7 @@ description: "MCN 达人爆款视频解析与人设卡生成技能:达人账 核心流程概要: - **触发**:账号详情视频列表「解析视频」按钮 / 用户指定单条视频 - **流程**:`upload_douyin_video` 提交(3-5 分钟等待)→ `short_video_detail` 查询(content + analysis)→ `scripts/json_tool.py` 保存到 `{产出根目录}/{达人昵称}/视频对标/{视频文件夹}/` -- **可选写库**:① 环境有 MCN 插件接口(dsh)时补录 `account_video_source` 并提取选题;② **工作台环境(mcn-work-shop,本机 `http://localhost:8900`)**:功能三/五解析产物落盘后调用 `POST /api/import/account`(body `{"account":"{达人昵称}"}`)同步入库——扫描账号产出目录写工作台库 `mcn-plugin.db`(`视频分析/*/analysis.json`→`account_video_analysis`、`content.json`→`account_video_source`、`{达人昵称}账号设定.md`→`account_persona`、`{达人昵称}账号数据分析.md`→`account_analysis`、`短视频表格.xlsx`→`account_videos` 补视频列表),供工作台选题(`/api/ai/topics`)基于真实视频解析内容生成。入库实现见工作台 `server.js`(09-01 新增) +- **可选写库**:① 环境有 MCN 插件接口(dsh)时补录 `account_video_source` 并提取选题;② **工作台环境(mcn-workshop,本机 `http://localhost:8900`)**:功能三/五解析产物落盘后调用 `POST /api/import/account`(body `{"account":"{达人昵称}"}`)同步入库——扫描账号产出目录写工作台库 `mcn-plugin.db`(`视频分析/*/analysis.json`→`account_video_analysis`、`content.json`→`account_video_source`、`{达人昵称}账号设定.md`→`account_persona`、`{达人昵称}账号数据分析.md`→`account_analysis`、`短视频表格.xlsx`→`account_videos` 补视频列表),供工作台选题(`/api/ai/topics`)基于真实视频解析内容生成。入库实现见工作台 `server.js`(09-01 新增) ## 八、功能六:生成账号设定卡片 diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/浏览器搜索抖音账号操作规范.md b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/浏览器搜索抖音账号操作规范.md index 6e72b7e..ca483ba 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/浏览器搜索抖音账号操作规范.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/浏览器搜索抖音账号操作规范.md @@ -262,7 +262,7 @@ PY **锁规则(每个需要浏览器的任务必须遵守)**: -1. **锁文件位置**:**工作台目录下的 `.browser-lock`**(工作台目录 = 本技能根下的 `mcn-work-shop/`)。工作台 `/api/run` 对浏览器类任务会把**运行时解析出的绝对路径**注入 prompt,AI 会话执行时**以注入的路径为准**;此处不写死盘符。 +1. **锁文件位置**:**工作台目录下的 `.browser-lock`**(工作台目录 = 本技能根下的 `mcn-workshop/`)。工作台 `/api/run` 对浏览器类任务会把**运行时解析出的绝对路径**注入 prompt,AI 会话执行时**以注入的路径为准**;此处不写死盘符。 - 工作台触发的会话 cwd 即**工作台目录**,故可直接用相对路径 `.browser-lock`(推荐,跨机器可迁移) - 手工执行(cwd 不是工作台目录)时,先定位工作台目录再拼 `.browser-lock` 2. **操作前**:检查锁文件是否存在(`ls`)→ 存在则等 10 秒重试(最多 18 次 ≈ 3 分钟) diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/规则/路径引用规范.md b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/规则/路径引用规范.md index cbb0466..9f9ca2b 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/规则/路径引用规范.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/规则/路径引用规范.md @@ -33,7 +33,7 @@ | 场景 | 说明 | |------|------| -| 仅开发机工具 | 不随技能分发的目录(如 `mcn-work-shop/`),内部可保留开发机路径 | +| 仅开发机工具 | 不随技能分发的目录(如 `mcn-workshop/`),内部可保留开发机路径 | | 环境判别/路径配置文档 | `路径配置.md`、SKILL.md 环境判定节——锚点 git 检出特征是**规则内容**不是引用,保留 | | 示例/教学文本 | `C:/Videos/demo.mp4`、`C:/path/to/file` 等明确示例(`path/to`、`demo` 字样) | | 历史日志 | `.workbuddy/memory/` 既有日志保留原样,不改写历史 | diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/铁律避坑规则/已有数据处理规则.md b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/铁律避坑规则/已有数据处理规则.md index 4c7fa2a..42867e6 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/铁律避坑规则/已有数据处理规则.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-dou-analysis/references/铁律避坑规则/已有数据处理规则.md @@ -43,9 +43,9 @@ | 更新时间近 1 月提示刷新 | references/feature/01_获取账号信息.md | | 卡片存在询问覆盖/微调(触发判定链) | references/feature/06_生成账号设定卡片.md | | 已有表格直接读、人设卡对齐、解析降级 | references/feature/04_分析视频数据.md | -| 入库 upsert 去重(删旧保新/重复跳过) | mcn-work-shop/server.js `/api/import/account` | +| 入库 upsert 去重(删旧保新/重复跳过) | mcn-workshop/server.js `/api/import/account` | ## 相关接口速查 -- 工作台库:`mcn-work-shop/mcn-plugin.db`(表:hot_accounts / account_persona / account_analysis / account_video_source / account_video_analysis / account_videos) +- 工作台库:`mcn-workshop/mcn-plugin.db`(表:hot_accounts / account_persona / account_analysis / account_video_source / account_video_analysis / account_videos) - 产出根目录:以 `references/路径配置.md` 为准(开发机默认桌面 `MCNSkill项目/`) diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-script-review/SKILL.md b/project/短视频脚本创作/V1.0/subskills/mcn-script-review/SKILL.md index b3480fa..bde42b4 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-script-review/SKILL.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-script-review/SKILL.md @@ -172,4 +172,4 @@ script-review/ ← Skill 根目录 |------|------|------| | short-video-script | 脚本**创作** + 创作流程内 S11 诊断 | 本技能只读引用其判定标准 | | **script-review(本技能)** | 已有脚本**复盘** + 创作过程**复盘** + 经验沉淀 | 独立运行,不侵入创作流程 | -| MCN工作台(mcn-work-shop) | AI脚本诊断页(#/reviews)复盘入口 | 复盘/对比按钮的 AI 任务 prompt 明确指定按本技能执行 | +| MCN工作台(mcn-workshop) | AI脚本诊断页(#/reviews)复盘入口 | 复盘/对比按钮的 AI 任务 prompt 明确指定按本技能执行 | diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-script-review/references/规则/路径引用规范.md b/project/短视频脚本创作/V1.0/subskills/mcn-script-review/references/规则/路径引用规范.md index cbb0466..9f9ca2b 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-script-review/references/规则/路径引用规范.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-script-review/references/规则/路径引用规范.md @@ -33,7 +33,7 @@ | 场景 | 说明 | |------|------| -| 仅开发机工具 | 不随技能分发的目录(如 `mcn-work-shop/`),内部可保留开发机路径 | +| 仅开发机工具 | 不随技能分发的目录(如 `mcn-workshop/`),内部可保留开发机路径 | | 环境判别/路径配置文档 | `路径配置.md`、SKILL.md 环境判定节——锚点 git 检出特征是**规则内容**不是引用,保留 | | 示例/教学文本 | `C:/Videos/demo.mp4`、`C:/path/to/file` 等明确示例(`path/to`、`demo` 字样) | | 历史日志 | `.workbuddy/memory/` 既有日志保留原样,不改写历史 | diff --git a/project/短视频脚本创作/V1.0/subskills/mcn-video-prompt/references/规则/路径引用规范.md b/project/短视频脚本创作/V1.0/subskills/mcn-video-prompt/references/规则/路径引用规范.md index 19a5eb5..a7f99b1 100644 --- a/project/短视频脚本创作/V1.0/subskills/mcn-video-prompt/references/规则/路径引用规范.md +++ b/project/短视频脚本创作/V1.0/subskills/mcn-video-prompt/references/规则/路径引用规范.md @@ -33,7 +33,7 @@ | 场景 | 说明 | |------|------| -| 仅开发机工具 | 不随技能分发的目录(如 `mcn-work-shop/`),内部可保留开发机路径 | +| 仅开发机工具 | 不随技能分发的目录(如 `mcn-workshop/`),内部可保留开发机路径 | | 环境判别/路径配置文档 | `路径配置.md`、SKILL.md 环境判定节——锚点 git 检出特征是**规则内容**不是引用,保留 | | 示例/教学文本 | `C:/Videos/demo.mp4`、`C:/path/to/file` 等明确示例(`path/to`、`demo` 字样) | | 历史日志 | `.workbuddy/memory/` 既有日志保留原样,不改写历史 | diff --git a/失误与规避记录.md b/失误与规避记录.md index 7c3fd61..1a77790 100644 --- a/失误与规避记录.md +++ b/失误与规避记录.md @@ -152,7 +152,7 @@ S1/S2 产物文件名改名(01_需求拆分/拆解 → 01_需求理解/02_需 仓库本地路径由 `D:\AgentSkill\mcn-video-script` 迁移至 `D:\AI技能\mcn-short-video` 后,技能树内以「本机是否存在 `D:\AgentSkill`」为锚点的**三环境两步判定全部失效**——开发机被误判为「③ 用户环境」,后果是产出落桌面而非既定位置、且误启用 `references-add/` 增量分支。该锚点当时被规则文档列为「允许项(环境判别锚点)」,属于**设计上就写死**的路径,因此历次相对路径审计都没能查出——它不是"漏网违规",而是"合规但脆弱"。 -同批还查出 8 处**真正的违规**:`mcn-work-shop/server.js` 的 `cwd` / `BROWSER_LOCK`(真实写入 automations 表与浏览器锁文件)、两个 `.cjs` 任务提交脚本 prompt 内的 DB 绝对路径、`V1.0/SKILL.md` 文档里的运行时 `cwds`、`Lite1.0/SKILL.md` 的外部技能依赖路径(且该段自 09-02 dou-analysis 并入 `subskills/` 后从未同步)。 +同批还查出 8 处**真正的违规**:`mcn-workshop/server.js` 的 `cwd` / `BROWSER_LOCK`(真实写入 automations 表与浏览器锁文件)、两个 `.cjs` 任务提交脚本 prompt 内的 DB 绝对路径、`V1.0/SKILL.md` 文档里的运行时 `cwds`、`Lite1.0/SKILL.md` 的外部技能依赖路径(且该段自 09-02 dou-analysis 并入 `subskills/` 后从未同步)。 **规避与铁律:** 1. **环境判定的锚点必须是"仓库特征",不能是盘符地标**:统一改用 ① 技能路径是否含 `.dsh` 路径段 → ② `git -C {技能目录} rev-parse --show-toplevel` 是否返回仓库根(无 git 时兜底向上找 `.git`)。仓库搬移到任何盘符/目录名都能自动正确判定。 diff --git a/技能审计报告_V1.0_20260914.md b/技能审计报告_V1.0_20260914.md index 6c1a790..f50d3af 100644 --- a/技能审计报告_V1.0_20260914.md +++ b/技能审计报告_V1.0_20260914.md @@ -21,7 +21,7 @@ | # | 检查点 | 等级 | 位置 | 问题 | |:--:|:---:|:---:|------|------| | 1 | 6 引用链路 | **P1** | `7_生成短视频大纲.md:31` ← `6_生成短视频框架.md:280` | S7 声明"从 S6 场次结构表取模板骨架(模板A/B)",S6 明文模板A/B **非执行指令**、场次表不含模板 → 上游无此字段 | -| 2 | 7 引用路径 | **P1** | `mcn-work-shop/docs/AI会话任务输入输出对照.md` + `SKILL.md:81` | 契约表只登记 5 个前缀常量,实际 **8 个入口缺失 2 个**(`SKILL_HINT_ANALYZE`、`SKILL_HINT_VIDEO_PARSE`),与 SKILL.md 第 7 条 P0 冲突 | +| 2 | 7 引用路径 | **P1** | `mcn-workshop/docs/AI会话任务输入输出对照.md` + `SKILL.md:81` | 契约表只登记 5 个前缀常量,实际 **8 个入口缺失 2 个**(`SKILL_HINT_ANALYZE`、`SKILL_HINT_VIDEO_PARSE`),与 SKILL.md 第 7 条 P0 冲突 | | 3 | 8/10 一致性 | **P1** | `创作流程规范.md:97` ←→ `SKILL.md:296-298` | 「八类35项自检」= 交付前强制(规范);承载它的 S11 = 可选(SKILL.md)→ 门禁口径相反 | | 4 | 8 术语统一 | **P1** | `SKILL.md:223`、`5_生成短视频选题.md:187/229/255/294/411` | 钩子家族 4 种叫法(开场钩子/开篇钩子/钩子预设/钩子策略),违反 09-07 概念分层规范 §三 | | 5 | 8/9 一致性 | **P1** | `创作流程规范.md:406,408` | 旧技能名「`脚本创作技能/`」残留 + 单独一个乱码字符(`读��最新`) | @@ -145,4 +145,4 @@ - `references/知识库/` 66 个方法文件的**内容级**审计(本次只做跨文件术语/引用交叉验证) - `references/素材库/` 10 个库文件的**字段级**审计(15 字段一致性未逐库核对) - 第三方原样分发子技能内部(browser-harness、lieflat-charts、mcn-video-prompt/参考skills、nuwa-skill-main)——按"上游原包不改"约定跳过 -- `mcn-work-shop` 前端/后端代码级审计(本次只核了 AI 任务入口契约这一条链路) +- `mcn-workshop` 前端/后端代码级审计(本次只核了 AI 任务入口契约这一条链路)