@whyshu/argent-engine 0.0.0-stage → 0.6.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 (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 版本一致性门禁(0.4.0 起)—— 「**活引用 == 唯一真源**」的可执行形式。
4
+ *
5
+ * ## 为什么有它
6
+ * 平台版本此前**只出现在 `CHANGELOG.md` / `VERSION` / 版本规则文档**三处,靠人记着同批改。
7
+ * 0.4.0 起 Argent 从"能力层"重定位为**智能体平台**(版本语义见
8
+ * `docs/Argent-宿主适配契约.md` §8),版本会被**宿主**、**安装器**、**业务线兼容区间**、
9
+ * **MCP 握手**分别读走 ⇒ "改一处、漏一处"的漂移代价从"文档不一致"升级为
10
+ * **"装得上但跑不起来" / "页面说版本不满足而实际能跑"**(后者真发生过,见
11
+ * `docs/Argent-工作台口径审计-2026-10-06.md` 的 P0-5)。故把判据固化。
12
+ *
13
+ * ## 判据(每条都点名 文件:行)
14
+ * R0-TRUTH 唯一真源存在且是 **semver**(缺 / 非 semver ⇒ 红)
15
+ * R1-LOCKSTEP **活引用**逐条等于真源(清单见 `LIVE_REFS`,⛔ 不写第二份清单)
16
+ * R2-RUNTIME **跑起来也一致**:CLI `version` / CLI `versions` / MCP `initialize.serverInfo`
17
+ * R3-CARRIER 版本**载体**必须"从 `VERSION` 读",⛔ 不许写死(结构性断言)
18
+ * R4-SCAN **反向**:活引用面里**新写死一个别的版本** ⇒ 红(这才是"有牙"的那一半)
19
+ * R5-CHANGELOG 最新一段 `## X.Y.Z` = 真源;更旧的段按定义历史(**降级为 info,不误判**)
20
+ * R6-DSH-LOCK `dsh-version-lock/**`(宿主依赖版本,**另一条轴**)产出 **0 条**发现
21
+ * R7-HISTDOC `docs/H1-…` 必须带「本文实测时 Argent 平台版本 = …」标注;带标注 ⇒ 其历史值豁免
22
+ * R8-COVERAGE 扫描面**真扫到了东西**(防"永远返回 0 违规的扫描器也能绿")
23
+ *
24
+ * ## 为什么 R4 只扫"活引用面"(`core/ scripts/ plugins/ presets/ verticals/ deploy/ configs/` + `VERSION`)
25
+ * `docs/**` 与 `CHANGELOG.md` **按定义是历史记录面**:里面大量 `0.3.0` 是**当轮实测值 / 沿革**,
26
+ * 把它们拉进红判据 = **恒红陷阱**(实测:全仓扫描会出 63 条,63 条全是合法历史)。
27
+ * ⇒ 活引用面**判红**,历史面**按规则豁免**(R5/R6/R7 各给证据),这正是本文件头的分工。
28
+ *
29
+ * ## 用法
30
+ * node scripts/check-version-consistency.mjs [--root <dir>] [--json] [--strict] [--no-runtime]
31
+ * 退出码:0 = 无 error(**默认只报告**,与 `check-docs-drift` / `check-pack-closure` 同档)
32
+ * 1 = 有 error(**只有 `--strict` 才非零退出**)
33
+ * 2 = 用法 / 真源缺失(**恒非零**:拿不到真源就无法判定,⛔ 不许静默成功)
34
+ *
35
+ * 回归与**有牙证明**:`scripts/tests/version-consistency.test.mjs`(真仓 0 error +
36
+ * 注入"写死别的版本"必须变红 + 历史文档不被误判 + 真源缺失/非 semver 必须红)。
37
+ */
38
+ import { execFileSync, spawn } from 'node:child_process'
39
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
40
+ import { dirname, extname, join, relative, resolve, sep } from 'node:path'
41
+ import { fileURLToPath } from 'node:url'
42
+
43
+ const HERE = dirname(fileURLToPath(import.meta.url))
44
+ const SELF = fileURLToPath(import.meta.url)
45
+ const SELF_ROOT = resolve(HERE, '..')
46
+
47
+ // ── 扫描面(**活引用面**;历史记录面见文件头 R4 说明)──────────────────────────
48
+ const SURFACE = ['VERSION', 'core', 'scripts', 'plugins', 'presets', 'verticals', 'deploy', 'configs']
49
+ const SKIP_DIR = /(^|\/)(node_modules|\.git|archive)(\/|$)/
50
+ const TEXT_EXT = new Set(['.md', '.mjs', '.js', '.cjs', '.json', '.yml', '.yaml', '.sh', '.py', '.txt'])
51
+ const MAX_BYTES = 3_000_000
52
+
53
+ /** 版本**载体**:这些文件的版本必须来自 `VERSION`,⛔ 不许写死。 */
54
+ const CARRIERS = [
55
+ { file: 'core/cli/argent.mjs', why: 'CLI `argent version` 的唯一出口' },
56
+ { file: 'core/mcp/server.mjs', why: 'MCP `initialize.serverInfo.version` 的唯一出口' },
57
+ { file: 'plugins/argent-core/src/index.js', why: '设置页 / `/api/argent-state/version` 的版本展示' },
58
+ ]
59
+
60
+ /** 声明式**活引用清单**:每条 = 一个"必须等于真源"的落点(⛔ 不另抄第二份)。 */
61
+ const LIVE_REFS = [
62
+ { id: 'VERSION', file: 'VERSION', how: '整个文件 trim 后即真源本身' },
63
+ { id: 'contracts.argentVersion', file: 'core/contracts/argent.contracts.json', how: '契约自带版本,X/Y 位必须与 VERSION 同批一致' },
64
+ { id: 'deploy/mcp/README.md 标题', file: 'deploy/mcp/README.md', how: '部署 runbook 的首行标题必须写明现役平台版本' },
65
+ ]
66
+
67
+ /** 历史实测文档:**必须带"当时版本"标注**才豁免(标注本身就是豁免条件)。 */
68
+ const HIST_DOCS = ['docs/H1-第三方宿主采信-2026-10-06.md']
69
+ const ANNOTATION_RE = /本文实测时\s*Argent\s*平台版本\s*=\s*`?([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?)`?/
70
+
71
+ // ── 版本号识别(**轴**的区分是本门禁的核心,别把别的轴当成 Argent)──────────────
72
+ /** Argent 平台版本轴:`0.X.Y`。⛔ 能力包是 `1.x.y`、插件/角色/业务线是各自的小版本。 */
73
+ const ARGENT_TOK = /(?<![\d.])0\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?![\d.])/g
74
+ /** 哨兵值:`VERSION` 读不到时的占位(`core/mcp/server.mjs`),⛔ 不是版本声明。 */
75
+ const SENTINELS = new Set(['0.0.0'])
76
+ /**
77
+ * 「Argent 版本声明」语境 —— 只有命中才把这一行的版本字面量当**声明**看。
78
+ * ⚠️ 刻意**不含**裸 `version:`:插件包(`0.1.0`/`0.2.0`)、能力包、业务线版本都是**别的轴**,
79
+ * 认它会立刻退化成假红机器(详见 `docs/Argent-宿主适配契约.md` §8 的三级版本语义)。
80
+ */
81
+ const CONTEXT = [
82
+ [/argentVersion|ARGENT_VERSION/i, 'argentVersion'],
83
+ [/server[_-]?info/i, 'serverInfo'],
84
+ [/requires\.argent/, 'requires.argent'],
85
+ [/(?<![-\w])(?:Argent|argent)(?![-\w])[^\n]{0,16}?\d+\.\d+\.\d+/, 'Argent+版本'],
86
+ [/\d+\.\d+\.\d+[^\n]{0,16}?(?<![-\w])(?:Argent|argent)(?![-\w])/, '版本+Argent'],
87
+ [/(平台版本|产品版本|版本线|现役版本|当前版本|现行版本|Argent 平台)/, '版本声明词'],
88
+ [/["']argent["']\s*:/, '"argent":'],
89
+ ]
90
+ /** 行内**历史语境**:命中 ⇒ 该处是沿革/实测记录 ⇒ 降级 info(与 `check-docs-drift` 的 R1/R3 同法)。 */
91
+ const HISTORY = /历史|归档|退役|旧版|旧名|旧行为|曾经|曾|当年|当轮|实测|修复|血泪|不再维护|前身|遗留|迁移|沿革|起强制|verifiedOn|由\s*`?>=/
92
+
93
+ const isSemver = (s) => /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(String(s || ''))
94
+
95
+ /** npm 风格区间子集(仓库实际只用这几种):`>=A <B` / `>=A` / `<B` / `A`。缺位按 0 补。 */
96
+ function satisfies(version, range) {
97
+ const p = (s) => {
98
+ const m = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?$/.exec(String(s).trim())
99
+ return m ? [Number(m[1]), Number(m[2] || 0), Number(m[3] || 0), m[4] || '~'] : null
100
+ }
101
+ const cmp = (a, b) => {
102
+ for (let i = 0; i < 3; i += 1) if (a[i] !== b[i]) return a[i] - b[i]
103
+ if (a[3] === b[3]) return 0
104
+ if (a[3] === '~') return 1 // 正式版 > 预发布
105
+ if (b[3] === '~') return -1
106
+ return a[3] < b[3] ? -1 : 1
107
+ }
108
+ const v = p(version)
109
+ if (!v) return false
110
+ const parts = String(range || '').trim().split(/\s+/).filter(Boolean)
111
+ if (!parts.length) return false
112
+ for (const part of parts) {
113
+ const m = /^(>=|<=|>|<|=)?\s*(.+)$/.exec(part)
114
+ if (!m) return false
115
+ const bound = p(m[2])
116
+ if (!bound) return false
117
+ const d = cmp(v, bound)
118
+ const op = m[1] || '='
119
+ const okOp = op === '>=' ? d >= 0 : op === '<=' ? d <= 0 : op === '>' ? d > 0 : op === '<' ? d < 0 : d === 0
120
+ if (!okOp) return false
121
+ }
122
+ return true
123
+ }
124
+
125
+ /** 该 token 是否只是**文件名/路径**的一部分(`docs/Argent-0.3.0-验收清单.md`)⇒ 不是版本声明。 */
126
+ function insideFilename(line, idx, tok) {
127
+ const after = line.slice(idx + tok.length)
128
+ if (/^[-\w]*\.(?:md|json|mjs|js|cjs|yml|yaml|sh|py|txt)\b/.test(after)) return true
129
+ const before = line.slice(0, idx)
130
+ return /[\w./-]$/.test(before) && /^[-\w]/.test(after)
131
+ }
132
+
133
+ function walkFiles(abs, root, out = []) {
134
+ let st
135
+ try { st = statSync(abs) } catch { return out }
136
+ if (st.isFile()) { out.push(abs); return out }
137
+ for (const e of readdirSync(abs, { withFileTypes: true })) {
138
+ const c = join(abs, e.name)
139
+ const rel = relative(root, c)
140
+ if (SKIP_DIR.test(rel)) continue
141
+ if (e.isDirectory()) walkFiles(c, root, out)
142
+ else if (TEXT_EXT.has(extname(e.name)) && statSync(c).size < MAX_BYTES) out.push(c)
143
+ }
144
+ return out
145
+ }
146
+
147
+ /** 只读:把一行按判据分类。返回 findings(不含 == 真源的 token)。 */
148
+ function scanLine({ rel, line, lineNo, truth, findings, info, whyText }) {
149
+ let filenameHit = false
150
+ for (const m of line.matchAll(ARGENT_TOK)) {
151
+ const tok = m[0]
152
+ // ① 别的轴:`0.1.x` 是**宿主(dsh)**轴 —— 仓库里 Argent 从未是 0.1.x
153
+ // (0.9.9 → 0.2.0 → 0.3.0 → 0.4.0),而 dsh 全是 0.1.x(快照 `dsh-version-lock/*.json`)。
154
+ if (m[1] === '1') continue
155
+ // ② 哨兵占位(读不到 VERSION 时的兜底),不是版本声明。
156
+ if (SENTINELS.has(tok)) continue
157
+ // ③ 宿主**预发布**轴:`0.x.y-rc.N` 且本行点名 dsh ⇒ 宿主版本(如桌面端内置 `0.2.0-rc.2`)。
158
+ if (/-rc\.\d+$/.test(tok) && /\bdsh\b/i.test(line)) continue
159
+ // ④ 文件名 / 路径里的版本号不是声明。
160
+ if (insideFilename(line, m.index, tok)) continue
161
+ // ⑤ 与真源一致 ⇒ 无需报告。
162
+ if (tok === truth) continue
163
+ const hit = CONTEXT.find(([re]) => re.test(line))
164
+ if (!hit) continue
165
+ const rec = { file: rel, line: lineNo, token: tok, context: hit[1], text: line.trim().slice(0, 140) }
166
+ if (HISTORY.test(line)) info.push({ ...rec, rule: 'R4-SCAN', why: '行内含历史语境(沿革/实测/已修)⇒ 降级为 info,不判红' })
167
+ else findings.push({ rule: 'R4-SCAN', file: rel, line: lineNo, token: tok,
168
+ message: `活引用面出现 Argent 版本字面量 \`${tok}\`(语境:${hit[1]}),与唯一真源 \`${truth}\` 不一致`,
169
+ why: whyText })
170
+ }
171
+ }
172
+
173
+ // ── 主入口 ──────────────────────────────────────────────────────────────────
174
+ export function run(opts = {}) {
175
+ const root = resolve(opts.root || SELF_ROOT)
176
+ const findings = []
177
+ const info = []
178
+ const err = (rule, file, line, message, why) => findings.push({ rule, file, line, token: null, message, why })
179
+
180
+ // ── R0-TRUTH ─────────────────────────────────────────────────────────────
181
+ const versionPath = join(root, 'VERSION')
182
+ if (!existsSync(versionPath)) {
183
+ err('R0-TRUTH', 'VERSION', 0, '唯一真源 `VERSION` 不存在 ⇒ 无法判定(⛔ 不静默成功)', '平台版本唯一真源 = 仓库根 `VERSION`')
184
+ return { ok: false, findings, info, truth: null, stats: { files: 0 } }
185
+ }
186
+ const truth = readFileSync(versionPath, 'utf8').trim()
187
+ if (!isSemver(truth)) {
188
+ err('R0-TRUTH', 'VERSION', 1, `唯一真源不是 semver:${JSON.stringify(truth)}`, '`VERSION` 必须是 X.Y.Z(可带 -rc.N)')
189
+ }
190
+
191
+ // ── R1-LOCKSTEP ──────────────────────────────────────────────────────────
192
+ for (const ref of LIVE_REFS) {
193
+ const abs = join(root, ref.file)
194
+ if (!existsSync(abs)) { err('R1-LOCKSTEP', ref.file, 0, `活引用声明的文件不存在(${ref.id})`, ref.how); continue }
195
+ const text = readFileSync(abs, 'utf8')
196
+ if (ref.id === 'VERSION') {
197
+ if (text.trim() !== truth) err('R1-LOCKSTEP', ref.file, 1, `活引用 ${ref.id} = ${JSON.stringify(text.trim())} ≠ 真源 ${truth}`, ref.how)
198
+ continue
199
+ }
200
+ if (ref.id === 'contracts.argentVersion') {
201
+ let j = null
202
+ try { j = JSON.parse(text) } catch { err('R1-LOCKSTEP', ref.file, 0, '契约文件不是合法 JSON', ref.how); continue }
203
+ // 契约里的 argentVersion 就是真源之一:X/Y 位必须与 VERSION 一致(§3.2「唯一出口」)
204
+ const [tx, ty] = truth.split(/[.-]/).map(Number)
205
+ const [cx, cy] = String(j.argentVersion || '').split(/[.-]/).map(Number)
206
+ if (!isSemver(j.argentVersion)) err('R1-LOCKSTEP', ref.file, 2, `argentVersion 不是 semver:${JSON.stringify(j.argentVersion)}`, ref.how)
207
+ else if (tx !== cx || ty !== cy) err('R1-LOCKSTEP', ref.file, 2, `argentVersion = ${j.argentVersion},与 VERSION ${truth} 的 X/Y 位不一致`, ref.how)
208
+ continue
209
+ }
210
+ // deploy/mcp/README.md:首行标题
211
+ const head = text.split('\n').find((l) => l.trim()) || ''
212
+ if (!head.includes(truth)) err('R1-LOCKSTEP', ref.file, 1, `标题未写明现役平台版本 ${truth}:${head.slice(0, 100)}`, ref.how)
213
+ }
214
+
215
+ // 业务线兼容区间(`verticals/*/manifest.json#requires.argent` 必须**容纳**真源)
216
+ const vdir = join(root, 'verticals')
217
+ const verticals = []
218
+ if (existsSync(vdir)) {
219
+ for (const e of readdirSync(vdir, { withFileTypes: true })) {
220
+ if (!e.isDirectory()) continue
221
+ const man = join(vdir, e.name, 'manifest.json')
222
+ if (!existsSync(man)) continue
223
+ let j = null
224
+ try { j = JSON.parse(readFileSync(man, 'utf8')) } catch { err('R1-LOCKSTEP', `verticals/${e.name}/manifest.json`, 0, 'manifest.json 不是合法 JSON', '业务线兼容区间必须可判'); continue }
225
+ const rel = `verticals/${e.name}/manifest.json`
226
+ const range = j.requires?.argent
227
+ if (!range) { err('R1-LOCKSTEP', rel, 0, '缺 `requires.argent` 兼容区间', '业务线必须声明兼容的 Argent 平台区间'); continue }
228
+ verticals.push({ id: j.id, version: j.version, range, ok: satisfies(truth, range) })
229
+ if (!satisfies(truth, range)) {
230
+ err('R1-LOCKSTEP', rel, 0, `平台版本 ${truth} **不落在**本线声明的兼容区间 \`${range}\` 内`,
231
+ '平台抬版必须同批抬兼容区间 —— 否则「Argent 能力 → 概览」会把能跑的业务线标成 `⚠️ 版本不满足`(P0-5 实测发生过)')
232
+ }
233
+ }
234
+ }
235
+
236
+ // ── R5-CHANGELOG(历史段豁免,但**最新段**必须等于真源)────────────────────
237
+ const clPath = join(root, 'CHANGELOG.md')
238
+ let changelog = { sections: [], newest: null }
239
+ if (!existsSync(clPath)) err('R5-CHANGELOG', 'CHANGELOG.md', 0, 'CHANGELOG.md 不存在', '发布记录是版本线的对外凭证')
240
+ else {
241
+ readFileSync(clPath, 'utf8').split('\n').forEach((line, i) => {
242
+ const m = /^##\s+(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(line)
243
+ if (m) changelog.sections.push({ version: m[1], line: i + 1 })
244
+ })
245
+ changelog.newest = changelog.sections[0]?.version || null
246
+ if (!changelog.sections.length) err('R5-CHANGELOG', 'CHANGELOG.md', 0, '没有任何 `## X.Y.Z` 段', '发布记录格式:`## X.Y.Z(日期)`')
247
+ else if (changelog.newest !== truth) {
248
+ err('R5-CHANGELOG', 'CHANGELOG.md', changelog.sections[0].line, `最新段是 \`${changelog.newest}\`,真源是 \`${truth}\` ⇒ 抬版没写发布记录`, '最新段 = 现役版本;更旧的段按定义是历史')
249
+ }
250
+ // 更旧的段:**按定义历史** ⇒ 显式登记为 info(证明"没被误判",⛔ 不是漏扫)
251
+ for (const s of changelog.sections.slice(1)) {
252
+ info.push({ rule: 'R5-CHANGELOG', file: 'CHANGELOG.md', line: s.line, token: s.version,
253
+ why: '发布记录的**历史段**(不是现行声明)⇒ 按定义豁免,⛔ 不追改' })
254
+ }
255
+ }
256
+
257
+ // ── R6-DSH-LOCK(宿主依赖版本是**另一条轴**,必须 0 发现)──────────────────
258
+ const lockDir = join(root, 'dsh-version-lock')
259
+ let lockFiles = []
260
+ if (existsSync(lockDir)) lockFiles = walkFiles(lockDir, root)
261
+ for (const f of lockFiles) {
262
+ const rel = relative(root, f)
263
+ const text = readFileSync(f, 'utf8')
264
+ for (const m of text.matchAll(/(?<![\d.])0\.(\d+)\.(\d+)(?:-rc\.(\d+))?(?![\d.])/g)) {
265
+ if (m[1] !== '1') info.push({ rule: 'R6-DSH-LOCK', file: rel, line: 0, token: m[0], why: '宿主快照里的版本(dsh 轴)⇒ 与 Argent 平台版本不同轴,登记为 info 以自证"未误判"' })
266
+ }
267
+ }
268
+
269
+ // ── R7-HISTDOC(历史实测文档须带"当时版本"标注)──────────────────────────
270
+ const histDocs = []
271
+ for (const rel of HIST_DOCS) {
272
+ const abs = join(root, rel)
273
+ if (!existsSync(abs)) { err('R7-HISTDOC', rel, 0, '声明的历史实测文档不存在', 'HIST_DOCS 清单过期'); continue }
274
+ const text = readFileSync(abs, 'utf8')
275
+ const m = ANNOTATION_RE.exec(text)
276
+ const lineNo = m ? text.slice(0, m.index).split('\n').length : 0
277
+ if (!m) {
278
+ err('R7-HISTDOC', rel, 0, '历史实测文档**缺**「本文实测时 Argent 平台版本 = X.Y.Z」标注',
279
+ '标注既是"这是当轮证据"的声明,也是本文件豁免它历史值的**条件**(⛔ 不追改实测值)')
280
+ } else {
281
+ histDocs.push({ file: rel, line: lineNo, annotated: m[1] })
282
+ // 证据面 = **标注行以外**的正文(标注自己写着那个版本,不去掉就会自证 ⇒ 假绿)。
283
+ const body = text.slice(0, m.index) + text.slice(m.index + m[0].length)
284
+ const toks = [...body.matchAll(ARGENT_TOK)].map((x) => x[0]).filter((t) => !SENTINELS.has(t))
285
+ const others = [...new Set(toks.filter((t) => t !== truth))]
286
+ if (m[1] !== truth && !others.includes(m[1])) {
287
+ err('R7-HISTDOC', rel, lineNo, `标注写的版本 \`${m[1]}\` 在本文件正文里找不到对应实测值(标注与证据不符)`, '标注必须指向文中的真实返回值')
288
+ }
289
+ info.push({ rule: 'R7-HISTDOC', file: rel, line: lineNo, token: m[1],
290
+ why: `带"当时版本"标注 ⇒ 其历史值(如 ${others.slice(0, 3).join(' / ') || '无'})按纪律豁免,⛔ 不追改` })
291
+ }
292
+ }
293
+
294
+ // ── R3-CARRIER(载体必须"读 VERSION",不许写死)────────────────────────────
295
+ for (const c of CARRIERS) {
296
+ const abs = join(root, c.file)
297
+ if (!existsSync(abs)) { err('R3-CARRIER', c.file, 0, '声明的版本载体不存在', c.why); continue }
298
+ const text = readFileSync(abs, 'utf8')
299
+ if (!/['"`]VERSION['"`]/.test(text)) {
300
+ err('R3-CARRIER', c.file, 0, '没有从 `VERSION` 读版本(找不到 `\'VERSION\'` 字面量)—— 版本被写死了?', c.why)
301
+ }
302
+ }
303
+
304
+ // ── R4-SCAN(反向:活引用面不得写死别的版本)──────────────────────────────
305
+ const files = []
306
+ for (const s of SURFACE) {
307
+ const abs = join(root, s)
308
+ if (existsSync(abs)) walkFiles(abs, root, files)
309
+ }
310
+ // 门禁自己**不扫自己**(判据里必然含示例版本号);它的**回归夹具**同理
311
+ // (`scripts/tests/version-consistency.test.mjs` 的职责就是注入错误版本 ⇒ 必须豁免,否则恒红)。
312
+ // ⚠️ 豁免面 = **这两个精确路径**,⛔ 不是 `scripts/tests/**` 整个目录 ——
313
+ // 目录级豁免会漏掉真·活引用(2026-10-10 实测:`scripts/tests/no-host-smoke.test.mjs` 曾把
314
+ // 平台版本写死成字面量,正是本门禁要抓的那类)。
315
+ const SELF_EXCLUDED = new Set([SELF, join(root, 'scripts', 'tests', 'version-consistency.test.mjs')].map((p) => resolve(p)))
316
+ const scanned = files.filter((f) => !SELF_EXCLUDED.has(resolve(f)))
317
+ for (const f of scanned) {
318
+ let text
319
+ try { text = readFileSync(f, 'utf8') } catch { continue }
320
+ if (text.includes('\u0000')) continue
321
+ const rel = relative(root, f)
322
+ text.split('\n').forEach((line, i) => scanLine({
323
+ rel, line, lineNo: i + 1, truth, findings, info,
324
+ whyText: `活引用面的 Argent 版本字面量必须等于 \`VERSION\`(${truth})或改从 \`VERSION\` 读;`
325
+ + '确属历史记录 ⇒ 请写进 `CHANGELOG.md` / 带"当时版本"标注的阶段文档(见契约 §8 纪律 2)',
326
+ }))
327
+ }
328
+
329
+ // ── R8-COVERAGE(防"扫描器什么都没扫也能绿")─────────────────────────────
330
+ if (typeof opts.skipCoverage !== 'boolean' || !opts.skipCoverage) {
331
+ if (scanned.length < 100) err('R8-COVERAGE', '.', 0, `只扫到 ${scanned.length} 个文件(< 100)⇒ 扫描面可能失效`, '扫描面 = 活引用面;扫不到东西的门禁等于没有门禁')
332
+ }
333
+
334
+ return {
335
+ ok: findings.length === 0,
336
+ findings,
337
+ info,
338
+ truth,
339
+ stats: { files: scanned.length, surface: SURFACE, info: info.length, carriers: CARRIERS.length, lockFiles: lockFiles.length, verticals },
340
+ changelog,
341
+ histDocs,
342
+ }
343
+ }
344
+
345
+ // ── R2-RUNTIME(跑起来也一致;`--no-runtime` 可跳过,用于纯静态夹具)──────────
346
+ export async function runRuntime(opts = {}) {
347
+ const root = resolve(opts.root || SELF_ROOT)
348
+ const findings = []
349
+ const detail = {}
350
+ const cli = join(root, 'core', 'cli', 'argent.mjs')
351
+ const truth = readFileSync(join(root, 'VERSION'), 'utf8').trim()
352
+ try {
353
+ const j = JSON.parse(execFileSync(process.execPath, [cli, 'version', '--json'], { encoding: 'utf8' }))
354
+ detail.cliVersion = j.argent
355
+ if (j.argent !== truth) findings.push({ rule: 'R2-RUNTIME', file: 'core/cli/argent.mjs', line: 0, token: j.argent, message: `CLI \`argent version\` 回 ${j.argent} ≠ 真源 ${truth}`, why: '活引用必须等于唯一真源' })
356
+ } catch (e) {
357
+ findings.push({ rule: 'R2-RUNTIME', file: 'core/cli/argent.mjs', line: 0, token: null, message: `CLI \`argent version\` 跑不起来:${String(e.message).split('\n')[0]}`, why: '跑不起来 ⇒ 无法判定(⛔ 不当通过)' })
358
+ }
359
+ try {
360
+ const j = JSON.parse(execFileSync(process.execPath, [cli, 'versions', '--json'], { encoding: 'utf8' }))
361
+ detail.cliVersions = { cli: j.cli?.argent, contracts: j.contracts?.argentVersion }
362
+ if (j.cli?.argent !== truth) findings.push({ rule: 'R2-RUNTIME', file: 'core/cli/argent.mjs', line: 0, token: j.cli?.argent, message: `\`versions --json\` 的 cli.argent = ${j.cli?.argent} ≠ 真源 ${truth}`, why: '五面对账的版本出口' })
363
+ if (j.contracts?.argentVersion && j.contracts.argentVersion !== truth) findings.push({ rule: 'R2-RUNTIME', file: 'core/contracts/argent.contracts.json', line: 0, token: j.contracts.argentVersion, message: `\`versions --json\` 的 contracts.argentVersion = ${j.contracts.argentVersion} ≠ 真源 ${truth}`, why: '五面对账的版本出口' })
364
+ } catch (e) {
365
+ findings.push({ rule: 'R2-RUNTIME', file: 'core/cli/argent.mjs', line: 0, token: null, message: `CLI \`versions --json\` 跑不起来:${String(e.message).split('\n')[0]}`, why: '跑不起来 ⇒ 无法判定(⛔ 不当通过)' })
366
+ }
367
+ detail.mcp = await mcpServerInfo(root)
368
+ if (detail.mcp.error) findings.push({ rule: 'R2-RUNTIME', file: 'core/mcp/server.mjs', line: 0, token: null, message: `MCP 握手失败:${detail.mcp.error}`, why: '宿主读的就是这个版本' })
369
+ else if (detail.mcp.version !== truth) findings.push({ rule: 'R2-RUNTIME', file: 'core/mcp/server.mjs', line: 50, token: detail.mcp.version, message: `MCP \`initialize.serverInfo.version\` = ${detail.mcp.version} ≠ 真源 ${truth}`, why: '宿主读的就是这个版本(H1 采信证据即此项)' })
370
+ return { findings, detail }
371
+ }
372
+
373
+ /** 起一个 stdio MCP 子进程握手一次,取 `initialize.serverInfo`(**只读、零副作用**)。 */
374
+ export function mcpServerInfo(root, timeoutMs = 15000) {
375
+ const server = join(root, 'core', 'mcp', 'server.mjs')
376
+ if (!existsSync(server)) return { error: 'core/mcp/server.mjs 不存在' }
377
+ const child = spawn(process.execPath, [server], { stdio: ['pipe', 'pipe', 'ignore'], env: { ...process.env, ARGENT_TELEMETRY_OFF: '1' } })
378
+ return new Promise((res) => {
379
+ let buf = ''
380
+ let done = false
381
+ const finish = (v) => { if (done) return; done = true; try { child.kill() } catch {} ; res(v) }
382
+ const timer = setTimeout(() => finish({ error: `握手超时(${timeoutMs}ms)` }), timeoutMs)
383
+ child.stdout.setEncoding('utf8')
384
+ child.stdout.on('data', (c) => {
385
+ buf += c
386
+ let i
387
+ while ((i = buf.indexOf('\n')) >= 0) {
388
+ const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1)
389
+ if (!line) continue
390
+ let m; try { m = JSON.parse(line) } catch { continue }
391
+ if (m.id === 1) {
392
+ clearTimeout(timer)
393
+ finish(m.error ? { error: m.error.message } : { name: m.result?.serverInfo?.name, version: m.result?.serverInfo?.version, protocolVersion: m.result?.protocolVersion })
394
+ }
395
+ }
396
+ })
397
+ child.on('error', (e) => { clearTimeout(timer); finish({ error: e.message }) })
398
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'version-consistency', version: '1' } } })}\n`)
399
+ })
400
+ }
401
+
402
+ // ── CLI ─────────────────────────────────────────────────────────────────────
403
+ const isMain = process.argv[1] && resolve(process.argv[1]) === SELF
404
+ if (isMain) {
405
+ const argv = process.argv.slice(2)
406
+ const has = (f) => argv.includes(f)
407
+ const val = (f) => { const i = argv.indexOf(f); return i >= 0 ? argv[i + 1] : undefined }
408
+ if (has('-h') || has('--help')) {
409
+ console.log(readFileSync(SELF, 'utf8').split('*/')[0].replace(/^\/\*\*?/, '').replace(/^ \* ?/gm, '').trim())
410
+ process.exit(0)
411
+ }
412
+ const STRICT = has('--strict')
413
+ const JSON_OUT = has('--json')
414
+ const NO_RUNTIME = has('--no-runtime')
415
+ const root = val('--root')
416
+ const r = run({ root })
417
+ if (r.truth === null) {
418
+ console.error(`✗ 版本一致性:拿不到唯一真源 ⇒ 停住(⛔ 不静默成功)`)
419
+ process.exit(2)
420
+ }
421
+ let runtime = { findings: [], detail: {} }
422
+ if (!NO_RUNTIME) runtime = await runRuntime({ root })
423
+ const findings = [...r.findings, ...runtime.findings]
424
+ const out = {
425
+ ok: findings.length === 0,
426
+ truth: r.truth,
427
+ findings,
428
+ info: r.info,
429
+ runtime: runtime.detail,
430
+ stats: r.stats,
431
+ changelog: r.changelog,
432
+ histDocs: r.histDocs,
433
+ }
434
+ if (JSON_OUT) {
435
+ console.log(JSON.stringify(out, null, 2))
436
+ } else {
437
+ console.log('\n【版本一致性】活引用 == 唯一真源')
438
+ console.log(` 唯一真源:VERSION = ${r.truth}`)
439
+ console.log(` 活引用:${LIVE_REFS.map((x) => x.id).join(' | ')} | 业务线 ${r.stats.verticals.length} 条`)
440
+ console.log(` 版本载体(必须读 VERSION):${r.stats.carriers} 个`)
441
+ for (const v of r.stats.verticals) {
442
+ console.log(` · ${v.id} ${v.version} requires.argent \`${v.range}\` ⇒ ${v.ok ? '✅ 容纳现役平台' : '❌ 不兼容'}`)
443
+ }
444
+ if (r.changelog.newest) console.log(` CHANGELOG 最新段:${r.changelog.newest}(共 ${r.changelog.sections.length} 段;更旧的段按定义历史)`)
445
+ if (!NO_RUNTIME) console.log(` 跑起来:CLI version=${runtime.detail.cliVersion} | versions.cli=${runtime.detail.cliVersions?.cli} | MCP serverInfo=${runtime.detail.mcp?.name},${runtime.detail.mcp?.version}`)
446
+ console.log(` 反向扫描:${r.stats.files} 个文件(活引用面)| dsh 快照 ${r.stats.lockFiles} 个`)
447
+ console.log(` 历史记录(豁免,⛔ 不追改):${r.info.length} 条`)
448
+ for (const f of findings) console.log(` ❌ [${f.rule}] ${f.file}:${f.line || 0} ${f.message}`)
449
+ console.log(`\n${findings.length === 0 ? '✅' : '❌'} version-consistency: ${findings.length} 个 error / ${r.info.length} 条历史豁免`)
450
+ }
451
+ process.exit(findings.length === 0 ? 0 : (STRICT ? 1 : 0))
452
+ }