@whyshu/argent-engine 0.0.0-stage → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/MANIFEST.json +341 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +576 -0
  55. package/engine/scripts/billing-mode-daily.sh +13 -0
  56. package/engine/scripts/build-engine-package.mjs +126 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +378 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/collect-evidence.mjs +42 -0
  81. package/engine/scripts/config-explain.mjs +278 -0
  82. package/engine/scripts/configure-provider.rb +29 -0
  83. package/engine/scripts/customer-config-migrate.mjs +179 -0
  84. package/engine/scripts/customer-preflight.py +892 -0
  85. package/engine/scripts/customer-readiness.sh +135 -0
  86. package/engine/scripts/daily-report-inbox.py +242 -0
  87. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  88. package/engine/scripts/daily-report.mjs +1310 -0
  89. package/engine/scripts/daily-report.sh +47 -0
  90. package/engine/scripts/data/lip-product-dict.json +306 -0
  91. package/engine/scripts/deliver-customer.mjs +932 -0
  92. package/engine/scripts/delivery-faces.txt +31 -0
  93. package/engine/scripts/desktop-trial.sh +146 -0
  94. package/engine/scripts/dev-window.sh +261 -0
  95. package/engine/scripts/drill-clean-machine.sh +315 -0
  96. package/engine/scripts/dsh-api-client.mjs +215 -0
  97. package/engine/scripts/dsh-backup-scope.sh +83 -0
  98. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  99. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  100. package/engine/scripts/dsh-plane.mjs +153 -0
  101. package/engine/scripts/dsh-portforward.py +223 -0
  102. package/engine/scripts/dsh-preflight.mjs +179 -0
  103. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  104. package/engine/scripts/dsh-release-watch.mjs +458 -0
  105. package/engine/scripts/dsh-remote-check.sh +99 -0
  106. package/engine/scripts/dsh-restart.sh +179 -0
  107. package/engine/scripts/dsh-restore.sh +111 -0
  108. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  109. package/engine/scripts/dsh-snapshot.mjs +116 -0
  110. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  111. package/engine/scripts/dsh-upgrade.mjs +612 -0
  112. package/engine/scripts/dsh-url.sh +86 -0
  113. package/engine/scripts/dsh-web-launch.mjs +155 -0
  114. package/engine/scripts/ensure-store-windows.py +509 -0
  115. package/engine/scripts/entitlements.py +458 -0
  116. package/engine/scripts/env_probe.py +411 -0
  117. package/engine/scripts/feishu-card.mjs +252 -0
  118. package/engine/scripts/final-check.mjs +43 -0
  119. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  120. package/engine/scripts/flow-auto-retry.py +454 -0
  121. package/engine/scripts/flow-requirements.json +735 -0
  122. package/engine/scripts/flow-scaffold.py +275 -0
  123. package/engine/scripts/flow-timers-plist.sh +87 -0
  124. package/engine/scripts/flow-timers.sh +184 -0
  125. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  126. package/engine/scripts/flow-trigger.sh +571 -0
  127. package/engine/scripts/flow_billing.py +354 -0
  128. package/engine/scripts/flow_capability_stats.py +314 -0
  129. package/engine/scripts/flow_case.py +194 -0
  130. package/engine/scripts/flow_claim.py +497 -0
  131. package/engine/scripts/flow_config.py +1025 -0
  132. package/engine/scripts/flow_failure.py +588 -0
  133. package/engine/scripts/flow_learn.py +105 -0
  134. package/engine/scripts/flow_ledger.py +235 -0
  135. package/engine/scripts/flow_policy.py +238 -0
  136. package/engine/scripts/flow_preflight.py +339 -0
  137. package/engine/scripts/flow_registry.py +341 -0
  138. package/engine/scripts/flow_retry.py +304 -0
  139. package/engine/scripts/flow_return_to_ops.py +191 -0
  140. package/engine/scripts/flow_source.py +315 -0
  141. package/engine/scripts/flow_sources/__init__.py +53 -0
  142. package/engine/scripts/flow_sources/cases_json.py +106 -0
  143. package/engine/scripts/flow_sources/common.py +428 -0
  144. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  145. package/engine/scripts/flow_stale.py +185 -0
  146. package/engine/scripts/flow_status_reconcile.py +344 -0
  147. package/engine/scripts/flow_triage.py +849 -0
  148. package/engine/scripts/flow_write_verify.py +496 -0
  149. package/engine/scripts/fulfillment.js +586 -0
  150. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  151. package/engine/scripts/gen-preset-rows.mjs +259 -0
  152. package/engine/scripts/init-ml.sh +2278 -0
  153. package/engine/scripts/install-internal-role.sh +190 -0
  154. package/engine/scripts/install-license.mjs +641 -0
  155. package/engine/scripts/install-one-shot.mjs +665 -0
  156. package/engine/scripts/integration_contract.py +220 -0
  157. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  158. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  159. package/engine/scripts/invoice-mapping.example.json +40 -0
  160. package/engine/scripts/invoice-table.sh +195 -0
  161. package/engine/scripts/invoice_carrier_notice.py +279 -0
  162. package/engine/scripts/invoice_fail_closeout.py +297 -0
  163. package/engine/scripts/invoice_lip_runner.py +1508 -0
  164. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  165. package/engine/scripts/invoice_ready_audit.py +191 -0
  166. package/engine/scripts/invoice_runner.py +675 -0
  167. package/engine/scripts/invoice_stageb.py +516 -0
  168. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  169. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  170. package/engine/scripts/lease.py +377 -0
  171. package/engine/scripts/lib/agent-install.mjs +649 -0
  172. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  173. package/engine/scripts/lib/argent-datadir.sh +18 -0
  174. package/engine/scripts/lib/argent_datadir.py +285 -0
  175. package/engine/scripts/lib/argent_filelock.py +507 -0
  176. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  177. package/engine/scripts/lib/cdp_ports.py +197 -0
  178. package/engine/scripts/lib/customer-env.mjs +102 -0
  179. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  180. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  181. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  182. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  183. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  184. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  185. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  186. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  187. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  188. package/engine/scripts/lib/patch-compose.mjs +241 -0
  189. package/engine/scripts/lib/patch-target.mjs +35 -0
  190. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  191. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  192. package/engine/scripts/lib/preset-compat.mjs +127 -0
  193. package/engine/scripts/lib/preset-install.mjs +575 -0
  194. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  195. package/engine/scripts/lib/preset-sync.mjs +59 -0
  196. package/engine/scripts/lib/profile-retire.mjs +97 -0
  197. package/engine/scripts/lib/python-runtime.mjs +190 -0
  198. package/engine/scripts/lib/registry.mjs +72 -0
  199. package/engine/scripts/lib/repo-root.sh +56 -0
  200. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  201. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  202. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  203. package/engine/scripts/lib/settings-panels.mjs +816 -0
  204. package/engine/scripts/lib/stdio_utf8.py +86 -0
  205. package/engine/scripts/lib/wake-auth.mjs +392 -0
  206. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  207. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  208. package/engine/scripts/license-e2e-drill.mjs +426 -0
  209. package/engine/scripts/license-gate.mjs +245 -0
  210. package/engine/scripts/lip_backlog.py +171 -0
  211. package/engine/scripts/lip_table.py +185 -0
  212. package/engine/scripts/local-usage.mjs +214 -0
  213. package/engine/scripts/migrate-providers.mjs +127 -0
  214. package/engine/scripts/migrate-skills.sh +65 -0
  215. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  216. package/engine/scripts/ml-store-ids.json +26 -0
  217. package/engine/scripts/ml_artifact_attribution.py +333 -0
  218. package/engine/scripts/ml_capacity_probe.py +1158 -0
  219. package/engine/scripts/ml_hub_status.py +1556 -0
  220. package/engine/scripts/ml_schema_check.py +115 -0
  221. package/engine/scripts/ml_store_ids.py +139 -0
  222. package/engine/scripts/npm-publish-argent.mjs +499 -0
  223. package/engine/scripts/npm-publish-argent.sh +20 -0
  224. package/engine/scripts/orchestrator.py +13811 -0
  225. package/engine/scripts/pack-plugins.mjs +140 -0
  226. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  227. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  228. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  229. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  230. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  231. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  232. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  233. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  234. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  235. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  236. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  237. package/engine/scripts/plan-role-rename.mjs +125 -0
  238. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  239. package/engine/scripts/plugin-composition.json +15 -0
  240. package/engine/scripts/poc-mcp-host.mjs +455 -0
  241. package/engine/scripts/poll-fulfillment.sh +295 -0
  242. package/engine/scripts/poll-invoice.sh +76 -0
  243. package/engine/scripts/profile-doctor.mjs +686 -0
  244. package/engine/scripts/provider-select.sh +71 -0
  245. package/engine/scripts/provider-switch.sh +146 -0
  246. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  247. package/engine/scripts/python-runtime.sh +78 -0
  248. package/engine/scripts/query_runner.py +310 -0
  249. package/engine/scripts/release-argent.sh +574 -0
  250. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  251. package/engine/scripts/retire-inventory.mjs +40 -0
  252. package/engine/scripts/run-tests.sh +257 -0
  253. package/engine/scripts/s1-audit.mjs +85 -0
  254. package/engine/scripts/self-sign-license.mjs +179 -0
  255. package/engine/scripts/session-health-scan.mjs +63 -0
  256. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  257. package/engine/scripts/setup.sh +22 -0
  258. package/engine/scripts/skill-install.mjs +629 -0
  259. package/engine/scripts/sku_translate.py +148 -0
  260. package/engine/scripts/sync-plugins.sh +90 -0
  261. package/engine/scripts/sync-presets.sh +27 -0
  262. package/engine/scripts/sync-scripts.sh +445 -0
  263. package/engine/scripts/telemetry.py +873 -0
  264. package/engine/scripts/ui-copy-count.mjs +220 -0
  265. package/engine/scripts/usage-anchor.sh +137 -0
  266. package/engine/scripts/usage_report.py +531 -0
  267. package/engine/scripts/validate-preset-schema.mjs +208 -0
  268. package/engine/scripts/vertical-init.mjs +323 -0
  269. package/engine/scripts/vertical-lint.mjs +482 -0
  270. package/engine/scripts/wenyao-browser.sh +74 -0
  271. package/engine/scripts/wenyao-fixture-export.py +116 -0
  272. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  273. package/engine/scripts/wenyao-login.sh +35 -0
  274. package/engine/scripts/wenyao-template-map.json +292 -0
  275. package/engine/scripts/wenyao_attach.py +131 -0
  276. package/engine/scripts/wenyao_download_template.py +144 -0
  277. package/engine/scripts/wenyao_fill_template.py +568 -0
  278. package/engine/scripts/wenyao_login.py +145 -0
  279. package/engine/scripts/wenyao_portal.py +633 -0
  280. package/engine/scripts/wenyao_recon.py +230 -0
  281. package/engine/scripts/wenyao_run.py +503 -0
  282. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  283. package/engine/scripts/wenyao_skill_contract.py +156 -0
  284. package/engine/scripts/wenyao_template_fetch.py +175 -0
  285. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  286. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  287. package/engine/scripts/ziniao-keepalive.sh +217 -0
  288. package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
  289. package/engine/scripts/ziniao-windows.sh +154 -0
  290. package/engine/skills/README.md +86 -0
  291. package/engine/skills/argent-dev/SKILL.md +41 -0
  292. package/engine/skills/internal.json +37 -0
  293. package/engine/skills/lktop-pm/SKILL.md +35 -0
  294. package/engine/skills/lktopapp-dev/SKILL.md +34 -0
  295. package/engine/skills/ml-config-guide/SKILL.md +50 -0
  296. package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
  297. package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
  298. package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
  299. package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
  300. package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
  301. package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
  302. package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
  303. package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
  304. package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
  305. package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
  306. package/engine/skills/ml-invoice/SKILL.md +506 -0
  307. package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
  308. package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
  309. package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
  310. package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
  311. package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
  312. package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
  313. package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
  314. package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
  315. package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
  316. package/engine/skills/pet-dev/SKILL.md +42 -0
  317. package/engine/skills/whyshu-pm/SKILL.md +35 -0
  318. package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
  319. package/engine/version.mjs +1 -0
  320. package/engine/verticals/README.md +103 -0
  321. package/engine/verticals/_engine-words.baseline.txt +237 -0
  322. package/engine/verticals/_engine-words.json +19 -0
  323. package/engine/verticals/mercadolibre/README.md +43 -0
  324. package/engine/verticals/mercadolibre/config.json +709 -0
  325. package/engine/verticals/mercadolibre/flows.json +738 -0
  326. package/engine/verticals/mercadolibre/manifest.json +538 -0
  327. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  328. package/engine/verticals/mercadolibre/preflight.py +143 -0
  329. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  330. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  331. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  332. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  333. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  334. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  335. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  336. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  337. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  338. package/package.json +22 -4
  339. package/requirements.txt +5 -0
  340. package/README.md +0 -3
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * repair-unresolved-tools.mjs — 修复"step/end 时存在未解决 tool call"的 v4 session。
4
+ *
5
+ * 背景(2026-09-29):0.1.7 初版加固补丁 `dsh-session--tool-result-block-017.patch` 的
6
+ * "message 必须含 tool-result block" 规则与 v4 形态矛盾(v4 中该 wrapper 是 retired syntax,
7
+ * 迁移器会把 tool/result 展开为 text block),导致合法的 tool/result 在 append 边界被拒:
8
+ * - tool/result 写入失败 → step/end 时悬挂未解决的 tool call
9
+ * - session 只能加载失败("stored log is corrupt: step/end leaves unresolved tool call")
10
+ * 修正版补丁已上线;本脚本用于修复已被毒化的存量 session。
11
+ *
12
+ * 修复原理:对每个未解决点,把前缀交给 dsh 官方 `interruptedTurnClosers` 生成
13
+ * 补齐事件(tool/result、isError、ToolOutcomeUnknownError / ToolNotStartedError),
14
+ * 插入后对后续事件 seq 顺移并修正内部引用(surfaceOp/sourceEventSeqs/messageSeqs/headerSeq)。
15
+ *
16
+ * 用法:
17
+ * node repair-unresolved-tools.mjs --scan 扫描全部
18
+ * node repair-unresolved-tools.mjs --repair <id> [--dir <path>] 预演(不写盘)
19
+ * node repair-unresolved-tools.mjs --repair <id> --apply 写盘(自动备份 .bak)
20
+ */
21
+ // 2026-09-29 修:原实现写死开发机路径 `/opt/homebrew/lib/...` ⇒ 客户机(node 官方包,
22
+ // 树在 ~/node/lib/node_modules)一跑就 ERR_MODULE_NOT_FOUND。按仓库规矩走共享解析器,
23
+ // 且用**动态 import**(scope 要到运行时才知道)。
24
+ // 0.6.0 F1 修复批(2026-10-14):本脚本**必须**有宿主树(`interruptedTurnClosers` 从它 import)
25
+ // ⇒ 探不到就**响亮失败**(⛔ 不用假路径 —— 旧写法在 npx 宿主上会拿一个不存在的树去 import)。
26
+ import { requireDshScope } from './lib/dsh-scope.mjs';
27
+ let _SCOPE;
28
+ try { _SCOPE = requireDshScope({ what: '未决工具修复(repair-unresolved-tools)' }); }
29
+ catch (e) { console.error(` ❌ ${e.message}`); process.exit(1); }
30
+ const { interruptedTurnClosers } = await import(`${_SCOPE}/dsh-session/lib/index.js`);
31
+ import { zstdCompress, zstdDecompress, constants } from 'node:zlib';
32
+ import { promisify } from 'node:util';
33
+ import fs from 'node:fs';
34
+ import path from 'node:path';
35
+ import os from 'node:os';
36
+ import { execFileSync } from 'node:child_process';
37
+
38
+ const zstdCompressAsync = promisify(zstdCompress);
39
+ const CHECKSUM_OPTIONS = { params: { [constants.ZSTD_c_checksumFlag]: 1 } };
40
+ const SESSIONS_ROOT = path.join(os.homedir(), '.dsh', 'sessions');
41
+
42
+ // ---------- 工具 ----------
43
+ function findSessionDir(id) {
44
+ for (const proj of fs.readdirSync(SESSIONS_ROOT)) {
45
+ const p = path.join(SESSIONS_ROOT, proj, id);
46
+ if (fs.existsSync(path.join(p, 'session.v4.jsonl.zstd'))) return p;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ async function readLog(file) {
52
+ // 注意:node:zlib 的 zstdDecompress 只解第一帧;多帧文件须用 zstd CLI 全量解压
53
+ const raw = execFileSync('zstd', ['-d', '-c', file], { maxBuffer: 1 << 30 });
54
+ const lines = raw.toString('utf8').trim().split('\n');
55
+ return { header: JSON.parse(lines[0]), events: lines.slice(1).map(l => JSON.parse(l)) };
56
+ }
57
+
58
+ async function writeLog(file, header, events) {
59
+ const headerLine = JSON.stringify(header) + '\n';
60
+ const body = events.map(e => JSON.stringify(e)).join('\n') + '\n';
61
+ const f1 = await zstdCompressAsync(Buffer.from(headerLine), CHECKSUM_OPTIONS);
62
+ const f2 = await zstdCompressAsync(Buffer.from(body), CHECKSUM_OPTIONS);
63
+ fs.writeFileSync(file, Buffer.concat([f1, f2]));
64
+ }
65
+
66
+ /** 检测未解决点:step/end(或 turn/end)时 pendingCalls 非空 */
67
+ function analyze(events) {
68
+ const pending = new Map();
69
+ const issues = [];
70
+ for (let i = 0; i < events.length; i++) {
71
+ const ev = events[i], t = ev.type, d = ev.data || {};
72
+ if (t === 'turn/start') pending.clear();
73
+ else if (t === 'turn/end') { if (pending.size) issues.push({ index: i, type: 'turn/end', pending: [...pending.entries()] }); pending.clear(); }
74
+ else if (t === 'step/end') { if (pending.size) issues.push({ index: i, type: 'step/end', pending: [...pending.entries()] }); pending.clear(); }
75
+ else if (t === 'assistant/message') {
76
+ for (const b of (d.message?.content || [])) if (b.type === 'tool-call') pending.set(b.id, { step: d.step });
77
+ } else if (t === 'tool/call') {
78
+ const e = pending.get(d.callId); if (e) e.callSeq = ev.seq;
79
+ } else if (t === 'tool/result') {
80
+ pending.delete(d.message?.source?.callId);
81
+ }
82
+ }
83
+ return issues;
84
+ }
85
+
86
+ /** 顺移事件内部引用 */
87
+ function shiftRefs(ev, fromSeq, delta) {
88
+ const d = ev.data || {};
89
+ if (d.surfaceOp && typeof d.surfaceOp === 'object') {
90
+ if (typeof d.surfaceOp.startSeq === 'number' && d.surfaceOp.startSeq >= fromSeq) d.surfaceOp.startSeq += delta;
91
+ if (typeof d.surfaceOp.endSeq === 'number' && d.surfaceOp.endSeq >= fromSeq) d.surfaceOp.endSeq += delta;
92
+ }
93
+ if (Array.isArray(d.sourceEventSeqs)) d.sourceEventSeqs = d.sourceEventSeqs.map(s => (typeof s === 'number' && s >= fromSeq) ? s + delta : s);
94
+ if (Array.isArray(d.messageSeqs)) d.messageSeqs = d.messageSeqs.map(s => (typeof s === 'number' && s >= fromSeq) ? s + delta : s);
95
+ if (typeof d.headerSeq === 'number' && d.headerSeq >= fromSeq) d.headerSeq += delta;
96
+ }
97
+
98
+ /** 修复一个会话的事件流(返回修复后事件数组 + 修复报告) */
99
+ function repair(events) {
100
+ const issues = analyze(events);
101
+ if (issues.length === 0) return { events, fixes: [] };
102
+ let fixed = structuredClone(events);
103
+ const fixes = [];
104
+ // 从后往前修,避免编号漂移
105
+ for (const issue of issues.reverse()) {
106
+ const insertAt = issue.index; // 该 step/end(或 turn/end)在 fixed 中的位置
107
+ const prefix = fixed.slice(0, insertAt);
108
+ const closers = interruptedTurnClosers(prefix);
109
+ const results = closers.filter(c => c.type === 'tool/result');
110
+ if (results.length === 0) { fixes.push({ at: insertAt, note: 'closers 未产出(跳过)' }); continue; }
111
+ const insertSeq = prefix.length > 0 ? prefix.at(-1).seq + 1 : 0;
112
+ // 原事件(step/end 起)顺移
113
+ const tail = fixed.slice(insertAt).map(ev => {
114
+ const copy = structuredClone(ev);
115
+ copy.seq += results.length;
116
+ shiftRefs(copy, insertSeq, results.length);
117
+ return copy;
118
+ });
119
+ fixed = [...prefix, ...results, ...tail];
120
+ fixes.push({ at: insertAt, inserted: results.length, seq: insertSeq, calls: results.map(r => r.data.message.source.callId) });
121
+ }
122
+ // 连续性校验
123
+ for (let i = 0; i < fixed.length; i++) {
124
+ if (fixed[i].seq !== i) throw new Error(`修复后 seq 断裂 at index ${i}: got ${fixed[i].seq}`);
125
+ }
126
+ return { events: fixed, fixes };
127
+ }
128
+
129
+ // ---------- 主流程 ----------
130
+ const args = process.argv.slice(2);
131
+ const mode = args.includes('--scan') ? 'scan' : args.includes('--repair') ? 'repair' : null;
132
+ const apply = args.includes('--apply');
133
+ const id = args[args.indexOf('--repair') + 1];
134
+
135
+ if (mode === 'scan') {
136
+ let checked = 0, broken = 0;
137
+ for (const proj of fs.readdirSync(SESSIONS_ROOT)) {
138
+ for (const sess of fs.readdirSync(path.join(SESSIONS_ROOT, proj))) {
139
+ const f = path.join(SESSIONS_ROOT, proj, sess, 'session.v4.jsonl.zstd');
140
+ if (!fs.existsSync(f)) continue;
141
+ checked++;
142
+ try {
143
+ const { events } = await readLog(f);
144
+ const issues = analyze(events);
145
+ if (issues.length) {
146
+ broken++;
147
+ console.log(`☠ ${sess} (${issues.length} 个未解决点)`);
148
+ for (const is of issues) console.log(` ${is.type} · 缺失: ${is.pending.map(([c]) => c).join(', ')}`);
149
+ }
150
+ } catch (e) {
151
+ console.log(`?! ${sess}: ${e.message}`);
152
+ }
153
+ }
154
+ }
155
+ console.log(`\n扫描 ${checked} 个 v4 文件;损坏 ${broken} 个`);
156
+ } else if (mode === 'repair' && id) {
157
+ const dir = findSessionDir(id);
158
+ if (!dir) { console.error(`找不到会话: ${id}`); process.exit(1); }
159
+ const file = path.join(dir, 'session.v4.jsonl.zstd');
160
+ const { header, events } = await readLog(file);
161
+ console.log(`会话 ${id}: ${events.length} 事件`);
162
+ const issues = analyze(events);
163
+ if (!issues.length) { console.log('✓ 健康,无需修复'); process.exit(0); }
164
+ console.log(`发现 ${issues.length} 个未解决点`);
165
+ const { events: fixed, fixes } = repair(events);
166
+ for (const fx of fixes) console.log(' 修复:', JSON.stringify(fx));
167
+ console.log(`修复后: ${fixed.length} 事件,seq 0..${fixed.at(-1).seq}`);
168
+ if (!apply) {
169
+ console.log('\n[预演] 未写盘。加 --apply 执行(自动备份 .bak-<ts>)');
170
+ } else {
171
+ const bak = `${file}.bak-${Date.now()}`;
172
+ fs.copyFileSync(file, bak);
173
+ await writeLog(file, header, fixed);
174
+ console.log(`\n✓ 已写盘。备份: ${bak}`);
175
+ }
176
+ } else {
177
+ console.log('用法:\n node repair-unresolved-tools.mjs --scan\n node repair-unresolved-tools.mjs --repair <session-id> [--apply]');
178
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ // 0.7.0 W8 只读盘点:把"待退役面"逐类定位(⛔ 只读;不改文件、不动生产、不重启)。
3
+ import { readdirSync, readFileSync, statSync, existsSync, writeFileSync } from 'node:fs'
4
+ import { join, relative, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ const REPO = join(dirname(fileURLToPath(import.meta.url)), '..')
7
+ const SKIP = new Set(['.git', 'node_modules', 'dist', '__pycache__', 'attic', '.venv'])
8
+ function walk(dir, out = []) {
9
+ for (const e of readdirSync(dir)) {
10
+ if (SKIP.has(e)) continue
11
+ const p = join(dir, e); const st = statSync(p)
12
+ if (st.isDirectory()) walk(p, out)
13
+ else if (/\.(mjs|js|cjs|py|sh|json|md|plist|ts)$/.test(e) && st.size < 2_000_000) out.push(p)
14
+ }
15
+ return out
16
+ }
17
+ const files = walk(REPO)
18
+ const hits = (re, filter = () => true) => files.filter(filter).map((f) => ({ f: relative(REPO, f), n: (readFileSync(f, 'utf8').match(re) || []).length })).filter((x) => x.n > 0).sort((a, b) => b.n - a.n)
19
+ const patches = existsSync(join(REPO, 'scripts/patches')) ? readdirSync(join(REPO, 'scripts/patches')).filter((f) => f.endsWith('.patch')) : []
20
+ const doc = {
21
+ generatedAt: new Date().toISOString(),
22
+ 面: {
23
+ '内核补丁(patch 数 / 引用点)': { patches: patches.length, files: hits(/scripts\/patches\//).slice(0, 10) },
24
+ '旧单文件分发 install-argent.mjs': { files: hits(/install-argent\.mjs/).slice(0, 10) },
25
+ '写死 ~/.dsh 路径': { files: hits(/~\/\.dsh|\.dsh\/argent/, (f) => !f.includes('/docs/') && !f.includes('/patches/')).slice(0, 15) },
26
+ 'launchd 双线(flow-trigger / plist / launchctl)': { files: hits(/flow-trigger|launchctl|LaunchAgents|\.plist/).slice(0, 15) },
27
+ },
28
+ }
29
+ console.log(JSON.stringify(doc, null, 2))
30
+ if (process.argv.includes('--write')) {
31
+ const p = join(REPO, 'docs/Argent-0.7.0-退役面盘点.md')
32
+ const L = ['# Argent 0.7.0 待退役面盘点(只读生成)', '', `生成时间:${doc.generatedAt}`, '', '> 本文件由 `node scripts/retire-inventory.mjs --write` 生成;⛔ 只读盘点,未改动任何被点名的面。', '']
33
+ for (const [k, v] of Object.entries(doc.面)) {
34
+ L.push(`## ${k}`, '')
35
+ if (v.patches !== undefined) L.push(`- patch 文件数:**${v.patches}**`)
36
+ for (const x of v.files) L.push(`- \`${x.f}\`(${x.n} 处)`)
37
+ L.push('')
38
+ }
39
+ writeFileSync(p, L.join('\n')); console.log(`\n✅ 已写 ${relative(REPO, p)}`)
40
+ }
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env bash
2
+ . "$(dirname "$0")/lib/argent-datadir.sh" # 0.7.0 W8/S1:数据根唯一判据(⛔ 不再写死 ~/.dsh/argent)
3
+ # 全量测试入口(2026-09-30 补:本仓库此前有 123 个测试却无总入口 ⇒ 门禁无法一键复验)
4
+ #
5
+ # 用法:
6
+ # bash scripts/run-tests.sh [--filter <关键字>] [--skip <子串>]...
7
+ # bash scripts/run-tests.sh --clean-tree-check [--filter <关键字>]
8
+ # bash scripts/run-tests.sh --print-git-skips [<树>] # 测试钩子:打印该树里因缺 .git 要跳过的用例
9
+ # · *.test.mjs → node
10
+ # · *.test.py → venv python(缺 venv 则回落系统 python3)
11
+ # 退出码:任一失败即 1;末尾打印通过/失败计数与失败清单。
12
+ #
13
+ # `--clean-tree-check` = **干净树自检**:`git archive HEAD` 解到临时目录后跑**同一套**判据,
14
+ # 用来判"HEAD 既存红"(提交本身就是红的)。它**自动规避三类环境性假红**(2026-10-08 固化):
15
+ # ① `/tmp` → `/private/tmp` 符号链接(用 `pwd -P` 的物理路径建树并 `cd` 进去);
16
+ # ② git 不跟踪的空目录(`git archive` 不带 ⇒ `ENOENT`),自动镜像 —— **通用兜底**;
17
+ # `scripts/flow-prompts/` 曾是唯一实例,该用例的根因已于 2026-10-08 修(用例容忍缺目录);
18
+ # ③ **需要 `.git` 的用例**(裸检出没有 `.git`)按**判据 `[ -d .git ]`** 跳过、点名原因
19
+ # (判据在 `git_index_skips()` 一处;候选见 `NEED_GIT_CANDIDATES`)。
20
+ # 判据与两条假红的区别见 docs/CI-自建runner.md §8.1「两类假红」。保树调试:`ARGENT_CLEAN_TREE_KEEP=1`。
21
+ set -uo pipefail
22
+ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
23
+ cd "$REPO"
24
+ PY="$ARGENT_DATA_DIR_RESOLVED/venv/bin/python3"; [ -x "$PY" ] || PY=python3
25
+ # 测试期间**绝不发遥测**(2026-09-30:夹具流程 flowA 的失败事件曾被推到生产告警)
26
+ export ARGENT_TELEMETRY_OFF=1
27
+ export ARGENT_TESTS_OFFLINE=1 # 测试期**零网络**(2026-10-11):监听类用例(dsh-release-watch 等)见到它就**拒绝**真发请求 ⇒ "夹具回归零网络"由开关兜住,不靠自觉
28
+ # 测试进程**不继承会话/实例的 DSH_HOME**(2026-10-02):
29
+ # DSH_HOME 是**实例级**开关,而数据目录三级链把它排在"默认 ~/.dsh/argent"之前 ⇒
30
+ # 测试用假 HOME 隔离时会被会话的 DSH_HOME 覆盖,**静默写进真实实例**(实测:本机
31
+ # DSH_HOME=~/.dsh 时,role-mapping/skin/设置页用例把 roles.json、skin.json 写进了生产数据目录)。
32
+ # 故跑测试时清掉它(venv 解释器先落成 ARGENT_PY,避免清掉后找不到 venv)。
33
+ if [ -z "${ARGENT_PY:-}" ] && [ -x "${DSH_HOME:-$HOME/.dsh}/argent/venv/bin/python3" ]; then
34
+ export ARGENT_PY="${DSH_HOME:-$HOME/.dsh}/argent/venv/bin/python3"
35
+ fi
36
+ unset DSH_HOME
37
+
38
+ # ── 「需要 git 索引」的用例:**按判据跳过**,⛔ 不是一份"永远跳过"的静态名单 ──────────────
39
+ # 判据只有一个:**被跑的树里有没有 `.git`**(`[ -d .git ]`)。
40
+ # · 树里有 `.git` ⇒ 这些用例**照常跑**(它们的判据本身就是"仓库/工作树状态");
41
+ # · 树里没有(`git archive HEAD` 出来的裸检出)⇒ 显式跳过并**点名原因**(这才是"干净树不适用")。
42
+ # 为什么必须判据驱动(2026-10-14 批):③ 的实例里 `agent-title-naming` / `dead-pointer-scan`
43
+ # 靠 `git ls-files` 列受控文件 ⇒ 裸检出(没有索引)里恒红,是**检查器**的缺陷,不是用例的问题。
44
+ # ⛔ 不许因此改那两条用例的判据、⛔ 不许把它们删掉 —— 只在这里按 `.git` 的有无跳过。
45
+ NEED_GIT_CANDIDATES=(
46
+ # release-dl:跑 release-argent.sh,它做 git status / git rev-parse refs/tags
47
+ release-dl
48
+ # install-capabilities:【7b】完整安装以本仓为克隆源(ARGENT_REPO_URL=<repo>)⇒ 需 .git
49
+ install-capabilities
50
+ # install-registry-loadable:按设计要读工作树/仓库身份(`git rev-parse` 一类)
51
+ install-registry-loadable
52
+ # agent-title-naming:`git ls-files -z` 列受控文本文件(裸检出没有索引 ⇒ execFileSync 抛)
53
+ agent-title-naming
54
+ # dead-pointer-scan:同上(`git ls-files -z` 列受控文件)
55
+ dead-pointer-scan
56
+ )
57
+
58
+ # 该树里**因缺 `.git`** 要跳过的用例子串(一行一个;有 `.git` ⇒ 零行)。
59
+ # ⭐ 这是判据的**唯一实现**:干净树分支与回归夹具(`scripts/tests/clean-tree-git-skips.test.mjs`)
60
+ # 都调它,⛔ 不写第二份名单。
61
+ git_index_skips() {
62
+ local tree="${1:-.}"
63
+ [ -d "$tree/.git" ] || printf '%s\n' "${NEED_GIT_CANDIDATES[@]}"
64
+ }
65
+
66
+ # ── 参数解析(**默认行为不变**:不带参数 = 跑全量套件)──────────────────────────────
67
+ MODE="run"; FILTER=""; SKIP=()
68
+ while [ $# -gt 0 ]; do
69
+ case "${1:-}" in
70
+ --filter) FILTER="${2:-}"; if [ $# -ge 2 ]; then shift 2; else shift; fi ;;
71
+ --skip) if [ $# -ge 2 ] && [ -n "${2:-}" ]; then SKIP+=("$2"); shift 2
72
+ else echo " ✗ --skip 需要一个非空子串(用法见 bash scripts/run-tests.sh --help)"; exit 2; fi ;;
73
+ --clean-tree-check) MODE="clean-tree"; shift ;;
74
+ --print-git-skips) git_index_skips "${2:-.}"; exit 0 ;;
75
+ -h|--help) sed -n '4,19p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
76
+ *) echo " ✗ 未知参数:$1(用法见 bash scripts/run-tests.sh --help)"; exit 2 ;;
77
+ esac
78
+ done
79
+
80
+ # 命中任一 --skip 子串 ⇒ 跳过(与 --filter 同样按路径子串匹配)
81
+ skip_match() {
82
+ local f="$1" s
83
+ for s in ${SKIP[@]+"${SKIP[@]}"}; do [ -n "$s" ] && [[ "$f" == *"$s"* ]] && return 0; done
84
+ return 1
85
+ }
86
+
87
+ # ── `--clean-tree-check`:先建干净树并规避三类环境性假红,再把自己重跑一遍(判据不变)──────
88
+ if [ "$MODE" = "clean-tree" ]; then
89
+ if [ "${ARGENT_CLEAN_TREE_PHASE:-}" = "1" ]; then
90
+ echo " ✗ 已经在干净树自检的子里程里了(不要嵌套)——直接跑 bash scripts/run-tests.sh 即可"; exit 2
91
+ fi
92
+ command -v git >/dev/null 2>&1 || { echo " ✗ 需要 git(干净树自检靠 git archive HEAD 取树)"; exit 2; }
93
+ git -C "$REPO" rev-parse --verify HEAD >/dev/null 2>&1 || {
94
+ echo " ✗ $REPO 不是一个带 HEAD 的 git 仓 —— 干净树自检要在**有 .git 的检出**里发起"
95
+ echo " (它取的是 HEAD 的内容;在裸检出里无从取树)"; exit 2; }
96
+
97
+ CLEAN_DIR="$(mktemp -d "${TMPDIR:-/tmp}argent-clean-tree-XXXXXX")" || exit 2
98
+ if [ "${ARGENT_CLEAN_TREE_KEEP:-}" != "1" ]; then
99
+ trap 'rm -rf "$CLEAN_DIR"' EXIT INT TERM
100
+ fi
101
+ git -C "$REPO" archive HEAD | tar -x -C "$CLEAN_DIR" || { echo " ✗ git archive HEAD 取树失败"; exit 2; }
102
+
103
+ # 闸门:**取出来的那棵树里的 `run-tests.sh` 必须认识 `--skip`**。
104
+ # 为什么必须有它(2026-10-08 实测踩到):干净树取自 `HEAD`,若本次改动**还没提交**,
105
+ # 树里就是**旧版**脚本 —— 而旧版**静默忽略**未知参数 ⇒ `--skip`/`--filter` 全部失效、
106
+ # 跑成"全量 + 那 2 条按设计跳过的用例照样红" ⇒ 输出看起来像"干净树有 4 条红",
107
+ # **把环境性假红伪装成 HEAD 既存红**(正是本自检要消灭的误导)。宁可当场停下,也不给假结论。
108
+ if ! grep -q -- '--skip)' "$CLEAN_DIR/scripts/run-tests.sh" 2>/dev/null; then
109
+ echo " ✗ HEAD 里的 scripts/run-tests.sh 还是旧版(不认识 --skip)——多半是本次改动尚未提交。"
110
+ echo " 裸检出取的是 HEAD 的内容 ⇒ 现在跑只会得到**误导性**结果(--skip/--filter 被静默忽略)。"
111
+ echo " 处置:先 commit(或先把新版 run-tests.sh 提交),再跑 --clean-tree-check。"
112
+ exit 2
113
+ fi
114
+
115
+ # ① 真实路径:`/tmp` → `/private/tmp`(macOS 亦含 `/var` → `/private/var`)是符号链接。
116
+ # 若沿用逻辑路径 `$PWD`,node 侧(`import.meta.url` 走 realpath)与 bash 侧(`pwd` 走 `$PWD`)
117
+ # 会各说各话 ⇒ `repo-root` 用例句"开发机:解析到本仓"恒红。故一律 `pwd -P` 取物理路径再 cd 进去。
118
+ REAL="$(cd "$CLEAN_DIR" && pwd -P)" || exit 2
119
+ cd "$REAL"
120
+
121
+ # ② git **不跟踪空目录** ⇒ `git archive` 自然不带 ⇒ 裸检出里没有该目录。
122
+ # **历史实例**:`scripts/flow-prompts/`(`check-sop-consistency.mjs` 的 C2 曾无条件 readdirSync 它
123
+ # ⇒ 真·新克隆上 ENOENT 判红)。**该条根因已于 2026-10-08 修**(用例容忍缺目录,
124
+ # 见 `legacyPromptFiles()`)⇒ 本步骤现在只是**通用兜底**:对**其它**未跟踪空目录仍然必要。
125
+ # 只镜像**空目录**:内容为空 ⇒ 不可能夹带任何在制品,纯粹补回"目录存在"这个事实。
126
+ MIRRORED=()
127
+ while IFS= read -r d; do
128
+ [ -n "$d" ] || continue
129
+ rel="${d#./}"
130
+ [ -d "$REAL/$rel" ] && continue
131
+ mkdir -p "$REAL/$rel" && MIRRORED+=("$rel")
132
+ done < <(cd "$REPO" && find . -type d -empty -not -name .git -not -name node_modules -not -path '*/.git/*' -not -path '*/node_modules/*' 2>/dev/null | sort)
133
+
134
+ # ③ 需要 `.git` 的用例:**干净树不适用**(裸检出没有 `.git`,它们查的是"仓库/工作树状态",
135
+ # 不是被测代码本身)。⭐ **判据 = `[ -d .git ]`**(实现在 `git_index_skips()` 一处):
136
+ # 树里有 `.git` ⇒ 照常跑;没有 ⇒ 跳过并点名原因。⛔ **不因此改用例语义**、⛔ 不删用例。
137
+ NEED_GIT=()
138
+ while IFS= read -r g; do [ -n "$g" ] && NEED_GIT+=("$g"); done < <(git_index_skips "$REAL")
139
+
140
+ echo
141
+ echo " ═══ 干净树自检(HEAD=$(git -C "$REPO" rev-parse --short HEAD),不含工作树在制品)═══"
142
+ echo " 树位置:$REAL"
143
+ echo " ① 符号链接:已用 pwd -P 的**物理路径**建树并 cd(规避 /tmp → /private/tmp)"
144
+ if [ "${#MIRRORED[@]}" -gt 0 ]; then
145
+ echo " ② 未跟踪空目录:已镜像 ${#MIRRORED[@]} 个 —— ${MIRRORED[*]}"
146
+ else
147
+ echo " ② 未跟踪空目录:无(无需镜像)"
148
+ fi
149
+ if [ -d "$REAL/.git" ]; then
150
+ echo " ③ 需要 .git 的用例:判据 \`[ -d .git ]\` ⇒ 这棵树**有** .git ⇒ 全部照常跑(0 条跳过)"
151
+ else
152
+ echo " ③ 需要 .git 的用例:判据 \`[ -d .git ]\` ⇒ 这棵树**没有** .git ⇒ 按设计跳过 ${#NEED_GIT[@]} 条 —— ${NEED_GIT[*]}"
153
+ echo " · release-dl:跑 release-argent.sh --help,该脚本做 git status / git rev-parse refs/tags"
154
+ echo " · install-capabilities:【7b】完整安装以本仓为克隆源(ARGENT_REPO_URL=<repo>)⇒ 需 .git"
155
+ echo " · install-registry-loadable:按设计要读工作树/仓库身份(\`git rev-parse\` 一类)"
156
+ echo " · agent-title-naming / dead-pointer-scan:靠 \`git ls-files\` 列受控文件 ⇒ 裸检出(无索引)里必然抛(2026-10-14 复现;检查器按判据跳过,⛔ 不改用例判据、⛔ 不删用例)"
157
+ fi
158
+ echo " ⛔ 这是「干净树不适用」,**不是红**;不得据此放宽这些用例的判据(docs/CI-自建runner.md §8.1)"
159
+ echo " · 判据的唯一实现:\`bash scripts/run-tests.sh --print-git-skips <树>\`(一行一个;有 .git ⇒ 零行)"
160
+ echo " ═══════════════════════════════════════════════════════════════════════"
161
+
162
+ inner=()
163
+ for g in "${NEED_GIT[@]}"; do inner+=(--skip "$g"); done
164
+ for s in ${SKIP[@]+"${SKIP[@]}"}; do inner+=(--skip "$s"); done
165
+ [ -n "$FILTER" ] && inner+=(--filter "$FILTER")
166
+ env ARGENT_CLEAN_TREE_PHASE=1 bash "$REAL/scripts/run-tests.sh" ${inner[@]+"${inner[@]}"}
167
+ rc=$?
168
+ if [ "${ARGENT_CLEAN_TREE_KEEP:-}" = "1" ]; then echo " (树已保留:${REAL})"; fi
169
+ exit $rc
170
+ fi
171
+
172
+ pass=0; fail=0; failed=(); skipped=()
173
+ shopt -s nullglob
174
+
175
+ # ── 测试环境卫生:**结果判据**(2026-10-03 两次事故后补;2026-10-04 扩成三层)──────────────
176
+ # 为什么在这里:跑测试**前**给真实数据目录拍一份只读快照,跑完**后**比对 ⇒ 只要有任何一条
177
+ # 路径漏了隔离、写了真实目录,就在**这一轮**被抓(不靠"下次注意")。三层判据:
178
+ # ① 关键文件(skin.json / flow-ledger.jsonl / license-gate.jsonl …)的 size/mtime/sha256 —— **判红**;
179
+ # ② 目录级发现:全树**路径集合差**,只报"新增" —— 抓"在真实目录里新建文件"这个盲区
180
+ # (1016 份夹具备份就是这么溜过去的:当时①全程绿);
181
+ # ③ 配置形态(`*.json`/`*.yml`/`*.yaml`/`*.toml`,排除体积区)的**内容** sha256。
182
+ # 姿态(起步):②③ **只报告、不改退出码**(允许表 `CHURN_ALLOW` 稳定前不判红;要判红得显式
183
+ # `--verify <snap> --strict`)⇒ 本脚本的失败判据仍只由①与各用例决定。
184
+ # 快照文件落在 ${TMPDIR}(不在数据目录里);判据实现见 scripts/check-test-env-safety.mjs。
185
+ SNAP="$(mktemp -t argent-tests-env-snapshot)"
186
+ node scripts/check-test-env-safety.mjs --snapshot "$SNAP" >/dev/null 2>&1 || true
187
+
188
+ for f in scripts/tests/*.test.mjs; do
189
+ [ -n "$FILTER" ] && [[ "$f" != *"$FILTER"* ]] && continue
190
+ if skip_match "$f"; then skipped+=("$f"); continue; fi
191
+ if out=$(env -u DSH_HOME node "$f" 2>&1); then pass=$((pass+1)); else fail=$((fail+1)); failed+=("$f"); printf ' ❌ %s\n%s\n' "$f" "$(printf '%s' "$out" | tail -6 | sed 's/^/ /')"; fi
192
+ done
193
+ for f in scripts/tests/*.test.py; do
194
+ [ -n "$FILTER" ] && [[ "$f" != *"$FILTER"* ]] && continue
195
+ if skip_match "$f"; then skipped+=("$f"); continue; fi
196
+ if out=$(env -u DSH_HOME "$PY" "$f" 2>&1); then pass=$((pass+1)); else fail=$((fail+1)); failed+=("$f"); printf ' ❌ %s\n%s\n' "$f" "$(printf '%s' "$out" | tail -6 | sed 's/^/ /')"; fi
197
+ done
198
+ echo
199
+ if [ "${#skipped[@]}" -gt 0 ]; then
200
+ printf ' ⏭ 按 --skip 跳过 %d 条(**不计入失败**,但它们没有在本轮被验证):%s\n' "${#skipped[@]}" "${skipped[*]}"
201
+ fi
202
+ echo " ============================================="
203
+ printf ' 测试结果:%d 通过 / %d 失败' "$pass" "$fail"
204
+ [ "$fail" -eq 0 ] && printf ' ✓\n' || printf '\n 失败清单:%s\n' "${failed[*]}"
205
+ echo " ============================================="
206
+ # 红了先分清"真问题"与"环境/在制品":**干净树(= git archive HEAD,不含工作树在制品)也应全绿**。
207
+ # 仍红 ⇒ HEAD 既存红(提交本身的问题,必须修)。判据与**两类假红**的区分见 docs/CI-自建runner.md §8.1。
208
+ # 下面这条命令是**可复现**的:它自己规避符号链接、镜像未跟踪空目录、并按设计跳过需要 .git 的用例。
209
+ [ "$fail" -eq 0 ] || echo " ↳ 干净树判据(可复现):bash scripts/run-tests.sh --clean-tree-check"
210
+
211
+ # ── 跑完立刻比对真实数据目录(只读比较)──────────────────────────────────────────────
212
+ # ① 变了 ⇒ 判红并点名文件(**退出码语义与以前一致**);②③ 的发现只打印(起步姿态),不在这里判红。
213
+ if [ -f "$SNAP" ]; then
214
+ if ! env -u DSH_HOME node scripts/check-test-env-safety.mjs --verify "$SNAP"; then
215
+ fail=$((fail+1)); failed+=("(真实数据目录被写)")
216
+ fi
217
+ rm -f "$SNAP"
218
+ fi
219
+ [ "$fail" -eq 0 ] || exit 1
220
+
221
+ # ── 能力包闭包体检(只报告;**不**参与失败判据,2026-10-04)────────────────────────────
222
+ # 判据:`scripts/check-pack-closure.mjs` —— 按每个能力包的 pack.engine + files 清单解析包内代码的
223
+ # 静态 import / Python import / 子进程调用图,报"引用到、仓库里有、但不在包内"的运行期文件。
224
+ # 为什么接在**非失败**位置:缺件可能常年存在(输出里带修法),接进失败判据就是把门禁变成恒红陷阱;
225
+ # 真要有非零判据请单独跑 `node scripts/check-pack-closure.mjs --strict`(或接进 CI)。
226
+ # 全量跑时打一次基线,让"包内容漂移"不会悄悄发生;`--filter` 迭代时不跑(省时间)。
227
+ # 位置在 provider 装配巡检之前:那一段是最后一块且不改变退出码(本段同样不改变)。
228
+ if [ -z "$FILTER" ] && [ -f "$REPO/scripts/check-pack-closure.mjs" ]; then
229
+ echo
230
+ if closure=$(env -u DSH_HOME node "$REPO/scripts/check-pack-closure.mjs" --quiet 2>&1); then
231
+ printf '%s\n' "$closure" | sed '/^[[:space:]]*$/d; s/^/ /'
232
+ case "$closure" in
233
+ *"缺件 0 个"*) ;;
234
+ *) echo " ↳ 缺件修法:写进 scripts/flow-requirements.json#pack.engine(core 件另进 CORE_KEY),"
235
+ echo " 再提三个带 flow 的能力包版本 + python3 scripts/capability_pack.py --stamp;详见 docs/README.md §六.13" ;;
236
+ esac
237
+ else
238
+ echo " ⚠️ 能力包闭包体检没跑成(exit≠0,多半是 python 不可用)—— 手动跑 node scripts/check-pack-closure.mjs"
239
+ fi
240
+ fi
241
+
242
+ # ── 生产状态守卫(2026-09-30 加;2026-10-02 口径更新):跑完测试立刻做一次 **provider 装配巡检** ──
243
+ # 背景(历史):`~/.dsh/settings.yaml` 会被"自身 schema 重写"丢掉未知的 `llm-deepseek` 段 ⇒ 当时那套
244
+ # 「计费模式」静默退回直连(实测:文件只剩 1 字节)。**那套机制已移除** —— 现在是两个独立 provider
245
+ # 并存(官方直连 / 问述代理),由用户在输入框的模型选择器里选 ⇒ 判据变成「**组合声明了的** provider
246
+ # 有没有装上」:exit 1 = 声明了却没装上;exit 2 = 无法判定(组合树读不到)。
247
+ if [ -x "$REPO/scripts/check-billing-mode.sh" ]; then
248
+ if ! bash "$REPO/scripts/check-billing-mode.sh" >/tmp/billing-guard.out 2>&1; then
249
+ echo
250
+ echo " ⚠️ provider 装配守卫:组合**声明了**的 provider 没装上(exit 1)或无法判定(exit 2)"
251
+ sed 's/^/ /' /tmp/billing-guard.out
252
+ echo " ↳ 处置(**请手动执行**):bash scripts/provider-select.sh status 看当前默认 provider;"
253
+ echo " 要切换用 bash scripts/provider-select.sh {whyshu|deepseek}(改完需重启 dsh)。"
254
+ echo " ⛔ 不要用 check-billing-mode.sh --fix(已熔断 exit 3):它会诱使人改官方 provider 的 baseURL。"
255
+ # 不再因此失败:**用户有权选择官方直连**(2026-09-30 纠正)。只大声提示,不阻断。
256
+ fi
257
+ fi
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ // 0.7.0 W8/S1 **精确分类器**(只读):把 `~/.dsh` 字面量分成
3
+ // A 写入点(改它才影响"数据落哪",必须改)| B 回退候选(只读候选链,按分类保留)| C 注释(不动)。
4
+ // 用法:node scripts/s1-audit.mjs [--json] [--only A]
5
+ import { readdirSync, readFileSync, statSync } from 'node:fs'
6
+ import { join, relative, dirname } from 'node:path'
7
+ import { fileURLToPath } from 'node:url'
8
+ const REPO = join(dirname(fileURLToPath(import.meta.url)), '..')
9
+ const SKIP = new Set(['.git', 'node_modules', 'dist', '__pycache__', 'attic', '.venv', 'docs', 'patches', 'tests'])
10
+ const RE = /\.dsh\/argent|~\/\.dsh|\$HOME\/\.dsh|\$\{HOME\}\/\.dsh/
11
+ function walk(dir, out = []) {
12
+ for (const e of readdirSync(dir)) {
13
+ if (SKIP.has(e)) continue
14
+ const p = join(dir, e); const st = statSync(p)
15
+ if (st.isDirectory()) walk(p, out)
16
+ else if (/\.(mjs|js|cjs|py|sh)$/.test(e) && st.size < 2_000_000) out.push(p)
17
+ }
18
+ return out
19
+ }
20
+ // 去注释:整块块注释、行首 # 或 // 、以及行尾 // 之后的片段(⚠️ 本文件注释里别写那对定界符,会提前闭合)
21
+ function stripComments(src, ext) {
22
+ let s = src.replace(/\/\*[\s\S]*?\*\//g, '')
23
+ return s.split('\n').map((line) => {
24
+ const t = line.trimStart()
25
+ if (t.startsWith('#') || t.startsWith('*') || t.startsWith('//')) return ''
26
+ if (ext === '.py' || ext === '.sh') return line.replace(/(^|\s)#.*$/, '')
27
+ return line.replace(/\/\/.*$/, '')
28
+ })
29
+ }
30
+ const WRITE = /(=|>>?|mkdir|touch|tee|appendFile|writeFile|Path\(|open\(|log|LOG_DIR|LOGDIR|DATA_DIR|DATA_HOME|PAUSE)/i
31
+ const FALLBACK = /(for\s+\w+\s+in|\|\||-f\s|\$\(.*\|\||candidates?|fallback|候选)/i
32
+ const files = walk(REPO).map((f) => ({ f, ext: f.slice(f.lastIndexOf('.')) }))
33
+ const rows = []
34
+ for (const { f, ext } of files) {
35
+ const lines = stripComments(readFileSync(f, 'utf8'), ext)
36
+ const hits = []
37
+ lines.forEach((line, i) => {
38
+ if (!RE.test(line)) return
39
+ const kind = WRITE.test(line) && !FALLBACK.test(line) ? 'A' : FALLBACK.test(line) ? 'B' : 'A?'
40
+ hits.push({ n: i + 1, kind, text: line.trim().slice(0, 100) })
41
+ })
42
+ if (hits.length) rows.push({ file: relative(REPO, f), hits })
43
+ }
44
+ const count = (k) => rows.reduce((n, r) => n + r.hits.filter((h) => h.kind === k).length, 0)
45
+ const doc = { files: rows.length, A: count('A'), 'A?': count('A?'), B: count('B'), rows: rows.sort((a, b) => b.hits.length - a.hits.length) }
46
+ if (process.argv.includes('--writes')) {
47
+ // 「非赋值写入」口径:redirect / mkdir / touch / `-f` 测试 / 内嵌 open —— 这些同样决定"数据落哪"
48
+ const W = /(>>?\s*\S*\.dsh\/argent|mkdir[^\n]*\.dsh\/argent|touch[^\n]*\.dsh\/argent|-f\s+"?\S*\.dsh\/argent|open\([^\n]*\.dsh\/argent|\btee\b[^\n]*\.dsh\/argent)/
49
+ const MSG = /(echo |scp |note\(|\$FIX)/ // 提示语/夹具路径 ⇒ 不算写入
50
+ const hits = []
51
+ for (const r of rows) for (const h of r.hits) if (W.test(h.text) && !MSG.test(h.text)) hits.push({ file: r.file, line: h.n, text: h.text })
52
+ if (process.argv.includes('--json')) { console.log(JSON.stringify({ total: hits.length, hits }, null, 2)); process.exit(0) }
53
+ console.log(`非赋值写入残留:**${hits.length} 处**(目标 0;白名单外的都算红)`)
54
+ for (const h of hits.slice(0, 10)) console.log(` ${h.file}:${h.line} ${h.text.slice(0, 88)}`)
55
+ process.exit(0)
56
+ }
57
+ if (process.argv.includes('--assignments')) {
58
+ // 「只认赋值行」口径:真正决定"数据落哪"的写法(⛔ 不含候选链/提示语/注释)
59
+ const ASSIGN = /^\s*(export\s+)?[A-Za-z_][A-Za-z0-9_]*=(.*)$/
60
+ const out = []
61
+ for (const r of rows) for (const h of r.hits) if (ASSIGN.test(h.text) && RE.test(h.text)) out.push({ file: r.file, line: h.n, text: h.text })
62
+ const argentHits = out.filter((h) => /\.dsh\/argent/.test(h.text))
63
+ const hostHits = out.filter((h) => !/\.dsh\/argent/.test(h.text))
64
+ if (process.argv.includes('--json')) {
65
+ console.log(JSON.stringify({ total: out.length, argentDataDir: argentHits.length, hostOwnedDsh: hostHits.length, hits: out }, null, 2))
66
+ process.exit(0)
67
+ }
68
+ // ⚠️ 必须拆分:`~/.dsh/argent`(= **Argent 数据目录**,W8 要退役的)vs 其它 `.dsh`(dsh 自己的 home:
69
+ // settings.yaml / profiles/ / .agent-presets ⇒ 属宿主,不归 Argent 管)
70
+ const argent = argentHits, hostOwn = hostHits // 与 --json 同源,⛔ 不写两遍拆分
71
+ if (process.argv.includes('--json')) { console.log(JSON.stringify({ total: out.length, argentDataDir: argent.length, hostOwnedDsh: hostOwn.length, hits: out }, null, 2)); process.exit(0) }
72
+ console.log(`赋值行字面量残留:**${out.length} 处**,其中 Argent 数据目录 \`~/.dsh/argent\` **${argent.length} 处**(W8 目标 = 0)`)
73
+ console.log(` 另 ${hostOwn.length} 处是 **dsh 自己的 home**(settings/profiles/presets)⇒ 属宿主面,不归 Argent`)
74
+ for (const h of argent.slice(0, 12)) console.log(` [argent] ${h.file}:${h.line} ${h.text.slice(0, 84)}`)
75
+ process.exit(0)
76
+ }
77
+ if (process.argv.includes('--json')) { console.log(JSON.stringify(doc, null, 2)); process.exit(0) }
78
+ console.log(`非注释字面量:${doc.files} 个文件 | A 写入点 ${doc.A} | A? 待判 ${doc['A?']} | B 回退候选 ${doc.B}`)
79
+ const onlyA = process.argv.includes('--only') && process.argv[process.argv.indexOf('--only') + 1] === 'A'
80
+ for (const r of doc.rows.slice(0, 12)) {
81
+ const hs = onlyA ? r.hits.filter((h) => h.kind.startsWith('A')) : r.hits
82
+ if (!hs.length) continue
83
+ console.log(` ${r.file} (${hs.length})`)
84
+ for (const h of hs.slice(0, 3)) console.log(` [${h.kind}] L${h.n}: ${h.text}`)
85
+ }