@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,8 +1,21 @@
1
+ /**
2
+ * 价格漂移监测合同测试(issue #49 长效机制,run-all §41)。
3
+ * 全确定性、零网络:
4
+ * - diff 算法守门:baseline 改/新/删均被检出;
5
+ * - offline 模式:无 baseline → skipped_no_baseline;有 baseline → unchanged(reason 提示 --fetch);
6
+ * - fetch 模式:fetch 失败 → skipped_no_network;parse 失败 → skipped_parse_error;baseline 不存在 →
7
+ * fetched_no_table_entry 且写 fixture;baseline 存在 + 完全相同 → unchanged;存在 + 不同 → drift;
8
+ * - 写入 baseline fixture 的 schema_version 守门;
9
+ * - PR 段落渲染:drift 含 model/field/from/to/direction 表格 + 纪律注脚。
10
+ * - diff 细节:字段 up/down/new/removed 四个方向全检。
11
+ *
12
+ * 网络例一律走注入的 fetcher / providers 路径,不真联网(真联网在 founder 手动 / CI 心跳)。
13
+ */
1
14
  import assert from 'node:assert/strict';
2
15
  import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
16
  import { join } from 'node:path';
4
17
  import { tmpdir } from 'node:os';
5
- import { runPriceDriftWatch, parseOpenaiHtml } from './price-drift-watch.js';
18
+ import { runPriceDriftWatch, parseOpenaiHtml, } from './price-drift-watch.js';
6
19
  let passed = 0;
7
20
  function ok(label) {
8
21
  passed += 1;
@@ -12,135 +25,113 @@ function tmpRoot() {
12
25
  return mkdtempSync(join(tmpdir(), 'drift-'));
13
26
  }
14
27
  function makeBaseline(rows, asOf = '2026-08-30T00:00:00.000Z') {
15
- return JSON.stringify({
16
- schema_version: 'gotry_llm_price_baseline_v1',
17
- as_of: asOf,
18
- source_url: 'mock://test',
19
- rows
20
- });
28
+ return JSON.stringify({ schema_version: 'gotry_llm_price_baseline_v1', as_of: asOf, source_url: 'mock://test', rows });
21
29
  }
30
+ /** 测试用注入 provider:baseline_path 指向 tmp 文件,parser 接受 mock HTML 字符串。 */
22
31
  function makeMockProvider(id, baselinePath, _fetchReturn) {
23
- const parser = (html)=>{
32
+ const parser = (html) => {
24
33
  const rows = [];
25
- for (const seg of html.split(';')){
34
+ for (const seg of html.split(';')) {
26
35
  const [model, nums] = seg.split(':');
27
- if (!model || !nums) continue;
36
+ if (!model || !nums)
37
+ continue;
28
38
  const [miss, hit, out] = nums.split(',').map(Number);
29
- if (!Number.isFinite(miss) || !Number.isFinite(out)) continue;
30
- rows.push({
31
- model: model.trim(),
32
- input_cache_miss_usd_per_1m: miss,
33
- input_cache_hit_usd_per_1m: Number.isFinite(hit) ? hit : null,
34
- output_usd_per_1m: out
35
- });
39
+ if (!Number.isFinite(miss) || !Number.isFinite(out))
40
+ continue;
41
+ rows.push({ model: model.trim(), input_cache_miss_usd_per_1m: miss, input_cache_hit_usd_per_1m: Number.isFinite(hit) ? hit : null, output_usd_per_1m: out });
36
42
  }
37
43
  return rows;
38
44
  };
39
- return [
40
- {
45
+ return [{
41
46
  id,
42
47
  source_url: `mock://${id}`,
43
48
  baseline_path: baselinePath,
44
- parse: parser
45
- }
46
- ];
49
+ parse: parser,
50
+ }];
47
51
  }
48
52
  function fetcherFromMap(map) {
49
- return async (url)=>{
53
+ return async (url) => {
50
54
  const v = map[url];
51
- if (v instanceof Error) throw v;
52
- if (typeof v === 'string') return v;
55
+ if (v instanceof Error)
56
+ throw v;
57
+ if (typeof v === 'string')
58
+ return v;
53
59
  throw new Error(`no mock for ${url}`);
54
60
  };
55
61
  }
62
+ // ---- 1. offline 无 baseline → skipped_no_baseline ----
56
63
  async function testOfflineNoBaseline() {
57
64
  const root = tmpRoot();
58
65
  try {
59
66
  const report = await runPriceDriftWatch({
60
67
  mode: 'offline',
61
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
62
- providers: makeMockProvider('mockprov', join(root, 'no-such-file.json'), '')
68
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
69
+ providers: makeMockProvider('mockprov', join(root, 'no-such-file.json'), ''),
63
70
  });
64
71
  assert.equal(report.schema_version, 'gotry_llm_price_drift_v1');
65
72
  assert.equal(report.providers['mockprov'].status, 'skipped_no_baseline');
66
73
  ok('offline 无 baseline → skipped_no_baseline');
67
- } finally{
68
- rmSync(root, {
69
- recursive: true,
70
- force: true
71
- });
74
+ }
75
+ finally {
76
+ rmSync(root, { recursive: true, force: true });
72
77
  }
73
78
  }
79
+ // ---- 2. offline 有 baseline → unchanged(reason 提示 --fetch) ----
74
80
  async function testOfflineWithBaseline() {
75
81
  const root = tmpRoot();
76
82
  try {
77
83
  const bp = join(root, 'b.json');
78
84
  writeFileSync(bp, makeBaseline([
79
- {
80
- model: 'm1',
81
- input_cache_miss_usd_per_1m: 1.0,
82
- input_cache_hit_usd_per_1m: 0.1,
83
- output_usd_per_1m: 3.0
84
- }
85
+ { model: 'm1', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
85
86
  ]));
86
87
  const report = await runPriceDriftWatch({
87
88
  mode: 'offline',
88
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
89
- providers: makeMockProvider('mockprov', bp, '')
89
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
90
+ providers: makeMockProvider('mockprov', bp, ''),
90
91
  });
91
92
  assert.equal(report.providers['mockprov'].status, 'unchanged');
92
93
  assert.match(report.providers['mockprov'].reason ?? '', /--fetch/, 'offline reason 应提示 --fetch');
93
94
  ok('offline 有 baseline → unchanged + reason 提示 --fetch');
94
- } finally{
95
- rmSync(root, {
96
- recursive: true,
97
- force: true
98
- });
95
+ }
96
+ finally {
97
+ rmSync(root, { recursive: true, force: true });
99
98
  }
100
99
  }
100
+ // ---- 3. fetch 模式:fet 失败 → skipped_no_network ----
101
101
  async function testFetchFailure() {
102
102
  const root = tmpRoot();
103
103
  try {
104
104
  const bp = join(root, 'b.json');
105
105
  writeFileSync(bp, makeBaseline([
106
- {
107
- model: 'm1',
108
- input_cache_miss_usd_per_1m: 1.0,
109
- input_cache_hit_usd_per_1m: 0.1,
110
- output_usd_per_1m: 3.0
111
- }
106
+ { model: 'm1', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
112
107
  ]));
113
108
  const report = await runPriceDriftWatch({
114
109
  mode: 'fetch',
115
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
110
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
116
111
  providers: makeMockProvider('mockprov', bp, ''),
117
- fetcher: fetcherFromMap({
118
- 'mock://mockprov': new Error('ECONNREFUSED')
119
- })
112
+ fetcher: fetcherFromMap({ 'mock://mockprov': new Error('ECONNREFUSED') }),
120
113
  });
121
114
  assert.equal(report.providers['mockprov'].status, 'skipped_no_network');
122
115
  assert.match(report.providers['mockprov'].reason ?? '', /ECONNREFUSED/);
123
116
  ok('fetch 失败 → skipped_no_network');
124
- } finally{
125
- rmSync(root, {
126
- recursive: true,
127
- force: true
128
- });
117
+ }
118
+ finally {
119
+ rmSync(root, { recursive: true, force: true });
129
120
  }
130
121
  }
122
+ // ---- 4. fetch 模式:首次 fetch 无 baseline → fetched_no_table_entry 且写 fixture ----
131
123
  async function testFetchFirstTimeWritesBaseline() {
132
124
  const root = tmpRoot();
133
125
  try {
134
126
  const bp = join(root, 'b.json');
135
127
  const report = await runPriceDriftWatch({
136
128
  mode: 'fetch',
137
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
129
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
138
130
  providers: makeMockProvider('mockprov', bp, ''),
139
- fetcher: fetcherFromMap({
140
- 'mock://mockprov': 'm1:1.0,0.1,3.0;m2:2.0,0.2,5.0'
141
- })
131
+ fetcher: fetcherFromMap({ 'mock://mockprov': 'm1:1.0,0.1,3.0;m2:2.0,0.2,5.0' }),
142
132
  });
143
133
  assert.equal(report.providers['mockprov'].status, 'fetched_no_table_entry');
134
+ // 必须有 fixture 文件
144
135
  const { existsSync, readFileSync } = await import('node:fs');
145
136
  assert.ok(existsSync(bp), 'baseline fixture 必须被写');
146
137
  const written = JSON.parse(readFileSync(bp, 'utf8'));
@@ -148,155 +139,115 @@ async function testFetchFirstTimeWritesBaseline() {
148
139
  assert.ok(Array.isArray(written['rows']));
149
140
  assert.equal(written['rows'].length, 2);
150
141
  ok('fetch 首次 → fetched_no_table_entry + 写 fixture');
151
- } finally{
152
- rmSync(root, {
153
- recursive: true,
154
- force: true
155
- });
142
+ }
143
+ finally {
144
+ rmSync(root, { recursive: true, force: true });
156
145
  }
157
146
  }
147
+ // ---- 5. fetch 模式:baseline + current 完全相同 → unchanged ----
158
148
  async function testFetchUnchanged() {
159
149
  const root = tmpRoot();
160
150
  try {
161
151
  const bp = join(root, 'b.json');
162
152
  writeFileSync(bp, makeBaseline([
163
- {
164
- model: 'm1',
165
- input_cache_miss_usd_per_1m: 1.0,
166
- input_cache_hit_usd_per_1m: 0.1,
167
- output_usd_per_1m: 3.0
168
- }
153
+ { model: 'm1', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
169
154
  ]));
170
155
  const report = await runPriceDriftWatch({
171
156
  mode: 'fetch',
172
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
157
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
173
158
  providers: makeMockProvider('mockprov', bp, ''),
174
- fetcher: fetcherFromMap({
175
- 'mock://mockprov': 'm1:1.0,0.1,3.0'
176
- })
159
+ fetcher: fetcherFromMap({ 'mock://mockprov': 'm1:1.0,0.1,3.0' }),
177
160
  });
178
161
  assert.equal(report.providers['mockprov'].status, 'unchanged');
179
162
  ok('fetch 模式:baseline + current 同 → unchanged');
180
- } finally{
181
- rmSync(root, {
182
- recursive: true,
183
- force: true
184
- });
163
+ }
164
+ finally {
165
+ rmSync(root, { recursive: true, force: true });
185
166
  }
186
167
  }
168
+ // ---- 6. fetch 模式:baseline + current 不同 → drift + 字段方向 ----
187
169
  async function testFetchDrift() {
188
170
  const root = tmpRoot();
189
171
  try {
190
172
  const bp = join(root, 'b.json');
191
173
  writeFileSync(bp, makeBaseline([
192
- {
193
- model: 'm-keep',
194
- input_cache_miss_usd_per_1m: 1.0,
195
- input_cache_hit_usd_per_1m: 0.1,
196
- output_usd_per_1m: 3.0
197
- },
198
- {
199
- model: 'm-up',
200
- input_cache_miss_usd_per_1m: 1.0,
201
- input_cache_hit_usd_per_1m: 0.1,
202
- output_usd_per_1m: 3.0
203
- },
204
- {
205
- model: 'm-down',
206
- input_cache_miss_usd_per_1m: 1.0,
207
- input_cache_hit_usd_per_1m: 0.1,
208
- output_usd_per_1m: 3.0
209
- },
210
- {
211
- model: 'm-removed',
212
- input_cache_miss_usd_per_1m: 1.0,
213
- input_cache_hit_usd_per_1m: 0.1,
214
- output_usd_per_1m: 3.0
215
- }
174
+ { model: 'm-keep', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
175
+ { model: 'm-up', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
176
+ { model: 'm-down', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
177
+ { model: 'm-removed', input_cache_miss_usd_per_1m: 1.0, input_cache_hit_usd_per_1m: 0.1, output_usd_per_1m: 3.0 },
216
178
  ]));
217
179
  const report = await runPriceDriftWatch({
218
180
  mode: 'fetch',
219
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
181
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
220
182
  providers: makeMockProvider('mockprov', bp, ''),
221
183
  fetcher: fetcherFromMap({
222
184
  'mock://mockprov': [
223
185
  'm-keep:1.0,0.1,3.0',
224
- 'm-up:1.5,0.1,3.0',
225
- 'm-down:0.8,0.1,3.0',
226
- 'm-new:2.0,0.2,4.0'
227
- ].join(';')
228
- })
186
+ 'm-up:1.5,0.1,3.0', // miss 1.0→1.5 up
187
+ 'm-down:0.8,0.1,3.0', // miss 1.0→0.8 down
188
+ 'm-new:2.0,0.2,4.0', // 全 new
189
+ // m-removed 不在 current,标记 removed
190
+ ].join(';'),
191
+ }),
229
192
  });
230
193
  const r = report.providers['mockprov'];
231
194
  assert.equal(r.status, 'drift');
232
195
  const drift = r.drift ?? [];
233
- const fields = drift.map((d)=>`${d.model}|${d.field}|${d.direction}`).sort();
196
+ // 期望:m-up input_cache_miss up, m-down input_cache_miss down, m-new input_cache_miss new, m-removed input_cache_miss removed
197
+ const fields = drift.map(d => `${d.model}|${d.field}|${d.direction}`).sort();
234
198
  assert.deepEqual(fields, [
235
199
  'm-down|input_cache_miss_usd_per_1m|down',
236
200
  'm-new|input_cache_miss_usd_per_1m|new',
237
201
  'm-removed|input_cache_miss_usd_per_1m|removed',
238
- 'm-up|input_cache_miss_usd_per_1m|up'
202
+ 'm-up|input_cache_miss_usd_per_1m|up',
239
203
  ], 'drift 字段方向必须逐项正确');
240
204
  ok('fetch drift:up/down/new/removed 四向全检');
241
- } finally{
242
- rmSync(root, {
243
- recursive: true,
244
- force: true
245
- });
205
+ }
206
+ finally {
207
+ rmSync(root, { recursive: true, force: true });
246
208
  }
247
209
  }
210
+ // ---- 7. PR 段落渲染:drift 时含表格 + 纪律注脚 ----
248
211
  async function testPrParagraph() {
249
212
  const root = tmpRoot();
250
213
  try {
251
214
  const bp = join(root, 'b.json');
252
215
  writeFileSync(bp, makeBaseline([
253
- {
254
- model: 'deepseek-v4-flash',
255
- input_cache_miss_usd_per_1m: 1.32,
256
- input_cache_hit_usd_per_1m: 0.014,
257
- output_usd_per_1m: 3.0
258
- }
216
+ { model: 'deepseek-v4-flash', input_cache_miss_usd_per_1m: 1.32, input_cache_hit_usd_per_1m: 0.014, output_usd_per_1m: 3.0 },
259
217
  ]));
260
218
  const report = await runPriceDriftWatch({
261
219
  mode: 'fetch',
262
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
220
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
263
221
  providers: makeMockProvider('mockprov', bp, ''),
264
- fetcher: fetcherFromMap({
265
- 'mock://mockprov': 'deepseek-v4-flash:1.32,0.014,3.0'
266
- })
222
+ fetcher: fetcherFromMap({ 'mock://mockprov': 'deepseek-v4-flash:1.32,0.014,3.0' }),
267
223
  });
268
224
  assert.equal(report.providers['mockprov'].status, 'unchanged');
269
225
  assert.match(report.suggested_pr_paragraph, /价格漂移监测/);
270
226
  assert.match(report.suggested_pr_paragraph, /mockprov/);
271
227
  assert.match(report.suggested_pr_paragraph, /人工 PR/);
272
228
  ok('PR 段落含监测标题 + provider 变动 + 纪律注脚');
273
- } finally{
274
- rmSync(root, {
275
- recursive: true,
276
- force: true
277
- });
229
+ }
230
+ finally {
231
+ rmSync(root, { recursive: true, force: true });
278
232
  }
279
233
  }
234
+ // ---- 8. baseline schema_version 守门:不匹配 schema 视为不存在(loader 容错) ----
280
235
  async function testBadBaseline() {
281
236
  const root = tmpRoot();
282
237
  try {
283
238
  const bp = join(root, 'b.json');
284
- writeFileSync(bp, JSON.stringify({
285
- schema_version: 'gotry_llm_price_baseline_v99',
286
- rows: []
287
- }));
239
+ writeFileSync(bp, JSON.stringify({ schema_version: 'gotry_llm_price_baseline_v99', rows: [] }));
240
+ // 离线模式:坏 baseline 视为不存在 → skipped_no_baseline
288
241
  const report = await runPriceDriftWatch({
289
242
  mode: 'offline',
290
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
291
- providers: makeMockProvider('mockprov', bp, '')
243
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
244
+ providers: makeMockProvider('mockprov', bp, ''),
292
245
  });
293
246
  assert.equal(report.providers['mockprov'].status, 'skipped_no_baseline', '坏 baseline schema_version 视为不存在,offline 标 SKIP');
294
247
  ok('坏 baseline schema_version → offline 视为不存在(SKIP)');
295
- } finally{
296
- rmSync(root, {
297
- recursive: true,
298
- force: true
299
- });
248
+ }
249
+ finally {
250
+ rmSync(root, { recursive: true, force: true });
300
251
  }
301
252
  }
302
253
  await testOfflineNoBaseline();
@@ -307,135 +258,83 @@ await testFetchUnchanged();
307
258
  await testFetchDrift();
308
259
  await testPrParagraph();
309
260
  await testBadBaseline();
261
+ // ---- 9. snapshot 路径:parser 解析 __NEXT_DATA__ 内嵌 JSON dump,fallback 解析段落文字 ----
310
262
  async function testSnapshotNextDataParser() {
311
263
  const root = tmpRoot();
312
264
  try {
313
265
  const bp = join(root, 'b.json');
314
266
  const sp = join(root, 'snapshot.html');
315
267
  writeFileSync(bp, makeBaseline([
316
- {
317
- model: 'gpt-4o',
318
- input_cache_miss_usd_per_1m: 2.5,
319
- input_cache_hit_usd_per_1m: 1.25,
320
- output_usd_per_1m: 10.0
321
- }
268
+ { model: 'gpt-4o', input_cache_miss_usd_per_1m: 2.5, input_cache_hit_usd_per_1m: 1.25, output_usd_per_1m: 10.0 },
322
269
  ]));
270
+ // 真实 snapshot 文件模拟 __NEXT_DATA__ 形态
323
271
  const html = `<html><script id="__NEXT_DATA__" type="application/json">${JSON.stringify({
324
- props: {
325
- pageProps: {
326
- models: [
327
- {
328
- name: 'gpt-4o',
329
- input_cost_per_token: 0.0000025,
330
- output_cost_per_token: 0.00001,
331
- cached_input_cost_per_token: 0.00000125
332
- }
333
- ]
334
- }
335
- }
272
+ props: { pageProps: { models: [
273
+ { name: 'gpt-4o', input_cost_per_token: 0.0000025, output_cost_per_token: 0.00001, cached_input_cost_per_token: 0.00000125 },
274
+ ] } },
336
275
  })}</script></html>`;
337
276
  writeFileSync(sp, html);
338
277
  const report = await runPriceDriftWatch({
339
278
  mode: 'offline',
340
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
341
- providers: [
342
- {
343
- id: 'mockprov',
344
- source_url: 'mock://mockprov',
345
- baseline_path: bp,
346
- snapshot_path: sp,
347
- parse: parseOpenaiHtml
348
- }
349
- ]
279
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
280
+ providers: [{ id: 'mockprov', source_url: 'mock://mockprov', baseline_path: bp, snapshot_path: sp, parse: parseOpenaiHtml }],
350
281
  });
351
282
  assert.equal(report.providers['mockprov'].status, 'unchanged', 'snapshot 与 baseline 同价 → unchanged(offline)');
352
283
  ok('snapshot 路径:__NEXT_DATA__ 内嵌 JSON dump 解析成功 + 与 baseline 同 → unchanged');
353
- } finally{
354
- rmSync(root, {
355
- recursive: true,
356
- force: true
357
- });
284
+ }
285
+ finally {
286
+ rmSync(root, { recursive: true, force: true });
358
287
  }
359
288
  }
289
+ // ---- 10. snapshot 路径:parser fallback 段落文字;baseline 不一致 → drift ----
360
290
  async function testSnapshotFallbackDrift() {
361
291
  const root = tmpRoot();
362
292
  try {
363
293
  const bp = join(root, 'b.json');
364
294
  const sp = join(root, 'snapshot.html');
365
295
  writeFileSync(bp, makeBaseline([
366
- {
367
- model: 'gpt-4o',
368
- input_cache_miss_usd_per_1m: 2.5,
369
- input_cache_hit_usd_per_1m: 1.25,
370
- output_usd_per_1m: 10.0
371
- }
296
+ { model: 'gpt-4o', input_cache_miss_usd_per_1m: 2.5, input_cache_hit_usd_per_1m: 1.25, output_usd_per_1m: 10.0 },
372
297
  ]));
298
+ // __NEXT_DATA__ 解析失败(空 props),parser fallback 走段落文字
373
299
  const html = '<html><body><p>gpt-4o … $5.00 / 1M input … $20.00 / 1M output</p></body></html>';
374
300
  writeFileSync(sp, html);
375
301
  const report = await runPriceDriftWatch({
376
302
  mode: 'offline',
377
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
378
- providers: [
379
- {
380
- id: 'mockprov',
381
- source_url: 'mock://mockprov',
382
- baseline_path: bp,
383
- snapshot_path: sp,
384
- parse: parseOpenaiHtml
385
- }
386
- ]
303
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
304
+ providers: [{ id: 'mockprov', source_url: 'mock://mockprov', baseline_path: bp, snapshot_path: sp, parse: parseOpenaiHtml }],
387
305
  });
388
306
  const r = report.providers['mockprov'];
389
307
  assert.equal(r.status, 'drift', 'snapshot fallback 解析 + baseline 不一致 → drift');
390
308
  const drift = r.drift ?? [];
391
- assert.ok(drift.some((d)=>d.field === 'input_cache_miss_usd_per_1m' && d.from === 2.5 && d.to === 5.0 && d.direction === 'up'), 'miss 字段 up drift');
392
- assert.ok(drift.some((d)=>d.field === 'output_usd_per_1m' && d.from === 10.0 && d.to === 20.0 && d.direction === 'up'), 'output 字段 up drift');
309
+ assert.ok(drift.some(d => d.field === 'input_cache_miss_usd_per_1m' && d.from === 2.5 && d.to === 5.0 && d.direction === 'up'), 'miss 字段 up drift');
310
+ assert.ok(drift.some(d => d.field === 'output_usd_per_1m' && d.from === 10.0 && d.to === 20.0 && d.direction === 'up'), 'output 字段 up drift');
393
311
  ok('snapshot fallback:段落文字解析 + 与 baseline 不同 → drift(含 up/down/new/removed)');
394
- } finally{
395
- rmSync(root, {
396
- recursive: true,
397
- force: true
398
- });
312
+ }
313
+ finally {
314
+ rmSync(root, { recursive: true, force: true });
399
315
  }
400
316
  }
317
+ // ---- 11. snapshot 缺失守门:有 baseline 但无 snapshot → skipped_no_snapshot ----
401
318
  async function testSnapshotMissing() {
402
319
  const root = tmpRoot();
403
320
  try {
404
321
  const bp = join(root, 'b.json');
405
322
  writeFileSync(bp, makeBaseline([
406
- {
407
- model: 'gpt-4o',
408
- input_cache_miss_usd_per_1m: 2.5,
409
- input_cache_hit_usd_per_1m: 1.25,
410
- output_usd_per_1m: 10.0
411
- }
323
+ { model: 'gpt-4o', input_cache_miss_usd_per_1m: 2.5, input_cache_hit_usd_per_1m: 1.25, output_usd_per_1m: 10.0 },
412
324
  ]));
413
325
  const report = await runPriceDriftWatch({
414
326
  mode: 'offline',
415
- observedAt: ()=>new Date('2026-08-30T12:00:00Z'),
416
- providers: [
417
- {
418
- id: 'mockprov',
419
- source_url: 'mock://mockprov',
420
- baseline_path: bp,
421
- snapshot_path: join(root, 'no-such-snapshot.html'),
422
- parse: parseOpenaiHtml
423
- }
424
- ]
327
+ observedAt: () => new Date('2026-08-30T12:00:00Z'),
328
+ providers: [{ id: 'mockprov', source_url: 'mock://mockprov', baseline_path: bp, snapshot_path: join(root, 'no-such-snapshot.html'), parse: parseOpenaiHtml }],
425
329
  });
426
330
  assert.equal(report.providers['mockprov'].status, 'skipped_no_snapshot', 'snapshot 缺失 → SKIP');
427
331
  ok('snapshot 缺失 → skipped_no_snapshot(offline)');
428
- } finally{
429
- rmSync(root, {
430
- recursive: true,
431
- force: true
432
- });
332
+ }
333
+ finally {
334
+ rmSync(root, { recursive: true, force: true });
433
335
  }
434
336
  }
435
337
  await testSnapshotNextDataParser();
436
338
  await testSnapshotFallbackDrift();
437
339
  await testSnapshotMissing();
438
340
  console.log(`price-drift-watch tests: ${passed} 组断言全绿(offline,fetch 模式手动/CI 心跳)`);
439
-
440
-
441
- //# sourceURL=ts/scripts/price-drift-tests.ts