@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18

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 (357) hide show
  1. package/README.md +134 -15
  2. package/assets/integrations/skills/pappra/SKILL.md +28 -7
  3. package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
  4. package/assets/integrations/skills/pappra/references/environment.md +360 -0
  5. package/assets/integrations/skills/pappra/references/hosts.md +20 -0
  6. package/assets/integrations/skills/pappra/references/operations.md +36 -5
  7. package/dist/adapters/pi/recovery-extension.d.ts +65 -0
  8. package/dist/adapters/pi/recovery-extension.js +238 -0
  9. package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
  10. package/dist/adapters/pi/session-transition-extension.js +265 -0
  11. package/dist/app/automatic-handoff-events.d.ts +26 -0
  12. package/dist/app/automatic-handoff-events.js +134 -0
  13. package/dist/app/bounded-file.d.ts +2 -0
  14. package/dist/app/bounded-file.js +2 -0
  15. package/dist/app/capability-evidence/claude.d.ts +37 -0
  16. package/dist/app/capability-evidence/claude.js +206 -0
  17. package/dist/app/capability-evidence/codex.d.ts +35 -0
  18. package/dist/app/capability-evidence/codex.js +193 -0
  19. package/dist/app/capability-evidence/index.d.ts +33 -0
  20. package/dist/app/capability-evidence/index.js +117 -0
  21. package/dist/app/capability-evidence/pi.d.ts +73 -0
  22. package/dist/app/capability-evidence/pi.js +669 -0
  23. package/dist/app/capability-evidence/shared.d.ts +69 -0
  24. package/dist/app/capability-evidence/shared.js +195 -0
  25. package/dist/app/collab-auto-handoff.d.ts +55 -0
  26. package/dist/app/collab-auto-handoff.js +384 -0
  27. package/dist/app/collab-bindings.d.ts +57 -0
  28. package/dist/app/collab-bindings.js +1006 -0
  29. package/dist/app/collab-bootstrap-transport.d.ts +163 -0
  30. package/dist/app/collab-bootstrap-transport.js +769 -0
  31. package/dist/app/collab-bootstrap.d.ts +137 -0
  32. package/dist/app/collab-bootstrap.js +3455 -0
  33. package/dist/app/collab-handoff.d.ts +12 -0
  34. package/dist/app/collab-handoff.js +511 -0
  35. package/dist/app/collab-messages.d.ts +2 -0
  36. package/dist/app/collab-messages.js +2 -0
  37. package/dist/app/collab-notify.d.ts +156 -0
  38. package/dist/app/collab-notify.js +1208 -0
  39. package/dist/app/collab-preflight.d.ts +34 -0
  40. package/dist/app/collab-preflight.js +96 -0
  41. package/dist/app/collab-recovery-executor.d.ts +161 -0
  42. package/dist/app/collab-recovery-executor.js +1153 -0
  43. package/dist/app/collab-recovery-projection.d.ts +103 -0
  44. package/dist/app/collab-recovery-projection.js +659 -0
  45. package/dist/app/collab-recovery.d.ts +25 -0
  46. package/dist/app/collab-recovery.js +429 -0
  47. package/dist/app/collab-resources.d.ts +16 -0
  48. package/dist/app/collab-resources.js +278 -0
  49. package/dist/app/collab-role-events.d.ts +22 -0
  50. package/dist/app/collab-role-events.js +329 -0
  51. package/dist/app/collab-role-orchestration.d.ts +53 -0
  52. package/dist/app/collab-role-orchestration.js +601 -0
  53. package/dist/app/collab-role-plan.d.ts +81 -0
  54. package/dist/app/collab-role-plan.js +504 -0
  55. package/dist/app/collab-role-repair.d.ts +109 -0
  56. package/dist/app/collab-role-repair.js +821 -0
  57. package/dist/app/collab-rollover.d.ts +263 -0
  58. package/dist/app/collab-rollover.js +1112 -0
  59. package/dist/app/collab-route.d.ts +64 -0
  60. package/dist/app/collab-route.js +240 -0
  61. package/dist/app/collab-session-transition.d.ts +221 -0
  62. package/dist/app/collab-session-transition.js +3334 -0
  63. package/dist/app/collab-state.d.ts +7 -0
  64. package/dist/app/collab-state.js +162 -0
  65. package/dist/app/collab-task-store.d.ts +22 -0
  66. package/dist/app/collab-task-store.js +204 -0
  67. package/dist/app/collab-work-execution.d.ts +127 -0
  68. package/dist/app/collab-work-execution.js +1027 -0
  69. package/dist/app/collab-work-plan.d.ts +87 -0
  70. package/dist/app/collab-work-plan.js +437 -0
  71. package/dist/app/collaboration.d.ts +8 -2
  72. package/dist/app/collaboration.js +101 -12
  73. package/dist/app/doctor.js +108 -5
  74. package/dist/app/host-mcp.js +34 -8
  75. package/dist/app/host-probe.js +4 -4
  76. package/dist/app/init.d.ts +58 -4
  77. package/dist/app/init.js +768 -155
  78. package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
  79. package/dist/app/managed-pi-bootstrap-admission.js +104 -0
  80. package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
  81. package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
  82. package/dist/app/managed-pi-delivery.d.ts +8 -0
  83. package/dist/app/managed-pi-delivery.js +110 -0
  84. package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
  85. package/dist/app/managed-pi-handoff-receiver.js +263 -0
  86. package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
  87. package/dist/app/managed-pi-handoff-reference.js +60 -0
  88. package/dist/app/managed-pi-herdr.d.ts +21 -0
  89. package/dist/app/managed-pi-herdr.js +185 -0
  90. package/dist/app/managed-pi-initial-admission.d.ts +28 -0
  91. package/dist/app/managed-pi-initial-admission.js +416 -0
  92. package/dist/app/managed-pi-initial-launch.d.ts +5 -0
  93. package/dist/app/managed-pi-initial-launch.js +61 -0
  94. package/dist/app/managed-pi-native-observation.d.ts +36 -0
  95. package/dist/app/managed-pi-native-observation.js +191 -0
  96. package/dist/app/managed-pi-readiness.d.ts +29 -0
  97. package/dist/app/managed-pi-readiness.js +288 -0
  98. package/dist/app/managed-pi-repair-stop.d.ts +21 -0
  99. package/dist/app/managed-pi-repair-stop.js +371 -0
  100. package/dist/app/managed-pi-resume-admission.d.ts +32 -0
  101. package/dist/app/managed-pi-resume-admission.js +618 -0
  102. package/dist/app/managed-pi-retirement.d.ts +28 -0
  103. package/dist/app/managed-pi-retirement.js +112 -0
  104. package/dist/app/managed-pi-sdk-control.d.ts +7 -0
  105. package/dist/app/managed-pi-sdk-control.js +169 -0
  106. package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
  107. package/dist/app/managed-pi-sdk-runtime.js +483 -0
  108. package/dist/app/managed-pi-source-admission.d.ts +14 -0
  109. package/dist/app/managed-pi-source-admission.js +323 -0
  110. package/dist/app/managed-pi-transition-host.d.ts +36 -0
  111. package/dist/app/managed-pi-transition-host.js +292 -0
  112. package/dist/app/managed-pi.d.ts +54 -0
  113. package/dist/app/managed-pi.js +248 -0
  114. package/dist/app/shell-readiness.d.ts +66 -0
  115. package/dist/app/shell-readiness.js +368 -0
  116. package/dist/app/task-pin.d.ts +2 -23
  117. package/dist/app/task-pin.js +2 -113
  118. package/dist/cli/commands/collaboration-binding.d.ts +13 -0
  119. package/dist/cli/commands/collaboration-binding.js +118 -0
  120. package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
  121. package/dist/cli/commands/collaboration-bootstrap.js +84 -0
  122. package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
  123. package/dist/cli/commands/collaboration-capabilities.js +127 -0
  124. package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
  125. package/dist/cli/commands/collaboration-handoff.js +102 -0
  126. package/dist/cli/commands/collaboration-message.d.ts +17 -0
  127. package/dist/cli/commands/collaboration-message.js +177 -0
  128. package/dist/cli/commands/collaboration-plan.d.ts +8 -0
  129. package/dist/cli/commands/collaboration-plan.js +108 -0
  130. package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
  131. package/dist/cli/commands/collaboration-preflight.js +167 -0
  132. package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
  133. package/dist/cli/commands/collaboration-recovery-run.js +224 -0
  134. package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
  135. package/dist/cli/commands/collaboration-recovery.js +104 -0
  136. package/dist/cli/commands/collaboration-resources.d.ts +11 -0
  137. package/dist/cli/commands/collaboration-resources.js +96 -0
  138. package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
  139. package/dist/cli/commands/collaboration-role-plan.js +69 -0
  140. package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
  141. package/dist/cli/commands/collaboration-role-run.js +128 -0
  142. package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
  143. package/dist/cli/commands/collaboration-rollover.js +103 -0
  144. package/dist/cli/commands/collaboration-state.d.ts +10 -0
  145. package/dist/cli/commands/collaboration-state.js +96 -0
  146. package/dist/cli/commands/collaboration-transition.d.ts +10 -0
  147. package/dist/cli/commands/collaboration-transition.js +75 -0
  148. package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
  149. package/dist/cli/commands/collaboration-work-run.js +178 -0
  150. package/dist/cli/commands/collaboration.d.ts +4 -0
  151. package/dist/cli/commands/collaboration.js +39 -4
  152. package/dist/cli/commands/continuity.js +2 -6
  153. package/dist/cli/commands/docs.d.ts +2 -0
  154. package/dist/cli/commands/docs.js +89 -15
  155. package/dist/cli/commands/host.d.ts +7 -1
  156. package/dist/cli/commands/host.js +122 -7
  157. package/dist/cli/commands/init.d.ts +20 -3
  158. package/dist/cli/commands/init.js +897 -232
  159. package/dist/cli/commands/mcp.d.ts +1 -0
  160. package/dist/cli/commands/mcp.js +11 -5
  161. package/dist/cli/commands/repair.d.ts +12 -0
  162. package/dist/cli/commands/repair.js +41 -0
  163. package/dist/cli/commands/runtime.d.ts +1 -1
  164. package/dist/cli/commands/runtime.js +139 -38
  165. package/dist/cli/commands/session.d.ts +7 -0
  166. package/dist/cli/commands/session.js +82 -0
  167. package/dist/cli/commands/task.js +19 -5
  168. package/dist/cli/run.js +866 -24
  169. package/dist/code/identity.d.ts +7 -0
  170. package/dist/code/identity.js +7 -3
  171. package/dist/code/registration.js +7 -0
  172. package/dist/code/service.d.ts +9 -0
  173. package/dist/code/service.js +16 -3
  174. package/dist/code/text.js +19 -7
  175. package/dist/collab/admission.d.ts +91 -66
  176. package/dist/collab/admission.js +21 -6
  177. package/dist/collab/canonical.d.ts +1 -1
  178. package/dist/collab/canonical.js +44 -3
  179. package/dist/collab/envelope.d.ts +2 -2
  180. package/dist/collab/envelope.js +12 -12
  181. package/dist/collab/first-work.d.ts +3 -0
  182. package/dist/collab/first-work.js +15 -0
  183. package/dist/collab/handoff.d.ts +16 -0
  184. package/dist/collab/handoff.js +39 -5
  185. package/dist/collab/index.d.ts +97 -1
  186. package/dist/collab/index.js +9 -1
  187. package/dist/collab/messages.js +126 -3
  188. package/dist/collab/persisted-messages.d.ts +123 -0
  189. package/dist/collab/persisted-messages.js +496 -0
  190. package/dist/collab/preflight.d.ts +1 -1
  191. package/dist/collab/resources.d.ts +13 -3
  192. package/dist/collab/resources.js +37 -6
  193. package/dist/collab/rollover.d.ts +311 -0
  194. package/dist/collab/rollover.js +1024 -0
  195. package/dist/collab/store.d.ts +1 -0
  196. package/dist/collab/store.js +1 -4
  197. package/dist/collab/types.d.ts +2 -354
  198. package/dist/collab/validation.d.ts +2 -10
  199. package/dist/collab/validation.js +2 -303
  200. package/dist/collab/work-plan.d.ts +105 -0
  201. package/dist/collab/work-plan.js +843 -0
  202. package/dist/continuity/context.d.ts +11 -2
  203. package/dist/continuity/context.js +100 -13
  204. package/dist/continuity/handoff-bundle.d.ts +296 -0
  205. package/dist/continuity/handoff-bundle.js +477 -0
  206. package/dist/continuity/index.d.ts +2 -0
  207. package/dist/continuity/index.js +1 -0
  208. package/dist/continuity/paths.js +10 -2
  209. package/dist/continuity/service.js +91 -15
  210. package/dist/continuity/task-pin.d.ts +24 -0
  211. package/dist/continuity/task-pin.js +590 -0
  212. package/dist/doctor.d.ts +1 -0
  213. package/dist/doctor.js +6 -0
  214. package/dist/experimental/index.d.ts +13 -0
  215. package/dist/experimental/index.js +25 -0
  216. package/dist/experimental/pi.d.ts +4 -0
  217. package/dist/experimental/pi.js +12 -0
  218. package/dist/hosts/claude/native.js +1 -1
  219. package/dist/hosts/codex/native.js +1 -1
  220. package/dist/hosts/codex/protocol.js +1 -1
  221. package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
  222. package/dist/hosts/integration/bootstrap-config.js +254 -0
  223. package/dist/hosts/integration/bundle-export.d.ts +44 -0
  224. package/dist/hosts/integration/bundle-export.js +366 -3
  225. package/dist/hosts/integration/bundle.js +3 -0
  226. package/dist/hosts/integration/hook.js +36 -9
  227. package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
  228. package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
  229. package/dist/hosts/integration/pappra-hook.mjs +61 -24749
  230. package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
  231. package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
  232. package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
  233. package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
  234. package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
  235. package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
  236. package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
  237. package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
  238. package/dist/hosts/integration/pi-native-events.d.ts +112 -0
  239. package/dist/hosts/integration/pi-native-events.js +348 -0
  240. package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
  241. package/dist/hosts/integration/pi-rollover-extension.js +431 -0
  242. package/dist/hosts/integration/recovery-config.d.ts +48 -0
  243. package/dist/hosts/integration/recovery-config.js +117 -0
  244. package/dist/hosts/integration/service.d.ts +6 -1
  245. package/dist/hosts/integration/service.js +485 -35
  246. package/dist/hosts/integration/support.d.ts +7 -0
  247. package/dist/hosts/integration/support.js +9 -1
  248. package/dist/hosts/integration/templates.d.ts +3 -0
  249. package/dist/hosts/integration/templates.js +26 -3
  250. package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
  251. package/dist/hosts/pi/managed-host-identity.js +40 -0
  252. package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
  253. package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
  254. package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
  255. package/dist/hosts/pi/managed-sdk-session.js +1639 -0
  256. package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
  257. package/dist/hosts/pi/managed-session-extension.js +84 -0
  258. package/dist/hosts/pi/managed-session.d.ts +177 -0
  259. package/dist/hosts/pi/managed-session.js +1557 -0
  260. package/dist/hosts/pi/protocol.js +1 -1
  261. package/dist/hosts/pi/query-result.js +1 -1
  262. package/dist/hosts/pi/resume-capability.d.ts +41 -0
  263. package/dist/hosts/pi/resume-capability.js +33 -0
  264. package/dist/hosts/pi/rpc.d.ts +182 -134
  265. package/dist/hosts/pi/rpc.js +230 -37
  266. package/dist/hosts/pilot.d.ts +97 -74
  267. package/dist/hosts/pilot.js +5 -1
  268. package/dist/hosts/version.d.ts +3 -0
  269. package/dist/hosts/version.js +18 -0
  270. package/dist/index.d.ts +0 -1
  271. package/dist/index.js +0 -1
  272. package/dist/infra/bounded-file.d.ts +19 -0
  273. package/dist/infra/bounded-file.js +66 -0
  274. package/dist/infra/fs.js +10 -2
  275. package/dist/infra/git.d.ts +29 -0
  276. package/dist/infra/git.js +45 -9
  277. package/dist/infra/json-property.d.ts +2 -0
  278. package/dist/infra/json-property.js +69 -0
  279. package/dist/infra/json.d.ts +4 -0
  280. package/dist/infra/json.js +24 -0
  281. package/dist/infra/owned-process-group.js +1 -1
  282. package/dist/infra/private-lock.d.ts +16 -0
  283. package/dist/infra/private-lock.js +148 -0
  284. package/dist/infra/private-state.d.ts +7 -0
  285. package/dist/infra/private-state.js +146 -0
  286. package/dist/infra/process-identity.d.ts +12 -0
  287. package/dist/infra/process-identity.js +142 -0
  288. package/dist/infra/validation.d.ts +5 -0
  289. package/dist/infra/validation.js +15 -0
  290. package/dist/init.d.ts +1 -0
  291. package/dist/init.js +6 -0
  292. package/dist/install/apply.d.ts +9 -1
  293. package/dist/install/apply.js +176 -12
  294. package/dist/install/dependencies.d.ts +38 -0
  295. package/dist/install/dependencies.js +282 -0
  296. package/dist/install/ownership.js +30 -1
  297. package/dist/install/references.d.ts +31 -0
  298. package/dist/install/references.js +208 -0
  299. package/dist/install/repair.d.ts +37 -0
  300. package/dist/install/repair.js +798 -0
  301. package/dist/install/reset-local.d.ts +16 -0
  302. package/dist/install/reset-local.js +304 -0
  303. package/dist/install/rollback.d.ts +3 -0
  304. package/dist/install/rollback.js +9 -3
  305. package/dist/install/runtime.d.ts +10 -6
  306. package/dist/install/runtime.js +299 -59
  307. package/dist/install/source-fingerprint.d.ts +2 -0
  308. package/dist/install/source-fingerprint.js +103 -0
  309. package/dist/knowledge/catalog.d.ts +1 -0
  310. package/dist/knowledge/catalog.js +143 -10
  311. package/dist/knowledge/journal.js +17 -7
  312. package/dist/knowledge/paths.js +2 -1
  313. package/dist/knowledge/refs.js +4 -0
  314. package/dist/knowledge/register-batch.d.ts +4 -0
  315. package/dist/knowledge/register-batch.js +225 -0
  316. package/dist/knowledge/relocate.d.ts +5 -0
  317. package/dist/knowledge/relocate.js +92 -0
  318. package/dist/knowledge/scan.js +103 -47
  319. package/dist/knowledge/service.d.ts +3 -0
  320. package/dist/knowledge/service.js +33 -1
  321. package/dist/knowledge/write.d.ts +4 -1
  322. package/dist/knowledge/write.js +1 -1
  323. package/dist/mcp/server.d.ts +4 -2
  324. package/dist/mcp/server.js +87 -136
  325. package/dist/schemas/collaboration-state-validation.d.ts +6 -0
  326. package/dist/schemas/collaboration-state-validation.js +342 -0
  327. package/dist/schemas/collaboration-state.d.ts +436 -0
  328. package/dist/schemas/collaboration-state.js +1 -0
  329. package/dist/schemas/collaboration.d.ts +335 -0
  330. package/dist/schemas/collaboration.js +272 -0
  331. package/dist/schemas/continuity.d.ts +8 -0
  332. package/dist/schemas/continuity.js +13 -3
  333. package/dist/schemas/doctor.d.ts +1 -0
  334. package/dist/schemas/doctor.js +1 -0
  335. package/dist/schemas/document.d.ts +61 -0
  336. package/dist/schemas/document.js +9 -0
  337. package/dist/schemas/errors.d.ts +1 -0
  338. package/dist/schemas/errors.js +1 -0
  339. package/dist/schemas/host.d.ts +24 -0
  340. package/dist/schemas/host.js +19 -0
  341. package/dist/schemas/install.d.ts +8 -2
  342. package/dist/schemas/install.js +11 -1
  343. package/dist/schemas/managed-pi-execution.d.ts +16 -0
  344. package/dist/schemas/managed-pi-execution.js +2 -0
  345. package/dist/schemas/outcome.d.ts +9 -5
  346. package/dist/workspace/config-file.js +1 -3
  347. package/dist/workspace/documents.js +12 -2
  348. package/dist/workspace/repositories.d.ts +7 -0
  349. package/dist/workspace/repositories.js +14 -5
  350. package/package.json +35 -35
  351. package/skills/pappra/SKILL.md +28 -7
  352. package/skills/pappra/references/collaboration.md +633 -0
  353. package/skills/pappra/references/environment.md +360 -0
  354. package/skills/pappra/references/hosts.md +20 -0
  355. package/skills/pappra/references/operations.md +36 -5
  356. package/dist/app/collab-from.d.ts +0 -35
  357. package/dist/app/collab-from.js +0 -84
@@ -0,0 +1,590 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { ensurePrivateState } from '../infra/private-state.js';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { observePath, readTextFileBounded, resolveRealpath, } from '../infra/fs.js';
6
+ import { diagnosticCodes } from '../schemas/errors.js';
7
+ import { isValidTaskId } from '../schemas/continuity.js';
8
+ import { errorMessage, failed, succeeded, } from '../schemas/outcome.js';
9
+ import { isPathInside, samePath } from '../schemas/paths.js';
10
+ import { z } from 'zod';
11
+ import { acquireInstallLock, ensureStatePathBoundary, releaseInstallLock, workspaceInstallPaths, } from '../install/ownership.js';
12
+ import { inspectWorkspaceConfig } from '../workspace/config.js';
13
+ export const TASK_PIN_SCHEMA = 'pappra/task-pin/1';
14
+ export const TASK_PREFERENCES_SCHEMA = 'pappra/task-preferences/1';
15
+ const taskPreferencesSchema = z.strictObject({
16
+ schema: z.literal(TASK_PREFERENCES_SCHEMA),
17
+ workspaceId: z.uuid(),
18
+ autoResume: z.boolean(),
19
+ });
20
+ function taskStatePaths(root) {
21
+ const localDir = join(root, '.pappra', 'local');
22
+ return {
23
+ localDir,
24
+ pinPath: join(localDir, 'current-task'),
25
+ configPath: join(root, '.pappra', 'workspace.json'),
26
+ preferencesPath: join(localDir, 'task-preferences.json'),
27
+ };
28
+ }
29
+ /**
30
+ * Resolves the workspace to its real path so a symlinked workspace root keeps
31
+ * working. A root that cannot be resolved is never treated as a workspace.
32
+ */
33
+ async function canonicalWorkspaceRoot(root) {
34
+ return await resolveRealpath(resolve(root));
35
+ }
36
+ function unsafe(message, nextAction) {
37
+ return failed(diagnosticCodes.document_path_unsafe, message, { nextAction });
38
+ }
39
+ async function observe(path) {
40
+ try {
41
+ return succeeded(await observePath(path));
42
+ }
43
+ catch (error) {
44
+ return failed(diagnosticCodes.fileUnreadable, `Cannot inspect task state path ${path}: ${errorMessage(error)}`, { nextAction: 'Check permissions; no task state was read or written.' });
45
+ }
46
+ }
47
+ /**
48
+ * Fails closed unless `target` is the canonical private task-state location:
49
+ * `.pappra` and `.pappra/local` are real directories inside the workspace
50
+ * (continuity uses the same namespace and rejects redirects too), every
51
+ * existing ancestor resolves inside the workspace, and the final entry is a
52
+ * regular file or absent. The managed-state helper provides containment.
53
+ */
54
+ async function privateTaskStateBoundary(root, target) {
55
+ const containment = await ensureStatePathBoundary(root, target);
56
+ if (!containment.ok) {
57
+ return unsafe(`Private task state resolves outside the workspace: ${target} (${containment.message})`, 'Restore .pappra/local as a real directory inside the workspace; no task state was read or written.');
58
+ }
59
+ const localDir = taskStatePaths(root).localDir;
60
+ for (const namespace of [dirname(localDir), localDir]) {
61
+ const observation = await observe(namespace);
62
+ if (!observation.ok)
63
+ return observation;
64
+ if (observation.value.kind === 'missing')
65
+ break;
66
+ if (observation.value.kind === 'symlink') {
67
+ return unsafe(`Private task state is redirected through a symlink: ${namespace}`, 'Restore .pappra/local as a real directory inside the workspace; Pappra does not follow redirected task state.');
68
+ }
69
+ if (observation.value.kind !== 'directory') {
70
+ return unsafe(`Private task-state parent is not a directory: ${namespace}`, 'Inspect the private task-state path; no task state was read or written.');
71
+ }
72
+ }
73
+ const final = await observe(target);
74
+ if (!final.ok)
75
+ return final;
76
+ if (final.value.kind === 'symlink') {
77
+ return unsafe(`Task state path is a symlink: ${target}`, 'Replace it with a regular file inside .pappra/local; Pappra does not follow task-state symlinks.');
78
+ }
79
+ if (final.value.kind !== 'missing' && final.value.kind !== 'file') {
80
+ return unsafe(`Task state path is not a regular file: ${target}`, 'Inspect the private task-state path; no task state was read or written.');
81
+ }
82
+ return succeeded(null);
83
+ }
84
+ /** The shared configuration file must not be replaced through a redirect. */
85
+ async function configBoundary(root, configPath) {
86
+ const containment = await ensureStatePathBoundary(root, configPath);
87
+ if (!containment.ok) {
88
+ return unsafe(`Workspace configuration resolves outside the workspace: ${configPath} (${containment.message})`, 'Keep .pappra/workspace.json inside the workspace; nothing was written.');
89
+ }
90
+ const observation = await observe(configPath);
91
+ if (!observation.ok)
92
+ return observation;
93
+ if (observation.value.kind === 'symlink') {
94
+ return unsafe(`Workspace configuration is a symlink: ${configPath}`, 'Replace it with a regular .pappra/workspace.json; Pappra does not replace redirected configuration.');
95
+ }
96
+ if (observation.value.kind !== 'missing' &&
97
+ observation.value.kind !== 'file') {
98
+ return unsafe(`Workspace configuration is not a regular file: ${configPath}`, 'Inspect the configuration path; nothing was written.');
99
+ }
100
+ return succeeded(null);
101
+ }
102
+ function identityOf(observation, real) {
103
+ if (observation.stats === null)
104
+ return null;
105
+ return { dev: observation.stats.dev, ino: observation.stats.ino, real };
106
+ }
107
+ function sameFile(left, right) {
108
+ return (left !== null &&
109
+ right !== null &&
110
+ left.dev === right.dev &&
111
+ left.ino === right.ino &&
112
+ samePath(left.real, right.real));
113
+ }
114
+ function sameSnapshot(left, right) {
115
+ return (sameFile(left.root, right.root) &&
116
+ ((left.pappra === null && right.pappra === null) ||
117
+ sameFile(left.pappra, right.pappra)) &&
118
+ ((left.local === null && right.local === null) ||
119
+ sameFile(left.local, right.local)));
120
+ }
121
+ /**
122
+ * Pins the real `.pappra` and `.pappra/local` directories (device/inode plus
123
+ * real path). Node offers no `openat`-style handle-relative mutation, so these
124
+ * checks are cooperative: they close redirects that occur at awaited seams we
125
+ * can observe, but they are not an OS-level fence for a same-user race that
126
+ * lands between the final check and the syscall.
127
+ */
128
+ async function namespaceSnapshot(canonical) {
129
+ const rootObservation = await observe(canonical);
130
+ if (!rootObservation.ok)
131
+ return rootObservation;
132
+ if (rootObservation.value.kind !== 'directory') {
133
+ return unsafe(`Workspace root is not a directory: ${canonical}`, 'Run this action from a real registered workspace.');
134
+ }
135
+ const paths = taskStatePaths(canonical);
136
+ const entries = {
137
+ pappra: null,
138
+ local: null,
139
+ };
140
+ for (const key of ['pappra', 'local']) {
141
+ const path = key === 'pappra' ? dirname(paths.localDir) : paths.localDir;
142
+ const observation = await observe(path);
143
+ if (!observation.ok)
144
+ return observation;
145
+ if (observation.value.kind === 'missing')
146
+ continue;
147
+ if (observation.value.kind !== 'directory') {
148
+ return unsafe(`Private task state is not a real directory: ${path}`, 'Restore .pappra/local as a real directory inside the workspace; nothing was written.');
149
+ }
150
+ const real = await resolveRealpath(path);
151
+ if (real === null || !isPathInside(canonical, real)) {
152
+ return unsafe(`Private task state resolves outside the workspace: ${path}`, 'Restore .pappra/local inside the workspace; nothing was written.');
153
+ }
154
+ entries[key] = identityOf(observation.value, real);
155
+ }
156
+ const rootReal = await resolveRealpath(canonical);
157
+ if (rootReal === null) {
158
+ return unsafe(`Cannot resolve workspace root: ${canonical}`, 'Retry from an existing workspace root.');
159
+ }
160
+ return succeeded({
161
+ root: identityOf(rootObservation.value, rootReal),
162
+ ...entries,
163
+ });
164
+ }
165
+ async function assertSnapshot(canonical, expected) {
166
+ const current = await namespaceSnapshot(canonical);
167
+ if (!current.ok)
168
+ return current;
169
+ if (!sameSnapshot(expected, current.value)) {
170
+ return unsafe('Private task-state namespace changed during the operation.', 'Inspect .pappra/local; no further write or cleanup was attempted through the changed path.');
171
+ }
172
+ return succeeded(null);
173
+ }
174
+ /**
175
+ * Verifies an entry is a regular file inside the pinned state directory
176
+ * (`local` for pins/backups, `.pappra` for the shared configuration).
177
+ */
178
+ async function verifyEntry(snapshot, path, expected, scope = 'local') {
179
+ const observation = await observe(path);
180
+ if (!observation.ok)
181
+ return observation;
182
+ if (observation.value.kind === 'missing') {
183
+ return expected === 'file'
184
+ ? unsafe(`Task state entry is missing: ${path}`, 'Inspect the private task-state path; nothing was written.')
185
+ : succeeded(null);
186
+ }
187
+ if (observation.value.kind === 'symlink') {
188
+ return unsafe(`Task state entry is a symlink: ${path}`, 'Replace it with a regular file; Pappra does not follow redirected task state.');
189
+ }
190
+ if (observation.value.kind !== 'file') {
191
+ return unsafe(`Task state entry is not a regular file: ${path}`, 'Inspect the private task-state path; nothing was read or written.');
192
+ }
193
+ if (expected === 'missing') {
194
+ return unsafe(`Task state entry appeared unexpectedly: ${path}`, 'Inspect the private task-state path; nothing was written.');
195
+ }
196
+ const allowed = scope === 'local' ? snapshot.local : snapshot.pappra;
197
+ if (allowed === null) {
198
+ return unsafe('Private task-state directory is unavailable.', 'Inspect .pappra/local; nothing was written.');
199
+ }
200
+ const real = await resolveRealpath(path);
201
+ const parent = await resolveRealpath(dirname(path));
202
+ if (real === null ||
203
+ parent === null ||
204
+ (!samePath(parent, allowed.real) && !isPathInside(allowed.real, parent)) ||
205
+ !isPathInside(allowed.real, real) ||
206
+ samePath(allowed.real, real)) {
207
+ return unsafe(`Task state entry resolves outside the pinned private directory: ${path}`, 'Inspect .pappra/local; nothing was written.');
208
+ }
209
+ return succeeded(null);
210
+ }
211
+ /**
212
+ * Removes a temporary file only while the pinned namespace is still the one we
213
+ * wrote into. A redirected namespace leaves the residual in place for
214
+ * inspection instead of deleting through the new path.
215
+ */
216
+ async function cleanupTemporary(canonical, snapshot, temporary, scope = 'local') {
217
+ const stable = await assertSnapshot(canonical, snapshot);
218
+ if (!stable.ok)
219
+ return;
220
+ const inside = await verifyEntry(snapshot, temporary, 'file-or-missing', scope);
221
+ if (!inside.ok)
222
+ return;
223
+ await rm(temporary, { force: true }).catch(() => undefined);
224
+ }
225
+ /**
226
+ * Publishes one owned file with revalidation around the write and rename, and
227
+ * reads the result back. The last check-to-syscall window is cooperative, as
228
+ * described on `namespaceSnapshot`.
229
+ */
230
+ async function writeTaskStateFile(canonical, snapshot, path, bytes, scope = 'local', guard) {
231
+ const temporary = `${path}.tmp-${randomUUID()}`;
232
+ const before = await assertSnapshot(canonical, snapshot);
233
+ if (!before.ok)
234
+ return before;
235
+ if (guard !== undefined) {
236
+ const current = await guard();
237
+ if (!current.ok)
238
+ return current;
239
+ }
240
+ try {
241
+ await writeFile(temporary, bytes, { mode: 0o600, flag: 'wx' });
242
+ }
243
+ catch (error) {
244
+ return failed(diagnosticCodes.fileUnreadable, `Cannot write task state: ${errorMessage(error)}`, { nextAction: 'Inspect permissions and the private task-state path.' });
245
+ }
246
+ const written = await assertSnapshot(canonical, snapshot);
247
+ if (!written.ok) {
248
+ await cleanupTemporary(canonical, snapshot, temporary, scope);
249
+ return written;
250
+ }
251
+ const placed = await verifyEntry(snapshot, temporary, 'file', scope);
252
+ if (!placed.ok) {
253
+ await cleanupTemporary(canonical, snapshot, temporary, scope);
254
+ return placed;
255
+ }
256
+ const beforeRename = await assertSnapshot(canonical, snapshot);
257
+ if (!beforeRename.ok) {
258
+ await cleanupTemporary(canonical, snapshot, temporary, scope);
259
+ return beforeRename;
260
+ }
261
+ if (guard !== undefined) {
262
+ const current = await guard();
263
+ if (!current.ok) {
264
+ await cleanupTemporary(canonical, snapshot, temporary, scope);
265
+ return current;
266
+ }
267
+ }
268
+ try {
269
+ await rename(temporary, path);
270
+ }
271
+ catch (error) {
272
+ await cleanupTemporary(canonical, snapshot, temporary, scope);
273
+ return failed(diagnosticCodes.fileUnreadable, `Cannot publish task state: ${errorMessage(error)}`, {
274
+ nextAction: 'Inspect the private task-state path; a residual may remain.',
275
+ });
276
+ }
277
+ const afterRename = await assertSnapshot(canonical, snapshot);
278
+ if (!afterRename.ok) {
279
+ return unsafe('Private task-state namespace changed after publication; verify the file before relying on it.', 'Inspect .pappra/local and the task state; no cleanup was attempted.');
280
+ }
281
+ const published = await verifyEntry(snapshot, path, 'file', scope);
282
+ if (!published.ok)
283
+ return published;
284
+ const readBack = await readTextFileBounded(path, 1024 * 1024);
285
+ if (!readBack.ok || !readBack.value.bytes.equals(bytes)) {
286
+ return failed(readBack.ok ? diagnosticCodes.fileUnreadable : readBack.code, readBack.ok
287
+ ? 'Task state bytes changed immediately after publication.'
288
+ : readBack.message, {
289
+ nextAction: 'Inspect the private task-state file before relying on it.',
290
+ });
291
+ }
292
+ return succeeded(null);
293
+ }
294
+ export async function readTaskPin(root) {
295
+ const canonical = await canonicalWorkspaceRoot(root);
296
+ if (canonical === null)
297
+ return null;
298
+ const snapshot = await namespaceSnapshot(canonical);
299
+ if (!snapshot.ok)
300
+ return null;
301
+ const target = taskStatePaths(canonical).pinPath;
302
+ const boundary = await privateTaskStateBoundary(canonical, target);
303
+ if (!boundary.ok)
304
+ return null;
305
+ const placed = await verifyEntry(snapshot.value, target, 'file-or-missing');
306
+ if (!placed.ok)
307
+ return null;
308
+ const read = await readTextFileBounded(target, 64 * 1024);
309
+ if (!read.ok)
310
+ return null;
311
+ const after = await namespaceSnapshot(canonical);
312
+ if (!after.ok || !sameSnapshot(snapshot.value, after.value))
313
+ return null;
314
+ const stable = await verifyEntry(snapshot.value, target, 'file-or-missing');
315
+ if (!stable.ok)
316
+ return null;
317
+ try {
318
+ const value = JSON.parse(read.value.text);
319
+ if (typeof value !== 'object' ||
320
+ value === null ||
321
+ !('schema' in value) ||
322
+ value.schema !== TASK_PIN_SCHEMA ||
323
+ !('taskId' in value) ||
324
+ typeof value.taskId !== 'string' ||
325
+ !isValidTaskId(value.taskId)) {
326
+ return null;
327
+ }
328
+ return value;
329
+ }
330
+ catch {
331
+ return null;
332
+ }
333
+ }
334
+ export async function pinTask(root, taskId) {
335
+ if (!isValidTaskId(taskId)) {
336
+ return {
337
+ ok: false,
338
+ message: 'task id must be at most 64 characters: a letter or digit followed by letters, digits, dot, dash or underscore.',
339
+ };
340
+ }
341
+ const canonical = await canonicalWorkspaceRoot(root);
342
+ if (canonical === null) {
343
+ return { ok: false, message: `Cannot resolve workspace root: ${root}` };
344
+ }
345
+ const target = taskStatePaths(canonical).pinPath;
346
+ const boundary = await privateTaskStateBoundary(canonical, target);
347
+ if (!boundary.ok)
348
+ return { ok: false, message: boundary.message };
349
+ const privateState = await ensurePrivateState(canonical);
350
+ if (!privateState.ok)
351
+ return { ok: false, message: privateState.message };
352
+ const initial = await namespaceSnapshot(canonical);
353
+ if (!initial.ok)
354
+ return { ok: false, message: initial.message };
355
+ try {
356
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
357
+ }
358
+ catch (error) {
359
+ return {
360
+ ok: false,
361
+ message: `Cannot create task state directory: ${errorMessage(error)}`,
362
+ };
363
+ }
364
+ const prepared = await namespaceSnapshot(canonical);
365
+ if (!prepared.ok)
366
+ return { ok: false, message: prepared.message };
367
+ if (prepared.value.local === null ||
368
+ (initial.value.pappra !== null &&
369
+ !sameFile(initial.value.pappra, prepared.value.pappra)) ||
370
+ (initial.value.local !== null &&
371
+ !sameFile(initial.value.local, prepared.value.local))) {
372
+ return {
373
+ ok: false,
374
+ message: 'Private task-state directory changed while it was being created; nothing was written.',
375
+ };
376
+ }
377
+ const pin = {
378
+ schema: TASK_PIN_SCHEMA,
379
+ taskId,
380
+ pinnedAt: new Date().toISOString(),
381
+ };
382
+ const written = await writeTaskStateFile(canonical, prepared.value, target, Buffer.from(`${JSON.stringify(pin, null, 2)}\n`, 'utf8'));
383
+ if (!written.ok)
384
+ return { ok: false, message: written.message };
385
+ return pin;
386
+ }
387
+ export async function unpinTask(root) {
388
+ const canonical = await canonicalWorkspaceRoot(root);
389
+ if (canonical === null) {
390
+ return { ok: false, message: `Cannot resolve workspace root: ${root}` };
391
+ }
392
+ const target = taskStatePaths(canonical).pinPath;
393
+ const boundary = await privateTaskStateBoundary(canonical, target);
394
+ if (!boundary.ok)
395
+ return { ok: false, message: boundary.message };
396
+ const snapshot = await namespaceSnapshot(canonical);
397
+ if (!snapshot.ok)
398
+ return { ok: false, message: snapshot.message };
399
+ const presence = await observe(target);
400
+ if (!presence.ok)
401
+ return { ok: false, message: presence.message };
402
+ if (presence.value.kind !== 'missing') {
403
+ const placed = await verifyEntry(snapshot.value, target, 'file');
404
+ if (!placed.ok)
405
+ return { ok: false, message: placed.message };
406
+ }
407
+ const before = await assertSnapshot(canonical, snapshot.value);
408
+ if (!before.ok)
409
+ return { ok: false, message: before.message };
410
+ if (presence.value.kind !== 'missing') {
411
+ const quarantine = `${target}.removed-${randomUUID()}`;
412
+ try {
413
+ // Move the pin aside inside the pinned directory first. A redirect that
414
+ // lands exactly at the unlink still cannot destroy an unrelated outside
415
+ // file that already uses the pin name.
416
+ await rename(target, quarantine);
417
+ }
418
+ catch (error) {
419
+ return {
420
+ ok: false,
421
+ message: `Cannot remove task pin: ${errorMessage(error)}`,
422
+ };
423
+ }
424
+ const afterMove = await namespaceSnapshot(canonical);
425
+ if (!afterMove.ok || !sameSnapshot(snapshot.value, afterMove.value)) {
426
+ return {
427
+ ok: false,
428
+ message: 'Private task-state namespace changed while the pin was being removed; inspect .pappra/local and the quarantined pin before retrying.',
429
+ };
430
+ }
431
+ const moved = await verifyEntry(snapshot.value, quarantine, 'file');
432
+ if (!moved.ok)
433
+ return { ok: false, message: moved.message };
434
+ const beforeDelete = await assertSnapshot(canonical, snapshot.value);
435
+ if (!beforeDelete.ok)
436
+ return { ok: false, message: beforeDelete.message };
437
+ try {
438
+ await rm(quarantine, { force: true });
439
+ }
440
+ catch (error) {
441
+ return {
442
+ ok: false,
443
+ message: `Cannot remove task pin: ${errorMessage(error)}`,
444
+ };
445
+ }
446
+ const afterDelete = await namespaceSnapshot(canonical);
447
+ if (!afterDelete.ok || !sameSnapshot(snapshot.value, afterDelete.value)) {
448
+ return {
449
+ ok: false,
450
+ message: 'Private task-state namespace changed while the pin was being removed; verify .pappra/local before retrying.',
451
+ };
452
+ }
453
+ }
454
+ return { ok: true };
455
+ }
456
+ /**
457
+ * Stores the personal hook preference in ignored checkout-local state. Shared
458
+ * workspace configuration is read for identity but is never rewritten.
459
+ */
460
+ export async function setAutoResume(root, enabled) {
461
+ const canonical = await canonicalWorkspaceRoot(root);
462
+ if (canonical === null)
463
+ return { ok: false, message: `Cannot resolve workspace root: ${root}` };
464
+ const paths = taskStatePaths(canonical);
465
+ const boundary = await configBoundary(canonical, paths.configPath);
466
+ if (!boundary.ok)
467
+ return { ok: false, message: boundary.message };
468
+ const inspection = await inspectWorkspaceConfig(canonical);
469
+ if (!inspection.value)
470
+ return {
471
+ ok: false,
472
+ message: 'Workspace configuration is not usable; no preference was changed.',
473
+ };
474
+ const prepared = await ensurePrivateState(canonical);
475
+ if (!prepared.ok)
476
+ return { ok: false, message: prepared.message };
477
+ const snapshot = await namespaceSnapshot(canonical);
478
+ if (!snapshot.ok)
479
+ return { ok: false, message: snapshot.message };
480
+ const lock = await acquireInstallLock(workspaceInstallPaths(canonical).lockPath, 'set-auto-resume', '.pappra/local/task-preferences.json', canonical);
481
+ if (!lock.ok || !lock.lock)
482
+ return {
483
+ ok: false,
484
+ message: lock.message ?? 'Task preferences are locked.',
485
+ };
486
+ let result;
487
+ try {
488
+ result = await (async () => {
489
+ const existing = await readPreferences(canonical, snapshot.value, inspection.value.workspaceId);
490
+ if (!existing.ok)
491
+ return { ok: false, message: existing.message };
492
+ if (existing.value.preferences?.autoResume === enabled)
493
+ return { ok: true };
494
+ const guard = async () => {
495
+ const stable = await assertSnapshot(canonical, snapshot.value);
496
+ if (!stable.ok)
497
+ return stable;
498
+ const config = await inspectWorkspaceConfig(canonical);
499
+ if (config.value?.workspaceId !== inspection.value.workspaceId)
500
+ return unsafe('Workspace identity changed before preference publication.', 'Retry in the intended workspace.');
501
+ const current = await readPreferences(canonical, snapshot.value, inspection.value.workspaceId);
502
+ if (!current.ok)
503
+ return current;
504
+ return current.value.digest === existing.value.digest
505
+ ? succeeded(null)
506
+ : unsafe('Task preferences changed before publication; the update was abandoned.', 'Review the private preference and retry explicitly.');
507
+ };
508
+ const preferences = {
509
+ schema: TASK_PREFERENCES_SCHEMA,
510
+ workspaceId: inspection.value.workspaceId,
511
+ autoResume: enabled,
512
+ };
513
+ const written = await writeTaskStateFile(canonical, snapshot.value, paths.preferencesPath, Buffer.from(`${JSON.stringify(preferences, null, 2)}\n`), 'local', guard);
514
+ return written.ok
515
+ ? { ok: true }
516
+ : { ok: false, message: written.message };
517
+ })();
518
+ }
519
+ catch (error) {
520
+ result = {
521
+ ok: false,
522
+ message: `Cannot update task preferences: ${errorMessage(error)}`,
523
+ };
524
+ }
525
+ const stable = await assertSnapshot(canonical, snapshot.value);
526
+ if (!stable.ok)
527
+ return {
528
+ ok: false,
529
+ message: `${stable.message} The preference lock was retained as evidence.`,
530
+ };
531
+ const released = await releaseInstallLock(lock.path, lock.lock.token);
532
+ if (!released.ok)
533
+ return {
534
+ ok: false,
535
+ message: `Task preference may have committed, but its lock was not released: ${released.message}`,
536
+ };
537
+ return result;
538
+ }
539
+ async function readPreferences(canonical, snapshot, workspaceId) {
540
+ const path = taskStatePaths(canonical).preferencesPath;
541
+ const stable = await assertSnapshot(canonical, snapshot);
542
+ if (!stable.ok)
543
+ return stable;
544
+ const boundary = await privateTaskStateBoundary(canonical, path);
545
+ if (!boundary.ok)
546
+ return boundary;
547
+ const placed = await verifyEntry(snapshot, path, 'file-or-missing');
548
+ if (!placed.ok)
549
+ return placed;
550
+ const presence = await observe(path);
551
+ if (!presence.ok)
552
+ return presence;
553
+ if (presence.value.kind === 'missing')
554
+ return succeeded({ preferences: null, digest: null });
555
+ const read = await readTextFileBounded(path, 64 * 1024);
556
+ if (!read.ok)
557
+ return read;
558
+ const after = await assertSnapshot(canonical, snapshot);
559
+ if (!after.ok)
560
+ return after;
561
+ const verified = await verifyEntry(snapshot, path, 'file');
562
+ if (!verified.ok)
563
+ return verified;
564
+ try {
565
+ const parsed = taskPreferencesSchema.safeParse(JSON.parse(read.value.text));
566
+ if (!parsed.success || parsed.data.workspaceId !== workspaceId)
567
+ return unsafe('Private task preferences have an invalid schema or foreign workspace identity.', 'Inspect .pappra/local/task-preferences.json; no shared configuration is modified.');
568
+ return succeeded({ preferences: parsed.data, digest: read.value.digest });
569
+ }
570
+ catch {
571
+ return unsafe('Private task preferences are not valid JSON.', 'Inspect .pappra/local/task-preferences.json; its bytes were preserved.');
572
+ }
573
+ }
574
+ export async function readAutoResume(root) {
575
+ const canonical = await canonicalWorkspaceRoot(root);
576
+ if (canonical === null)
577
+ return false;
578
+ const paths = taskStatePaths(canonical);
579
+ const boundary = await configBoundary(canonical, paths.configPath);
580
+ if (!boundary.ok)
581
+ return false;
582
+ const inspection = await inspectWorkspaceConfig(canonical);
583
+ if (!inspection.value)
584
+ return false;
585
+ const snapshot = await namespaceSnapshot(canonical);
586
+ if (!snapshot.ok)
587
+ return false;
588
+ const read = await readPreferences(canonical, snapshot.value, inspection.value.workspaceId);
589
+ return read.ok && read.value.preferences?.autoResume === true;
590
+ }
@@ -0,0 +1 @@
1
+ export * from './app/doctor.js';
package/dist/doctor.js ADDED
@@ -0,0 +1,6 @@
1
+ // Core entrypoint for passive diagnostics.
2
+ //
3
+ // The implementation lives in `src/app/doctor.ts`; this barrel keeps the
4
+ // published `@phyzess/pappra/doctor` path independent of the internal `app/`
5
+ // layout.
6
+ export * from './app/doctor.js';
@@ -0,0 +1,13 @@
1
+ export * as collaboration from '../app/collaboration.js';
2
+ export * as collabAdmission from '../collab/admission.js';
3
+ export * as collabRolePlan from '../app/collab-role-plan.js';
4
+ export * as collabRoleOrchestration from '../app/collab-role-orchestration.js';
5
+ export * as collabAutomaticHandoff from '../app/collab-auto-handoff.js';
6
+ export * as collabWorkPlanInspection from '../app/collab-work-plan.js';
7
+ export * as collabWorkExecution from '../app/collab-work-execution.js';
8
+ export * as collabRecovery from '../app/collab-recovery.js';
9
+ export * as collabRecoveryProjection from '../app/collab-recovery-projection.js';
10
+ export * as collabRecoveryExecutor from '../app/collab-recovery-executor.js';
11
+ export * as collabSessionTransition from '../app/collab-session-transition.js';
12
+ export * as collabWorkPlan from '../collab/work-plan.js';
13
+ export * from './pi.js';
@@ -0,0 +1,25 @@
1
+ // Curated experimental surface for the 0.1.0 beta.
2
+ //
3
+ // The beta publishes a compact map: the core subpaths, this barrel, and its Pi
4
+ // adapter (`pappra/experimental/pi`). Collaboration and managed-Pi
5
+ // implementations stay private to `src/app` and `src/hosts`, and the
6
+ // namespaces below are the supported way to reach them — so those files may
7
+ // move without a new published path to maintain.
8
+ //
9
+ // There is no per-surface subpath and no compatibility shim for the narrower
10
+ // beta map: import the namespace you need from here, or from the Pi adapter.
11
+ export * as collaboration from '../app/collaboration.js';
12
+ export * as collabAdmission from '../collab/admission.js';
13
+ export * as collabRolePlan from '../app/collab-role-plan.js';
14
+ export * as collabRoleOrchestration from '../app/collab-role-orchestration.js';
15
+ export * as collabAutomaticHandoff from '../app/collab-auto-handoff.js';
16
+ export * as collabWorkPlanInspection from '../app/collab-work-plan.js';
17
+ export * as collabWorkExecution from '../app/collab-work-execution.js';
18
+ export * as collabRecovery from '../app/collab-recovery.js';
19
+ export * as collabRecoveryProjection from '../app/collab-recovery-projection.js';
20
+ export * as collabRecoveryExecutor from '../app/collab-recovery-executor.js';
21
+ export * as collabSessionTransition from '../app/collab-session-transition.js';
22
+ export * as collabWorkPlan from '../collab/work-plan.js';
23
+ // Managed-Pi surfaces have their own entrypoint, and are composed here so one
24
+ // import covers the whole experimental surface.
25
+ export * from './pi.js';
@@ -0,0 +1,4 @@
1
+ export * as piManaged from '../hosts/pi/managed-session.js';
2
+ export * as piManagedSdk from '../hosts/pi/managed-sdk-session.js';
3
+ export * as piRecovery from '../adapters/pi/recovery-extension.js';
4
+ export * as piSessionTransition from '../adapters/pi/session-transition-extension.js';
@@ -0,0 +1,12 @@
1
+ // Pi adapter barrel: managed-Pi runtime and opt-in host extensions.
2
+ //
3
+ // Exposed as `@phyzess/pappra/experimental/pi` because these namespaces pin an
4
+ // observed Pi integration and are the most likely to change between beta
5
+ // releases. The native managed-Pi controllers stay under `src/hosts`; the
6
+ // recovery and transition composition lives in `src/adapters/pi`. This barrel
7
+ // claims no native capability that the pinned host version has not been
8
+ // observed to provide.
9
+ export * as piManaged from '../hosts/pi/managed-session.js';
10
+ export * as piManagedSdk from '../hosts/pi/managed-sdk-session.js';
11
+ export * as piRecovery from '../adapters/pi/recovery-extension.js';
12
+ export * as piSessionTransition from '../adapters/pi/session-transition-extension.js';
@@ -1,4 +1,4 @@
1
- import { errorCode, object, parseJson } from '../../collab/validation.js';
1
+ import { errorCode, object, parseJson } from '../../infra/validation.js';
2
2
  import { spawn } from 'node:child_process';
3
3
  import { createHash } from 'node:crypto';
4
4
  import fs from 'node:fs';
@@ -1,4 +1,4 @@
1
- import { object, parseJson, errorCode } from '../../collab/validation.js';
1
+ import { object, parseJson, errorCode } from '../../infra/validation.js';
2
2
  import { isRecord, } from './protocol.js';
3
3
  function errorDetail(error) {
4
4
  if (object(error) && typeof error['detail'] === 'string')