@whyshu/argent-engine 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,128 @@
1
+ /**
2
+ * 插件生命周期名单(**唯一真源**,profile-doctor 与 argent-upgrade 共用)。
3
+ *
4
+ * 为什么要有这份名单:profile 组合是"多来源手工拼装"的(bundles + cordis.patch.yml +
5
+ * dependencies + node_modules),任何一处漂移都不立刻报错,只在"下次重启"爆。
6
+ * 两处知识必须一致,否则 doctor 报红、升级器却以为正常(2026-09-28 客户机 P3 实测)。
7
+ * 组合目标本身在 plugin-composition.json(连 Python 预检也读它)。
8
+ */
9
+ import { readFileSync } from 'node:fs'
10
+ import { dirname, join } from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+ import { npmNameOf } from './plugin-packages.mjs'
13
+
14
+ /**
15
+ * 组合目标(**唯一真源** = scripts/plugin-composition.json,JS 升级器与 Python 预检共用)。
16
+ * 2026-09-28 的教训:预检硬编码「期望 argent-flow」,而升级目标组合里没有它
17
+ * ⇒ 预检报红、升级器却以为正常。故两边都从这里读。
18
+ */
19
+ const COMPOSITION_FILE = join(dirname(fileURLToPath(import.meta.url)), '..', 'plugin-composition.json')
20
+
21
+ /** @returns {{ [type: string]: { patch: string[], bundles: string[] } }} */
22
+ export function targetCombos() {
23
+ const raw = JSON.parse(readFileSync(COMPOSITION_FILE, 'utf8'))
24
+ const out = {}
25
+ for (const [k, v] of Object.entries(raw)) {
26
+ if (k.startsWith('_')) continue
27
+ out[k] = { patch: [...(v.patch || [])], bundles: [...(v.bundles || [])] }
28
+ }
29
+ return out
30
+ }
31
+
32
+ /** 某个实例类型要装进 profile 的全部插件(patch 行 + bundle 层)。 */
33
+ export function comboPlugins(type) {
34
+ const c = targetCombos()[type]
35
+ if (!c) throw new Error(`未知实例类型 ${type}(见 scripts/plugin-composition.json)`)
36
+ return [...c.patch, ...c.bundles]
37
+ }
38
+
39
+ /**
40
+ * 组合条目 → `cordis.patch.yml` 的行(`{id, name}`)。
41
+ *
42
+ * W9(2026-10-12):`name:` 是 **npm 包名**(`@whyshu/argent-core`),`id:` 仍是裸 id。
43
+ * 两者分开是插件的结构性事实(口径在 `scripts/lib/plugin-packages.mjs`);
44
+ * ⛔ 未知插件(历史/试验行)原样回裸名,⛔ **不**善意补 scope。
45
+ * @param {string} type 实例类型
46
+ * @returns {Array<{id:string, name:string}>}
47
+ */
48
+ export function compositionRows(type) {
49
+ const c = targetCombos()[type]
50
+ if (!c) throw new Error(`未知实例类型 ${type}(见 scripts/plugin-composition.json)`)
51
+ return c.patch.map((id) => ({ id, name: npmNameOf(id) || id }))
52
+ }
53
+
54
+ /** 插件 id → 组合行的规范 `name:`(npm 名;未知 id ⇒ 原样回)。 */
55
+ export function pluginRowName(id) {
56
+ return npmNameOf(id) || String(id)
57
+ }
58
+
59
+ /**
60
+ * 已迁到 **bundle 层** 的插件:出现在 `cordis.patch.yml` 的 insert 行里即为重复挂载
61
+ * (加载器报 `duplicate loader entry id` ⇒ dsh web 启动即失败 ⇒ KeepAlive crash loop)。
62
+ * 它们必须登记在 profile `package.json` 的 `dsh.profile.bundles` 里。
63
+ */
64
+ export const BUNDLE_LAYER_PLUGINS = [...new Set(Object.values(targetCombos()).flatMap((c) => c.bundles))]
65
+
66
+ /**
67
+ * bundle 层名单的 `{id, name}` 配对(**唯一**出口;⛔ 不许在调用方再拼一次名字)。
68
+ *
69
+ * W9(2026-10-13):`dsh.profile.bundles` 的条目是**包名**(`@whyshu/argent-skin`),不是 id。
70
+ * 名单本身(`plugin-composition.json#*.bundles`)用 id 声明"哪些插件走 bundle 层",
71
+ * 名字一律经 `plugin-packages.mjs` 求 —— 两件事分开,是本模块的结构性事实。
72
+ * 存量机器上是裸名条目(W9 漏迁的第三处)⇒ 读侧用
73
+ * `plugin-packages.mjs#migrateBundleNames()` 迁,⛔ 不要在这里另写一份映射。
74
+ */
75
+ export function bundleLayerRows() {
76
+ return BUNDLE_LAYER_PLUGINS.map((id) => ({ id, name: pluginRowName(id) }))
77
+ }
78
+
79
+ /** 迁移原因(doctor 报告用)。 */
80
+ export const BUNDLE_LAYER_REASON = '已改为 bundle 层插件(登记在 dsh.profile.bundles)'
81
+
82
+ /** 必须存在的 patch 行(缺失即补)。 */
83
+ export const REQUIRED_PATCH_ROWS = ['argent-core']
84
+
85
+ /**
86
+ * 组合层面退役的插件:patch 层出现这些行就摘掉。判据是「**source 已从仓库删除**或已被
87
+ * 原生 dsh 覆盖」——不是「不在目标组合里」(后者会误伤活着的插件)。
88
+ * 2026-09-28 补 kanban/notify/drive:P4-4c 退场时只改了组合生成器,客户机 patch 层
89
+ * 仍留着 insert 行 ⇒ doctor 报 row-pkg-missing(包根本没装)⇒ 升级卡在体检门禁上。
90
+ */
91
+ export const RETIRED_PATCH_ROWS = {
92
+ 'argent-files': '已废弃——原生 dsh 已有文件引用(dsh-file-reference / -local / sidebar-files)',
93
+ 'argent-instances': '已退役(P4-4c:实例管理并入 argent-core)',
94
+ 'argent-instances-local': '已退役(P4-4c:实例管理并入 argent-core)',
95
+ 'argent-tool-instances': '已退役(P4-4c:实例管理并入 argent-core)',
96
+ 'argent-packs': '已退役(P4-4c:能力包改由 scripts/ 与 install.json 管理)',
97
+ 'argent-ml': '已退出常驻组合行——按需安装的独立插件(源码保留在 plugins/argent-ml)',
98
+ 'argent-kanban': '已退役(P4-4c:看板退场)',
99
+ 'argent-notify': '已退役(P4-4c:通知并入网关)',
100
+ 'argent-drive': '已退役(P4-4c:云盘能力退场)',
101
+ }
102
+
103
+ /**
104
+ * 允许**保留源码**的退役插件:这些名字仍出现在 `plugins/` 下(按需安装 / 曾以 tgz 交付),
105
+ * 但**不得**出现在任何 profile 的常驻组合行里。
106
+ * 与「源码已从仓库删除」的退役(kanban/notify/drive…)区分开:字段一个样,
107
+ * 不变量不一样,混在一起会让门禁假红(2026-09-28 实测:argent-ml 就是这么被抓出来的)。
108
+ */
109
+ export const LEGACY_PLUGIN_SOURCES = ['argent-ml']
110
+
111
+ /** 生成 { id: reason } 副本(doctor 用;避免调用方直接改到共享对象)。 */
112
+ export function retiredRowMap() {
113
+ return { ...RETIRED_PATCH_ROWS }
114
+ }
115
+
116
+ /** 退役插件 id 集合(升级器摘行用)。 */
117
+ export function retiredRowIds() {
118
+ return new Set(Object.keys(RETIRED_PATCH_ROWS))
119
+ }
120
+
121
+ /**
122
+ * 生成 { id: reason } 形态,供 doctor 的 BUNDLE_MIGRATED 直接使用。
123
+ */
124
+ export function bundleLayerMap() {
125
+ const out = {}
126
+ for (const id of BUNDLE_LAYER_PLUGINS) out[id] = BUNDLE_LAYER_REASON
127
+ return out
128
+ }
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Argent 插件的 **id ↔ npm 包名** 唯一真源(W9 · 2026-10-12)。
3
+ *
4
+ * ## 为什么要有这份名单
5
+ * W9 把插件从"只靠 `file:` 指向客户机克隆"改成"**发 npm**"(`@whyshu/argent-*`,
6
+ * 用户名 scope;见 `docs/Argent-插件化评估与实施计划.md` §6 决策点 4)。于是**同一个插件**
7
+ * 在一个仓库里有两个名字,且**两个名字都必须被接受**:
8
+ *
9
+ * | 名字 | 谁在用 | 会不会变 |
10
+ * |---|---|---|
11
+ * | **id**(`argent-core`) | 目录名 `plugins/argent-core`、组合条目 `- id:`、`plugin-composition.json`、`agents/catalog.json#plugins`、退役名单、日志前缀 | ⛔ **不变**(改成 scoped 会让存量 profile 的组合行、台账、catalog 一起漂) |
12
+ * | **npm 名**(`@whyshu/argent-core`) | `package.json#name`、profile `dependencies` 的键、`cordis.patch.yml` 的 `name:`、`dsh plugin add <spec>` | 本批**新增** |
13
+ *
14
+ * ⚠️ profile `dependencies` 的**键**由 `file:` 目标自己的 `package.json#name` 决定
15
+ * (实测 pnpm 11:`pnpm add file:<dir>` 用 target 的 name 建键,**不是**目录名)⇒ 一旦
16
+ * `package.json#name` 变成 scoped,`file:` 安装**也会**落成 scoped 键 ⇒ 两态收敛到同一个名字。
17
+ * 这条是本设计的前提,也是"文件名/目录名不动、只动包名"能成立的原因。
18
+ *
19
+ * ## 过渡兼容(⛔ 不许把存量客户弄坏)
20
+ * 存量 profile 里是 `dependencies['argent-core'] = file:…` + 组合行 `name: 'argent-core'` +
21
+ * `dsh.profile.bundles` 里的裸名条目 `'argent-skin'`(第三处,2026-10-13 真机才补上)。
22
+ * 本模块提供**双向解析**(`pluginIdOf` / `npmNameOf` 都同时认两个名字)与**幂等迁移**
23
+ * (`migrateDependencyKeys` / `migrateBundleNames` 在本文件;`migratePluginRowNames` 见 `patch-compose.mjs`):
24
+ * · 安装器**读**存量状态时两个名字都认(⛔ 不把旧裸名当成"未知/悬挂");
25
+ * · 安装器**写**新状态时一律写 npm 名;
26
+ * · 迁移**幂等**(第二次跑零改动)且**有备份 + 回滚**(见 `install-argent.mjs`)。
27
+ *
28
+ * ## 纪律
29
+ * 纯 node 内建(`node:fs` / `node:path`),零第三方、零宿主 import ⇒ 与
30
+ * `scripts/lib/dsh-versions.mjs` 同档(可被 L1/L2 双侧安全复用)。
31
+ * ⛔ **不要**在这里加"哪些插件该装"的判据 —— 那是 `plugin-composition.json` /
32
+ * `agents/catalog.json` 的事,本文件只回答"这个插件的包名是什么、这个名字是谁"。
33
+ */
34
+ import { readFileSync } from 'node:fs'
35
+
36
+ /** npm 用户名 scope(npm 账号名 = `whyshu`;⛔ **不是组织**,见计划 §6 决策点 4)。 */
37
+ export const NPM_SCOPE = '@whyshu'
38
+
39
+ /**
40
+ * 插件清单(**唯一真源**)。新增插件在这里加一行即可 ——
41
+ * 安装器 / 组合渲染 / 迁移 / 回归用例都从这里取,⛔ 不许在别处再写一份字面量。
42
+ *
43
+ * `id` = 目录名(`plugins/<id>`)、组合条目 id、日志前缀;
44
+ * `npm` = npm 包名(`package.json#name`、`dependencies` 键、组合行 `name:`)。
45
+ */
46
+ export const PLUGIN_PACKAGES = [
47
+ { id: 'argent-core', npm: `${NPM_SCOPE}/argent-core` },
48
+ { id: 'argent-skin', npm: `${NPM_SCOPE}/argent-skin` },
49
+ { id: 'argent-presets', npm: `${NPM_SCOPE}/argent-presets` },
50
+ { id: 'argent-flow', npm: `${NPM_SCOPE}/argent-flow` },
51
+ { id: 'argent-gateway', npm: `${NPM_SCOPE}/argent-gateway` },
52
+ { id: 'argent-ml', npm: `${NPM_SCOPE}/argent-ml` },
53
+ // W2(0.6.0):**壳 bundle** —— 不是第 7 个「代码插件」,而是一次装配 core+skin+presets 的 **bundle**
54
+ // (零 `src/`、零宿主 import ⇒ `peerDependencies` 结构性为空,见 `plugin-peer-coverage` 的豁免登记表)。
55
+ // 登记在这里的理由与其它插件**同一条**:
56
+ // · 安装器要按**官方路径**装它(`@whyshu/argent-shell@<版本>`)与 `file:` 回退
57
+ // (`file:<克隆>/plugins/argent-shell`),两种规格都由本文件产出(⛔ 不再在安装器里拼第二份字面量);
58
+ // · 发布流水线(`scripts/npm-publish-argent.mjs --only argent-shell`)按本清单认包
59
+ // —— 不登记 ⇒ Lead 用**官方**发布脚本发不出它。
60
+ { id: 'argent-shell', npm: `${NPM_SCOPE}/argent-shell` },
61
+ ]
62
+
63
+ /**
64
+ * **智能体 bundle 的目录名前缀**(W3,0.6.0):`plugins/argent-agent-<agent-id>`。
65
+ *
66
+ * ⚠️ 这里**只认前缀、绝不列名字**:agent id 由 **L0 货架声明**(`agents/catalog.json#agents[].id`)给定,
67
+ * 其中含**业务线词**(货架上的商品名本身就带它)⇒ 把逐个名字写进引擎面会**当场脏掉**
68
+ * `scripts/vertical-lint.mjs` 的 engine-words 判据(基线 0 新增 = 判红)。名字的真源仍是货架声明,
69
+ * 本文件只回答"这个前缀的包名怎么拼"。
70
+ */
71
+ export const AGENT_BUNDLE_PREFIX = 'argent-agent-'
72
+
73
+ /**
74
+ * 是不是**智能体 bundle** 的目录名/id(`argent-agent-<id>`;⛔ 不认路径形态与子路径导出)。
75
+ * 判据 = 前缀 + 非空 + 单段(含 `/` 的是 scoped 名/路径,走各自的归一分支)。
76
+ */
77
+ export function isAgentBundleId(ref) {
78
+ const s = String(ref ?? '').trim()
79
+ return s.startsWith(AGENT_BUNDLE_PREFIX) && s.length > AGENT_BUNDLE_PREFIX.length && !s.includes('/')
80
+ }
81
+
82
+ /** 全部插件 id(保序)。 */
83
+ export const PLUGIN_IDS = PLUGIN_PACKAGES.map((p) => p.id)
84
+
85
+ /** 全部 npm 包名(保序)。 */
86
+ export const PLUGIN_NPM_NAMES = PLUGIN_PACKAGES.map((p) => p.npm)
87
+
88
+ const BY_ID = new Map(PLUGIN_PACKAGES.map((p) => [p.id, p]))
89
+ const BY_NPM = new Map(PLUGIN_PACKAGES.map((p) => [p.npm, p]))
90
+
91
+ /** 去掉子路径导出(`argent-core/llm-whyshu` → `argent-core`)。 */
92
+ function baseRef(ref) {
93
+ const s = String(ref ?? '').trim().replace(/\\/g, '/')
94
+ if (!s) return ''
95
+ if (s.startsWith('@')) {
96
+ const parts = s.split('/')
97
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : s
98
+ }
99
+ return s.split('/')[0]
100
+ }
101
+
102
+ /** 是不是**已知**的 Argent 插件 id(裸名形态;也接受 `@whyshu/argent-*`)。 */
103
+ export function isKnownPluginId(ref) {
104
+ return pluginIdOf(ref) !== null
105
+ }
106
+
107
+ /**
108
+ * 把**任意一种写法**归一到插件 id —— 认:
109
+ * · 裸 id:`argent-core`
110
+ * · npm 名:`@whyshu/argent-core`
111
+ * · 子路径导出:`argent-core/llm-whyshu`、`@whyshu/argent-core/llm-whyshu`
112
+ * · 目录/`file:` 规格里带上的路径尾段:`/x/plugins/argent-core`、`file:/x/plugins/argent-core`
113
+ * 认不出 ⇒ `null`(⛔ 不猜、不抛)。
114
+ */
115
+ export function pluginIdOf(ref) {
116
+ let s = baseRef(String(ref ?? '').replace(/^file:/, ''))
117
+ if (!s) return null
118
+ if (BY_ID.has(s)) return s
119
+ if (BY_NPM.has(s)) return BY_NPM.get(s).id
120
+ // W3:**智能体 bundle**(`argent-agent-<id>`)—— 按**规则**认(前缀),⛔ 不看名字表(名字带业务线词)
121
+ if (isAgentBundleId(s)) return s
122
+ if (s.startsWith(`${NPM_SCOPE}/`)) {
123
+ const scoped = s.slice(NPM_SCOPE.length + 1)
124
+ if (isAgentBundleId(scoped)) return scoped
125
+ }
126
+ // 路径形态(绝对/相对):取尾段再判一次
127
+ const tail = s.split('/').filter(Boolean).pop()
128
+ if (tail && BY_ID.has(tail)) return tail
129
+ if (tail && isAgentBundleId(tail)) return tail
130
+ return null
131
+ }
132
+
133
+ /**
134
+ * 插件 id → npm 包名。传进来的若是**已知**的 npm 名则原样回;未知 ⇒ `null`
135
+ * (⛔ 不"善意补 scope" —— 那会让打错的名字静默变成另一个包)。
136
+ */
137
+ export function npmNameOf(id) {
138
+ const i = pluginIdOf(id)
139
+ if (!i) return null
140
+ if (BY_ID.has(i)) return BY_ID.get(i).npm
141
+ // W3:智能体 bundle 的 npm 名 = `@whyshu/<目录名>`(规则,⛔ 不查表)
142
+ return isAgentBundleId(i) ? `${NPM_SCOPE}/${i}` : null
143
+ }
144
+
145
+ /** 插件名(id 或 npm 名)→ 清单条目 `{id, npm}`;未知 ⇒ `null`。 */
146
+ export function packageMetaOf(ref) {
147
+ const i = pluginIdOf(ref)
148
+ if (!i) return null
149
+ return BY_ID.has(i) ? BY_ID.get(i) : { id: i, npm: `${NPM_SCOPE}/${i}` }
150
+ }
151
+
152
+ /** `plugins/<id>` 的绝对路径(`repoRoot` = 仓库根)。 */
153
+ export function pluginPackageDir(repoRoot, ref) {
154
+ const i = pluginIdOf(ref)
155
+ if (!i) return null
156
+ return `${String(repoRoot).replace(/[\\/]+$/, '')}/plugins/${i}`
157
+ }
158
+
159
+ /** `file:` 规格(离线/送装/无网回退用;pnpm 会按 target 的 `package.json#name` 建键 ⇒ 落 scoped)。 */
160
+ export function fileSpecOf(repoRoot, ref) {
161
+ const dir = pluginPackageDir(repoRoot, ref)
162
+ return dir ? `file:${dir.replace(/\\/g, '/')}` : null
163
+ }
164
+
165
+ /** npm 规格(`@whyshu/argent-core@1.2.3`;不给版本 ⇒ 不带 `@版本`)。 */
166
+ export function npmSpecOf(ref, version = '') {
167
+ const npm = npmNameOf(ref)
168
+ if (!npm) return null
169
+ return version ? `${npm}@${String(version).replace(/^v/, '')}` : npm
170
+ }
171
+
172
+ /**
173
+ * 去重归一到 id 列表(保序;未知项**原样保留**在 `unknown` 里给调用方处理,⛔ 不静默丢)。
174
+ * @returns {{ids: string[], unknown: string[]}}
175
+ */
176
+ export function normalizePluginRefs(list) {
177
+ const ids = []
178
+ const unknown = []
179
+ for (const raw of list ?? []) {
180
+ const i = pluginIdOf(raw)
181
+ if (i) { if (!ids.includes(i)) ids.push(i) } else unknown.push(String(raw))
182
+ }
183
+ return { ids, unknown }
184
+ }
185
+
186
+ /** 读 `plugins/<id>/package.json#version`(读不到 ⇒ `null`;⛔ 不编造)。 */
187
+ export function readPluginVersion(repoRoot, ref, { readFile = readFileSync } = {}) {
188
+ const dir = pluginPackageDir(repoRoot, ref)
189
+ if (!dir) return null
190
+ try {
191
+ const doc = JSON.parse(readFile(`${dir}/package.json`, 'utf8'))
192
+ return typeof doc.version === 'string' ? doc.version : null
193
+ } catch { return null }
194
+ }
195
+
196
+ /** 读 `plugins/<id>/package.json`(读不到 ⇒ `null`)。 */
197
+ export function readPluginManifest(repoRoot, ref, { readFile = readFileSync } = {}) {
198
+ const dir = pluginPackageDir(repoRoot, ref)
199
+ if (!dir) return null
200
+ try { return JSON.parse(readFile(`${dir}/package.json`, 'utf8')) } catch { return null }
201
+ }
202
+
203
+ // ── 依赖图迁移(纯函数;安装器与回归用例共用,⛔ 不写第二份)────────────────────
204
+
205
+ /**
206
+ * **依赖键迁移**:把存量 profile 里的裸键(`argent-core`)改成 npm 名键(`@whyshu/argent-core`)。
207
+ *
208
+ * 语义(刻意保守):
209
+ * · 只碰**本仓已知插件**的键(⛔ 绝不泛化改别人的依赖);
210
+ * · `spec` **原样保留**(`file:` / npm / 版本范围都不动 —— 迁移的是**键**,不是来源);
211
+ * · 两个键都在(迁移跑过一半 / npm add 之后的中间态)⇒ 裸键判为**陈旧别名**,进 `stale`
212
+ * 由调用方在"组合行已全部迁完"之后决定是否 `pnpm remove`(⛔ 本函数**不动**它)。
213
+ * · 幂等:再喂进来 ⇒ `renamed`/`stale` 皆空、`deps` 逐字节等价。
214
+ * @param {Record<string,string>} deps
215
+ * @returns {{deps: Record<string,string>, renamed: Array<{from:string,to:string}>, stale: string[]}}
216
+ */
217
+ export function migrateDependencyKeys(deps) {
218
+ const out = {}
219
+ const renamed = []
220
+ const stale = []
221
+ const src = deps && typeof deps === 'object' ? deps : {}
222
+ for (const [k, v] of Object.entries(src)) {
223
+ const id = pluginIdOf(k)
224
+ if (!id) { out[k] = v; continue }
225
+ const npm = BY_ID.get(id).npm
226
+ // 键已经是 npm 名 ⇒ 本位
227
+ if (k === npm) { out[k] = v; continue }
228
+ // 裸键:若 npm 键已在 ⇒ 它是陈旧别名;否则改名(spec 原样)
229
+ if (Object.prototype.hasOwnProperty.call(src, npm)) {
230
+ stale.push(k)
231
+ out[k] = v // ⛔ 先留着:组合行还没迁完时它是旧行的解析来源
232
+ } else {
233
+ out[npm] = v
234
+ renamed.push({ from: k, to: npm })
235
+ }
236
+ }
237
+ return { deps: out, renamed, stale }
238
+ }
239
+
240
+ /**
241
+ * **严格**判据(**改写客户 profile 时用这个**):只认两种**精确**写法 ——
242
+ * · 裸 id:`argent-core`
243
+ * · 本仓 npm 名:`@whyshu/argent-core`
244
+ * ⛔ 不认路径形态、不认"尾段同名"的**别的 scope**(`@acme/argent-skin`)。
245
+ *
246
+ * 为什么必须与宽松的 {@link pluginIdOf} 分开:`pluginIdOf` 要认 `file:/x/plugins/argent-core`
247
+ * 这类**路径**(读侧判"装没装"),而"尾段同名"这条在**写侧**是灾难 —— 它会把**第三方的**
248
+ * `@acme/argent-skin` 改写成 `@whyshu/argent-skin`(⛔ 把别人的包换成我们的)。
249
+ * 读侧宽松、写侧严格,是本模块的一条硬边界。
250
+ */
251
+ export function exactPluginIdOf(ref) {
252
+ const s = String(ref ?? '').trim()
253
+ if (!s) return null
254
+ if (BY_ID.has(s)) return s
255
+ if (BY_NPM.has(s)) return BY_NPM.get(s).id
256
+ // W3:智能体 bundle —— 同上,按规则(⛔ 不认"尾段同名"的别的 scope / 路径形态)
257
+ if (isAgentBundleId(s)) return s
258
+ if (s.startsWith(`${NPM_SCOPE}/`)) {
259
+ const scoped = s.slice(NPM_SCOPE.length + 1)
260
+ if (isAgentBundleId(scoped)) return scoped
261
+ }
262
+ return null
263
+ }
264
+
265
+ /**
266
+ * **bundles 条目迁移**:`dsh.profile.bundles` 里的裸名(`argent-skin`)⇒ npm 名(`@whyshu/argent-skin`)。
267
+ *
268
+ * 为什么必须有它(2026-10-13 真机,W9 漏迁):`dsh.profile.bundles` 与依赖键/组合行**同属
269
+ * "profile 面向字符串"**,而 W9 的迁移只做了后者两处 ⇒ 存量机器的 `bundles` 里留着裸名。
270
+ * 裸名在 profile 里**不可解析**(实体在 `node_modules/@whyshu/argent-skin`)⇒ bundle 加载不到
271
+ * ⇒ **皮肤/UI 壳静默不加载**(⛔ 安装期零报错;且 `bundles` 的顺序 = 各 bundle patch 的
272
+ * 应用顺序,基座必须在前且在位)。
273
+ *
274
+ * 语义(与 {@link migrateDependencyKeys} 同档,刻意保守):
275
+ * · 只碰**本仓已知插件**({@link exactPluginIdOf} 认得出:裸 id 或本仓 npm 名)——官方 `@deepseek-ai/*`、
276
+ * 别人的 scope、路径形态一律**逐字原样保留**
277
+ * (⛔ 不越权、不"善意补 scope"、不去重);
278
+ * · 已知插件的**两个名字都在** ⇒ 后面那个判为**陈旧别名**,摘掉(同一个 bundle ⛔ 不许留两个
279
+ * 名字,否则会重复挂载);保留**首个出现位置** ⇒ 顺序(= patch 应用顺序)不变;
280
+ * · 幂等:再喂回来 ⇒ `renamed`/`stale` 皆空、`bundles` 逐元素相等。
281
+ * @param {unknown} bundles `dsh.profile.bundles` 的当前值(非数组 ⇒ 当空表)
282
+ * @returns {{bundles: string[], renamed: Array<{from:string,to:string}>, stale: string[]}}
283
+ */
284
+ export function migrateBundleNames(bundles) {
285
+ const src = Array.isArray(bundles) ? bundles : []
286
+ const out = []
287
+ const renamed = []
288
+ const stale = []
289
+ const landed = new Set() // 已落位的**规范名**(已知插件)
290
+ for (const raw of src) {
291
+ const name = String(raw)
292
+ const id = exactPluginIdOf(name) // 写侧用**严格**判据(⛔ 不认别的 scope/路径尾段)
293
+ if (!id) { out.push(name); continue } // 官方/未知:原样(⛔ 不越权)
294
+ const want = BY_ID.get(id).npm
295
+ if (landed.has(want)) { stale.push(name); continue } // 同一条目的第二个名字 ⇒ 摘掉
296
+ landed.add(want)
297
+ if (name !== want) renamed.push({ from: name, to: want })
298
+ out.push(want)
299
+ }
300
+ return { bundles: out, renamed, stale }
301
+ }
302
+
303
+ /** 该引用是不是**已知插件的裸名**(`argent-skin`;scoped 名 / 官方包 / 别的 scope / 未知 ⇒ false)。 */
304
+ export function isBarePluginName(ref) {
305
+ const s = String(ref ?? '').trim()
306
+ const id = exactPluginIdOf(s)
307
+ return !!id && s === id
308
+ }
309
+
310
+ /**
311
+ * 把**任意一种写法**归一到写 profile 用的规范名(npm 名)。
312
+ * 已知插件 ⇒ `@whyshu/argent-<id>`;**未知/官方条目 ⇒ 原样回**(⛔ 不猜)。
313
+ */
314
+ export function canonicalPluginName(ref) {
315
+ const id = exactPluginIdOf(ref)
316
+ return id ? BY_ID.get(id).npm : String(ref ?? '')
317
+ }
318
+
319
+ /**
320
+ * 迁移后的**陈旧别名**是否可安全摘掉:仅当组合行里已经没有插件指向裸名。
321
+ * @param {string} patchText cordis.patch.yml 内容
322
+ * @param {string[]} staleDeps 待摘的裸依赖键
323
+ * @returns {string[]} 仍被组合行引用的键(⛔ 这些**不许**摘)
324
+ */
325
+ export function staleDepsStillReferenced(patchText, staleDeps) {
326
+ const text = String(patchText ?? '')
327
+ return (staleDeps ?? []).filter((k) => new RegExp(`^\\s*name:\\s*['"]${escapeRe(k)}['"]\\s*$`, 'm').test(text))
328
+ }
329
+
330
+ function escapeRe(s) {
331
+ return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
332
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * preset 行的跨版本兼容规则(**唯一真源**)。
3
+ *
4
+ * 背景(2026-09-28):0.1.7 除了把 preset 从"目录扫描"改成"组合里的一行插件",还**删掉了
5
+ * 若干子包**(preflight 的"消失子包"名单)。我们 13 个 preset 都引用了其中一个 ——
6
+ * `@deepseek-ai/dsh-workflow-worker-thread` —— 于是行式声明虽然在 0.1.7 上注册成功,
7
+ * 每个 preset 都带 `broken` 诊断:
8
+ *
9
+ * workflow-worker-thread (@deepseek-ai/dsh-workflow-worker-thread): never started
10
+ *
11
+ * 这类"包没了"的问题**不会**在生成阶段暴露(我们只是搬运文本),必须在生成时按目标版本
12
+ * 改写,并且用"每行包都能解析"的硬门禁兜住(见 scripts/lib/preset-rows-check.mjs)。
13
+ *
14
+ * ⚠️ 规则只改**行**(id + name),不动 config —— 语义变化要单独评估并写进 note。
15
+ */
16
+
17
+ /** 行改写规则:`from` 匹配 `name:` 行的包名;`idFrom/idTo` 改该行的 `id:`。 */
18
+ export const ROW_RULES = [
19
+ {
20
+ from: '@deepseek-ai/dsh-workflow-worker-thread',
21
+ to: '@deepseek-ai/dsh-workflow-ptc',
22
+ idFrom: 'workflow-worker-thread',
23
+ idTo: 'workflow-ptc',
24
+ note: '0.1.7 起工作流运行时改名:worker-thread → ptc(provider: spawn 语义不变,官方 standard 即此写法)',
25
+ },
26
+ ]
27
+
28
+ /**
29
+ * **必填键补齐**(按包):目标版本把某键改成必填时,缺了会让 preset 挂载失败
30
+ * (校验器报 `缺必填键`)。取值一律抄**官方 standard preset**的同名行,并在此注明出处。
31
+ *
32
+ * 实测(2026-09-28,0.1.7-rc.2):`@deepseek-ai/dsh-tool-subagent` 新增必填 `maxDepth`,
33
+ * 我们 13 个角色各有两行(fork / claude-code)缺失 ⇒ 校验器 FAIL 117 项。
34
+ */
35
+ export const REQUIRED_KEY_DEFAULTS = {
36
+ '@deepseek-ai/dsh-tool-subagent': {
37
+ maxDepth: { value: 'provider-managed', source: "0.1.7 官方 dsh-web-app/presets/standard.patch.yml" },
38
+ },
39
+ }
40
+
41
+ /** 该行的缩进(用于插键)。 */
42
+ function indentOf(line) {
43
+ const m = /^(\s*)/.exec(line)
44
+ return m ? m[1] : ''
45
+ }
46
+
47
+ /**
48
+ * 给缺必填键的行补键(逐行处理;插入位置在 `config:` 之后,缩进按该行体)。
49
+ * @param {string} text
50
+ * @returns {{ text: string, applied: string[] }}
51
+ */
52
+ export function applyRequiredKeys(text) {
53
+ const lines = String(text).split('\n')
54
+ const out = []
55
+ const applied = []
56
+ let i = 0
57
+ while (i < lines.length) {
58
+ const rowStart = /^(\s*)- id:\s*(\S+)\s*$/.exec(lines[i])
59
+ if (!rowStart) { out.push(lines[i]); i += 1; continue }
60
+ // 收集整块(后续缩进更深的行)
61
+ const baseIndent = indentOf(lines[i]).length
62
+ let j = i + 1
63
+ while (j < lines.length && lines[j].trim() !== '' && indentOf(lines[j]).length > baseIndent) j += 1
64
+ const block = lines.slice(i, j)
65
+ const pkgLine = block.findIndex((l) => /^\s*name:\s*'?@/.test(l))
66
+ const pkg = pkgLine >= 0 ? (/name:\s*'?([^'\s]+)'?/.exec(block[pkgLine]) || [])[1] : null
67
+ const defaults = pkg ? REQUIRED_KEY_DEFAULTS[pkg] : null
68
+ if (!defaults) { out.push(...block); i = j; continue }
69
+ const cfgIdx = block.findIndex((l) => /^\s*config:\s*$/.test(l))
70
+ const missing = Object.entries(defaults).filter(([k]) => !block.some((l) => new RegExp(`^\\s*${k}:`).test(l)))
71
+ if (!missing.length) { out.push(...block); i = j; continue }
72
+ const cfgIndent = cfgIdx >= 0 ? indentOf(block[cfgIdx]) + ' ' : `${' '.repeat(baseIndent + 2)} `
73
+ const insertAt = cfgIdx >= 0 ? cfgIdx + 1 : block.length
74
+ const additions = missing.map(([k, spec]) => `${cfgIndent}${k}: ${spec.value}`)
75
+ const newBlock = cfgIdx >= 0
76
+ ? [...block.slice(0, insertAt), ...additions, ...block.slice(insertAt)]
77
+ : [...block.slice(0, insertAt), `${' '.repeat(baseIndent + 2)}config:`, ...additions, ...block.slice(insertAt)]
78
+ out.push(...newBlock)
79
+ for (const [k, spec] of missing) applied.push(`${pkg} 补必填键 ${k}: ${spec.value}(出处:${spec.source})`)
80
+ i = j
81
+ }
82
+ return { text: out.join('\n'), applied }
83
+ }
84
+
85
+ /**
86
+ * 对一份 preset 组合文本应用兼容改写(按行处理,保留注释/缩进/!!js)。
87
+ * @param {string} text
88
+ * @returns {{ text: string, applied: string[] }}
89
+ */
90
+ export function applyRowCompat(text) {
91
+ const lines = String(text).split('\n')
92
+ const applied = []
93
+ for (let i = 0; i < lines.length; i += 1) {
94
+ const m = /^(\s*name:\s*)'([^']+)'\s*$/.exec(lines[i])
95
+ if (!m) continue
96
+ const rule = ROW_RULES.find((r) => r.from === m[2])
97
+ if (!rule) continue
98
+ lines[i] = `${m[1]}'${rule.to}'`
99
+ applied.push(`${rule.from} → ${rule.to}`)
100
+ // 上一行的 `- id:` 同步改名(存在才改)
101
+ for (let j = i - 1; j >= 0 && j >= i - 3; j -= 1) {
102
+ const idm = /^(\s*- id:\s*)(\S+)\s*$/.exec(lines[j])
103
+ if (idm) {
104
+ if (idm[2] === rule.idFrom) lines[j] = `${idm[1]}${rule.idTo}`
105
+ break
106
+ }
107
+ }
108
+ }
109
+ const keyFix = applyRequiredKeys(lines.join('\n'))
110
+ return { text: keyFix.text, applied: [...applied, ...keyFix.applied] }
111
+ }
112
+
113
+ /**
114
+ * 抽出一份组合文本里**所有行引用的包名**(`name:` 行;跳过本地文件路径与 cordis: 内置)。
115
+ * @param {string} text
116
+ * @returns {string[]} 去重后的包名
117
+ */
118
+ export function collectRowPackages(text) {
119
+ const out = new Set()
120
+ for (const line of String(text).split('\n')) {
121
+ const m = /^\s*name:\s*'?([^'\n]+?)'?\s*$/.exec(line)
122
+ if (!m) continue
123
+ const name = m[1].trim()
124
+ if (name.startsWith('@') && !name.startsWith('.')) out.add(name)
125
+ }
126
+ return [...out].sort()
127
+ }