Impeccable Live 模式接入配置:写配置、适配框架注入、自动放行 CSP 的一站式设置指南
导读:Impeccable 的 live-setup.md 描述了 live 变体模式(在浏览器里选中元素、生成 HTML+CSS 变体并通过 HMR 热替换)的一次性项目配置流程——即
live.mjs首次启动回报config_missing/config_invalid/configDrift、或配置缺少cspChecked字段时的处理手册。本文从这份文档出发,结合仓库内的测试 fixture 与脚本实现,讲透.impeccable/live/config.json的完整 schema、各框架注入点对照表、注入日志自愈机制、配置漂移处理,以及"检测 CSP → 询问用户 → 按 shape 打补丁"的完整链路。读完你可以独立为任何 Vite / Next.js / Nuxt / SvelteKit / Astro / TanStack 项目完成 live 模式的首次接入。
这份文档在 live 模式里的角色
Impeccable 的 live 模式采用 "boot → 轮询事件 → 处理事件" 的运行模型,其运行契约与事件分派定义在 live.md。而 live-setup 属于一次性启动设置,与每次会话的热路径无关,只在以下三种情况下被 live.md 加载:
live.mjs启动时返回config_missing(配置不存在)或config_invalid(配置不合法);- 返回的
configDrift非空,需要向用户解释未覆盖的 HTML 文件; - 配置中缺少
cspChecked字段(首次设置必须执行 CSP 检测)。
也就是说:live-setup 拥有配置 schema、跨框架 files 表、注入适配器、漂移自愈以及 CSP 检测与征询流程的全部权责。日常轮询热路径中的 generate / steer / accept / discard 处理一律不回读本文件。
仓库中存在该 skill 的多份分发副本,例如 .opencode/skills/impeccable/reference/live-setup.md、.claude/skills/impeccable/reference/live-setup.md、skill/reference/live-setup.md 与 plugin/skills/impeccable/reference/live-setup.md 等,内容一致、面向不同 Agent 宿主分发。
一、写入 .impeccable/live/config.json
1.1 配置文件位置与完整 schema
创建 live.mjs 启动时回报的 path(默认 .impeccable/live/config.json),内容如下:
{
"files": ["<path-or-glob>", "<path-or-glob>", ...],
"exclude": ["<optional-glob>", ...],
"insertBefore": "</body>",
"commentSyntax": "html",
"cspChecked": true
}
各字段语义:
| 字段 | 必填 | 含义 |
|---|---|---|
files |
是 | 注入目标:浏览器实际加载的 HTML 文件路径或 glob。注意它不一定是源码——是"页面被加载的入口";tracked 与 generated 在此并不重要(wrap 阶段另有针对生成文件的守卫) |
exclude |
否 | 排除 glob:把 files 中 glob 命中的文件再剔除掉(如邮件模板、demo fixture) |
insertBefore |
是 | 注入锚点,默认 </body> |
commentSyntax |
是 | 注入文件使用的注释语法,html 或 jsx(html 对应注释 <!-- -->,jsx 对应 {/* */}) |
cspChecked |
首次缺失 | 记录下方的 CSP 检测步骤是否已执行;首次设置时缺省 |
硬性排除路径(不可覆盖): **/node_modules/** 与 **/.git/**。向这两个目录注入等价于插桩第三方代码,任何配置都不允许写入。
Glob 语法: ** 匹配任意数量段(含零段),* 匹配段内任意字符,? 匹配单字符。路径一律相对项目根、使用正斜杠。
从源码结构看,config 会被扩展分发到前端 overlay:live 服务器把 .impeccable/config.json + config.local.json 的检测器忽略规则、连同注入配置 files glob 的 served-root 前缀一起序列化进 window.__IMPECCABLE_PROJECT_IGNORES__(见 live-browser-ignores.js),保证 overlay 端与 CLI、edit hook 端对检测结果的抑制口径一致。
1.2 跨框架注入点对照表
live-setup 内置一张按框架区分的注入点表,选锚点的原则是该锚点必须存在于每个目标文件中(</body> 几乎总是成立):
| 框架 | files |
insertBefore |
commentSyntax |
|---|---|---|---|
| SPA 单壳(Vite / React / Plain HTML) | ["index.html"] |
</body> |
html |
| Next.js(App Router) | ["app/layout.tsx"] |
</body> |
jsx |
| Next.js(Pages) | ["pages/_document.tsx"] |
</body> |
jsx |
| Nuxt | ["app.vue"] |
</body> |
html |
| Svelte / SvelteKit | ["src/app.html"] |
</body> |
html |
| TanStack Router(SPA, Vite) | ["index.html"] |
</body> |
html |
| TanStack Start(SSR) | ["src/routes/__root.tsx"] |
<Scripts |
jsx |
| Astro | ["<root layout .astro>"] |
</body> |
html |
| 多页站点(每路由独立 HTML) | ["public/**/*.html"] 服务目录 glob |
</body> |
html |
实践要点:
- 多页站点优先用 glob,这样新页面自动被覆盖,无需每次新增页面都改配置;
- 生成器重建页面的站点,注入只存活到下次再生成:每次 build 后需重跑
live.mjs。不过 accept 不受影响——它走 fallback 流程把结果写入真正的源码; insertAfter与insertBefore对应两种定位方式:前者匹配在某行之后插入。
测试仓库里正是用一套 fixture 来固化这套配置解析行为。例如 tests/framework-fixtures/README.md 特别强调:nextjs-app/ 以 app/layout.tsx 作为 JSX 注入目标(commentSyntax: "jsx");而测试会把 live 配置写在与 fixture 同级的 tmp 根,因此 config.files 必须既能从仓库根解析又能从 app 根解析——此时写 glob "**/index.html" 两边都满足,写字面量 "index.html" 则只从 app 根有效,静态扫描会回报 file_not_found。这是配置与测试基建耦合的一个鲜活示例。
1.3 框架适配器:注入时自动探测
每次注入都会把写入结果记录在 .impeccable/live/inject-journal.json;下一次注入或移除时,会先自愈崩溃或错误目录停止遗留下的半成品。
SvelteKit、Nuxt、TanStack Start 会服务端渲染 document shell,在入口模板里塞裸 <script> 无法可靠执行,所以 live-inject.mjs 会探测到它们并路由到专用适配器:
- SvelteKit:从
+layout.svelte派生一个 dev-only 根组件; - Nuxt:写一个 dev-only
.client.ts插件; - TanStack Start:在
__root中生成 dev-only 的ImpeccableLiveRoot组件。
此时 files 的值仍作为合法的检测/CSP 提示被保留,但不再是字面的插入点。普通 TanStack Router SPA 则走基线 Vite 路径。
1.4 配置漂移:configDrift
每次 boot 时,live 会在常见页面根(public/、src/、app/、pages/)下扫描 HTML 文件,凡不属于已解析 files 列表覆盖范围的,都会作为 configDrift.orphans 呈现,并附带提示。处理规则:
- 每个会话只向用户汇报一次:哪些文件尚未被覆盖;
- 建议用户追加这些文件或将
files换成 glob; - 绝不自动更新配置——决定权在用户;
- 无漂移时
configDrift为null。
二、CSP 检测(仅首次设置)
如果 config.cspChecked === true,跳过整节——用户已经被问过一次。否则执行检测脚本:
node .opencode/skills/impeccable/scripts/detect-csp.mjs
输出 { shape, signals };shape 命名的是补丁机制(patch mechanism),因此一套模板可以覆盖多个框架:
| shape | 含义 | 可否自动打补丁 | 典型场景 |
|---|---|---|---|
null |
无 CSP | —— | 直接写配置 cspChecked: true 结束 |
append-arrays |
CSP 是结构化指令数组 | ✅ | monorepo helper 的 additionalScriptSrc / additionalConnectSrc、SvelteKit kit.csp.directives、Nuxt nuxt-security |
append-string |
CSP 是字面字符串值 | ✅ | 内联 next.config.* 的 headers()、Nuxt routeRules |
middleware / meta-tag |
已检测但无法自动补丁 | ❌ | 展示检测到的文件,请用户手动把 http://localhost:8400 加进 script-src 与 connect-src,再标记 cspChecked: true |
2.1 征询话术(Consent Prompt)
向用户展示将要执行的改动,并请求确认,标准话术如下:
CSP patch needed. I detected a Content Security Policy in your project that blocks
http://localhost:8400: the live picker won't load without an allowance. Here's the change I'd make:[file: <patchTarget>] [exact diff, 2-5 lines]It's guarded by
NODE_ENV === "development"so the extra entry only appears in dev and never reaches production. You can remove it any time by reverting this file. Apply? [y/n]
分支处理:
- 用户答 no:跳过补丁,提示"直到手动添加放行否则 live 无法工作",但仍写入
cspChecked: true(问题已经问过,不再重复打扰); - 用户答 yes:按 shape 应用补丁,再写
cspChecked: true。
2.2 append-arrays:向指令数组追加
在持有 CSP 数组的文件顶部声明 dev-only 常量:
// Dev-only allowance so impeccable live mode can load. Guarded by NODE_ENV.
const __impeccableLiveDev =
process.env.NODE_ENV === "development" ? ["http://localhost:8400"] : [];
然后把它展开进 script-src 与 connect-src 两个数组。分框架落点:
- Next.js + monorepo helper:编辑 app 自身的
next.config.*(不是共享 helper),追加additionalScriptSrc/additionalConnectSrc; - SvelteKit:
svelte.config.js,kit.csp.directives['script-src']与['connect-src']; - Nuxt + nuxt-security:
nuxt.config.*,security.headers.contentSecurityPolicy['script-src']与['connect-src']。
仓库用 fixture 固化了两份人类/Agent 评审用的补丁参考输出:
- tests/framework-fixtures/nextjs-turborepo/expected-after-patch.ts:在 turborepo 的共享
createBaseNextConfig之上,app 的next.config.ts声明__impeccableLiveDev,再通过additionalScriptSrc: [posthogHost, ...__impeccableLiveDev]与additionalConnectSrc: [...__impeccableLiveDev]展开进 CSP(该文件注释明确写了"Reference output for agent/human review — not executed by tests"); - tests/framework-fixtures/sveltekit-csp/expected-after-patch.js:SvelteKit
svelte.config.js的对应形态。
幂等性:如果文件里已存在 __impeccableLiveDev,说明补丁已应用过——只需标记 cspChecked: true,不要重复声明。
2.3 append-string:向字面字符串插值
两步补丁:声明 dev-only 字符串,再把它插值进 CSP 值的两个指令处(前导空格保证拼接干净;编辑时把字面量转成模板字符串):
// Dev-only allowance so impeccable live mode can load.
const __impeccableLiveDev =
process.env.NODE_ENV === "development" ? " http://localhost:8400" : "";
转换示意:
script-src 'self' 'unsafe-inline'变为`script-src 'self' 'unsafe-inline'${__impeccableLiveDev}`connect-src 'self'变为`connect-src 'self'${__impeccableLiveDev}`
分框架落点:Next.js 内联 headers()(next.config.*);Nuxt 的 routeRules['/**'].headers['Content-Security-Policy'](nuxt.config.*)。参考输出见 tests/framework-fixtures/nextjs-inline-csp/expected-after-patch.js 与 tests/framework-fixtures/nuxt-csp/expected-after-patch.ts。
三、故障排查与收尾
3.1 用户拒绝 CSP 补丁导致 live 不可用
如果用户当时答了 "no",事后回报 live 无法工作:原因是 dev CSP 挡住了 http://localhost:8400。处理办法是把 cspChecked 从 .impeccable/live/config.json 中删掉,再重跑 live.mjs——设置流程会再次询问。
3.2 设置完成后的标准动作
设置完成后重跑 live.mjs。此时 boot 应返回 { ok: true, ... } 进入正常 live 会话;若仍带 configDrift,按 1.4 节 只汇报一次并交由用户决策。
四、仓库中的佐证与延伸阅读
- 运行契约:live.md 定义了 live 模式的执行顺序、poll 循环与各事件(
generate/steer/accept/discard/manual_edit_apply等)处理,并在 "First-time setup" 一节把配置权责指向 live-setup; - 框架 fixture 测试矩阵:tests/framework-fixtures.test.mjs 与 tests/framework-fixtures/README.md 系统验证了各框架的注入与恢复行为;tests/live-e2e/agent.mjs、tests/live-e2e/session.mjs 提供端到端会话仿真;
- 参考一致性测试:tests/live-reference.test.mjs 校验 skill 主文档与 reference 之间的路由契约,确保 setup 引导保持"聚焦于把 live 路由到其 reference";
- 浏览器端忽略规则解析:live-browser-ignores.js 及其单测 tests/live-browser-ignores.test.mjs 展示了 live 服务器如何把
filesglob 与忽略规则注入 overlay; - 分发副本:同一 skill 面向多个 Agent 宿主分发,例如 plugin/skills/impeccable/reference/live-setup.md、skill/reference/live-setup.md。
五、速查清单
接入一个新项目的 live 模式,一次跑通的最小路径:
- 首次运行
live.mjs,接受返回的config_missing/config_invalid; - 按 1.1 节 的 schema 在 boot 报告的
path写入.impeccable/live/config.json,从 1.2 节 查表的files/insertBefore/commentSyntax取值,cspChecked先留空; - 跑
detect-csp.mjs,按输出shape决定:null直接收尾,append-arrays/append-string走征询-自动补丁(NODE_ENV === "development"守卫,可随时 revert),middleware/meta-tag手动放行http://localhost:8400; - 标记
cspChecked: true并重跑live.mjs,按需解释configDrift; - 若之后 CSP 相关的 live 失效,删除
cspChecked重新触发询问即可。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00