tianshu-harness 3.23.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 (255) hide show
  1. package/LICENSE +189 -0
  2. package/README.en.md +1066 -0
  3. package/README.ja.md +1214 -0
  4. package/README.ko.md +1216 -0
  5. package/README.md +309 -0
  6. package/completions/rivet.bash +118 -0
  7. package/completions/rivet.fish +110 -0
  8. package/completions/rivet.ps1 +230 -0
  9. package/completions/rivet.zsh +187 -0
  10. package/dist/account-OAEKORZE.js +47 -0
  11. package/dist/account-login-V6FOLHJR.js +65 -0
  12. package/dist/account-status-KHNPPAFL.js +23 -0
  13. package/dist/agent/worker-process/child.js +407 -0
  14. package/dist/browser-cli-FLNY5ZHI.js +19 -0
  15. package/dist/browser-readiness-LWVGSBD5.js +12 -0
  16. package/dist/build-backends-5KXKR2S4.js +13 -0
  17. package/dist/build-options-66Y4ZE7C.js +9 -0
  18. package/dist/bundled-skills/brainstorming/SKILL.md +139 -0
  19. package/dist/bundled-skills/visual-acceptance/SKILL.md +64 -0
  20. package/dist/chain-7MAMFXZ3.js +10 -0
  21. package/dist/checkpoint-MND4DUIX.js +33 -0
  22. package/dist/chunk-2E6CK2RC.js +392 -0
  23. package/dist/chunk-2EBWGAXD.js +51 -0
  24. package/dist/chunk-2HDP7RMR.js +119 -0
  25. package/dist/chunk-2HKD2UFM.js +1000 -0
  26. package/dist/chunk-2VYRLULP.js +51 -0
  27. package/dist/chunk-2XN7PM4N.js +125 -0
  28. package/dist/chunk-35H7GVNR.js +66 -0
  29. package/dist/chunk-3C3R7JXY.js +77 -0
  30. package/dist/chunk-3VGD2FXB.js +118 -0
  31. package/dist/chunk-424AP7FM.js +534 -0
  32. package/dist/chunk-4J5TZGOE.js +3707 -0
  33. package/dist/chunk-4QJ3Y5JM.js +898 -0
  34. package/dist/chunk-4U2ET7ZY.js +1326 -0
  35. package/dist/chunk-4XPFLICE.js +876 -0
  36. package/dist/chunk-5B2OCU5P.js +31707 -0
  37. package/dist/chunk-5GGOGLSN.js +73 -0
  38. package/dist/chunk-5JRZPLI6.js +251 -0
  39. package/dist/chunk-5TQ3JYBU.js +397 -0
  40. package/dist/chunk-66JPZSGX.js +55 -0
  41. package/dist/chunk-6HSVXHCZ.js +764 -0
  42. package/dist/chunk-6S5FPUS7.js +21 -0
  43. package/dist/chunk-722LQO64.js +131 -0
  44. package/dist/chunk-73NJ7IT7.js +354 -0
  45. package/dist/chunk-7FW3I7WC.js +95 -0
  46. package/dist/chunk-7KUCBNLB.js +173 -0
  47. package/dist/chunk-7RDOV3DF.js +201 -0
  48. package/dist/chunk-A5MNH4ER.js +79 -0
  49. package/dist/chunk-ADIVZ2D5.js +15 -0
  50. package/dist/chunk-AE3P3F6W.js +366 -0
  51. package/dist/chunk-AERM3VGP.js +280 -0
  52. package/dist/chunk-AEWHC4WM.js +246 -0
  53. package/dist/chunk-AOVQ7VIF.js +169 -0
  54. package/dist/chunk-AOYHP3WI.js +12411 -0
  55. package/dist/chunk-B3YEBABJ.js +7160 -0
  56. package/dist/chunk-BCQXEZPJ.js +154 -0
  57. package/dist/chunk-BKTQAYO2.js +418 -0
  58. package/dist/chunk-BSECIPAQ.js +80 -0
  59. package/dist/chunk-BUIMGNDD.js +52 -0
  60. package/dist/chunk-BXHV3GSB.js +10320 -0
  61. package/dist/chunk-C6ZVZCPP.js +139 -0
  62. package/dist/chunk-CEZNQVWM.js +82 -0
  63. package/dist/chunk-CGFZGR7G.js +1492 -0
  64. package/dist/chunk-CGOADM2X.js +337 -0
  65. package/dist/chunk-CI4R6P2B.js +24 -0
  66. package/dist/chunk-D2TCGRSX.js +73 -0
  67. package/dist/chunk-D4PTYFOG.js +154 -0
  68. package/dist/chunk-D54ZWG4T.js +717 -0
  69. package/dist/chunk-DEBI37QB.js +145 -0
  70. package/dist/chunk-DWROHPXP.js +1051 -0
  71. package/dist/chunk-DZ6UOJ5D.js +92 -0
  72. package/dist/chunk-EBJTVRQN.js +81 -0
  73. package/dist/chunk-EGEI2RQA.js +1167 -0
  74. package/dist/chunk-EHHUASO5.js +1305 -0
  75. package/dist/chunk-EPKTIOED.js +41 -0
  76. package/dist/chunk-ER5JW2QZ.js +5681 -0
  77. package/dist/chunk-ETHFACAB.js +315 -0
  78. package/dist/chunk-EXUE5W2F.js +112 -0
  79. package/dist/chunk-EXXICUIW.js +73 -0
  80. package/dist/chunk-FFQSZJOO.js +169 -0
  81. package/dist/chunk-FHFKPGDO.js +85 -0
  82. package/dist/chunk-FW3QKMFX.js +158 -0
  83. package/dist/chunk-GD4ZNZ7W.js +30 -0
  84. package/dist/chunk-GHVMWF2I.js +581 -0
  85. package/dist/chunk-GMKAXSYQ.js +297 -0
  86. package/dist/chunk-HBVF6BWO.js +325 -0
  87. package/dist/chunk-HV4YGVMS.js +379 -0
  88. package/dist/chunk-ILBCKAV6.js +89 -0
  89. package/dist/chunk-J4U4AW46.js +440 -0
  90. package/dist/chunk-J6ESSM47.js +1791 -0
  91. package/dist/chunk-JBZHIC6T.js +2309 -0
  92. package/dist/chunk-JCFWOULR.js +245 -0
  93. package/dist/chunk-JRF5L6OD.js +131 -0
  94. package/dist/chunk-JSS75KCG.js +2014 -0
  95. package/dist/chunk-JSXWIWWJ.js +225 -0
  96. package/dist/chunk-JYV7I2QH.js +98 -0
  97. package/dist/chunk-KIOXTQQL.js +1596 -0
  98. package/dist/chunk-KWVTXT5B.js +89 -0
  99. package/dist/chunk-KX22RD6R.js +33 -0
  100. package/dist/chunk-LA6IPTGH.js +709 -0
  101. package/dist/chunk-LH5VCGLB.js +1796 -0
  102. package/dist/chunk-LQQ6XJ2Y.js +269 -0
  103. package/dist/chunk-MJQ55WZZ.js +65 -0
  104. package/dist/chunk-ML3UKMH3.js +40 -0
  105. package/dist/chunk-MOZPLHSU.js +5526 -0
  106. package/dist/chunk-NGEWJ4DF.js +89 -0
  107. package/dist/chunk-NH6VIOAR.js +211 -0
  108. package/dist/chunk-O5VZQKCB.js +26106 -0
  109. package/dist/chunk-OHSV42QB.js +748 -0
  110. package/dist/chunk-OO3TAHO5.js +4070 -0
  111. package/dist/chunk-OTNHSAGN.js +38282 -0
  112. package/dist/chunk-OUWAC7AQ.js +442 -0
  113. package/dist/chunk-OXMIV4WE.js +270 -0
  114. package/dist/chunk-P5KJURUR.js +229 -0
  115. package/dist/chunk-P6GJOVO6.js +307 -0
  116. package/dist/chunk-QARAGUXX.js +23 -0
  117. package/dist/chunk-R43HJCDX.js +2711 -0
  118. package/dist/chunk-RAXBAWRN.js +210 -0
  119. package/dist/chunk-RK3JHWPT.js +39 -0
  120. package/dist/chunk-RWA23GMM.js +24 -0
  121. package/dist/chunk-S4K4L6IM.js +179 -0
  122. package/dist/chunk-SAXK6LVF.js +124 -0
  123. package/dist/chunk-SEPGEJNK.js +1607 -0
  124. package/dist/chunk-SUJERP5I.js +285 -0
  125. package/dist/chunk-T3ED6BY2.js +913 -0
  126. package/dist/chunk-THKDVSEC.js +90 -0
  127. package/dist/chunk-TI4GSEL5.js +52 -0
  128. package/dist/chunk-TPI73L57.js +164 -0
  129. package/dist/chunk-TVHW77C7.js +338 -0
  130. package/dist/chunk-TWNHOSDT.js +1073 -0
  131. package/dist/chunk-UFMN76QD.js +347 -0
  132. package/dist/chunk-UHGLFRZE.js +205 -0
  133. package/dist/chunk-UTZYPKBF.js +15231 -0
  134. package/dist/chunk-UXNLELW6.js +25 -0
  135. package/dist/chunk-UXXS53EA.js +166 -0
  136. package/dist/chunk-V5DHFGI7.js +171 -0
  137. package/dist/chunk-V5YIPMHV.js +2353 -0
  138. package/dist/chunk-WMUWEO2C.js +754 -0
  139. package/dist/chunk-WOE3D7JM.js +945 -0
  140. package/dist/chunk-X2IK5LVR.js +2592 -0
  141. package/dist/chunk-XU2YXCDL.js +459 -0
  142. package/dist/chunk-YJG5HIX5.js +203 -0
  143. package/dist/chunk-Z3QE7TZM.js +72 -0
  144. package/dist/chunk-Z5Z3JHJY.js +64 -0
  145. package/dist/chunk-ZDIFBJFT.js +168 -0
  146. package/dist/chunk-ZGGCEXDC.js +176 -0
  147. package/dist/chunk-ZMZCZNKU.js +263 -0
  148. package/dist/chunk-ZNC5T65N.js +17 -0
  149. package/dist/chunk-ZO3I3A7O.js +308 -0
  150. package/dist/chunk-ZQKXBUB7.js +217 -0
  151. package/dist/chunk-ZTRINFM2.js +78 -0
  152. package/dist/chunk-ZU7JZCVP.js +193 -0
  153. package/dist/chunk-ZZAWNALS.js +350 -0
  154. package/dist/cli/entry.js +54 -0
  155. package/dist/connector-L5QQTR6Y.js +26 -0
  156. package/dist/context-62DC6EC3.js +31 -0
  157. package/dist/council-convene-NJWPNBDU.js +46 -0
  158. package/dist/create-agent-config-ODSQWNVC.js +55 -0
  159. package/dist/default-registry-2PU3BTAU.js +68 -0
  160. package/dist/deliver-task-KONV4C4M.js +57 -0
  161. package/dist/delivery-gate-v2-F2YVMVGK.js +27 -0
  162. package/dist/domain-knowledge-store-DDZ3F4J7.js +11 -0
  163. package/dist/fetch-core-ZPPYDSZH.js +14 -0
  164. package/dist/galaxy-K72BBDQN.js +91 -0
  165. package/dist/goal-criteria-SCALAU2D.js +141 -0
  166. package/dist/goal-persist-XWY2GDQX.js +19 -0
  167. package/dist/goal-tracker-FXMJ2GVK.js +11 -0
  168. package/dist/headless-LSB6I7PT.js +162 -0
  169. package/dist/headless-coordinator-VDPDMVUT.js +90 -0
  170. package/dist/inject-U55SMB6A.js +39 -0
  171. package/dist/login-flow-CD4ZGXOC.js +59 -0
  172. package/dist/logs-cli-SQVYFMMN.js +283 -0
  173. package/dist/loop-ULZYBDA3.js +76 -0
  174. package/dist/main.js +4064 -0
  175. package/dist/manager-ROZBCWPA.js +171 -0
  176. package/dist/manager-Z7XFFUOM.js +10670 -0
  177. package/dist/meridian-indexer-ZIW363L7.js +21 -0
  178. package/dist/native-resolver-PN34UIVV.js +13 -0
  179. package/dist/node_modules/better-sqlite3/lib/database.js +90 -0
  180. package/dist/node_modules/better-sqlite3/lib/index.js +3 -0
  181. package/dist/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
  182. package/dist/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
  183. package/dist/node_modules/better-sqlite3/lib/methods/function.js +31 -0
  184. package/dist/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
  185. package/dist/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
  186. package/dist/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
  187. package/dist/node_modules/better-sqlite3/lib/methods/table.js +189 -0
  188. package/dist/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
  189. package/dist/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
  190. package/dist/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  191. package/dist/node_modules/better-sqlite3/lib/util.js +12 -0
  192. package/dist/node_modules/better-sqlite3/package.json +59 -0
  193. package/dist/ownership-ledger-E3WIEKPL.js +9 -0
  194. package/dist/package.json +3 -0
  195. package/dist/path-grants-XRRXHXG7.js +40 -0
  196. package/dist/paths-LVV5IMQN.js +49 -0
  197. package/dist/plan-fact-anchors-LMVQZCW5.js +13 -0
  198. package/dist/plan-store-ODLTTPFB.js +17 -0
  199. package/dist/plan-templates-5FPFBOL6.js +109 -0
  200. package/dist/plugin-loader-ZZKX7ZE6.js +22 -0
  201. package/dist/prefix-budget-62OV5HQC.js +13 -0
  202. package/dist/project-trust-5WS5MOMU.js +31 -0
  203. package/dist/provider-cli-ALDQXIT7.js +37 -0
  204. package/dist/providers-XQYTAVPK.js +11 -0
  205. package/dist/proxy-resolver-V2Y6534D.js +15 -0
  206. package/dist/recovery-cli-KPGY6FY3.js +106 -0
  207. package/dist/review-discipline-MNSYCST5.js +55 -0
  208. package/dist/review-router-BVQZW3MX.js +18 -0
  209. package/dist/seed-capsules/seed-capsule-attack-methodology.md +62 -0
  210. package/dist/seed-capsules/seed-capsule-changgeng.md +31 -0
  211. package/dist/seed-capsules/seed-capsule-design-aesthetics.md +38 -0
  212. package/dist/seed-capsules/seed-capsule-diagnostic-ladder.md +54 -0
  213. package/dist/seed-capsules/seed-capsule-fu.md +39 -0
  214. package/dist/seed-capsules/seed-capsule-huagai.md +73 -0
  215. package/dist/seed-capsules/seed-capsule-kaiyang.md +65 -0
  216. package/dist/seed-capsules/seed-capsule-knowledge-work.md +40 -0
  217. package/dist/seed-capsules/seed-capsule-tanlang.md +47 -0
  218. package/dist/seed-capsules/seed-capsule-tianfu.md +23 -0
  219. package/dist/seed-capsules/seed-capsule-tianliang.md +63 -0
  220. package/dist/seed-capsules/seed-capsule-tianquan.md +32 -0
  221. package/dist/seed-capsules/seed-capsule-tianxuan-ccr.md +22 -0
  222. package/dist/seed-capsules/seed-capsule-tianxuan.md +64 -0
  223. package/dist/seed-capsules/seed-capsule-yaoguang.md +87 -0
  224. package/dist/serve-X2R2UTLF.js +170 -0
  225. package/dist/serve-agent-KHSU6RVV.js +965 -0
  226. package/dist/session-persist-HUUC772H.js +37 -0
  227. package/dist/session-registry-6XUD5C4Y.js +15 -0
  228. package/dist/skill-loader-CK3RMO47.js +48 -0
  229. package/dist/skills/cli-harness-sop.md +207 -0
  230. package/dist/spawn-git-4HFU65AH.js +29 -0
  231. package/dist/sse-EYUTBWUK.js +512 -0
  232. package/dist/starflow-NOFBMPFU.js +30 -0
  233. package/dist/streamableHttp-V67CNJMO.js +447 -0
  234. package/dist/task-contract-6XHBNJQK.js +32 -0
  235. package/dist/task-ledger-3K4TJIER.js +9 -0
  236. package/dist/team-orchestrate-S6NHZP7X.js +64 -0
  237. package/dist/title-extract-QX4XGCYN.js +50 -0
  238. package/dist/todo-QDXGRZZP.js +31 -0
  239. package/dist/todo-store-V3TTO5AH.js +12 -0
  240. package/dist/tool-preset-LU5BYEDZ.js +23 -0
  241. package/dist/tool-tiers-GD742YBN.js +23 -0
  242. package/dist/turndown.es-OI55K6C3.js +17373 -0
  243. package/dist/verification-attribution-EKG4CNUV.js +13 -0
  244. package/dist/volatile-git-MBTST7RA.js +15 -0
  245. package/dist/wave-checkpoint-XLIIHXQA.js +30 -0
  246. package/dist/web-cli-DUG4I35O.js +233 -0
  247. package/dist/web-search-3IK542AA.js +34 -0
  248. package/dist/workers/cpu-worker.js +45 -0
  249. package/dist/workflow-runner-HX7UBAYG.js +297 -0
  250. package/dist/worktree-baseline-O5NCU6M2.js +9 -0
  251. package/package.json +122 -0
  252. package/scripts/check-mobile-env.mjs +76 -0
  253. package/scripts/fetch-native-sqlite.js +392 -0
  254. package/scripts/prune-installed-wasms.js +45 -0
  255. package/scripts/tree-sitter-wasm-keep.js +40 -0
package/package.json ADDED
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "tianshu-harness",
3
+ "version": "3.23.0",
4
+ "description": "Cognitive runtime (CVM) for coding agents — stable, evidence-gated delivery; prefix cache works with all models (98–99% hit rate)",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "keywords": [
8
+ "coding-agent",
9
+ "terminal",
10
+ "tui",
11
+ "deepseek",
12
+ "ai",
13
+ "cli"
14
+ ],
15
+ "homepage": "https://github.com/huiliyi37/Tianshu-Tui#readme",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/huiliyi37/Tianshu-Tui.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/huiliyi37/Tianshu-Tui/issues"
22
+ },
23
+ "engines": {
24
+ "node": ">=24"
25
+ },
26
+ "files": [
27
+ "dist/",
28
+ "!dist/native",
29
+ "!dist/pro",
30
+ "!dist/node_modules/**",
31
+ "dist/node_modules/better-sqlite3/",
32
+ "scripts/check-mobile-env.mjs",
33
+ "scripts/fetch-native-sqlite.js",
34
+ "scripts/prune-installed-wasms.js",
35
+ "scripts/tree-sitter-wasm-keep.js",
36
+ "README.md",
37
+ "LICENSE",
38
+ "completions/"
39
+ ],
40
+ "bin": {
41
+ "rivet": "dist/cli/entry.js",
42
+ "tianshu": "dist/cli/entry.js"
43
+ },
44
+ "scripts": {
45
+ "postinstall": "node scripts/check-mobile-env.mjs && (node scripts/install-git-hooks.mjs || node -e 1) && (node scripts/fetch-native-sqlite.js || node -e 1) && (node scripts/prune-installed-wasms.js || node -e 1)",
46
+ "prepack": "npm run build && node scripts/stage-cli-sqlite-wrapper.js",
47
+ "dev": "tsup --watch",
48
+ "build": "tsup && node scripts/pack-native.js && node scripts/stage-runtime-deps.js",
49
+ "build:bundle": "tsup",
50
+ "start": "node --expose-gc --max-old-space-size=4096 dist/cli/entry.js",
51
+ "typecheck": "tsx scripts/typecheck-guard.ts",
52
+ "lint": "oxlint src scripts",
53
+ "budgets:check": "tsx scripts/verify-source-budgets.ts",
54
+ "lint:fix": "oxlint src scripts --fix",
55
+ "typecheck:direct": "tsc --noEmit",
56
+ "typecheck:baseline": "tsx scripts/typecheck-baseline.ts",
57
+ "test": "tsx scripts/run-node-tests.ts",
58
+ "test:unit": "tsx scripts/run-node-tests.ts --unit",
59
+ "test:integration": "tsx scripts/run-node-tests.ts --integration",
60
+ "test:fast": "tsx scripts/run-node-tests.ts --exclude-tui",
61
+ "test:desktop": "node --import tsx desktop/scripts/run-tests.ts 'src/**/__tests__/*.test.ts' 'scripts/__tests__/*.test.ts'",
62
+ "test:incremental": "tsx scripts/test-incremental.ts",
63
+ "structure:check": "tsx --test src/__tests__/architecture-guards.test.ts",
64
+ "contributors:check": "tsx scripts/contributors.ts --check",
65
+ "cache:audit": "tsx src/cache/cache-audit-cli.ts",
66
+ "prefix:budget": "tsx scripts/prefix-budget.ts",
67
+ "tokens:audit": "tsx scripts/analyze-output-tokens.ts",
68
+ "diagnose:orphans": "tsx scripts/diagnose-tool-orphans.ts",
69
+ "benchmark": "tsx scripts/run-benchmark.ts",
70
+ "smoke:mcp": "tsx scripts/smoke-mcp-presets.ts",
71
+ "docs:index": "tsx scripts/docs-index.ts",
72
+ "docs:check": "tsx scripts/docs-index.ts --check",
73
+ "probe:thresholds": "tsx scripts/telemetry-probes/threshold-coverage.ts",
74
+ "probe:control-health": "tsx scripts/telemetry-probes/control-health.ts",
75
+ "probe:test": "tsx scripts/run-node-tests.ts scripts/telemetry-probes",
76
+ "probe:typecheck": "tsc --noEmit -p scripts/telemetry-probes/tsconfig.json"
77
+ },
78
+ "dependencies": {
79
+ "@ast-grep/lang-json": "^0.0.7",
80
+ "@ast-grep/lang-python": "^0.0.6",
81
+ "@ast-grep/napi": "^0.44.0",
82
+ "esbuild": "^0.28.1",
83
+ "pdfjs-dist": "^6.3.289",
84
+ "playwright-core": "1.62.0",
85
+ "tree-sitter-wasms": "^0.1.13",
86
+ "typescript": "^5.7.0",
87
+ "web-tree-sitter": "^0.24.7",
88
+ "zod-to-json-schema": "^3.25.2"
89
+ },
90
+ "optionalDependencies": {
91
+ "@huiliyi37/node-addon-landlock-run": "^0.4.0",
92
+ "better-sqlite3": "^12.10.0"
93
+ },
94
+ "overrides": {
95
+ "esbuild": "^0.28.1"
96
+ },
97
+ "devDependencies": {
98
+ "@modelcontextprotocol/sdk": "^1.29.0",
99
+ "@tauri-apps/cli": "^2.11.2",
100
+ "@types/better-sqlite3": "^7.6.13",
101
+ "@types/diff": "^7.0.2",
102
+ "@types/node": "^22.0.0",
103
+ "@types/pngjs": "^6.0.5",
104
+ "@types/turndown": "^5.0.6",
105
+ "chalk": "^5.6.2",
106
+ "diff": "^9.0.0",
107
+ "exceljs": "^4.4.0",
108
+ "get-east-asian-width": "^1.6.0",
109
+ "oxlint": "^1.80.0",
110
+ "parquet-wasm": "^0.7.2",
111
+ "patch-package": "^8.0.0",
112
+ "pixelmatch": "^7.2.0",
113
+ "pngjs": "^7.0.0",
114
+ "string-width": "^8.2.1",
115
+ "ts-morph": "^28.0.0",
116
+ "tsup": "^8.4.0",
117
+ "tsx": "^4.22.0",
118
+ "turndown": "^7.2.4",
119
+ "undici": "^8.3.0",
120
+ "zod": "^3.24.0"
121
+ }
122
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 移动端安装环境守卫(postinstall 第一步,2026-09-05)。
4
+ *
5
+ * 背景(issue #55 同期的移动端调研):手机端两类环境——
6
+ * 1. 裸 Termux(bionic,process.platform === 'android'):必需原生依赖
7
+ * `@ast-grep/napi` 与 `esbuild` 没有 android 平台二进制,npm 会静默跳过,
8
+ * 安装「成功」但运行必坏。此类环境必须 fail-loud 并指引 proot-distro。
9
+ * 2. proot-distro(glibc arm64,platform === 'linux'):官方支持路径,依赖
10
+ * 按 linux-arm64 正常解析;ripgrep 缺失时 grep 工具族如实降级,此处仅提醒。
11
+ *
12
+ * 逃生口:RIVET_ALLOW_MOBILE_INSTALL=1 时裸 Termux 也放行(降级为醒目警告),
13
+ * 供实验环境自担风险继续安装。
14
+ */
15
+ import { execFileSync } from 'node:child_process'
16
+ import { pathToFileURL } from 'node:url'
17
+
18
+ function detectRipgrep() {
19
+ try {
20
+ execFileSync('rg', ['--version'], { stdio: 'ignore' })
21
+ return true
22
+ } catch {
23
+ return false
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @param {object} [input] 测试注入点
29
+ * @param {string} [input.platform] process.platform
30
+ * @param {string} [input.arch] process.arch
31
+ * @param {Record<string, string|undefined>} [input.env] process.env
32
+ * @param {boolean} [input.hasRg] ripgrep 是否在 PATH
33
+ * @returns {{ level: 'ok'|'warn'|'fatal', messages: string[] }}
34
+ */
35
+ export function evaluateMobileInstallEnv(input = {}) {
36
+ const platform = input.platform ?? process.platform
37
+ const arch = input.arch ?? process.arch
38
+ const env = input.env ?? process.env
39
+ const hasRg = input.hasRg ?? detectRipgrep()
40
+
41
+ if (platform === 'android') {
42
+ const guidance = [
43
+ '检测到裸 Termux 环境(Android bionic)。',
44
+ 'tianshu-harness 的必需原生依赖(@ast-grep/napi / esbuild)没有 Android 平台二进制,npm 会静默跳过它们——装完运行必坏。',
45
+ '官方支持路径是 proot-distro(glibc 发行版):',
46
+ ' pkg install proot-distro && proot-distro install ubuntu && proot-distro login ubuntu',
47
+ ' (容器内)apt install -y curl ripgrep,安装 Node >= 24,然后 npm i -g tianshu-harness',
48
+ '沙箱/LSP/语音等能力在容器内会自动降级,不影响核心功能。',
49
+ ]
50
+ if (env.RIVET_ALLOW_MOBILE_INSTALL === '1') {
51
+ return {
52
+ level: 'warn',
53
+ messages: ['RIVET_ALLOW_MOBILE_INSTALL=1:跳过裸 Termux 拦截,继续安装(运行时大概率损坏,自担风险)。', ...guidance],
54
+ }
55
+ }
56
+ return { level: 'fatal', messages: guidance }
57
+ }
58
+
59
+ const warnings = []
60
+ if (platform === 'linux' && arch === 'arm64' && !hasRg) {
61
+ warnings.push('未检测到 ripgrep——grep/glob 工具族将不可用。安装:Debian/Ubuntu(proot 容器内)apt install -y ripgrep;Arch pacman -S ripgrep。')
62
+ }
63
+ return { level: warnings.length > 0 ? 'warn' : 'ok', messages: warnings }
64
+ }
65
+
66
+ function main() {
67
+ const result = evaluateMobileInstallEnv()
68
+ if (result.level === 'ok') return
69
+ const log = result.level === 'fatal' ? console.error : console.warn
70
+ for (const line of result.messages) log(line)
71
+ if (result.level === 'fatal') process.exit(1)
72
+ }
73
+
74
+ const invokedDirectly = process.argv[1]
75
+ && import.meta.url === pathToFileURL(process.argv[1]).href
76
+ if (invokedDirectly || process.env.RIVET_MOBILE_GUARD_FORCE_RUN === '1') main()
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 从国内镜像下载 better-sqlite3 预编译原生二进制(postinstall 调用)。
4
+ *
5
+ * 背景:better-sqlite3 是 optionalDependency,其 prebuild-install 从 GitHub
6
+ * Releases 拉取预编译 .node 文件。中国大陆用户常因 GitHub 不可达导致下载失败,
7
+ * 又缺少编译工具链做 fallback 源码编译 → 静默跳过 → 运行时退化为纯内存模式。
8
+ *
9
+ * 本脚本在 postinstall 时运行,穷尽安装链后才降级(不能随便降级到内存模式):
10
+ * 1. better-sqlite3 已通过 npm 正常编译安装 → 拷贝到 dist/native/(零开销)
11
+ * 2. 四路预编译镜像(npmmirror registry / npmmirror CDN 直连 / kkgithub /
12
+ * GitHub 直连)逐个尝试拉取预编译 .tar.gz
13
+ * 3. 镜像全败 → npm install better-sqlite3 --no-save 源码编译兜底(走用户
14
+ * 自己的 registry 配置;有编译工具链的机器此时仍能装上;启动自愈以
15
+ * RIVET_FETCH_SKIP_COMPILE=1 调用时跳过本段——编译数分钟,留给
16
+ * postinstall/手动重跑,启动路径只做分钟内下载自愈)
17
+ * 4. 全部失败 → 落 .fetch-failed 标记 + 可操作提示,不阻断安装(optional 语义保留)
18
+ *
19
+ * 与 pack-native.js 的分工:
20
+ * pack-native.js — 桌面 sidecar 打包期,走 prebuild-install 确保 ABI 匹配
21
+ * fetch-native-sqlite.js — CLI 用户 postinstall,从镜像快速拉取
22
+ */
23
+
24
+ import { createWriteStream, existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, rmSync } from 'node:fs'
25
+ import { mkdir, unlink } from 'node:fs/promises'
26
+ import { createHash } from 'node:crypto'
27
+ import { createRequire } from 'node:module'
28
+ import { dirname, join } from 'node:path'
29
+ import { fileURLToPath } from 'node:url'
30
+ import { pipeline } from 'node:stream/promises'
31
+ import { get } from 'node:https'
32
+ import { createGunzip } from 'node:zlib'
33
+ import { execSync } from 'node:child_process'
34
+ import { Readable } from 'node:stream'
35
+ import { tmpdir } from 'node:os'
36
+
37
+ const __dirname = dirname(fileURLToPath(import.meta.url))
38
+ const repoRoot = join(__dirname, '..')
39
+
40
+ // ── 平台/架构/ABI ──────────────────────────────────────────────────
41
+ const PLATFORM = process.platform
42
+ const ARCH = process.arch
43
+ const ABI = process.versions.modules
44
+
45
+ const PLATFORM_TOKEN = { win32: 'win32', darwin: 'darwin', linux: 'linux' }[PLATFORM]
46
+ const ARCH_TOKEN = { x64: 'x64', arm64: 'arm64', ia32: 'ia32' }[ARCH] ?? ARCH
47
+
48
+ if (!PLATFORM_TOKEN) {
49
+ console.warn(`[fetch-native-sqlite] unsupported platform: ${PLATFORM}, skipping`)
50
+ process.exit(0)
51
+ }
52
+
53
+ // ── better-sqlite3 版本 ────────────────────────────────────────────
54
+ // 兼容 optionalDependencies 与 dependencies 两种声明位置——避免将来挪动
55
+ // 依赖段时兜底链静默失效(原本只读 optionalDependencies)。
56
+ const req = createRequire(import.meta.url)
57
+ let version
58
+ try {
59
+ const pkg = req('../package.json')
60
+ const raw = pkg.optionalDependencies?.['better-sqlite3'] ?? pkg.dependencies?.['better-sqlite3']
61
+ if (!raw) {
62
+ console.log('[fetch-native-sqlite] better-sqlite3 not found in dependencies/optionalDependencies, skipping')
63
+ process.exit(0)
64
+ }
65
+ version = raw.replace(/^[\^~]/, '')
66
+ } catch {
67
+ console.log('[fetch-native-sqlite] cannot read package.json, skipping')
68
+ process.exit(0)
69
+ }
70
+
71
+ // ── 目标路径 ────────────────────────────────────────────────────────
72
+ const TARGET_DIR = join(repoRoot, 'dist', 'native')
73
+ const TARGET = join(TARGET_DIR, 'better_sqlite3.node')
74
+ const FAILED_MARKER = join(TARGET_DIR, '.fetch-failed')
75
+ const NODE_MODULES_NATIVE = join(
76
+ repoRoot, 'node_modules', 'better-sqlite3', 'build', 'Release', 'better_sqlite3.node',
77
+ )
78
+
79
+ /**
80
+ * 失败标记(native-resolver 启动自愈消费):
81
+ * 写入——下载链全部失败时落 dist/native/.fetch-failed({ ts, error }),
82
+ * 启动自愈见 5 分钟内的新鲜标记即跳过重试,不再每次启动白等下载超时;
83
+ * 清除——所有成功路径(已有产物/复用 node_modules/下载成功)都清掉陈旧标记。
84
+ * 静默失败从此有痕迹:用户与自愈逻辑都能看到「上次什么时候、败在哪一步」。
85
+ */
86
+ function clearFailedMarker() {
87
+ try {
88
+ if (existsSync(FAILED_MARKER)) rmSync(FAILED_MARKER, { force: true })
89
+ } catch { /* 标记清理失败不影响主流程 */ }
90
+ }
91
+
92
+ function writeFailedMarker(lastError) {
93
+ try {
94
+ mkdirSync(TARGET_DIR, { recursive: true })
95
+ writeFileSync(FAILED_MARKER, JSON.stringify({ ts: Date.now(), error: String(lastError).slice(0, 500) }, null, 2) + '\n')
96
+ } catch { /* 写不了标记也不阻断——提示文案已在 stdout/stderr */ }
97
+ }
98
+
99
+ const TARBALL = `better-sqlite3-v${version}-node-v${ABI}-${PLATFORM_TOKEN}-${ARCH_TOKEN}.tar.gz`
100
+
101
+ // ── 完整性钉扎(供应链防线)────────────────────────────────────────────
102
+ // 官方 release tarball 的 SHA-256(v12.10.0;ABI 127=node22、137=node24)。
103
+ // 来源:github.com 直连下载计算,npmmirror 双源交叉验证一致。
104
+ // 升级 better-sqlite3 版本时须重新生成本表(sha256sum 各平台 tarball),
105
+ // 否则新版本走「无钉扎」策略(见 verifyTarball)——kkgithub 镜像将拒绝服务。
106
+ const SHA256_PINNED = {
107
+ 'better-sqlite3-v12.10.0-node-v127-darwin-arm64.tar.gz': '35533f9ada82cb3f33760c1dc2f128f77c2b34ec2b2ede722824cb64cd50a46a',
108
+ 'better-sqlite3-v12.10.0-node-v127-darwin-x64.tar.gz': '8d2ef0eb7f880f88882f0a7a7362a15e2d210a077681f0672d0dfc8b919bd91b',
109
+ 'better-sqlite3-v12.10.0-node-v127-linux-arm64.tar.gz': 'efb625877ea517be6003c14dbabdb748d0ac172ab50bc4e5af08c2cb272b70c6',
110
+ 'better-sqlite3-v12.10.0-node-v127-linux-x64.tar.gz': '899dcec7d0e4e2ec35db783a9155b878ae15754512585de7352bbd33cb1d6c48',
111
+ 'better-sqlite3-v12.10.0-node-v127-win32-arm64.tar.gz': '81ab2327e76e8cc6290a08528d41f19b913d4c5146bfd12c159970fa59559d48',
112
+ 'better-sqlite3-v12.10.0-node-v127-win32-x64.tar.gz': '86a12ba6f19ecf1e4db4effaf5918e716d4d4fb60b0316e18d95d569ba62d6bc',
113
+ 'better-sqlite3-v12.10.0-node-v137-darwin-arm64.tar.gz': 'b140983c8befcef30532ea615aa106c770f2f95cd20994d31ca593c0b4e85423',
114
+ 'better-sqlite3-v12.10.0-node-v137-darwin-x64.tar.gz': 'a02f8e9c2024f2bd4386e58671524fcf722c5187b549f46a955d8e9c3b22f733',
115
+ 'better-sqlite3-v12.10.0-node-v137-linux-arm64.tar.gz': '7648f3a8295cf03a036eb392b66fbef75347662d654f6ab558f5f33c9e47d69a',
116
+ 'better-sqlite3-v12.10.0-node-v137-linux-x64.tar.gz': 'c2f7503e6cc3a2b1dc9fd03e7194934438f42e0724ecac6696da0582585362f2',
117
+ 'better-sqlite3-v12.10.0-node-v137-win32-arm64.tar.gz': '406e45058184a8f2d2f541fa8deb06933e29bf4dc384fa069c0930dde6e75681',
118
+ 'better-sqlite3-v12.10.0-node-v137-win32-x64.tar.gz': '0f6d948e6438f64c983d08a2048ef10d6f467f36a8c08f20d31f585c019eb83c',
119
+ }
120
+
121
+ /**
122
+ * 校验策略:
123
+ * 有钉扎 → 所有镜像一律校验,不匹配即硬失败换下一路(被替换的镜像无法
124
+ * 投毒——产物是加载进 agent 进程的原生代码);
125
+ * 无钉扎 → 仅允许既有基础设施源(npmmirror registry/CDN、github 官方),
126
+ * 第三方代理 kkgithub 拒绝——版本升级漏更新哈希表时收窄暴露面,
127
+ * 而不是放行全部镜像。
128
+ */
129
+ async function verifyTarball(tmpTar, mirrorName) {
130
+ const expected = SHA256_PINNED[TARBALL]
131
+ if (!expected) {
132
+ if (mirrorName === 'kkgithub') {
133
+ throw new Error(`no SHA-256 pin for ${TARBALL} — third-party proxy rejected without pin (regenerate SHA256_PINNED on version bump)`)
134
+ }
135
+ console.warn(`[fetch-native-sqlite] ⚠ no SHA-256 pin for ${TARBALL} — allowing infra mirror (${mirrorName}) unverified`)
136
+ return
137
+ }
138
+ const hash = createHash('sha256').update(readFileSync(tmpTar)).digest('hex')
139
+ if (hash !== expected) {
140
+ throw new Error(`SHA-256 mismatch for ${TARBALL} from ${mirrorName}: got ${hash}`)
141
+ }
142
+ console.log(`[fetch-native-sqlite] SHA-256 verified (${hash.slice(0, 12)}…)`)
143
+ }
144
+
145
+ // ── 镜像源(按优先级)────────────────────────────────────────────────
146
+ // 四路预编译 + 一路源码编译(stage 4),穷尽后才降级——不能随便降级到内存模式。
147
+ const MIRRORS = [
148
+ {
149
+ name: 'npmmirror',
150
+ url: `https://registry.npmmirror.com/-/binary/better-sqlite3/v${version}/${TARBALL}`,
151
+ },
152
+ {
153
+ // registry 域名故障/被墙但 CDN 可达时的直连兜底(registry 302 的最终目标)
154
+ name: 'npmmirror-cdn',
155
+ url: `https://cdn.npmmirror.com/binaries/better-sqlite3/v${version}/${TARBALL}`,
156
+ },
157
+ {
158
+ name: 'kkgithub',
159
+ url: `https://kkgithub.com/WiseLibs/better-sqlite3/releases/download/v${version}/${TARBALL}`,
160
+ },
161
+ {
162
+ name: 'github (direct)',
163
+ url: `https://github.com/WiseLibs/better-sqlite3/releases/download/v${version}/${TARBALL}`,
164
+ },
165
+ ]
166
+
167
+ // ── main ────────────────────────────────────────────────────────────
168
+
169
+ async function main() {
170
+ // 1. 已有 dist/native 产物 → 跳过(顺带清陈旧失败标记——产物在,标记必过期)
171
+ if (existsSync(TARGET)) {
172
+ clearFailedMarker()
173
+ console.log('[fetch-native-sqlite] native binary already present, skipping')
174
+ process.exit(0)
175
+ }
176
+
177
+ // 2. node_modules 中已有编译产物 → 直接复用(npm 正常安装了)
178
+ if (existsSync(NODE_MODULES_NATIVE)) {
179
+ mkdirSync(TARGET_DIR, { recursive: true })
180
+ copyFileSync(NODE_MODULES_NATIVE, TARGET)
181
+ clearFailedMarker()
182
+ console.log('[fetch-native-sqlite] ✓ native binary found in node_modules, copied to dist/native/')
183
+ process.exit(0)
184
+ }
185
+
186
+ // 3. 从镜像下载
187
+ mkdirSync(TARGET_DIR, { recursive: true })
188
+
189
+ let lastError = 'unknown'
190
+ for (const mirror of MIRRORS) {
191
+ try {
192
+ console.log(`[fetch-native-sqlite] trying ${mirror.name}: ${mirror.url}`)
193
+ await downloadAndExtract(mirror.url, TARGET_DIR, mirror.name)
194
+ clearFailedMarker()
195
+ console.log(`[fetch-native-sqlite] ✓ downloaded from ${mirror.name}`)
196
+ process.exit(0)
197
+ } catch (err) {
198
+ const msg = err instanceof Error ? err.message : String(err)
199
+ lastError = `${mirror.name}: ${msg}`
200
+ console.warn(`[fetch-native-sqlite] ${mirror.name} failed: ${msg}`)
201
+ }
202
+ }
203
+
204
+ // 4. 预编译全败 → 源码编译兜底(本地 npm install:走用户自己的 registry 配置,
205
+ // prebuild-install 再试一轮 GitHub,失败则 node-gyp 编译——有编译工具链的
206
+ // 机器此时能装上;npm 整体不可达则快速失败)。启动自愈(native-resolver)
207
+ // 以 RIVET_FETCH_SKIP_COMPILE=1 调本脚本时跳过——编译动辄数分钟,启动路径
208
+ // 只做分钟内的下载自愈,编译兜底留给 postinstall/手动重跑。
209
+ if (!process.env.RIVET_FETCH_SKIP_COMPILE) {
210
+ try {
211
+ console.log('[fetch-native-sqlite] mirrors exhausted, trying source build via npm install …')
212
+ execSync(`npm install better-sqlite3@${version} --no-save --no-audit --no-fund`, {
213
+ cwd: repoRoot,
214
+ stdio: 'pipe',
215
+ timeout: 10 * 60_000, // 源码编译 2-5 分钟常见,给足
216
+ })
217
+ if (existsSync(NODE_MODULES_NATIVE)) {
218
+ mkdirSync(TARGET_DIR, { recursive: true })
219
+ copyFileSync(NODE_MODULES_NATIVE, TARGET)
220
+ clearFailedMarker()
221
+ console.log('[fetch-native-sqlite] ✓ source build succeeded, copied to dist/native/')
222
+ process.exit(0)
223
+ }
224
+ lastError = 'npm install: no binary produced'
225
+ } catch (err) {
226
+ lastError = `npm install: ${err instanceof Error ? err.message : String(err)}`.slice(0, 400)
227
+ console.warn(`[fetch-native-sqlite] source build failed: ${lastError}`)
228
+ }
229
+ }
230
+
231
+ // 5. 全部失败 → 落失败标记(启动自愈 5 分钟内不再重试)+ 可操作提示
232
+ writeFailedMarker(lastError)
233
+ console.warn('')
234
+ console.warn('⚠ [fetch-native-sqlite] All mirrors failed. better-sqlite3 not available.')
235
+ console.warn(' Session history & cross-session memory will run in memory-only mode.')
236
+ console.warn('')
237
+ // Fix 指引修正(2026-08-17):旧的 windows-build-tools(npm 包已废弃多年)+
238
+ // `npm rebuild better-sqlite3 -g tianshu-harness`(rebuild 不到全局安装包里的
239
+ // optional dep,语法本身也不通)全部换成本仓库自带的预编译拉取脚本——
240
+ // 无需任何编译工具链,网络恢复后在安装目录重跑一次即可。
241
+ console.warn(' Fix: 网络恢复后在安装目录重跑本脚本(含预编译四镜像 + 源码编译兜底):')
242
+ console.warn(' npm root -g # 找到全局 node_modules 路径')
243
+ if (PLATFORM === 'win32') {
244
+ console.warn(' cd <该路径>\\tianshu-harness && node scripts\\fetch-native-sqlite.js')
245
+ } else {
246
+ console.warn(' cd <该路径>/tianshu-harness && node scripts/fetch-native-sqlite.js')
247
+ }
248
+ process.exit(0)
249
+ }
250
+
251
+ // ── downloadAndExtract ──────────────────────────────────────────────
252
+
253
+ /**
254
+ * 下载 better-sqlite3 .tar.gz 预编译包,解压出 .node 文件到 destDir。
255
+ * 实测镜像 tarball 内部结构(npm 镜像源的 prebuild):
256
+ * build/
257
+ * Release/
258
+ * better_sqlite3.node ← 我们需要的(仅 2 层目录,strip-components=2)
259
+ * 注意:原代码假设是 package/build/Release/...(3 层)用了 strip=3,实际镜像
260
+ * tarball 是 2 层,strip 3 会剥光路径报 "Not found in archive"。
261
+ */
262
+ async function downloadAndExtract(url, destDir, mirrorName) {
263
+ const tmpTar = join(tmpdir(), `better-sqlite3-${mirrorName}-${Date.now()}.tar.gz`)
264
+
265
+ try {
266
+ await downloadFile(url, tmpTar)
267
+ await verifyTarball(tmpTar, mirrorName)
268
+ await extractNodeBinary(tmpTar, destDir)
269
+ } finally {
270
+ try { await unlink(tmpTar) } catch { /* ignore */ }
271
+ }
272
+ }
273
+
274
+ function downloadFile(url, dest) {
275
+ return new Promise((resolve, reject) => {
276
+ const timeout = 30_000
277
+ // 延迟创建文件流:只在确认是最终 200 响应时才 open 目标文件。
278
+ // 否则重定向链(npmmirror registry → cdn.npmmirror.com 实测 302)会反复
279
+ // close()/createWriteStream() 同一路径,Windows 文件锁语义下偶发 EBUSY。
280
+ let file = null
281
+
282
+ const req = get(url, { timeout }, (res) => {
283
+ // 处理重定向:未写任何字节,直接递归跟新 URL
284
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
285
+ // 释放本次响应体,避免 socket 泄漏
286
+ res.resume()
287
+ downloadFile(res.headers.location, dest).then(resolve, reject)
288
+ return
289
+ }
290
+ if (res.statusCode !== 200) {
291
+ res.resume()
292
+ reject(new Error(`HTTP ${res.statusCode}`))
293
+ return
294
+ }
295
+ // 最终响应——此时才创建目标文件流
296
+ file = createWriteStream(dest)
297
+ pipeline(res, file).then(resolve, reject)
298
+ })
299
+
300
+ req.on('error', (err) => {
301
+ if (file) file.close()
302
+ reject(err)
303
+ })
304
+ req.on('timeout', () => {
305
+ req.destroy()
306
+ if (file) file.close()
307
+ reject(new Error('download timeout'))
308
+ })
309
+ })
310
+ }
311
+
312
+ async function extractNodeBinary(tarPath, destDir) {
313
+ // 用系统 tar 命令解压。镜像 tarball 结构为 build/Release/better_sqlite3.node
314
+ // (2 层目录),strip-components=2 剥到文件名本身。Windows 上 bsdtar(Win10+ 自带)
315
+ // 同样支持该标志;旧版不支持时进 catch 走纯 Node 解压 fallback。
316
+ const cmd = process.platform === 'win32'
317
+ ? `tar -xzf "${tarPath}" -C "${destDir}" --strip-components=2 --wildcards "*/better_sqlite3.node"`
318
+ : `tar -xzf "${tarPath}" -C "${destDir}" --strip-components=2 "*/better_sqlite3.node"`
319
+
320
+ try {
321
+ execSync(cmd, { stdio: 'pipe', timeout: 30_000 })
322
+ } catch {
323
+ // tar 可能不支持 --strip-components(Windows 旧版),用 Node 手动解压
324
+ await extractWithNode(tarPath, destDir)
325
+ }
326
+
327
+ if (!existsSync(join(destDir, 'better_sqlite3.node'))) {
328
+ throw new Error('better_sqlite3.node not found in tarball after extraction')
329
+ }
330
+ }
331
+
332
+ async function extractWithNode(tarPath, destDir) {
333
+ // 纯 Node 解压:读取 .tar.gz,在 tar 流中找 better_sqlite3.node
334
+ const zlib = await import('node:zlib')
335
+ const { createReadStream } = await import('node:fs')
336
+ const { Transform } = await import('node:stream')
337
+
338
+ return new Promise((resolve, reject) => {
339
+ const gunzip = zlib.createGunzip()
340
+ let buffer = Buffer.alloc(0)
341
+ let found = false
342
+
343
+ const parser = new Transform({
344
+ transform(chunk, _encoding, callback) {
345
+ buffer = Buffer.concat([buffer, chunk])
346
+ // Tar 格式:512 字节 header + 数据块
347
+ while (buffer.length >= 512 && !found) {
348
+ const name = buffer.toString('utf8', 0, 100).replace(/\0/g, '')
349
+ const sizeStr = buffer.toString('utf8', 124, 136).replace(/\0/g, '').trim()
350
+ const size = parseInt(sizeStr, 8) || 0
351
+
352
+ if (name.endsWith('better_sqlite3.node')) {
353
+ const dataStart = Math.ceil(512 / 512) * 512 // next 512-aligned
354
+ const dataEnd = dataStart + size
355
+ if (buffer.length >= dataEnd) {
356
+ const fileData = buffer.subarray(dataStart, dataEnd)
357
+ const dest = join(destDir, 'better_sqlite3.node')
358
+ // 原 require('node:fs') 在 ESM 模块下会抛 "require is not defined"
359
+ // ——Windows 上 tar 不支持 --strip-components 进此分支必崩。改用顶部 import。
360
+ writeFileSync(dest, fileData)
361
+ found = true
362
+ resolve()
363
+ return
364
+ }
365
+ // Need more data
366
+ break
367
+ }
368
+
369
+ // Skip this entry
370
+ const totalLen = 512 + Math.ceil(size / 512) * 512
371
+ if (buffer.length >= totalLen) {
372
+ buffer = buffer.subarray(totalLen)
373
+ } else {
374
+ break // need more data
375
+ }
376
+ }
377
+ callback()
378
+ },
379
+ flush(callback) {
380
+ if (!found) reject(new Error('better_sqlite3.node not found in tar stream'))
381
+ callback()
382
+ },
383
+ })
384
+
385
+ createReadStream(tarPath)
386
+ .pipe(gunzip)
387
+ .pipe(parser)
388
+ .on('error', reject)
389
+ })
390
+ }
391
+
392
+ main()
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * prune-installed-wasms.js — npm 安装后裁剪 tree-sitter-wasms 的多余语法 wasm。
4
+ *
5
+ * 背景:tree-sitter-wasms 整包约 50MB(40+ 语言 grammar),但运行时只有
6
+ * src/repo/meridian-parser.ts 加载 TS/Python/Go 三个(见 LANG_WASM)。
7
+ * 桌面 sidecar 打包期已由 stage-runtime-deps.js 裁剪;npm 用户走 postinstall,
8
+ * 在这里对已安装到 node_modules 的副本做同样裁剪,把安装体积砍掉 ~47MB。
9
+ *
10
+ * 安全语义:
11
+ * - pnpm 内容寻址 store(路径含 .pnpm)会污染共享缓存 → 跳过
12
+ * - 任何异常只告警不阻断安装(裁剪失败只是体积大,功能不受影响)
13
+ */
14
+ import { existsSync } from 'node:fs'
15
+ import { dirname, join } from 'node:path'
16
+ import { createRequire } from 'node:module'
17
+ import { pruneTreeSitterWasms } from './tree-sitter-wasm-keep.js'
18
+
19
+ try {
20
+ const req = createRequire(import.meta.url)
21
+ const wasmsPkgJson = req.resolve('tree-sitter-wasms/package.json')
22
+ const pkgRoot = dirname(wasmsPkgJson)
23
+
24
+ if (pkgRoot.includes('.pnpm')) {
25
+ console.log('[prune-installed-wasms] pnpm 共享 store,跳过裁剪')
26
+ process.exit(0)
27
+ }
28
+
29
+ const outDir = join(pkgRoot, 'out')
30
+ if (!existsSync(outDir)) {
31
+ console.log('[prune-installed-wasms] tree-sitter-wasms/out 不存在,跳过')
32
+ process.exit(0)
33
+ }
34
+
35
+ const { kept, removed } = pruneTreeSitterWasms(outDir)
36
+ if (removed.length > 0) {
37
+ console.log(
38
+ `[prune-installed-wasms] 保留 ${kept.length} 个 grammar(${kept.join(', ')}),裁剪 ${removed.length} 个`,
39
+ )
40
+ } else {
41
+ console.log('[prune-installed-wasms] 已是最小集,无需裁剪')
42
+ }
43
+ } catch (err) {
44
+ console.warn('[prune-installed-wasms] 裁剪失败(不影响功能):', err && err.message ? err.message : err)
45
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Allowlist of tree-sitter grammar wasm files shipped in the packaged sidecar.
3
+ *
4
+ * Only languages loaded by `src/repo/meridian-parser.ts` (LANG_WASM) are kept.
5
+ * Unsupported languages fall back to heuristic chunking; missing wasm would
6
+ * only surface if meridian tried to parse them (it doesn't).
7
+ */
8
+ import { existsSync, readdirSync, rmSync } from 'node:fs'
9
+ import { join } from 'node:path'
10
+
11
+ /** Filenames under tree-sitter-wasms/out/ that must remain after staging. */
12
+ export const TS_WASM_KEEP = new Set([
13
+ 'tree-sitter-typescript.wasm',
14
+ 'tree-sitter-python.wasm',
15
+ 'tree-sitter-go.wasm',
16
+ ])
17
+
18
+ /**
19
+ * Delete non-allowlisted .wasm files under `outDir`.
20
+ * @param {string} outDir absolute path to tree-sitter-wasms/out
21
+ * @returns {{ kept: string[], removed: string[] }}
22
+ */
23
+ export function pruneTreeSitterWasms(outDir) {
24
+ /** @type {string[]} */
25
+ const kept = []
26
+ /** @type {string[]} */
27
+ const removed = []
28
+ if (!existsSync(outDir)) return { kept, removed }
29
+ for (const f of readdirSync(outDir)) {
30
+ if (!f.endsWith('.wasm')) continue
31
+ const p = join(outDir, f)
32
+ if (TS_WASM_KEEP.has(f)) {
33
+ kept.push(f)
34
+ } else {
35
+ rmSync(p, { force: true })
36
+ removed.push(f)
37
+ }
38
+ }
39
+ return { kept, removed }
40
+ }