首页
/ impeccable 单次 Live 模式配置指南:从 config.json 骨架到 CSP 探测补丁

impeccable 单次 Live 模式配置指南:从 config.json 骨架到 CSP 探测补丁

2026-09-07 11:21:55作者:冯爽妲Honey

impeccable 的 live-setup 流程处理的是「一次性」的 Live 变体模式项目初始化:把实时浏览器挑选器(live picker,监听 http://localhost:8400 的辅助服务器)接入开发服务器所服务的 HTML 文档骨架,并解决开发态 CSP 的放行问题。本文以 skill/reference/live-setup.md 为核心文档,结合仓库内的注入逻辑描述、框架 fixture 与参考补丁输出,讲解 config.json 的完整字段、各框架的 files/锚点对照表、配置漂移处理,以及四种 CSP 形状(shape)的探测与自动补丁方案。读完后,你既能独立为一个新项目完成 live mode 首启配置,也能理解为什么这些配置与补丁被设计为「只能由用户决定、绝不自动改配置」。

本文涉及路径若在打包产物中引用,helper 脚本路径存在 {{scripts_path}} 占位(例如 .kiro/skills/impeccable/scripts/detect-csp.mjs),在不同 Agent harness 的分发副本中会被替换为真实绝对路径;仓库内源文件位置统一以 skill/tests/framework-fixtures/ 为准。

live-setup 何时被加载:它不在每次会话的热路径上

live-setup 文档明确界定了自己的触发边界:它不是每次会话都会执行的流程,而只会在以下四种情况之一出现时,由 live.md 按需加载:

  • live.mjs 启动时报出 config_missingconfig_invalid{ ok: false, error: ..., path });
  • configDrift 需要向用户说明(存在未被 files 覆盖的 HTML);
  • 配置中缺少 cspChecked 字段(即 CSP 探测/征询这一步从未跑过)。

也就是说,这是「一次性配置」,一旦写入 .impeccable/live/config.json 并标记 cspChecked: true,之后的会话启动会直接跳过整个 setup 段落,直接进入 per-session 的轮询热路径。live-setup 本身「不参与每次会话的热路径」——注入脚本常驻、轮询恢复等工作由 live.md 中描述的其他命令负责。

运行上下文上还有一点值得注意:仓库的 ADR(docs/adr-live-variant-mode.md)注明 live 的辅助服务器是「live-server.mjs (node, localhost:8400+, zero dependencies)」。这正是后续 CSP 补丁要向 script-src/connect-src 追加 http://localhost:8400 的根本原因——live picker 与变体注入脚本都要从这一本地源加载,任何开发环境的内容安全策略都必须显式放行它。

第一步:写入 config.json

在 boot 报告返回的 path(默认 .impeccable/live/config.json)下创建配置文件:

{
  "files": ["<path-or-glob>", "<path-or-glob>", ...],
  "exclude": ["<optional-glob>", ...],
  "insertBefore": "</body>",
  "commentSyntax": "html",
  "cspChecked": true
}

各字段含义如下(以下解释均直接对应 setup 流程的约定):

字段 作用 说明
files 注入目标 必须是浏览器实际加载的 HTML 文件,不一定是源码文件(tracked 还是 generated 在此无关紧要,wrap 流程有自己的 generated 文件守卫);条目可以是字面路径或 glob
exclude 可选排除 跳过 files glob 本会匹配到的文件(如邮件模板、demo fixtures)
insertBefore 注入锚点 在该行/字符串之前插入注入脚本;</body> 几乎对所有文件都成立
commentSyntax 注释/标记语法 决定注入标记的注释风格:htmljsx,必须与目标文件的语言一致
cspChecked 首启标记 记录下文的 CSP 探测/征询步骤已执行过;首次 setup 时该字段不存在

glob 语法与硬性排除路径

  • **:匹配任意数量的路径段(含零段);*:匹配单个路径段内部;?:匹配单个字符。
  • 所有路径均相对项目根目录,并使用正斜杠 /
  • 硬排除路径(无法被任何配置覆盖)**/node_modules/****/.git/**。向这两处注入相当于给第三方代码插桩,因此直接拒绝。

各框架 files / 锚点 / 注释语法对照表

Framework files insertBefore commentSyntax
SPA with single shell (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
Multi-page (separate HTML per route) ["public/**/*.html"] glob over the served dir </body> html

选择锚点的经验法则:

  • 挑一个在每个目标文件里都存在的锚点(</body> 几乎总是可用)。insertAfter 则是在某一行之后匹配插入,两者互斥选一。
  • 多页面站点优先使用 glob(如 public/**/*.html),这样新增页面会被自动纳入注入范围,无需每次改配置。
  • 由 generator 重建页面的站点要注意:注入只在下次重新生成之前存活,因此每次 build 之后需要重新运行 live.mjs。这一限制不影响 accept 流程——accept 通过 fallback 流程把选定变体写入真正的源码文件,不受重建影响。

框架适配器:注入时的自动探测

同样是「把 <script> 注入文档骨架」,不同框架的可行路径并不一样。注入器会在注入时自动探测框架类型,而非要求用户手填:

  • SvelteKit、Nuxt、TanStack Start 会对文档 shell 做服务端渲染,入口模板里放一个原生 <script> 并不会可靠执行。此时 live-inject.mjs 检测到框架后会转交给专用适配器:
    • SvelteKit:从 +layout.svelte 挂一个仅 dev 生效的根组件;
    • Nuxt:挂一个仅 dev 生效的 .client.ts plugin;
    • TanStack Start:在 __root 中生成一个仅 dev 生效的 ImpeccableLiveRoot 组件。
  • 而普通的 TanStack Router SPA 走 Vite 基线路径,无需适配器。

注意适配器模式下,config 里的 files 值仍然保留——它是有用的检测/CSP 提示,但不再是字面上的注入落点。

这份配置的实际校验与回归在仓库里是有完整 fixture 支撑的。查看 tests/framework-fixtures/README.md 可以看到,测试会把每个 fixture 目录复制进临时 git 仓库,然后驱动 live-inject.mjslive-wrap.mjslive-accept.mjslib/is-generated.mjs 进行验证;fixture.jsonconfig 字段内容正是要写入 .impeccable/live/config.json 的样例,csp 字段则声明了该 fixture 期望的 CSP 形状(shape)、patchTargetexpectedAfter(参考补丁输出文件)。仓库根目录的 tests/framework-fixtures/ 覆盖了 nextjs-app-router、nuxt、sveltekit、tanstack-router-vite、astro、vite-react 等 20+ 种项目形态,其中 CSP 相关的有 nextjs-inline-csp/nextjs-turborepo/nuxt-csp/sveltekit-csp/

注入日志与自愈

每次 inject 都会把「我写了什么」记录到 .impeccable/live/inject-journal.json。下次 inject(或 remove)时会读取该日志,把上次崩溃或目录停错残留的注入工件「治愈」(heal)掉。这与 live 会话的持久化结构是配套的——live 的运行期状态目录 .impeccable/live/ 还会承载 roots.json(应用根目录清单)与 sessions/ 下的 append-only 会话日志,只是后者属于会话热路径而非 setup 范畴。

Config drift:发现未被覆盖的页面

每次 boot 时,注入流程会扫描项目常见页面根目录(public/src/app/pages/)下的 HTML 文件,检查解析后的 files 列表是否覆盖到它们:

  • 未覆盖到的文件会作为 configDrift.orphans 暴露出来,并附带提示;
  • 处理原则是:每个会话只向用户说明一次「哪些文件没有被覆盖」,并提议把它加进 files,或把 files 换成 glob;
  • 绝不自动更新配置——是否覆盖由用户决定;
  • 没有漂移时,configDriftnull

之所以约束得这么严格,是因为 .impeccable/live/config.json 是跨会话的项目级配置:一旦注入脚本被 files 显式覆盖的文件清单“记死”,新页面可能永远得不到注入能力;反过来如果盲目扩 glob,又可能把邮件模板、demo fixture 等不该插桩的文件纳入。把决定权留给用户,是避免配置静默扩张引发副作用的关键设计。

CSP 探测(仅首次执行)

只有当 config.cspChecked !== true 时才会执行本节;一旦问过用户一次,配置里就会写入 cspChecked: true,后续跳过。

运行探测脚本(路径以所在 harness 副本为准,源文档示例使用 .kiro 副本):

node .kiro/skills/impeccable/scripts/detect-csp.mjs

输出形如 { shape, signals }shape 命名的是「补丁机制」而非具体框架,因此一份模板可以覆盖多个框架:

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-srcconnect-src,再标记 cspChecked: true 继续

征询话术(务必使用这份措辞)

探测完成后,若需要补丁,要向用户出示如下确认,diff 必须是精确的 2–5 行:

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

补丁机制一:append-arrays(结构化数组)

在持有 CSP 数组的文件顶部声明一个 dev-only 常量,然后把 ...__impeccableLiveDev 追加进 script-srcconnect-src 数组:

// Dev-only allowance so impeccable live mode can load. Guarded by NODE_ENV.
const __impeccableLiveDev =
  process.env.NODE_ENV === "development" ? ["http://localhost:8400"] : [];

分框架的落点:

  • Next.js + monorepo helper:改应用自己的 next.config.*(不要改共享 helper),追加到 additionalScriptSrc / additionalConnectSrc。仓库中的参考输出见 tests/framework-fixtures/nextjs-turborepo/expected-after-patch.ts——它展示了 createBaseNextConfig({ ..., additionalScriptSrc: [posthogHost, ...__impeccableLiveDev], additionalConnectSrc: [posthogHost, ...__impeccableLiveDev] }) 的完整形态,文件头注释亦称之为 “Shape 1 (shared-helper)”。
  • SvelteKit:改 svelte.config.js,追加到 kit.csp.directives['script-src']['connect-src'] 数组。参考输出见 tests/framework-fixtures/sveltekit-csp/expected-after-patch.js,其中 directives'script-src': ['self', 'unsafe-inline', ...__impeccableLiveDev]'connect-src': ['self', ...__impeccableLiveDev]
  • Nuxt + nuxt-security:改 nuxt.config.*,追加到 security.headers.contentSecurityPolicy['script-src']['connect-src']

幂等性约定:补丁函数是幂等的——若目标文件中已存在 __impeccableLiveDev,说明此前已打过补丁,只需标记 cspChecked: true 即可,不必重复改动。

补丁机制二: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}`

分框架的落点:

两处 fixture 都印证了核心安全前提:放行条目由 process.env.NODE_ENV === "development" 守卫——额外条目只出现在 dev,永远不会进入生产响应,用户随时可通过 revert 文件移除。

Troubleshooting

场景:用户曾对 CSP 补丁回答 no,之后反馈 live 不工作。

原因基本可以断定:开发环境的 CSP 仍在拦截 http://localhost:8400。解决方式是让 setup 重新征询一次:

  1. .impeccable/live/config.json删除 cspChecked 字段(保留其余配置);
  2. 重新运行 live.mjs——boot 会因缺少 cspChecked 再次进入 live-setup 的 CSP 探测与征询流程。

这正是 cspChecked 被刻意设计为「问题已问过」的持久标记而非不可逆开关的原因:删掉它,就能安全地重放一次征询,而不会影响已经写好的 files/exclude/insertBefore 等其余配置。

setup 全部完成后,重新运行 live.mjs 继续正常流程。

小结:一次配置、三重守卫

回看整个 live-setup,它的健壮性来自三个互相独立的设计决策:

  1. 配置决策权归用户:是否覆盖漂移页面、是否应用 CSP 补丁,都由用户拍板;configDrift 只提醒、cspChecked 只记录“问过了”,配置字段绝不静默改写。
  2. 按注入机制而非框架分类:CSP 的 shape 命名的是补丁方式(append-arrays / append-string / middleware / meta-tag),使同一份 diff 模板能跨 Next.js、Nuxt、SvelteKit 复用,并用 NODE_ENV === "development" 守卫保证生产零泄漏。
  3. 状态可恢复config.json 是唯一持久配置,inject-journal.json 自愈残留注入,删除 cspChecked 即可重放 CSP 征询——任何一步出错都能从源码级位置(如 tests/framework-fixtures/ 下的参考补丁输出)对照修正,再回到 live.mjs 主流程。

把这份一次性配置做完,项目就获得了后续 live 会话的稳定前提:注入脚本可靠驻留在文档骨架中、页面加入/删除不再产生盲区、且开发服务器在内容安全策略下能正常加载 http://localhost:8400 上的 live picker。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388