@herbertgao/pi-extensions 2026.8.8 → 2026.8.10

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 (266) hide show
  1. package/README.md +6 -3
  2. package/node_modules/@czottmann/pi-automode/CHANGELOG.md +24 -0
  3. package/node_modules/@czottmann/pi-automode/README.md +78 -112
  4. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +18 -14
  5. package/node_modules/@czottmann/pi-automode/docs/adr/ADR-001-permission-precedence-and-trust-boundaries.md +44 -0
  6. package/node_modules/@czottmann/pi-automode/docs/adr/INDEX.md +5 -0
  7. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +198 -104
  8. package/node_modules/@czottmann/pi-automode/docs/configuration.md +155 -0
  9. package/node_modules/@czottmann/pi-automode/docs/defaults.md +55 -14
  10. package/node_modules/@czottmann/pi-automode/docs/diagnostics.md +90 -0
  11. package/node_modules/@czottmann/pi-automode/docs/observability-logging.md +61 -26
  12. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +5 -0
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +172 -18
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +127 -30
  15. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +3 -0
  16. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +273 -31
  17. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/log.ts +60 -5
  18. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +92 -8
  19. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +203 -13
  20. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/state.ts +1 -0
  21. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +11 -0
  22. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/utils.ts +9 -1
  23. package/node_modules/@czottmann/pi-automode/package.json +9 -2
  24. package/node_modules/@czottmann/pi-automode/skills/automode-diagnostics/SKILL.md +63 -0
  25. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +3 -3
  26. package/node_modules/@herbertgao/pi-cc-extensions/README.md +3 -3
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +8 -3
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +56 -29
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +15 -13
  31. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  32. package/node_modules/@herbertgao/pi-handoff/CHANGELOG.md +29 -0
  33. package/node_modules/@herbertgao/pi-handoff/README.md +15 -18
  34. package/node_modules/@herbertgao/pi-handoff/package.json +11 -4
  35. package/node_modules/@herbertgao/pi-handoff/src/complete-text.ts +33 -0
  36. package/node_modules/@herbertgao/pi-handoff/src/index.ts +127 -232
  37. package/node_modules/@herbertgao/pi-handoff/src/session-query.ts +15 -30
  38. package/node_modules/@herbertgao/pi-mermaid-open/CHANGELOG.md +14 -0
  39. package/node_modules/@herbertgao/pi-mermaid-open/README.md +21 -4
  40. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/herdr-plugin.toml +11 -0
  41. package/node_modules/@herbertgao/pi-mermaid-open/herdr-plugin/viewer.mjs +305 -0
  42. package/node_modules/@herbertgao/pi-mermaid-open/package.json +15 -4
  43. package/node_modules/@herbertgao/pi-mermaid-open/src/index.ts +351 -101
  44. package/node_modules/@herbertgao/pi-preferred-thinking/CHANGELOG.md +12 -0
  45. package/node_modules/@herbertgao/pi-preferred-thinking/README.md +30 -14
  46. package/node_modules/@herbertgao/pi-preferred-thinking/package.json +7 -4
  47. package/node_modules/@herbertgao/pi-preferred-thinking/src/index.ts +140 -85
  48. package/node_modules/@herbertgao/pi-recap/CHANGELOG.md +12 -0
  49. package/node_modules/@herbertgao/pi-recap/README.md +5 -3
  50. package/node_modules/@herbertgao/pi-recap/package.json +9 -3
  51. package/node_modules/@herbertgao/pi-recap/src/index.ts +3 -7
  52. package/node_modules/@herbertgao/pi-recap/src/model-picker.ts +6 -19
  53. package/node_modules/@herbertgao/pi-recap/src/models.ts +9 -21
  54. package/node_modules/@herbertgao/pi-recap/src/tui.ts +1 -1
  55. package/node_modules/@herbertgao/pi-rename/CHANGELOG.md +49 -0
  56. package/node_modules/@herbertgao/pi-rename/README.md +13 -11
  57. package/node_modules/@herbertgao/pi-rename/package.json +13 -3
  58. package/node_modules/@herbertgao/pi-rename/src/index.ts +135 -59
  59. package/node_modules/@herbertgao/pi-rename/src/models.ts +5 -13
  60. package/node_modules/@herbertgao/pi-rename/src/naming.ts +2 -6
  61. package/node_modules/@herbertgao/pi-stash/package.json +3 -3
  62. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  63. package/node_modules/@herbertgao/pi-subagents/README.md +16 -3
  64. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  65. package/node_modules/@herbertgao/pi-subagents/src/cross-extension-rpc.ts +15 -4
  66. package/node_modules/@herbertgao/pi-subagents/src/index.ts +16 -0
  67. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +5 -1
  68. package/node_modules/pi-web-access/CHANGELOG.md +13 -0
  69. package/node_modules/pi-web-access/README.md +2 -2
  70. package/node_modules/pi-web-access/chrome-cookies.ts +12 -7
  71. package/node_modules/pi-web-access/gemini-search.ts +28 -10
  72. package/node_modules/pi-web-access/index.ts +24 -10
  73. package/node_modules/pi-web-access/package.json +1 -1
  74. package/node_modules/resume-from/LICENSE +21 -0
  75. package/node_modules/resume-from/README.md +161 -0
  76. package/node_modules/resume-from/dist/adapters/claude-code/adapter.d.ts +25 -0
  77. package/node_modules/resume-from/dist/adapters/claude-code/adapter.js +60 -0
  78. package/node_modules/resume-from/dist/adapters/claude-code/contract.d.ts +8 -0
  79. package/node_modules/resume-from/dist/adapters/claude-code/contract.js +4 -0
  80. package/node_modules/resume-from/dist/adapters/claude-code/entries.d.ts +30 -0
  81. package/node_modules/resume-from/dist/adapters/claude-code/entries.js +107 -0
  82. package/node_modules/resume-from/dist/adapters/claude-code/index.d.ts +3 -0
  83. package/node_modules/resume-from/dist/adapters/claude-code/index.js +2 -0
  84. package/node_modules/resume-from/dist/adapters/claude-code/layout.d.ts +29 -0
  85. package/node_modules/resume-from/dist/adapters/claude-code/layout.js +80 -0
  86. package/node_modules/resume-from/dist/adapters/claude-code/readback.d.ts +9 -0
  87. package/node_modules/resume-from/dist/adapters/claude-code/readback.js +39 -0
  88. package/node_modules/resume-from/dist/adapters/claude-code/reader.d.ts +37 -0
  89. package/node_modules/resume-from/dist/adapters/claude-code/reader.js +415 -0
  90. package/node_modules/resume-from/dist/adapters/claude-code/redaction.d.ts +26 -0
  91. package/node_modules/resume-from/dist/adapters/claude-code/redaction.js +132 -0
  92. package/node_modules/resume-from/dist/adapters/claude-code/validation.d.ts +4 -0
  93. package/node_modules/resume-from/dist/adapters/claude-code/validation.js +39 -0
  94. package/node_modules/resume-from/dist/adapters/claude-code/writer.d.ts +23 -0
  95. package/node_modules/resume-from/dist/adapters/claude-code/writer.js +306 -0
  96. package/node_modules/resume-from/dist/adapters/codex/contract.d.ts +15 -0
  97. package/node_modules/resume-from/dist/adapters/codex/contract.js +4 -0
  98. package/node_modules/resume-from/dist/adapters/codex/index.d.ts +6 -0
  99. package/node_modules/resume-from/dist/adapters/codex/index.js +44 -0
  100. package/node_modules/resume-from/dist/adapters/codex/read.d.ts +28 -0
  101. package/node_modules/resume-from/dist/adapters/codex/read.js +270 -0
  102. package/node_modules/resume-from/dist/adapters/codex/readback.d.ts +9 -0
  103. package/node_modules/resume-from/dist/adapters/codex/readback.js +47 -0
  104. package/node_modules/resume-from/dist/adapters/codex/redaction.d.ts +26 -0
  105. package/node_modules/resume-from/dist/adapters/codex/redaction.js +132 -0
  106. package/node_modules/resume-from/dist/adapters/codex/rollout.d.ts +75 -0
  107. package/node_modules/resume-from/dist/adapters/codex/rollout.js +185 -0
  108. package/node_modules/resume-from/dist/adapters/codex/validation.d.ts +7 -0
  109. package/node_modules/resume-from/dist/adapters/codex/validation.js +78 -0
  110. package/node_modules/resume-from/dist/adapters/codex/write.d.ts +17 -0
  111. package/node_modules/resume-from/dist/adapters/codex/write.js +141 -0
  112. package/node_modules/resume-from/dist/adapters/contract.d.ts +74 -0
  113. package/node_modules/resume-from/dist/adapters/contract.js +4 -0
  114. package/node_modules/resume-from/dist/adapters/pi/adapter.d.ts +15 -0
  115. package/node_modules/resume-from/dist/adapters/pi/adapter.js +209 -0
  116. package/node_modules/resume-from/dist/adapters/pi/contract.d.ts +24 -0
  117. package/node_modules/resume-from/dist/adapters/pi/contract.js +4 -0
  118. package/node_modules/resume-from/dist/adapters/pi/format.d.ts +162 -0
  119. package/node_modules/resume-from/dist/adapters/pi/format.js +223 -0
  120. package/node_modules/resume-from/dist/adapters/pi/index.d.ts +7 -0
  121. package/node_modules/resume-from/dist/adapters/pi/index.js +10 -0
  122. package/node_modules/resume-from/dist/adapters/pi/parse.d.ts +41 -0
  123. package/node_modules/resume-from/dist/adapters/pi/parse.js +371 -0
  124. package/node_modules/resume-from/dist/adapters/pi/redaction.d.ts +26 -0
  125. package/node_modules/resume-from/dist/adapters/pi/redaction.js +132 -0
  126. package/node_modules/resume-from/dist/adapters/pi/serialize.d.ts +22 -0
  127. package/node_modules/resume-from/dist/adapters/pi/serialize.js +87 -0
  128. package/node_modules/resume-from/dist/adapters/pi/validate.d.ts +14 -0
  129. package/node_modules/resume-from/dist/adapters/pi/validate.js +134 -0
  130. package/node_modules/resume-from/dist/bin.d.ts +35 -0
  131. package/node_modules/resume-from/dist/bin.js +135 -0
  132. package/node_modules/resume-from/dist/contract.d.ts +10 -0
  133. package/node_modules/resume-from/dist/contract.js +4 -0
  134. package/node_modules/resume-from/dist/host/agents.d.ts +29 -0
  135. package/node_modules/resume-from/dist/host/agents.js +20 -0
  136. package/node_modules/resume-from/dist/host/cli/args.d.ts +21 -0
  137. package/node_modules/resume-from/dist/host/cli/args.js +187 -0
  138. package/node_modules/resume-from/dist/host/cli/contract.d.ts +27 -0
  139. package/node_modules/resume-from/dist/host/cli/contract.js +4 -0
  140. package/node_modules/resume-from/dist/host/cli/index.d.ts +2 -0
  141. package/node_modules/resume-from/dist/host/cli/index.js +1 -0
  142. package/node_modules/resume-from/dist/host/cli/presentation.d.ts +2 -0
  143. package/node_modules/resume-from/dist/host/cli/presentation.js +11 -0
  144. package/node_modules/resume-from/dist/host/cli/render.d.ts +3 -0
  145. package/node_modules/resume-from/dist/host/cli/render.js +70 -0
  146. package/node_modules/resume-from/dist/host/cli/runner.d.ts +8 -0
  147. package/node_modules/resume-from/dist/host/cli/runner.js +125 -0
  148. package/node_modules/resume-from/dist/host/contract.d.ts +42 -0
  149. package/node_modules/resume-from/dist/host/contract.js +4 -0
  150. package/node_modules/resume-from/dist/host/entry.d.ts +52 -0
  151. package/node_modules/resume-from/dist/host/entry.js +91 -0
  152. package/node_modules/resume-from/dist/host/index.d.ts +11 -0
  153. package/node_modules/resume-from/dist/host/index.js +10 -0
  154. package/node_modules/resume-from/dist/host/pi-extension/command.d.ts +18 -0
  155. package/node_modules/resume-from/dist/host/pi-extension/command.js +103 -0
  156. package/node_modules/resume-from/dist/host/pi-extension/contract.d.ts +34 -0
  157. package/node_modules/resume-from/dist/host/pi-extension/contract.js +4 -0
  158. package/node_modules/resume-from/dist/host/pi-extension/index.d.ts +6 -0
  159. package/node_modules/resume-from/dist/host/pi-extension/index.js +4 -0
  160. package/node_modules/resume-from/dist/host/pi-extension/picker.d.ts +16 -0
  161. package/node_modules/resume-from/dist/host/pi-extension/picker.js +35 -0
  162. package/node_modules/resume-from/dist/host/pi-extension/presentation.d.ts +2 -0
  163. package/node_modules/resume-from/dist/host/pi-extension/presentation.js +11 -0
  164. package/node_modules/resume-from/dist/host/pi-extension/register.d.ts +25 -0
  165. package/node_modules/resume-from/dist/host/pi-extension/register.js +11 -0
  166. package/node_modules/resume-from/dist/host/pi-extension/ui.d.ts +20 -0
  167. package/node_modules/resume-from/dist/host/pi-extension/ui.js +1 -0
  168. package/node_modules/resume-from/dist/host/profile.d.ts +8 -0
  169. package/node_modules/resume-from/dist/host/profile.js +31 -0
  170. package/node_modules/resume-from/dist/host/registry.d.ts +8 -0
  171. package/node_modules/resume-from/dist/host/registry.js +33 -0
  172. package/node_modules/resume-from/dist/host/wiring.d.ts +41 -0
  173. package/node_modules/resume-from/dist/host/wiring.js +67 -0
  174. package/node_modules/resume-from/dist/import/confirmation.d.ts +6 -0
  175. package/node_modules/resume-from/dist/import/confirmation.js +27 -0
  176. package/node_modules/resume-from/dist/import/contract.d.ts +41 -0
  177. package/node_modules/resume-from/dist/import/contract.js +4 -0
  178. package/node_modules/resume-from/dist/import/discovery/contract.d.ts +58 -0
  179. package/node_modules/resume-from/dist/import/discovery/contract.js +4 -0
  180. package/node_modules/resume-from/dist/import/discovery/errors.d.ts +9 -0
  181. package/node_modules/resume-from/dist/import/discovery/errors.js +12 -0
  182. package/node_modules/resume-from/dist/import/discovery/finder.d.ts +10 -0
  183. package/node_modules/resume-from/dist/import/discovery/finder.js +129 -0
  184. package/node_modules/resume-from/dist/import/discovery/homes.d.ts +23 -0
  185. package/node_modules/resume-from/dist/import/discovery/homes.js +83 -0
  186. package/node_modules/resume-from/dist/import/discovery/index.d.ts +2 -0
  187. package/node_modules/resume-from/dist/import/discovery/index.js +2 -0
  188. package/node_modules/resume-from/dist/import/discovery/ordering.d.ts +3 -0
  189. package/node_modules/resume-from/dist/import/discovery/ordering.js +21 -0
  190. package/node_modules/resume-from/dist/import/errors.d.ts +22 -0
  191. package/node_modules/resume-from/dist/import/errors.js +20 -0
  192. package/node_modules/resume-from/dist/import/index.d.ts +3 -0
  193. package/node_modules/resume-from/dist/import/index.js +5 -0
  194. package/node_modules/resume-from/dist/import/landing/contract.d.ts +44 -0
  195. package/node_modules/resume-from/dist/import/landing/contract.js +4 -0
  196. package/node_modules/resume-from/dist/import/landing/errors.d.ts +15 -0
  197. package/node_modules/resume-from/dist/import/landing/errors.js +15 -0
  198. package/node_modules/resume-from/dist/import/landing/handover.d.ts +5 -0
  199. package/node_modules/resume-from/dist/import/landing/handover.js +12 -0
  200. package/node_modules/resume-from/dist/import/landing/index.d.ts +4 -0
  201. package/node_modules/resume-from/dist/import/landing/index.js +4 -0
  202. package/node_modules/resume-from/dist/import/landing/lander.d.ts +7 -0
  203. package/node_modules/resume-from/dist/import/landing/lander.js +133 -0
  204. package/node_modules/resume-from/dist/import/landing/marker.d.ts +9 -0
  205. package/node_modules/resume-from/dist/import/landing/marker.js +46 -0
  206. package/node_modules/resume-from/dist/import/pipeline.d.ts +17 -0
  207. package/node_modules/resume-from/dist/import/pipeline.js +134 -0
  208. package/node_modules/resume-from/dist/import/preview/builder.d.ts +6 -0
  209. package/node_modules/resume-from/dist/import/preview/builder.js +76 -0
  210. package/node_modules/resume-from/dist/import/preview/contract.d.ts +39 -0
  211. package/node_modules/resume-from/dist/import/preview/contract.js +4 -0
  212. package/node_modules/resume-from/dist/import/preview/format.d.ts +19 -0
  213. package/node_modules/resume-from/dist/import/preview/format.js +45 -0
  214. package/node_modules/resume-from/dist/import/preview/index.d.ts +2 -0
  215. package/node_modules/resume-from/dist/import/preview/index.js +1 -0
  216. package/node_modules/resume-from/dist/import/preview/warnings.d.ts +11 -0
  217. package/node_modules/resume-from/dist/import/preview/warnings.js +80 -0
  218. package/node_modules/resume-from/dist/import/transfer/contract.d.ts +48 -0
  219. package/node_modules/resume-from/dist/import/transfer/contract.js +4 -0
  220. package/node_modules/resume-from/dist/import/transfer/index.d.ts +2 -0
  221. package/node_modules/resume-from/dist/import/transfer/index.js +1 -0
  222. package/node_modules/resume-from/dist/import/transfer/rules.d.ts +3 -0
  223. package/node_modules/resume-from/dist/import/transfer/rules.js +228 -0
  224. package/node_modules/resume-from/dist/import/wiring.d.ts +24 -0
  225. package/node_modules/resume-from/dist/import/wiring.js +27 -0
  226. package/node_modules/resume-from/dist/index.d.ts +36 -0
  227. package/node_modules/resume-from/dist/index.js +36 -0
  228. package/node_modules/resume-from/dist/platform/config/contract.d.ts +35 -0
  229. package/node_modules/resume-from/dist/platform/config/contract.js +4 -0
  230. package/node_modules/resume-from/dist/platform/config/defaults.d.ts +16 -0
  231. package/node_modules/resume-from/dist/platform/config/defaults.js +22 -0
  232. package/node_modules/resume-from/dist/platform/config/errors.d.ts +9 -0
  233. package/node_modules/resume-from/dist/platform/config/errors.js +12 -0
  234. package/node_modules/resume-from/dist/platform/config/index.d.ts +5 -0
  235. package/node_modules/resume-from/dist/platform/config/index.js +6 -0
  236. package/node_modules/resume-from/dist/platform/config/loader.d.ts +10 -0
  237. package/node_modules/resume-from/dist/platform/config/loader.js +47 -0
  238. package/node_modules/resume-from/dist/platform/config/paths.d.ts +13 -0
  239. package/node_modules/resume-from/dist/platform/config/paths.js +37 -0
  240. package/node_modules/resume-from/dist/platform/config/validate.d.ts +9 -0
  241. package/node_modules/resume-from/dist/platform/config/validate.js +117 -0
  242. package/node_modules/resume-from/dist/platform/repo/contract.d.ts +30 -0
  243. package/node_modules/resume-from/dist/platform/repo/contract.js +4 -0
  244. package/node_modules/resume-from/dist/platform/repo/git.d.ts +21 -0
  245. package/node_modules/resume-from/dist/platform/repo/git.js +74 -0
  246. package/node_modules/resume-from/dist/platform/repo/index.d.ts +2 -0
  247. package/node_modules/resume-from/dist/platform/repo/index.js +1 -0
  248. package/node_modules/resume-from/dist/platform/repo/reader.d.ts +8 -0
  249. package/node_modules/resume-from/dist/platform/repo/reader.js +130 -0
  250. package/node_modules/resume-from/dist/platform/store/contract.d.ts +31 -0
  251. package/node_modules/resume-from/dist/platform/store/contract.js +4 -0
  252. package/node_modules/resume-from/dist/platform/store/file-committer.d.ts +3 -0
  253. package/node_modules/resume-from/dist/platform/store/file-committer.js +391 -0
  254. package/node_modules/resume-from/dist/platform/store/index.d.ts +2 -0
  255. package/node_modules/resume-from/dist/platform/store/index.js +2 -0
  256. package/node_modules/resume-from/dist/platform/tokens/contract.d.ts +12 -0
  257. package/node_modules/resume-from/dist/platform/tokens/contract.js +4 -0
  258. package/node_modules/resume-from/dist/platform/tokens/estimator.d.ts +12 -0
  259. package/node_modules/resume-from/dist/platform/tokens/estimator.js +111 -0
  260. package/node_modules/resume-from/dist/platform/tokens/index.d.ts +2 -0
  261. package/node_modules/resume-from/dist/platform/tokens/index.js +1 -0
  262. package/node_modules/resume-from/dist/session/contract.d.ts +109 -0
  263. package/node_modules/resume-from/dist/session/contract.js +4 -0
  264. package/node_modules/resume-from/package.json +80 -0
  265. package/node_modules/resume-from/shims/pi/extensions/resume-from.js +118 -0
  266. package/package.json +19 -14
@@ -0,0 +1,48 @@
1
+ import type { ImportConfig } from "../../platform/config/contract.js";
2
+ export type { ImportConfig };
3
+ import type { TokenEstimator } from "../../platform/tokens/contract.js";
4
+ export type { TokenEstimator };
5
+ import type { AgentId, CanonicalSession, CanonicalTurn, HomePath, RepoSnapshot, SessionId, SessionRef, SourceProvenance, TargetProfile, ToolCallRecord, ToolEffect, TurnKind, TurnRole } from "../../session/contract.js";
6
+ export type { AgentId, CanonicalSession, CanonicalTurn, HomePath, RepoSnapshot, SessionId, SessionRef, SourceProvenance, TargetProfile, ToolCallRecord, ToolEffect, TurnKind, TurnRole, };
7
+ /** Why a turn did not cross over (FR-31, FR-54). */
8
+ export type DropReason = "budget" | "broken-tail";
9
+ /** Why a turn is kept whatever the budget says (FR-32). */
10
+ export type PinReason = "first-request" | "recent-turn" | "summary" | "changed-files";
11
+ /** One turn the rules removed. */
12
+ export interface TurnDrop {
13
+ /** The turn's index in the source session. */
14
+ index: number;
15
+ reason: DropReason;
16
+ }
17
+ /** One turn the rules protected. */
18
+ export interface TurnPin {
19
+ /** The turn's index in the source session. */
20
+ index: number;
21
+ reason: PinReason;
22
+ }
23
+ /** The result of applying requirement sections D and E to one source session. */
24
+ export interface TransferPlan {
25
+ target: TargetProfile;
26
+ /** The source metadata, unchanged. */
27
+ provenance: SourceProvenance;
28
+ /** Only the turns that cross over, in source order. */
29
+ turns: CanonicalTurn[];
30
+ pins: TurnPin[];
31
+ drops: TurnDrop[];
32
+ keptTurnCount: number;
33
+ droppedTurnCount: number;
34
+ /** Tool result bodies the rules removed (FR-24, FR-25). */
35
+ bodiesDropped: number;
36
+ /** Estimated cost of the kept turns, in tokens. */
37
+ estimatedTokens: number;
38
+ /** budgetShare multiplied by the target window, rounded down (FR-29, FR-30). */
39
+ budgetTokens: number;
40
+ /** True when an incomplete trailing tool call was removed (FR-54, FR-55). */
41
+ brokenTailDropped: boolean;
42
+ /** Set when the pinned content alone exceeds the budget. The import cannot run (FR-33). */
43
+ blockedReason: string | null;
44
+ }
45
+ /** Applies requirement sections D and E. Pure: the same input always gives the same plan. */
46
+ export interface TransferRules {
47
+ apply(session: CanonicalSession, target: TargetProfile, config: ImportConfig, estimator: TokenEstimator): TransferPlan;
48
+ }
@@ -0,0 +1,4 @@
1
+ // GENERATED from src/import/transfer/module.md — the Public Contract section is the normative home.
2
+ // Declarations only: no behaviour, no defaults. If this file and module.md disagree,
3
+ // the document wins and this file is corrected.
4
+ export {};
@@ -0,0 +1,2 @@
1
+ export type { DropReason, PinReason, TransferPlan, TransferRules, TurnDrop, TurnPin, } from "./contract.js";
2
+ export { createTransferRules } from "./rules.js";
@@ -0,0 +1 @@
1
+ export { createTransferRules } from "./rules.js";
@@ -0,0 +1,3 @@
1
+ import type { TransferRules } from "./contract.js";
2
+ /** The rules hold no state, so every instance behaves identically (T-TRA-19). */
3
+ export declare function createTransferRules(): TransferRules;
@@ -0,0 +1,228 @@
1
+ // Requirement sections D and E, as executable rules. Pure: no clock, no filesystem,
2
+ // no network, no randomness — the preview and the commit of one request must agree.
3
+ /** FR-25. The record ends with this, so the target model reads that the body is gone. */
4
+ const DROPPED_BODY_MARKER = "(content dropped: imported session, may be stale)";
5
+ /** FR-23. One line, in words, when the source recorded no outcome at all. */
6
+ const NO_OUTCOME_RECORDED = "(outcome not recorded by the source)";
7
+ /** FR-33. The one advice sentence every budget-driven block ends with. */
8
+ const BUDGET_ADVICE = "Raise budgetShare, lower pinnedRecentTurns, or choose a target with a larger window.";
9
+ /** Role/kind delimiters and message framing that target serializers add around every turn. */
10
+ const TURN_FRAMING_TOKENS = 4;
11
+ /** The order pins are reported in when one turn is pinned for more than one reason. */
12
+ const PIN_ORDER = [
13
+ "first-request",
14
+ "recent-turn",
15
+ "summary",
16
+ "changed-files",
17
+ ];
18
+ /** FR-23: one line about the outcome. Line breaks and blank lines collapse to single spaces. */
19
+ function toSingleLine(text) {
20
+ return text
21
+ .split(/\r?\n/)
22
+ .map((line) => line.trim())
23
+ .filter((line) => line !== "")
24
+ .join(" ");
25
+ }
26
+ function normalizeRecord(record) {
27
+ const outcome = toSingleLine(record.outcomeLine);
28
+ const stated = outcome === "" ? NO_OUTCOME_RECORDED : outcome;
29
+ return {
30
+ toolName: record.toolName,
31
+ argumentsText: record.argumentsText,
32
+ // Append only when the adapter did not already embed it (Pi, Claude Code embed it;
33
+ // Codex does not). toSingleLine runs first so trailing whitespace cannot defeat endsWith.
34
+ outcomeLine: record.bodyDropped && !stated.endsWith(DROPPED_BODY_MARKER)
35
+ ? `${stated} ${DROPPED_BODY_MARKER}`
36
+ : stated,
37
+ effect: record.effect,
38
+ bodyDropped: record.bodyDropped,
39
+ ...(record.resultRecorded !== undefined ? { resultRecorded: record.resultRecorded } : {}),
40
+ };
41
+ }
42
+ /**
43
+ * Rebuilt field by field, never copied by reference or spread: a field the canonical
44
+ * vocabulary has no room for — hidden reasoning, a system prompt, an environment block,
45
+ * telemetry, vendor state, a smuggled result body — must not survive into the plan (FR-28).
46
+ */
47
+ function normalizeTurn(turn) {
48
+ return {
49
+ index: turn.index,
50
+ role: turn.role,
51
+ kind: turn.kind,
52
+ text: turn.text,
53
+ toolCall: turn.toolCall ? normalizeRecord(turn.toolCall) : null,
54
+ timestamp: turn.timestamp,
55
+ };
56
+ }
57
+ /** FR-54: a tool call the source never recorded a result for. */
58
+ function isUnansweredCall(turn) {
59
+ if (turn?.kind !== "tool-call")
60
+ return false;
61
+ // Check resultRecorded directly; outcomeLine is always non-empty (adapters fill it with a
62
+ // fallback text), so an outcomeLine=="" check would be dead and never catch real broken tails.
63
+ //
64
+ // Use === false, not !resultRecorded: an absent flag (undefined) means "unknown, treat as
65
+ // recorded" — construction sites outside this module may omit the field (e.g. test fixtures),
66
+ // and we must not silently break tails that were never broken. Only an explicit false fires. (FR-54)
67
+ return !turn.toolCall || turn.toolCall.resultRecorded === false;
68
+ }
69
+ /** Adapters understand their native tool schema; this layer only normalizes their path list. */
70
+ function dedupeChangedPaths(recorded) {
71
+ const paths = [];
72
+ const seen = new Set();
73
+ for (const path of recorded) {
74
+ if (path !== "" && !seen.has(path)) {
75
+ seen.add(path);
76
+ paths.push(path);
77
+ }
78
+ }
79
+ return paths;
80
+ }
81
+ /** FR-32. A turn can be pinned for more than one reason; every reason is reported. */
82
+ function computePins(turns, pinnedRecentTurns) {
83
+ const reasons = new Map();
84
+ const pin = (index, reason) => {
85
+ const existing = reasons.get(index);
86
+ if (existing)
87
+ existing.add(reason);
88
+ else
89
+ reasons.set(index, new Set([reason]));
90
+ };
91
+ const firstRequest = turns.find((turn) => turn.role === "user" && turn.kind === "message");
92
+ if (firstRequest)
93
+ pin(firstRequest.index, "first-request");
94
+ const recent = Math.min(Math.max(0, Math.trunc(pinnedRecentTurns)), turns.length);
95
+ for (const turn of turns.slice(turns.length - recent))
96
+ pin(turn.index, "recent-turn");
97
+ for (const turn of turns) {
98
+ if (turn.kind === "summary")
99
+ pin(turn.index, "summary");
100
+ if (turn.toolCall?.effect === "mutating")
101
+ pin(turn.index, "changed-files");
102
+ }
103
+ const pins = [];
104
+ for (const turn of turns) {
105
+ const set = reasons.get(turn.index);
106
+ if (!set)
107
+ continue;
108
+ reasons.delete(turn.index);
109
+ for (const reason of PIN_ORDER) {
110
+ if (set.has(reason))
111
+ pins.push({ index: turn.index, reason });
112
+ }
113
+ }
114
+ return pins;
115
+ }
116
+ /** The cost of what actually ships for this turn: its text, or its record's three fields. */
117
+ function turnCost(turn, estimator) {
118
+ const record = turn.toolCall;
119
+ const text = record
120
+ ? `${turn.text}\n${record.toolName}\n${record.argumentsText}\n${record.outcomeLine}`
121
+ : turn.text;
122
+ return estimator.estimate(text) + TURN_FRAMING_TOKENS;
123
+ }
124
+ function copyProvenance(provenance, changedPaths) {
125
+ return {
126
+ ref: {
127
+ agent: provenance.ref.agent,
128
+ home: provenance.ref.home,
129
+ id: provenance.ref.id,
130
+ },
131
+ title: provenance.title,
132
+ startedAt: provenance.startedAt,
133
+ updatedAt: provenance.updatedAt,
134
+ repo: {
135
+ commit: provenance.repo.commit,
136
+ branch: provenance.repo.branch,
137
+ changedPaths,
138
+ },
139
+ };
140
+ }
141
+ function apply(session, target, config, estimator) {
142
+ const source = session.turns;
143
+ const drops = [];
144
+ // 1. Drop the broken tail (FR-54, FR-55): every consecutive trailing call the source never
145
+ // answered — an interrupted parallel tool batch leaves several unanswered calls, not one.
146
+ let cut = source.length;
147
+ while (cut > 0 && isUnansweredCall(source[cut - 1])) {
148
+ const turn = source[cut - 1];
149
+ if (turn)
150
+ drops.push({ index: turn.index, reason: "broken-tail" });
151
+ cut -= 1;
152
+ }
153
+ const brokenTailDropped = cut < source.length;
154
+ // 2. Select content (FR-22 to FR-28). The record shape is what enforces FR-24.
155
+ const selected = source.slice(0, cut).map(normalizeTurn);
156
+ // 3. Compute the budget (FR-29, FR-30).
157
+ const budgetTokens = Math.floor(config.budgetShare * target.windowTokens);
158
+ // 4. Pin (FR-32).
159
+ const pins = computePins(selected, config.pinnedRecentTurns);
160
+ const pinned = new Set(pins.map((entry) => entry.index));
161
+ const costs = selected.map((turn) => turnCost(turn, estimator));
162
+ let estimatedTokens = 0;
163
+ let pinnedTokens = 0;
164
+ for (const [position, turn] of selected.entries()) {
165
+ const cost = costs[position] ?? 0;
166
+ estimatedTokens += cost;
167
+ if (pinned.has(turn.index))
168
+ pinnedTokens += cost;
169
+ }
170
+ // 5. Stop if the pinned content alone exceeds the budget (FR-33).
171
+ let blockedReason = null;
172
+ const kept = selected.map(() => true);
173
+ if (pinnedTokens > budgetTokens) {
174
+ blockedReason =
175
+ `Pinned content needs ${pinnedTokens} tokens but the budget is ${budgetTokens} tokens ` +
176
+ `(budgetShare ${config.budgetShare} of a ${target.windowTokens}-token window). ` +
177
+ BUDGET_ADVICE;
178
+ }
179
+ else {
180
+ // 6. Drop the oldest unpinned turn until the estimate fits (FR-31, FR-34).
181
+ for (const [position, turn] of selected.entries()) {
182
+ if (estimatedTokens <= budgetTokens)
183
+ break;
184
+ if (pinned.has(turn.index))
185
+ continue;
186
+ kept[position] = false;
187
+ estimatedTokens -= costs[position] ?? 0;
188
+ drops.push({ index: turn.index, reason: "budget" });
189
+ }
190
+ }
191
+ // 7. Report (FR-17, FR-18, FR-35).
192
+ const turns = selected.filter((_, position) => kept[position]);
193
+ // 7a. Block a plan that would import nothing: a zero-turn result is an import that cannot
194
+ // succeed regardless of budget, and "Nothing to import" is the honest diagnosis (FR-17, FR-33).
195
+ // The cause determines the advice: budget-driven drops suggest raising the budget;
196
+ // an empty-content session (no budget drops) means the session itself has nothing to offer.
197
+ if (blockedReason === null && turns.length === 0) {
198
+ const budgetDriven = drops.some((d) => d.reason === "budget");
199
+ blockedReason = budgetDriven
200
+ ? `nothing to import — the budget removed all turns. ${BUDGET_ADVICE}`
201
+ : "nothing to import — the session has no importable content. Choose a different session.";
202
+ }
203
+ drops.sort((left, right) => left.index - right.index);
204
+ return {
205
+ target: {
206
+ agent: target.agent,
207
+ home: target.home,
208
+ windowTokens: target.windowTokens,
209
+ },
210
+ provenance: copyProvenance(session.provenance, dedupeChangedPaths(session.provenance.repo.changedPaths)),
211
+ turns,
212
+ pins,
213
+ drops,
214
+ keptTurnCount: turns.length,
215
+ droppedTurnCount: drops.length,
216
+ // Counted over the whole selection, not the survivors: step 2 removed every body
217
+ // before the budget ran, so a turn the budget later dropped still lost one.
218
+ bodiesDropped: selected.filter((turn) => turn.toolCall?.bodyDropped === true).length,
219
+ estimatedTokens,
220
+ budgetTokens,
221
+ brokenTailDropped,
222
+ blockedReason,
223
+ };
224
+ }
225
+ /** The rules hold no state, so every instance behaves identically (T-TRA-19). */
226
+ export function createTransferRules() {
227
+ return { apply };
228
+ }
@@ -0,0 +1,24 @@
1
+ import type { RepoReader } from "../platform/repo/contract.js";
2
+ import type { AgentAdapter, FileCommitter, ImportConfig, ImportPipeline, TokenEstimator } from "./contract.js";
3
+ import { type HandoverCommandBuilder } from "./landing/index.js";
4
+ import { type PipelineStages } from "./pipeline.js";
5
+ export interface ImportPipelineDeps {
6
+ /** Every constructed adapter, in the order the composition root listed them (FR-57). */
7
+ adapters: readonly AgentAdapter[];
8
+ config: ImportConfig;
9
+ estimator: TokenEstimator;
10
+ committer: FileCommitter;
11
+ /** Read by the preview to say how far the tree moved (FR-37, FR-38). */
12
+ repo: RepoReader;
13
+ /**
14
+ * The import time written into the marker (FR-47). Injected because no stage may read a
15
+ * clock: a preview and the commit that follows it must compute the same plan.
16
+ */
17
+ now?: () => string;
18
+ /** Renders an agent's own resume command for the handover (FR-45). */
19
+ handoverCommand?: HandoverCommandBuilder;
20
+ }
21
+ /** The four stages, wired. Internal: a host builds a pipeline, never a stage. */
22
+ export declare function buildStages(deps: ImportPipelineDeps): PipelineStages;
23
+ /** One import, from listing to landing. The only thing a host constructs here. */
24
+ export declare function createImportPipeline(deps: ImportPipelineDeps): ImportPipeline;
@@ -0,0 +1,27 @@
1
+ // The Internal Design, as code: this module builds its four stages and nothing else builds
2
+ // them. Everything the stages need arrives from src/host/ — this module constructs no adapter,
3
+ // no committer, no estimator and no configuration.
4
+ import { createSessionFinder } from "./discovery/index.js";
5
+ import { createSessionLander } from "./landing/index.js";
6
+ import { createPipelineFromStages } from "./pipeline.js";
7
+ import { createPreviewBuilder } from "./preview/index.js";
8
+ import { createTransferRules } from "./transfer/index.js";
9
+ /** The four stages, wired. Internal: a host builds a pipeline, never a stage. */
10
+ export function buildStages(deps) {
11
+ return {
12
+ finder: createSessionFinder({ adapters: deps.adapters, config: deps.config }),
13
+ rules: createTransferRules(),
14
+ // Per request: the preview reads the repository the request names (FR-13, FR-37).
15
+ previewFor: (repoRoot) => createPreviewBuilder(deps.repo, repoRoot),
16
+ lander: createSessionLander(deps.handoverCommand === undefined ? {} : { handoverCommand: deps.handoverCommand }),
17
+ adapters: deps.adapters,
18
+ config: deps.config,
19
+ estimator: deps.estimator,
20
+ committer: deps.committer,
21
+ now: deps.now ?? (() => new Date().toISOString()),
22
+ };
23
+ }
24
+ /** One import, from listing to landing. The only thing a host constructs here. */
25
+ export function createImportPipeline(deps) {
26
+ return createPipelineFromStages(buildStages(deps));
27
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The package entry point.
3
+ *
4
+ * There is one wiring in this system and it lives in `src/host/`, which Decision 3 makes the
5
+ * composition root: the agent list is there because putting it in `src/adapters/` would cycle.
6
+ * This file's whole job is to name the one path into that wiring, so the three ways in — a
7
+ * program that imports the package, the command binary, and the Pi extension — are three doors
8
+ * onto one composition rather than three compositions.
9
+ *
10
+ * That is why nothing here reaches `src/session/`, `src/adapters/`, `src/import/` or
11
+ * `src/platform/`. A second import of any of them from here would be a second wiring path, and
12
+ * the entry point would start deciding things the host already decides.
13
+ */
14
+ import { type Host, type HostDeps } from "./host/index.js";
15
+ export type { AgentId, AgentRuntime, HandoverInstruction, HomePath, HostWiring, ImportPipeline, ImportRequest, LandingResult, ResumeFrom, SelectionInput, SessionId, SessionRef, TargetProfile, } from "./contract.js";
16
+ /**
17
+ * The two entry points C-1 forces (Decision 4): a binary for the agents that cannot host a
18
+ * picker, and an in-process activation for the one that can. Both are re-exported rather than
19
+ * rebuilt — each already loads the host through `createHost` below.
20
+ *
21
+ * `activatePiExtension` is published here because the package's `./pi-extension` subpath resolves
22
+ * to `src/host/pi-extension/`, which holds the command and the picker but not the activation that
23
+ * builds a host for them. A Pi host that reached only for the subpath would have to wire the
24
+ * system itself.
25
+ */
26
+ export { activatePiExtension, type PiActivation, runCommandBinary } from "./host/index.js";
27
+ export type { Host, HostDeps };
28
+ /**
29
+ * Loads configuration, builds the agent list, and returns the host wiring.
30
+ *
31
+ * `createHost()` with no argument is the contract's call and the one every entry point makes:
32
+ * every dependency defaults to the shipped implementation. The optional argument is the seam the
33
+ * host already publishes — it is what lets a test replace one generic service of `src/platform/`
34
+ * and see the rest of the system run unchanged (T-ROO-22).
35
+ */
36
+ export declare function createHost(deps?: HostDeps): Promise<Host>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The package entry point.
3
+ *
4
+ * There is one wiring in this system and it lives in `src/host/`, which Decision 3 makes the
5
+ * composition root: the agent list is there because putting it in `src/adapters/` would cycle.
6
+ * This file's whole job is to name the one path into that wiring, so the three ways in — a
7
+ * program that imports the package, the command binary, and the Pi extension — are three doors
8
+ * onto one composition rather than three compositions.
9
+ *
10
+ * That is why nothing here reaches `src/session/`, `src/adapters/`, `src/import/` or
11
+ * `src/platform/`. A second import of any of them from here would be a second wiring path, and
12
+ * the entry point would start deciding things the host already decides.
13
+ */
14
+ import { createHost as buildHost } from "./host/index.js";
15
+ /**
16
+ * The two entry points C-1 forces (Decision 4): a binary for the agents that cannot host a
17
+ * picker, and an in-process activation for the one that can. Both are re-exported rather than
18
+ * rebuilt — each already loads the host through `createHost` below.
19
+ *
20
+ * `activatePiExtension` is published here because the package's `./pi-extension` subpath resolves
21
+ * to `src/host/pi-extension/`, which holds the command and the picker but not the activation that
22
+ * builds a host for them. A Pi host that reached only for the subpath would have to wire the
23
+ * system itself.
24
+ */
25
+ export { activatePiExtension, runCommandBinary } from "./host/index.js";
26
+ /**
27
+ * Loads configuration, builds the agent list, and returns the host wiring.
28
+ *
29
+ * `createHost()` with no argument is the contract's call and the one every entry point makes:
30
+ * every dependency defaults to the shipped implementation. The optional argument is the seam the
31
+ * host already publishes — it is what lets a test replace one generic service of `src/platform/`
32
+ * and see the rest of the system run unchanged (T-ROO-22).
33
+ */
34
+ export function createHost(deps = {}) {
35
+ return buildHost(deps);
36
+ }
@@ -0,0 +1,35 @@
1
+ import type { AgentId, HomePath } from "../../session/contract.js";
2
+ export type { AgentId, HomePath };
3
+ /** One extra home the user added to the search list (FR-5). */
4
+ export interface HomeEntry {
5
+ agent: AgentId;
6
+ home: HomePath;
7
+ }
8
+ /** A context window the user set for one agent, overriding the adapter default (FR-18). */
9
+ export interface WindowOverride {
10
+ agent: AgentId;
11
+ windowTokens: number;
12
+ }
13
+ /** User configuration. Every field has a default (FR-5, FR-30, FR-32). */
14
+ export interface ImportConfig {
15
+ /** Homes searched in addition to every adapter's default home. Default empty (FR-5). */
16
+ extraHomes: HomeEntry[];
17
+ /** Share of the target window one import may use. Default 0.30 (FR-30, Q-1). */
18
+ budgetShare: number;
19
+ /** Recent turns kept word for word. Default 5 (FR-32, Q-2). */
20
+ pinnedRecentTurns: number;
21
+ /** Context windows the user set explicitly. Default empty. */
22
+ windowOverrides: WindowOverride[];
23
+ }
24
+ /** Why a configuration was rejected (FR-56). */
25
+ export interface ConfigError {
26
+ /** The setting at fault, for example "budgetShare". */
27
+ field: string;
28
+ /** What is wrong, and what the user can do next. */
29
+ message: string;
30
+ }
31
+ /** Loads configuration and fills every missing field with its default. */
32
+ export interface ConfigLoader {
33
+ /** Rejects for invalid values or unreadable paths. A genuinely missing file is not an error. */
34
+ load(): Promise<ImportConfig>;
35
+ }
@@ -0,0 +1,4 @@
1
+ // GENERATED from src/platform/config/module.md — the Public Contract section is the normative home.
2
+ // Declarations only: no behaviour, no defaults. If this file and module.md disagree,
3
+ // the document wins and this file is corrected.
4
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { ImportConfig } from "./contract.js";
2
+ /**
3
+ * Share of the target window one import may use (FR-30, Q-1).
4
+ * This is the single home of the number: answering Q-1 is editing this line (T-CFG-8, T-CFG-14).
5
+ */
6
+ export declare const DEFAULT_BUDGET_SHARE = 0.3;
7
+ /**
8
+ * Recent turns kept word for word (FR-32, Q-2).
9
+ * Single home of the number, for the same reason as the budget share.
10
+ */
11
+ export declare const DEFAULT_PINNED_RECENT_TURNS = 5;
12
+ /**
13
+ * A complete configuration with nothing set by the user.
14
+ * Fresh arrays every call: a caller that mutates its copy must not affect the next load.
15
+ */
16
+ export declare function defaultConfig(): ImportConfig;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Share of the target window one import may use (FR-30, Q-1).
3
+ * This is the single home of the number: answering Q-1 is editing this line (T-CFG-8, T-CFG-14).
4
+ */
5
+ export const DEFAULT_BUDGET_SHARE = 0.3;
6
+ /**
7
+ * Recent turns kept word for word (FR-32, Q-2).
8
+ * Single home of the number, for the same reason as the budget share.
9
+ */
10
+ export const DEFAULT_PINNED_RECENT_TURNS = 5;
11
+ /**
12
+ * A complete configuration with nothing set by the user.
13
+ * Fresh arrays every call: a caller that mutates its copy must not affect the next load.
14
+ */
15
+ export function defaultConfig() {
16
+ return {
17
+ extraHomes: [],
18
+ budgetShare: DEFAULT_BUDGET_SHARE,
19
+ pinnedRecentTurns: DEFAULT_PINNED_RECENT_TURNS,
20
+ windowOverrides: [],
21
+ };
22
+ }
@@ -0,0 +1,9 @@
1
+ import type { ConfigError } from "./contract.js";
2
+ /**
3
+ * What `ConfigLoader.load` rejects with (FR-56). An `Error` as well as a `ConfigError`,
4
+ * so a rejection that reaches a host still carries a stack.
5
+ */
6
+ export declare class ConfigLoadError extends Error implements ConfigError {
7
+ readonly field: string;
8
+ constructor(field: string, message: string);
9
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * What `ConfigLoader.load` rejects with (FR-56). An `Error` as well as a `ConfigError`,
3
+ * so a rejection that reaches a host still carries a stack.
4
+ */
5
+ export class ConfigLoadError extends Error {
6
+ field;
7
+ constructor(field, message) {
8
+ super(message);
9
+ this.name = "ConfigLoadError";
10
+ this.field = field;
11
+ }
12
+ }
@@ -0,0 +1,5 @@
1
+ export type { ConfigError, ConfigLoader, HomeEntry, ImportConfig, WindowOverride, } from "./contract.js";
2
+ export { DEFAULT_BUDGET_SHARE, DEFAULT_PINNED_RECENT_TURNS, defaultConfig } from "./defaults.js";
3
+ export { ConfigLoadError } from "./errors.js";
4
+ export { type ConfigLoaderOptions, createConfigLoader } from "./loader.js";
5
+ export { defaultConfigPath } from "./paths.js";
@@ -0,0 +1,6 @@
1
+ // src/platform/config/ — loads the user's settings and fills every missing one with its
2
+ // default. Types are declared in ./contract.js; this file is the module's behaviour.
3
+ export { DEFAULT_BUDGET_SHARE, DEFAULT_PINNED_RECENT_TURNS, defaultConfig } from "./defaults.js";
4
+ export { ConfigLoadError } from "./errors.js";
5
+ export { createConfigLoader } from "./loader.js";
6
+ export { defaultConfigPath } from "./paths.js";
@@ -0,0 +1,10 @@
1
+ import type { ConfigLoader } from "./contract.js";
2
+ export interface ConfigLoaderOptions {
3
+ /** Configuration file to read. Defaults to `~/.config/resume-from/config.json`. */
4
+ configPath?: string | undefined;
5
+ }
6
+ /**
7
+ * A loader that reads one JSON file and fills every missing setting with its default.
8
+ * It never writes: a missing file is a missing file, not something to create (T-CFG-13).
9
+ */
10
+ export declare function createConfigLoader(options?: ConfigLoaderOptions): ConfigLoader;
@@ -0,0 +1,47 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { defaultConfig } from "./defaults.js";
4
+ import { ConfigLoadError } from "./errors.js";
5
+ import { defaultConfigPath } from "./paths.js";
6
+ import { buildConfig } from "./validate.js";
7
+ /**
8
+ * A loader that reads one JSON file and fills every missing setting with its default.
9
+ * It never writes: a missing file is a missing file, not something to create (T-CFG-13).
10
+ */
11
+ export function createConfigLoader(options = {}) {
12
+ const configPath = options.configPath ?? defaultConfigPath();
13
+ return {
14
+ async load() {
15
+ const text = await readConfigFile(configPath);
16
+ // No file, or a file the user emptied: both mean "nothing set", not "nothing works".
17
+ if (text === undefined || text.trim() === "")
18
+ return defaultConfig();
19
+ let parsed;
20
+ try {
21
+ parsed = JSON.parse(text);
22
+ }
23
+ catch (cause) {
24
+ // Not a fallback to defaults: a file the user wrote and the tool ignored is worse
25
+ // than an error.
26
+ throw new ConfigLoadError(configPath, `${configPath} is not valid JSON: ${reason(cause)}. Fix the syntax, or delete the ` +
27
+ "file to use the defaults.");
28
+ }
29
+ return buildConfig(parsed, dirname(configPath), configPath);
30
+ },
31
+ };
32
+ }
33
+ /** The file's text, or undefined when there is no file to read. */
34
+ async function readConfigFile(configPath) {
35
+ try {
36
+ return await readFile(configPath, "utf8");
37
+ }
38
+ catch (cause) {
39
+ const code = cause.code;
40
+ if (code === "ENOENT")
41
+ return undefined;
42
+ throw new ConfigLoadError(configPath, `${configPath} cannot be read: ${reason(cause)}. Check the path and its permissions.`);
43
+ }
44
+ }
45
+ function reason(cause) {
46
+ return cause instanceof Error ? cause.message : String(cause);
47
+ }
@@ -0,0 +1,13 @@
1
+ import type { HomePath } from "./contract.js";
2
+ /** Where the configuration lives when the caller names no other file. */
3
+ export declare function defaultConfigPath(): string;
4
+ /**
5
+ * Turns a home as written into an absolute, symlink-free path: a leading `~` becomes the
6
+ * user's home, a relative path is resolved against the configuration file's own directory
7
+ * (an invocation's working directory would make the same file mean different things), and
8
+ * symlinks are followed so `src/import/discovery/` deduplicates locations, not spellings.
9
+ *
10
+ * A home that does not exist keeps its resolved spelling. Existence is not checked here —
11
+ * a home may be created between the load and the search.
12
+ */
13
+ export declare function resolveHomePath(raw: string, baseDir: string): Promise<HomePath>;
@@ -0,0 +1,37 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, resolve } from "node:path";
4
+ const CONFIG_DIR = ".config";
5
+ const APP_DIR = "resume-from";
6
+ const CONFIG_FILE = "config.json";
7
+ /** Where the configuration lives when the caller names no other file. */
8
+ export function defaultConfigPath() {
9
+ return resolve(homedir(), CONFIG_DIR, APP_DIR, CONFIG_FILE);
10
+ }
11
+ /**
12
+ * Turns a home as written into an absolute, symlink-free path: a leading `~` becomes the
13
+ * user's home, a relative path is resolved against the configuration file's own directory
14
+ * (an invocation's working directory would make the same file mean different things), and
15
+ * symlinks are followed so `src/import/discovery/` deduplicates locations, not spellings.
16
+ *
17
+ * A home that does not exist keeps its resolved spelling. Existence is not checked here —
18
+ * a home may be created between the load and the search.
19
+ */
20
+ export async function resolveHomePath(raw, baseDir) {
21
+ const expanded = expandTilde(raw);
22
+ const absolute = isAbsolute(expanded) ? resolve(expanded) : resolve(baseDir, expanded);
23
+ try {
24
+ return await realpath(absolute);
25
+ }
26
+ catch {
27
+ return absolute;
28
+ }
29
+ }
30
+ function expandTilde(raw) {
31
+ if (raw === "~")
32
+ return homedir();
33
+ // Only "~/": "~other" is another user's home to a shell, and a plain segment here.
34
+ if (raw.startsWith("~/"))
35
+ return resolve(homedir(), raw.slice(2));
36
+ return raw;
37
+ }