@whyshu/argent-engine 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.json +307 -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 +575 -0
- package/engine/scripts/billing-mode-daily.sh +12 -0
- package/engine/scripts/build-engine-package.mjs +123 -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 +377 -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/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 +134 -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 +46 -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 +260 -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/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +720 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +86 -0
- package/engine/scripts/flow-timers.sh +182 -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 +1024 -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 +2275 -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 +194 -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.py +262 -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 +293 -0
- package/engine/scripts/poll-invoice.sh +74 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +70 -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/run-tests.sh +256 -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 +136 -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 +73 -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 +34 -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 +216 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
- package/engine/scripts/ziniao-windows.sh +153 -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 +706 -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 +21 -4
- package/README.md +0 -3
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ════════════════════════════════════════════════════════════════════════════════
|
|
3
|
+
# scripts/ci-run.sh —— Argent **自建 CI runner**:一次跑完**固定判据集合** + 互斥 + 可追溯记录
|
|
4
|
+
#
|
|
5
|
+
# 为什么有它(对着 1.0 判据表的 G6「全门禁进 CI」):
|
|
6
|
+
# · 全量套件实测 169/0、约 312–389 秒;本机常有**多代理并发** ⇒ 并发跑套件会出现**瞬时假红**
|
|
7
|
+
# (`argent-datadir-parity` / `capability-pack` 各实测一次,**单跑即绿**)。
|
|
8
|
+
# 故 CI 的第一价值**不是"跑得更多"**,而是三件事:
|
|
9
|
+
# ① **串行化**:同一时刻只允许一份 CI(互斥锁)⇒ 从根上消掉并发假红;
|
|
10
|
+
# ② **固定判据集合**:15 条写死在本文件里,不靠"这次记得跑哪几条";
|
|
11
|
+
# ③ **可追溯记录**:每次运行落一份 JSON(每条判据的退出码/耗时/摘要行 + git HEAD/dirty +
|
|
12
|
+
# 机器与版本),失败时**点名**是哪条 ⇒ "这次为什么红"一眼可查。
|
|
13
|
+
#
|
|
14
|
+
# 用法:
|
|
15
|
+
# bash scripts/ci-run.sh # 全量判据(顺序 = 由快到慢;全量套件倒数第二,环境哨兵最后)
|
|
16
|
+
# bash scripts/ci-run.sh --list # 只列判据清单(名字/命令/期望输出/超时),**不加锁**
|
|
17
|
+
# bash scripts/ci-run.sh --only <a,b,...> # 只跑指定判据(开发期省时间;记录里标 partial=true)
|
|
18
|
+
# bash scripts/ci-run.sh --quiet # 只打印失败项与汇总
|
|
19
|
+
# 环境变量:ARGENT_CI_HOME=<目录>(缺省 ~/.dsh/argent/ci)|CI_TIMEOUT_<判据名大写> 覆盖该条超时
|
|
20
|
+
#
|
|
21
|
+
# 退出码:
|
|
22
|
+
# 0 全部判据通过
|
|
23
|
+
# 1 任一判据失败(末尾**点名**失败的判据 + 摘要行 + 日志段)
|
|
24
|
+
# 2 用法错误(未知参数 / 未知判据名 / 仓库不对)
|
|
25
|
+
# 75 已有 CI 在跑(TEMPFAIL:**不排队、不等待**,按 sysexits 惯例 75 = 临时故障)
|
|
26
|
+
#
|
|
27
|
+
# 锁语义(`~/.dsh/argent/ci/lock`,用 `mkdir` 的**原子性**做互斥;macOS 无 flock(1)):
|
|
28
|
+
# · 抢不到锁(锁在、pid 还活着)⇒ 立刻 exit 75 并打印「已有 CI 在跑 (pid …)」;
|
|
29
|
+
# · **陈旧锁**(锁在、pid 已不在)⇒ 打印接管提示后接管 —— 否则一次 kill -9 会让 CI **永久锁死**;
|
|
30
|
+
# ⛔ 这是唯一的"自动接管"路径,且只在 pid 确实不存在时触发;
|
|
31
|
+
# · 拿锁者只删**自己**的锁(EXIT/INT/TERM 清理),绝不删别人的。
|
|
32
|
+
#
|
|
33
|
+
# ⛔ 只读纪律(本脚本及其判据集合都不许违反):
|
|
34
|
+
# · 不 push、不部署、不连客户机、不改凭据/签名密钥、**不跑包管理器**(pnpm/npm install);
|
|
35
|
+
# · **不** git checkout / clean / stash —— git 状态**只只读采集**(HEAD / 分支 / dirty 清单),
|
|
36
|
+
# 因为本仓库常有**他人未提交的在制品**(一次 checkout 就毁了);
|
|
37
|
+
# · 唯一的本地写入只有两处:① 本脚本的记录与日志(`~/.dsh/argent/ci/**`);
|
|
38
|
+
# ② `sync-scripts --no-pull` 按**它自己的契约**把仓库 scripts/core/configs/skills/verticals/deploy 重新投递到
|
|
39
|
+
# 本机安装克隆(`~/.argent-dsh`)—— `--no-pull` 去掉唯一的网络动作;两处都不是生产、都不出网。
|
|
40
|
+
#
|
|
41
|
+
# 纪律(判据措辞即判据):每条判据除退出码外还要求输出**命中 `expect` 正则**(见 `criterion_expect`),
|
|
42
|
+
# 防"脚本跑了但什么都没查"的假绿(例:套件必须打印 `测试结果:N 通过 / 0 失败` 且 N≥1)。
|
|
43
|
+
# 改判据输出措辞 ⇒ 这里会转红并点名 —— 那是**有意的**:请同步改本文件的 expect。
|
|
44
|
+
# ════════════════════════════════════════════════════════════════════════════════
|
|
45
|
+
set -uo pipefail
|
|
46
|
+
|
|
47
|
+
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
48
|
+
REPO="${ARGENT_DSH_REPO:-$(cd "${SELF_DIR}/.." && pwd -P)}"
|
|
49
|
+
|
|
50
|
+
# ── 解释器判据(与 run-tests.sh 同源:venv 有就用 venv)──────────────────────────
|
|
51
|
+
PY="${ARGENT_PY:-$HOME/.dsh/argent/venv/bin/python3}"
|
|
52
|
+
[ -x "$PY" ] || PY="$(command -v python3 || echo python3)"
|
|
53
|
+
PYVER="$("$PY" -V 2>&1 | head -1 || true)"
|
|
54
|
+
|
|
55
|
+
# CI 自己的目录 = **数据目录** 下的 `ci/`。⛔ 数据目录**只认唯一判据**(CLI 包装
|
|
56
|
+
# `scripts/argent-datadir.py`),**不在 shell 里重写三级链**(不写 `$HOME/.dsh/argent`、
|
|
57
|
+
# 不读那两个实例级开关)—— 这条由 `scripts/tests/argent-datadir-parity.test.mjs`【4】守着,
|
|
58
|
+
# **2026-10-05 本 CI 首跑就被它抓到过**(第一版正是自写硬编码 + 读 ARGENT_DATA_DIR ⇒ 套件判红)。
|
|
59
|
+
DATA_DIR="$("$PY" "${REPO}/scripts/argent-datadir.py" 2>/dev/null || true)"
|
|
60
|
+
if [ -z "${DATA_DIR}" ]; then
|
|
61
|
+
echo "✗ 数据目录判据不可用(${REPO}/scripts/argent-datadir.py 取不到)⇒ 停住,不猜目录" >&2
|
|
62
|
+
echo " (锁/记录/日志都落在数据目录下,猜错目录等于把记录写到别处)" >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
CI_HOME="${ARGENT_CI_HOME:-${DATA_DIR}/ci}"
|
|
66
|
+
LOCK="${CI_HOME}/lock"
|
|
67
|
+
LOGS="${CI_HOME}/logs"
|
|
68
|
+
|
|
69
|
+
usage() {
|
|
70
|
+
cat <<'EOF'
|
|
71
|
+
用法:
|
|
72
|
+
bash scripts/ci-run.sh # 全量判据(由快到慢)
|
|
73
|
+
bash scripts/ci-run.sh --list # 列判据清单(不加锁)
|
|
74
|
+
bash scripts/ci-run.sh --only <a,b,...> # 只跑指定判据(记录标 partial)
|
|
75
|
+
bash scripts/ci-run.sh --quiet # 只打印失败项与汇总
|
|
76
|
+
退出码:0 全绿 | 1 有判据红 | 2 用法错误 | 75 已有 CI 在跑(TEMPFAIL)
|
|
77
|
+
EOF
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ARGV_STR="$*" # 原始参数(记录用;下面的解析会 shift 掉)
|
|
81
|
+
|
|
82
|
+
# ── 参数 ───────────────────────────────────────────────────────────────────────
|
|
83
|
+
ONLY=""; QUIET=0; DO_LIST=0
|
|
84
|
+
while [ $# -gt 0 ]; do
|
|
85
|
+
case "$1" in
|
|
86
|
+
--list) DO_LIST=1; shift ;;
|
|
87
|
+
--only)
|
|
88
|
+
[ $# -ge 2 ] || { echo "✗ --only 缺值(用法:--only <名字,名字>;--list 看名字)" >&2; exit 2; }
|
|
89
|
+
ONLY="${ONLY:+${ONLY},}$2"; shift 2 ;;
|
|
90
|
+
--only=*)
|
|
91
|
+
ONLY="${ONLY:+${ONLY},}${1#--only=}"; shift ;;
|
|
92
|
+
--quiet|-q) QUIET=1; shift ;;
|
|
93
|
+
-h|--help) usage; exit 0 ;;
|
|
94
|
+
*) echo "✗ 未知参数:$1" >&2; usage >&2; exit 2 ;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
# ── 毫秒时钟(记录耗时用;bash 3.2 没有 EPOCHREALTIME,故按可用工具降级)───────────
|
|
99
|
+
MS_TOOL=""
|
|
100
|
+
if command -v perl >/dev/null 2>&1; then MS_TOOL=perl
|
|
101
|
+
elif command -v "${PY}" >/dev/null 2>&1; then MS_TOOL=python
|
|
102
|
+
else MS_TOOL=date; fi
|
|
103
|
+
now_ms() {
|
|
104
|
+
case "${MS_TOOL}" in
|
|
105
|
+
perl) perl -MTime::HiRes=time -e 'printf "%.0f\n", time*1000' ;;
|
|
106
|
+
python) "${PY}" -c 'import time;print(int(time.time()*1000))' ;;
|
|
107
|
+
*) echo "$(( $(date +%s) * 1000 ))" ;;
|
|
108
|
+
esac
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# ── 进程树终止(超时用;macOS 没有 timeout(1))──────────────────────────────────
|
|
112
|
+
kill_tree() {
|
|
113
|
+
local p="$1" c
|
|
114
|
+
for c in $(pgrep -P "${p}" 2>/dev/null || true); do kill_tree "${c}"; done
|
|
115
|
+
kill -TERM "${p}" 2>/dev/null || true
|
|
116
|
+
return 0
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ── 判据集合(**唯一的清单**;顺序 = 执行顺序 = 由快到慢)─────────────────────────
|
|
120
|
+
# 设计口径:① 几秒的判据先跑(红了立刻看得见)② 全量套件倒数第二(最慢)
|
|
121
|
+
# ③ `test-env-safety` **最后**跑 —— 它比对的是本次 CI 开始时拍的快照 ⇒ 覆盖整个窗口。
|
|
122
|
+
CRITERIA="kernel-purity vertical-lint docs-drift host-matrix sample-redaction 1.0-criteria judgment-drift capability-pack pack-closure config-catalog role-mapping skill-classification production-home plugin-bootcheck sync-scripts suite test-env-safety"
|
|
123
|
+
|
|
124
|
+
criterion_title() {
|
|
125
|
+
case "$1" in
|
|
126
|
+
kernel-purity) printf '%s' '内核纯净度(L0/L1 不引宿主;scripts/ 未归类即红)' ;;
|
|
127
|
+
vertical-lint) printf '%s' '业务线 lint(命名空间/跨线引用/引擎业务词/manifest 自洽)' ;;
|
|
128
|
+
docs-drift) printf '%s' '文档漂移(--strict:可疑 0 条;含 R4 矩阵结构)' ;;
|
|
129
|
+
host-matrix) printf '%s' '宿主矩阵结构(--strict --matrix-only:列数/空标签/状态符号)' ;;
|
|
130
|
+
sample-redaction) printf '%s' '官网样例脱敏(--strict:禁用串 0 命中 + 键名与指标清单对齐)' ;;
|
|
131
|
+
# 1.0 判据表:§2.0(八面总览)与 §2.1–§2.8(判据行)的**计数自洽**(唯一真源 = 判据行)
|
|
132
|
+
1.0-criteria) printf '%s' '1.0 判据计数自洽(§2.0 复算:40 条 + 逐面计数 + 卡点非空)' ;;
|
|
133
|
+
judgment-drift) printf '%s' '判断漂移(要求 R1=0:契约漂移 0 条)' ;;
|
|
134
|
+
capability-pack) printf '%s' '能力包自洽(--validate:版本/文件/流程/preset/提示词)' ;;
|
|
135
|
+
pack-closure) printf '%s' '能力包闭包(--strict:缺件 0 个)' ;;
|
|
136
|
+
# 2026-10-09:配置地基的四道门禁(双向对账 / 敏感不出门 / 必须项可解析 / 新增即生效)
|
|
137
|
+
config-catalog) printf '%s' '配置目录四道门禁(--strict:双向对账 + 敏感不出门 + 必须项可解析 + 新增即生效)' ;;
|
|
138
|
+
role-mapping) printf '%s' '角色-技能声明映射(声明的技能都真实存在)' ;;
|
|
139
|
+
skill-classification) printf '%s' '技能归类(外发/内部两边齐全,未归类 0)' ;;
|
|
140
|
+
production-home) printf '%s' '本机姿态(开发机档:launchctl 里 flow-trigger = 0)' ;;
|
|
141
|
+
plugin-bootcheck) printf '%s' '插件启动冒烟(隔离 home;preset 全挂载)' ;;
|
|
142
|
+
sync-scripts) printf '%s' '安装克隆对账(--no-pull:scripts/core/configs/skills/verticals/deploy 与仓库一致)' ;;
|
|
143
|
+
suite) printf '%s' '全量测试套件(scripts/run-tests.sh 的枚举面)' ;;
|
|
144
|
+
test-env-safety) printf '%s' '测试环境卫生哨兵(--verify:真实数据目录关键文件未变)' ;;
|
|
145
|
+
*) printf '%s' '(未知判据)' ;;
|
|
146
|
+
esac
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
criterion_cmd() {
|
|
150
|
+
case "$1" in
|
|
151
|
+
kernel-purity) printf '%s' 'node scripts/tests/kernel-purity.test.mjs' ;;
|
|
152
|
+
vertical-lint) printf '%s' 'node scripts/vertical-lint.mjs' ;;
|
|
153
|
+
docs-drift) printf '%s' 'node scripts/check-docs-drift.mjs --strict' ;;
|
|
154
|
+
host-matrix) printf '%s' 'node scripts/check-docs-drift.mjs --strict --matrix-only' ;;
|
|
155
|
+
sample-redaction) printf '%s' 'node scripts/check-sample-redaction.mjs --strict' ;;
|
|
156
|
+
1.0-criteria) printf '%s' 'node scripts/check-1.0-criteria.mjs --strict' ;;
|
|
157
|
+
judgment-drift) printf '%s' 'node scripts/check-judgment-drift.mjs' ;;
|
|
158
|
+
capability-pack) printf '%s' "${PY} scripts/capability_pack.py --validate" ;;
|
|
159
|
+
pack-closure) printf '%s' 'node scripts/check-pack-closure.mjs --strict' ;;
|
|
160
|
+
config-catalog) printf '%s' 'node scripts/check-config-catalog.mjs --strict' ;;
|
|
161
|
+
role-mapping) printf '%s' 'node scripts/check-role-mapping.mjs' ;;
|
|
162
|
+
skill-classification) printf '%s' 'node scripts/check-skill-classification.mjs' ;;
|
|
163
|
+
production-home) printf '%s' 'node scripts/check-production-home.mjs --json' ;;
|
|
164
|
+
plugin-bootcheck) printf '%s' 'node scripts/plugin-bootcheck.mjs' ;;
|
|
165
|
+
sync-scripts) printf '%s' 'bash scripts/sync-scripts.sh --no-pull' ;;
|
|
166
|
+
suite) printf '%s' 'bash scripts/run-tests.sh' ;;
|
|
167
|
+
test-env-safety)
|
|
168
|
+
if [ -n "${ENV_SNAP:-}" ]; then printf '%s' "node scripts/check-test-env-safety.mjs --verify ${ENV_SNAP}"
|
|
169
|
+
else printf '%s' 'node scripts/check-test-env-safety.mjs --verify <运行期快照文件>'; fi ;;
|
|
170
|
+
*) printf '%s' '' ;;
|
|
171
|
+
esac
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# 每条判据的**期望输出**(多行 = 全部必须命中;⚠️ 只用 POSIX ERE:BSD grep 不认 \s 这类简写)
|
|
175
|
+
criterion_expect() {
|
|
176
|
+
case "$1" in
|
|
177
|
+
kernel-purity) printf '%s\n' 'kernel-purity: [1-9][0-9]* 通过 / 0 失败' ;;
|
|
178
|
+
vertical-lint) printf '%s\n' '结果:✅ 通过' ;;
|
|
179
|
+
docs-drift) printf '%s\n' '可疑 0 条' ;;
|
|
180
|
+
host-matrix) printf '%s\n' '矩阵结构:0 处' '可疑 0 条' ;;
|
|
181
|
+
sample-redaction) printf '%s\n' '禁用串命中 0 处' '结构与键名一致' ;;
|
|
182
|
+
1.0-criteria) printf '%s\n' '判据条数:40 条' '结论:✅ 一致' ;;
|
|
183
|
+
judgment-drift) printf '%s\n' '契约漂移 0 条' ;;
|
|
184
|
+
capability-pack) printf '%s\n' '每个能力包都自洽' ;;
|
|
185
|
+
pack-closure) printf '%s\n' '缺件 0 个' ;;
|
|
186
|
+
config-catalog) printf '%s\n' '四道门禁全绿' ;;
|
|
187
|
+
role-mapping) printf '%s\n' '已声明的技能都真实存在' ;;
|
|
188
|
+
skill-classification) printf '%s\n' '未归类 0' ;;
|
|
189
|
+
production-home) printf '%s\n' '"countOk":[[:space:]]*true' '"flowTriggers":[[:space:]]*0' ;;
|
|
190
|
+
plugin-bootcheck) printf '%s\n' 'DONE fail=0' '启动冒烟通过' ;;
|
|
191
|
+
sync-scripts) printf '%s\n' '已与仓库一致' ;;
|
|
192
|
+
suite) printf '%s\n' '测试结果:[1-9][0-9]* 通过 / 0 失败' ;;
|
|
193
|
+
test-env-safety) printf '%s\n' '关键文件未变' ;;
|
|
194
|
+
*) printf '%s\n' '' ;;
|
|
195
|
+
esac
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# 摘要行抽取:优先取**最后一条**命中 summaryRe 的行;抽不到 ⇒ 退化为末条非空行(绝不空着)
|
|
199
|
+
criterion_summary_re() {
|
|
200
|
+
case "$1" in
|
|
201
|
+
kernel-purity) printf '%s' 'kernel-purity: ' ;;
|
|
202
|
+
vertical-lint) printf '%s' '结果:' ;;
|
|
203
|
+
docs-drift) printf '%s' '可疑 [0-9]+ 条' ;;
|
|
204
|
+
host-matrix) printf '%s' '矩阵结构:' ;;
|
|
205
|
+
sample-redaction) printf '%s' '结论:' ;;
|
|
206
|
+
1.0-criteria) printf '%s' '结论:' ;;
|
|
207
|
+
judgment-drift) printf '%s' '结论:' ;;
|
|
208
|
+
capability-pack) printf '%s' '每个能力包都自洽' ;;
|
|
209
|
+
pack-closure) printf '%s' '闭包体检:' ;;
|
|
210
|
+
config-catalog) printf '%s' '结果:' ;;
|
|
211
|
+
role-mapping) printf '%s' '技能目录:' ;;
|
|
212
|
+
skill-classification) printf '%s' '结论:' ;;
|
|
213
|
+
production-home) printf '%s' '"flowTriggers":' ;;
|
|
214
|
+
plugin-bootcheck) printf '%s' '启动冒烟通过' ;;
|
|
215
|
+
sync-scripts) printf '%s' '已与仓库一致' ;;
|
|
216
|
+
suite) printf '%s' '测试结果:' ;;
|
|
217
|
+
test-env-safety) printf '%s' '结论:' ;;
|
|
218
|
+
*) printf '%s' '' ;;
|
|
219
|
+
esac
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# 超时(秒):全量套件 312–389s 实测 ⇒ 给 30 分钟余量;可用 CI_TIMEOUT_<判据名大写> 覆盖
|
|
223
|
+
criterion_timeout() {
|
|
224
|
+
local n ov
|
|
225
|
+
n="$(printf '%s' "$1" | tr 'a-z-' 'A-Z_')"
|
|
226
|
+
ov="$(printenv "CI_TIMEOUT_${n}" 2>/dev/null || true)"
|
|
227
|
+
if [ -n "$ov" ]; then printf '%s' "$ov"; return 0; fi
|
|
228
|
+
case "$1" in
|
|
229
|
+
suite) printf '%s' 1800 ;;
|
|
230
|
+
sync-scripts|plugin-bootcheck) printf '%s' 600 ;;
|
|
231
|
+
production-home) printf '%s' 120 ;;
|
|
232
|
+
*) printf '%s' 300 ;;
|
|
233
|
+
esac
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
in_set() { case " ${CRITERIA} " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
|
|
237
|
+
|
|
238
|
+
# ── 由快到慢的执行顺序里筛出本次要跑的(保持 CRITERIA 的先后)─────────────────────
|
|
239
|
+
ONLY_NORM=""
|
|
240
|
+
if [ -n "${ONLY}" ]; then
|
|
241
|
+
ONLY_NORM="$(printf '%s' "${ONLY}" | tr ',' ' ')"
|
|
242
|
+
for n in ${ONLY_NORM}; do
|
|
243
|
+
in_set "${n}" || { echo "✗ 未知判据:${n}(用 --list 看清单)" >&2; exit 2; }
|
|
244
|
+
done
|
|
245
|
+
fi
|
|
246
|
+
SELECTED=""
|
|
247
|
+
for c in ${CRITERIA}; do
|
|
248
|
+
if [ -z "${ONLY_NORM}" ]; then SELECTED="${SELECTED}${SELECTED:+ }${c}"; continue; fi
|
|
249
|
+
case " ${ONLY_NORM} " in *" ${c} "*) SELECTED="${SELECTED}${SELECTED:+ }${c}" ;; esac
|
|
250
|
+
done
|
|
251
|
+
PARTIAL=0
|
|
252
|
+
[ "${SELECTED}" = "${CRITERIA}" ] || PARTIAL=1
|
|
253
|
+
|
|
254
|
+
TOTAL_ALL=0; for c in ${CRITERIA}; do TOTAL_ALL=$((TOTAL_ALL + 1)); done
|
|
255
|
+
TOTAL_N=0; for c in ${SELECTED}; do TOTAL_N=$((TOTAL_N + 1)); done
|
|
256
|
+
|
|
257
|
+
# ── --list:只列清单(不加锁、不建目录、不跑任何判据)────────────────────────────
|
|
258
|
+
if [ "${DO_LIST}" = 1 ]; then
|
|
259
|
+
echo "Argent 自建 CI —— 判据清单(${TOTAL_ALL} 条;执行顺序 = 由快到慢,全量套件倒数第二、环境哨兵最后)"
|
|
260
|
+
echo " 仓库:${REPO}"
|
|
261
|
+
echo " 记录:${CI_HOME}/<UTC 时间戳>.json(并更新 latest.json / latest-full.json)| 日志:${LOGS}/<ts>.log"
|
|
262
|
+
echo
|
|
263
|
+
i=0
|
|
264
|
+
for c in ${CRITERIA}; do
|
|
265
|
+
i=$((i + 1))
|
|
266
|
+
printf ' %2d. %-20s %s\n' "$i" "$c" "$(criterion_title "$c")"
|
|
267
|
+
printf ' 命令:%s\n' "$(criterion_cmd "$c")"
|
|
268
|
+
printf ' 期望:%s\n' "$(criterion_expect "$c" | tr '\n' ' ')"
|
|
269
|
+
printf ' 超时:%ss\n' "$(criterion_timeout "$c")"
|
|
270
|
+
done
|
|
271
|
+
echo
|
|
272
|
+
echo " 跑全部:bash scripts/ci-run.sh | 跑一条:bash scripts/ci-run.sh --only <名字>"
|
|
273
|
+
exit 0
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# ── 仓库自检(判据都要在仓库里跑)───────────────────────────────────────────────
|
|
277
|
+
if [ ! -f "${REPO}/scripts/run-tests.sh" ]; then
|
|
278
|
+
echo "✗ 仓库不对:${REPO} 里没有 scripts/run-tests.sh(用 ARGENT_DSH_REPO=<仓库路径> 指定)" >&2
|
|
279
|
+
exit 2
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
# ── 遥测总闸(⛔ CI 绝不发遥测;与 run-tests.sh 同源)───────────────────────────
|
|
283
|
+
export ARGENT_TELEMETRY_OFF=1
|
|
284
|
+
|
|
285
|
+
# ── 互斥锁(mkdir 原子创建)────────────────────────────────────────────────────
|
|
286
|
+
LOCK_STALE=0
|
|
287
|
+
# ⚠️ 必须先建父目录:`mkdir <锁>` 在**父目录不存在**时失败原因是 **ENOENT**(不是 EEXIST)——
|
|
288
|
+
# 2026-10-05 首跑实测踩到:`~/.dsh/argent/ci` 尚不存在 ⇒ 第一版把 ENOENT 误判成"已有 CI 在跑"
|
|
289
|
+
# ⇒ **首次运行假 75**。故先 `mkdir -p`,且 mkdir 失败后必须分清"锁真的在"与"根本建不出来"。
|
|
290
|
+
mkdir -p "${CI_HOME}" "${LOGS}"
|
|
291
|
+
if ! mkdir "${LOCK}" 2>/dev/null; then
|
|
292
|
+
if [ ! -d "${LOCK}" ]; then
|
|
293
|
+
echo "✗ 建锁失败(不是锁竞争):${LOCK} —— 检查父目录是否存在且可写" >&2
|
|
294
|
+
exit 2
|
|
295
|
+
fi
|
|
296
|
+
OWNER_PID="$(cat "${LOCK}/pid" 2>/dev/null | tr -dc '0-9' || true)"
|
|
297
|
+
OWNER_AT="$(cat "${LOCK}/started" 2>/dev/null || true)"
|
|
298
|
+
if [ -n "${OWNER_PID}" ] && kill -0 "${OWNER_PID}" 2>/dev/null; then
|
|
299
|
+
echo "⛔ 已有 CI 在跑 (pid ${OWNER_PID},起于 ${OWNER_AT:-未知}) ⇒ 本次不排队,直接退出(exit 75)"
|
|
300
|
+
echo " 锁:${LOCK}(要立刻跑,请等它结束;⛔ 不要手删活锁)"
|
|
301
|
+
exit 75
|
|
302
|
+
fi
|
|
303
|
+
echo "⚠️ 发现陈旧锁(pid ${OWNER_PID:-未知} 已不在,起于 ${OWNER_AT:-未知})⇒ 接管(否则一次 kill -9 会永久锁死 CI)"
|
|
304
|
+
rm -rf "${LOCK}"
|
|
305
|
+
if ! mkdir "${LOCK}" 2>/dev/null; then
|
|
306
|
+
echo "⛔ 接管锁失败(可能刚好有并发进程抢到)⇒ exit 75" >&2
|
|
307
|
+
exit 75
|
|
308
|
+
fi
|
|
309
|
+
LOCK_STALE=1
|
|
310
|
+
fi
|
|
311
|
+
printf '%s\n' "$$" > "${LOCK}/pid"
|
|
312
|
+
date -u +%Y-%m-%dT%H:%M:%SZ > "${LOCK}/started"
|
|
313
|
+
printf '%s\n' "${REPO}" > "${LOCK}/repo"
|
|
314
|
+
|
|
315
|
+
LOCK_OWNED=1
|
|
316
|
+
cleanup() {
|
|
317
|
+
if [ "${LOCK_OWNED}" = 1 ] && [ -n "${LOCK}" ] && [ -f "${LOCK}/pid" ] && [ "$(cat "${LOCK}/pid" 2>/dev/null)" = "$$" ]; then
|
|
318
|
+
rm -rf "${LOCK}"
|
|
319
|
+
fi
|
|
320
|
+
[ -n "${TMP_CI:-}" ] && [ -d "${TMP_CI}" ] && rm -rf "${TMP_CI}"
|
|
321
|
+
return 0
|
|
322
|
+
}
|
|
323
|
+
trap cleanup EXIT INT TERM
|
|
324
|
+
|
|
325
|
+
TMP_CI="$(mktemp -d -t argent-ci)"
|
|
326
|
+
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
327
|
+
[ -e "${CI_HOME}/${TS}.json" ] && TS="${TS}-$$"
|
|
328
|
+
LOG="${LOGS}/${TS}.log"
|
|
329
|
+
REC="${CI_HOME}/${TS}.json"
|
|
330
|
+
LATEST="${CI_HOME}/latest.json"
|
|
331
|
+
LATEST_FULL="${CI_HOME}/latest-full.json"
|
|
332
|
+
TSV="${TMP_CI}/results.tsv"
|
|
333
|
+
: > "${TSV}"
|
|
334
|
+
|
|
335
|
+
START_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
336
|
+
START_MS="$(now_ms)"
|
|
337
|
+
|
|
338
|
+
# 日志:本脚本自己的话(同时进控制台与日志)
|
|
339
|
+
say() { printf '%s\n' "$*" | tee -a "${LOG}"; }
|
|
340
|
+
|
|
341
|
+
say "════════ Argent 自建 CI ════════"
|
|
342
|
+
say " 运行 ${TS}(UTC)|仓库 ${REPO}"
|
|
343
|
+
say " 判据 $( [ "${PARTIAL}" = 1 ] && printf '%s' "${TOTAL_N}/${TOTAL_ALL}(--only)" || printf '%s' "${TOTAL_ALL} 条(全量)" )"
|
|
344
|
+
say " 锁 ${LOCK}(pid $$)$([ "${LOCK_STALE}" = 1 ] && printf '%s' ' |⚠️ 本次是接管陈旧锁' || true)"
|
|
345
|
+
say " 记录 ${REC} | 日志 ${LOG}"
|
|
346
|
+
|
|
347
|
+
# ── 环境哨兵的快照:**在第一条判据之前**拍(这样最后那条 --verify 覆盖整个 CI 窗口)──
|
|
348
|
+
ENV_SNAP=""
|
|
349
|
+
case " ${SELECTED} " in
|
|
350
|
+
*" test-env-safety "*)
|
|
351
|
+
ENV_SNAP="${TMP_CI}/env-snapshot.json"
|
|
352
|
+
if node scripts/check-test-env-safety.mjs --snapshot "${ENV_SNAP}" >"${TMP_CI}/snapshot.out" 2>&1; then
|
|
353
|
+
say " 环境哨兵:已在跑任何判据前拍下真实数据目录快照(${ENV_SNAP})"
|
|
354
|
+
else
|
|
355
|
+
say " ⚠️ 环境哨兵快照拍失败(${ENV_SNAP}):$(tail -2 "${TMP_CI}/snapshot.out" | tr '\n' ' ')"
|
|
356
|
+
fi
|
|
357
|
+
;;
|
|
358
|
+
esac
|
|
359
|
+
|
|
360
|
+
# 单行字段净化(TSV 安全)+ 摘要抽取
|
|
361
|
+
san() { printf '%s' "${1:-}" | tr '\t\n' ' ' | cut -c1-600; }
|
|
362
|
+
extract_summary() {
|
|
363
|
+
local f="$1" re="$2" line=""
|
|
364
|
+
if [ -n "${re}" ]; then
|
|
365
|
+
line="$(grep -E -- "${re}" "${f}" 2>/dev/null | tail -1 | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' || true)"
|
|
366
|
+
fi
|
|
367
|
+
if [ -z "${line}" ]; then
|
|
368
|
+
line="$(grep -v '^[[:space:]]*$' "${f}" 2>/dev/null | tail -1 | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' || true)"
|
|
369
|
+
fi
|
|
370
|
+
printf '%s' "${line}" | tr '\t' ' ' | cut -c1-400
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
PASS_N=0; FAIL_N=0
|
|
374
|
+
for id in ${SELECTED}; do
|
|
375
|
+
cmd="$(criterion_cmd "${id}")"
|
|
376
|
+
title="$(criterion_title "${id}")"
|
|
377
|
+
tmo="$(criterion_timeout "${id}")"
|
|
378
|
+
expects="$(criterion_expect "${id}")"
|
|
379
|
+
expects_flat="$(printf '%s' "${expects}" | tr '\n' ';' | sed -e 's/;;*$//' -e 's/;;*/; /g')"
|
|
380
|
+
out_file="${TMP_CI}/${id}.out"
|
|
381
|
+
tflag="${TMP_CI}/${id}.timedout"
|
|
382
|
+
: > "${out_file}"; rm -f "${tflag}"
|
|
383
|
+
|
|
384
|
+
started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
385
|
+
s_ms="$(now_ms)"
|
|
386
|
+
( cd "${REPO}" && bash -c "${cmd}" ) >"${out_file}" 2>&1 &
|
|
387
|
+
cpid=$!
|
|
388
|
+
( sleep "${tmo}"; : > "${tflag}"; kill_tree "${cpid}" ) >/dev/null 2>&1 &
|
|
389
|
+
wpid=$!
|
|
390
|
+
wait "${cpid}"; rc=$?
|
|
391
|
+
kill_tree "${wpid}"
|
|
392
|
+
wait "${wpid}" 2>/dev/null || true
|
|
393
|
+
e_ms="$(now_ms)"
|
|
394
|
+
finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
395
|
+
dur=$((e_ms - s_ms))
|
|
396
|
+
[ "${dur}" -lt 0 ] && dur=0
|
|
397
|
+
dur_s="$(awk "BEGIN{printf \"%.1f\", ${dur}/1000}")"
|
|
398
|
+
|
|
399
|
+
timed_out=0; [ -f "${tflag}" ] && timed_out=1
|
|
400
|
+
expect_ok=1; miss_pat=""
|
|
401
|
+
if [ "${timed_out}" = 1 ]; then
|
|
402
|
+
status="fail"; why="(超时 ${tmo}s 未结束,已尽力终止进程树)"
|
|
403
|
+
else
|
|
404
|
+
while IFS= read -r pat; do
|
|
405
|
+
[ -n "${pat}" ] || continue
|
|
406
|
+
if ! grep -Eq -- "${pat}" "${out_file}"; then expect_ok=0; miss_pat="${pat}"; break; fi
|
|
407
|
+
done < <(printf '%s\n' "${expects}")
|
|
408
|
+
if [ "${rc}" -ne 0 ]; then
|
|
409
|
+
status="fail"; why="(exit ${rc})"
|
|
410
|
+
elif [ "${expect_ok}" != 1 ]; then
|
|
411
|
+
# 退出码 0 但输出没命中期望 ⇒ 判红:防"脚本跑了但什么都没查"的假绿
|
|
412
|
+
status="fail"; why="(输出未命中期望判据:${miss_pat})"
|
|
413
|
+
else
|
|
414
|
+
status="pass"; why=""
|
|
415
|
+
fi
|
|
416
|
+
fi
|
|
417
|
+
summary="$(extract_summary "${out_file}" "$(criterion_summary_re "${id}")")"
|
|
418
|
+
|
|
419
|
+
# 全文进日志(按判据分段,可 grep)
|
|
420
|
+
{
|
|
421
|
+
printf '\n──────── [%s] %s\n' "${id}" "${title}"
|
|
422
|
+
printf ' 命令:%s\n 期望:%s\n 开始:%s | 超时:%ss\n' "${cmd}" "${expects_flat:-(无)}" "${started_at}" "${tmo}"
|
|
423
|
+
cat "${out_file}"
|
|
424
|
+
printf '──────── [%s] 结束 rc=%s 状态=%s 耗时=%sms\n' "${id}" "${rc}" "${status}" "${dur}"
|
|
425
|
+
} >> "${LOG}"
|
|
426
|
+
|
|
427
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
428
|
+
"${id}" "$(san "${title}")" "${status}" "${rc}" "${timed_out}" "${expect_ok}" "${dur}" \
|
|
429
|
+
"${started_at}" "${finished_at}" "$(san "${cmd}")" "$(san "${expects_flat}")" "$(san "${summary}")" >> "${TSV}"
|
|
430
|
+
|
|
431
|
+
if [ "${status}" = "pass" ]; then
|
|
432
|
+
PASS_N=$((PASS_N + 1))
|
|
433
|
+
[ "${QUIET}" = 1 ] || printf ' ✅ %-20s %6ss %s\n' "${id}" "${dur_s}" "${summary}"
|
|
434
|
+
else
|
|
435
|
+
FAIL_N=$((FAIL_N + 1))
|
|
436
|
+
printf ' ❌ %-20s %6ss %s %s\n' "${id}" "${dur_s}" "${summary}" "${why}"
|
|
437
|
+
fi
|
|
438
|
+
done
|
|
439
|
+
|
|
440
|
+
END_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
441
|
+
END_MS="$(now_ms)"
|
|
442
|
+
ELAPSED_MS=$((END_MS - START_MS))
|
|
443
|
+
[ "${ELAPSED_MS}" -lt 0 ] && ELAPSED_MS=0
|
|
444
|
+
ELAPSED_S="$(awk "BEGIN{printf \"%.1f\", ${ELAPSED_MS}/1000}")"
|
|
445
|
+
[ "${FAIL_N}" -eq 0 ] && RESULT="pass" || RESULT="fail"
|
|
446
|
+
[ "${FAIL_N}" -eq 0 ] && EXIT_CODE=0 || EXIT_CODE=1
|
|
447
|
+
|
|
448
|
+
# ── 记录(git 状态**只读**采集)────────────────────────────────────────────────
|
|
449
|
+
CI_HEAD="$(git -C "${REPO}" rev-parse --short=12 HEAD 2>/dev/null || true)"
|
|
450
|
+
CI_BRANCH="$(git -C "${REPO}" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
451
|
+
DIRTY_LIST="$(git -C "${REPO}" status --porcelain 2>/dev/null | head -20 || true)"
|
|
452
|
+
DIRTY_COUNT="$(git -C "${REPO}" status --porcelain 2>/dev/null | wc -l | tr -d ' ' || true)"
|
|
453
|
+
[ -n "${DIRTY_COUNT}" ] || DIRTY_COUNT=0
|
|
454
|
+
[ "${DIRTY_COUNT}" -gt 0 ] 2>/dev/null && CI_DIRTY=1 || CI_DIRTY=0
|
|
455
|
+
|
|
456
|
+
export CI_TS="${TS}" CI_PARTIAL="${PARTIAL}" CI_ONLY="${ONLY_NORM// /,}" CI_RESULT="${RESULT}" CI_EXIT="${EXIT_CODE}"
|
|
457
|
+
export CI_STARTED="${START_ISO}" CI_FINISHED="${END_ISO}" CI_DURATION_MS="${ELAPSED_MS}"
|
|
458
|
+
export CI_REPO="${REPO}" CI_HEAD="${CI_HEAD}" CI_BRANCH="${CI_BRANCH}" CI_DIRTY="${CI_DIRTY}"
|
|
459
|
+
export CI_DIRTY_COUNT="${DIRTY_COUNT}" CI_DIRTY_FILES="${DIRTY_LIST}"
|
|
460
|
+
export CI_HOST="$(hostname -s 2>/dev/null || hostname)" CI_PLATFORM="$(uname -s)" CI_RELEASE="$(uname -r)" CI_ARCH="$(uname -m)"
|
|
461
|
+
export CI_MACOS="$(sw_vers -productVersion 2>/dev/null || true)"
|
|
462
|
+
export CI_NODE="$(node -v 2>/dev/null || true)" CI_DSH="$(dsh --version 2>/dev/null | head -1 || true)"
|
|
463
|
+
export CI_PY="${PY}" CI_PYVER="${PYVER}" CI_PATH="${PATH}"
|
|
464
|
+
export CI_LOCK="${LOCK}" CI_PID="$$" CI_STALE="${LOCK_STALE}"
|
|
465
|
+
export CI_ALL_COUNT="${TOTAL_ALL}" CI_LOG="${LOG}" CI_HOME="${CI_HOME}"
|
|
466
|
+
export CI_DATA_DIR="${DATA_DIR}"
|
|
467
|
+
export CI_ARGV="${ARGV_STR}"
|
|
468
|
+
|
|
469
|
+
node - "${TSV}" "${REC}" "${LATEST}" "${LATEST_FULL}" <<'NODE' || echo "⚠️ 记录写入失败(JSON 组装出错)" >&2
|
|
470
|
+
const fs = require('node:fs')
|
|
471
|
+
const [tsvPath, recPath, latestPath, latestFullPath] = process.argv.slice(2)
|
|
472
|
+
const E = process.env
|
|
473
|
+
const rows = fs.readFileSync(tsvPath, 'utf8').split('\n').filter((l) => l.trim() !== '')
|
|
474
|
+
const criteria = rows.map((line) => {
|
|
475
|
+
const c = line.split('\t')
|
|
476
|
+
return {
|
|
477
|
+
name: c[0] || '',
|
|
478
|
+
title: c[1] || '',
|
|
479
|
+
status: c[2] || '',
|
|
480
|
+
exitCode: Number(c[3] || 0),
|
|
481
|
+
timedOut: c[4] === '1',
|
|
482
|
+
expectOk: c[5] === '1',
|
|
483
|
+
durationMs: Number(c[6] || 0),
|
|
484
|
+
startedAt: c[7] || '',
|
|
485
|
+
finishedAt: c[8] || '',
|
|
486
|
+
cmd: c[9] || '',
|
|
487
|
+
expect: (c[10] || '') === '' ? [] : c[10].split('; '),
|
|
488
|
+
summary: c[11] || '',
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
const passCount = criteria.filter((c) => c.status === 'pass').length
|
|
492
|
+
const rec = {
|
|
493
|
+
schema: 'argent-ci/1',
|
|
494
|
+
runId: E.CI_TS || '',
|
|
495
|
+
partial: E.CI_PARTIAL === '1',
|
|
496
|
+
only: (E.CI_ONLY || '') === '' ? [] : E.CI_ONLY.split(','),
|
|
497
|
+
result: E.CI_RESULT || '',
|
|
498
|
+
exitCode: Number(E.CI_EXIT || 0),
|
|
499
|
+
startedAt: E.CI_STARTED || '',
|
|
500
|
+
finishedAt: E.CI_FINISHED || '',
|
|
501
|
+
durationMs: Number(E.CI_DURATION_MS || 0),
|
|
502
|
+
repo: {
|
|
503
|
+
path: E.CI_REPO || '',
|
|
504
|
+
head: E.CI_HEAD || '',
|
|
505
|
+
branch: E.CI_BRANCH || '',
|
|
506
|
+
dirty: E.CI_DIRTY === '1',
|
|
507
|
+
dirtyCount: Number(E.CI_DIRTY_COUNT || 0),
|
|
508
|
+
dirtyFiles: (E.CI_DIRTY_FILES || '') === '' ? [] : E.CI_DIRTY_FILES.split('\n'),
|
|
509
|
+
},
|
|
510
|
+
machine: {
|
|
511
|
+
hostname: E.CI_HOST || '', platform: E.CI_PLATFORM || '', release: E.CI_RELEASE || '',
|
|
512
|
+
arch: E.CI_ARCH || '', macos: E.CI_MACOS || '',
|
|
513
|
+
},
|
|
514
|
+
runtime: { node: E.CI_NODE || '', dsh: E.CI_DSH || '', python: E.CI_PY || '', pythonVersion: E.CI_PYVER || '', path: E.CI_PATH || '' },
|
|
515
|
+
lock: { path: E.CI_LOCK || '', pid: Number(E.CI_PID || 0), staleTakenOver: E.CI_STALE === '1' },
|
|
516
|
+
counts: { all: Number(E.CI_ALL_COUNT || 0), selected: criteria.length, pass: passCount, fail: criteria.length - passCount },
|
|
517
|
+
criteria, // ⚠️ 2026-10-05 修:第一版漏了这个键 ⇒ 记录里只有 counts、**没有逐条判据**(正是要交付的东西)。
|
|
518
|
+
// 自证口径:写完必须 `node -e 'console.log(r.criteria.length)'` 回读一次,不是看一眼 counts 就算数。
|
|
519
|
+
env: { dataDir: E.CI_DATA_DIR || '', dataDirSource: 'scripts/argent-datadir.py(唯一判据)' },
|
|
520
|
+
log: E.CI_LOG || '',
|
|
521
|
+
argv: (E.CI_ARGV || '') === '' ? [] : E.CI_ARGV.split('\n'),
|
|
522
|
+
notes: [
|
|
523
|
+
'只读校验:不 push / 不部署 / 不连客户机 / 不改凭据 / 不跑包管理器 / 不做 git checkout|clean(git 状态只读采集)。',
|
|
524
|
+
'本地写入仅两处:本记录与日志(' + (E.CI_HOME || '') + '/**);sync-scripts --no-pull 按脚本自身契约把仓库重新投递到本机安装克隆(无网络动作)。',
|
|
525
|
+
'判据措辞即判据:每条 criterion.expect 是必须命中的输出正则;措辞变更会让该条转红(防"脚本跑了但什么都没查"的假绿)。',
|
|
526
|
+
],
|
|
527
|
+
}
|
|
528
|
+
const text = JSON.stringify(rec, null, 2) + '\n'
|
|
529
|
+
fs.writeFileSync(recPath, text)
|
|
530
|
+
fs.writeFileSync(latestPath, text)
|
|
531
|
+
if (rec.partial !== true) fs.writeFileSync(latestFullPath, text)
|
|
532
|
+
NODE
|
|
533
|
+
|
|
534
|
+
# ── 汇总 ───────────────────────────────────────────────────────────────────────
|
|
535
|
+
say ""
|
|
536
|
+
say "════════════════════════════════════════════════════════════════"
|
|
537
|
+
if [ "${FAIL_N}" -eq 0 ]; then
|
|
538
|
+
say " CI 结果:${PASS_N}/${TOTAL_N} 通过 ✓(耗时 ${ELAPSED_S}s)"
|
|
539
|
+
else
|
|
540
|
+
say " CI 结果:${PASS_N}/${TOTAL_N} 通过(耗时 ${ELAPSED_S}s)⇒ 退出码 1"
|
|
541
|
+
say " ❌ 失败的判据(${FAIL_N}):"
|
|
542
|
+
while IFS="$(printf '\t')" read -r c_name c_title c_status c_rc c_to c_eo c_dur c_s c_f c_cmd c_exp c_sum; do
|
|
543
|
+
[ "${c_status}" = "pass" ] && continue
|
|
544
|
+
[ -n "${c_name}" ] || continue
|
|
545
|
+
say " · ${c_name}(${c_title})exit=${c_rc}$([ "${c_to}" = 1 ] && printf '%s' ' 超时' || true)"
|
|
546
|
+
say " 摘要:${c_sum}"
|
|
547
|
+
say " 全文:${LOG} 的 [${c_name}] 段"
|
|
548
|
+
done < "${TSV}"
|
|
549
|
+
fi
|
|
550
|
+
if [ "${PARTIAL}" = 1 ]; then
|
|
551
|
+
say " ⚠️ 本次只跑了 ${TOTAL_N}/${TOTAL_ALL} 条(--only)⇒ 这是**部分结果**:"
|
|
552
|
+
say " latest.json 已更新为部分结果(partial=true);**全量门禁状态看 latest-full.json**"
|
|
553
|
+
fi
|
|
554
|
+
say " 记录:${REC}"
|
|
555
|
+
say " ${LATEST}$([ "${PARTIAL}" = 1 ] && printf '%s' '(本次已更新,partial=true)' || true)"
|
|
556
|
+
say " 日志:${LOG}"
|
|
557
|
+
say "════════════════════════════════════════════════════════════════"
|
|
558
|
+
exit "${EXIT_CODE}"
|