Commit Graph

12 Commits

Author SHA1 Message Date
浅念 8f2b9825a6 feat(build): 最小门禁与状态机闭环
Task 6(Plan 4):validating 接真实门禁——五项产物检查(①内部死链 ②sitemap
一致 ③SEO 基线 ④大小写冲突 ⑤产物完整性),P0 阻断/P2 报告分级;validation_
runs/validation_issues 落库(severity/rule/url/evidence),任一 P0 → job failed
(VALIDATION_FAILED + 产物清场),全过 → succeeded。HTML 解析用 htmlparser2
(sanitize-html 既有传递依赖,lock 已解析 12.0.0——apps/api 显式声明复用,零新
包版本),禁正则提标签。报告 json/html 双落盘且 HTML 先写(Plan 3 窄窗口修,
html 转义模板)。顺手:build-snapshot 测试 spawn 缩减 7→2(六文件物化/bodyHtml
重写/ADR-010 改 materializeRenderSnapshot 直调;复用两场景并谱共享单次成功构建),
根套件 97.3s→75.8s;两测试文件清场补 validation 域反序(FK 链先行)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 12:29:48 +08:00
浅念 1cd309d56d feat(theme): pilot-minimal 骨架与布局组件
Plan 4 Task 3:
- themes/pilot-minimal 入 workspace(astro ^5.18 纯静态;依赖仅 astro)
- theme.manifest:contracts isThemeManifest 守卫通过——key=pilot-minimal/
  七模板条目(home/page/product-list/product-detail/news-list/news-detail/
  not-found);brand 刻意不入 requiredSiteConfig(恒可能 {},Task 1 移交契约)
- load-render-data:全主题唯一取数入口(五禁纪律结构保证,JSDoc 钉死)——
  SF_BUILD_INPUT 六文件、缺文件/形状破裂明确错误不静默空、同目录缓存单例、
  name 空→'Untitled Site'/brand 缺席→{}、编码 slug 原样贯穿(源码令牌扫描
  禁 percent-decode)
- 布局/组件:BaseLayout/SeoHead(title/desc/canonical/og)/SiteHeader(导航
  路由缺席容忍隐藏)/SiteFooter/RichText(信任链:清洗+重写后直出)/
  ResponsiveMedia/Breadcrumbs/ProductCard/NewsCard/Pagination(V1 禁用态)/
  NotFound + tokens.css(75rem/系统字体/4-8-16-24-48/中性色+单 accent)
- 根 test 链并入主题(vitest 3 + astro getViteConfig,Container API 组件
  渲染测试);typecheck 四段变五段;.astro/ 入 gitignore

354 根测试 + 16 主题测试全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 08:38:00 +08:00
浅念 586d715455 feat(source): 输入登记服务与 sf CLI(content-addressed 快照)
- registerSource(db, input):wxr/sitemap/媒体目录三类输入登记为 content-addressed
  快照——流式 copy+sha256 单遍双算(64KB 块),副本落
  storage/snapshots/<kind>/<sha256前2>/<sha256>(文件)或 <sha256>/(媒体整树),
  后续任务只读副本;UNIQUE(site,kind,sha256) 幂等(重复登记复用既有行)
- media_bundle 哈希钉死:排序相对路径('/' 归一)+ 每文件 sha256 → canonical
  JSON(键排序无空白)→ 再 sha256;不含 mtime、不依赖遍历顺序;完整清单
  (path/bytes/sha256)入 metadata jsonb;符号链接(含 junction)跳过并记录
- site 按 key select-then-insert upsert(23505 竞态重查);connection 判重键
  (site_id,name) 为应用层 select-then-insert(表无库级唯一,注释注明)
- 路径安全:输入 realpath 后须在 allowedRoots 白名单内(缺省 storage/incoming),
  ENOENT/越界/类型不符抛带 code 的 SourceRegisterError;全部输入先校验后摄取,
  任何失败零落库零复制
- 报告落 artifacts/register/<runId>/reports/register.json(runId=时间戳+随机;
  输入清单/哈希/大小/耗时)
- scripts/sf.ts(commander):wxr:probe 只读诊断(任意可读路径,IO/损坏文件出
  报告不崩——Task 6 fatalError 契约)出 JSON/HTML 报告;source:register 调服务
  出摘要。CLI 机制钉死:根 devDep tsx + 根 script sf + commander 根依赖,TS 源码
  直连(@sf/* 经 tsconfig paths,不经 dist)
- source.module.ts:Nest 注册 SourceRegisterService(standalone context 用);
  register.service.ts 刻意零装饰器(CLI/测试直 import 不拉 reflect-metadata)
- launder 类型垫片改经 triple-slash 引用随 html.ts 进任意编译程序(apps/api 与
  根程序直连适配器源码的新场景);apps/api 增 @sf/adapter-wp workspace 依赖与
  paths 映射(register.service 消费 ADAPTER_VERSION 单源)
- 测试 +7(tests/integration/source-register.test.ts,临时库 + tmpdir storage/
  报告根 finally 清理):三输入登记/清单哈希定义对拍/重复登记幂等/mtime 排除/
  副本布局与内容一致/路径安全六分支/junction 跳过/报告契约;全套 260 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 12:31:15 +08:00
浅念 7506fef875 fix(adapter-wp): srcset 审计精确化(parse-srcset);false 形态防御;复用 launder.naughtyHref;等价测试参数化
审查修复四项:srcset 逐候选审计改用 parse-srcset 同库实现(data: 逗号内嵌
URL 不再误切;全候选被拒/解析异常/零候选→整属性删除记账,含空 srcset);
deriveMirrorView 对 false/缺省形态 fail fast(allowedTags/allowedAttributes
的 false=库全放行语义与镜像空集解释相反,修正原注释);URL 判定复用
launder.naughtyHref(sanitize-html 2.18 同一实现路径,删手写镜像);
仪表化等价测试参数化为 15 个对抗样本(实体/控制字符/NULL/URL 注释走私、
未引号/重复属性、深嵌套、srcset 形态)。测试 204→228。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 11:18:39 +08:00
浅念 8dd6eaf1b6 feat(adapter-wp): HTML 白名单清洗与差异收集
sanitize-html@^2(MIT)白名单清洗 + onOpenTag 观察钩子按文档序收集 SanitizeDiff[]
(镜像判定从 SANITIZE_CONFIG 单源推导;仪表化与清洗输出字节等价,测试钉死)。
白名单决策冻结:h1/布局标签/内联样式/iframe 拒绝(diff 留痕),外链 img 含
1x1 追踪像素本切片保留(远程依赖报告归 Task 6,路径重写归阶段 3)。
测试 184→204。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 10:39:37 +08:00
浅念 25e8b9cb5c feat(adapter-wp): saxes 流式 WXR 解析器
parseWxrStream(readable, {limits, now}) → AsyncGenerator<WxrEvent>:
逐 chunk 喂入 saxes(^6.0.0,ISC;计划所写 ^0.9 不存在于版本历史),
item/term 按文档序交错产出;任一时刻只持有当前聚合状态 + 单 chunk 内
待 yield 队列,绝不收集已产出 item 列表(峰值内存与文件大小解耦)。

- 字段:postId(数值解析)/postType/status/postName/link/title/
  contentEncoded/excerptEncoded/postDate/postModified/attachmentUrl/
  creator + metas{key,value}[] + categories{domain,nicename,value}[] +
  rawLocation(wxr://item/<n>、wxr://term/<n>)
- 错误体系照 PhpError 先例:WxrError 基类;WxrParseError 带 saxes
  行列位置(error 事件同步抛出时捕获);WxrLimitError 按 kind 区分
- 限额:bytes/items/terms/depth/cdata/fieldLength(先检查后追加,
  超限内容永不入缓冲)/metaPerItem/time(可注入时钟)——超限即抛,
  不静默截断
- 实体防线(测试钉死):saxes 实体表固定为五预定义 + 数字引用,
  DOCTYPE 内部/SYSTEM 实体不注册——引用即 WxrParseError,替换文本
  不进产出;纯字符串解析,全程无网络 I/O
- 跨 chunk 喂入(7 字节/块)与单 chunk 产出一致(StringDecoder 处理
  多字节边界);畸形 XML(提前闭合/截断)→ WxrParseError 带行列

130 测试全绿(新增 22)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:55:11 +08:00
浅念 dc4557cccf feat(adapter-wp): 包骨架与限额契约
Plan 2 Task 1:@sf/adapter-wp 包骨架(照 contracts 先例:build extends +
rootDir "." + exclude test)+ WxrLimits 十字段限额契约与 DEFAULT_WXR_LIMITS
值域冻结(设计基线 §7.2)+ 跨包接线。

跨包接线(一份 paths 三方消费):
- 根 tsconfig compilerOptions.paths 把 @sf/contracts、@sf/adapter-wp 映射到
  各自 src/index.ts;adapter tsconfig extends 根 tsconfig 使包级 tsc 读同一
  份 paths(extends 相对路径按声明处解析,映射始终落在仓库根)
- 根 vitest.config.ts resolve.alias 指向同一映射(vitest 不读 tsconfig paths)
- adapter 加 @sf/contracts workspace:* 依赖;三方均直连 src,不经 dist
  (已实测:移走 contracts dist 后 vitest 与包级 tsc 仍全绿)
- 根 devDependencies 加 @types/node@^22(对齐 apps/api 钉 22):alias 的
  fileURLToPath 辅助需 node 类型,此前根程序靠 apps/api 传递 @types 才有
  node 全局,属偶合

限额类型锁(照 contracts 单源派生先例):DEFAULT_WXR_LIMITS as const
satisfies WxrLimits + _LIMIT_KEYS 键集与 keyof WxrLimits 双向咬合断言;
timeBudgetMs 以可注入时钟(构造函数 now())测试于 Task 3、实测覆盖于 Task 8。

验证:pnpm test 78 绿(75+3);pnpm --filter @sf/adapter-wp exec tsc
-p tsconfig.json --noEmit exit 0(--listFiles 证实 contracts 经 src 解析);
根 typecheck 链与 pnpm lint 绿;build 产物形状核验后清理。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-21 08:05:08 +08:00
浅念 494beadf94 chore: 终审 Minor 清理——@types/node 钉 22、contracts 构建排除测试、helpers 盲区注记、计划收口注记
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 21:08:34 +08:00
浅念 3c32becba6 chore: 收尾清账——type:module/gitattributes/engines/lint/CI/根typecheck覆盖
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 20:56:26 +08:00
浅念 514f37301e feat(api): NestJS standalone 骨架 + Drizzle/postgres.js 接入
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:56:50 +08:00
浅念 857389e68b feat(contracts): UniversalRow 契约 v1 冻结 + 守卫 + 测试
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 18:15:47 +08:00
浅念 30c78800e0 chore(workspace): pnpm monorepo 骨架 + vitest 冒烟
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-20 17:36:33 +08:00