@whyshu/argent-engine 0.0.0-stage → 0.6.1

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 (340) hide show
  1. package/MANIFEST.json +341 -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 +576 -0
  55. package/engine/scripts/billing-mode-daily.sh +13 -0
  56. package/engine/scripts/build-engine-package.mjs +126 -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 +378 -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/collect-evidence.mjs +42 -0
  81. package/engine/scripts/config-explain.mjs +278 -0
  82. package/engine/scripts/configure-provider.rb +29 -0
  83. package/engine/scripts/customer-config-migrate.mjs +179 -0
  84. package/engine/scripts/customer-preflight.py +892 -0
  85. package/engine/scripts/customer-readiness.sh +135 -0
  86. package/engine/scripts/daily-report-inbox.py +242 -0
  87. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  88. package/engine/scripts/daily-report.mjs +1310 -0
  89. package/engine/scripts/daily-report.sh +47 -0
  90. package/engine/scripts/data/lip-product-dict.json +306 -0
  91. package/engine/scripts/deliver-customer.mjs +932 -0
  92. package/engine/scripts/delivery-faces.txt +31 -0
  93. package/engine/scripts/desktop-trial.sh +146 -0
  94. package/engine/scripts/dev-window.sh +261 -0
  95. package/engine/scripts/drill-clean-machine.sh +315 -0
  96. package/engine/scripts/dsh-api-client.mjs +215 -0
  97. package/engine/scripts/dsh-backup-scope.sh +83 -0
  98. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  99. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  100. package/engine/scripts/dsh-plane.mjs +153 -0
  101. package/engine/scripts/dsh-portforward.py +223 -0
  102. package/engine/scripts/dsh-preflight.mjs +179 -0
  103. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  104. package/engine/scripts/dsh-release-watch.mjs +458 -0
  105. package/engine/scripts/dsh-remote-check.sh +99 -0
  106. package/engine/scripts/dsh-restart.sh +179 -0
  107. package/engine/scripts/dsh-restore.sh +111 -0
  108. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  109. package/engine/scripts/dsh-snapshot.mjs +116 -0
  110. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  111. package/engine/scripts/dsh-upgrade.mjs +612 -0
  112. package/engine/scripts/dsh-url.sh +86 -0
  113. package/engine/scripts/dsh-web-launch.mjs +155 -0
  114. package/engine/scripts/ensure-store-windows.py +509 -0
  115. package/engine/scripts/entitlements.py +458 -0
  116. package/engine/scripts/env_probe.py +411 -0
  117. package/engine/scripts/feishu-card.mjs +252 -0
  118. package/engine/scripts/final-check.mjs +43 -0
  119. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  120. package/engine/scripts/flow-auto-retry.py +454 -0
  121. package/engine/scripts/flow-requirements.json +735 -0
  122. package/engine/scripts/flow-scaffold.py +275 -0
  123. package/engine/scripts/flow-timers-plist.sh +87 -0
  124. package/engine/scripts/flow-timers.sh +184 -0
  125. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  126. package/engine/scripts/flow-trigger.sh +571 -0
  127. package/engine/scripts/flow_billing.py +354 -0
  128. package/engine/scripts/flow_capability_stats.py +314 -0
  129. package/engine/scripts/flow_case.py +194 -0
  130. package/engine/scripts/flow_claim.py +497 -0
  131. package/engine/scripts/flow_config.py +1025 -0
  132. package/engine/scripts/flow_failure.py +588 -0
  133. package/engine/scripts/flow_learn.py +105 -0
  134. package/engine/scripts/flow_ledger.py +235 -0
  135. package/engine/scripts/flow_policy.py +238 -0
  136. package/engine/scripts/flow_preflight.py +339 -0
  137. package/engine/scripts/flow_registry.py +341 -0
  138. package/engine/scripts/flow_retry.py +304 -0
  139. package/engine/scripts/flow_return_to_ops.py +191 -0
  140. package/engine/scripts/flow_source.py +315 -0
  141. package/engine/scripts/flow_sources/__init__.py +53 -0
  142. package/engine/scripts/flow_sources/cases_json.py +106 -0
  143. package/engine/scripts/flow_sources/common.py +428 -0
  144. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  145. package/engine/scripts/flow_stale.py +185 -0
  146. package/engine/scripts/flow_status_reconcile.py +344 -0
  147. package/engine/scripts/flow_triage.py +849 -0
  148. package/engine/scripts/flow_write_verify.py +496 -0
  149. package/engine/scripts/fulfillment.js +586 -0
  150. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  151. package/engine/scripts/gen-preset-rows.mjs +259 -0
  152. package/engine/scripts/init-ml.sh +2278 -0
  153. package/engine/scripts/install-internal-role.sh +190 -0
  154. package/engine/scripts/install-license.mjs +641 -0
  155. package/engine/scripts/install-one-shot.mjs +665 -0
  156. package/engine/scripts/integration_contract.py +220 -0
  157. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  158. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  159. package/engine/scripts/invoice-mapping.example.json +40 -0
  160. package/engine/scripts/invoice-table.sh +195 -0
  161. package/engine/scripts/invoice_carrier_notice.py +279 -0
  162. package/engine/scripts/invoice_fail_closeout.py +297 -0
  163. package/engine/scripts/invoice_lip_runner.py +1508 -0
  164. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  165. package/engine/scripts/invoice_ready_audit.py +191 -0
  166. package/engine/scripts/invoice_runner.py +675 -0
  167. package/engine/scripts/invoice_stageb.py +516 -0
  168. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  169. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  170. package/engine/scripts/lease.py +377 -0
  171. package/engine/scripts/lib/agent-install.mjs +649 -0
  172. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  173. package/engine/scripts/lib/argent-datadir.sh +18 -0
  174. package/engine/scripts/lib/argent_datadir.py +285 -0
  175. package/engine/scripts/lib/argent_filelock.py +507 -0
  176. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  177. package/engine/scripts/lib/cdp_ports.py +197 -0
  178. package/engine/scripts/lib/customer-env.mjs +102 -0
  179. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  180. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  181. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  182. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  183. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  184. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  185. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  186. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  187. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  188. package/engine/scripts/lib/patch-compose.mjs +241 -0
  189. package/engine/scripts/lib/patch-target.mjs +35 -0
  190. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  191. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  192. package/engine/scripts/lib/preset-compat.mjs +127 -0
  193. package/engine/scripts/lib/preset-install.mjs +575 -0
  194. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  195. package/engine/scripts/lib/preset-sync.mjs +59 -0
  196. package/engine/scripts/lib/profile-retire.mjs +97 -0
  197. package/engine/scripts/lib/python-runtime.mjs +190 -0
  198. package/engine/scripts/lib/registry.mjs +72 -0
  199. package/engine/scripts/lib/repo-root.sh +56 -0
  200. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  201. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  202. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  203. package/engine/scripts/lib/settings-panels.mjs +816 -0
  204. package/engine/scripts/lib/stdio_utf8.py +86 -0
  205. package/engine/scripts/lib/wake-auth.mjs +392 -0
  206. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  207. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  208. package/engine/scripts/license-e2e-drill.mjs +426 -0
  209. package/engine/scripts/license-gate.mjs +245 -0
  210. package/engine/scripts/lip_backlog.py +171 -0
  211. package/engine/scripts/lip_table.py +185 -0
  212. package/engine/scripts/local-usage.mjs +214 -0
  213. package/engine/scripts/migrate-providers.mjs +127 -0
  214. package/engine/scripts/migrate-skills.sh +65 -0
  215. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  216. package/engine/scripts/ml-store-ids.json +26 -0
  217. package/engine/scripts/ml_artifact_attribution.py +333 -0
  218. package/engine/scripts/ml_capacity_probe.py +1158 -0
  219. package/engine/scripts/ml_hub_status.py +1556 -0
  220. package/engine/scripts/ml_schema_check.py +115 -0
  221. package/engine/scripts/ml_store_ids.py +139 -0
  222. package/engine/scripts/npm-publish-argent.mjs +499 -0
  223. package/engine/scripts/npm-publish-argent.sh +20 -0
  224. package/engine/scripts/orchestrator.py +13811 -0
  225. package/engine/scripts/pack-plugins.mjs +140 -0
  226. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  227. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  228. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  229. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  230. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  231. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  232. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  233. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  234. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  235. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  236. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  237. package/engine/scripts/plan-role-rename.mjs +125 -0
  238. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  239. package/engine/scripts/plugin-composition.json +15 -0
  240. package/engine/scripts/poc-mcp-host.mjs +455 -0
  241. package/engine/scripts/poll-fulfillment.sh +295 -0
  242. package/engine/scripts/poll-invoice.sh +76 -0
  243. package/engine/scripts/profile-doctor.mjs +686 -0
  244. package/engine/scripts/provider-select.sh +71 -0
  245. package/engine/scripts/provider-switch.sh +146 -0
  246. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  247. package/engine/scripts/python-runtime.sh +78 -0
  248. package/engine/scripts/query_runner.py +310 -0
  249. package/engine/scripts/release-argent.sh +574 -0
  250. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  251. package/engine/scripts/retire-inventory.mjs +40 -0
  252. package/engine/scripts/run-tests.sh +257 -0
  253. package/engine/scripts/s1-audit.mjs +85 -0
  254. package/engine/scripts/self-sign-license.mjs +179 -0
  255. package/engine/scripts/session-health-scan.mjs +63 -0
  256. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  257. package/engine/scripts/setup.sh +22 -0
  258. package/engine/scripts/skill-install.mjs +629 -0
  259. package/engine/scripts/sku_translate.py +148 -0
  260. package/engine/scripts/sync-plugins.sh +90 -0
  261. package/engine/scripts/sync-presets.sh +27 -0
  262. package/engine/scripts/sync-scripts.sh +445 -0
  263. package/engine/scripts/telemetry.py +873 -0
  264. package/engine/scripts/ui-copy-count.mjs +220 -0
  265. package/engine/scripts/usage-anchor.sh +137 -0
  266. package/engine/scripts/usage_report.py +531 -0
  267. package/engine/scripts/validate-preset-schema.mjs +208 -0
  268. package/engine/scripts/vertical-init.mjs +323 -0
  269. package/engine/scripts/vertical-lint.mjs +482 -0
  270. package/engine/scripts/wenyao-browser.sh +74 -0
  271. package/engine/scripts/wenyao-fixture-export.py +116 -0
  272. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  273. package/engine/scripts/wenyao-login.sh +35 -0
  274. package/engine/scripts/wenyao-template-map.json +292 -0
  275. package/engine/scripts/wenyao_attach.py +131 -0
  276. package/engine/scripts/wenyao_download_template.py +144 -0
  277. package/engine/scripts/wenyao_fill_template.py +568 -0
  278. package/engine/scripts/wenyao_login.py +145 -0
  279. package/engine/scripts/wenyao_portal.py +633 -0
  280. package/engine/scripts/wenyao_recon.py +230 -0
  281. package/engine/scripts/wenyao_run.py +503 -0
  282. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  283. package/engine/scripts/wenyao_skill_contract.py +156 -0
  284. package/engine/scripts/wenyao_template_fetch.py +175 -0
  285. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  286. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  287. package/engine/scripts/ziniao-keepalive.sh +217 -0
  288. package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
  289. package/engine/scripts/ziniao-windows.sh +154 -0
  290. package/engine/skills/README.md +86 -0
  291. package/engine/skills/argent-dev/SKILL.md +41 -0
  292. package/engine/skills/internal.json +37 -0
  293. package/engine/skills/lktop-pm/SKILL.md +35 -0
  294. package/engine/skills/lktopapp-dev/SKILL.md +34 -0
  295. package/engine/skills/ml-config-guide/SKILL.md +50 -0
  296. package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
  297. package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
  298. package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
  299. package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
  300. package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
  301. package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
  302. package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
  303. package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
  304. package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
  305. package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
  306. package/engine/skills/ml-invoice/SKILL.md +506 -0
  307. package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
  308. package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
  309. package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
  310. package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
  311. package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
  312. package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
  313. package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
  314. package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
  315. package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
  316. package/engine/skills/pet-dev/SKILL.md +42 -0
  317. package/engine/skills/whyshu-pm/SKILL.md +35 -0
  318. package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
  319. package/engine/version.mjs +1 -0
  320. package/engine/verticals/README.md +103 -0
  321. package/engine/verticals/_engine-words.baseline.txt +237 -0
  322. package/engine/verticals/_engine-words.json +19 -0
  323. package/engine/verticals/mercadolibre/README.md +43 -0
  324. package/engine/verticals/mercadolibre/config.json +709 -0
  325. package/engine/verticals/mercadolibre/flows.json +738 -0
  326. package/engine/verticals/mercadolibre/manifest.json +538 -0
  327. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  328. package/engine/verticals/mercadolibre/preflight.py +143 -0
  329. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  330. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  331. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  332. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  333. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  334. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  335. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  336. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  337. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  338. package/package.json +22 -4
  339. package/requirements.txt +5 -0
  340. package/README.md +0 -3
@@ -0,0 +1,499 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `@whyshu/argent-*` 六包发布器(W9,2026-10-12)—— **默认 dry-run**。
4
+ *
5
+ * 入口是 `scripts/npm-publish-argent.sh`(薄包装,只做 `exec node <本文件> "$@"`)。
6
+ * 为什么实现放在 `.mjs` 而入口留 `.sh`:本脚本干的全是 **JSON / 内容摘要 / 退避** 这类事
7
+ * (`npm pack --json` 的解析、`files` 闭合双向比对、`dist.integrity` 回读),
8
+ * 用 shell 拼 JSON 的失败方式是"静默取到 undefined" —— 正是本仓库反复吃亏的那一类形态。
9
+ * ⇒ 判据与 IO 全在 JS 里,shell 只当入口(名字仍按批次要求的 `npm-publish-argent.sh`)。
10
+ *
11
+ * ## 为什么默认 dry-run(⛔ 不是懒)
12
+ * `npm publish` 是**不可撤销**的对外动作(同名同版本不可覆盖)。默认档只做**只读**判据:
13
+ * 文件闭合、版本纪律、注册表现状,并打印"会发哪些 / 会跳过哪些 / 会判哪些红"。
14
+ * 要真发必须显式 `--apply`。
15
+ *
16
+ * ## 判据(每条都有稳定判据码,红 ⇒ 非零退出)
17
+ * | 码 | 判据 | 红条件 |
18
+ * |---|---|---|
19
+ * | `PUB-J0` | 凭据预检:`<token-file>` 存在且 **mode = 600**;`npm whoami` == `whyshu` | 任一不满足 |
20
+ * | `PUB-J1` | **文件闭合**:`npm pack --dry-run --json` 的清单 ↔ `package.json#files` **双向**自洽(含 `main`/`exports`/`dsh.bundle.patch` 引用面) | 缺件 / 多件 / 死条目 |
21
+ * | `PUB-J2` | **版本纪律(本地台账)**:审计 JSONL 里该 `(包名, 版本)` 最近一条记录的 `integrity` 必须等于本次本地 pack 的 integrity | 不等 ⇒ "内容变了、版本没变"(⛔ 不发、⛔ 不覆盖) |
22
+ * | `PUB-J3` | **幂等**:注册表上该版本的 `dist.integrity` == 本地 ⇒ **跳过**(打印 SKIP),调用方可以放心重跑 | 不是红(红的是下面 J4) |
23
+ * | `PUB-J4` | 注册表上**已存在**该版本,但 `dist.integrity` **不等于**本地 ⇒ 判红(⛔ 绝不覆盖) | 不等 |
24
+ * | `PUB-J5` | **回读退避**:发布后回读 `dist.integrity`;实测注册表**复制延迟 ~3 分钟** ⇒ 一次 404/空/非零**都不算失败**,按 `--retry-interval-ms`(默认 15s)退避重试至多 `--retry-max` 次(默认 20 ⇒ ~5 分钟) | `--apply` 下用尽仍读不到;或回读到的 integrity ≠ 刚发布的字节 |
25
+ * | `PUB-J6` | **审计**:`--apply` 下每个包落一行 JSONL(包名/版本/integrity/时间/来源提交 hash/动作/账号) | 写不进 ⇒ 红(审计是"发过什么"的唯一凭证) |
26
+ *
27
+ * ## 幂等语义(一句话)
28
+ * "已发布且 integrity 一致 ⇒ 跳过;已发布但不一致 ⇒ 判红;本地内容变了而版本没变 ⇒ 判红。"
29
+ * ⇒ **可以无脑重跑**:重跑要么跳过、要么把没发成功的补上,⛔ 永远不会覆盖已发布的字节。
30
+ *
31
+ * ## 纪律
32
+ * · token **只**经临时 `.npmrc`(0600,`trap` 清理)传给 npm,⛔ 不进命令行/日志/审计;
33
+ * · ⛔ 不写 `~/.npmrc`;⛔ 不动仓库工作树(`npm pack --dry-run` 不落 tgz);
34
+ * · 审计按需 `mkdir -p` 父目录,0600。
35
+ *
36
+ * 用法:`bash scripts/npm-publish-argent.sh [--apply] [--only <id[,id…]>] …`(见 `--help`)。
37
+ */
38
+ import { execFileSync, spawnSync } from 'node:child_process'
39
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, appendFileSync } from 'node:fs'
40
+ import { tmpdir } from 'node:os'
41
+ import { dirname, join, resolve } from 'node:path'
42
+ import { fileURLToPath, pathToFileURL } from 'node:url'
43
+ import { PLUGIN_PACKAGES, pluginIdOf, isAgentBundleId } from './lib/plugin-packages.mjs'
44
+ // 数据目录**唯一判据**:本文件的默认令牌/审计路径**必须**经它算,⛔ 不许自己拼 `.dsh/argent`
45
+ // (`scripts/tests/argent-datadir.test.mjs`【3】按机器判据点名任何硬编码的第二份链;
46
+ // 同族写法先例:`scripts/argent-catalog.mjs#DATA_DIR`)。
47
+ import { resolveDataDir } from './lib/argent-datadir.mjs'
48
+
49
+ const HERE = dirname(fileURLToPath(import.meta.url))
50
+ const REPO = resolve(HERE, '..')
51
+
52
+ /** 默认数据目录(`ARGENT_DATA_DIR` → `$DSH_HOME/argent` → 老位置/新装兜底)。 */
53
+ function defaultDataDir() {
54
+ return resolveDataDir({ env: process.env }).dir
55
+ }
56
+
57
+ /** npm 账号名(**用户名 scope**,⛔ 不是组织)。 */
58
+ const EXPECTED_USER = 'whyshu'
59
+
60
+ /**
61
+ * npm 自动纳入包内、因而**不需要**出现在 `files` 里的路径(逐条给理由)。
62
+ * 依据 = npm 官方 `files` 语义(这几个是历史约定 + `package.json` 永远在内)。
63
+ */
64
+ const AUTO_INCLUDE = [
65
+ 'package.json', // npm 强制纳入(否则包装不起来)
66
+ 'README', 'README.md', 'README.markdown', 'README.txt',
67
+ 'LICENSE', 'LICENSE.md', 'LICENSE.txt', 'LICENCE', 'LICENCE.md', 'LICENCE.txt',
68
+ 'CHANGELOG', 'CHANGELOG.md', 'CHANGELOG.txt',
69
+ 'NOTICE', 'NOTICE.md', 'NOTICE.txt',
70
+ ]
71
+ /** 自动纳入的**前缀**形态(`README.zh.md` 这类)。 */
72
+ const AUTO_INCLUDE_RE = /^(?:README|LICEN[CS]E|CHANGELOG|NOTICE|HISTORY|AUTHORS)(?:[.\w-]*)$/
73
+
74
+ const USAGE = `用法:bash scripts/npm-publish-argent.sh [选项]
75
+ (不带 --apply ⇒ **dry-run**:只做只读判据并打印计划,⛔ 不发布、⛔ 不写审计、⛔ 不动注册表)
76
+
77
+ --apply 真发布(⛔ 不可撤销:同名同版本的字节不能覆盖)
78
+ --only <id[,id…]> 只处理这些插件 id(默认 = 6 个全量,顺序取真源)
79
+ --npm-bin <path> 用哪个 npm(默认 PATH 上的 \`npm\`)
80
+ --registry <url> registry(默认 https://registry.npmjs.org)
81
+ --token-file <path> npm granular token(默认 ~/.dsh/argent/npm-token,必须 600)
82
+ --audit <path> 审计 JSONL(默认 ~/.dsh/argent/npm-publish-audit.jsonl)
83
+ --retry-interval-ms <n> 回读退避间隔毫秒(默认 15000;实测注册表复制延迟 ~3 分钟)
84
+ --retry-max <n> 回读最大次数(默认 20 ⇒ 约 5 分钟)
85
+ --repo <dir> 仓库根(默认由本文件位置推)
86
+ -h | --help 本帮助
87
+
88
+ 退出码:0 = 全绿(含"全部跳过");1 = 有判据判红;2 = 用法错误。
89
+ 重跑安全:**幂等**(已发布且 integrity 一致 ⇒ 跳过;不一致 ⇒ 判红,⛔ 不覆盖)。
90
+ `
91
+
92
+ function die(code, msg) {
93
+ if (msg) console.error(msg)
94
+ process.exit(code)
95
+ }
96
+
97
+ /** 极简参数解析(⛔ 未知 flag 一律用法错误 —— 静默忽略参数是本仓库反复吃亏的形态)。 */
98
+ function parseArgs(argv) {
99
+ const o = {
100
+ apply: false, only: null, npmBin: 'npm', registry: 'https://registry.npmjs.org',
101
+ tokenFile: join(defaultDataDir(), 'npm-token'),
102
+ audit: join(defaultDataDir(), 'npm-publish-audit.jsonl'),
103
+ retryIntervalMs: 15000, retryMax: 20, repo: REPO,
104
+ }
105
+ const need = (i, name) => {
106
+ if (i + 1 >= argv.length) die(2, `✗ ${name} 需要一个取值(见 --help)`)
107
+ return argv[i + 1]
108
+ }
109
+ for (let i = 0; i < argv.length; i += 1) {
110
+ const a = argv[i]
111
+ switch (a) {
112
+ case '--apply': o.apply = true; break
113
+ case '--only': o.only = String(need(i, a)).split(',').map((s) => s.trim()).filter(Boolean); i += 1; break
114
+ case '--npm-bin': o.npmBin = need(i, a); i += 1; break
115
+ case '--registry': o.registry = need(i, a); i += 1; break
116
+ case '--token-file': o.tokenFile = need(i, a); i += 1; break
117
+ case '--audit': o.audit = need(i, a); i += 1; break
118
+ case '--repo': o.repo = resolve(need(i, a)); i += 1; break
119
+ case '--retry-interval-ms': {
120
+ const v = Number(need(i, a)); i += 1
121
+ if (!Number.isFinite(v) || v < 0) die(2, `✗ --retry-interval-ms 需要一个 ≥ 0 的数,实得 ${JSON.stringify(argv[i])}`)
122
+ o.retryIntervalMs = v; break
123
+ }
124
+ case '--retry-max': {
125
+ const v = Number(need(i, a)); i += 1
126
+ if (!Number.isInteger(v) || v < 1) die(2, `✗ --retry-max 需要一个 ≥ 1 的整数,实得 ${JSON.stringify(argv[i])}`)
127
+ o.retryMax = v; break
128
+ }
129
+ case '-h': case '--help': console.log(USAGE); process.exit(0); break
130
+ default: die(2, `✗ 未知参数 ${a}(⛔ 不静默忽略;见 --help)`)
131
+ }
132
+ }
133
+ if (o.only) {
134
+ // ⚠️ W3(0.6.0):智能体 bundle(`argent-agent-<id>`)**也**是合法取值 —— 它们**不在** `PLUGIN_PACKAGES`
135
+ // 的静态表里(名字带业务线词,列进引擎面会脏了 engine-words 判据)⇒ 认的是**前缀规则**
136
+ // (`isAgentBundleId`),与 `pluginIdOf` 同一个真源。
137
+ const unknown = o.only.filter((x) => !pluginIdOf(x))
138
+ if (unknown.length) {
139
+ die(2, `✗ --only 里有未知插件 id:${unknown.join('、')}\n 合法取值:${PLUGIN_PACKAGES.map((p) => p.id).join('、')}`
140
+ + ` + 任意智能体 bundle(\`plugins/argent-agent-<id>\`,由 agents/catalog.json 生成)`)
141
+ }
142
+ }
143
+ return o
144
+ }
145
+
146
+ // ─────────────────────────────────────────────────────────────────────────────
147
+ // 判据(纯函数;可注入 IO ⇒ 自带夹具回归里能逐条注入违规证明"有牙")
148
+ // ─────────────────────────────────────────────────────────────────────────────
149
+
150
+ /** npm `files` 语义的**最小**实现:条目是文件 ⇒ 精确匹配;是目录 ⇒ 前缀匹配。 */
151
+ export function isCoveredByFiles(rel, files) {
152
+ const r = String(rel).replace(/^\.\//, '')
153
+ return (files || []).some((f) => {
154
+ const e = String(f).replace(/^\.\//, '').replace(/\/+$/, '')
155
+ return e !== '' && (r === e || r.startsWith(`${e}/`))
156
+ })
157
+ }
158
+
159
+ /** manifest 里"运行时会被读到"的文件引用面(`main` / `exports` / `dsh.bundle.patch`)。 */
160
+ export function collectRuntimeRefs(m) {
161
+ const out = []
162
+ const push = (x) => { if (typeof x === 'string' && x.startsWith('./')) out.push(x.replace(/^\.\//, '')) }
163
+ push(m.main)
164
+ const walk = (e) => {
165
+ if (typeof e === 'string') push(e)
166
+ else if (e && typeof e === 'object') for (const v of Object.values(e)) walk(v)
167
+ }
168
+ walk(m.exports)
169
+ walk(m.dsh?.bundle?.patch)
170
+ return [...new Set(out)]
171
+ }
172
+
173
+ /** 自动纳入判定(`package.json` ⇒ true;`README*` 等前缀形态也只对**顶层**文件生效)。 */
174
+ export function isAutoIncluded(rel) {
175
+ const r = String(rel).replace(/^\.\//, '')
176
+ if (AUTO_INCLUDE.includes(r)) return true
177
+ // npm 只自动纳入**包根**下的这几个名字,不含子目录里的同名文件
178
+ return !r.includes('/') && AUTO_INCLUDE_RE.test(r)
179
+ }
180
+
181
+ /**
182
+ * **PUB-J1 文件闭合**(双向)。
183
+ *
184
+ * 为什么双向(单向后患无穷):
185
+ * · 只查"声明了但没进包" ⇒ 漏掉"**没声明却进包**"(= 把不该发的发出去,比如测试夹具/内网地址);
186
+ * · 只查"包里没有多余" ⇒ 漏掉"**声明是死的**"(`files` 里写了盘上不存在的路径,npm 静默忽略)。
187
+ * ⛔ 不把"多件"一律判红:npm 会自动纳入 `package.json`/`README`/`LICENSE`… ⇒ 那组进 `AUTO_INCLUDE`。
188
+ * @returns {{problems: Array<{code:string,where:string,detail:string}>}}
189
+ */
190
+ export function checkFilesClosure({ manifest, packFiles, exists = existsSync, dir = '' }) {
191
+ const problems = []
192
+ const add = (where, detail) => problems.push({ code: 'PUB-J1', where, detail })
193
+ const declared = Array.isArray(manifest.files) ? manifest.files.map((x) => String(x)) : []
194
+ if (!declared.length) add('package.json#files', '缺 files(npm 发布时必须显式声明;不写 ⇒ 全仓进包)')
195
+ const paths = packFiles.map((f) => String(f).replace(/^\.\//, ''))
196
+ for (const d of declared) {
197
+ const rel = d.replace(/^\.\//, '').replace(/\/+$/, '')
198
+ if (!rel) continue
199
+ if (dir && !exists(join(dir, rel))) add('package.json#files', `files 里的 ${JSON.stringify(d)} 在盘上不存在(死条目)`)
200
+ // W9-D3 修正(2026-10-13):原写法 `!isCoveredByFiles(rel, [rel]) && …` 的第一个操作数**恒 false** ⇒
201
+ // 短路 ⇒ "files 声明了但没进包"这一支**不可达**(缺件的包会被静默发出去)。只保留真正有义的那半。
202
+ if (!paths.some((p) => p === rel || p.startsWith(`${rel}/`))) {
203
+ add('package.json#files', `files 声明的 ${JSON.stringify(d)} **没有进包**(装出来的包缺件)`)
204
+ }
205
+ }
206
+ for (const p of paths) {
207
+ if (isCoveredByFiles(p, declared) || isAutoIncluded(p)) continue
208
+ add('package.json#files', `包里有 ${JSON.stringify(p)},但它既不在 files 的覆盖范围内、也不是 npm 自动纳入的那几个 ⇒ 要么补 files、要么别发它`)
209
+ }
210
+ for (const ref of collectRuntimeRefs(manifest)) {
211
+ if (ref === 'package.json') continue
212
+ if (!paths.includes(ref)) add('package.json', `manifest 引用了运行时文件 ${JSON.stringify(ref)},但它不在包里(装完必崩)`)
213
+ }
214
+ return { problems }
215
+ }
216
+
217
+ /** 解析 `npm pack --dry-run --json` 的输出(数组;坏形状 ⇒ 抛)。 */
218
+ export function parsePackJson(text) {
219
+ const doc = JSON.parse(String(text))
220
+ const one = Array.isArray(doc) ? doc[0] : doc
221
+ if (!one || typeof one !== 'object') throw new Error('npm pack 的 JSON 里没有包对象')
222
+ return {
223
+ name: String(one.name || ''),
224
+ version: String(one.version || ''),
225
+ integrity: String(one.integrity || ''),
226
+ filename: String(one.filename || ''),
227
+ files: (one.files || []).map((f) => String(f.path || '')),
228
+ }
229
+ }
230
+
231
+ /** 审计 JSONL 里该 `(包名, 版本)` 的**最近一条**记录(读不到/坏行 ⇒ null)。 */
232
+ export function lastLedgerRecord(auditPath, pkg, version) {
233
+ let text = ''
234
+ try { text = readFileSync(auditPath, 'utf8') } catch { return null }
235
+ let hit = null
236
+ for (const line of text.split('\n')) {
237
+ const s = line.trim()
238
+ if (!s) continue
239
+ let rec = null
240
+ try { rec = JSON.parse(s) } catch { continue }
241
+ if (rec && rec.pkg === pkg && String(rec.version) === String(version)) hit = rec
242
+ }
243
+ return hit
244
+ }
245
+
246
+ // ─────────────────────────────────────────────────────────────────────────────
247
+ // IO 层
248
+ // ─────────────────────────────────────────────────────────────────────────────
249
+
250
+ function npmRun(o, args, { cwd = '', allowFail = false } = {}) {
251
+ const r = spawnSync(o.npmBin, args, {
252
+ cwd: cwd || undefined,
253
+ encoding: 'utf8',
254
+ env: { ...process.env, NPM_CONFIG_USERCONFIG: o.userconfig, NPM_CONFIG_LOGLEVEL: 'error' },
255
+ })
256
+ const out = `${r.stdout || ''}`
257
+ const err = `${r.stderr || ''}`
258
+ if (r.status !== 0 && !allowFail) {
259
+ throw new Error(`\`${o.npmBin} ${args.join(' ')}\` 退出 ${r.status}:${(err || out).trim().split('\n').slice(0, 4).join(' / ')}`)
260
+ }
261
+ return { status: r.status, out, err }
262
+ }
263
+
264
+ /** 注册表回读:`{'found',integrity}` / `{'missing'}`(E404 ⇒ 明确"没有")/ `{'unknown'}`。 */
265
+ function probeRegistry(o, pkg, version) {
266
+ const r = npmRun(o, ['view', `${pkg}@${version}`, 'dist.integrity', '--json', '--registry', o.registry], { allowFail: true })
267
+ const text = `${r.out}${r.err}`
268
+ if (/E404|404 Not Found|is not in this registry/i.test(text)) return { status: 'missing' }
269
+ if (r.status !== 0) return { status: 'unknown', why: text.trim().split('\n').slice(0, 3).join(' / ') }
270
+ let v = ''
271
+ try { v = JSON.parse(r.out.trim()) } catch { v = r.out.trim().replace(/^"|"$/g, '') }
272
+ if (!v || typeof v !== 'string') return { status: 'unknown', why: `回读不是字符串:${JSON.stringify(r.out.slice(0, 120))}` }
273
+ return { status: 'found', integrity: v }
274
+ }
275
+
276
+ const sleepMs = (ms) => new Promise((r) => setTimeout(r, ms))
277
+
278
+ /**
279
+ * 退避回读(**PUB-J5**):一次 404/空/非零**都不算失败**(实测注册表复制延迟 ~3 分钟)。
280
+ * @param {{probe:Function, max:number, intervalMs:number, want:'found'|'answer'}} o
281
+ * `want:'found'`(发布后)⇒ 只有拿到 integrity 才停;`want:'answer'`(发布前探测)⇒ 拿到
282
+ * "found 或明确 missing"即停(都不必等满预算)。
283
+ */
284
+ async function readback({ probe, max, intervalMs, want }) {
285
+ let last = { status: 'unknown' }
286
+ const attempts = []
287
+ for (let i = 1; i <= max; i += 1) {
288
+ last = probe()
289
+ attempts.push(last.status)
290
+ if (last.status === 'found') return { ...last, attempts: i }
291
+ if (want === 'answer' && last.status === 'missing') return { ...last, attempts: i }
292
+ if (i < max && intervalMs > 0) await sleepMs(intervalMs)
293
+ }
294
+ return { ...last, attempts: max, exhausted: true, attemptLog: attempts }
295
+ }
296
+
297
+ // ─────────────────────────────────────────────────────────────────────────────
298
+
299
+ async function main() {
300
+ const o = parseArgs(process.argv.slice(2))
301
+ o.repo = o.repo || REPO
302
+
303
+ // ── PUB-J0 凭据预检 ────────────────────────────────────────────────────────
304
+ const reds = []
305
+ const red = (code, where, detail) => { reds.push({ code, where, detail }); console.error(` \x1b[1;31m✗\x1b[0m [${code}] ${where} —— ${detail}`) }
306
+ // W9-D1b 修正(2026-10-13):先算清凭据**文件本身**是否合规;不合规 ⇒ ⛔ 不读它、不建 .npmrc、不跑 whoami。
307
+ // (旧实现"判红之后照用",夹具实证:mode=644 时仍把令牌真的送进了一次 `npm whoami`。)
308
+ let credFileOk = false
309
+ if (!existsSync(o.tokenFile)) {
310
+ red('PUB-J0', o.tokenFile, '凭据文件不存在(npm granular access token;见 docs/Argent-插件化评估与实施计划.md §6 决策点 4)')
311
+ } else {
312
+ const mode = statSync(o.tokenFile).mode & 0o777
313
+ if (mode !== 0o600) red('PUB-J0', o.tokenFile, `权限是 ${mode.toString(8)}(必须 600:\`chmod 600 ${o.tokenFile}\`)`)
314
+ else credFileOk = true
315
+ }
316
+
317
+ // 临时 userconfig(token 只经它传给 npm;⛔ 不写 ~/.npmrc、⛔ 不进命令行)
318
+ // W9-D1b:只有凭据**文件合规**(存在 + 600)才读它 —— 否则一个 644 的令牌会被真的送进 `npm whoami`。
319
+ let tmp = ''
320
+ let token = ''
321
+ if (credFileOk) { try { token = readFileSync(o.tokenFile, 'utf8').trim() } catch { token = '' } }
322
+ if (token) {
323
+ tmp = mkdtempSync(join(tmpdir(), 'argent-npm-publish-'))
324
+ o.userconfig = join(tmp, 'npmrc')
325
+ const host = (() => { try { return new URL(o.registry).host } catch { return 'registry.npmjs.org' } })()
326
+ writeFileSync(o.userconfig, `//${host}/:_authToken=${token}\n`, { mode: 0o600 })
327
+ chmodSync(o.userconfig, 0o600)
328
+ }
329
+ const cleanup = () => { if (tmp) { try { rmSync(tmp, { recursive: true, force: true }) } catch { /* 忽略 */ } } }
330
+ process.on('exit', cleanup)
331
+ for (const sig of ['SIGINT', 'SIGTERM']) process.on(sig, () => { cleanup(); process.exit(1) })
332
+
333
+ if (token) {
334
+ const w = npmRun(o, ['whoami', '--registry', o.registry], { allowFail: true })
335
+ const got = `${w.out}`.trim()
336
+ if (w.status !== 0 || got !== EXPECTED_USER) {
337
+ red('PUB-J0', 'npm whoami', `必须是 \`${EXPECTED_USER}\`,实得 \`${got || '(空)'}\`(exit ${w.status})—— 令牌无效/过期,或 registry 不对`)
338
+ } else {
339
+ console.log(` \x1b[32m✓\x1b[0m 凭据预检:\`npm whoami\` = ${got}(token ${o.tokenFile},mode 600)`)
340
+ }
341
+ }
342
+
343
+ // ── 安全闸门(W9-D1 修正,2026-10-13):凭据预检**判红 ⇒ 禁止 `--apply` 发布** ──────────────
344
+ // 夹具实证:旧实现里 `red()` 只入 `reds`,主循环在 `publish` 前**从不检查** ⇒ `whoami` 报别人 /
345
+ // 令牌 mode=644 时,`--apply` 仍会把包发出去(可能"发到一半",人工得反查哪些真出去了)。
346
+ // 判据:J0 段结束时 `reds` 为空 = 预检干净(更早的用法面判红会 exit 2,不会走到这里)。
347
+ const credOk = reds.length === 0
348
+
349
+ // 发布面
350
+ // ⚠️ W3(0.6.0):**智能体 bundle 由盘上现扫**(`plugins/argent-agent-*`,前缀规则)——
351
+ // 它们的名字来自 L0 货架声明(含业务线词)⇒ ⛔ 不进 `plugin-packages.mjs` 的静态表(engine-words 判据),
352
+ // 但**必须**能被本发布器认出来(否则 Lead 发不出 W3 的 6 个包)。id ↔ npm 名仍由**同一个**真源推导。
353
+ const agentRows = (() => {
354
+ try {
355
+ return readdirSync(join(o.repo, 'plugins'))
356
+ .filter((d) => isAgentBundleId(d) && existsSync(join(o.repo, 'plugins', d, 'package.json')))
357
+ .sort()
358
+ .map((id) => ({ id, npm: `@whyshu/${id}` }))
359
+ } catch { return [] }
360
+ })()
361
+ const registryRows = [...PLUGIN_PACKAGES, ...agentRows]
362
+ const wanted = o.only ? o.only.map((x) => pluginIdOf(x)).filter(Boolean) : registryRows.map((p) => p.id)
363
+ const rows = registryRows.filter((p) => wanted.includes(p.id))
364
+ const mode = o.apply ? '\x1b[1;31mAPPLY(真发布)\x1b[0m' : '\x1b[1;33mDRY-RUN(默认;只读判据 + 计划,⛔ 不发布)\x1b[0m'
365
+ console.log(`\nArgent 插件发布器 —— ${mode}`)
366
+ console.log(` registry:${o.registry}|registry 复制延迟实测 ~3 分钟 ⇒ 回读退避 ${o.retryIntervalMs}ms × 最多 ${o.retryMax} 次`)
367
+ console.log(` 发布面(${rows.length}):${rows.map((r) => r.npm).join('、')}`)
368
+ if (!o.apply) console.log(' \x1b[2m↳ dry-run 的回读只探一次("尚未可见"就是计划发布;⛔ 不必为空包等 5 分钟;给 --retry-max 可覆盖)\x1b[0m')
369
+
370
+ const plan = []
371
+ const audit = []
372
+ for (const p of rows) {
373
+ const dir = join(o.repo, 'plugins', p.id)
374
+ console.log(`\n── ${p.npm}(plugins/${p.id})`)
375
+ let manifest = null
376
+ try { manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) } catch (e) {
377
+ red('PUB-J1', `plugins/${p.id}/package.json`, `读不出/不是 JSON:${e.message}`); continue
378
+ }
379
+ if (manifest.name !== p.npm) red('PUB-J1', `plugins/${p.id}/package.json#name`, `包名是 ${JSON.stringify(manifest.name)},真源期望 ${JSON.stringify(p.npm)}`)
380
+ // 本地内容字样(`npm pack --dry-run` ⇒ 零写、可离线、integrity = 会写进注册表的那个)
381
+ let pack = null
382
+ try {
383
+ const r = npmRun(o, ['pack', '--dry-run', '--json'], { cwd: dir })
384
+ pack = parsePackJson(r.out)
385
+ } catch (e) {
386
+ red('PUB-J1', `plugins/${p.id}`, `\`npm pack --dry-run --json\` 失败:${e.message}`); continue
387
+ }
388
+ const clos = checkFilesClosure({ manifest, packFiles: pack.files, dir })
389
+ for (const x of clos.problems) red(x.code, `${x.where}`, x.detail)
390
+ if (clos.problems.length) continue
391
+ console.log(` \x1b[32m✓\x1b[0m [PUB-J1] 文件闭合:${pack.files.length} 个文件 ↔ files ${JSON.stringify(manifest.files)}`)
392
+ console.log(` \x1b[2m↳ ${pack.name}@${pack.version} integrity ${pack.integrity}\x1b[0m`)
393
+
394
+ // ── PUB-J2 版本纪律(本地台账)────────────────────────────────────────────
395
+ const rec = lastLedgerRecord(o.audit, pack.name, pack.version)
396
+ if (rec && rec.integrity && rec.integrity !== pack.integrity) {
397
+ red('PUB-J2', `${pack.name}@${pack.version}`, `本地台账记的 integrity 是 ${rec.integrity},本次 pack 是 ${pack.integrity}`
398
+ + ' ⇒ **内容变了、版本没变**(审计 JSONL 是"发过什么"的唯一凭证)⇒ 提版本再发,⛔ 绝不覆盖')
399
+ continue
400
+ }
401
+ if (rec) console.log(` \x1b[32m✓\x1b[0m [PUB-J2] 版本纪律:台账里 ${pack.name}@${pack.version} 的 integrity 与本机一致`)
402
+
403
+ // ── PUB-J3 / J4:注册表现状 ───────────────────────────────────────────────
404
+ const probeMax = o.apply ? Math.min(3, o.retryMax) : 1
405
+ const pre = await readback({ probe: () => probeRegistry(o, pack.name, pack.version), max: probeMax, intervalMs: o.retryIntervalMs, want: 'answer' })
406
+ if (pre.status === 'found') {
407
+ if (pre.integrity === pack.integrity) {
408
+ console.log(` \x1b[32m✓\x1b[0m [PUB-J3] SKIP ${pack.name}@${pack.version}(注册表 integrity 与本地一致;幂等 ⇒ 无需重发)`)
409
+ plan.push({ pkg: pack.name, version: pack.version, integrity: pack.integrity, action: 'skip' })
410
+ audit.push({ pkg: pack.name, version: pack.version, integrity: pack.integrity, action: 'skipped' })
411
+ continue
412
+ }
413
+ red('PUB-J4', `${pack.name}@${pack.version}`, `注册表上已有该版本,但 dist.integrity = ${pre.integrity},本机 pack = ${pack.integrity}`
414
+ + ' ⇒ **已发布但不一致**(⛔ 绝不覆盖:同名同版本的字节不可变)⇒ 提版本再发')
415
+ continue
416
+ }
417
+ if (pre.status === 'unknown') {
418
+ red('PUB-J5', `${pack.name}@${pack.version}`, `注册表探测失败(不是 404,是读不到):${pre.why || '(无细节)'}`
419
+ + ' ⇒ 先确认 `--registry` / 网络 / 凭据,再重跑(本脚本幂等)')
420
+ continue
421
+ }
422
+ // 明确"注册表上还没有这个版本" ⇒ 计划发布
423
+ console.log(` \x1b[33m•\x1b[0m ${pack.name}@${pack.version} 尚未在注册表可见 ⇒ ${o.apply ? '发布' : '**计划发布**'}`)
424
+ if (!o.apply) { plan.push({ pkg: pack.name, version: pack.version, integrity: pack.integrity, action: 'publish(plan)' }); continue }
425
+
426
+ // W9-D1 安全闸门:预检不干净 ⇒ **拒绝发布**(⛔ 不允许"边判红边发")
427
+ if (!credOk) {
428
+ red('PUB-J0', `${pack.name}@${pack.version}`, '凭据预检未通过(见上)⇒ **拒绝发布**:⛔ 不允许"边判红边发"(否则可能发到一半,人工得反查哪些真出去了)')
429
+ continue
430
+ }
431
+
432
+ // ── 真发布 + PUB-J5 回读退避 ─────────────────────────────────────────────
433
+ let pubOk = false
434
+ try {
435
+ const r = npmRun(o, ['publish', '--access', 'public', '--registry', o.registry], { cwd: dir })
436
+ pubOk = true
437
+ const line = `${r.out}${r.err}`.trim().split('\n').filter(Boolean).slice(-2).join(' / ')
438
+ if (line) console.log(` \x1b[2m↳ npm: ${line}\x1b[0m`)
439
+ } catch (e) {
440
+ red('PUB-J6', pack.name, `\`npm publish\` 失败:${e.message}`)
441
+ continue
442
+ }
443
+ if (!pubOk) continue
444
+ console.log(` \x1b[32m✓\x1b[0m 已发布 ${pack.name}@${pack.version} ⇒ 回读 dist.integrity(退避 ${o.retryIntervalMs}ms × ${o.retryMax})`)
445
+ const post = await readback({ probe: () => probeRegistry(o, pack.name, pack.version), max: o.retryMax, intervalMs: o.retryIntervalMs, want: 'found' })
446
+ if (post.status !== 'found') {
447
+ red('PUB-J5', `${pack.name}@${pack.version}`, `回读用尽 ${post.attempts} 次仍读不到 dist.integrity`
448
+ + `(状态序列 ${JSON.stringify(post.attemptLog || [])})—— 注册表复制可能还在路上(实测 ~3 分钟)`
449
+ + ';**发布本身已成功**,稍后重跑本脚本会走"幂等跳过"这条路(⛔ 不要重复手工发布)')
450
+ // W9-D2 修正(2026-10-13):发布**已经发生** ⇒ 必须留痕(原实现 `continue` 在 `audit.push` 之前,
451
+ // 恰恰在最需要凭据的"发了但验不了"场景下不留任何记录,与文件头 J6 冲突)。
452
+ audit.push({ pkg: pack.name, version: pack.version, integrity: '(unverified)', action: 'published-unverified' })
453
+ continue
454
+ }
455
+ if (post.integrity !== pack.integrity) {
456
+ red('PUB-J5', `${pack.name}@${pack.version}`, `回读到的 integrity 是 ${post.integrity},本机 pack 是 ${pack.integrity}`
457
+ + ' ⇒ 注册表上的字节**不是**我们刚发布的那些(可疑:有人在并发发布同名版本)')
458
+ audit.push({ pkg: pack.name, version: pack.version, integrity: String(post.integrity || '(unverified)'), action: 'published-unverified' })
459
+ continue
460
+ }
461
+ console.log(` \x1b[32m✓\x1b[0m [PUB-J5] 回读一致(第 ${post.attempts} 次):${post.integrity}`)
462
+ plan.push({ pkg: pack.name, version: pack.version, integrity: pack.integrity, action: 'published' })
463
+ audit.push({ pkg: pack.name, version: pack.version, integrity: pack.integrity, action: 'published' })
464
+ }
465
+
466
+ // ── PUB-J6 审计 ────────────────────────────────────────────────────────────
467
+ if (o.apply && audit.length) {
468
+ try {
469
+ mkdirSync(dirname(o.audit), { recursive: true })
470
+ let source = 'unknown'
471
+ try { source = execFileSync('git', ['-C', o.repo, 'rev-parse', '--short', 'HEAD'], { encoding: 'utf8' }).trim() || 'unknown' } catch { source = 'unknown' }
472
+ const at = new Date().toISOString()
473
+ const lines = audit.map((a) => `${JSON.stringify({ at, pkg: a.pkg, version: a.version, integrity: a.integrity, action: a.action, source, by: EXPECTED_USER })}\n`).join('')
474
+ appendFileSync(o.audit, lines, { mode: 0o600 })
475
+ console.log(`\n \x1b[32m✓\x1b[0m [PUB-J6] 审计已追加 ${audit.length} 行 → ${o.audit}(来源提交 ${source};⛔ 不含令牌)`)
476
+ } catch (e) {
477
+ red('PUB-J6', o.audit, `审计写入失败:${e.message}`)
478
+ }
479
+ }
480
+
481
+ // ── 汇总 ───────────────────────────────────────────────────────────────────
482
+ console.log('\n── 计划/结果')
483
+ for (const x of plan) console.log(` ${x.action === 'published' ? '\x1b[32m已发布\x1b[0m' : x.action === 'skip' ? '\x1b[36m跳过\x1b[0m' : '\x1b[33m待发布\x1b[0m'} ${x.pkg}@${x.version}`)
484
+ if (!plan.length && !reds.length) console.log(' (无)')
485
+ if (reds.length) {
486
+ console.error(`\n\x1b[1;31m✗ ${reds.length} 条判据判红(分布:${[...new Set(reds.map((r) => r.code))].join('、')})\x1b[0m`)
487
+ console.error(' ⛔ 本脚本**不会**覆盖已发布的同名同版本;红的含义是"停下来提版本/修文件/修凭据"。')
488
+ process.exit(1)
489
+ }
490
+ console.log(`\n\x1b[32m✅ 全绿\x1b[0m(${plan.filter((x) => x.action === 'published').length} 已发布 / ${plan.filter((x) => x.action === 'skip').length} 跳过 / `
491
+ + `${plan.filter((x) => String(x.action).startsWith('publish')).length - plan.filter((x) => x.action === 'published').length} 待发布)`)
492
+ if (o.apply) console.log(` 真发布用的一条命令就是刚才这条:--apply(重跑安全:已发布且一致 ⇒ 跳过)`)
493
+ }
494
+
495
+ // 只有直接执行才跑 main(允许本文件被回归用例 import 复用纯函数)
496
+ const isDirect = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)
497
+ if (isDirect) {
498
+ main().catch((e) => { console.error(`\n\x1b[1;31m✗ 发布器异常:${e && e.message ? e.message : e}\x1b[0m`); process.exit(1) })
499
+ }
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # `@whyshu/argent-*` 六包发布器 —— **入口**(W9,2026-10-12)。默认 **dry-run**。
3
+ #
4
+ # 为什么只是一层薄包装:真正的判据与 IO(`npm pack --json` 解析、`files` 闭合双向比对、
5
+ # `dist.integrity` 退避回读、审计 JSONL)全在 `scripts/npm-publish-argent.mjs` 里 ——
6
+ # 用 shell 拼 JSON 的失败方式是"静默取到 undefined",正是本仓库反复吃亏的那一类形态。
7
+ # 名字留 `.sh` 是因为交付物与运维惯例都按它来;行为口径见 .mjs 的文件头(判据表 PUB-J0…J6)。
8
+ #
9
+ # 用法:
10
+ # bash scripts/npm-publish-argent.sh # dry-run(只读判据 + 计划)
11
+ # bash scripts/npm-publish-argent.sh --apply # 真发布(⛔ 不可撤销;重跑安全=幂等)
12
+ # bash scripts/npm-publish-argent.sh --help
13
+ set -euo pipefail
14
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ NODE_BIN="${ARGENT_NODE_BIN:-node}"
16
+ command -v "$NODE_BIN" >/dev/null 2>&1 || {
17
+ echo "✗ 找不到 node(\$ARGENT_NODE_BIN=${ARGENT_NODE_BIN:-<空>})—— 发布器需要 node ≥ 20" >&2
18
+ exit 2
19
+ }
20
+ exec "$NODE_BIN" "$HERE/npm-publish-argent.mjs" "$@"