@edspencer/paddock 0.0.1 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +69 -0
  2. package/install-notice.mjs +29 -0
  3. package/package.json +58 -5
  4. package/packages/server/dist/adoptable.d.ts +127 -0
  5. package/packages/server/dist/adoptable.js +315 -0
  6. package/packages/server/dist/app.d.ts +67 -0
  7. package/packages/server/dist/app.js +325 -0
  8. package/packages/server/dist/archive.d.ts +42 -0
  9. package/packages/server/dist/archive.js +125 -0
  10. package/packages/server/dist/attachments-config.d.ts +114 -0
  11. package/packages/server/dist/attachments-config.js +165 -0
  12. package/packages/server/dist/attachments-hint.d.ts +53 -0
  13. package/packages/server/dist/attachments-hint.js +140 -0
  14. package/packages/server/dist/attachments.d.ts +33 -0
  15. package/packages/server/dist/attachments.js +117 -0
  16. package/packages/server/dist/auth.d.ts +84 -0
  17. package/packages/server/dist/auth.js +247 -0
  18. package/packages/server/dist/background.d.ts +36 -0
  19. package/packages/server/dist/background.js +137 -0
  20. package/packages/server/dist/bind-safety.d.ts +56 -0
  21. package/packages/server/dist/bind-safety.js +51 -0
  22. package/packages/server/dist/brand.d.ts +44 -0
  23. package/packages/server/dist/brand.js +88 -0
  24. package/packages/server/dist/chat-dto.d.ts +210 -0
  25. package/packages/server/dist/chat-dto.js +308 -0
  26. package/packages/server/dist/cli/import-chats.d.ts +21 -0
  27. package/packages/server/dist/cli/import-chats.js +197 -0
  28. package/packages/server/dist/cli/paddock.d.ts +2 -0
  29. package/packages/server/dist/cli/paddock.js +247 -0
  30. package/packages/server/dist/concurrency.d.ts +57 -0
  31. package/packages/server/dist/concurrency.js +71 -0
  32. package/packages/server/dist/config.d.ts +538 -0
  33. package/packages/server/dist/config.js +625 -0
  34. package/packages/server/dist/curation-config.d.ts +55 -0
  35. package/packages/server/dist/curation-config.js +64 -0
  36. package/packages/server/dist/environment-prompt.d.ts +52 -0
  37. package/packages/server/dist/environment-prompt.js +61 -0
  38. package/packages/server/dist/event-bus.d.ts +87 -0
  39. package/packages/server/dist/event-bus.js +48 -0
  40. package/packages/server/dist/git.d.ts +128 -0
  41. package/packages/server/dist/git.js +446 -0
  42. package/packages/server/dist/github-auth.d.ts +44 -0
  43. package/packages/server/dist/github-auth.js +169 -0
  44. package/packages/server/dist/herdctl-agent-config.d.ts +98 -0
  45. package/packages/server/dist/herdctl-agent-config.js +331 -0
  46. package/packages/server/dist/herdctl-agent-names.d.ts +172 -0
  47. package/packages/server/dist/herdctl-agent-names.js +228 -0
  48. package/packages/server/dist/herdctl-jobs-index.d.ts +65 -0
  49. package/packages/server/dist/herdctl-jobs-index.js +225 -0
  50. package/packages/server/dist/herdctl-jobs.d.ts +98 -0
  51. package/packages/server/dist/herdctl-jobs.js +225 -0
  52. package/packages/server/dist/herdctl.d.ts +623 -0
  53. package/packages/server/dist/herdctl.js +1279 -0
  54. package/packages/server/dist/hook-config.d.ts +217 -0
  55. package/packages/server/dist/hook-config.js +224 -0
  56. package/packages/server/dist/http-bytes.d.ts +24 -0
  57. package/packages/server/dist/http-bytes.js +52 -0
  58. package/packages/server/dist/index.d.ts +1 -0
  59. package/packages/server/dist/index.js +14 -0
  60. package/packages/server/dist/instance-config.d.ts +143 -0
  61. package/packages/server/dist/instance-config.js +385 -0
  62. package/packages/server/dist/localcommand.d.ts +23 -0
  63. package/packages/server/dist/localcommand.js +140 -0
  64. package/packages/server/dist/management-auth.d.ts +99 -0
  65. package/packages/server/dist/management-auth.js +165 -0
  66. package/packages/server/dist/management-config.d.ts +164 -0
  67. package/packages/server/dist/management-config.js +286 -0
  68. package/packages/server/dist/management-mcp-server.d.ts +20 -0
  69. package/packages/server/dist/management-mcp-server.js +95 -0
  70. package/packages/server/dist/management-metadata.d.ts +67 -0
  71. package/packages/server/dist/management-metadata.js +56 -0
  72. package/packages/server/dist/management-ops.d.ts +110 -0
  73. package/packages/server/dist/management-ops.js +449 -0
  74. package/packages/server/dist/management-policy.d.ts +172 -0
  75. package/packages/server/dist/management-policy.js +259 -0
  76. package/packages/server/dist/message-provenance.d.ts +112 -0
  77. package/packages/server/dist/message-provenance.js +260 -0
  78. package/packages/server/dist/models.d.ts +164 -0
  79. package/packages/server/dist/models.js +208 -0
  80. package/packages/server/dist/openapi.d.ts +66 -0
  81. package/packages/server/dist/openapi.js +183 -0
  82. package/packages/server/dist/parent-detach.d.ts +30 -0
  83. package/packages/server/dist/parent-detach.js +109 -0
  84. package/packages/server/dist/preload.d.ts +43 -0
  85. package/packages/server/dist/preload.js +55 -0
  86. package/packages/server/dist/project-files.d.ts +76 -0
  87. package/packages/server/dist/project-files.js +191 -0
  88. package/packages/server/dist/project-mime.d.ts +35 -0
  89. package/packages/server/dist/project-mime.js +55 -0
  90. package/packages/server/dist/project-paths.d.ts +88 -0
  91. package/packages/server/dist/project-paths.js +126 -0
  92. package/packages/server/dist/project-types.d.ts +316 -0
  93. package/packages/server/dist/project-types.js +48 -0
  94. package/packages/server/dist/projects.d.ts +202 -0
  95. package/packages/server/dist/projects.js +894 -0
  96. package/packages/server/dist/queued-message.d.ts +35 -0
  97. package/packages/server/dist/queued-message.js +116 -0
  98. package/packages/server/dist/read-state.d.ts +46 -0
  99. package/packages/server/dist/read-state.js +139 -0
  100. package/packages/server/dist/recovery-config.d.ts +111 -0
  101. package/packages/server/dist/recovery-config.js +105 -0
  102. package/packages/server/dist/recovery.d.ts +262 -0
  103. package/packages/server/dist/recovery.js +490 -0
  104. package/packages/server/dist/resume-drain.d.ts +122 -0
  105. package/packages/server/dist/resume-drain.js +158 -0
  106. package/packages/server/dist/route-context.d.ts +168 -0
  107. package/packages/server/dist/route-context.js +39 -0
  108. package/packages/server/dist/route-errors.d.ts +8 -0
  109. package/packages/server/dist/route-errors.js +13 -0
  110. package/packages/server/dist/routes/chats.d.ts +12 -0
  111. package/packages/server/dist/routes/chats.js +1406 -0
  112. package/packages/server/dist/routes/git.d.ts +13 -0
  113. package/packages/server/dist/routes/git.js +236 -0
  114. package/packages/server/dist/routes/mcp.d.ts +83 -0
  115. package/packages/server/dist/routes/mcp.js +250 -0
  116. package/packages/server/dist/routes/meta.d.ts +15 -0
  117. package/packages/server/dist/routes/meta.js +447 -0
  118. package/packages/server/dist/routes/projects.d.ts +25 -0
  119. package/packages/server/dist/routes/projects.js +736 -0
  120. package/packages/server/dist/routes/triggers.d.ts +21 -0
  121. package/packages/server/dist/routes/triggers.js +324 -0
  122. package/packages/server/dist/routes/workspace-mount.d.ts +48 -0
  123. package/packages/server/dist/routes/workspace-mount.js +32 -0
  124. package/packages/server/dist/routes.d.ts +26 -0
  125. package/packages/server/dist/routes.js +29 -0
  126. package/packages/server/dist/run-provenance.d.ts +109 -0
  127. package/packages/server/dist/run-provenance.js +206 -0
  128. package/packages/server/dist/runs.d.ts +92 -0
  129. package/packages/server/dist/runs.js +72 -0
  130. package/packages/server/dist/schedule-config.d.ts +68 -0
  131. package/packages/server/dist/schedule-config.js +75 -0
  132. package/packages/server/dist/schedule-session.d.ts +34 -0
  133. package/packages/server/dist/schedule-session.js +120 -0
  134. package/packages/server/dist/self-mcp-descriptions.d.ts +20 -0
  135. package/packages/server/dist/self-mcp-descriptions.js +145 -0
  136. package/packages/server/dist/self-mcp-read.d.ts +15 -0
  137. package/packages/server/dist/self-mcp-read.js +97 -0
  138. package/packages/server/dist/self-mcp-triggers.d.ts +30 -0
  139. package/packages/server/dist/self-mcp-triggers.js +144 -0
  140. package/packages/server/dist/self-mcp-types.d.ts +248 -0
  141. package/packages/server/dist/self-mcp-types.js +10 -0
  142. package/packages/server/dist/self-mcp-util.d.ts +73 -0
  143. package/packages/server/dist/self-mcp-util.js +157 -0
  144. package/packages/server/dist/self-mcp-write.d.ts +35 -0
  145. package/packages/server/dist/self-mcp-write.js +215 -0
  146. package/packages/server/dist/self-mcp.d.ts +95 -0
  147. package/packages/server/dist/self-mcp.js +448 -0
  148. package/packages/server/dist/send-file-mcp.d.ts +46 -0
  149. package/packages/server/dist/send-file-mcp.js +281 -0
  150. package/packages/server/dist/session-hub.d.ts +186 -0
  151. package/packages/server/dist/session-hub.js +291 -0
  152. package/packages/server/dist/spawn-capability.d.ts +99 -0
  153. package/packages/server/dist/spawn-capability.js +106 -0
  154. package/packages/server/dist/star.d.ts +26 -0
  155. package/packages/server/dist/star.js +94 -0
  156. package/packages/server/dist/start.d.ts +1 -0
  157. package/packages/server/dist/start.js +21 -0
  158. package/packages/server/dist/subagents.d.ts +337 -0
  159. package/packages/server/dist/subagents.js +626 -0
  160. package/packages/server/dist/sweep.d.ts +122 -0
  161. package/packages/server/dist/sweep.js +696 -0
  162. package/packages/server/dist/tooldetails.d.ts +33 -0
  163. package/packages/server/dist/tooldetails.js +466 -0
  164. package/packages/server/dist/transcribe.d.ts +69 -0
  165. package/packages/server/dist/transcribe.js +119 -0
  166. package/packages/server/dist/transcripts.d.ts +42 -0
  167. package/packages/server/dist/transcripts.js +164 -0
  168. package/packages/server/dist/trigger-config.d.ts +278 -0
  169. package/packages/server/dist/trigger-config.js +417 -0
  170. package/packages/server/dist/trigger-runtime.d.ts +94 -0
  171. package/packages/server/dist/trigger-runtime.js +82 -0
  172. package/packages/server/dist/trigger-session.d.ts +34 -0
  173. package/packages/server/dist/trigger-session.js +122 -0
  174. package/packages/server/dist/triggers.d.ts +83 -0
  175. package/packages/server/dist/triggers.js +124 -0
  176. package/packages/server/dist/trusted-proxy.d.ts +57 -0
  177. package/packages/server/dist/trusted-proxy.js +157 -0
  178. package/packages/server/dist/turn-notice.d.ts +154 -0
  179. package/packages/server/dist/turn-notice.js +271 -0
  180. package/packages/server/dist/unread.d.ts +36 -0
  181. package/packages/server/dist/unread.js +123 -0
  182. package/packages/server/dist/usage.d.ts +66 -0
  183. package/packages/server/dist/usage.js +243 -0
  184. package/packages/server/dist/wake-injection.d.ts +156 -0
  185. package/packages/server/dist/wake-injection.js +136 -0
  186. package/packages/server/dist/whisper-local.d.ts +9 -0
  187. package/packages/server/dist/whisper-local.js +122 -0
  188. package/packages/server/dist/ws-context.d.ts +158 -0
  189. package/packages/server/dist/ws-context.js +1 -0
  190. package/packages/server/dist/ws-protocol.d.ts +423 -0
  191. package/packages/server/dist/ws-protocol.js +251 -0
  192. package/packages/server/dist/ws-self-mcp.d.ts +52 -0
  193. package/packages/server/dist/ws-self-mcp.js +28 -0
  194. package/packages/server/dist/ws-triggers.d.ts +29 -0
  195. package/packages/server/dist/ws-triggers.js +261 -0
  196. package/packages/server/dist/ws-turn.d.ts +86 -0
  197. package/packages/server/dist/ws-turn.js +751 -0
  198. package/packages/server/dist/ws.d.ts +62 -0
  199. package/packages/server/dist/ws.js +1024 -0
  200. package/packages/server/package.json +6 -0
  201. package/packages/web/dist/assets/InstanceConfigPage-Bypd_quL.js +2 -0
  202. package/packages/web/dist/assets/MarkdownRenderer-Co8w9iuX.js +30 -0
  203. package/packages/web/dist/assets/ProjectRedirect-DXrA24Cy.js +2 -0
  204. package/packages/web/dist/assets/ProjectView-B9opP7Sv.js +13 -0
  205. package/packages/web/dist/assets/ProjectsGrid-D2cwb2jY.js +2 -0
  206. package/packages/web/dist/assets/arc-B--FIRB8.js +2 -0
  207. package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-B6dRWZZ6.js +37 -0
  208. package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
  209. package/packages/web/dist/assets/blockDiagram-GPEHLZMM-DLxFAvvf.js +133 -0
  210. package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
  211. package/packages/web/dist/assets/c4Diagram-AAUBKEIU-CNee5ks4.js +11 -0
  212. package/packages/web/dist/assets/channel-Bw0A52CB.js +2 -0
  213. package/packages/web/dist/assets/chunk-2J33WTMH-DObw03Xl.js +2 -0
  214. package/packages/web/dist/assets/chunk-4BX2VUAB-CH2SR6nN.js +2 -0
  215. package/packages/web/dist/assets/chunk-55IACEB6-WlVpegT3.js +2 -0
  216. package/packages/web/dist/assets/chunk-727SXJPM-g8GT9Rgd.js +207 -0
  217. package/packages/web/dist/assets/chunk-AQP2D5EJ-DbN4PsDF.js +232 -0
  218. package/packages/web/dist/assets/chunk-FMBD7UC4-DSN3BjFG.js +16 -0
  219. package/packages/web/dist/assets/chunk-ND2GUHAM-D9R4le0z.js +2 -0
  220. package/packages/web/dist/assets/chunk-QZHKN3VN-D4YgRaKH.js +2 -0
  221. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-itf_kt-q.js +2 -0
  222. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-itf_kt-q.js +2 -0
  223. package/packages/web/dist/assets/core-CysJ2gLj.js +4 -0
  224. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-D1PB0KNu.js +2 -0
  225. package/packages/web/dist/assets/cpp-Bm3ii6kY.js +2 -0
  226. package/packages/web/dist/assets/csharp-BNpuYZMG.js +2 -0
  227. package/packages/web/dist/assets/css-DazXZka4.js +2 -0
  228. package/packages/web/dist/assets/cytoscape.esm-CUqq0XTU.js +332 -0
  229. package/packages/web/dist/assets/dagre-BM42HDAG-C6Uuq-dq.js +5 -0
  230. package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
  231. package/packages/web/dist/assets/diagram-2AECGRRQ-C9JvQ425.js +44 -0
  232. package/packages/web/dist/assets/diagram-5GNKFQAL-Cu6pdh2d.js +11 -0
  233. package/packages/web/dist/assets/diagram-KO2AKTUF-PmXcUQhY.js +4 -0
  234. package/packages/web/dist/assets/diagram-LMA3HP47-9ww6_9Iq.js +25 -0
  235. package/packages/web/dist/assets/diagram-OG6HWLK6-DWQVjv_n.js +25 -0
  236. package/packages/web/dist/assets/erDiagram-TEJ5UH35-6F7xTuff.js +86 -0
  237. package/packages/web/dist/assets/flowDiagram-I6XJVG4X-SCiGu7m6.js +163 -0
  238. package/packages/web/dist/assets/format-yf7Vf9kR.js +2 -0
  239. package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BNpPwxkL.js +293 -0
  240. package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-C0gyAJjW.js +107 -0
  241. package/packages/web/dist/assets/go-BPx_y7j8.js +2 -0
  242. package/packages/web/dist/assets/graph-BlwPajkw.js +2 -0
  243. package/packages/web/dist/assets/index-B-YuzkF6.js +71 -0
  244. package/packages/web/dist/assets/index-CvGiQddO.css +1 -0
  245. package/packages/web/dist/assets/infoDiagram-5YYISTIA-CJ2CFPfy.js +3 -0
  246. package/packages/web/dist/assets/ini-B3BVgEsG.js +2 -0
  247. package/packages/web/dist/assets/init-Gi6I4Gst.js +2 -0
  248. package/packages/web/dist/assets/ishikawaDiagram-YF4QCWOH-Cs0D32Ry.js +71 -0
  249. package/packages/web/dist/assets/java-B5VKqtH2.js +2 -0
  250. package/packages/web/dist/assets/javascript-BKRaQes9.js +2 -0
  251. package/packages/web/dist/assets/journeyDiagram-JHISSGLW-Bc9fuI2i.js +140 -0
  252. package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
  253. package/packages/web/dist/assets/kanban-definition-UN3LZRKU-D_tC7QfC.js +90 -0
  254. package/packages/web/dist/assets/katex-C5jXJg4s.js +258 -0
  255. package/packages/web/dist/assets/kotlin-Cw-eF_bm.js +3 -0
  256. package/packages/web/dist/assets/lastTab-CU1zsF0j.js +2 -0
  257. package/packages/web/dist/assets/layout-Boy8vhSP.js +2 -0
  258. package/packages/web/dist/assets/linear-BzGHkF9K.js +2 -0
  259. package/packages/web/dist/assets/mermaid.core-DlbsYzE-.js +310 -0
  260. package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bxeh-ToD.js +97 -0
  261. package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +2 -0
  262. package/packages/web/dist/assets/php-CRdd4dfS.js +3 -0
  263. package/packages/web/dist/assets/pieDiagram-4H26LBE5-CDmilo8S.js +31 -0
  264. package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
  265. package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-D14-JyE9.js +8 -0
  266. package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
  267. package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-CJ1PUGu-.js +85 -0
  268. package/packages/web/dist/assets/ruby-7T4ktxyv.js +2 -0
  269. package/packages/web/dist/assets/rust-HC3MBUhY.js +2 -0
  270. package/packages/web/dist/assets/sankeyDiagram-5OEKKPKP-BkUK73W0.js +41 -0
  271. package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
  272. package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-sbcvsuLp.js +163 -0
  273. package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
  274. package/packages/web/dist/assets/stateDiagram-AJRCARHV-DV-7BdHK.js +2 -0
  275. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BlkGdXKW.js +2 -0
  276. package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
  277. package/packages/web/dist/assets/timeline-definition-PNZ67QCA-C5su21G_.js +121 -0
  278. package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
  279. package/packages/web/dist/assets/vennDiagram-CIIHVFJN-JNkIJAaQ.js +35 -0
  280. package/packages/web/dist/assets/wardley-L42UT6IY-qcz6t2vR.js +174 -0
  281. package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-CMrOIZ-c.js +79 -0
  282. package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
  283. package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-C9q6Oy9u.js +8 -0
  284. package/packages/web/dist/assets/yaml-drsDnIKT.js +2 -0
  285. package/packages/web/dist/favicon.ico +0 -0
  286. package/packages/web/dist/fonts/inter-latin.woff2 +0 -0
  287. package/packages/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
  288. package/packages/web/dist/icons/apple-touch-icon.png +0 -0
  289. package/packages/web/dist/icons/favicon-16.png +0 -0
  290. package/packages/web/dist/icons/favicon-32.png +0 -0
  291. package/packages/web/dist/icons/icon-192.png +0 -0
  292. package/packages/web/dist/icons/icon-512.png +0 -0
  293. package/packages/web/dist/icons/maskable-512.png +0 -0
  294. package/packages/web/dist/index.html +56 -0
  295. package/packages/web/dist/manifest.webmanifest +32 -0
  296. package/packages/web/dist/sw.js +139 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * project-paths — pure path/slug/repo helpers + the project error type.
3
+ *
4
+ * Extracted from projects.ts (issue #403): slug + repo-URL validation, the
5
+ * notebook-vs-repo-backed working-directory resolution, the date stamp, and the
6
+ * seed CLAUDE.md template. All pure (only `node:path`), no `ProjectStore` ties.
7
+ * The public helpers (`isValidRepoUrl`/`repoCheckoutName`/`workingDirFor`/
8
+ * `slugify`/`ProjectError`) are re-exported from projects.ts so existing
9
+ * importers keep resolving in one place.
10
+ */
11
+ import path from "node:path";
12
+ export const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
13
+ /** The root workspace's key: its path relative to `projectsRoot` is empty. */
14
+ export const ROOT_KEY = "";
15
+ /** Whether a key addresses the root workspace (i.e. `projectsRoot` itself). */
16
+ export function isRootKey(key) {
17
+ return key === ROOT_KEY;
18
+ }
19
+ /**
20
+ * The root workspace's name in the **herdctl agent namespace**.
21
+ *
22
+ * This is the one place a sentinel is unavoidable, because that namespace
23
+ * genuinely cannot represent the empty key: herdctl agent names must be
24
+ * non-empty and must satisfy TWO disagreeing patterns — `AGENT_NAME_PATTERN`
25
+ * (`/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/`, used by `addAgent`) and the session store's
26
+ * `SAFE_IDENTIFIER_PATTERN` (`/^[a-zA-Z0-9]([a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/`,
27
+ * which requires a trailing alphanumeric). A bare `keeper-` fails both, and the
28
+ * shape #516 originally proposed (`keeper-__root__`) registered cleanly and then
29
+ * threw PathTraversalError on the first resume.
30
+ *
31
+ * `_root` satisfies both patterns and can never collide with a project, because
32
+ * {@link SLUG_RE} rejects underscores — no slug can equal it.
33
+ *
34
+ * **This is a NAME, not an identity.** A workspace is identified by its relative
35
+ * path ({@link WorkspaceKey}); this encoding exists only at the herdctl
36
+ * boundary, is applied in one function, and keeps the four agent-name builders
37
+ * (`keeper-`/`sweeper-`/`hook-`/`trigger-`) uniform with no root branch.
38
+ */
39
+ export const ROOT_AGENT_KEY = "_root";
40
+ /** Encode a workspace key for use inside a herdctl agent name. */
41
+ export function agentKeyFor(key) {
42
+ return isRootKey(key) ? ROOT_AGENT_KEY : key;
43
+ }
44
+ /** Decode a herdctl agent-name segment back to a workspace key. */
45
+ export function keyFromAgentKey(agentKey) {
46
+ return agentKey === ROOT_AGENT_KEY ? ROOT_KEY : agentKey;
47
+ }
48
+ /**
49
+ * Accepted repo-URL shapes for a repo-backed project (issue #187): https(s),
50
+ * ssh (`git@host:owner/repo`), git://, and a local `file://` or absolute path
51
+ * (the last two make deterministic tests possible without a network). Anything
52
+ * else is rejected up front so a bad value never reaches `git clone`.
53
+ */
54
+ export const REPO_URL_RE = /^(?:https?:\/\/|git:\/\/|ssh:\/\/|file:\/\/|\/|git@[^\s]+:).+/i;
55
+ /** Validate a candidate repo URL (issue #187). */
56
+ export function isValidRepoUrl(url) {
57
+ const u = url.trim();
58
+ return u.length > 0 && u.length <= 512 && REPO_URL_RE.test(u);
59
+ }
60
+ /**
61
+ * Derive the checkout directory NAME for a repo-backed project from its repo URL
62
+ * (issue #187) — the repo's own basename, sans a trailing `.git`, sanitised to a
63
+ * filesystem-safe token. The keeper's cwd becomes `<projectDir>/<thisName>`, so
64
+ * the cwd basename reads like the repo (e.g. `paddock`). Deterministic (same URL
65
+ * ⇒ same name), which is why `repo` is immutable once set.
66
+ */
67
+ export function repoCheckoutName(repo) {
68
+ const cleaned = repo
69
+ .trim()
70
+ .replace(/\/+$/, "")
71
+ .replace(/\.git$/i, "");
72
+ const base = cleaned.split(/[/:]/).pop() ?? "";
73
+ const safe = base.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^[-.]+|[-.]+$/g, "");
74
+ return safe || "repo";
75
+ }
76
+ /**
77
+ * Resolve a project's working directory (keeper cwd) from its metadata dir + repo
78
+ * URL: the nested checkout for a repo-backed project, else the metadata dir
79
+ * itself for a notebook project (issue #187).
80
+ */
81
+ export function workingDirFor(dir, repo) {
82
+ return repo ? path.join(dir, repoCheckoutName(repo)) : dir;
83
+ }
84
+ export class ProjectError extends Error {
85
+ code;
86
+ constructor(message, code) {
87
+ super(message);
88
+ this.code = code;
89
+ this.name = "ProjectError";
90
+ }
91
+ }
92
+ export function slugify(name) {
93
+ return name
94
+ .toLowerCase()
95
+ .trim()
96
+ .replace(/[^a-z0-9]+/g, "-")
97
+ .replace(/^-+|-+$/g, "")
98
+ .slice(0, 64);
99
+ }
100
+ export function today() {
101
+ return new Date().toISOString().slice(0, 10);
102
+ }
103
+ /**
104
+ * Minimal seed for a per-project CLAUDE.md (issue #177): the durable-identity
105
+ * layer of the two-level native-context model (instance-wide + per-project
106
+ * CLAUDE.md, loaded natively once #176 lands). Deliberately terse — the sweeper
107
+ * amends it conservatively over time under "Curated notes"; everything a human
108
+ * writes is preserved. OVERVIEW.md holds current state, CHANGELOG.md holds
109
+ * history; this file holds only what the project durably IS and how we work on it.
110
+ */
111
+ export function claudeTemplate(name, summary) {
112
+ return [
113
+ `# ${name}`,
114
+ "",
115
+ summary?.trim() || "<!-- One-line description of what this project is. -->",
116
+ "",
117
+ "<!--",
118
+ "Durable project identity & conventions — what this project fundamentally is,",
119
+ "key long-lived facts, and how we work on it. Changes rarely. Current state",
120
+ "lives in OVERVIEW.md; per-turn history lives in CHANGELOG.md. Content you",
121
+ "write here is preserved; the curator only APPENDS newly-discovered durable",
122
+ 'facts under the "Curated notes" heading below.',
123
+ "-->",
124
+ "",
125
+ ].join("\n");
126
+ }
@@ -0,0 +1,316 @@
1
+ /**
2
+ * project-types — the project layer's type surface + link normaliser.
3
+ *
4
+ * Extracted from projects.ts (issue #403): the on-disk `project.yaml` schema
5
+ * ({@link ProjectYaml}), the API-facing {@link Project} DTO, the create/update
6
+ * input shapes, the small render/listing enums, and the pure {@link normalizeLinks}
7
+ * read-boundary coercion. These are declarations only (plus one pure function), so
8
+ * they carry no coupling to `ProjectStore`. projects.ts re-exports everything here
9
+ * (a barrel) so every existing `import { ... } from "./projects.js"` keeps resolving.
10
+ */
11
+ import { type PaddockSchedule } from "./schedule-config.js";
12
+ import { type PaddockHook } from "./hook-config.js";
13
+ import { type PaddockTrigger } from "./trigger-config.js";
14
+ import { type RecoveryOverride } from "./recovery-config.js";
15
+ import { type CurationOverride } from "./curation-config.js";
16
+ import { type AttachmentsOverride } from "./attachments-config.js";
17
+ export type { PaddockSchedule, PaddockHook, PaddockTrigger, RecoveryOverride, AttachmentsOverride, };
18
+ /**
19
+ * project.yaml status enum (matches the documented standard). The runtime list is
20
+ * the single source of truth, with {@link ProjectStatus} derived from it, so a
21
+ * caller that must VALIDATE a status string (the `create_project` MCP tool, issue
22
+ * #467) and a caller that merely types one can't drift apart.
23
+ */
24
+ export declare const PROJECT_STATUSES: readonly ["idea", "active", "paused", "blocked", "done", "abandoned"];
25
+ export type ProjectStatus = (typeof PROJECT_STATUSES)[number];
26
+ export type ProjectVisibility = "public" | "private";
27
+ /** Render-kind hint for a project file (drives the UI renderer choice). */
28
+ export type FileKind = "markdown" | "html" | "text" | "image";
29
+ /**
30
+ * One entry in a project directory listing (issue #259): a name plus whether
31
+ * it's a file or a subdirectory, so the Files tab can distinguish the two and
32
+ * let the user descend into folders. Listed one level at a time.
33
+ */
34
+ export interface FileEntry {
35
+ name: string;
36
+ kind: "file" | "dir";
37
+ }
38
+ export interface ProjectLink {
39
+ label: string;
40
+ url: string;
41
+ }
42
+ /**
43
+ * Coerce a raw parsed `links` value into well-formed {label,url} objects.
44
+ *
45
+ * A legacy / hand-edited `project.yaml` may declare `links` as a bare YAML
46
+ * string list (the natural shorthand — `- https://example.com`) rather than the
47
+ * {label,url} object form. Passing those through untouched yields a `string[]`
48
+ * DTO, which crashes the Settings pane (its `cleanedLinks` memo calls
49
+ * `l.url.trim()` / `l.label.trim()` on what is actually a string → TypeError
50
+ * during render). So normalize here at the read boundary: a bare string becomes
51
+ * `{label:"", url:<string>}`, an object entry is trimmed and kept, and any entry
52
+ * without a usable url (or of an unexpected type) is dropped. Because this runs
53
+ * in `normalize`, the next save round-trips the file into the object form — the
54
+ * project self-heals.
55
+ */
56
+ export declare function normalizeLinks(raw: unknown): ProjectLink[];
57
+ /**
58
+ * The on-disk project.yaml shape.
59
+ *
60
+ * Note on dates: `started` is the creation date and `updated` is last-touched.
61
+ */
62
+ export interface ProjectYaml {
63
+ name: string;
64
+ slug: string;
65
+ status: ProjectStatus;
66
+ domain: string[];
67
+ visibility: ProjectVisibility;
68
+ started: string;
69
+ updated: string;
70
+ summary: string;
71
+ /**
72
+ * The project's "area" — its single, exclusive home (Homelab / House / Side
73
+ * Projects). One per project (unlike the many cross-cutting `domain` tags);
74
+ * drives the sectioned landing page. Optional on disk: an absent/empty value
75
+ * means "Unsorted" and is rendered in its own section. Free-form so new areas
76
+ * can be added without a schema change; the UI knows the canonical set.
77
+ */
78
+ group?: string;
79
+ links?: ProjectLink[];
80
+ /**
81
+ * Files (relative names within the project dir) the user has pinned as
82
+ * sibling tabs in the UI. Order-preserving, deduped. Default [].
83
+ */
84
+ pinned?: string[];
85
+ /**
86
+ * The Claude model the project's keeper agent runs on. Optional on disk: an
87
+ * absent value resolves to DEFAULT_MODEL in the DTO. (CONTRACT-v3 §4.)
88
+ */
89
+ model?: string;
90
+ /**
91
+ * Per-project ALLOW-LIST of offered models (issue #457 Step 2), by catalog id.
92
+ * Optional on disk: absent ⇒ this project offers the instance list
93
+ * (`cfg.models` resolved, else the full catalog). When set it NARROWS the picker
94
+ * to this subset — every id must be a known catalog model AND within the instance
95
+ * allow-list (enforced by PATCH validation); it never widens beyond the instance
96
+ * list. The picker's default `model` is constrained to this subset in the UI. Same
97
+ * round-trip discipline as `model` — carried only when present so files without it
98
+ * are unchanged, resolved against the instance list in the web (never baked
99
+ * concrete on disk).
100
+ */
101
+ models?: string[];
102
+ /**
103
+ * Per-project keeper-agent overrides (issue #12). All optional on disk — an
104
+ * absent value inherits the fleet default and resolves to the concrete default
105
+ * in the DTO. `permissionMode`/`maxTurns`/`docker` (Docker isolation on/off).
106
+ */
107
+ permissionMode?: string;
108
+ maxTurns?: number;
109
+ docker?: boolean;
110
+ /**
111
+ * How the keeper's chat turns are driven — `batch` (one-shot trigger) or
112
+ * `session` (persistent managed openChatSession, enabling cross-turn autonomy;
113
+ * Paddock#111). Optional on disk: absent inherits the global default
114
+ * (`PADDOCK_DRIVE_MODE`, else DEFAULT_DRIVE_MODE).
115
+ */
116
+ driveMode?: string;
117
+ /**
118
+ * How deep a spawn tree rooted at this project's chats may grow before spawned
119
+ * children stop receiving the self-management MCP (issue #262 / DD-3). A spawned
120
+ * turn at depth `d` gets the self-MCP (incl. `send_message`, so a child can
121
+ * report back) iff `d <= maxSpawnDepth`. Optional on disk: absent inherits the
122
+ * instance default (`PADDOCK_MAX_SPAWN_DEPTH`, else DEFAULT_MAX_SPAWN_DEPTH = 1),
123
+ * resolved at dispatch — the same inherit/override discipline as `driveMode`.
124
+ */
125
+ maxSpawnDepth?: number;
126
+ /**
127
+ * Per-project override for the hook-management MCP gate (Epic G / G5, GG-4) —
128
+ * whether this project's turns get the `mcp__paddock_manage__{list,set,remove}_hook`
129
+ * tools. Optional on disk: absent inherits the instance default
130
+ * (`PADDOCK_HOOKS_MCP`, else OFF), resolved at dispatch via
131
+ * {@link import("./hook-config.js").resolveHooksMcpEnabled} — the same
132
+ * inherit/override discipline as `driveMode`/`maxSpawnDepth`. `true` opts this
133
+ * project agent in; `false` opts it out even when the instance default is on.
134
+ */
135
+ hooksMcpEnabled?: boolean;
136
+ /**
137
+ * Per-project keeper-chat recovery override (issue #301). Every field optional;
138
+ * an absent field inherits the instance default (`cfg.recovery`, itself
139
+ * `PADDOCK_RECOVERY_*` env / YAML), resolved at dispatch via
140
+ * {@link import("./recovery-config.js").resolveRecoveryConfig} — the same
141
+ * inherit/override discipline as `driveMode`/`maxSpawnDepth`. Absent ⇒ this
142
+ * project inherits every recovery default. Persisted (and re-read) through
143
+ * {@link sanitizeRecoveryOverride} so a malformed hand-edit degrades cleanly.
144
+ */
145
+ recovery?: RecoveryOverride;
146
+ /**
147
+ * Per-project inbound-attachment override (issue #328). Every field optional;
148
+ * an absent field inherits the instance default (`cfg.attachments`, itself
149
+ * `PADDOCK_ATTACHMENTS_*` env / YAML), resolved at request time via
150
+ * {@link import("./attachments-config.js").resolveAttachmentsConfig} — the same
151
+ * inherit/override discipline as `recovery`. Absent ⇒ this project inherits
152
+ * every attachment default. Persisted (and re-read) through
153
+ * {@link sanitizeAttachmentsOverride} so a malformed hand-edit degrades cleanly.
154
+ * (Surfacing this in the Settings tab is deferred to Phase 2; the resolve/
155
+ * override plumbing exists now.)
156
+ */
157
+ attachments?: AttachmentsOverride;
158
+ /**
159
+ * Per-project sweeper-curation budget override (issue #384). Every field
160
+ * optional; an absent field inherits the instance default (`cfg.curation`,
161
+ * itself `PADDOCK_CURATION_*` env / YAML), resolved at sweep time via
162
+ * {@link import("./curation-config.js").resolveCurationConfig} — the same
163
+ * inherit/override discipline as `recovery`/`attachments`. Absent ⇒ this
164
+ * project inherits every curation default. Persisted (and re-read) through
165
+ * {@link sanitizeCurationOverride} so a malformed hand-edit degrades cleanly.
166
+ */
167
+ curation?: CurationOverride;
168
+ /**
169
+ * External git repo URL that backs this project (issue #187). When present the
170
+ * project is REPO-BACKED: Paddock clones the repo into a nested `.gitignore`d
171
+ * checkout under the project dir and the keeper's working directory is that
172
+ * checkout (so the repo's OWN `CLAUDE.md`, git history, branches and PR flow
173
+ * work natively). Absent ⇒ a NOTEBOOK project (the classic type: cwd = the
174
+ * project dir in the instance data repo). Set at creation; immutable thereafter
175
+ * (like `slug`/`started`).
176
+ */
177
+ repo?: string;
178
+ /**
179
+ * Scheduled chat sessions for this project (issue #265 / DD-2), keyed by a
180
+ * stable schedule name. Each value is herdctl's `ScheduleSchema` shape (plus the
181
+ * Paddock-only `promptFile`), forwarded unmolested into the keeper agent's
182
+ * `schedules` block so herdctl arms the cron/interval directly. Absent/empty ⇒
183
+ * the project has no schedules (unchanged behaviour).
184
+ */
185
+ schedules?: Record<string, PaddockSchedule>;
186
+ /**
187
+ * Event hooks for this project (Epic G / G1), keyed by a stable hook name. Each
188
+ * value is a {@link PaddockHook} — a lifecycle event + a capability set + a prompt
189
+ * (inline or `.paddock/hooks/*.md`) + `enabled`. Unlike schedules, a hook is NOT
190
+ * forwarded into the keeper agent's config: each hook is registered as its OWN
191
+ * herdctl agent `hook-<slug>-<name>` whose tool config IS its capability (GG-1).
192
+ * Absent/empty ⇒ the project has no hooks (unchanged behaviour). New hooks default
193
+ * `enabled: false` (GG-3).
194
+ */
195
+ hooks?: Record<string, PaddockHook>;
196
+ /**
197
+ * Unified triggers for this project (Epic T "Unify Triggers" / T1), keyed by a
198
+ * stable trigger name. Each value is a {@link PaddockTrigger} — a discriminated
199
+ * `trigger` (schedule / event / webhook) + a shared `run` (prompt, session, tools,
200
+ * …) + `enabled`. The declarative successor collapsing the separate `schedules` +
201
+ * `hooks` blocks (which T3/T5 retire): a SCHEDULE trigger is forwarded into the
202
+ * keeper agent's `schedules` block; an EVENT trigger is registered as its own
203
+ * `trigger-<slug>-<name>` agent (like a hook); a WEBHOOK trigger is shape-reserved.
204
+ * Absent/empty ⇒ the project has no triggers. New triggers default `enabled: false`.
205
+ */
206
+ triggers?: Record<string, PaddockTrigger>;
207
+ }
208
+ /** API-facing project DTO (adds derived fields). */
209
+ export interface Project extends ProjectYaml {
210
+ /** The project's area — ALWAYS concrete in the DTO (`yaml.group ?? ""`). An
211
+ * empty string means "Unsorted". */
212
+ group: string;
213
+ /**
214
+ * Absolute path to the project's METADATA directory — the per-slug dir in the
215
+ * instance data repo that holds `project.yaml`, `OVERVIEW.md`, `CHANGELOG.md`,
216
+ * `.chats/`, and (for a notebook project) `CLAUDE.md`. For a NOTEBOOK project
217
+ * this is also the keeper's cwd; for a REPO-BACKED project the cwd is
218
+ * {@link workingDir} (the nested checkout) instead — see #187.
219
+ */
220
+ dir: string;
221
+ /**
222
+ * The keeper agent's working directory (cwd). For a notebook project this
223
+ * equals {@link dir}; for a repo-backed project it's the nested checkout
224
+ * (`<dir>/<repo-name>`), so the repo's own `CLAUDE.md` + git tooling apply
225
+ * (issue #187). Always concrete in the DTO.
226
+ */
227
+ workingDir: string;
228
+ /** Whether this project is backed by an external git repo (issue #187). */
229
+ repoBacked: boolean;
230
+ /** Whether OVERVIEW.md exists in the project dir (sweep-curated context). */
231
+ hasOverview: boolean;
232
+ /** Always present in the DTO (defaults to []). */
233
+ pinned: string[];
234
+ /**
235
+ * The keeper agent's model — ALWAYS concrete in the DTO (`yaml.model ??
236
+ * DEFAULT_MODEL`), even though it's optional on disk.
237
+ */
238
+ model: string;
239
+ /** Keeper permission mode — ALWAYS concrete in the DTO (issue #12). */
240
+ permissionMode: string;
241
+ /** Keeper max_turns — ALWAYS concrete in the DTO (issue #12). */
242
+ maxTurns: number;
243
+ /** Whether the keeper runs in a Docker sandbox — ALWAYS concrete (issue #12). */
244
+ docker: boolean;
245
+ }
246
+ export interface CreateProjectInput {
247
+ name: string;
248
+ slug?: string;
249
+ status?: ProjectStatus;
250
+ domain?: string[];
251
+ group?: string;
252
+ visibility?: ProjectVisibility;
253
+ summary?: string;
254
+ links?: ProjectLink[];
255
+ /**
256
+ * External git repo URL to back this project (issue #187). When set, the
257
+ * project is created as REPO-BACKED: the repo is cloned into a nested
258
+ * `.gitignore`d checkout and the keeper's cwd becomes that checkout. Absent ⇒
259
+ * a notebook project (the classic type).
260
+ */
261
+ repo?: string;
262
+ }
263
+ /** Partial metadata update (slug + started are immutable). */
264
+ export type UpdateProjectInput = Partial<Pick<ProjectYaml, "name" | "status" | "domain" | "group" | "visibility" | "summary" | "links" | "model" | "permissionMode" | "maxTurns" | "docker">> & {
265
+ /**
266
+ * Per-project offered-models allow-list (issue #457 Step 2). A non-empty string
267
+ * array sets the per-project override (each id validated known + within the
268
+ * instance allow-list); an empty array or `null` CLEARS it so the project offers
269
+ * the instance list again; `undefined`/absent leaves the current value untouched.
270
+ * Same tri-state discipline as `driveMode`.
271
+ */
272
+ models?: string[] | null;
273
+ /**
274
+ * Keeper drive mode (Paddock#111). A string sets a per-project override;
275
+ * `null` CLEARS the override so the project inherits the box-wide global
276
+ * default again (issue #122's reset-to-inherit); `undefined`/absent leaves the
277
+ * current value untouched.
278
+ */
279
+ driveMode?: string | null;
280
+ /**
281
+ * Max spawn depth (issue #262). A number sets a per-project override; `null`
282
+ * CLEARS it so the project inherits the instance default again; `undefined`/
283
+ * absent leaves the current value untouched. Same tri-state as `driveMode`.
284
+ */
285
+ maxSpawnDepth?: number | null;
286
+ /**
287
+ * Hook-management MCP override (Epic G / G5). A boolean sets a per-project
288
+ * override; `null` CLEARS it so the project inherits the instance default again;
289
+ * `undefined`/absent leaves the current value untouched. Same tri-state as
290
+ * `driveMode`/`maxSpawnDepth`.
291
+ */
292
+ hooksMcpEnabled?: boolean | null;
293
+ /**
294
+ * Keeper-chat recovery override (issue #301). A {@link RecoveryOverride} object
295
+ * REPLACES the per-project override; `null` CLEARS it so the project inherits
296
+ * every instance default again; `undefined`/absent leaves the current value
297
+ * untouched. Same tri-state as `driveMode`/`maxSpawnDepth` (the whole override
298
+ * object is the unit — sanitised on write).
299
+ */
300
+ recovery?: RecoveryOverride | null;
301
+ /**
302
+ * Inbound-attachment override (issue #328). An {@link AttachmentsOverride}
303
+ * object REPLACES the per-project override; `null` CLEARS it so the project
304
+ * inherits every instance default again; `undefined`/absent leaves it
305
+ * untouched. Same tri-state as `recovery` (the whole override object is the
306
+ * unit — sanitised on write). Wired now for the Phase 2 Settings surface.
307
+ */
308
+ attachments?: AttachmentsOverride | null;
309
+ /**
310
+ * Sweeper-curation budget override (issue #384). A {@link CurationOverride}
311
+ * object REPLACES the per-project override; `null` CLEARS it so the project
312
+ * inherits every instance default again; `undefined`/absent leaves it
313
+ * untouched. Same tri-state as `recovery`/`attachments`, sanitised on write.
314
+ */
315
+ curation?: CurationOverride | null;
316
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * project.yaml status enum (matches the documented standard). The runtime list is
3
+ * the single source of truth, with {@link ProjectStatus} derived from it, so a
4
+ * caller that must VALIDATE a status string (the `create_project` MCP tool, issue
5
+ * #467) and a caller that merely types one can't drift apart.
6
+ */
7
+ export const PROJECT_STATUSES = [
8
+ "idea",
9
+ "active",
10
+ "paused",
11
+ "blocked",
12
+ "done",
13
+ "abandoned",
14
+ ];
15
+ /**
16
+ * Coerce a raw parsed `links` value into well-formed {label,url} objects.
17
+ *
18
+ * A legacy / hand-edited `project.yaml` may declare `links` as a bare YAML
19
+ * string list (the natural shorthand — `- https://example.com`) rather than the
20
+ * {label,url} object form. Passing those through untouched yields a `string[]`
21
+ * DTO, which crashes the Settings pane (its `cleanedLinks` memo calls
22
+ * `l.url.trim()` / `l.label.trim()` on what is actually a string → TypeError
23
+ * during render). So normalize here at the read boundary: a bare string becomes
24
+ * `{label:"", url:<string>}`, an object entry is trimmed and kept, and any entry
25
+ * without a usable url (or of an unexpected type) is dropped. Because this runs
26
+ * in `normalize`, the next save round-trips the file into the object form — the
27
+ * project self-heals.
28
+ */
29
+ export function normalizeLinks(raw) {
30
+ if (!Array.isArray(raw))
31
+ return [];
32
+ const out = [];
33
+ for (const item of raw) {
34
+ if (typeof item === "string") {
35
+ const url = item.trim();
36
+ if (url)
37
+ out.push({ label: "", url });
38
+ }
39
+ else if (item && typeof item === "object") {
40
+ const rec = item;
41
+ const url = typeof rec.url === "string" ? rec.url.trim() : "";
42
+ const label = typeof rec.label === "string" ? rec.label.trim() : "";
43
+ if (url)
44
+ out.push({ label, url });
45
+ }
46
+ }
47
+ return out;
48
+ }