Repository navigation
Conversation
生产每次加载有 1 条 CSP console error:Cloudflare 自动注入的 Web Analytics beacon bootstrap 被 script-src 拦。已实测证伪「加 hash」这条路——内联块里的 r/t 每请求都变 (两次拉取值不同),Chrome 建议的那个 hash 只对当次响应有效。 不是 #693/1320 引入的回归:1320 之前 script-src 就没有 unsafe-inline 也没有 hash, 这段一直被拦;1320 的 spec 只枚举了 consent 块与 gtag.js 两类。现在把第三类写进 spec,避免下个改 CSP 的 session 再当成新发现。
osv-scanner 从 10-04 起阻断所有本地推送:braces 3.0.3 命中 CVE-2026-93687(8.7 High), 而它上游没有修复版(npm 最新就是 3.0.3;OSV ranges 只有 last_affected、没有 fixed 事件), 所以不能像三天前 axios 那次用 overrides 抬版本解决。它是 tailwindcss@3 → chokidar/micromatch 的 dev 传依赖,不在生产产物里。 只豁免这一条、带理由与 ignoreUntil=2027-01-31(实测:未来日期放行、过期后重新报错, 是真执行的时间闸)。到期若仍无修复版,走根治:换掉 tailwind v3 的 watch/glob 依赖链。 AAV-1328。
自动发现的文件名不能带点(.osv-scanner.toml 实测不生效);未知键是整份配置拒绝 加载而非忽略该条,写错等于豁免全废;ignoreUntil 需 RFC3339 且真的会过期(两个方向 都实测过);osv 只在 pre-push、CI 的 security-audit 是 --omit=dev + continue-on-error, 所以存在「CI 全绿但所有人都推不动」的形态。
相对上一版的四处调整,依据都取一手源码: 1. 取消 weekly schedule,回到引擎默认 at any time,改由 prHourlyLimit=2 + prConcurrentLimit=5 节流。按小时滚动才是 Renovate 的模型;把一批更新攒到周一 是 Dependabot 的成批观感,代价是双轨期看不到任何动作、出问题也发现得晚。 prConcurrentLimit 5 仍对齐 dependabot.yml 的 open-pull-requests-limit。 2. 纳入 devcontainer manager:.devcontainer/devcontainer.json 的 image(mcr.microsoft.com/devcontainers/typescript-node:22)与 features(ghcr.io/devcontainers/features/github-cli:1)此前无人看守—— dependabot.yml 只配了 npm 与 github-actions。该 manager 同时提取 image 与 features 两处版本(源码 manager readme),正好覆盖我们 Codespaces 的两个 版本点。typescript-node 与 CI 的 node-version 是跨消费者契约,故一律人工评审, 不进 automerge 标签。 3. minimumReleaseAge=7 days 只加在 github-actions 的 non-major 规则上,不是全局。 它复刻 scripts/check-dep-release-age.mjs 的供应链等待期,而那道门的判据是 package-lock 的 diff,看不见 workflow 里的 action 钉版 ⇒ actions 此前没有任 何等待期保护,这里是唯一能落地该策略的位置。 4. 同时设 minimumReleaseAgeBehaviour=timestamp-optional,因为等待期依赖 release timestamp,而源码注明 Docker 源只在 Docker Hub 取 tag_last_pushed; 我们两个引用都在 mcr.microsoft.com 与 ghcr.io,无时间戳时默认的 timestamp-required 会把更新判成 pending 永久搁置(util/minimum-release-age.ts 的 isPending 分支)。devcontainer 规则因此不加等待期,只设人工评审。 automerge 仍保持 false:合并决策继续走现有 automerge.yml 的 PAT 路径,避免开出 第二条合并通道(也保住 lovable→dev MERGE / 其余 SQUASH 的策略)。 生效前提:Renovate 只读默认分支的配置,本文件需再经 lovable→dev→main 一轮。
AAV-1320 在 Linear 已 Done 却还留在「仅列 open」表里,按文件自身规则移出。
dev 已由 #716(删除 Dependabot 配置)与 #717(Renovate 接管 npm + 7 天等待期) 定下"只用 Renovate"的方向;本分支的 renovate.json 与它从同一 v1 分叉,故以 dev 的决策为基底合并,只改两处会让策略静默变形的地方: 1. 规则按 manager 分域。dev 版两条 packageRules 没有 matchManagers,npm 进入 enabledManagers 后,npm 的非 major 升级会被打上 github_actions 标签,更紧要的是 会拿到 automerge 标签 ⇒ 运行时依赖的 minor/patch 将被自动合入。原 dependabot-auto-triage.yml 的门槛是 npm 只放行 direct:development 的 patch/minor, 运行时依赖一律人工评审。这里用 matchDepTypes 复刻该门槛:devDependencies 非 major 才进 automerge 标签,dependencies(运行时)任何类型都不给,major 一律人工。 2. 加 minimumReleaseAgeBehaviour=timestamp-optional。等待期依赖 release 时间戳, 源码注明 Docker 数据源的 tag_last_pushed 只在 Docker Hub 可得;纳入 devcontainer 后,我们两个引用(mcr.microsoft.com、ghcr.io)都没有时间戳,默认的 timestamp-required 会把它们判成 pending 永久搁置(util/minimum-release-age.ts 的 isPending 分支)。 保留 dev 的选择:weekly schedule、prHourlyLimit 0、全局 7 天等待期、npm 接管。 新增 devcontainer manager(.devcontainer/devcontainer.json 的 image 与 features 此前 无人看守;typescript-node tag 与 CI 的 node-version 是跨消费者契约,故一律人工评审)。 另:不在此刻删除 dependabot-auto-triage.yml / dependabot-resolve-peer-conflicts.yml。 dependabot.yml 只在 dev 被删、main 上仍在 ⇒ Dependabot 仍会往 dev 开 PR,这两个 workflow 目前还活着;等 dependabot.yml 离开 main 之后再收。 验证:renovate.json 无冲突标记且全部键经官方 schema 校验通过(含 matchDepTypes); overrides 在 merge 中存活(brace-expansion 1.1.21/5.0.12、fast-uri 3.1.8); lock 与合并后 package.json 一致(npm install 无 diff);osv-scanner rc=0; npm audit --omit=dev 0 漏洞;check:e2e-market-names 29 spec rc=0。
node 版本在本仓有两个家:workflow 的 node-version(走 node 数据源)与 Dev Container 镜像 typescript-node(走 docker 数据源),二者之间没有任何机制保证同步。正在开的 #727 就是这个裂口的形状——它只改 6 个 workflow 到 v24,不碰 .devcontainer,合下去 CI 就跑 Node 24、Codespaces 仍是 22(本仓无 .nvmrc、package.json 无 engines,所以 这两处就是全部真值)。 做法是跨 manager 分组而不是手工拼一个 PR:后者只修这一次,前者让"必须同批"成为 配置约束——matchDatasources [node, docker] + matchPackageNames [node, /typescript-node/] → groupName "node runtime",标签 manual-review。分组只在 devcontainer manager 生效后才有意义,故依赖同一份配置里的 enabledManagers。 另:pre-push e2e 从 workers=2 降到 1。该路径跑冷启动 dev server,两条用例 (waitForTableReady、waitForLoadState('networkidle'))在别的 agent 门禁共用本机时 会超时——2026-09-29/30 两次推送被无关红挡下,安静复跑均全绿(其中 faq-anchor 本地 9/9、board 里另一 session 独立判为负载争用)。选择降并发而不是把用例加进 grep-invert 排除名单:排除等于让门禁少测东西,而慢一点不减覆盖。CI 侧的 2 分片 不变(跑 built preview,不受影响)。 验证:renovate.json 全部键经官方 schema 校验(含 matchDatasources/matchPackageNames/ groupName),7 条规则;prettier 干净;check:e2e-market-names rc=0。
建票时把 CF 边缘注入的 __CF$cv 块误归为 Web Analytics beacon。真浏览器复现 + 逐 inline script 扫字面量证明它加载 challenge-platform/scripts/jsd,且站内 已无 cloudflareinsights 引用;zone 在 Free 计划,该检测按一手文档不可关。
#716 删 .github/dependabot.yml 时,AAV-1301 的 ignore(@eslint/js >= 10)没有去处: npm manager 已开、10.0.1 在 npm 上,下一个窗口会重新提出那个两次让 dev 的 peer-dep-check 与 lint 变红的 bump。用 allowedVersions "<10" 复刻原裁定,9.x 照旧流动。 droid-review.md 里 review_depth / security_scan_schedule 两条 workflow 实际并未设置, 删掉;后者改写为 `on: pull_request` 无 schedule 这种文件可证的说法。
reserves-table-market-filter-pin (5) 用"第 3 行"当作"筛选后会改序"的保证。CI 在 2026-10-05 的快照里整页都是同一个 market,筛选什么都没动,于是 pin 断言量到行仍停在 第三位,两次尝试 y 值一致 —— 确定性,不是抖动。旧选择器面对同一份数据也点到同一颗 chip,所以这不是 #735 的改动引入的。 改成扫可见行的 market chip label,取第一个与首行不同的行;整页同 market 时按本仓 discovery+条件 skip 的既有形状跳过并写明原因。纯函数进 e2e/reserveDiscovery.ts, 配 8 行矩阵单测(去掉 reference 条件会红 4 条,确认断言真咬住实现)。
:393 Path B 的等待是 poll tbody 行数 > 0,消息写着"等表格重新排序",实际只证明表格存在。 supply=900 在 CI 那份快照上没让列表移动,于是"按 Clear 必然触发 pin scroll"量到 0,首跑与 retry 同因,反复挡推送门。 改成用本文件已有的 getVisibleReserveOrder + didReorder(:259 早就这么守):候选场景值逐个试 到确实改序才继续,全都不改序就 skip 并写明原因。实测 :393 从 15.9s 失败变 9.7s 通过。
…qoder-0929a 行) # Conflicts: # SESSION-BOARD.md
位置断言与"模拟子行仍可见"两次都通过,唯独 window.scrollBy 探针读 0:重新排序把目标行 上方移走之后,仅靠布局它就已经落进锚点带,不需要滚动。那条探针断言的是机制而不是契约, 且机制归 :259 那条用例专门拥有(改了序就滚、没改序就不滚),删掉重复不减少覆盖。 如实记:这是一次断言放宽,依据写在代码注释与看板第十一行段。
…/_ 导致 pre-push 整条门静默不跑 的机制发现)
…osv 门 6.1.4 命中 GHSA-rj75-hqrm-r3gf(5.9,dev-only);6.x 无修复版(最高 6.1.4),修复只在 7.1.6, 而 postcss-nested / tailwindcss 均声明 ^6.1.x ⇒ 正常升级路径不存在,只能像 fast-uri/js-yaml/ws 那样用 overrides 钉。 跨大版本强钉的验证:npm run build(Tailwind 实际编译)rc=0,且产出的 CSS 逐字节一致 (index-DyVJKc4h.css / vendor-blockchain-DR00Q9RG.css 内容哈希同名,拼接 md5 相同), osv-scanner rc=0。备选是 osv-scanner.toml 的 ignoreUntil 条目(AAV-1328 对 braces 的做法)。
husky 写的 core.hooksPath 是相对值 .husky/_,而该目录只在 npm install 的 prepare 里生成(且自我忽略:.husky/_/.gitignore 内容是 *)。新 worktree 没这个目录 ⇒ git 找不到任何 hook ⇒ pre-commit/pre-push 整条不执行,而 commit/push 仍退出 0。 本仓有四道门只存在于 pre-push(osv-scanner / semgrep / knip / dup:check),CI 里 没有对应 job,所以这条路会产出「PR 全绿但门从没跑过」。#747 就是这样推上去的。 prepare 改为 `npx husky && node scripts/husky-hooks-path.mjs`:把共享 config 里的 hooksPath 指向主 checkout 的绝对路径,任何 worktree 都解析得到;配套在两个 hook 开头加 node_modules 守卫,缺依赖时明确报错。opt-out: HUSKY_HOOKS_PATH_ABSOLUTE=0; 自查: npm run check:hooks-path。附 17 条 node:test 覆盖判定表(不覆盖非本仓所有的 hooksPath、幂等、只认字面量 0 才关闭)。 另在 renovate.json 给 vitest 族加分组规则:vitest 5 的 peer 要求 @vitest/* 同版本, 只升一半会让严格 npm ci 在 peer-dep-check(dev/main 均必填)上失败——#731 即此病, 换成 Renovate 也会重犯,除非同批评审(照 node runtime 那条的先例)。
playwright.config 只把 proxy 放进 project 的 use,覆盖的是"测试用的浏览器"; e2e/global-setup.ts 自己 chromium.launch() 起的那个始终走直连。直连吞吐一差, warm-up 就卡在 portfolio-mode-toggle 的 120s 预算上,整轮在任何一条测试开始之前 中止——于是文档里的 E2E_PROXY 看起来完全失效。 实测判据(2026-10-07):/markets 三次直连 ttfb 1.4–5.0s 但 total 82s/100s/150s+ 都还没传完(HTTP 都 200),而本机 mixed 口 7891 用 4.2s。所以旧注释里那句 "API 已在 ~1s 返回 200"正是被证伪的前提:200 不说明传输时间。 改法是把代理判定抽成 e2e/browserProxy.ts 的单一来源,两个消费者都从这里取, 避免再次各写各的;src/test/browserProxy.test.ts 的 R8 是 wiring guard,直接断言 两处都调用 browserProxyArgs 且不再手写 process.env.E2E_PROXY。 验证:8 条用例绿、typecheck rc=0、e2e 显式 tsc rc=0(e2e 不在 lint/typecheck 覆盖内)、 E2E_PROXY 下 test:e2e:pre-push 63 passed / 1 flaky(既存:标题由实盘数据生成, retry 后标题变化报 Test not found)/ rc=0;改前同一命令在 warm-up 处 TimeoutError 中止。
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Droid finished @0xPabloLI's task —— View job A well-tested harness hardening PR whose core mechanism (absolute hooksPath, fail-loud node_modules guards, single-source e2e proxy) is sound, with one real bug: the self-check's advertised remediation command cannot actually apply because the npm script hardwires --check. No security findings — command execution uses fixed argv arrays, hooksPath is derived only from git's own metadata, and both env-var opt-outs fail closed. 1 inline comment posted |
| return { | ||
| ok: false, | ||
| verdict: 'relative-hooks-path', | ||
| hint: `core.hooksPath is "${configValue || '(unset)'}" — linked worktrees silently run no hooks; run \`npm run check:hooks-path -- --apply\` or reinstall`, |
There was a problem hiding this comment.
[P1] Advertised fix command npm run check:hooks-path -- --apply is a no-op
The relative-hooks-path verdict tells users to run npm run check:hooks-path -- --apply, but the npm script is pinned to node scripts/husky-hooks-path.mjs --check in package.json, so the trailing -- --apply yields --check --apply. In scripts/husky-hooks-path.mjs, checkOnly = argv.includes('--check') is then true, which short-circuits the apply branch (if (!checkOnly && decision.action === 'set')), so the suggested command just re-reports the same failure and exits 1 instead of repairing core.hooksPath. The one remediation path the gate itself recommends can never fix the state it diagnoses; the working invocation is only the bare node scripts/husky-hooks-path.mjs --apply listed in the CLI header, which the hint never mentions.
| hint: `core.hooksPath is "${configValue || '(unset)'}" — linked worktrees silently run no hooks; run \`npm run check:hooks-path -- --apply\` or reinstall`, | |
| hint: `core.hooksPath is "${configValue || '(unset)'}" — linked worktrees silently run no hooks; run \`node scripts/husky-hooks-path.mjs --apply\` or reinstall`, |
三个 *.generated 模块是 gitignore 的生成物:npm test 的第一步会生成它们,但 pre-commit 把 typecheck 排在 npm test **之前** ⇒ 在一个刚 npm install、还没跑过测试的 linked worktree 里, 任何提交都死在 TS2307 Cannot find module './chainIconManifest.generated',读起来完全像代码回归。 对照实验(在 /tmp 新 worktree 里做的):删掉生成物 → typecheck 报 3 条 TS2307; 跑 node scripts/generate-icon-manifests.mjs → 0 条。命令与 npm test 用的是同一个, 生成物本身仍被忽略,不会污染提交。 注:这条改动要等它进了主 checkout 的工作树才会真正被执行——绝对 hooksPath 下 git 跑的是 主 checkout 的 .husky/* 内容(PR 正文里已记这条限制)。
Summary
prepare改为npx husky && node scripts/husky-hooks-path.mjs:把共享的core.hooksPath从相对值.husky/_改成主 checkout 的绝对路径.husky/pre-commit/.husky/pre-push开头加node_modules守卫,缺依赖时明确报错scripts/lib/husky-hooks-path.mjs+scripts/husky-hooks-path.mjs+ 17 条node:test;npm run check:hooks-path自查e2e/browserProxy.ts:让E2E_PROXY也作用于 prewarm 浏览器(修一次真实的中断,见下文)+ 8 条用例含 wiring guardrenovate.json给 vitest 族加分组规则docs/workflows/cross-branch-workflow.md新增「建 worktree 后的必做第一步:装依赖」两笔功能 commit + 一笔 harness commit:
bac29c2c— hooksPath 绝对化 +node_modules守卫 + renovate vitest 族分组 + 文档8d73964f— e2e prewarm 代理(e2e/browserProxy.ts单一来源 + wiring guard)pre-commit在typecheck前先生成 icon/token manifest第三条:全新 worktree 里连 commit 都提交不了
三个
*.generated模块是 gitignore 的生成物。npm test的第一步会生成它们,但.husky/pre-commit把npm run typecheck排在npm test之前 ⇒ 在一个刚npm install、还没跑过测试的 linked worktree 里,任何提交都死在:读起来完全像代码回归。对照实验(在
/tmp新 worktree 里做,不是猜):删掉生成物 ⇒ typecheck 报 3 条 TS2307;跑node scripts/generate-icon-manifests.mjs(与npm test用的同一条命令)⇒ 0 条,且生成物仍被 git 忽略、不污染提交。注:这条改动要等它进了主 checkout 的工作树才真正被执行——绝对 hooksPath 下 git 跑的是主 checkout 的
.husky/*内容(本 PR 已把这条限制写在正文里)。要修的是哪个洞(不是"少跑几步测试",是门整条消失)
husky 把
core.hooksPath写成相对值.husky/_,而那个目录由npm install的prepare生成(它内部放了一个内容为*的.gitignore,即自我忽略,所以不进版本库)。git worktree add出来的目录没有它 ⇒ git 在该 worktree 里按相对路径找不到任何 hook ⇒ pre-commit 与 pre-push 一条都不执行,而git commit/git push照样退出 0。这比漏跑测试严重,因为本仓有四道门只存在于 pre-push:
osv-scanner、semgrep、knip、dup:check。CI 里没有对应 job,而 CI 的security-audit还是npm audit --omit=dev,看不见 dev 依赖的通告。⇒ 从干净 worktree 推上去的 PR 可以全绿而这四道门从没跑过。实际事故:#747 就是这么推上去的(内容无害,但等于绕过了本地门)。机制与判据
git commitgit push.husky/_)foreign,不改别人的配置HUSKY_HOOKS_PATH_ABSOLUTE=0(只认字面量0,写错不会静默关闭 ⇒ R13 专门守这条)端到端复现(不是只测函数):新建一个不装依赖的 worktree,尝试提交 ⇒ HEAD 未移动、文件停在 staged,门确实拦下了;同一操作在改前会静默成功。
apply两次输出set然后noop(幂等,R10 守着);npm run check:hooks-path报gated。测试:17 条 node:test 覆盖判定表;
node --test "scripts/**/*.test.mjs"154/154;npm testrc=0(3793 单测)。一处如实的局限:绝对 hooksPath 下,git 执行的是主 checkout 工作树里的
.husky/*内容。所以本 PR 合入前,其它 worktree 跑到的是旧版 hook(没有node_modules守卫,但四道门照常执行——修的是"完全不跑",守卫只是把失败变得可读)。合入并把 lovable 更新后,守卫才在所有 worktree 生效。renovate.json 那条为什么一起放这里
vitest与@vitest/*是 peer 锁死的一族:@vitest/coverage-v8@5的 peer 要求 vitest 精确同版本,只升一半 ⇒ 严格npm ci在peer-dep-check(dev 与 main 都必填)上失败。#731 就是这个病,而 Renovate 默认按包拆 PR,会原样重犯——所以本 PR(#748)只能人工把两包一起升。这里照node runtime那条的先例加分组规则,把"必须同批评审"写进配置而不是靠人记得。附带修掉的第二个既存缺陷:
E2E_PROXY管不到 prewarm 浏览器推送这台机器今天被 e2e 挡了一次,查下去发现不是环境问题那么轻——是个真缺陷:
playwright.config.ts把代理只放进 project 的use,覆盖的是测试用的浏览器;而e2e/global-setup.ts的 prewarm 自己chromium.launch(),从不带代理。于是直连吞吐一差,warm-up 就卡在portfolio-mode-toggle的 120s 预算上,整轮在任何一条测试开始之前中止,文档里的逃生阀看起来完全失效。量出来的判据(不是猜负载):
/markets(三次)7891global-setup.ts原注释里那句「while the /markets API itself had already returned 200 in ~1s」正是被证伪的前提:200 不说明传输时间。改法:代理判定抽成
e2e/browserProxy.ts单一来源,playwright.config.ts与global-setup.ts都从这里取;src/test/browserProxy.test.ts的 R8 是 wiring guard,断言两处都调用browserProxyArgs()且不再手写process.env.E2E_PROXY——两份各写各的正是这次分叉的成因(照e2eDevServerCommand()消除命令副本的先例)。验证:8 条用例绿;typecheck rc=0;e2e 显式 tsc rc=0(
e2e/不在 lint/typecheck 覆盖内,按 AGENTS.md 单独跑);E2E_PROXY=… npm run test:e2e:pre-push⇒ 改前TimeoutError at global-setup.ts:38、0 条测试执行;改后 63 passed / 1 flaky / rc=0。那 1 条 flaky 是既存问题(标题由实盘数据生成,retry 后标题变化 ⇒Test not found in the test file),与本次改动无关,已在重试后通过。本次推送带
E2E_PROXY=http://127.0.0.1:7891:直连此刻传不动 413KB 响应。这不是绕门(同一个 knob 早已是 tests 的既有用法,现在只是让 prewarm 也认它),但如果出口长期如此,warm-up 的 120s 预算与/markets的体积需要另开票谈。Test plan
peer-dep-check/lint/build转绿git commit,应看到❌ [pre-commit] BLOCKED: node_modules 不存在于 …npm run check:hooks-path在主 checkout 与 worktree 里都报gatedE2E_PROXY=… npm run test:e2e:pre-push能跑过 warm-up(本次已实测)@vitest/*)