@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.
- package/MANIFEST.json +341 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +378 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/collect-evidence.mjs +42 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +135 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +47 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +261 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/final-check.mjs +43 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1025 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2278 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +195 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent-datadir.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +137 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +74 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +35 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +738 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +22 -4
- package/requirements.txt +5 -0
- 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" "$@"
|