@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24

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 (327) hide show
  1. package/README.md +102 -194
  2. package/README.zh-CN.md +115 -210
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-bootstrap.js +466 -35
  5. package/bin/gotry-inner.js +255 -36
  6. package/bin/gotry-process-liveness.d.ts +27 -0
  7. package/bin/gotry-process-liveness.js +97 -0
  8. package/bin/gotry-runtime-resolution.d.ts +1 -1
  9. package/bin/gotry-runtime-resolution.js +2 -2
  10. package/client/client.js +174 -0
  11. package/cordis.gotry-patch.yml +70 -93
  12. package/dist/capabilities/agent-reach-deep.js +45 -122
  13. package/dist/capabilities/agent-reach.js +63 -148
  14. package/dist/capabilities/anything.js +89 -75
  15. package/dist/capabilities/artifacts.js +123 -104
  16. package/dist/capabilities/channel-health.js +65 -45
  17. package/dist/capabilities/channel-registry.js +108 -87
  18. package/dist/capabilities/doctor.js +357 -235
  19. package/dist/capabilities/effect.js +223 -309
  20. package/dist/capabilities/fact-log.js +28 -14
  21. package/dist/capabilities/flyai.js +245 -156
  22. package/dist/capabilities/ground-transfer.js +509 -0
  23. package/dist/capabilities/hbcli.js +144 -148
  24. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  25. package/dist/capabilities/incident-log.js +131 -61
  26. package/dist/capabilities/model-override.js +30 -10
  27. package/dist/capabilities/opensky.js +54 -43
  28. package/dist/capabilities/resilience.js +50 -32
  29. package/dist/capabilities/session/action-cache.js +50 -44
  30. package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
  31. package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
  32. package/dist/capabilities/session/adapters/dida-portal.js +206 -0
  33. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  34. package/dist/capabilities/session/adapters/rail-12306.js +169 -112
  35. package/dist/capabilities/session/benchmark.js +85 -149
  36. package/dist/capabilities/session/extension-bridge.js +320 -259
  37. package/dist/capabilities/session/extension-channel.js +57 -76
  38. package/dist/capabilities/session/extension-distribution.js +115 -119
  39. package/dist/capabilities/session/extract.js +31 -21
  40. package/dist/capabilities/session/golden-score.js +55 -33
  41. package/dist/capabilities/session/health-watch.js +146 -154
  42. package/dist/capabilities/session/read-guard.js +55 -47
  43. package/dist/capabilities/session/static-flight-golden.js +49 -36
  44. package/dist/capabilities/session/transport.js +54 -61
  45. package/dist/capabilities/session/wizard.js +63 -93
  46. package/dist/capabilities/session-consent.js +143 -47
  47. package/dist/capabilities/session-login.js +95 -126
  48. package/dist/capabilities/session-search.js +391 -253
  49. package/dist/capabilities/sponsor-plugin.js +341 -0
  50. package/dist/capabilities/visa-policy.js +87 -73
  51. package/dist/capabilities/weather.js +163 -235
  52. package/dist/scripts/action-cache-tests.js +24 -59
  53. package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
  54. package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
  55. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  56. package/dist/scripts/agent-reach-tests.js +26 -18
  57. package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
  58. package/dist/scripts/anything-tests.js +58 -62
  59. package/dist/scripts/artifact-client-contract-tests.js +69 -0
  60. package/dist/scripts/artifacts-capability-tests.js +235 -0
  61. package/dist/scripts/async-collect.js +26 -15
  62. package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
  63. package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
  64. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
  65. package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
  66. package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
  67. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
  68. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
  69. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
  70. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
  71. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
  72. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
  73. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
  74. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
  75. package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
  76. package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
  77. package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
  78. package/dist/scripts/booking-executor-tests.js +80 -0
  79. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  80. package/dist/scripts/booking-saga-tests.js +61 -123
  81. package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
  82. package/dist/scripts/bootstrap-tests.js +1383 -128
  83. package/dist/scripts/build-changelog.js +89 -130
  84. package/dist/scripts/build-metrics-report.js +135 -143
  85. package/dist/scripts/changelog-tests.js +35 -82
  86. package/dist/scripts/channel-probe-tests.js +44 -116
  87. package/dist/scripts/channel-probe.js +84 -167
  88. package/dist/scripts/channel-registry-tests.js +97 -185
  89. package/dist/scripts/companion-tests.js +25 -70
  90. package/dist/scripts/dida-cookie-probe.js +87 -0
  91. package/dist/scripts/dida-sw-probe.js +114 -0
  92. package/dist/scripts/diff-test.js +17 -12
  93. package/dist/scripts/doctor-tests.js +428 -120
  94. package/dist/scripts/dsh-artifact-e2e.js +150 -0
  95. package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
  96. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  97. package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
  98. package/dist/scripts/dsh-runtime-closure.js +75 -63
  99. package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
  100. package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
  101. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  102. package/dist/scripts/effect-tests.js +165 -552
  103. package/dist/scripts/engine-run.js +1 -3
  104. package/dist/scripts/engine-tests.js +16 -10
  105. package/dist/scripts/evaluation-cadence-tests.js +69 -242
  106. package/dist/scripts/evaluation-contract-tests.js +193 -499
  107. package/dist/scripts/extension-distribution-cli.js +19 -15
  108. package/dist/scripts/extension-distribution-tests.js +98 -211
  109. package/dist/scripts/extension-tests.js +366 -389
  110. package/dist/scripts/fact-gate-tests.js +725 -263
  111. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  112. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  113. package/dist/scripts/flight-malformed-tests.js +159 -0
  114. package/dist/scripts/flyai-tests.js +325 -68
  115. package/dist/scripts/g5-guard-tests.js +131 -0
  116. package/dist/scripts/g5-guard.js +189 -0
  117. package/dist/scripts/gen-lunar-anchors.js +106 -0
  118. package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
  119. package/dist/scripts/ground-transfer-tests.js +956 -0
  120. package/dist/scripts/hbcli-e2e-tests.js +71 -90
  121. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  122. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  123. package/dist/scripts/hbcli-tests.js +41 -115
  124. package/dist/scripts/health-watch-cli.js +42 -28
  125. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  126. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  127. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  128. package/dist/scripts/i18n-tests.js +16 -9
  129. package/dist/scripts/incident-tests.js +281 -87
  130. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  131. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  132. package/dist/scripts/issue-271-liveness-tests.js +547 -0
  133. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  134. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  135. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  136. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  137. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  138. package/dist/scripts/journey-tests.js +22 -27
  139. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  140. package/dist/scripts/kernel-manifest-gate.js +126 -0
  141. package/dist/scripts/kernel-manifest-tests.js +212 -0
  142. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  143. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  144. package/dist/scripts/kernel-manifest.js +369 -0
  145. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  146. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  147. package/dist/scripts/ledger-tests.js +325 -340
  148. package/dist/scripts/ledger-workflow-crash.js +10 -20
  149. package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
  150. package/dist/scripts/memory-capture-tests.js +242 -61
  151. package/dist/scripts/memory-decay-tests.js +18 -52
  152. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  153. package/dist/scripts/memory-lifecycle.js +196 -0
  154. package/dist/scripts/memory-metrics.js +24 -12
  155. package/dist/scripts/memory-value-report.js +474 -222
  156. package/dist/scripts/memory-value-tests.js +328 -0
  157. package/dist/scripts/metrics-report-tests.js +62 -199
  158. package/dist/scripts/model-override-e2e.js +66 -105
  159. package/dist/scripts/nightly-evidence-tests.js +64 -89
  160. package/dist/scripts/nightly-evidence.js +108 -111
  161. package/dist/scripts/nudge-digest.js +39 -28
  162. package/dist/scripts/onboarding-tests.js +63 -88
  163. package/dist/scripts/opensky-check.js +18 -13
  164. package/dist/scripts/opensky-tests.js +28 -28
  165. package/dist/scripts/persona-surface-guard-tests.js +37 -12
  166. package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
  167. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  168. package/dist/scripts/price-drift-tests.js +126 -227
  169. package/dist/scripts/price-drift-watch.js +209 -253
  170. package/dist/scripts/probe-poi-tests.js +52 -45
  171. package/dist/scripts/product-metrics.js +187 -273
  172. package/dist/scripts/publish-preverify.js +47 -41
  173. package/dist/scripts/realtime-pricing-tests.js +37 -76
  174. package/dist/scripts/replay-async.js +20 -18
  175. package/dist/scripts/replay-real.js +16 -18
  176. package/dist/scripts/replay.js +52 -82
  177. package/dist/scripts/session-attach-diagnose.js +16 -12
  178. package/dist/scripts/session-attach-poc.js +34 -37
  179. package/dist/scripts/session-benchmark.js +66 -147
  180. package/dist/scripts/session-dida-live-e2e.js +151 -0
  181. package/dist/scripts/session-extract-tests.js +14 -35
  182. package/dist/scripts/session-login.js +14 -13
  183. package/dist/scripts/session-tests.js +545 -583
  184. package/dist/scripts/sf-live-benchmark.js +92 -110
  185. package/dist/scripts/sf-live-cli-tests.js +2 -9
  186. package/dist/scripts/sf-soft-score-tests.js +19 -60
  187. package/dist/scripts/sf-summary-tests.js +149 -0
  188. package/dist/scripts/sf-summary.js +466 -84
  189. package/dist/scripts/skeleton-check.js +16 -14
  190. package/dist/scripts/skeleton-integration-test.js +11 -10
  191. package/dist/scripts/skills-contract-tests.js +51 -46
  192. package/dist/scripts/smoke-session-gate-tests.js +12 -20
  193. package/dist/scripts/smoke.js +541 -422
  194. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  195. package/dist/scripts/state-cli-tests.js +226 -101
  196. package/dist/scripts/state-cli.js +549 -212
  197. package/dist/scripts/static-golden-tests.js +46 -180
  198. package/dist/scripts/time-eval-tests.js +210 -192
  199. package/dist/scripts/travel-timeline-tests.js +29 -93
  200. package/dist/scripts/turn-handoff-collect-tests.js +46 -93
  201. package/dist/scripts/turn-handoff-collect.js +71 -99
  202. package/dist/scripts/turn-policy-tests.js +28 -29
  203. package/dist/scripts/typed-contract-canary.js +77 -162
  204. package/dist/scripts/tz-resolver-sanity.js +50 -0
  205. package/dist/scripts/tz-resolver-tests.js +584 -0
  206. package/dist/scripts/unified-tests.js +23 -19
  207. package/dist/scripts/visa-policy-tests.js +31 -44
  208. package/dist/scripts/weather-tests.js +121 -543
  209. package/dist/scripts/wish-channel-gate-tests.js +29 -86
  210. package/dist/scripts/write-gate-tests.js +522 -0
  211. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  212. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  213. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  214. package/dist/scripts/z3-race-tests.js +18 -12
  215. package/dist/src/artifact-gate.js +652 -221
  216. package/dist/src/backend/kernel.js +91 -0
  217. package/dist/src/backend/modules/booking-copilot.js +55 -0
  218. package/dist/src/backend/modules/booking-executor.js +124 -0
  219. package/dist/src/backend/modules/session-search.js +194 -0
  220. package/dist/src/benchmark-agent-conformance.js +296 -262
  221. package/dist/src/benchmark-environment-bridge.js +298 -413
  222. package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
  223. package/dist/src/benchmark-tool-isolation.js +94 -67
  224. package/dist/src/bookable-facts.js +299 -174
  225. package/dist/src/booking-saga.js +64 -120
  226. package/dist/src/booking-surface/availability-policy.js +443 -576
  227. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  228. package/dist/src/booking-surface/contracts.js +24 -80
  229. package/dist/src/booking-surface/dsh-planner.js +510 -281
  230. package/dist/src/booking-surface/dsh-plugin.js +17 -1
  231. package/dist/src/booking-surface/error-codes.js +82 -10
  232. package/dist/src/booking-surface/index.js +0 -3
  233. package/dist/src/booking-surface/profile.js +10 -54
  234. package/dist/src/booking-surface/recovery-chain.js +367 -0
  235. package/dist/src/booking-surface/runtime.js +765 -891
  236. package/dist/src/booking-surface/server.js +200 -251
  237. package/dist/src/booking-surface/startup.js +58 -54
  238. package/dist/src/booking-surface/validation.js +150 -218
  239. package/dist/src/bridge.js +20 -13
  240. package/dist/src/companions.js +48 -74
  241. package/dist/src/contracts.js +21 -43
  242. package/dist/src/dsh-llm.js +82 -120
  243. package/dist/src/engine.js +253 -239
  244. package/dist/src/evaluation-cadence.js +72 -209
  245. package/dist/src/evaluation-contracts.js +220 -843
  246. package/dist/src/flight-pack-adapter.js +54 -0
  247. package/dist/src/flight-pack-contract.js +2 -0
  248. package/dist/src/gotry-backend.js +70 -0
  249. package/dist/src/hotel-date-gate.js +118 -0
  250. package/dist/src/i18n.js +18 -7
  251. package/dist/src/index.js +1425 -1629
  252. package/dist/src/journey.js +132 -81
  253. package/dist/src/ledger-repair-apply.js +538 -0
  254. package/dist/src/ledger-repair-plan.js +463 -0
  255. package/dist/src/loop.js +257 -166
  256. package/dist/src/memory-capture.js +180 -32
  257. package/dist/src/memory-decay.js +30 -14
  258. package/dist/src/memory-lifecycle.js +1310 -0
  259. package/dist/src/memory-utility.js +27 -40
  260. package/dist/src/mock-llm.js +36 -61
  261. package/dist/src/model.js +81 -36
  262. package/dist/src/realtime-pricing.js +63 -72
  263. package/dist/src/slot-spec.js +84 -106
  264. package/dist/src/state-ledger.js +310 -471
  265. package/dist/src/subagent-job-id-guard.js +58 -0
  266. package/dist/src/time-anchor.js +231 -33
  267. package/dist/src/tool-packet.js +20 -8
  268. package/dist/src/travel-slots.js +58 -35
  269. package/dist/src/travel-timeline.js +44 -63
  270. package/dist/src/turn-deadline.js +121 -127
  271. package/dist/src/turn-policy.js +61 -89
  272. package/dist/src/tz-resolver.js +143 -0
  273. package/dist/src/unified.js +504 -381
  274. package/dist/src/wish-pool.js +39 -14
  275. package/dist/src/write-gate.js +495 -0
  276. package/dist/src/z3-shared.js +273 -7
  277. package/extension/README.md +4 -3
  278. package/extension/background.js +125 -14
  279. package/extension/content-bridge.js +23 -1
  280. package/extension/content-main.js +15 -4
  281. package/extension/manifest.json +16 -8
  282. package/package.json +251 -220
  283. package/ts/capabilities/agent-reach.ts +4 -4
  284. package/ts/capabilities/anything.ts +1 -1
  285. package/ts/capabilities/artifacts.ts +46 -9
  286. package/ts/capabilities/flyai.ts +156 -37
  287. package/ts/capabilities/hbcli.ts +1 -1
  288. package/ts/capabilities/incident-log.ts +88 -59
  289. package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
  290. package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
  291. package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
  292. package/ts/capabilities/session/extension-bridge.ts +226 -101
  293. package/ts/capabilities/session/extension-channel.ts +22 -9
  294. package/ts/capabilities/session/wizard.ts +2 -2
  295. package/ts/capabilities/session-consent.ts +98 -14
  296. package/ts/capabilities/session-login.ts +1 -0
  297. package/ts/capabilities/session-search.ts +315 -31
  298. package/ts/dsh-runtime/vendor/README.md +8 -4
  299. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
  300. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
  301. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
  302. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
  303. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
  304. package/ts/package.json +236 -16
  305. package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
  306. package/ts/scripts/state-cli.ts +418 -51
  307. package/ts/src/artifact-gate.ts +561 -15
  308. package/ts/src/benchmark-environment-bridge.ts +37 -20
  309. package/ts/src/bookable-facts.ts +183 -7
  310. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  311. package/ts/src/booking-surface/dsh-planner.ts +240 -32
  312. package/ts/src/booking-surface/dsh-plugin.js +17 -1
  313. package/ts/src/booking-surface/error-codes.ts +75 -0
  314. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  315. package/ts/src/booking-surface/server.ts +84 -51
  316. package/ts/src/booking-surface/validation.ts +2 -0
  317. package/ts/src/contracts.ts +12 -2
  318. package/ts/src/dsh-llm.ts +4 -8
  319. package/ts/src/index.ts +609 -90
  320. package/ts/src/loop.ts +116 -7
  321. package/ts/src/memory-capture.ts +227 -3
  322. package/ts/src/mock-llm.ts +4 -8
  323. package/ts/src/model.ts +88 -2
  324. package/ts/src/state-ledger.ts +47 -29
  325. package/ts/src/subagent-job-id-guard.ts +72 -0
  326. package/ts/src/time-anchor.ts +217 -13
  327. package/ts/src/unified.ts +316 -54
@@ -1,3 +1,22 @@
1
+ /**
2
+ * #138 指标面板第一切片:只读聚合呈现面 v1(ADR-11 质量层,工程面交付)。
3
+ *
4
+ * 聚合既有落盘侧车(不新增任何数据源)→ 单一 markdown 报告:
5
+ * - 事实闸 verdict 分布 + blocked 牊(<stateRoot>/gotry-state/bookable-facts.jsonl)
6
+ * - 通道健康 down/cooldown('ok' 恢复事件 latest-wins 超越;<stateRoot>/gotry-state/channel-health.jsonl,30 天窗,
7
+ * 与 doctor 持久面 readLatestChannelEvents 同口径)
8
+ * - 事故面(<stateRoot>/gotry-state/incidents.jsonl,--days 窗,默认 7)
9
+ * - 桥延迟 p50/p95/max + >500ms 违约计数(<stateRoot>/gotry-state/bridge-latency.jsonl;
10
+ * >500ms 是架构 §11 复审节奏的触发锚点,这里给出可见性)
11
+ * - 账本存在性(gotry-state.db 只 stat:openDb 建连即可能写 kv,指标面不打开 SQLite)
12
+ * - doctor 报告存在性 + 评测三层入口指针(运行结果由 run-all-tests.sh/CI 承载,v1 不重跑评测)
13
+ *
14
+ * 纪律:全程只读(缺文件=空节;坏行跳过,与 incidents/fact-log 同纪律);
15
+ * 默认写 stdout,仅 --out 指定路径时落盘;零新依赖零 LLM;
16
+ * 工程面交付,不构成 M3 Exit 证据(#138 口径,Exit 证据归 #22)。
17
+ *
18
+ * 运行:npx tsx ts/scripts/build-metrics-report.ts [--state-root <dir>] [--out <file>] [--days <N>]
19
+ */
1
20
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
21
  import { isAbsolute, join, resolve } from 'node:path';
3
22
  const BRIDGE_LATENCY_BUDGET_MS = 500;
@@ -6,107 +25,104 @@ function stateFile(stateRoot, name) {
6
25
  const root = stateRoot === '.' ? process.cwd() : stateRoot;
7
26
  return join(root, 'gotry-state', name);
8
27
  }
28
+ /** 坏行跳过的 JSONL 读取(与 incidents/fact-log/channel-health 同纪律) */
9
29
  function readJsonl(path) {
10
- if (!existsSync(path)) return [];
30
+ if (!existsSync(path))
31
+ return [];
11
32
  try {
12
33
  const rows = [];
13
- for (const line of readFileSync(path, 'utf-8').split('\n')){
34
+ for (const line of readFileSync(path, 'utf-8').split('\n')) {
14
35
  const t = line.trim();
15
- if (!t) continue;
36
+ if (!t)
37
+ continue;
16
38
  try {
17
39
  rows.push(JSON.parse(t));
18
- } catch {}
40
+ }
41
+ catch { /* 单行损坏不拖垮聚合 */ }
19
42
  }
20
43
  return rows;
21
- } catch {
44
+ }
45
+ catch {
22
46
  return [];
23
47
  }
24
48
  }
25
49
  function bounded(value, max = 120) {
26
- if (!value) return undefined;
50
+ if (!value)
51
+ return undefined;
27
52
  return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
28
53
  }
29
- const BLOCKED_VERDICTS = new Set([
30
- 'error',
31
- 'needs-setup'
32
- ]);
54
+ const BLOCKED_VERDICTS = new Set(['error', 'needs-setup']);
55
+ /** 同 fact_id 取最新(与 loadFactRegistry 的 dedupe 同语义;行序后者胜) */
33
56
  export function aggregateFacts(rows) {
34
57
  const latest = new Map();
35
58
  let lines = 0;
36
- for (const raw of rows){
37
- const row = typeof raw === 'object' && raw !== null ? raw : {};
59
+ for (const raw of rows) {
60
+ const row = (typeof raw === 'object' && raw !== null ? raw : {});
38
61
  lines += 1;
39
62
  const id = typeof row.fact_id === 'string' ? row.fact_id : '';
40
- if (!id) continue;
63
+ if (!id)
64
+ continue;
41
65
  latest.set(id, row);
42
66
  }
43
67
  const byKind = {};
44
- for (const row of latest.values()){
68
+ for (const row of latest.values()) {
45
69
  const kind = typeof row.kind === 'string' && row.kind ? row.kind : 'unknown';
46
70
  const verdict = typeof row.verdict === 'string' && row.verdict ? row.verdict : 'n/a';
47
- const slot = byKind[kind] ??= {
48
- total: 0,
49
- verdicts: {},
50
- blocked: 0
51
- };
71
+ const slot = (byKind[kind] ??= { total: 0, verdicts: {}, blocked: 0 });
52
72
  slot.total += 1;
53
73
  slot.verdicts[verdict] = (slot.verdicts[verdict] ?? 0) + 1;
54
- if (BLOCKED_VERDICTS.has(verdict)) slot.blocked += 1;
74
+ if (BLOCKED_VERDICTS.has(verdict))
75
+ slot.blocked += 1;
55
76
  }
56
- return {
57
- lines,
58
- deduped: latest.size,
59
- byKind
60
- };
77
+ return { lines, deduped: latest.size, byKind };
61
78
  }
62
79
  export function aggregateChannels(rows, opts = {}) {
63
80
  const nowMs = (opts.now ?? new Date()).getTime();
64
81
  const cutoff = nowMs - (opts.windowDays ?? CHANNEL_WINDOW_DAYS) * 86_400_000;
65
82
  const counts = new Map();
66
83
  const newest = new Map();
84
+ // ok 超越(外部事件接缝第 1 段):通道最新事件若为 'ok'(本地探针恢复),
85
+ // 该通道从「当前 down/cooldown」展示面退出——down 计数(历史频率)保留。
67
86
  const lastState = new Map();
68
- for (const raw of rows){
69
- const row = typeof raw === 'object' && raw !== null ? raw : {};
70
- if (typeof row.channel !== 'string' || !row.channel) continue;
71
- if (typeof row.state === 'string' && [
72
- 'down',
73
- 'cooldown',
74
- 'ok'
75
- ].includes(row.state)) {
87
+ for (const raw of rows) {
88
+ const row = (typeof raw === 'object' && raw !== null ? raw : {});
89
+ if (typeof row.channel !== 'string' || !row.channel)
90
+ continue;
91
+ if (typeof row.state === 'string' && ['down', 'cooldown', 'ok'].includes(row.state)) {
76
92
  lastState.set(row.channel, row.state);
77
93
  }
78
94
  }
79
- for (const raw of rows){
80
- const row = typeof raw === 'object' && raw !== null ? raw : {};
81
- if (typeof row.channel !== 'string' || !row.channel) continue;
82
- if (row.state !== 'down' && row.state !== 'cooldown') continue;
83
- if (lastState.get(row.channel) === 'ok') continue;
95
+ for (const raw of rows) {
96
+ const row = (typeof raw === 'object' && raw !== null ? raw : {});
97
+ if (typeof row.channel !== 'string' || !row.channel)
98
+ continue;
99
+ if (row.state !== 'down' && row.state !== 'cooldown')
100
+ continue;
101
+ if (lastState.get(row.channel) === 'ok')
102
+ continue;
84
103
  const at = typeof row.at === 'string' ? row.at : '';
85
104
  const atMs = Date.parse(at);
86
- if (Number.isFinite(atMs) && atMs < cutoff) continue;
105
+ if (Number.isFinite(atMs) && atMs < cutoff)
106
+ continue;
87
107
  counts.set(row.channel, (counts.get(row.channel) ?? 0) + 1);
88
108
  const atMsSafe = Number.isFinite(atMs) ? atMs : 0;
89
109
  const prev = newest.get(row.channel);
90
- if (prev && prev.atMs >= atMsSafe) continue;
110
+ if (prev && prev.atMs >= atMsSafe)
111
+ continue;
91
112
  newest.set(row.channel, {
92
113
  channel: row.channel,
93
114
  state: row.state,
94
115
  reason: typeof row.reason === 'string' ? row.reason : undefined,
95
116
  at: at || undefined,
96
117
  events: 0,
97
- atMs: atMsSafe
118
+ atMs: atMsSafe,
98
119
  });
99
120
  }
100
- const latest = [
101
- ...newest.values()
102
- ].map(({ atMs: _atMs, ...row })=>({
103
- ...row,
104
- events: counts.get(row.channel) ?? 0
105
- }));
106
- return {
107
- windowDays: opts.windowDays ?? CHANNEL_WINDOW_DAYS,
108
- latest
109
- };
121
+ const latest = [...newest.values()].map(({ atMs: _atMs, ...row }) => ({
122
+ ...row,
123
+ events: counts.get(row.channel) ?? 0,
124
+ }));
125
+ return { windowDays: opts.windowDays ?? CHANNEL_WINDOW_DAYS, latest };
110
126
  }
111
127
  export function aggregateIncidents(rows, opts = {}) {
112
128
  const nowMs = (opts.now ?? new Date()).getTime();
@@ -115,17 +131,16 @@ export function aggregateIncidents(rows, opts = {}) {
115
131
  const byKind = {};
116
132
  const sources = new Map();
117
133
  let total = 0;
118
- for (const raw of rows){
119
- const row = typeof raw === 'object' && raw !== null ? raw : {};
134
+ for (const raw of rows) {
135
+ const row = (typeof raw === 'object' && raw !== null ? raw : {});
120
136
  const ts = typeof row.ts === 'string' ? row.ts : '';
121
137
  const atMs = Date.parse(ts);
122
- if (Number.isFinite(atMs) && atMs < cutoff) continue;
138
+ if (Number.isFinite(atMs) && atMs < cutoff)
139
+ continue;
123
140
  const kind = typeof row.kind === 'string' && row.kind ? row.kind : 'unknown';
124
- const slot = byKind[kind] ??= {
125
- count: 0
126
- };
141
+ const slot = (byKind[kind] ??= { count: 0 });
127
142
  slot.count += 1;
128
- if (!slot.lastTs || Number.isFinite(atMs) && atMs > Date.parse(slot.lastTs)) {
143
+ if (!slot.lastTs || (Number.isFinite(atMs) && atMs > Date.parse(slot.lastTs))) {
129
144
  slot.lastTs = ts || slot.lastTs;
130
145
  slot.lastMessage = bounded(typeof row.message === 'string' ? row.message : undefined);
131
146
  }
@@ -133,69 +148,51 @@ export function aggregateIncidents(rows, opts = {}) {
133
148
  sources.set(source, (sources.get(source) ?? 0) + 1);
134
149
  total += 1;
135
150
  }
136
- const topSources = [
137
- ...sources.entries()
138
- ].sort((a, b)=>b[1] - a[1]).slice(0, 5).map(([source, count])=>({
139
- source,
140
- count
141
- }));
142
- return {
143
- windowDays,
144
- totalInWindow: total,
145
- byKind,
146
- topSources
147
- };
151
+ const topSources = [...sources.entries()]
152
+ .sort((a, b) => b[1] - a[1])
153
+ .slice(0, 5)
154
+ .map(([source, count]) => ({ source, count }));
155
+ return { windowDays, totalInWindow: total, byKind, topSources };
148
156
  }
149
157
  export function aggregateLatency(rows, opts = {}) {
150
158
  const nowMs = (opts.now ?? new Date()).getTime();
151
159
  const cutoff = nowMs - (opts.windowDays ?? 7) * 86_400_000;
152
160
  const values = [];
153
161
  const kinds = new Map();
154
- for (const raw of rows){
155
- const row = typeof raw === 'object' && raw !== null ? raw : {};
162
+ for (const raw of rows) {
163
+ const row = (typeof raw === 'object' && raw !== null ? raw : {});
156
164
  const ts = typeof row.ts === 'string' ? row.ts : '';
157
165
  const atMs = Date.parse(ts);
158
- if (Number.isFinite(atMs) && atMs < cutoff) continue;
166
+ if (Number.isFinite(atMs) && atMs < cutoff)
167
+ continue;
159
168
  const ms = typeof row.latencyMs === 'number' ? row.latencyMs : Number.NaN;
160
- if (!Number.isFinite(ms)) continue;
169
+ if (!Number.isFinite(ms))
170
+ continue;
161
171
  values.push(ms);
162
172
  const kind = typeof row.kind === 'string' && row.kind ? row.kind : 'unknown';
163
173
  kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
164
174
  }
165
- const sorted = [
166
- ...values
167
- ].sort((a, b)=>a - b);
168
- const pick = (q)=>sorted.length === 0 ? 0 : sorted[Math.min(sorted.length - 1, Math.ceil(q * sorted.length) - 1)];
175
+ const sorted = [...values].sort((a, b) => a - b);
176
+ const pick = (q) => (sorted.length === 0 ? 0 : sorted[Math.min(sorted.length - 1, Math.ceil(q * sorted.length) - 1)]);
169
177
  return {
170
178
  count: sorted.length,
171
179
  p50: pick(0.5),
172
180
  p95: pick(0.95),
173
181
  max: sorted.length === 0 ? 0 : sorted[sorted.length - 1],
174
- overBudget: sorted.filter((ms)=>ms > BRIDGE_LATENCY_BUDGET_MS).length,
182
+ overBudget: sorted.filter((ms) => ms > BRIDGE_LATENCY_BUDGET_MS).length,
175
183
  budgetMs: BRIDGE_LATENCY_BUDGET_MS,
176
- topKinds: [
177
- ...kinds.entries()
178
- ].sort((a, b)=>b[1] - a[1]).slice(0, 5).map(([kind, count])=>({
179
- kind,
180
- count
181
- }))
184
+ topKinds: [...kinds.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([kind, count]) => ({ kind, count })),
182
185
  };
183
186
  }
184
187
  function presenceOf(path) {
185
188
  try {
186
- if (!existsSync(path)) return {
187
- exists: false
188
- };
189
+ if (!existsSync(path))
190
+ return { exists: false };
189
191
  const st = statSync(path);
190
- return {
191
- exists: true,
192
- bytes: st.size,
193
- mtime: st.mtime.toISOString()
194
- };
195
- } catch {
196
- return {
197
- exists: false
198
- };
192
+ return { exists: true, bytes: st.size, mtime: st.mtime.toISOString() };
193
+ }
194
+ catch {
195
+ return { exists: false };
199
196
  }
200
197
  }
201
198
  export async function collectMetrics(stateRoot, opts = {}) {
@@ -204,27 +201,19 @@ export async function collectMetrics(stateRoot, opts = {}) {
204
201
  generatedAt: (opts.now ?? new Date()).toISOString(),
205
202
  stateRoot: root,
206
203
  facts: aggregateFacts(readJsonl(stateFile(stateRoot, 'bookable-facts.jsonl'))),
207
- channels: aggregateChannels(readJsonl(stateFile(stateRoot, 'channel-health.jsonl')), {
208
- now: opts.now,
209
- windowDays: CHANNEL_WINDOW_DAYS
210
- }),
211
- incidents: aggregateIncidents(readJsonl(stateFile(stateRoot, 'incidents.jsonl')), {
212
- now: opts.now,
213
- windowDays: opts.days ?? 7
214
- }),
215
- latency: aggregateLatency(readJsonl(stateFile(stateRoot, 'bridge-latency.jsonl')), {
216
- now: opts.now,
217
- windowDays: opts.days ?? 7
218
- }),
204
+ channels: aggregateChannels(readJsonl(stateFile(stateRoot, 'channel-health.jsonl')), { now: opts.now, windowDays: CHANNEL_WINDOW_DAYS }),
205
+ incidents: aggregateIncidents(readJsonl(stateFile(stateRoot, 'incidents.jsonl')), { now: opts.now, windowDays: opts.days ?? 7 }),
206
+ latency: aggregateLatency(readJsonl(stateFile(stateRoot, 'bridge-latency.jsonl')), { now: opts.now, windowDays: opts.days ?? 7 }),
219
207
  ledger: presenceOf(stateFile(stateRoot, 'gotry-state.db')),
220
- doctorReport: presenceOf(stateFile(stateRoot, 'doctor-report.md'))
208
+ doctorReport: presenceOf(stateFile(stateRoot, 'doctor-report.md')),
221
209
  };
222
210
  }
223
211
  function fmtRate(blocked, total) {
224
- return total === 0 ? '—' : `${blocked}/${total}(${(blocked / total * 100).toFixed(0)}%)`;
212
+ return total === 0 ? '—' : `${blocked}/${total}(${((blocked / total) * 100).toFixed(0)}%)`;
225
213
  }
226
214
  function fmtBytes(bytes) {
227
- if (bytes === undefined) return '—';
215
+ if (bytes === undefined)
216
+ return '—';
228
217
  return bytes >= 1_048_576 ? `${(bytes / 1_048_576).toFixed(1)}MB` : `${bytes}B`;
229
218
  }
230
219
  export function renderMetricsReport(s) {
@@ -240,11 +229,12 @@ export function renderMetricsReport(s) {
240
229
  const kinds = Object.entries(s.facts.byKind);
241
230
  if (kinds.length === 0) {
242
231
  L.push('(空:无事实落盘)');
243
- } else {
232
+ }
233
+ else {
244
234
  L.push('| kind | 去重后 | verdict 分布 | blocked(error+needs-setup) |');
245
235
  L.push('|---|---|---|---|');
246
- for (const [kind, st] of kinds){
247
- const dist = Object.entries(st.verdicts).map(([v, n])=>`${v}:${n}`).join(' / ');
236
+ for (const [kind, st] of kinds) {
237
+ const dist = Object.entries(st.verdicts).map(([v, n]) => `${v}:${n}`).join(' / ');
248
238
  L.push(`| ${kind} | ${st.total} | ${dist} | ${fmtRate(st.blocked, st.total)} |`);
249
239
  }
250
240
  L.push('');
@@ -255,10 +245,11 @@ export function renderMetricsReport(s) {
255
245
  L.push('');
256
246
  if (s.channels.latest.length === 0) {
257
247
  L.push('(空:窗口内无 down/cooldown 事件)');
258
- } else {
248
+ }
249
+ else {
259
250
  L.push('| 通道 | 最新状态 | 原因 | 时间 | 窗口事件 |');
260
251
  L.push('|---|---|---|---|---|');
261
- for (const c of s.channels.latest){
252
+ for (const c of s.channels.latest) {
262
253
  L.push(`| ${c.channel} | ${c.state} | ${bounded(c.reason ?? '', 60) ?? '—'} | ${c.at ?? '—'} | ${c.events} |`);
263
254
  }
264
255
  }
@@ -268,13 +259,14 @@ export function renderMetricsReport(s) {
268
259
  const incidentKinds = Object.entries(s.incidents.byKind);
269
260
  if (incidentKinds.length === 0) {
270
261
  L.push('(空:窗口内无事故)');
271
- } else {
262
+ }
263
+ else {
272
264
  L.push('| kind | 次数 | 最近一次 |');
273
265
  L.push('|---|---|---|');
274
- for (const [kind, st] of incidentKinds){
266
+ for (const [kind, st] of incidentKinds) {
275
267
  L.push(`| ${kind} | ${st.count} | ${st.lastTs ?? '—'}${st.lastMessage ? ` · ${st.lastMessage}` : ''} |`);
276
268
  }
277
- const src = s.incidents.topSources.map((t)=>`${t.source}×${t.count}`).join(', ');
269
+ const src = s.incidents.topSources.map((t) => `${t.source}×${t.count}`).join(', ');
278
270
  if (src) {
279
271
  L.push('');
280
272
  L.push(`Top 来源:${src}`);
@@ -285,16 +277,18 @@ export function renderMetricsReport(s) {
285
277
  L.push('');
286
278
  if (s.latency.count === 0) {
287
279
  L.push('(空:窗口内无延迟记录)');
288
- } else {
289
- const kinds = s.latency.topKinds.map((t)=>`${t.kind}×${t.count}`).join(', ');
280
+ }
281
+ else {
282
+ const kinds = s.latency.topKinds.map((t) => `${t.kind}×${t.count}`).join(', ');
290
283
  L.push(`样本 ${s.latency.count}:p50=${s.latency.p50}ms · p95=${s.latency.p95}ms · max=${s.latency.max}ms · 超预算(>${s.latency.budgetMs}ms)= **${s.latency.overBudget}**`);
291
- if (kinds) L.push(`按 kind:${kinds}`);
284
+ if (kinds)
285
+ L.push(`按 kind:${kinds}`);
292
286
  }
293
287
  L.push('');
294
288
  L.push('## 账本与体检报告');
295
289
  L.push('');
296
- L.push(`- 账本 gotry-state.db:${s.ledger.exists ? `存在(${fmtBytes(s.ledger.bytes)},更新 ${s.ledger.mtime};明细经 \`npx gotry state\` 查看,指标面不打开 SQLite)` : '不存在(该 stateRoot 无账本)'}`);
297
- L.push(`- doctor 报告 doctor-report.md:${s.doctorReport.exists ? `存在(更新 ${s.doctorReport.mtime})` : '不存在(跑 `npx gotry doctor` 生成)'}`);
290
+ L.push(`- 账本 gotry-state.db:${s.ledger.exists ? `存在(${fmtBytes(s.ledger.bytes)},更新 ${s.ledger.mtime};明细经 \`npx @danceiny/gotry state\` 查看,指标面不打开 SQLite)` : '不存在(该 stateRoot 无账本)'}`);
291
+ L.push(`- doctor 报告 doctor-report.md:${s.doctorReport.exists ? `存在(更新 ${s.doctorReport.mtime})` : '不存在(跑 `npx @danceiny/gotry doctor` 生成)'}`);
298
292
  L.push('');
299
293
  L.push('## 评测三层入口(ADR-11;运行结果由 run-all-tests.sh / CI 承载,v1 不在此重跑)');
300
294
  L.push('');
@@ -303,37 +297,35 @@ export function renderMetricsReport(s) {
303
297
  L.push('');
304
298
  return L.join('\n');
305
299
  }
300
+ // ---- CLI ---------------------------------------------------------------------
306
301
  export function parseArgs(argv) {
307
302
  let stateRoot = '.';
308
303
  let out;
309
304
  let days = 7;
310
- for(let i = 0; i < argv.length; i += 1){
305
+ for (let i = 0; i < argv.length; i += 1) {
311
306
  const a = argv[i];
312
- if (a === '--state-root') stateRoot = argv[++i] ?? stateRoot;
313
- else if (a === '--out') out = argv[++i];
314
- else if (a === '--days') days = Math.max(1, Number.parseInt(argv[++i] ?? '7', 10) || 7);
307
+ if (a === '--state-root')
308
+ stateRoot = argv[++i] ?? stateRoot;
309
+ else if (a === '--out')
310
+ out = argv[++i];
311
+ else if (a === '--days')
312
+ days = Math.max(1, Number.parseInt(argv[++i] ?? '7', 10) || 7);
315
313
  }
316
- return {
317
- stateRoot: isAbsolute(stateRoot) ? stateRoot : resolve(stateRoot),
318
- out,
319
- days
320
- };
314
+ return { stateRoot: isAbsolute(stateRoot) ? stateRoot : resolve(stateRoot), out, days };
321
315
  }
322
- export async function main(argv) {
316
+ export async function main(argv, injectedNow) {
323
317
  const { stateRoot, out, days } = parseArgs(argv);
324
- const snapshot = await collectMetrics(stateRoot, {
325
- days
326
- });
318
+ const now = injectedNow ?? new Date();
319
+ const snapshot = await collectMetrics(stateRoot, { days, now });
327
320
  const markdown = renderMetricsReport(snapshot);
328
321
  if (out) {
329
322
  writeFileSync(out, markdown + '\n', 'utf-8');
330
323
  process.stdout.write(`指标报告已写 ${out}(stateRoot 只读未动)\n`);
331
- } else {
324
+ }
325
+ else {
332
326
  process.stdout.write(markdown + '\n');
333
327
  }
334
328
  }
335
329
  const invokedDirectly = process.argv[1] && import.meta.url === new URL(`file://${resolve(process.argv[1])}`).href;
336
- if (invokedDirectly) await main(process.argv.slice(2));
337
-
338
-
339
- //# sourceURL=ts/scripts/build-metrics-report.ts
330
+ if (invokedDirectly)
331
+ await main(process.argv.slice(2));
@@ -1,10 +1,21 @@
1
+ /**
2
+ * CHANGELOG 生成器合同测试(run-all §42;2026-08-30 owner 拍板补 changelog 机制)。
3
+ * 纯函数、零网络、零 git 调用:
4
+ * - Conventional Commits 解析:feat/fix/perf/refactor/docs/test/build/ci/chore/revert + scope + ! + PR 引用
5
+ * - 不符合 Conventional 的 commit → other 桶(不丢历史)
6
+ * - groupCommits 按 Keep a Changelog 显示顺序输出
7
+ * - renderMarkdownEntry 节结构(Added/Changed/Fixed/...)
8
+ * - prependEntry:不覆盖 CHANGELOG.md 历史段,只在顶部插入新版本段
9
+ * - splitChangelog:preamble/history 分隔正确
10
+ */
1
11
  import assert from 'node:assert/strict';
2
- import { buildEntry, groupCommits, parseCommit, prependEntry, renderMarkdownEntry, splitChangelog } from './build-changelog.js';
12
+ import { buildEntry, groupCommits, parseCommit, prependEntry, renderMarkdownEntry, splitChangelog, } from './build-changelog.js';
3
13
  let passed = 0;
4
14
  function ok(label) {
5
15
  passed += 1;
6
16
  console.log(` ok ${label}`);
7
17
  }
18
+ // ---- 1. parseCommit:Conventional 完整形态 ----
8
19
  const sample = parseCommit('feat(cost): 价表 provider-aware v2 + 价格漂移监测 (#68)');
9
20
  assert.ok(sample);
10
21
  assert.equal(sample.type, 'feat');
@@ -12,12 +23,14 @@ assert.equal(sample.scope, 'cost');
12
23
  assert.equal(sample.subject, '价表 provider-aware v2 + 价格漂移监测');
13
24
  assert.equal(sample.prRef, '68');
14
25
  ok('parseCommit feat(scope)! + #PR');
26
+ // ---- 2. parseCommit:不带 scope ----
15
27
  const noScope = parseCommit('fix: 修复登录页可见性');
16
28
  assert.equal(noScope.type, 'fix');
17
29
  assert.equal(noScope.scope, null);
18
30
  assert.equal(noScope.subject, '修复登录页可见性');
19
31
  assert.equal(noScope.prRef, null);
20
32
  ok('parseCommit fix 无 scope');
33
+ // ---- 3. parseCommit:不符合 Conventional → other 桶,保留原文 ----
21
34
  const other = parseCommit('docs: README 更新');
22
35
  assert.equal(other.type, 'docs');
23
36
  const legacy = parseCommit('feat(issue #21 P3.6): 会话扩展 onboarding UX 闭环(点点点 + 装完零重跑)');
@@ -27,53 +40,18 @@ const historical = parseCommit('Merge branch main into feat/x');
27
40
  assert.equal(historical.type, 'other');
28
41
  assert.equal(historical.subject, 'Merge branch main into feat/x');
29
42
  ok('parseCommit 历史 commit + 中文 subject + 无 #PR 兼容');
30
- for (const t of [
31
- 'perf',
32
- 'refactor',
33
- 'docs',
34
- 'test',
35
- 'build',
36
- 'ci',
37
- 'chore',
38
- 'revert'
39
- ]){
43
+ // ---- 4. parseCommit:perf/refactor/docs/test/build/ci/chore/revert 全覆盖 ----
44
+ for (const t of ['perf', 'refactor', 'docs', 'test', 'build', 'ci', 'chore', 'revert']) {
40
45
  const p = parseCommit(`${t}: 一些改动`);
41
46
  assert.equal(p.type, t);
42
47
  }
43
48
  ok('parseCommit 全 9 类型支持');
49
+ // ---- 5. groupCommits:Keep a Changelog 显示顺序 ----
44
50
  const commits = [
45
- {
46
- sha: 'aaa',
47
- type: 'fix',
48
- scope: null,
49
- subject: 'fix bug',
50
- prRef: '1',
51
- raw: ''
52
- },
53
- {
54
- sha: 'bbb',
55
- type: 'feat',
56
- scope: null,
57
- subject: 'add feature',
58
- prRef: '2',
59
- raw: ''
60
- },
61
- {
62
- sha: 'ccc',
63
- type: 'docs',
64
- scope: null,
65
- subject: 'update docs',
66
- prRef: null,
67
- raw: ''
68
- },
69
- {
70
- sha: 'ddd',
71
- type: 'other',
72
- scope: null,
73
- subject: 'random commit',
74
- prRef: null,
75
- raw: ''
76
- }
51
+ { sha: 'aaa', type: 'fix', scope: null, subject: 'fix bug', prRef: '1', raw: '' },
52
+ { sha: 'bbb', type: 'feat', scope: null, subject: 'add feature', prRef: '2', raw: '' },
53
+ { sha: 'ccc', type: 'docs', scope: null, subject: 'update docs', prRef: null, raw: '' },
54
+ { sha: 'ddd', type: 'other', scope: null, subject: 'random commit', prRef: null, raw: '' },
77
55
  ];
78
56
  const grouped = groupCommits(commits);
79
57
  assert.equal(grouped.length, 4);
@@ -82,36 +60,13 @@ assert.equal(grouped[1].type, 'fix');
82
60
  assert.equal(grouped[2].type, 'docs');
83
61
  assert.equal(grouped[3].type, 'other');
84
62
  ok('groupCommits Keep a Changelog 显示顺序(Added → Fixed → Documentation → Other)');
85
- const entry = buildEntry({
86
- version: '0.0.1-rc.16',
87
- date: '2026-08-30',
88
- commits: [
89
- {
90
- sha: 'aaa',
91
- type: 'feat',
92
- scope: null,
93
- subject: 'add a brand new feature with substantial description text',
94
- prRef: '2',
95
- raw: ''
96
- },
97
- {
98
- sha: 'bbb',
99
- type: 'fix',
100
- scope: null,
101
- subject: 'fix a critical bug in the rendering path of the parser',
102
- prRef: '1',
103
- raw: ''
104
- },
105
- {
106
- sha: 'ccc',
107
- type: 'docs',
108
- scope: null,
109
- subject: 'update the documentation for the new auth module',
110
- prRef: null,
111
- raw: ''
112
- }
113
- ]
114
- });
63
+ // ---- 6. renderMarkdownEntry:节结构 + PR 引用 + sha 后缀 ----
64
+ // 用较长 subject 避开 truncate 逻辑(短 subject 仍可能触发 breaker,断言用「包含」而非「完全等」)
65
+ const entry = buildEntry({ version: '0.0.1-rc.16', date: '2026-08-30', commits: [
66
+ { sha: 'aaa', type: 'feat', scope: null, subject: 'add a brand new feature with substantial description text', prRef: '2', raw: '' },
67
+ { sha: 'bbb', type: 'fix', scope: null, subject: 'fix a critical bug in the rendering path of the parser', prRef: '1', raw: '' },
68
+ { sha: 'ccc', type: 'docs', scope: null, subject: 'update the documentation for the new auth module', prRef: null, raw: '' },
69
+ ] });
115
70
  const md = renderMarkdownEntry(entry);
116
71
  assert.match(md, /^## \[0\.0\.1-rc\.16\] - 2026-08-30/);
117
72
  assert.match(md, /### Added/);
@@ -119,8 +74,9 @@ assert.match(md, /### Fixed/);
119
74
  assert.match(md, /### Documentation/);
120
75
  assert.match(md, /- add a brand new feature[^()]*\(#2\)/);
121
76
  assert.match(md, /- fix a critical bug[^()]*\(#1\)/);
122
- assert.match(md, /- update the documentation[^()]*\(ccc\)/);
77
+ assert.match(md, /- update the documentation[^()]*\(ccc\)/); // sha 后缀(无 PR)
123
78
  ok('renderMarkdownEntry 节结构 + PR/sha 后缀');
79
+ // ---- 7. prependEntry:不覆盖 CHANGELOG.md 历史段 ----
124
80
  const existing = `# Changelog
125
81
 
126
82
  All notable changes to this project will be documented in this file.
@@ -138,28 +94,25 @@ assert.match(next, /## \[0\.0\.1-rc\.16\] - 2026-08-30/);
138
94
  assert.match(next, /## \[0\.0\.1-rc\.15\] - 2026-08-29/);
139
95
  assert.match(next, /预订 saga 状态机/);
140
96
  ok('prependEntry:新版本段在顶部,历史段原样保留');
97
+ // ---- 8. prependEntry:空文件 → 仅生成 CHANGELOG header + 新段 ----
141
98
  const fromEmpty = prependEntry('', newMd);
142
99
  assert.match(fromEmpty, /Keep a Changelog/);
143
100
  assert.match(fromEmpty, /## \[0\.0\.1-rc\.16\]/);
144
101
  ok('prependEntry:空文件首次生成');
102
+ // ---- 9. splitChangelog:preamble 与 history 分割 ----
145
103
  const split = splitChangelog(existing);
146
104
  assert.ok(split.preamble.includes('All notable changes'));
147
105
  assert.ok(split.history[0].includes('## [0.0.1-rc.15]'));
148
106
  ok('splitChangelog preamble/history 分隔正确');
149
- const emptyEntry = buildEntry({
150
- version: '0.0.1-rc.16',
151
- date: '2026-08-30',
152
- commits: []
153
- });
107
+ // ---- 10. buildEntry 空 sections 不崩(空 commit 列表)----
108
+ const emptyEntry = buildEntry({ version: '0.0.1-rc.16', date: '2026-08-30', commits: [] });
154
109
  const emptyMd = renderMarkdownEntry(emptyEntry);
155
110
  assert.match(emptyMd, /## \[0\.0\.1-rc\.16\]/);
156
111
  ok('buildEntry 空 commit 列表 → 仍输出版本头(0 section,合规)');
112
+ // ---- 11. parseCommit:! 标记(breaking change)----
157
113
  const breaking = parseCommit('feat(api)!: 重命名端点 /v1/users → /v2/users (#99)');
158
114
  assert.equal(breaking.type, 'feat');
159
115
  assert.equal(breaking.subject, '重命名端点 /v1/users → /v2/users');
160
116
  assert.equal(breaking.prRef, '99');
161
117
  ok('parseCommit feat(scope)!: breaking change marker 容错');
162
118
  console.log(`changelog tests: ${passed} 组断言全绿(纯函数)`);
163
-
164
-
165
- //# sourceURL=ts/scripts/changelog-tests.ts