@logictan/dsh-config-manager 0.1.60

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 (429) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/cordis.patch.yml +21 -0
  4. package/lib/adapters/agent-instructions.d.ts +13 -0
  5. package/lib/adapters/agent-instructions.js +46 -0
  6. package/lib/adapters/agent-presets.d.ts +13 -0
  7. package/lib/adapters/agent-presets.js +14 -0
  8. package/lib/adapters/credentials.d.ts +37 -0
  9. package/lib/adapters/credentials.js +122 -0
  10. package/lib/adapters/file-collection.d.ts +15 -0
  11. package/lib/adapters/file-collection.js +127 -0
  12. package/lib/adapters/index.d.ts +57 -0
  13. package/lib/adapters/index.js +55 -0
  14. package/lib/adapters/link-report.d.ts +16 -0
  15. package/lib/adapters/link-report.js +47 -0
  16. package/lib/adapters/mcp.d.ts +28 -0
  17. package/lib/adapters/mcp.js +178 -0
  18. package/lib/adapters/plugin-files.d.ts +18 -0
  19. package/lib/adapters/plugin-files.js +142 -0
  20. package/lib/adapters/plugins.d.ts +41 -0
  21. package/lib/adapters/plugins.js +603 -0
  22. package/lib/adapters/pnpm-workspace.d.ts +26 -0
  23. package/lib/adapters/pnpm-workspace.js +147 -0
  24. package/lib/adapters/prompts.d.ts +30 -0
  25. package/lib/adapters/prompts.js +231 -0
  26. package/lib/adapters/providers.d.ts +34 -0
  27. package/lib/adapters/providers.js +155 -0
  28. package/lib/adapters/self.d.ts +17 -0
  29. package/lib/adapters/self.js +71 -0
  30. package/lib/adapters/sessions.d.ts +14 -0
  31. package/lib/adapters/sessions.js +15 -0
  32. package/lib/adapters/settings.d.ts +28 -0
  33. package/lib/adapters/settings.js +165 -0
  34. package/lib/adapters/skills.d.ts +13 -0
  35. package/lib/adapters/skills.js +14 -0
  36. package/lib/adapters/test-helpers.d.ts +125 -0
  37. package/lib/adapters/test-helpers.js +236 -0
  38. package/lib/adapters/ui.d.ts +27 -0
  39. package/lib/adapters/ui.js +89 -0
  40. package/lib/adapters/workspaces.d.ts +13 -0
  41. package/lib/adapters/workspaces.js +106 -0
  42. package/lib/client.d.ts +1026 -0
  43. package/lib/client.js +10047 -0
  44. package/lib/core/analyzer.d.ts +99 -0
  45. package/lib/core/analyzer.js +934 -0
  46. package/lib/core/backup.d.ts +138 -0
  47. package/lib/core/backup.js +513 -0
  48. package/lib/core/boot-rescue.d.ts +146 -0
  49. package/lib/core/boot-rescue.js +618 -0
  50. package/lib/core/cache-cleaner.d.ts +31 -0
  51. package/lib/core/cache-cleaner.js +95 -0
  52. package/lib/core/config-lifecycle.d.ts +180 -0
  53. package/lib/core/config-lifecycle.js +481 -0
  54. package/lib/core/config-snapshot.d.ts +135 -0
  55. package/lib/core/config-snapshot.js +424 -0
  56. package/lib/core/config-state.d.ts +70 -0
  57. package/lib/core/config-state.js +177 -0
  58. package/lib/core/consult-source.d.ts +58 -0
  59. package/lib/core/consult-source.js +260 -0
  60. package/lib/core/crash-report.d.ts +84 -0
  61. package/lib/core/crash-report.js +317 -0
  62. package/lib/core/exporter.d.ts +68 -0
  63. package/lib/core/exporter.js +375 -0
  64. package/lib/core/ghost-sweep.d.ts +19 -0
  65. package/lib/core/ghost-sweep.js +52 -0
  66. package/lib/core/importer.d.ts +60 -0
  67. package/lib/core/importer.js +29 -0
  68. package/lib/core/index.d.ts +14 -0
  69. package/lib/core/index.js +14 -0
  70. package/lib/core/journal.d.ts +197 -0
  71. package/lib/core/journal.js +418 -0
  72. package/lib/core/local-plugin-host.d.ts +35 -0
  73. package/lib/core/local-plugin-host.js +120 -0
  74. package/lib/core/local-plugin-pack.d.ts +135 -0
  75. package/lib/core/local-plugin-pack.js +278 -0
  76. package/lib/core/messages.d.ts +310 -0
  77. package/lib/core/messages.js +621 -0
  78. package/lib/core/migration-consult.d.ts +117 -0
  79. package/lib/core/migration-consult.js +363 -0
  80. package/lib/core/migration-history.d.ts +153 -0
  81. package/lib/core/migration-history.js +411 -0
  82. package/lib/core/model-tools.d.ts +50 -0
  83. package/lib/core/model-tools.js +148 -0
  84. package/lib/core/msg-types.d.ts +8 -0
  85. package/lib/core/msg-types.js +6 -0
  86. package/lib/core/phase3-child-crash.d.ts +1 -0
  87. package/lib/core/phase3-child-crash.js +87 -0
  88. package/lib/core/phase3-host.d.ts +138 -0
  89. package/lib/core/phase3-host.js +359 -0
  90. package/lib/core/phase3-prod-child.d.ts +1 -0
  91. package/lib/core/phase3-prod-child.js +35 -0
  92. package/lib/core/phase4-crash-child.d.ts +1 -0
  93. package/lib/core/phase4-crash-child.js +103 -0
  94. package/lib/core/plugin-cli.d.ts +150 -0
  95. package/lib/core/plugin-cli.js +497 -0
  96. package/lib/core/reconcile.d.ts +120 -0
  97. package/lib/core/reconcile.js +389 -0
  98. package/lib/core/recovery-orchestrator.d.ts +94 -0
  99. package/lib/core/recovery-orchestrator.js +327 -0
  100. package/lib/core/restore.d.ts +162 -0
  101. package/lib/core/restore.js +703 -0
  102. package/lib/core/rollback.d.ts +12 -0
  103. package/lib/core/rollback.js +145 -0
  104. package/lib/core/run-registry.d.ts +85 -0
  105. package/lib/core/run-registry.js +157 -0
  106. package/lib/core/startup-barrier.d.ts +53 -0
  107. package/lib/core/startup-barrier.js +77 -0
  108. package/lib/core/types.d.ts +515 -0
  109. package/lib/core/types.js +27 -0
  110. package/lib/core/undo.d.ts +73 -0
  111. package/lib/core/undo.js +87 -0
  112. package/lib/core/validator.d.ts +18 -0
  113. package/lib/core/validator.js +77 -0
  114. package/lib/core/verify-recovery.d.ts +21 -0
  115. package/lib/core/verify-recovery.js +256 -0
  116. package/lib/core/watcher.d.ts +101 -0
  117. package/lib/core/watcher.js +219 -0
  118. package/lib/index.d.ts +238 -0
  119. package/lib/index.js +2764 -0
  120. package/lib/migrations/index.d.ts +29 -0
  121. package/lib/migrations/index.js +56 -0
  122. package/lib/migrations/v1-to-v2.d.ts +9 -0
  123. package/lib/migrations/v1-to-v2.js +10 -0
  124. package/lib/profiles/index.d.ts +4 -0
  125. package/lib/profiles/index.js +5 -0
  126. package/lib/profiles/profile-manager.d.ts +106 -0
  127. package/lib/profiles/profile-manager.js +543 -0
  128. package/lib/schema/config.d.ts +28 -0
  129. package/lib/schema/config.js +165 -0
  130. package/lib/schema/index.d.ts +24 -0
  131. package/lib/schema/index.js +26 -0
  132. package/lib/schema/manifest.d.ts +28 -0
  133. package/lib/schema/manifest.js +109 -0
  134. package/lib/schema/tombstones.d.ts +46 -0
  135. package/lib/schema/tombstones.js +64 -0
  136. package/lib/schema/types.d.ts +232 -0
  137. package/lib/schema/types.js +8 -0
  138. package/lib/schema/versions.d.ts +27 -0
  139. package/lib/schema/versions.js +50 -0
  140. package/lib/security/encryption.d.ts +85 -0
  141. package/lib/security/encryption.js +279 -0
  142. package/lib/security/index.d.ts +16 -0
  143. package/lib/security/index.js +17 -0
  144. package/lib/security/integrity.d.ts +30 -0
  145. package/lib/security/integrity.js +78 -0
  146. package/lib/security/redaction.d.ts +26 -0
  147. package/lib/security/redaction.js +95 -0
  148. package/lib/security/secret-scanner.d.ts +86 -0
  149. package/lib/security/secret-scanner.js +404 -0
  150. package/lib/security/vault.d.ts +59 -0
  151. package/lib/security/vault.js +151 -0
  152. package/lib/security/zip-security.d.ts +27 -0
  153. package/lib/security/zip-security.js +154 -0
  154. package/lib/sync/autosync-config.d.ts +39 -0
  155. package/lib/sync/autosync-config.js +151 -0
  156. package/lib/sync/autosync-scheduler.d.ts +139 -0
  157. package/lib/sync/autosync-scheduler.js +564 -0
  158. package/lib/sync/fs.d.ts +18 -0
  159. package/lib/sync/fs.js +49 -0
  160. package/lib/sync/git/git-transport.d.ts +100 -0
  161. package/lib/sync/git/git-transport.js +437 -0
  162. package/lib/sync/github-auth.d.ts +131 -0
  163. package/lib/sync/github-auth.js +264 -0
  164. package/lib/sync/layout.d.ts +43 -0
  165. package/lib/sync/layout.js +203 -0
  166. package/lib/sync/snapshot-json.d.ts +9 -0
  167. package/lib/sync/snapshot-json.js +107 -0
  168. package/lib/sync/sync-config.d.ts +81 -0
  169. package/lib/sync/sync-config.js +281 -0
  170. package/lib/sync/sync-engine.d.ts +269 -0
  171. package/lib/sync/sync-engine.js +591 -0
  172. package/lib/sync/sync-history.d.ts +42 -0
  173. package/lib/sync/sync-history.js +78 -0
  174. package/lib/sync/sync-selection.d.ts +29 -0
  175. package/lib/sync/sync-selection.js +127 -0
  176. package/lib/sync/sync-session.d.ts +40 -0
  177. package/lib/sync/sync-session.js +54 -0
  178. package/lib/sync/sync-state.d.ts +37 -0
  179. package/lib/sync/sync-state.js +108 -0
  180. package/lib/sync/transport.d.ts +75 -0
  181. package/lib/sync/transport.js +55 -0
  182. package/lib/sync/ui-prefs.d.ts +34 -0
  183. package/lib/sync/ui-prefs.js +109 -0
  184. package/lib/sync/webdav/webdav-transport.d.ts +77 -0
  185. package/lib/sync/webdav/webdav-transport.js +451 -0
  186. package/lib/ui/errors.d.ts +19 -0
  187. package/lib/ui/errors.js +81 -0
  188. package/lib/ui/export-flow.d.ts +82 -0
  189. package/lib/ui/export-flow.js +98 -0
  190. package/lib/ui/i18n.d.ts +289 -0
  191. package/lib/ui/i18n.js +581 -0
  192. package/lib/ui/migration-consult-view.d.ts +91 -0
  193. package/lib/ui/migration-consult-view.js +115 -0
  194. package/lib/ui/progress.d.ts +38 -0
  195. package/lib/ui/progress.js +87 -0
  196. package/lib/ui/report.d.ts +23 -0
  197. package/lib/ui/report.js +153 -0
  198. package/lib/ui/test-helpers.d.ts +69 -0
  199. package/lib/ui/test-helpers.js +154 -0
  200. package/lib/ui/types.d.ts +257 -0
  201. package/lib/ui/types.js +21 -0
  202. package/lib/utils/atomic-write.d.ts +88 -0
  203. package/lib/utils/atomic-write.js +399 -0
  204. package/lib/utils/env-lock.d.ts +393 -0
  205. package/lib/utils/env-lock.js +930 -0
  206. package/lib/utils/hashing.d.ts +17 -0
  207. package/lib/utils/hashing.js +41 -0
  208. package/lib/utils/json.d.ts +24 -0
  209. package/lib/utils/json.js +76 -0
  210. package/lib/utils/logger.d.ts +39 -0
  211. package/lib/utils/logger.js +82 -0
  212. package/lib/utils/paths.d.ts +42 -0
  213. package/lib/utils/paths.js +189 -0
  214. package/lib/utils/proxy.d.ts +69 -0
  215. package/lib/utils/proxy.js +455 -0
  216. package/lib/utils/recursive-walk.d.ts +24 -0
  217. package/lib/utils/recursive-walk.js +134 -0
  218. package/lib/utils/zip.d.ts +54 -0
  219. package/lib/utils/zip.js +285 -0
  220. package/package.json +188 -0
  221. package/src/adapters/agent-instructions.ts +48 -0
  222. package/src/adapters/agent-presets.ts +14 -0
  223. package/src/adapters/credentials.test.ts +90 -0
  224. package/src/adapters/credentials.ts +143 -0
  225. package/src/adapters/file-collection.ts +137 -0
  226. package/src/adapters/files.test.ts +353 -0
  227. package/src/adapters/index.ts +94 -0
  228. package/src/adapters/link-report.test.ts +65 -0
  229. package/src/adapters/link-report.ts +61 -0
  230. package/src/adapters/mcp.test.ts +96 -0
  231. package/src/adapters/mcp.ts +185 -0
  232. package/src/adapters/plugin-files.ts +146 -0
  233. package/src/adapters/plugins.test.ts +326 -0
  234. package/src/adapters/plugins.ts +615 -0
  235. package/src/adapters/pnpm-workspace.test.ts +87 -0
  236. package/src/adapters/pnpm-workspace.ts +150 -0
  237. package/src/adapters/prompts.test.ts +110 -0
  238. package/src/adapters/prompts.ts +230 -0
  239. package/src/adapters/providers.test.ts +126 -0
  240. package/src/adapters/providers.ts +173 -0
  241. package/src/adapters/roundtrip.test.ts +190 -0
  242. package/src/adapters/self.test.ts +92 -0
  243. package/src/adapters/self.ts +75 -0
  244. package/src/adapters/sessions.ts +15 -0
  245. package/src/adapters/settings.test.ts +100 -0
  246. package/src/adapters/settings.ts +187 -0
  247. package/src/adapters/skills.ts +14 -0
  248. package/src/adapters/test-helpers.ts +248 -0
  249. package/src/adapters/ui.test.ts +57 -0
  250. package/src/adapters/ui.ts +108 -0
  251. package/src/adapters/workspaces.test.ts +106 -0
  252. package/src/adapters/workspaces.ts +109 -0
  253. package/src/client/ConfigManagerSection.tsx +90 -0
  254. package/src/client/api.ts +66 -0
  255. package/src/client/client-types.ts +18 -0
  256. package/src/client/common/ErrorBanner.tsx +73 -0
  257. package/src/client/common/Icon.tsx +129 -0
  258. package/src/client/common/Modal.tsx +173 -0
  259. package/src/client/common/ToastViewport.tsx +73 -0
  260. package/src/client/common/toast-store.test.ts +192 -0
  261. package/src/client/common/toast-store.ts +181 -0
  262. package/src/client/common/toast-types.ts +7 -0
  263. package/src/client/common/ui.tsx +342 -0
  264. package/src/client/config-manager.module.css +2971 -0
  265. package/src/client/css-modules.d.ts +8 -0
  266. package/src/client/index.ts +65 -0
  267. package/src/client/locales.ts +56 -0
  268. package/src/client/lucide-icons.d.ts +38 -0
  269. package/src/client/run-store.test.ts +223 -0
  270. package/src/client/run-store.ts +372 -0
  271. package/src/client/sync/SyncConfirmView.tsx +379 -0
  272. package/src/client/sync/SyncHistoryView.test.ts +40 -0
  273. package/src/client/sync/SyncHistoryView.tsx +220 -0
  274. package/src/client/sync/SyncSettingsView.tsx +1535 -0
  275. package/src/client/sync/history-model.test.ts +194 -0
  276. package/src/client/sync/history-model.ts +195 -0
  277. package/src/client/sync/sync-api.test.ts +585 -0
  278. package/src/client/sync/sync-api.ts +543 -0
  279. package/src/client/sync/sync-locales.ts +416 -0
  280. package/src/client/sync/sync-push-preview.test.ts +52 -0
  281. package/src/client/sync/sync-view-v2.test.ts +213 -0
  282. package/src/client/sync/sync-view.test.ts +487 -0
  283. package/src/client/sync/sync-view.ts +759 -0
  284. package/src/core/analyzer.ts +1068 -0
  285. package/src/core/backup.ts +605 -0
  286. package/src/core/boot-rescue.test.ts +847 -0
  287. package/src/core/boot-rescue.ts +737 -0
  288. package/src/core/cache-cleaner.test.ts +169 -0
  289. package/src/core/cache-cleaner.ts +121 -0
  290. package/src/core/config-lifecycle.test.ts +727 -0
  291. package/src/core/config-lifecycle.ts +573 -0
  292. package/src/core/config-snapshot.test.ts +369 -0
  293. package/src/core/config-snapshot.ts +522 -0
  294. package/src/core/config-state.test.ts +381 -0
  295. package/src/core/config-state.ts +207 -0
  296. package/src/core/consult-source.test.ts +199 -0
  297. package/src/core/consult-source.ts +293 -0
  298. package/src/core/crash-report.test.ts +380 -0
  299. package/src/core/crash-report.ts +369 -0
  300. package/src/core/exporter.ts +417 -0
  301. package/src/core/ghost-sweep.test.ts +94 -0
  302. package/src/core/ghost-sweep.ts +67 -0
  303. package/src/core/importer.ts +83 -0
  304. package/src/core/index.ts +56 -0
  305. package/src/core/journal.test.ts +257 -0
  306. package/src/core/journal.ts +518 -0
  307. package/src/core/local-plugin-host.ts +152 -0
  308. package/src/core/local-plugin-pack.test.ts +359 -0
  309. package/src/core/local-plugin-pack.ts +363 -0
  310. package/src/core/messages.test.ts +54 -0
  311. package/src/core/messages.ts +664 -0
  312. package/src/core/migration-consult.test.ts +315 -0
  313. package/src/core/migration-consult.ts +494 -0
  314. package/src/core/migration-history.test.ts +319 -0
  315. package/src/core/migration-history.ts +484 -0
  316. package/src/core/model-tools.test.ts +190 -0
  317. package/src/core/model-tools.ts +208 -0
  318. package/src/core/msg-types.ts +10 -0
  319. package/src/core/phase3-child-crash.ts +95 -0
  320. package/src/core/phase3-host.ts +411 -0
  321. package/src/core/phase3-p1.test.ts +212 -0
  322. package/src/core/phase3-p2b-fingerprint.test.ts +189 -0
  323. package/src/core/phase3-prod-child.ts +36 -0
  324. package/src/core/phase3-production-integration.test.ts +286 -0
  325. package/src/core/phase4-crash-child.ts +101 -0
  326. package/src/core/phase4-crash-injection.test.ts +166 -0
  327. package/src/core/plugin-cli.fs.test.ts +184 -0
  328. package/src/core/plugin-cli.test.ts +137 -0
  329. package/src/core/plugin-cli.ts +563 -0
  330. package/src/core/reconcile.test.ts +392 -0
  331. package/src/core/reconcile.ts +498 -0
  332. package/src/core/recovery-orchestrator.test.ts +145 -0
  333. package/src/core/recovery-orchestrator.ts +366 -0
  334. package/src/core/restore-manage.test.ts +94 -0
  335. package/src/core/restore.ts +849 -0
  336. package/src/core/rollback.ts +151 -0
  337. package/src/core/run-progress.test.ts +237 -0
  338. package/src/core/run-registry.test.ts +232 -0
  339. package/src/core/run-registry.ts +209 -0
  340. package/src/core/smoke.test.ts +842 -0
  341. package/src/core/startup-barrier.ts +99 -0
  342. package/src/core/types.ts +542 -0
  343. package/src/core/undo.ts +116 -0
  344. package/src/core/validator.ts +84 -0
  345. package/src/core/verify-recovery.ts +265 -0
  346. package/src/core/watcher.test.ts +326 -0
  347. package/src/core/watcher.ts +263 -0
  348. package/src/index.facade.test.ts +265 -0
  349. package/src/index.sync.test.ts +217 -0
  350. package/src/index.ts +2975 -0
  351. package/src/migrations/index.ts +71 -0
  352. package/src/migrations/v1-to-v2.ts +17 -0
  353. package/src/profiles/index.ts +8 -0
  354. package/src/profiles/profile-manager.test.ts +282 -0
  355. package/src/profiles/profile-manager.ts +630 -0
  356. package/src/schema/config.ts +165 -0
  357. package/src/schema/index.ts +71 -0
  358. package/src/schema/manifest-schema.test.ts +519 -0
  359. package/src/schema/manifest.ts +124 -0
  360. package/src/schema/tombstones.test.ts +131 -0
  361. package/src/schema/tombstones.ts +89 -0
  362. package/src/schema/types.ts +245 -0
  363. package/src/schema/versions.ts +55 -0
  364. package/src/security/encryption.ts +335 -0
  365. package/src/security/index.ts +16 -0
  366. package/src/security/integrity.ts +102 -0
  367. package/src/security/redaction.ts +101 -0
  368. package/src/security/secret-scanner.ts +445 -0
  369. package/src/security/security.test.ts +1215 -0
  370. package/src/security/vault.test.ts +164 -0
  371. package/src/security/vault.ts +198 -0
  372. package/src/security/zip-security.ts +180 -0
  373. package/src/sync/autosync-config.test.ts +147 -0
  374. package/src/sync/autosync-config.ts +179 -0
  375. package/src/sync/autosync-scheduler.test.ts +490 -0
  376. package/src/sync/autosync-scheduler.ts +646 -0
  377. package/src/sync/fs.ts +48 -0
  378. package/src/sync/git/git-transport.test.ts +673 -0
  379. package/src/sync/git/git-transport.ts +491 -0
  380. package/src/sync/github-auth.test.ts +280 -0
  381. package/src/sync/github-auth.ts +356 -0
  382. package/src/sync/layout.test.ts +270 -0
  383. package/src/sync/layout.ts +222 -0
  384. package/src/sync/snapshot-json.test.ts +119 -0
  385. package/src/sync/snapshot-json.ts +128 -0
  386. package/src/sync/sync-config.test.ts +310 -0
  387. package/src/sync/sync-config.ts +299 -0
  388. package/src/sync/sync-engine.test.ts +1074 -0
  389. package/src/sync/sync-engine.ts +754 -0
  390. package/src/sync/sync-history.test.ts +116 -0
  391. package/src/sync/sync-history.ts +120 -0
  392. package/src/sync/sync-selection.test.ts +186 -0
  393. package/src/sync/sync-selection.ts +143 -0
  394. package/src/sync/sync-session.test.ts +143 -0
  395. package/src/sync/sync-session.ts +77 -0
  396. package/src/sync/sync-state.test.ts +164 -0
  397. package/src/sync/sync-state.ts +131 -0
  398. package/src/sync/transport.test.ts +111 -0
  399. package/src/sync/transport.ts +118 -0
  400. package/src/sync/ui-prefs.test.ts +152 -0
  401. package/src/sync/ui-prefs.ts +135 -0
  402. package/src/sync/webdav/webdav-transport.test.ts +871 -0
  403. package/src/sync/webdav/webdav-transport.ts +535 -0
  404. package/src/ui/errors.test.ts +47 -0
  405. package/src/ui/errors.ts +100 -0
  406. package/src/ui/export-flow.test.ts +87 -0
  407. package/src/ui/export-flow.ts +156 -0
  408. package/src/ui/i18n.ts +590 -0
  409. package/src/ui/migration-consult-view.test.ts +151 -0
  410. package/src/ui/migration-consult-view.ts +178 -0
  411. package/src/ui/progress.test.ts +47 -0
  412. package/src/ui/progress.ts +102 -0
  413. package/src/ui/report.test.ts +101 -0
  414. package/src/ui/report.ts +146 -0
  415. package/src/ui/test-helpers.ts +184 -0
  416. package/src/ui/types.ts +325 -0
  417. package/src/utils/atomic-write.test.ts +312 -0
  418. package/src/utils/atomic-write.ts +422 -0
  419. package/src/utils/env-lock.test.ts +999 -0
  420. package/src/utils/env-lock.ts +1086 -0
  421. package/src/utils/hashing.ts +53 -0
  422. package/src/utils/json.ts +77 -0
  423. package/src/utils/logger.ts +114 -0
  424. package/src/utils/paths.ts +176 -0
  425. package/src/utils/proxy.test.ts +465 -0
  426. package/src/utils/proxy.ts +493 -0
  427. package/src/utils/recursive-walk.test.ts +148 -0
  428. package/src/utils/recursive-walk.ts +145 -0
  429. package/src/utils/zip.ts +332 -0
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Phase 1 地基回归:配置快照持久化与回放(config-snapshot.ts)。
3
+ *
4
+ * 覆盖用户可见契约:
5
+ * - 快照落盘 → 读回:分区**字节**(Uint8Array)必须无损往返(JSON 会把它变成 {0:..});
6
+ * - id 冲突自动避让,不覆盖既有快照;
7
+ * - 非法/损坏快照读不炸(返回 null / 抛出可读错误),且不阻断列表;
8
+ * - 保留清理按 kind 分桶,pinned 一律豁免,manual 默认永不自动清理;
9
+ * - 回放走 adapter 管线:单向「validate → analyzeImport → applyItem」,Skip 项不执行;
10
+ * - 回放中单分区校验失败只跳过该分区,不中止其余分区。
11
+ */
12
+ import test from 'node:test';
13
+ import assert from 'node:assert/strict';
14
+ import fs from 'node:fs/promises';
15
+ import path from 'node:path';
16
+ import os from 'node:os';
17
+
18
+ import {
19
+ CONFIG_SNAPSHOT_FILE, deleteConfigSnapshot, listConfigSnapshots, loadConfigSnapshot,
20
+ makeConfigSnapshotId, pruneConfigSnapshots, readConfigSnapshotMeta, restoreConfigSnapshot,
21
+ saveConfigSnapshot, toUndoCandidate, updateConfigSnapshotMeta,
22
+ } from './config-snapshot.ts';
23
+ import { makeContext } from '../adapters/test-helpers.ts';
24
+ import type { ConfigState } from './config-state.ts';
25
+ import type { ConfigAdapter, ExportSection, HostContext } from './types.ts';
26
+ import type { SectionId } from '../schema/types.ts';
27
+
28
+ async function tmpDir(t: { after: (fn: () => Promise<void> | void) => void }, label: string): Promise<string> {
29
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), `dsh-cfgsnap-${label}-`));
30
+ t.after(async () => { await fs.rm(dir, { recursive: true, force: true }); });
31
+ return dir;
32
+ }
33
+
34
+ function state(sections: [SectionId, string][] = [['settings', 'h1']], capturedAt = '2026-09-13T00:00:00.000Z'): ConfigState {
35
+ return { capturedAt, sections: sections.map(([section, hash]) => ({ section, hash, counts: {}, fileCount: 0 })) };
36
+ }
37
+
38
+ function exported(id: SectionId, data: unknown, files?: { relativePath: string; data: Uint8Array }[]): ExportSection {
39
+ return { sectionId: id, data, ...(files !== undefined ? { files } : {}), counts: { n: 1 }, warnings: [] };
40
+ }
41
+
42
+ function ctx(): HostContext {
43
+ return makeContext('win32', 'C:/home/.dsh', 'web');
44
+ }
45
+
46
+ /** 可编程回放用 adapter:记录 applyItem 调用,validate 可失败。 */
47
+ function replayAdapter(
48
+ id: SectionId,
49
+ behavior: {
50
+ items?: { id: string; kind: string }[];
51
+ invalid?: boolean;
52
+ applyFails?: boolean;
53
+ } = {},
54
+ ): ConfigAdapter & { applied: string[] } {
55
+ const applied: string[] = [];
56
+ const adapter = {
57
+ id,
58
+ displayName: id,
59
+ defaultIncluded: true,
60
+ portability: 'portable' as const,
61
+ applied,
62
+ async export(): Promise<ExportSection> { return exported(id, { v: 1 }); },
63
+ async validate() {
64
+ return behavior.invalid === true
65
+ ? { valid: false, issues: [{ path: '$', message: 'bad shape', severity: 'error' as const }] }
66
+ : { valid: true, issues: [] };
67
+ },
68
+ async analyzeImport() {
69
+ return (behavior.items ?? []).map((it) => ({
70
+ id: it.id,
71
+ kind: it.kind as never,
72
+ adapter: id,
73
+ description: it.id,
74
+ severity: 'info' as const,
75
+ target: { adapter: id, ref: it.id },
76
+ }));
77
+ },
78
+ async applyItem(item: { id: string }) {
79
+ if (behavior.applyFails === true) return { ok: false, message: 'apply boom' };
80
+ applied.push(item.id);
81
+ return { ok: true };
82
+ },
83
+ };
84
+ return adapter as unknown as ConfigAdapter & { applied: string[] };
85
+ }
86
+
87
+ /* ------------------------------------------------------------ 创建与往返 */
88
+
89
+ test('saveConfigSnapshot + loadConfigSnapshot:分区字节无损往返(Uint8Array 不被 JSON 退化成对象)', async (t) => {
90
+ const dir = await tmpDir(t, 'roundtrip');
91
+ const bytes = new Uint8Array([0, 1, 2, 250, 255]);
92
+ const sections = new Map<SectionId, ExportSection>([
93
+ ['skills', exported('skills', { version: 1, files: [{ relativePath: 'a.md', contentHash: 'x', data: bytes }] })],
94
+ ]);
95
+ const meta = await saveConfigSnapshot({ dir, kind: 'manual', reason: 'test', sections, state: state() });
96
+ const loaded = await loadConfigSnapshot(dir, meta.id);
97
+ const data = loaded.data.skills as { files: { data: Uint8Array }[] };
98
+ assert.ok(data.files[0]!.data instanceof Uint8Array, '必须是 Uint8Array,而不是 {0:0,1:1} 这种对象');
99
+ assert.deepEqual([...data.files[0]!.data], [...bytes]);
100
+ });
101
+
102
+ test('saveConfigSnapshot:写入 meta 与 data,totalBytes > 0,sections 升序', async (t) => {
103
+ const dir = await tmpDir(t, 'meta');
104
+ const sections = new Map<SectionId, ExportSection>([
105
+ ['ui', exported('ui', { v: 1 })],
106
+ ['settings', exported('settings', { v: 1 })],
107
+ ]);
108
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'auto-change', trigger: 'watcher', sections, state: state() });
109
+ assert.equal(meta.kind, 'auto');
110
+ assert.equal(meta.trigger, 'watcher');
111
+ assert.ok(meta.totalBytes > 0);
112
+ assert.deepEqual(meta.sections, ['settings', 'ui']);
113
+ const onDisk = JSON.parse(await fs.readFile(path.join(dir, meta.id, CONFIG_SNAPSHOT_FILE), 'utf8')) as { meta: { id: string } };
114
+ assert.equal(onDisk.meta.id, meta.id);
115
+ });
116
+
117
+ test('saveConfigSnapshot:id 冲突时自动避让,不覆盖既有快照', async (t) => {
118
+ const dir = await tmpDir(t, 'collision');
119
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
120
+ const fixed = (): string => 'fixed-id';
121
+ const first = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'a', sections, state: state(), idFactory: fixed });
122
+ const second = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'b', sections, state: state(), idFactory: fixed });
123
+ assert.equal(first.id, 'fixed-id');
124
+ assert.notEqual(second.id, first.id);
125
+ assert.equal((await readConfigSnapshotMeta(dir, 'fixed-id'))?.reason, 'a', '既有快照不得被覆盖');
126
+ });
127
+
128
+ test('saveConfigSnapshot:超出 maxBytes 抛错且不落盘', async (t) => {
129
+ const dir = await tmpDir(t, 'toolarge');
130
+ const sections = new Map<SectionId, ExportSection>([
131
+ ['settings', exported('settings', { blob: 'x'.repeat(4096) })],
132
+ ]);
133
+ await assert.rejects(
134
+ () => saveConfigSnapshot({ dir, kind: 'auto', reason: 'big', sections, state: state(), maxBytes: 512, idFactory: () => 'big-one' }),
135
+ /超出上限/,
136
+ );
137
+ const entries = await fs.readdir(dir);
138
+ assert.equal(entries.length, 0, '超限不得留下半个快照目录');
139
+ });
140
+
141
+ test('makeConfigSnapshotId:形如 YYYYMMDD-HHMMSS-xxxx 且可被 isValidSnapshotId 接受', () => {
142
+ const id = makeConfigSnapshotId(() => new Date('2026-09-13T07:08:09.000Z'));
143
+ assert.match(id, /^\d{8}-\d{6}-[a-z0-9]{4}$/);
144
+ });
145
+
146
+ /* ------------------------------------------------------------ 读取容错 */
147
+
148
+ test('readConfigSnapshotMeta:缺失 / 损坏 / 非法 id 一律 null', async (t) => {
149
+ const dir = await tmpDir(t, 'tolerant');
150
+ assert.equal(await readConfigSnapshotMeta(dir, 'nope'), null);
151
+ assert.equal(await readConfigSnapshotMeta(dir, '../escape'), null);
152
+ assert.equal(await readConfigSnapshotMeta(dir, ''), null);
153
+ await fs.mkdir(path.join(dir, 'broken'), { recursive: true });
154
+ await fs.writeFile(path.join(dir, 'broken', CONFIG_SNAPSHOT_FILE), '{ not json', 'utf8');
155
+ assert.equal(await readConfigSnapshotMeta(dir, 'broken'), null);
156
+ await fs.mkdir(path.join(dir, 'mismatch'), { recursive: true });
157
+ await fs.writeFile(path.join(dir, 'mismatch', CONFIG_SNAPSHOT_FILE), JSON.stringify({ meta: { id: 'other', createdAt: 'x', kind: 'auto' } }), 'utf8');
158
+ assert.equal(await readConfigSnapshotMeta(dir, 'mismatch'), null, '目录名与 meta.id 不一致必须拒绝');
159
+ });
160
+
161
+ test('loadConfigSnapshot:非法 id 抛可读错误,不越界读', async (t) => {
162
+ const dir = await tmpDir(t, 'badid');
163
+ await assert.rejects(() => loadConfigSnapshot(dir, '../../etc/passwd'), /非法配置快照 id/);
164
+ });
165
+
166
+ /*
167
+ * 回归:meta.state 存在但形状不对 → 整条丢弃。
168
+ * 一条被手改/截断的快照曾能让 statesEqual 抛 TypeError,进而使 status()/undo() 整体 500
169
+ * —— 用户连快照列表都看不见。而「state 缺失」的旧格式快照仍应放行(可见/可恢复)。
170
+ */
171
+ test('readConfigSnapshotMeta:meta.state 形状不对 → 丢弃;state 缺失(旧格式)→ 放行', async (t) => {
172
+ const dir = await tmpDir(t, 'statefield');
173
+ const write = async (id: string, meta: Record<string, unknown>): Promise<void> => {
174
+ await fs.mkdir(path.join(dir, id), { recursive: true });
175
+ await fs.writeFile(
176
+ path.join(dir, id, CONFIG_SNAPSHOT_FILE),
177
+ JSON.stringify({ meta: { id, createdAt: '2026-09-13T00:00:00.000Z', kind: 'auto', ...meta }, data: {} }),
178
+ 'utf8',
179
+ );
180
+ };
181
+
182
+ await write('corrupt-null', { state: null });
183
+ assert.equal(await readConfigSnapshotMeta(dir, 'corrupt-null'), null, 'state=null → 丢弃');
184
+
185
+ await write('corrupt-string', { state: 'oops' });
186
+ assert.equal(await readConfigSnapshotMeta(dir, 'corrupt-string'), null, 'state 非对象 → 丢弃');
187
+
188
+ await write('corrupt-nosections', { state: { capturedAt: 'x' } });
189
+ assert.equal(await readConfigSnapshotMeta(dir, 'corrupt-nosections'), null, 'state 缺 sections → 丢弃');
190
+
191
+ await write('legacy', {});
192
+ assert.notEqual(await readConfigSnapshotMeta(dir, 'legacy'), null, '无 state 的旧格式快照仍须放行');
193
+
194
+ await write('good', { state: { capturedAt: 'x', sections: [] } });
195
+ assert.notEqual(await readConfigSnapshotMeta(dir, 'good'), null, '合法 state 放行');
196
+ });
197
+
198
+ test('listConfigSnapshots:损坏条目跳过,不阻断其余,结果按 createdAt 倒序', async (t) => {
199
+ const dir = await tmpDir(t, 'list');
200
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
201
+ await saveConfigSnapshot({ dir, kind: 'auto', reason: 'old', sections, state: state(), idFactory: () => 'old' });
202
+ await saveConfigSnapshot({
203
+ dir, kind: 'auto', reason: 'new', sections,
204
+ state: state([['settings', 'h2']], '2026-09-13T05:00:00.000Z'), idFactory: () => 'new',
205
+ });
206
+ await fs.mkdir(path.join(dir, 'corrupt'), { recursive: true });
207
+ await fs.writeFile(path.join(dir, 'corrupt', CONFIG_SNAPSHOT_FILE), 'garbage', 'utf8');
208
+ const metas = await listConfigSnapshots(dir);
209
+ assert.deepEqual(metas.map((m) => m.id), ['new', 'old']);
210
+ });
211
+
212
+ /* ------------------------------------------------------------ 元数据更新与删除 */
213
+
214
+ test('updateConfigSnapshotMeta:只改元数据字段,分区数据不动', async (t) => {
215
+ const dir = await tmpDir(t, 'updatemeta');
216
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { keep: 1 })]]);
217
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
218
+ const updated = await updateConfigSnapshotMeta(dir, meta.id, { consumed: true, note: 'hello' });
219
+ assert.equal(updated?.consumed, true);
220
+ assert.equal(updated?.note, 'hello');
221
+ const loaded = await loadConfigSnapshot(dir, meta.id);
222
+ assert.deepEqual(loaded.data.settings, { keep: 1 });
223
+ assert.equal(loaded.consumed, true);
224
+ assert.equal(await updateConfigSnapshotMeta(dir, 'missing', { consumed: true }), null);
225
+ });
226
+
227
+ test('deleteConfigSnapshot:删除幂等,非法 id 抛错', async (t) => {
228
+ const dir = await tmpDir(t, 'delete');
229
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
230
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
231
+ assert.equal(await deleteConfigSnapshot(dir, meta.id), true);
232
+ assert.equal(await deleteConfigSnapshot(dir, meta.id), false, '再次删除幂等返回 false');
233
+ await assert.rejects(() => deleteConfigSnapshot(dir, '../x'), /非法配置快照 id/);
234
+ });
235
+
236
+ /* ------------------------------------------------------------ 保留清理 */
237
+
238
+ test('pruneConfigSnapshots:按 kind 分桶,各留最新 N 份;manual 默认永不清理', async (t) => {
239
+ const dir = await tmpDir(t, 'prune');
240
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
241
+ const mk = async (id: string, kind: 'auto' | 'pre-restore' | 'manual', createdAt: string): Promise<void> => {
242
+ await saveConfigSnapshot({ dir, kind, reason: id, sections, state: state([['settings', id]], createdAt), idFactory: () => id });
243
+ };
244
+ await mk('a1', 'auto', '2026-09-13T01:00:00.000Z');
245
+ await mk('a2', 'auto', '2026-09-13T02:00:00.000Z');
246
+ await mk('a3', 'auto', '2026-09-13T03:00:00.000Z');
247
+ await mk('p1', 'pre-restore', '2026-09-13T01:00:00.000Z');
248
+ await mk('p2', 'pre-restore', '2026-09-13T02:00:00.000Z');
249
+ await mk('m1', 'manual', '2026-09-13T01:00:00.000Z');
250
+ const removed = await pruneConfigSnapshots(dir, { keepAuto: 2, keepPreRestore: 1 });
251
+ assert.deepEqual(removed.sort(), ['a1', 'p1']);
252
+ const left = (await listConfigSnapshots(dir)).map((m) => m.id).sort();
253
+ assert.deepEqual(left, ['a2', 'a3', 'm1', 'p2']);
254
+ });
255
+
256
+ test('pruneConfigSnapshots:pinned 一律豁免,即使超出保留数', async (t) => {
257
+ const dir = await tmpDir(t, 'prunepin');
258
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
259
+ const mk = async (id: string, createdAt: string): Promise<void> => {
260
+ await saveConfigSnapshot({ dir, kind: 'auto', reason: id, sections, state: state([['settings', id]], createdAt), idFactory: () => id });
261
+ };
262
+ await mk('old-pinned', '2026-09-13T01:00:00.000Z');
263
+ await mk('mid', '2026-09-13T02:00:00.000Z');
264
+ await mk('new', '2026-09-13T03:00:00.000Z');
265
+ await updateConfigSnapshotMeta(dir, 'old-pinned', { pinned: true });
266
+ const removed = await pruneConfigSnapshots(dir, { keepAuto: 1 });
267
+ assert.deepEqual(removed, ['mid']);
268
+ const left = (await listConfigSnapshots(dir)).map((m) => m.id).sort();
269
+ assert.deepEqual(left, ['new', 'old-pinned']);
270
+ });
271
+
272
+ test('pruneConfigSnapshots:未超限时不删任何东西', async (t) => {
273
+ const dir = await tmpDir(t, 'prunenoop');
274
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
275
+ await saveConfigSnapshot({ dir, kind: 'auto', reason: 'one', sections, state: state(), idFactory: () => 'one' });
276
+ assert.deepEqual(await pruneConfigSnapshots(dir, { keepAuto: 5 }), []);
277
+ });
278
+
279
+ /* ------------------------------------------------------------ 回放 */
280
+
281
+ test('restoreConfigSnapshot:走 adapter 管线,Skip 项不执行', async (t) => {
282
+ const dir = await tmpDir(t, 'replay');
283
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
284
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
285
+ const adapter = replayAdapter('settings', {
286
+ items: [
287
+ { id: 'create-a', kind: 'Create' },
288
+ { id: 'skip-b', kind: 'Skip' },
289
+ { id: 'update-c', kind: 'Update' },
290
+ ],
291
+ });
292
+ const report = await restoreConfigSnapshot({ dir, id: meta.id, adapters: [adapter], ctx: ctx() });
293
+ assert.equal(report.ok, true);
294
+ assert.deepEqual(adapter.applied, ['create-a', 'update-c']);
295
+ assert.deepEqual(report.applied, ['settings:create-a', 'settings:update-c']);
296
+ assert.deepEqual(report.skipped, ['settings:skip-b']);
297
+ assert.deepEqual(report.failed, []);
298
+ });
299
+
300
+ test('restoreConfigSnapshot:分区校验失败只跳过该分区,其余照常回放', async (t) => {
301
+ const dir = await tmpDir(t, 'replayinvalid');
302
+ const sections = new Map<SectionId, ExportSection>([
303
+ ['settings', exported('settings', { v: 1 })],
304
+ ['ui', exported('ui', { v: 2 })],
305
+ ]);
306
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
307
+ const bad = replayAdapter('settings', { invalid: true, items: [{ id: 'x', kind: 'Create' }] });
308
+ const good = replayAdapter('ui', { items: [{ id: 'y', kind: 'Create' }] });
309
+ const report = await restoreConfigSnapshot({ dir, id: meta.id, adapters: [bad, good], ctx: ctx() });
310
+ assert.equal(report.invalidSections.length, 1);
311
+ assert.equal(report.invalidSections[0]!.section, 'settings');
312
+ assert.deepEqual(good.applied, ['y'], '另一分区必须照常回放');
313
+ assert.deepEqual(report.applied, ['ui:y']);
314
+ });
315
+
316
+ test('restoreConfigSnapshot:applyItem 失败如实计入 failed 且 ok=false,不抛', async (t) => {
317
+ const dir = await tmpDir(t, 'replayfail');
318
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
319
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
320
+ const adapter = replayAdapter('settings', { items: [{ id: 'boom', kind: 'Update' }], applyFails: true });
321
+ const report = await restoreConfigSnapshot({ dir, id: meta.id, adapters: [adapter], ctx: ctx() });
322
+ assert.equal(report.ok, false);
323
+ assert.equal(report.failed.length, 1);
324
+ assert.match(report.failed[0]!.reason, /apply boom/);
325
+ });
326
+
327
+ test('restoreConfigSnapshot:only 限制回放分区', async (t) => {
328
+ const dir = await tmpDir(t, 'replayonly');
329
+ const sections = new Map<SectionId, ExportSection>([
330
+ ['settings', exported('settings', { v: 1 })],
331
+ ['ui', exported('ui', { v: 1 })],
332
+ ]);
333
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
334
+ const settings = replayAdapter('settings', { items: [{ id: 'a', kind: 'Create' }] });
335
+ const ui = replayAdapter('ui', { items: [{ id: 'b', kind: 'Create' }] });
336
+ const report = await restoreConfigSnapshot({ dir, id: meta.id, adapters: [settings, ui], ctx: ctx(), only: ['ui'] });
337
+ assert.deepEqual(settings.applied, []);
338
+ assert.deepEqual(ui.applied, ['b']);
339
+ assert.deepEqual(report.applied, ['ui:b']);
340
+ });
341
+
342
+ test('restoreConfigSnapshot:APPLY 顺序按 applyOrder 排定(副作用大的置后)', async (t) => {
343
+ const dir = await tmpDir(t, 'replayorder');
344
+ const sections = new Map<SectionId, ExportSection>([
345
+ ['settings', exported('settings', { v: 1 })],
346
+ ['plugins', exported('plugins', { v: 1 })],
347
+ ]);
348
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
349
+ const order: string[] = [];
350
+ const mk = (id: SectionId): ConfigAdapter => {
351
+ const a = replayAdapter(id, { items: [{ id: `${id}-item`, kind: 'Create' }] });
352
+ const original = a.applyItem.bind(a);
353
+ return { ...a, async applyItem(item, c) { order.push(id); return original(item, c); } } as ConfigAdapter;
354
+ };
355
+ await restoreConfigSnapshot({
356
+ dir, id: meta.id, adapters: [mk('settings'), mk('plugins')], ctx: ctx(),
357
+ applyOrder: ['settings', 'plugins'],
358
+ });
359
+ assert.deepEqual(order, ['settings', 'plugins']);
360
+ });
361
+
362
+ test('toUndoCandidate:只为 undo 规划暴露最小字段', async (t) => {
363
+ const dir = await tmpDir(t, 'candidate');
364
+ const sections = new Map<SectionId, ExportSection>([['settings', exported('settings', { v: 1 })]]);
365
+ const meta = await saveConfigSnapshot({ dir, kind: 'auto', reason: 'r', sections, state: state() });
366
+ const candidate = toUndoCandidate(meta);
367
+ assert.deepEqual(Object.keys(candidate).sort(), ['consumed', 'createdAt', 'id', 'kind', 'state']);
368
+ assert.equal(candidate.id, meta.id);
369
+ });