agentcss 0.24.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/01-tokens/README.md +1 -1
  2. package/01-tokens/tokens.css +67 -1
  3. package/01-tokens/typography.css +1 -1
  4. package/02-primitives/button/demo.html +8 -0
  5. package/02-primitives/card/README.md +15 -0
  6. package/02-primitives/choice/README.md +30 -0
  7. package/02-primitives/choice/choice.css +1 -1
  8. package/02-primitives/input/README.md +1 -0
  9. package/02-primitives/select/README.md +42 -2
  10. package/02-primitives/select/demo.html +11 -6
  11. package/02-primitives/select/select.js +184 -94
  12. package/02-primitives/timeline/demo.html +2 -2
  13. package/02-primitives/timeline/timeline.css +1 -1
  14. package/03-patterns/accordion/README.md +15 -0
  15. package/03-patterns/content/README.md +11 -0
  16. package/03-patterns/drawer/README.md +1 -1
  17. package/03-patterns/dropdown/demo.html +1 -1
  18. package/03-patterns/form-validation/README.md +18 -0
  19. package/03-patterns/form-validation/form-validation.css +12 -1
  20. package/03-patterns/list/README.md +11 -0
  21. package/03-patterns/overlay/README.md +26 -6
  22. package/03-patterns/overlay/overlay.css +22 -3
  23. package/03-patterns/overlay/overlay.js +63 -17
  24. package/03-patterns/states/README.md +15 -0
  25. package/03-patterns/states/states.css +18 -1
  26. package/03-patterns/tooltip/README.md +12 -0
  27. package/04-recipes/longform/longform.html +3 -3
  28. package/04-recipes/report/README.md +9 -0
  29. package/04-recipes/report/report.css +5 -0
  30. package/04-recipes/table/README.md +16 -0
  31. package/04-recipes/tierA-tokens.css +3 -3
  32. package/09-assets/README.md +18 -0
  33. package/09-assets/bar/bar.css +25 -10
  34. package/09-assets/echarts-adapter/demo.html +3 -1
  35. package/09-assets/echarts-adapter/echarts-adapter.js +107 -34
  36. package/09-assets/scientific-plot/README.md +2 -1
  37. package/09-assets/scientific-plot/scientific-plot.js +0 -7
  38. package/AGENTS.md +2 -1
  39. package/CHANGELOG.md +370 -18
  40. package/CONTRIBUTING.md +6 -5
  41. package/README.md +9 -2
  42. package/README.zh-CN.md +25 -10
  43. package/SECURITY.md +5 -2
  44. package/START-HERE.md +7 -7
  45. package/SUPPORT.md +86 -24
  46. package/ai/START-HERE.md +3 -2
  47. package/ai/cli.js +126 -6
  48. package/ai/components.json +258 -2
  49. package/ai/components.meta.json +257 -40
  50. package/ai/components.schema.json +96 -0
  51. package/ai/contract.schema.json +11 -0
  52. package/ai/pdf-pages.js +79 -0
  53. package/ai/pdf.js +354 -0
  54. package/ai/tokens.json +2 -2
  55. package/ai/validate.js +48 -1
  56. package/dist/01-tokens/tokens.css +1 -1
  57. package/dist/02-primitives/select/select.js +158 -71
  58. package/dist/03-patterns/form-validation/form-validation.css +1 -1
  59. package/dist/03-patterns/overlay/overlay.css +1 -1
  60. package/dist/03-patterns/overlay/overlay.js +40 -11
  61. package/dist/03-patterns/states/states.css +1 -1
  62. package/dist/04-recipes/report/report.css +1 -1
  63. package/dist/04-recipes/tierA-tokens.css +1 -1
  64. package/dist/09-assets/bar/bar.css +1 -1
  65. package/dist/09-assets/echarts-adapter/echarts-adapter.js +75 -33
  66. package/dist/09-assets/scientific-plot/scientific-plot.js +0 -7
  67. package/dist/COSTS.md +37 -37
  68. package/docs/ACCESSIBILITY.md +3 -3
  69. package/docs/FACTS.md +144 -0
  70. package/docs/LAYER-BASELINE.md +28 -3
  71. package/docs/PRINT.md +450 -0
  72. package/docs/RELEASE-PROCESS.md +286 -0
  73. package/docs/VERIFY-RELEASE.md +167 -0
  74. package/index.html +1 -1
  75. package/llms-full.txt +8 -8
  76. package/llms.txt +5 -0
  77. package/package.json +6 -1
@@ -0,0 +1,286 @@
1
+ # 发布流程与版本不可变规则
2
+
3
+ > 本文规定**渠道语义、版本不可变、生成链顺序、发布状态机与恢复步骤**。
4
+ > 怎么核验一个已发布版本,见 [VERIFY-RELEASE.md](VERIFY-RELEASE.md)。
5
+
6
+ ---
7
+
8
+ ## 1. 两个交付渠道
9
+
10
+ 同一份仓库,两个出口,语义不同:
11
+
12
+ | | npm 包 | 文档站 |
13
+ |---|---|---|
14
+ | 内容 | 源码 + `dist/` + `docs/` + `ai/` `esm/` `types/` + 根级文档 | 整棵工作树(除审计脚本、内部目录等) |
15
+ | 身份 | **版本快照**:`0.24.1` 这一版就是那一版 | **项目文档**:滚动更新 |
16
+ | 触发 | 打 tag `v<版本>` | 推主分支 |
17
+ | 页脚/元数据里的版本号 | 就是该快照的版本 | 是「生成时所用库的版本」,不是站点快照号 |
18
+ | 能否领先于已发布版本 | 不能(它就是被发布的那一份) | 可以(文档修正不必等发版) |
19
+ | 版本号相同是否意味内容相同 | — | **不意味**。两个渠道文件集不同,更新节奏也不同 |
20
+
21
+ **结论只有一句:版本快照以 npm 为准;站点是持续更新的文档,不是快照的镜像。**
22
+
23
+ 明确禁止的推论:
24
+
25
+ - 不能因为站点页脚写着与 npm 相同的版本号,就认为两边内容一致。
26
+ - 不能因为两个渠道都来自同一个仓库,就认为它们发布的是同一份文件集合。
27
+
28
+ ---
29
+
30
+ ## 2. 版本不可变
31
+
32
+ ### 2.1 四条硬规则
33
+
34
+ 1. **已发布版本的产物不得修改。** 不重发、不覆盖、不在本地改完之后用同一版本号再发一次。
35
+ 2. **发现已发布版本有错 ⇒ 进下一个 patch 版本。** 错误本身要登记(见第 3 节),但修正只能向前走。
36
+ 3. **不追溯修改历史 tag。** tag 是发布锚点;改它就等于改写「当时发布了什么」。
37
+ 4. **同一版本号不得用来暗示两个渠道内容相同。** 渠道差异必须写出来,而不是靠版本号掩盖。
38
+
39
+ 这四条的目的是让「`0.24.1` 是什么」有一个**唯一且不会变**的答案。只要允许同版本号在不同时间、不同渠道悄悄演变,任何核验都没有意义——你核对的可能是另一份东西。
40
+
41
+ ### 2.2 打 tag 的唯一正确形式
42
+
43
+ ```bash
44
+ git tag -a v0.24.2 -m "source-commit: <40 位提交 SHA>"
45
+ git push origin v0.24.2 # 只推这一个,不要 git push --tags
46
+ ```
47
+
48
+ - 必须是 **annotated tag**:轻量 tag 没有 message,也就没有可核的声明。
49
+ - `source-commit` 必须是**完整 40 位**:短 SHA 有歧义空间,而这条记录的全部价值就是"没有歧义地指认一个提交"。
50
+ - 发布门禁会核三件事:tag 消息里的声明 == tag 实际指向的提交;该提交被至少一个远端分支包含;tag 内 `VERSION` 与当前版本一致。
51
+ - 从 `0.24.2` 起这是**硬性要求**;更早的 tag 没有该字段是历史事实,门禁对它们走兼容判据(只核"是否属于某个远端分支")。
52
+ - 它**不能**证明"这个提交是当时最合适的发布提交"。那条判断属于人,不属于门禁。
53
+
54
+ ---
55
+
56
+ ## 3. 发现已发布版本有错时
57
+
58
+ 1. **确认影响面**:哪些文件、哪些消费者、是否影响 API 或行为。
59
+ 2. **在仓库与文档站修正**(这两处可以立即更新;npm 已发布包不动)。
60
+ 3. **登记已知偏差**:写进 [VERIFY-RELEASE.md](VERIFY-RELEASE.md) 的「已知偏差」一节,必须含受影响版本、文件、错误内容、正确内容、影响对象、修复状态、处理方式,并给出可复现的检查命令。
61
+ 4. **在下一个版本的 CHANGELOG 里说明**,让升级的人知道修了什么。
62
+ 5. **不覆盖历史**:不改 tag、不重发同版本、不要求 registry 删除已发布版本。
63
+
64
+ 现存的实例见 [VERIFY-RELEASE.md](VERIFY-RELEASE.md) 第 5 节。
65
+
66
+ ---
67
+
68
+ ## 4. 生成链:什么由什么生成,顺序是什么
69
+
70
+ ### 4.1 生成器与依赖顺序
71
+
72
+ 仓库里没有 `regen-all` 之类的编排脚本。顺序是**由依赖决定的**,不是约定:
73
+
74
+ | 顺序 | 生成器 | 真值输入 | 输出 | 改写源文件? |
75
+ |---|---|---|---|---|
76
+ | 1 | `gen-ai-contract.py` | `ai/components.meta.json` + 组件 CSS/JS + `ai/behaviors.json` | `ai/components.json`、`ai/tokens.json` | 否 |
77
+ | 2 | `gen-tokens-tree.py` | `01-tokens/tokens.css` | `ai/tokens.tree.json` | 否 |
78
+ | 3 | `make-tiersnippet.py` | `01-tokens/tokens.css` | `04-recipes/tierA-tokens.css` | **是**(写进源目录) |
79
+ | 4 | `gen-behavior.py` | `01-tokens/behavior/*.js` | 组件 JS 里的内联 core 段 | **是**(就地改写) |
80
+ | 5 | `gen-docs.py` | `ai/components.json` + 各组件 `README.md` | `guide/`、`index.html`、`404.html` | 否 |
81
+ | 6 | `gen-llms.py` | `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` | `llms-full.txt` | 否 |
82
+ | 7 | `fix-start-here.py` | `ai/components.json` | `ai/START-HERE.md` 的表格段 | 是(局部) |
83
+ | 8 | `build-dist.py` | `01`–`04`、`09` 的 CSS/JS | `dist/` | 否 |
84
+
85
+ 两条必须记住的推论:
86
+
87
+ - **第 3、4 步写回源文件**,所以它们必须排在 `build-dist.py` **之前**。反过来做,`dist` 里装的是旧源。
88
+ - **第 1 步是第 5、6、7 步的输入**,所以 `ai/components.json` 变了却不重跑下游,页面与 `llms-full.txt` 会各自停在上一个版本。
89
+
90
+ 这不是推测。把顺序颠倒(先 `build-dist` 再 `make-tiersnippet`)后,`dist-fresh` 会明确报:
91
+
92
+ ```
93
+ 源码改过但 dist 没重建:04-recipes/tierA-tokens.css
94
+ ```
95
+
96
+ 而按正确顺序重跑同一处改动,`dist-fresh` 是绿的。失败信息点名了文件,所以能确认红的原因是**产物过期**,不是环境噪声。
97
+
98
+ ### 4.2 冻结点:发布前到底检查了什么
99
+
100
+ 发布前跑的是 `npm run publish:check`,它等于:
101
+
102
+ ```
103
+ check-all.sh --fast # 静态层(跳过所有需要浏览器的判据)
104
+ pack-smoke.sh # npm pack → 装进空项目 → 解析入口
105
+ ```
106
+
107
+ `--fast` 覆盖第 4.1 节**全部 8 个生成器**的产物新鲜度(每个生成器都有对应的 `--check` 判据;其中 `gen-docs` 与 `gen-llms` 由同一道 `docs-site` 门禁覆盖)。所以"入库产物 == 生成器现算"在发布前是被验过的。
108
+
109
+ 其中 `dist` 那条是**两段**:① 源 sha 与 `dist/manifest.json` 记录一致;② 产物字节 == 生成器现算字节(干跑一遍、逐文件比、不写盘)。第二段是后补的 —— 在此之前手改 `dist` 不会被任何判据发现,而文档却写着"不要手改 dist",等于一道不存在的保护。
110
+
111
+ 它**不覆盖**任何需要浏览器的判据。其中与发布物直接相关的是 `dist` 的语义一致性:
112
+
113
+ - `dist-parity-check.js`(CSS 经 CSSOM 逐条比对)
114
+ - `dist-js-parity-gate.js`(JS 的可观测行为)
115
+
116
+ 这两道属于 component 层,`--fast` 会跳过它们。**因此发布工作流里单独、无条件地跑这两步**(见 5.1)。这条不写在别处,只写在这里,因为"发布前跑过全部门禁"是一句会骗人的话。
117
+
118
+ ---
119
+
120
+ ## 5. 发布状态机
121
+
122
+ 两条链**不是**一条链。它们各自独立,用提交 SHA 关联,但不能互相推断。
123
+
124
+ ### 5.1 npm 链(tag 触发)
125
+
126
+ | 态 | 含义 | 探测方式(只读) |
127
+ |---|---|---|
128
+ | `TAGGED` | 存在 annotated tag `v<版本>` | `git cat-file -t v<版本>` |
129
+ | `GATES_OK` | `publish:check` + dist 语义比对全绿 | 工作流日志 |
130
+ | `NPM_PUBLISHED` | registry 上有该版本,且 provenance 指向本 tag 的提交 | `bash 05-audit/release-run.sh probe` |
131
+ | `RELEASE_CREATED` | GitHub Release 存在 | 同上 |
132
+
133
+ 工作流按这个顺序落地:
134
+
135
+ 1. 校验 tag 名 == `package.json.version`,检查 `NPM_TOKEN`;
136
+ 2. `npm ci`;
137
+ 3. **probe**(只读):输出 `npm=… release=… provenance=…`;
138
+ 4. `publish:check`;
139
+ 5. 装 Chromium,跑两道 `dist` 语义比对(**无条件**,不受第 6 步的跳过影响);
140
+ 6. `npm publish --provenance`,**仅当** `npm != present`;
141
+ 7. `gh release create`,**仅当** `release != present`;
142
+ 8. **verify**(`if: always()`):npm + provenance + Release 三者齐备才算完成。
143
+
144
+ 第 5 步之所以无条件:如果它和 `npm publish` 共用同一个 `if`,恢复一次半完成的发布会**在没跑 dist 校验的情况下**继续建 Release —— 那等于把发布验证变成可跳过的装饰。
145
+
146
+ 第 5 步还有一条**同样重要**的前提:它是浏览器判据,必须先有 `:8000` 上这一棵树的静态服务。
147
+ 第一次接线时直接 `node …` 跑,CI 上 21 个组件全部 `ERR_CONNECTION_REFUSED` ——
148
+ 那不是"dist 与源码不等价",是**观测条件没搭好**。现在统一套
149
+ `05-audit/with-server.sh`(与 `check-all` 同一个 `http-serve.py`,就绪判据是
150
+ `index.html` 字节一致 **且** 带 `X-FL-Gate-Server` 身份戳)。
151
+
152
+ ⚠️ 由此得到一条通用纪律:**浏览器判据的失败信息必须能区分"观测失败"与"结论为假"**。
153
+ 把 `ERR_CONNECTION_REFUSED` 报成"不等价",等于让一道判据在环境问题时指控产品。
154
+
155
+ 第 3 步之所以不能只看「版本存在」:`npm view` 只能回答「这个版本在不在」,不能回答「它是不是我们发的」。probe 会取 attestation 里的 `gitCommit` 与 tag 指向比对;不一致就判失败,而不是跳过。
156
+
157
+ ⚠️ 第 8 步的回执必须**等 registry 索引**。`npm publish` 返回 0 之后,版本端点还要几十秒才可见;
158
+ 回执在发布后立刻跑,会拿到 `npm=absent` 并把一次成功的发布报成「未完成」。
159
+ `release-run.sh verify` 现在对 `absent` 重试(默认 18 次 × 10 秒),
160
+ 对 `unreadable` **不重试**(那是接口/网络问题,等下去也不会变)。
161
+
162
+ 这条不是推演,是 `v0.24.2` 第三次运行的现场:npm 与 Release 都建好了,
163
+ 只有回执红。事后人工复核——`gitHead`、provenance 的 `gitCommit`、
164
+ registry `integrity` 与 attestation `subject` 全部一致——产物是对的,
165
+ 错的是**回执比索引快**。
166
+
167
+ ### 5.2 站点链(主分支触发)
168
+
169
+ | 态 | 含义 | 探测方式 |
170
+ |---|---|---|
171
+ | `COMMIT_PUSHED` | 主分支上有提交 | `git rev-parse HEAD` |
172
+ | `SITE_DEPLOYED` | Pages 部署记录指向某个提交 | `gh api repos/{owner}/{repo}/deployments?environment=github-pages` |
173
+
174
+ 这条链没有"npm 版本"的概念:它部署的是主分支的当前状态。两条链的提交**可以不同**,见第 1 节。
175
+
176
+ ---
177
+
178
+ ## 6. 发布中断后怎么恢复
179
+
180
+ 先探测,再决定,不要重头再来:
181
+
182
+ ```bash
183
+ bash 05-audit/release-run.sh probe
184
+ ```
185
+
186
+ | probe 输出 | 含义 | 该做什么 |
187
+ |---|---|---|
188
+ | `npm=absent release=absent` | 全新发布 | 正常跑完工作流 |
189
+ | `npm=present provenance=ok release=absent` | npm 已发、Release 没建 | **重跑同一条工作流**;发布步骤会自动跳过,只补 Release |
190
+ | `npm=present provenance=ok release=present` | 已完成 | 不需要动作;`verify` 会打印回执 |
191
+ | `npm=present provenance=mismatch` | registry 上的版本不是这个 tag 构建的 | **停**。这不是"已完成",是"发错了"。按第 3 节处理 |
192
+ | `npm=present provenance=absent` | 该版本不是本工作流发的(可能是手工发布) | **停**,人工判断来源 |
193
+ | `npm=absent release=present` | 异常态(Release 有、包没有) | 人工核对;不要在没弄清原因前重发 |
194
+
195
+ 禁止的恢复方式:
196
+
197
+ - **已发布之后**删除 tag 重新打 —— 那会改写"当时发布了什么"。
198
+ - `npm unpublish` 之后再发同版本 —— 版本号不可变规则不允许。
199
+ - 只手工 `gh release create` 而不跑 `verify` —— 回执是"三者同时成立"的唯一记录。
200
+
201
+ **唯一的例外,判据是"有没有产出物"**:`probe` 明确给出
202
+ `npm=absent release=absent` 时,这个 tag 从未产生任何可被消费的东西,
203
+ 它可以删掉、在同一版本号上重打。判据不是"我觉得没发出去",是那两行输出
204
+ ——两个渠道都查过,都不存在。
205
+
206
+ 这条例外有实际先例:`v0.24.2` 第一次打 tag 后,`release.yml` 在
207
+ **发布前门禁**就失败了(`selftest-all` 里 `release-gate` 的夹具在 Linux 上失真:
208
+ 夹具缺 `.gitignore`,`__pycache__` 让"工作区干净"那条正确地变红)。
209
+ npm 与 Release 都是 absent ⇒ 删 tag、修夹具、在同一提交树上重打 `v0.24.2`。
210
+ **版本号没有跳号,也没有第二份同版本内容。**
211
+
212
+ ⚠️ 反过来说:只要 `npm=present`,哪怕 Release 没建,也不许删 tag 重打 ——
213
+ 那时 registry 上已经有一个别人可以 `npm install` 的包,tag 是它唯一的来源锚点。
214
+
215
+ ---
216
+
217
+ ## 7. 发布记录
218
+
219
+ 记录的作用不是复盘情绪,是把三类**不同**的事实分开:改造做完了没有 / 产物对不对 / 流程走通了没有。
220
+ 三者可以同时成立两个而第三个不成立——`0.24.2` 就是这种情况。
221
+
222
+ ### 7.1 `0.24.2` 的三层状态
223
+
224
+ | 层次 | 状态 |
225
+ |---|---|
226
+ | 改造实施(十项) | 完成,十项都有落点 |
227
+ | `0.24.2` 产物 | **有效**:registry 有版本、provenance 与 tag 提交一致、包内文档与仓库逐字节相同、站点抽样一致 |
228
+ | 这一次的发布运行 | **未通过**:CI 在最后一步回执上失败,红叉保留 |
229
+
230
+ **这三句话不能互相代替。** 不能因为回执误判就把前面的发布抹掉;也不能因为产物有效就把那次运行改判为成功。
231
+
232
+ ### 7.1.1 关闭登记
233
+
234
+ | 项 | 结论 |
235
+ |---|---|
236
+ | 改造项 | 十项全部落地,无新增注册门禁(结构断言并进既有 `workflow-gate`) |
237
+ | 产物 | `0.24.2` 有效,独立核验通过(见上表第二行) |
238
+ | 发布运行 | **未通过,红叉保留**。回执重试修复(`e837d52`)**不重发**同版本 |
239
+ | 回执重试 | 已修、**未被真实发布走过** ⇒ 转入待验清单,按 §7.3 六条验 |
240
+ | 遗留 | 无"待修但没人管"的项;`0.24.2` 那次运行的红叉是历史事实,不是待办 |
241
+
242
+ ⇒ **这一轮关闭。** 关闭不等于"发布流程全绿",只等于"这一轮要做的十件事做完了、
243
+ 产物可信、剩下的不确定性已经被明确登记"。CI 侧的稳定性由定时运行连续观察
244
+ (`05-audit/nightly-observe.py` 提供判据:连续若干次定时运行全绿才算稳定)。
245
+
246
+ 那一次运行的事实,按来源分列:
247
+
248
+ - 来自**运行记录**:`npm publish` 步骤退出码 0;GitHub Release 已创建;失败步骤 = 回执
249
+ - 来自**事后独立核验**:registry 的 `gitHead`、provenance 的 `gitCommit`、attestation `subject` 与 registry `integrity` 解码后相等、包内 `llms-full.txt` 与仓库同哈希、站点三个文件抽样同哈希
250
+ - 失败原因:回执在 `npm publish` 返回后立刻查询,版本端点尚未索引 ⇒ `npm=absent` ⇒ 判"未完成"。**是回执早了,不是发布失败**
251
+ - 后续修复:回执对 `absent` 重试、对 `unreadable` 不重试。该修复**不在那个 tag 的代码里**,重跑同 tag 不会生效
252
+
253
+ ⇒ **不为了一个绿叉重发同版本**。CI 运行记录是历史事实,不是可以修辞的状态。
254
+
255
+ ### 7.2 三类故障分开登记
256
+
257
+ 同一次失败里混着三种**故障模型**,防护手段完全不同。合成一句"发布失败"会让下一个人修错地方:
258
+
259
+ | 类型 | 表现 | 防护 |
260
+ |---|---|---|
261
+ | 测试环境失真 | 夹具里的临时仓库缺 `.gitignore`,`__pycache__` 让"工作区干净"正确地变红 | 夹具必须复刻真实环境的**全部**约束;要修夹具,不改判据 |
262
+ | 观测条件缺失 | 浏览器判据没起 `:8000` 静态服务,21 个组件全是 `ERR_CONNECTION_REFUSED` | 浏览器判据自带观测条件;失败信息要能区分"没测成"与"结论为假" |
263
+ | 外部系统最终一致性 | `npm publish` 成功,版本端点数十秒后才可见 | 只对 `absent` 重试,对 `unreadable` 不重试;上界用**次数**不用累计秒数 |
264
+
265
+ ### 7.3 下一次真实发布要验的六条(提前固定,不许事后放宽)
266
+
267
+ 回执的重试逻辑还没被真实发布走过。下一次打 tag 时,按这张表逐条判定:
268
+
269
+ | # | 场景 | 通过条件 |
270
+ |---|---|---|
271
+ | 1 | npm 立即可见 | `verify` 一次就输出 `npm=present`,退出码 0 |
272
+ | 2 | npm 延迟可见 | 首次 `absent` 后继续等,最终 `present`,退出码 0 |
273
+ | 3 | npm 始终 `absent` | 重试次数用尽后失败,且输出点名"重试 N 次仍未见版本端点" |
274
+ | 4 | npm `unreadable` | **不进入** `absent` 重试路径,立即报查询错误(接口/网络问题重试也不会变) |
275
+ | 5 | npm 已存在、Release 不存在 | 先核已有包的 provenance `gitCommit` 与 tag 指向一致,再继续建 Release |
276
+ | 6 | npm 已存在、provenance 不匹配 | 停止。**"版本存在"不等于"发布正确"** |
277
+
278
+ 第 5、6 条是同一条原则的两面:幂等不是跳过已完成的步骤,而是确认已完成的步骤与本次预期一致。
279
+
280
+ ---
281
+
282
+ ## 8. 本文档的边界
283
+
284
+ 本文覆盖:渠道语义、版本不可变、生成链顺序、冻结点范围、发布状态机、恢复步骤、发布记录。
285
+
286
+ 本文**不**覆盖:视觉回归基线、体积预算、组件行为契约等与"发布身份"无关的门禁;也不承诺对发布物做逐文件完整性校验(见 [VERIFY-RELEASE.md](VERIFY-RELEASE.md) 第 4 节)。
@@ -0,0 +1,167 @@
1
+ # 核验一个已发布版本
2
+
3
+ > 读者:想确认「我装到的这个包、我看到的这个站点,到底来自哪个提交」的人。
4
+ > 本文只讲**核验**;发布流程与版本规则见 [RELEASE-PROCESS.md](RELEASE-PROCESS.md)。
5
+
6
+ ---
7
+
8
+ ## 0. 三个问题,三种证据
9
+
10
+ | 问题 | 证据 | 强度 |
11
+ |---|---|---|
12
+ | npm 包对应哪个提交? | registry 元数据的 `gitHead`;provenance 的 `resolvedDependencies` | 前者是自报字段,后者由 CI 签发 |
13
+ | 这个包是不是那次构建产出的? | attestation 的 subject 摘要 vs registry 的 `dist.integrity` | 密码学绑定 |
14
+ | 站点对应哪个提交? | GitHub deployment 记录的 `sha` | 部署来源,不是内容校验 |
15
+
16
+ 三条都能查,但**强度不同**。下面每一步都写明它能证明到哪一步为止。
17
+
18
+ ---
19
+
20
+ ## 1. npm 包 → 提交
21
+
22
+ ```bash
23
+ npm view agentcss@0.24.1 version gitHead --registry=https://registry.npmjs.org/
24
+ ```
25
+
26
+ 实测输出:
27
+
28
+ ```
29
+ version = '0.24.1'
30
+ gitHead = 'f090c86c1ba728a6cf904cc36dffe1a5eca887b2'
31
+ ```
32
+
33
+ - `gitHead` 是 `npm publish` 时构建目录的 git HEAD,由 npm 自动写入。
34
+ - 显式写 `--registry=https://registry.npmjs.org/`:若本机把 npm 指向了镜像,`npm view` 读到的是镜像的缓存元数据,而镜像与公共 registry 之间可能有延迟。核验时不要用镜像。
35
+ - **`gitHead` 不是签名。** 能改 registry 元数据的一方也能改它。要签名级证据,必须做第 2 步。
36
+
37
+ ---
38
+
39
+ ## 2. 证明签名描述的就是这个 tarball
40
+
41
+ 先取 provenance 里被绑定的三个字段:
42
+
43
+ ```bash
44
+ curl -s https://registry.npmjs.org/-/npm/v1/attestations/agentcss@0.24.1 \
45
+ | python3 -c '
46
+ import base64, json, sys
47
+ att = json.load(sys.stdin)["attestations"]
48
+ p = next(a for a in att if a["predicateType"].endswith("provenance/v1"))
49
+ st = json.loads(base64.b64decode(p["bundle"]["dsseEnvelope"]["payload"]))
50
+ bd = st["predicate"]["buildDefinition"]
51
+ print("workflow.ref =", bd["externalParameters"]["workflow"]["ref"])
52
+ print("gitCommit =", bd["resolvedDependencies"][0]["digest"]["gitCommit"])
53
+ print("subject.sha512 =", st["subject"][0]["digest"]["sha512"])
54
+ '
55
+ ```
56
+
57
+ 实测输出:
58
+
59
+ ```
60
+ workflow.ref = refs/tags/v0.24.1
61
+ gitCommit = f090c86c1ba728a6cf904cc36dffe1a5eca887b2
62
+ subject.sha512 = df01f6d74818c4ed1e26129ed543355d4add76ccd31a09867b520df21d91ea586c6e4bb5eef293ef61b7250567ffc73b4cc69cd0f95a68b92261397c4271d742
63
+ ```
64
+
65
+ 再取 registry 记录的 tarball 摘要,并解码成同一进制:
66
+
67
+ ```bash
68
+ npm view agentcss@0.24.1 dist.integrity --registry=https://registry.npmjs.org/ \
69
+ | tr -d "'" \
70
+ | python3 -c 'import base64,sys; s=sys.stdin.read().strip(); print(base64.b64decode(s.split("-",1)[1]).hex())'
71
+ ```
72
+
73
+ 实测输出(与上面 `subject.sha512` **逐字符相同**):
74
+
75
+ ```
76
+ df01f6d74818c4ed1e26129ed543355d4add76ccd31a09867b520df21d91ea586c6e4bb5eef293ef61b7250567ffc73b4cc69cd0f95a68b92261397c4271d742
77
+ ```
78
+
79
+ **为什么必须做这一步:** attestation 是一个独立对象,它签的是「某个摘要」,不是「这个包」。不核对 subject 与 `dist.integrity` 的对应关系,就等于拿一张别人的证件复印件说「这是我」。
80
+
81
+ 两者相等时,可以说:**这个 tarball 由 `release.yml` 在 `refs/tags/v0.24.1` 上、从 `f090c86…` 构建并签名。**
82
+ 两者不等时,不要相信任何一方,先查是不是取错了版本。
83
+
84
+ ---
85
+
86
+ ## 3. 站点 → 提交
87
+
88
+ ```bash
89
+ # 在仓库目录里执行:{owner} / {repo} 由 gh 按当前远端自动填充
90
+ gh api 'repos/{owner}/{repo}/deployments?environment=github-pages&per_page=1' \
91
+ --jq '.[0] | "\(.sha) \(.ref)"'
92
+ ```
93
+
94
+ 实测输出(`<提交> <部署来源分支>`):
95
+
96
+ ```
97
+ 200c01cee9c9e0e5a86461c0e7570262fa595974 master
98
+ ```
99
+
100
+ - 这条记录说明:**最近一次 Pages 部署取自 `200c01c…`**。
101
+ - `ref` 是 `master`,不是 `v0.24.1`。站点跟随主分支,**不是版本快照**。
102
+ - 它**不证明**落盘文件与该提交逐字节一致:部署过程有一次 `rsync`,本次没有对它做全量校验。
103
+ - 想抽查单个文件是否一致,取线上文件与本地同路径文件的 sha256 对比即可;抽查只能支撑被抽查的那几个文件。
104
+
105
+ ---
106
+
107
+ ## 4. 能证明什么、不能证明什么
108
+
109
+ | 级别 | 能回答 | 本库现状 | 不能回答 |
110
+ |---|---|---|---|
111
+ | L0 声明 | 「我声明它来自 X」 | 文档、CHANGELOG、tag 消息 | 声明是否为真 |
112
+ | L1 抽样 | 「我抽查了 N 个文件,都与 X 一致」 | 有人做过小样本抽查 | 未抽查的文件是否一致 |
113
+ | L2 全量 | 「每一个文件都与 X 的对应文件一致」 | **不存在** | 构建过程是否被污染 |
114
+ | L3 密码学 | 「这个 tarball 由这条 workflow、这个提交构建」 | npm provenance(SLSA provenance v1) | 仓库之外的东西 |
115
+
116
+ 三条必须说清的话:
117
+
118
+ 1. **没有 L2。** 既没有逐个文件比对过 tarball 与提交,也没有比对过站点全量。因此「包内每个文件都来自该提交」这句话目前**说不出口**。
119
+ 2. **站点不是 npm 包的镜像。** 两个渠道发布的文件集本来就不同(包里有 `dist/`、没有 `guide/`;站点有 `guide/` 与演示页、没有构建清单)。
120
+ 3. **同一版本号不保证同一内容。** 历史上出现过一次,见下一节。
121
+
122
+ ---
123
+
124
+ ## 5. 已知偏差:0.24.1
125
+
126
+ | 项 | 内容 |
127
+ |---|---|
128
+ | 受影响版本 | `0.24.1`(npm 上已发布的包) |
129
+ | 受影响文件 | 包内 `llms-full.txt` |
130
+ | 错误内容 | 第 13 行的命令示例注释写成 `39 components` |
131
+ | 正确内容 | 同一文件第 24 行写的是 `Components (40)`;`ai/components.json` 当时也是 40 条 |
132
+ | 影响对象 | 读取该文件、按其中数字理解组件规模的 AI 编码助手与自动化工具 |
133
+ | 修复状态 | 仓库与文档站已修正;**npm 上已发布的 0.24.1 未修正**(已发布版本不覆盖) |
134
+ | 处理方式 | 由下一个 patch 版本自然带走;本文件保留记录,不追溯改历史 |
135
+
136
+ 复现(两条命令,前者读包内文件,后者读线上文档):
137
+
138
+ ```bash
139
+ SITE=$(npm view agentcss homepage --registry=https://registry.npmjs.org/)
140
+
141
+ curl -sL https://registry.npmjs.org/agentcss/-/agentcss-0.24.1.tgz \
142
+ | tar xzO package/llms-full.txt | grep -n "components, joined"
143
+
144
+ curl -s "$SITE/llms-full.txt" | grep -n "components, joined"
145
+ ```
146
+
147
+ 实测输出(同一版本号,两个渠道,两个数字):
148
+
149
+ ```
150
+ 13:npx agentcss ai components --json # 39 components, joined with pitfalls
151
+ 13:npx agentcss ai components --json # 40 components, joined with pitfalls
152
+ ```
153
+
154
+ 两份文件的 sha256(实测,供交叉核对):
155
+
156
+ ```
157
+ a3d36f35ee748632ba05705e669ce950260dd81a3d32b5f3f46fadae0bd084ef # npm 包内
158
+ 530de90aa18c2e36b6463409566ba4db4a47dc2212c7fd0bab6a11850b7d845d # 文档站 / 仓库当前
159
+ ```
160
+
161
+ **这不是「版本快照可以变」的证据**,恰恰相反:它是「快照已经发出、错误只能在下一个版本修正」的实例。规则见 [RELEASE-PROCESS.md](RELEASE-PROCESS.md) 第 2 节。
162
+
163
+ ---
164
+
165
+ ## 6. 更早的版本
166
+
167
+ provenance 不是从第一个版本就有的。实测 `0.10.0` 的 attestation 接口返回 404 —— 这类版本只能核对 `gitHead`(L0),不要把它当作签名证据。
package/index.html CHANGED
@@ -97,7 +97,7 @@ li a:active { background: var(--surface-sunken); }
97
97
  </p>
98
98
  </main>
99
99
  <footer class="wrap">
100
- <p class="note">0.24.1 · 零第三方运行时依赖</p>
100
+ <p class="note">0.25.0 · 零第三方运行时依赖</p>
101
101
  </footer>
102
102
  </body>
103
103
  </html>
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentcss — full reference (one file)
2
2
 
3
- > Generated from `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` at version **0.24.1**.
3
+ > Generated from `ai/components.json` + `ai/pitfalls*.json` + `ai/behaviors.json` at version **0.25.0**.
4
4
  > Do not edit by hand: run `python 05-audit/gen-llms.py` (freshness is gated).
5
5
  > Short version: `llms.txt`. Machine contract: `ai/*.json`.
6
6
 
@@ -10,7 +10,7 @@ No build step, no framework, no third-party runtime dependency. Class names are
10
10
  ## 1 · Query instead of guessing
11
11
 
12
12
  ```bash
13
- npx agentcss ai components --json # 39 components, joined with pitfalls
13
+ npx agentcss ai components --json # 40 components, joined with pitfalls
14
14
  npx agentcss ai components --json --maturity=stable
15
15
  npx agentcss ai pitfalls --json --component=table
16
16
  npx agentcss ai tokens --json --set=dark
@@ -27,7 +27,7 @@ Every subcommand accepts `--json`; failures print `{"ok":false,...}` and exit no
27
27
 
28
28
  | id | class | tier | maturity | css | js | what it is | pitfalls |
29
29
  |---|---|---|---|---|---|---|---|
30
- | `bar` | `bar` | asset | stable | 5.3 KB | `bar.js` | 柱状图:堆叠/分组两种语义、超量程裁剪、图例 | `chart-multiline-label` |
30
+ | `bar` | `bar` | asset | stable | 6.5 KB | `bar.js` | 柱状图:堆叠/分组两种语义、超量程裁剪、图例 | `chart-multiline-label` |
31
31
  | `model-viewer` | `mv` | asset | stable | 4.0 KB | `model-viewer.js` | 3D 模型查看器:令牌驱动样式,不含渲染库本体 | — |
32
32
  | `scientific-plot` | `splot` | asset | stable | 3.9 KB | `scientific-plot.js` | 二维科学绘图:折线/区间带/对数轴,纯令牌驱动、零依赖 | `chart-multiline-label` |
33
33
  | `sparkline` | `sparkline` | asset | stable | 7.2 KB | `sparkline.js` | 迷你趋势图:无坐标轴、纯形状,适合表格内嵌的一行走势 | `chart-multiline-label` |
@@ -35,20 +35,20 @@ Every subcommand accepts `--json`; failures print `{"ok":false,...}` and exit no
35
35
  | `content` | `prose` | pattern | stable | 11.1 KB | — | 长文排版容器(标题/段落/列表/引用/代码) | `single-scroll-container` `line-height-constant-sync` |
36
36
  | `drawer` | `drawer` | pattern | stable | 4.5 KB | `drawer.js` | 侧边抽屉,含遮罩 | `fixed-element-inventory` |
37
37
  | `dropdown` | `dd` | pattern | stable | 3.9 KB | `dropdown.js` | 下拉菜单 | `popover-measure-after-render` |
38
- | `form-validation` | `form` | pattern | stable | 11.0 KB | — | 表单校验反馈与错误汇总 | — |
38
+ | `form-validation` | `form` | pattern | stable | 11.8 KB | — | 表单校验反馈与错误汇总 | — |
39
39
  | `list` | `list` | pattern | stable | 6.1 KB | `flip.js` | 键值列表/条目列表 | `empty-state-no-decoration` `text-injection-xss` |
40
40
  | `nav` | `nav` | pattern | stable | 12.1 KB | `nav.js` | 顶部导航与目录 | — |
41
- | `overlay` | `toast` | pattern | stable | 12.0 KB | `overlay.js` | 吐司提示与模态遮罩 | `fixed-element-inventory` |
41
+ | `overlay` | `toast` | pattern | stable | 12.8 KB | `overlay.js` | 吐司提示与模态遮罩 | `fixed-element-inventory` |
42
42
  | `pagination` | `pagination` | pattern | stable | 7.7 KB | `pagination.js` | 分页控件 | — |
43
43
  | `reveal` | `reveal` | pattern | stable | 4.0 KB | `reveal.js` | 入场动效:一个 data-reveal 属性六个方向,无 JS 时内容全可见 | — |
44
- | `states` | `state` | pattern | stable | 10.1 KB | — | 空/加载/错误/成功四种整块状态 | `empty-state-no-decoration` `text-injection-xss` |
44
+ | `states` | `state` | pattern | stable | 10.9 KB | — | 空/加载/错误/成功四种整块状态 | `empty-state-no-decoration` `text-injection-xss` |
45
45
  | `tabs` | `tabs` | pattern | stable | 5.9 KB | `tabs.js` | 选项卡切换 | — |
46
46
  | `tooltip` | `tooltip` | pattern | stable | 3.5 KB | `tooltip.js` | 悬停/聚焦提示 | `popover-measure-after-render` `fixed-element-inventory` |
47
47
  | `tree` | `tree` | pattern | stable | 4.0 KB | `tree.js` | 可展开树形结构 | — |
48
48
  | `badge` | `badge` | primitive | stable | 6.1 KB | — | 状态标签:计数、分类、结果标记 | — |
49
49
  | `button` | `btn` | primitive | stable | 15.7 KB | — | 动作触发:主/次/危险/纯文字四档 | — |
50
50
  | `card` | `card` | primitive | stable | 10.4 KB | — | 内容容器:媒体+正文+操作区三段 | — |
51
- | `choice` | `choice` | primitive | stable | 16.8 KB | — | 单选/多选/原生 select 外观的统一控件 | — |
51
+ | `choice` | `choice` | primitive | stable | 16.7 KB | — | 单选/多选/原生 select 外观的统一控件 | — |
52
52
  | `combobox` | `combo` | primitive | stable | 7.0 KB | `combobox.js` | 可搜索 + 可多选标签的输入控件 | — |
53
53
  | `date-range` | `drange` | primitive | stable | 6.0 KB | `date-range.js` | 日期区间选择,含预设快捷范围 | — |
54
54
  | `delta` | `delta` | primitive | stable | 5.5 KB | — | 变化量:涨跌方向 + 符号 + 颜色。只描述往哪边变,不判断好坏(等于好恶是 … | — |
@@ -65,7 +65,7 @@ Every subcommand accepts `--json`; failures print `{"ok":false,...}` and exit no
65
65
  | `analysis-report` | `report` | recipe | stable | 8.4 KB | — | 组合型研究报告页面:正文与辅助栏并排,窄屏自然退回单栏 | `empty-layout-min-height` |
66
66
  | `app-shell` | `shell` | recipe | stable | 24.9 KB | `shell-nav.js` | 应用外壳:侧栏 + 顶栏 + 主区三段式;sticky 侧栏自滚、min-wi… | `empty-layout-min-height` `fixture-demo-split-truth` |
67
67
  | `detail-page` | `detail-page` | recipe | stable | 5.7 KB | — | 实体详情页骨架:字段齐全 / 部分缺失 / 无数据三种状态同构,无数据是一等状态 | `empty-layout-min-height` |
68
- | `report` | `rep` | recipe | stable | 15.7 KB | — | 报告版式系统 v1:共享基础 + 三套版式(杂志/科研技术/金融研究),只换一… | — |
68
+ | `report` | `rep` | recipe | stable | 16.1 KB | — | 报告版式系统 v1:共享基础 + 三套版式(杂志/科研技术/金融研究),只换一… | — |
69
69
  | `table` | `table` | recipe | stable | 18.1 KB | — | 数据表格:排序/加载/空/错误四态;三种用途预设(财务/学术/统计) | `single-scroll-container` `empty-state-no-decoration` `table-overflow-wrapper` `text-injection-xss` |
70
70
 
71
71
  ## 3 · Known failure modes (32)
package/llms.txt CHANGED
@@ -21,8 +21,13 @@ npx agentcss ai tokens --json --set=dark
21
21
  npx agentcss ai pitfalls --json --component=table
22
22
  npx agentcss ai validate ai/components.json --schema=components --json
23
23
  npx agentcss ai check page.json --profile=strict --json
24
+ npx agentcss ai pdf page.html --out page.pdf --max-pages=8 # HTML → PDF (zero deps: uses your Chrome)
24
25
  ```
25
26
 
27
+ Print / PDF: `--medium=paper` is the obligation switch — only then are `printPolicy`
28
+ and `pageBudget` enforced. Per-component paper behaviour lives in
29
+ `ai/components.json` → `printPolicy`. `@page` is owned by the exporter, not the library.
30
+
26
31
  Every subcommand accepts `--json`. On failure it prints
27
32
  `{"ok":false,"command":"…","error":{"kind":"…","message":"…"}}` and exits non-zero,
28
33
  so a pipeline can never mistake an error for data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcss",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "移动 Web 组件库:CSS + 可选行为脚本,零第三方运行时依赖。样式用 bundler 引入,交互行为用 script 标签引入。",
5
5
  "keywords": [
6
6
  "css",
@@ -81,6 +81,11 @@
81
81
  "gate": "bash 05-audit/check-all.sh",
82
82
  "gate:fast": "bash 05-audit/check-all.sh --fast",
83
83
  "gate:timing": "bash 05-audit/check-all.sh --timing",
84
+ "ci:observe": "python3 05-audit/nightly-observe.py",
85
+ "ci:print-cost": "node 05-audit/print-pagination-cost.js",
86
+ "ci:print-audit": "node 05-audit/print-content-audit.js --all",
87
+ "ci:merge-exp": "node 05-audit/render-merge-experiment.js",
88
+ "ci:zoom-merge": "node 05-audit/zoom-merge-experiment.js --all",
84
89
  "size:record": "python3 05-audit/size-baseline.py --record",
85
90
  "dist": "python3 05-audit/build-dist.py",
86
91
  "dist:check": "python3 05-audit/build-dist.py --check && node 05-audit/dist-parity-check.js && python3 05-audit/dist-cost-gate.py",