codetrellis 0.0.0-stage → 0.2.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 (449) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +27 -2
  3. package/bin/codetrellis.mjs +4 -0
  4. package/package.json +43 -4
  5. package/reader/material-reader.mjs +130346 -0
  6. package/resources/tree-sitter/README.md +150 -0
  7. package/resources/tree-sitter/tree-sitter-c-sharp.wasm +0 -0
  8. package/resources/tree-sitter/tree-sitter-go.wasm +0 -0
  9. package/resources/tree-sitter/tree-sitter-java.wasm +0 -0
  10. package/resources/tree-sitter/tree-sitter-javascript.wasm +0 -0
  11. package/resources/tree-sitter/tree-sitter-kotlin.wasm +0 -0
  12. package/resources/tree-sitter/tree-sitter-php.wasm +0 -0
  13. package/resources/tree-sitter/tree-sitter-python.wasm +0 -0
  14. package/resources/tree-sitter/tree-sitter-ruby.wasm +0 -0
  15. package/resources/tree-sitter/tree-sitter-rust.wasm +0 -0
  16. package/resources/tree-sitter/tree-sitter-swift.wasm +0 -0
  17. package/resources/tree-sitter/tree-sitter-tsx.wasm +0 -0
  18. package/resources/tree-sitter/tree-sitter-typescript.wasm +0 -0
  19. package/resources/tree-sitter/tree-sitter.wasm +0 -0
  20. package/src/backend/agent/claude-code-watcher.js +270 -0
  21. package/src/backend/index.js +8 -0
  22. package/src/backend/lib/sha256-file.js +39 -0
  23. package/src/backend/lib/zip-entries.js +98 -0
  24. package/src/backend/lifecycle.js +102 -0
  25. package/src/backend/mcp/binding-headers.js +54 -0
  26. package/src/backend/mcp/client-identity.js +44 -0
  27. package/src/backend/mcp/connector/command.js +110 -0
  28. package/src/backend/mcp/connector/core.js +278 -0
  29. package/src/backend/mcp/connector/files.js +121 -0
  30. package/src/backend/mcp/connector/hook.js +284 -0
  31. package/src/backend/mcp/connector/main.js +126 -0
  32. package/src/backend/mcp/connector/sse-upstream.js +152 -0
  33. package/src/backend/mcp/helpers.js +43 -0
  34. package/src/backend/mcp/prompt-builders.js +104 -0
  35. package/src/backend/mcp/resources.js +197 -0
  36. package/src/backend/mcp/server.js +813 -0
  37. package/src/backend/mcp/skill-guide.js +1528 -0
  38. package/src/backend/mcp/tools/architecture-tools.js +273 -0
  39. package/src/backend/mcp/tools/audio-tools.js +117 -0
  40. package/src/backend/mcp/tools/awareness-tools.js +623 -0
  41. package/src/backend/mcp/tools/budget-tools.js +185 -0
  42. package/src/backend/mcp/tools/channel-tools.js +205 -0
  43. package/src/backend/mcp/tools/contribution-tools.js +181 -0
  44. package/src/backend/mcp/tools/drift-tools.js +252 -0
  45. package/src/backend/mcp/tools/git-tools.js +316 -0
  46. package/src/backend/mcp/tools/governance-tools.js +175 -0
  47. package/src/backend/mcp/tools/graph-tools.js +181 -0
  48. package/src/backend/mcp/tools/intake-tools.js +244 -0
  49. package/src/backend/mcp/tools/mobile-tools.js +118 -0
  50. package/src/backend/mcp/tools/peer-tools.js +330 -0
  51. package/src/backend/mcp/tools/plan-item-tools.js +1601 -0
  52. package/src/backend/mcp/tools/plan-tools.js +680 -0
  53. package/src/backend/mcp/tools/presence-tools.js +185 -0
  54. package/src/backend/mcp/tools/project-config-tools.js +150 -0
  55. package/src/backend/mcp/tools/review-tools.js +172 -0
  56. package/src/backend/mcp/tools/session-tools.js +554 -0
  57. package/src/backend/mcp/tools/system-docs-tools.js +168 -0
  58. package/src/backend/mcp/tools/terminal-tools.js +186 -0
  59. package/src/backend/mcp/tools/test-tools.js +117 -0
  60. package/src/backend/mcp/tools/ui-tools.js +356 -0
  61. package/src/backend/mcp/types.js +15 -0
  62. package/src/backend/middleware/local-auth.js +116 -0
  63. package/src/backend/server.js +5951 -0
  64. package/src/backend/services/agent-event-log.js +350 -0
  65. package/src/backend/services/architecture-rule.js +584 -0
  66. package/src/backend/services/architecture-rules.js +275 -0
  67. package/src/backend/services/artefact-content-service.js +181 -0
  68. package/src/backend/services/artefact-service.js +305 -0
  69. package/src/backend/services/artefact-watcher.js +167 -0
  70. package/src/backend/services/ast-parser.js +327 -0
  71. package/src/backend/services/audio-buffer-service.js +152 -0
  72. package/src/backend/services/awareness-notices.js +139 -0
  73. package/src/backend/services/awareness-replies.js +194 -0
  74. package/src/backend/services/awareness-service.js +344 -0
  75. package/src/backend/services/awareness-signals.js +267 -0
  76. package/src/backend/services/baseline-store.js +83 -0
  77. package/src/backend/services/branch-workstreams.js +341 -0
  78. package/src/backend/services/breakpoint-service.js +584 -0
  79. package/src/backend/services/brief-service.js +299 -0
  80. package/src/backend/services/budget-service.js +528 -0
  81. package/src/backend/services/callsites/base.js +15 -0
  82. package/src/backend/services/callsites/csharp.js +148 -0
  83. package/src/backend/services/callsites/go.js +163 -0
  84. package/src/backend/services/callsites/index.js +53 -0
  85. package/src/backend/services/callsites/kotlin.js +123 -0
  86. package/src/backend/services/callsites/process-env.js +125 -0
  87. package/src/backend/services/callsites/python.js +91 -0
  88. package/src/backend/services/callsites/ruby.js +164 -0
  89. package/src/backend/services/callsites/shared.js +198 -0
  90. package/src/backend/services/callsites/swift.js +115 -0
  91. package/src/backend/services/callsites/typescript.js +87 -0
  92. package/src/backend/services/capability-token.js +108 -0
  93. package/src/backend/services/change-check.js +156 -0
  94. package/src/backend/services/channel-dispatcher-service.js +235 -0
  95. package/src/backend/services/channel-event-file-service.js +125 -0
  96. package/src/backend/services/channel-event-service.js +297 -0
  97. package/src/backend/services/check-runs.js +177 -0
  98. package/src/backend/services/checkout-identity.js +73 -0
  99. package/src/backend/services/claude-code-parallel.js +221 -0
  100. package/src/backend/services/claude-desktop-config.js +186 -0
  101. package/src/backend/services/clock.js +57 -0
  102. package/src/backend/services/cloud-files.js +191 -0
  103. package/src/backend/services/coalesce.js +50 -0
  104. package/src/backend/services/code-breakpoints.js +291 -0
  105. package/src/backend/services/comment-service.js +168 -0
  106. package/src/backend/services/commit-attribution.js +134 -0
  107. package/src/backend/services/commit-edges.js +99 -0
  108. package/src/backend/services/confined-fs.js +241 -0
  109. package/src/backend/services/conformity-gate.js +132 -0
  110. package/src/backend/services/contribution-service.js +409 -0
  111. package/src/backend/services/coverage-service.js +173 -0
  112. package/src/backend/services/criteria-service.js +721 -0
  113. package/src/backend/services/criterion-checks.js +387 -0
  114. package/src/backend/services/criterion-loop-service.js +411 -0
  115. package/src/backend/services/cross-system-service.js +231 -0
  116. package/src/backend/services/database.js +916 -0
  117. package/src/backend/services/db-schema.js +1358 -0
  118. package/src/backend/services/deviation-service.js +344 -0
  119. package/src/backend/services/diff-engine.js +168 -0
  120. package/src/backend/services/evidence.js +363 -0
  121. package/src/backend/services/external-intake-service.js +269 -0
  122. package/src/backend/services/external-pointer-service.js +195 -0
  123. package/src/backend/services/external-refs-service.js +214 -0
  124. package/src/backend/services/file-history.js +158 -0
  125. package/src/backend/services/file-watcher.js +193 -0
  126. package/src/backend/services/folder-requests.js +118 -0
  127. package/src/backend/services/freeze-service.js +189 -0
  128. package/src/backend/services/gemini-cli-hook.js +172 -0
  129. package/src/backend/services/git-activity-service.js +325 -0
  130. package/src/backend/services/git-blobs.js +70 -0
  131. package/src/backend/services/git-branches.js +357 -0
  132. package/src/backend/services/git-checkout.js +153 -0
  133. package/src/backend/services/git-commit-service.js +91 -0
  134. package/src/backend/services/git-env.js +121 -0
  135. package/src/backend/services/git-identity.js +84 -0
  136. package/src/backend/services/git-refs.js +386 -0
  137. package/src/backend/services/git-safety.js +58 -0
  138. package/src/backend/services/grant-guard.js +67 -0
  139. package/src/backend/services/html-view-policy.js +135 -0
  140. package/src/backend/services/human-decision.js +55 -0
  141. package/src/backend/services/importers.js +159 -0
  142. package/src/backend/services/intent-service.js +105 -0
  143. package/src/backend/services/ipc-dispatcher.js +164 -0
  144. package/src/backend/services/item-git-state.js +208 -0
  145. package/src/backend/services/line-changes.js +232 -0
  146. package/src/backend/services/line-history.js +174 -0
  147. package/src/backend/services/local-api-changes.js +40 -0
  148. package/src/backend/services/logger.js +217 -0
  149. package/src/backend/services/material-footprints.js +203 -0
  150. package/src/backend/services/material-place.js +82 -0
  151. package/src/backend/services/material-reader/child.js +4 -0
  152. package/src/backend/services/material-reader/docx-markdown.js +198 -0
  153. package/src/backend/services/material-reader/fixtures.test-helper.js +169 -0
  154. package/src/backend/services/material-reader/quote.js +44 -0
  155. package/src/backend/services/material-reader/read.js +430 -0
  156. package/src/backend/services/material-reader/reader-host.js +202 -0
  157. package/src/backend/services/material-signals.js +141 -0
  158. package/src/backend/services/mcp-capabilities.js +390 -0
  159. package/src/backend/services/mdns-service.js +304 -0
  160. package/src/backend/services/mobile-api-server.js +281 -0
  161. package/src/backend/services/mobile-approvals.js +306 -0
  162. package/src/backend/services/mobile-awareness.js +180 -0
  163. package/src/backend/services/mobile-breakpoints.js +115 -0
  164. package/src/backend/services/mobile-budget.js +98 -0
  165. package/src/backend/services/mobile-freeze.js +106 -0
  166. package/src/backend/services/mobile-proposals.js +100 -0
  167. package/src/backend/services/mobile-rpc-service.js +1289 -0
  168. package/src/backend/services/mobile-workstreams.js +160 -0
  169. package/src/backend/services/monorepo-detector.js +131 -0
  170. package/src/backend/services/other-work.js +45 -0
  171. package/src/backend/services/pack-seal.js +129 -0
  172. package/src/backend/services/paired-device-service.js +192 -0
  173. package/src/backend/services/pairing-server.js +323 -0
  174. package/src/backend/services/pairing-service.js +200 -0
  175. package/src/backend/services/pantry-resolution-service.js +166 -0
  176. package/src/backend/services/parsers/base.js +124 -0
  177. package/src/backend/services/parsers/csharp.js +224 -0
  178. package/src/backend/services/parsers/go.js +180 -0
  179. package/src/backend/services/parsers/index.js +101 -0
  180. package/src/backend/services/parsers/java.js +103 -0
  181. package/src/backend/services/parsers/kotlin.js +252 -0
  182. package/src/backend/services/parsers/php.js +113 -0
  183. package/src/backend/services/parsers/python.js +189 -0
  184. package/src/backend/services/parsers/ruby.js +212 -0
  185. package/src/backend/services/parsers/rust.js +97 -0
  186. package/src/backend/services/parsers/swift.js +221 -0
  187. package/src/backend/services/parsers/typescript.js +235 -0
  188. package/src/backend/services/pattern-scan.js +65 -0
  189. package/src/backend/services/patterns.js +281 -0
  190. package/src/backend/services/peer-audit-service.js +123 -0
  191. package/src/backend/services/peer-auth.js +153 -0
  192. package/src/backend/services/peer-capabilities.js +209 -0
  193. package/src/backend/services/peer-connection-service.js +450 -0
  194. package/src/backend/services/peer-grants.js +33 -0
  195. package/src/backend/services/persistence.js +116 -0
  196. package/src/backend/services/personal-sync-service.js +232 -0
  197. package/src/backend/services/pipeline-approvals.js +72 -0
  198. package/src/backend/services/pipeline.js +232 -0
  199. package/src/backend/services/plan-arrivals.js +68 -0
  200. package/src/backend/services/plan-changes-service.js +273 -0
  201. package/src/backend/services/plan-conflict-service.js +288 -0
  202. package/src/backend/services/plan-dependencies.js +91 -0
  203. package/src/backend/services/plan-doc-guard.js +90 -0
  204. package/src/backend/services/plan-documents-service.js +275 -0
  205. package/src/backend/services/plan-event-service.js +154 -0
  206. package/src/backend/services/plan-file-refs.js +106 -0
  207. package/src/backend/services/plan-file-service.js +1564 -0
  208. package/src/backend/services/plan-history-service.js +156 -0
  209. package/src/backend/services/plan-import-service.js +261 -0
  210. package/src/backend/services/plan-item-service.js +1223 -0
  211. package/src/backend/services/plan-migrate-service.js +395 -0
  212. package/src/backend/services/plan-overlay-service.js +173 -0
  213. package/src/backend/services/plan-phases-service.js +182 -0
  214. package/src/backend/services/plan-progress-service.js +134 -0
  215. package/src/backend/services/plan-review-service.js +262 -0
  216. package/src/backend/services/plan-service.js +682 -0
  217. package/src/backend/services/plan-status.js +113 -0
  218. package/src/backend/services/plan-template-publish-service.js +217 -0
  219. package/src/backend/services/plan-templates-service.js +242 -0
  220. package/src/backend/services/plan-templates.js +1164 -0
  221. package/src/backend/services/planned-overlap-actions.js +200 -0
  222. package/src/backend/services/plans-home.js +224 -0
  223. package/src/backend/services/play-forward.js +256 -0
  224. package/src/backend/services/playback-service.js +117 -0
  225. package/src/backend/services/power-service.js +167 -0
  226. package/src/backend/services/power-signals.js +81 -0
  227. package/src/backend/services/pr-draft-service.js +142 -0
  228. package/src/backend/services/presence-service.js +122 -0
  229. package/src/backend/services/pricing.js +60 -0
  230. package/src/backend/services/project-config-service.js +474 -0
  231. package/src/backend/services/project-scanner.js +184 -0
  232. package/src/backend/services/projection-service.js +69 -0
  233. package/src/backend/services/push-notification-service.js +288 -0
  234. package/src/backend/services/recent-projects-service.js +204 -0
  235. package/src/backend/services/record-chain.js +162 -0
  236. package/src/backend/services/recurrence-rule.js +63 -0
  237. package/src/backend/services/recurring-agent.js +89 -0
  238. package/src/backend/services/recurring-scheduler.js +77 -0
  239. package/src/backend/services/recurring-service.js +276 -0
  240. package/src/backend/services/reference-service.js +169 -0
  241. package/src/backend/services/release-public-key.js +30 -0
  242. package/src/backend/services/release-signature.js +64 -0
  243. package/src/backend/services/remote-audio-service.js +224 -0
  244. package/src/backend/services/remote-interaction-service.js +275 -0
  245. package/src/backend/services/remote-terminal-service.js +324 -0
  246. package/src/backend/services/rendition/child/main.js +76 -0
  247. package/src/backend/services/rendition/engine-host.js +249 -0
  248. package/src/backend/services/rendition/engine-lock.js +39 -0
  249. package/src/backend/services/rendition/engine-lock.json +14 -0
  250. package/src/backend/services/rendition/engine-manifest.js +97 -0
  251. package/src/backend/services/rendition/network-proof.js +73 -0
  252. package/src/backend/services/rendition/rendition-service.js +175 -0
  253. package/src/backend/services/replay-frames.js +387 -0
  254. package/src/backend/services/replay-state.js +201 -0
  255. package/src/backend/services/resolvers/base.js +108 -0
  256. package/src/backend/services/resolvers/csharp.js +109 -0
  257. package/src/backend/services/resolvers/go.js +105 -0
  258. package/src/backend/services/resolvers/index.js +60 -0
  259. package/src/backend/services/resolvers/java.js +63 -0
  260. package/src/backend/services/resolvers/kotlin.js +109 -0
  261. package/src/backend/services/resolvers/php.js +89 -0
  262. package/src/backend/services/resolvers/python.js +154 -0
  263. package/src/backend/services/resolvers/ruby.js +103 -0
  264. package/src/backend/services/resolvers/rust.js +77 -0
  265. package/src/backend/services/resolvers/swift.js +98 -0
  266. package/src/backend/services/resolvers/typescript.js +71 -0
  267. package/src/backend/services/retention.js +54 -0
  268. package/src/backend/services/review-architecture.js +236 -0
  269. package/src/backend/services/review-attest.js +68 -0
  270. package/src/backend/services/review-bundle.js +226 -0
  271. package/src/backend/services/review-graduation.js +93 -0
  272. package/src/backend/services/review-host/bitbucket.js +79 -0
  273. package/src/backend/services/review-host/detect.js +80 -0
  274. package/src/backend/services/review-host/github.js +97 -0
  275. package/src/backend/services/review-host/gitlab.js +77 -0
  276. package/src/backend/services/review-host/host-state.js +103 -0
  277. package/src/backend/services/review-host/http.js +58 -0
  278. package/src/backend/services/review-host/switch.js +120 -0
  279. package/src/backend/services/review-marks.js +72 -0
  280. package/src/backend/services/review-notes.js +169 -0
  281. package/src/backend/services/review-other-work.js +90 -0
  282. package/src/backend/services/review-queue-service.js +147 -0
  283. package/src/backend/services/review-risk.js +127 -0
  284. package/src/backend/services/review-task.js +121 -0
  285. package/src/backend/services/rule-approvals.js +268 -0
  286. package/src/backend/services/rule-baseline.js +134 -0
  287. package/src/backend/services/rule-changes.js +162 -0
  288. package/src/backend/services/rule-preview.js +47 -0
  289. package/src/backend/services/rule-proposals.js +104 -0
  290. package/src/backend/services/rule-scope.js +76 -0
  291. package/src/backend/services/rulebook.js +286 -0
  292. package/src/backend/services/rules-at.js +56 -0
  293. package/src/backend/services/rules-overview.js +88 -0
  294. package/src/backend/services/schema-reconciler.js +250 -0
  295. package/src/backend/services/secret-store.js +119 -0
  296. package/src/backend/services/section-workstreams.js +95 -0
  297. package/src/backend/services/self-write-tracker.js +75 -0
  298. package/src/backend/services/sensor-bridge-service.js +285 -0
  299. package/src/backend/services/session-service.js +192 -0
  300. package/src/backend/services/settings-service.js +324 -0
  301. package/src/backend/services/signal-breakpoints.js +172 -0
  302. package/src/backend/services/signed-approval-record.js +86 -0
  303. package/src/backend/services/signed-approvals.js +244 -0
  304. package/src/backend/services/signoff-pack.js +328 -0
  305. package/src/backend/services/signoff-rows.js +114 -0
  306. package/src/backend/services/skill-arrival-service.js +138 -0
  307. package/src/backend/services/skill-model.js +193 -0
  308. package/src/backend/services/skill-use-service.js +140 -0
  309. package/src/backend/services/skills-service.js +150 -0
  310. package/src/backend/services/snapshot-compare-service.js +397 -0
  311. package/src/backend/services/source-control.js +189 -0
  312. package/src/backend/services/spec-links-service.js +123 -0
  313. package/src/backend/services/spec-proposal-withdraw.js +50 -0
  314. package/src/backend/services/spec-proposals-service.js +470 -0
  315. package/src/backend/services/sql/embedded.js +95 -0
  316. package/src/backend/services/sql/index.js +63 -0
  317. package/src/backend/services/sql/refs.js +357 -0
  318. package/src/backend/services/sql/schema.js +312 -0
  319. package/src/backend/services/sql/tokenizer.js +195 -0
  320. package/src/backend/services/stack-overlaps.js +117 -0
  321. package/src/backend/services/stack-service.js +225 -0
  322. package/src/backend/services/state-sync-service.js +384 -0
  323. package/src/backend/services/stuck-sensor-service.js +200 -0
  324. package/src/backend/services/system-discovery.js +481 -0
  325. package/src/backend/services/system-docs-service.js +701 -0
  326. package/src/backend/services/task-attachments-service.js +340 -0
  327. package/src/backend/services/task-grounding.js +60 -0
  328. package/src/backend/services/task-records/check-run-record.js +146 -0
  329. package/src/backend/services/task-records/check-runs.js +115 -0
  330. package/src/backend/services/task-records/heads.js +88 -0
  331. package/src/backend/services/task-records/material-reads.js +225 -0
  332. package/src/backend/services/task-records/read-record.js +100 -0
  333. package/src/backend/services/task-records/record.js +173 -0
  334. package/src/backend/services/task-records/run-record.js +136 -0
  335. package/src/backend/services/task-records/shared-state.js +548 -0
  336. package/src/backend/services/task-records/signing.js +140 -0
  337. package/src/backend/services/task-records/split-signals.js +52 -0
  338. package/src/backend/services/task-records/test-runs.js +164 -0
  339. package/src/backend/services/task-records/trust.js +243 -0
  340. package/src/backend/services/task-rules.js +81 -0
  341. package/src/backend/services/task-workstreams.js +90 -0
  342. package/src/backend/services/terminal-history-service.js +266 -0
  343. package/src/backend/services/terminal-service.js +267 -0
  344. package/src/backend/services/test-clock.js +53 -0
  345. package/src/backend/services/tests/grounding.js +266 -0
  346. package/src/backend/services/tests/junit.js +104 -0
  347. package/src/backend/services/tests/teammate-runs.js +134 -0
  348. package/src/backend/services/tests/test-results.js +234 -0
  349. package/src/backend/services/tree-watcher.js +204 -0
  350. package/src/backend/services/trellis-service.js +204 -0
  351. package/src/backend/services/trusted-roots.js +186 -0
  352. package/src/backend/services/update-download-service.js +342 -0
  353. package/src/backend/services/update-service.js +266 -0
  354. package/src/backend/services/watch-ignore.js +76 -0
  355. package/src/backend/services/webhook-egress.js +206 -0
  356. package/src/backend/services/webrtc-service.js +413 -0
  357. package/src/backend/services/work-changes.js +59 -0
  358. package/src/backend/services/workstream-binding.js +82 -0
  359. package/src/backend/services/workstream-commits.js +116 -0
  360. package/src/backend/services/workstream-imports.js +177 -0
  361. package/src/backend/services/workstream-service.js +335 -0
  362. package/src/backend/services/workstream-symbols.js +223 -0
  363. package/src/backend/services/workstream-watch-service.js +391 -0
  364. package/src/backend/services/worktree-service.js +235 -0
  365. package/src/cli/agent.js +100 -0
  366. package/src/cli/args.js +201 -0
  367. package/src/cli/conformity.js +106 -0
  368. package/src/cli/desktop.js +91 -0
  369. package/src/cli/main.js +409 -0
  370. package/src/cli/pipeline.js +109 -0
  371. package/src/cli/plan-verbs.js +259 -0
  372. package/src/cli/review-adapters.js +218 -0
  373. package/src/cli/review-output.js +93 -0
  374. package/src/cli/review-post.js +89 -0
  375. package/src/cli/review-sink.js +183 -0
  376. package/src/cli/review-skill.js +44 -0
  377. package/src/cli/review-verify.js +104 -0
  378. package/src/cli/review.js +386 -0
  379. package/src/cli/sarif.js +148 -0
  380. package/src/cli/verbs.js +359 -0
  381. package/src/shared/build-info.js +39 -0
  382. package/src/shared/lib/agent-review.js +199 -0
  383. package/src/shared/lib/agent-turns.js +140 -0
  384. package/src/shared/lib/awareness-digest.js +120 -0
  385. package/src/shared/lib/branch-name.js +45 -0
  386. package/src/shared/lib/breakpoint-words.js +118 -0
  387. package/src/shared/lib/budget-words.js +56 -0
  388. package/src/shared/lib/burst.js +69 -0
  389. package/src/shared/lib/call-entry.js +105 -0
  390. package/src/shared/lib/check-compare.js +42 -0
  391. package/src/shared/lib/check-words.js +216 -0
  392. package/src/shared/lib/csv.js +62 -0
  393. package/src/shared/lib/folder-entry.js +87 -0
  394. package/src/shared/lib/freeze-words.js +60 -0
  395. package/src/shared/lib/fuzzy.js +84 -0
  396. package/src/shared/lib/git-state-words.js +80 -0
  397. package/src/shared/lib/grep-entry.js +86 -0
  398. package/src/shared/lib/grounding-line.js +61 -0
  399. package/src/shared/lib/import-line.js +71 -0
  400. package/src/shared/lib/item-status.js +249 -0
  401. package/src/shared/lib/line-changes.js +53 -0
  402. package/src/shared/lib/locator.js +71 -0
  403. package/src/shared/lib/matcher.js +120 -0
  404. package/src/shared/lib/merge-order.js +65 -0
  405. package/src/shared/lib/open-findings.js +68 -0
  406. package/src/shared/lib/other-work.js +108 -0
  407. package/src/shared/lib/package-entry.js +142 -0
  408. package/src/shared/lib/plan-vocab.js +40 -0
  409. package/src/shared/lib/pptx-xml.js +86 -0
  410. package/src/shared/lib/proposal-words.js +61 -0
  411. package/src/shared/lib/recurrence.js +183 -0
  412. package/src/shared/lib/references.js +72 -0
  413. package/src/shared/lib/rule-pattern.js +56 -0
  414. package/src/shared/lib/sdp-fingerprint.js +115 -0
  415. package/src/shared/lib/sdp-minimal.js +123 -0
  416. package/src/shared/lib/secret-paths.js +34 -0
  417. package/src/shared/lib/signal-words.js +172 -0
  418. package/src/shared/lib/signoff.js +56 -0
  419. package/src/shared/lib/skills-note.js +60 -0
  420. package/src/shared/lib/spec-sections.js +78 -0
  421. package/src/shared/lib/symbol-entry.js +60 -0
  422. package/src/shared/lib/tool-phrasing.js +338 -0
  423. package/src/shared/lib/turn-gap.js +27 -0
  424. package/src/shared/lib/workstream-words.js +38 -0
  425. package/src/shared/lib/xlsx-xml.js +144 -0
  426. package/src/shared/lib/zip-reader.js +99 -0
  427. package/src/shared/types/agent.js +27 -0
  428. package/src/shared/types/architecture-rules.js +30 -0
  429. package/src/shared/types/ast.js +15 -0
  430. package/src/shared/types/breakpoint.js +33 -0
  431. package/src/shared/types/channel.js +41 -0
  432. package/src/shared/types/criteria.js +15 -0
  433. package/src/shared/types/graph.js +15 -0
  434. package/src/shared/types/index.js +53 -0
  435. package/src/shared/types/peer.js +36 -0
  436. package/src/shared/types/pipeline.js +15 -0
  437. package/src/shared/types/plan.js +15 -0
  438. package/src/shared/types/play-forward.js +15 -0
  439. package/src/shared/types/power.js +15 -0
  440. package/src/shared/types/presence.js +15 -0
  441. package/src/shared/types/project-config.js +35 -0
  442. package/src/shared/types/project.js +15 -0
  443. package/src/shared/types/record.js +15 -0
  444. package/src/shared/types/recurring.js +15 -0
  445. package/src/shared/types/review.js +15 -0
  446. package/src/shared/types/settings.js +114 -0
  447. package/src/shared/types/stack.js +15 -0
  448. package/src/shared/types/system-doc.js +15 -0
  449. package/src/shared/types/system.js +15 -0
@@ -0,0 +1,1564 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var plan_file_service_exports = {};
29
+ __export(plan_file_service_exports, {
30
+ PLACEHOLDER_PLAN_TITLE: () => PLACEHOLDER_PLAN_TITLE,
31
+ cancelWriteThrough: () => cancelWriteThrough,
32
+ discoverPlanDirs: () => discoverPlanDirs,
33
+ exportIfSharedByDefault: () => exportIfSharedByDefault,
34
+ exportOnFirstTitle: () => exportOnFirstTitle,
35
+ exportPlan: () => exportPlan,
36
+ getLinkedPlanDir: () => getLinkedPlanDir,
37
+ importPlan: () => importPlan,
38
+ importedPlanRoot: () => importedPlanRoot,
39
+ makePlanSlug: () => makePlanSlug,
40
+ pruneOrphanedDirs: () => pruneOrphanedDirs,
41
+ reconcilePlanState: () => reconcilePlanState,
42
+ registerDiskHoldSettling: () => registerDiskHoldSettling,
43
+ scheduleWriteThrough: () => scheduleWriteThrough,
44
+ setPlanImportedListener: () => setPlanImportedListener,
45
+ startPlanFileWatcher: () => startPlanFileWatcher,
46
+ stopPlanFileWatcher: () => stopPlanFileWatcher,
47
+ unlinkPlan: () => unlinkPlan
48
+ });
49
+ module.exports = __toCommonJS(plan_file_service_exports);
50
+ var _lazy___channel_event_file_service = __toESM(require("./channel-event-file-service"));
51
+ var _lazy____server = __toESM(require("../server"));
52
+ var import_node_fs = __toESM(require("node:fs"));
53
+ var import_node_path = __toESM(require("node:path"));
54
+ var import_node_child_process = require("node:child_process");
55
+ var import_tree_watcher = require("./tree-watcher");
56
+ var import_yaml = require("yaml");
57
+ var planService = __toESM(require("./plan-service"));
58
+ var planPhasesService = __toESM(require("./plan-phases-service"));
59
+ var planDocsService = __toESM(require("./plan-documents-service"));
60
+ var planItemService = __toESM(require("./plan-item-service"));
61
+ var taskAttachmentsService = __toESM(require("./task-attachments-service"));
62
+ var commentService = __toESM(require("./comment-service"));
63
+ var criteriaService = __toESM(require("./criteria-service"));
64
+ var import_database = require("./database");
65
+ var import_skill_arrival_service = require("./skill-arrival-service");
66
+ var import_confined_fs = require("./confined-fs");
67
+ var import_self_write_tracker = require("./self-write-tracker");
68
+ var import_burst = require("../../shared/lib/burst");
69
+ var import_project_config_service = require("./project-config-service");
70
+ var import_plans_home = require("./plans-home");
71
+ var import_checkout_identity = require("./checkout-identity");
72
+ var import_section_workstreams = require("./section-workstreams");
73
+ var import_plan_doc_guard = require("./plan-doc-guard");
74
+ var import_breakpoint_service = require("./breakpoint-service");
75
+ var import_plan_file_refs = require("./plan-file-refs");
76
+ var import_signed_approvals = require("./signed-approvals");
77
+ var import_plan_arrivals = require("./plan-arrivals");
78
+ var import_cloud_files = require("./cloud-files");
79
+ function followTitleIfUncommitted(projectRoot, current, wanted) {
80
+ try {
81
+ if (import_node_fs.default.existsSync(wanted)) return current;
82
+ const from = (0, import_confined_fs.resolveWithin)(projectRoot, import_node_path.default.relative(projectRoot, current), "plan directory");
83
+ const to = (0, import_confined_fs.resolveWithin)(projectRoot, import_node_path.default.relative(projectRoot, wanted), "plan directory");
84
+ if (hasCommittedFiles(projectRoot, from)) return current;
85
+ const files = listFilesRecursive(from);
86
+ for (const f of files) {
87
+ (0, import_self_write_tracker.stampSelfWrite)(f);
88
+ (0, import_self_write_tracker.stampSelfWrite)(import_node_path.default.join(to, import_node_path.default.relative(from, f)));
89
+ }
90
+ import_node_fs.default.renameSync(from, to);
91
+ return to;
92
+ } catch (err) {
93
+ console.warn(`[Plans] Kept ${import_node_path.default.basename(current)}: ${err.message}`);
94
+ return current;
95
+ }
96
+ }
97
+ function hasCommittedFiles(projectRoot, dir) {
98
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(projectRoot, ".git"))) return false;
99
+ const out = (0, import_node_child_process.execFileSync)("git", ["-C", projectRoot, "ls-files", "--", import_node_path.default.relative(projectRoot, dir)], {
100
+ encoding: "utf-8",
101
+ stdio: ["ignore", "pipe", "ignore"],
102
+ timeout: 5e3
103
+ });
104
+ return out.trim().length > 0;
105
+ }
106
+ function listFilesRecursive(dir) {
107
+ const out = [];
108
+ for (const entry of import_node_fs.default.readdirSync(dir, { withFileTypes: true })) {
109
+ const p = import_node_path.default.join(dir, entry.name);
110
+ if (entry.isDirectory()) out.push(...listFilesRecursive(p));
111
+ else if (entry.isFile()) out.push(p);
112
+ }
113
+ return out;
114
+ }
115
+ const PLACEHOLDER_PLAN_TITLE = "Untitled plan";
116
+ function isPlaceholderTitle(title) {
117
+ const t = (title ?? "").trim();
118
+ return t === "" || t === PLACEHOLDER_PLAN_TITLE;
119
+ }
120
+ function exportOnFirstTitle(planUid, projectRoot, previousTitle) {
121
+ if (!isPlaceholderTitle(previousTitle)) return false;
122
+ if (getLinkedPlanDir(planUid, projectRoot)) return false;
123
+ return exportIfSharedByDefault(planUid, projectRoot);
124
+ }
125
+ function exportIfSharedByDefault(planUid, projectRoot) {
126
+ try {
127
+ if ((0, import_project_config_service.getEffectiveDefaultVisibility)(projectRoot) !== "shared") return false;
128
+ if (isPlaceholderTitle(planService.getPlan(planUid)?.title)) return false;
129
+ exportPlan(planUid, projectRoot);
130
+ return true;
131
+ } catch (err) {
132
+ console.warn(`[Plans] Could not write new plan ${planUid} to the project:`, err);
133
+ return false;
134
+ }
135
+ }
136
+ function exportPlan(planUid, projectRoot) {
137
+ const plan = planService.getPlan(planUid);
138
+ if (!plan) throw new Error(`Plan ${planUid} not found`);
139
+ const home = (0, import_plans_home.plansHome)(projectRoot);
140
+ if (!home) throw new import_plans_home.PlansFolderError((0, import_plans_home.getPlansFolder)(projectRoot).says);
141
+ const wanted = import_node_path.default.join(home, ".codetrellis", "plans", makePlanSlug(plan));
142
+ let planDir = getLinkedPlanDir(planUid, projectRoot);
143
+ if (planDir && planDir !== wanted) planDir = followTitleIfUncommitted(home, planDir, wanted);
144
+ planDir ??= wanted;
145
+ ensureDir(planDir);
146
+ const v2Items = planItemService.listAllItems(planUid);
147
+ if (v2Items.length > 0) {
148
+ return exportPlanV2(plan, planDir, v2Items, home);
149
+ }
150
+ return exportPlanV1(plan, planDir, planUid, home);
151
+ }
152
+ function exportPlanV1(plan, planDir, planUid, projectRoot) {
153
+ const phases = planPhasesService.listPhases(planUid);
154
+ const tasks = plan.tasks;
155
+ const docs = planDocsService.listPlanDocuments(planUid);
156
+ ensureDir(import_node_path.default.join(planDir, "phases"));
157
+ ensureDir(import_node_path.default.join(planDir, "tasks"));
158
+ ensureDir(import_node_path.default.join(planDir, "docs"));
159
+ const files = [];
160
+ const planFile = import_node_path.default.join(planDir, "plan.yaml");
161
+ writeFileAtomic(planFile, (0, import_yaml.stringify)(serializePlan(plan)));
162
+ files.push(planFile);
163
+ for (const phase of phases) {
164
+ const fname = `${pad2(phase.phaseNumber)}-${slugify(phase.title) || "phase"}.yaml`;
165
+ const fpath = import_node_path.default.join(planDir, "phases", fname);
166
+ writeFileAtomic(fpath, (0, import_yaml.stringify)(serializePhase(phase)));
167
+ files.push(fpath);
168
+ }
169
+ for (const task of tasks) {
170
+ const fname = `${pad3(task.sortOrder)}-${slugify(task.description) || "task"}.yaml`;
171
+ const fpath = import_node_path.default.join(planDir, "tasks", fname);
172
+ const attachments = taskAttachmentsService.listTaskAttachments(task.uid);
173
+ const comments = commentService.listCommentsFlat(task.uid);
174
+ writeFileAtomic(fpath, (0, import_yaml.stringify)(serializeTask(task, attachments, comments)));
175
+ files.push(fpath);
176
+ }
177
+ for (const doc of docs) {
178
+ const orderPrefix = doc.orderHint ? `${doc.orderHint}-` : "";
179
+ const fname = `${orderPrefix}${slugify(doc.title) || doc.docType}.md`;
180
+ const fpath = import_node_path.default.join(planDir, "docs", fname);
181
+ writeFileAtomic(fpath, serializeDoc(doc));
182
+ files.push(fpath);
183
+ }
184
+ ensureCodetrellisGitignore(import_node_path.default.join(projectRoot, ".codetrellis"));
185
+ return { planDir, files };
186
+ }
187
+ function exportPlanV2(plan, planDir, _allItems, projectRoot) {
188
+ const files = [];
189
+ const planFile = import_node_path.default.join(planDir, "plan.yaml");
190
+ writeFileAtomic(planFile, (0, import_yaml.stringify)(serializePlan(plan, 2)));
191
+ files.push(planFile);
192
+ const exportItems = planItemService.listItemsForExport(plan.uid);
193
+ const childrenOf = /* @__PURE__ */ new Map();
194
+ for (const { item, exportParentUid } of exportItems) {
195
+ const onDisk = { ...item, parentUid: exportParentUid };
196
+ const key = exportParentUid;
197
+ const list = childrenOf.get(key) ?? [];
198
+ list.push(onDisk);
199
+ childrenOf.set(key, list);
200
+ }
201
+ for (const list of childrenOf.values()) {
202
+ list.sort((a, b) => a.sortOrder - b.sortOrder || a.createdAt - b.createdAt);
203
+ }
204
+ const itemsDir = import_node_path.default.join(planDir, "items");
205
+ const preExisting = /* @__PURE__ */ new Set();
206
+ if (import_node_fs.default.existsSync(itemsDir)) {
207
+ collectYamlFiles(itemsDir, preExisting);
208
+ }
209
+ ensureDir(itemsDir);
210
+ const roots = childrenOf.get(null) ?? [];
211
+ writeItemChildren(roots, itemsDir, childrenOf, files);
212
+ const writtenSet = new Set(files);
213
+ for (const stale of preExisting) {
214
+ if (writtenSet.has(stale)) continue;
215
+ if ((0, import_cloud_files.isPlaceholder)(stale)) continue;
216
+ (0, import_self_write_tracker.stampSelfWrite)(stale);
217
+ try {
218
+ import_node_fs.default.unlinkSync(stale);
219
+ } catch {
220
+ }
221
+ }
222
+ pruneEmptyDirs(itemsDir);
223
+ ensureCodetrellisGitignore(import_node_path.default.join(projectRoot, ".codetrellis"));
224
+ return { planDir, files };
225
+ }
226
+ function collectYamlFiles(dir, out) {
227
+ for (const entry of import_node_fs.default.readdirSync(dir)) {
228
+ const full = import_node_path.default.join(dir, entry);
229
+ let stat;
230
+ try {
231
+ stat = import_node_fs.default.statSync(full);
232
+ } catch {
233
+ continue;
234
+ }
235
+ if (stat.isDirectory()) {
236
+ collectYamlFiles(full, out);
237
+ } else if (entry.endsWith(".yaml") || entry.endsWith(".yml")) {
238
+ out.add(full);
239
+ }
240
+ }
241
+ }
242
+ function pruneEmptyDirs(dir) {
243
+ if (!import_node_fs.default.existsSync(dir)) return;
244
+ for (const entry of import_node_fs.default.readdirSync(dir)) {
245
+ const full = import_node_path.default.join(dir, entry);
246
+ let stat;
247
+ try {
248
+ stat = import_node_fs.default.statSync(full);
249
+ } catch {
250
+ continue;
251
+ }
252
+ if (stat.isDirectory()) {
253
+ pruneEmptyDirs(full);
254
+ try {
255
+ if (import_node_fs.default.readdirSync(full).length === 0) import_node_fs.default.rmdirSync(full);
256
+ } catch {
257
+ }
258
+ }
259
+ }
260
+ }
261
+ function writeItemChildren(items, parentDir, childrenOf, files) {
262
+ for (const item of items) {
263
+ const children = childrenOf.get(item.uid) ?? [];
264
+ const slug = slugify(item.title) || item.kind;
265
+ const prefix = pad3(item.sortOrder);
266
+ if (children.length === 0) {
267
+ const fname = `${prefix}-${slug}.yaml`;
268
+ const fpath = import_node_path.default.join(parentDir, fname);
269
+ writeFileAtomic(fpath, (0, import_yaml.stringify)(serializeItem(item)));
270
+ files.push(fpath);
271
+ } else {
272
+ const dirName = `${prefix}-${slug}`;
273
+ const dirPath = import_node_path.default.join(parentDir, dirName);
274
+ ensureDir(dirPath);
275
+ const selfPath = import_node_path.default.join(dirPath, "_self.yaml");
276
+ writeFileAtomic(selfPath, (0, import_yaml.stringify)(serializeItem(item)));
277
+ files.push(selfPath);
278
+ writeItemChildren(children, dirPath, childrenOf, files);
279
+ }
280
+ }
281
+ }
282
+ function readPlanFile(planDir, file) {
283
+ if ((0, import_cloud_files.isPlaceholder)(import_node_path.default.resolve(file))) throw new import_cloud_files.NotOnDeviceError(import_node_path.default.relative(planDir, file) || "plan.yaml");
284
+ return (0, import_confined_fs.readTextWithin)(planDir, import_node_path.default.relative(planDir, file), "plan file");
285
+ }
286
+ function importPlan(planDirOrPlanYaml) {
287
+ importDepth++;
288
+ try {
289
+ return importPlanInternal(planDirOrPlanYaml);
290
+ } finally {
291
+ importDepth--;
292
+ }
293
+ }
294
+ let onlyOutsideChanges = false;
295
+ function oursUnchanged(file) {
296
+ return onlyOutsideChanges && (0, import_self_write_tracker.wasJustWrittenByUs)(file);
297
+ }
298
+ function importOutsideChanges(planDir) {
299
+ onlyOutsideChanges = true;
300
+ try {
301
+ return importPlan(planDir);
302
+ } finally {
303
+ onlyOutsideChanges = false;
304
+ }
305
+ }
306
+ function importPlanInternal(planDirOrPlanYaml) {
307
+ let planDir = planDirOrPlanYaml;
308
+ if (planDir.endsWith("plan.yaml")) {
309
+ planDir = import_node_path.default.dirname(planDir);
310
+ }
311
+ const planFile = import_node_path.default.join(planDir, "plan.yaml");
312
+ if (!import_node_fs.default.existsSync(planFile)) {
313
+ throw new Error(`Not a plan directory: ${planDir} (no plan.yaml)`);
314
+ }
315
+ const warnings = [];
316
+ const planRaw = (0, import_yaml.parse)(readPlanFile(planDir, planFile));
317
+ if (!planRaw || typeof planRaw !== "object" || !planRaw.uid || !planRaw.title) {
318
+ throw new Error(`Invalid plan.yaml at ${planFile} \u2014 missing uid or title`);
319
+ }
320
+ const planUid = planRaw.uid;
321
+ const held = planService.getPlan(planUid);
322
+ const checkout = checkoutOfPlanDir(planDir);
323
+ const sitsIn = checkout ? (0, import_plans_home.projectOfPlansHome)(checkout) ?? checkout : null;
324
+ if (held && sitsIn && (0, import_checkout_identity.heldByAnotherCheckout)(held.projectPath, sitsIn)) {
325
+ return {
326
+ plan: held,
327
+ phases: [],
328
+ tasks: [],
329
+ docs: [],
330
+ items: [],
331
+ version: planRaw.version === 2 || import_node_fs.default.existsSync(import_node_path.default.join(planDir, "items")) ? 2 : 1,
332
+ warnings: [`Not imported: this plan is held by ${held.projectPath}, another checkout of this repository. This copy's differences are that checkout's workstream changes.`]
333
+ };
334
+ }
335
+ const projectPath = importedPlanRoot(planUid, planDir, planRaw.projectPath);
336
+ const arriving = !planService.getPlan(planUid);
337
+ const skipOurs = (file) => !arriving && oursUnchanged(file);
338
+ if (!skipOurs(planFile)) {
339
+ upsertPlan(planUid, planRaw, projectPath);
340
+ if (arriving) (0, import_plan_arrivals.recordPlanArrival)(planUid, planFile);
341
+ else (0, import_plan_arrivals.completePlanArrival)(planUid, planFile);
342
+ if (planRaw.refs !== void 0) (0, import_plan_file_refs.importPlanRefs)(planUid, planRaw.refs);
343
+ }
344
+ const isV2 = planRaw.version === 2 || import_node_fs.default.existsSync(import_node_path.default.join(planDir, "items"));
345
+ const result = isV2 ? importPlanV2(planDir, planUid, warnings, skipOurs) : importPlanV1(planDir, planUid, warnings, skipOurs);
346
+ try {
347
+ const { discoverChannelEventFiles, importChannelEvent } = _lazy___channel_event_file_service;
348
+ for (const filePath of discoverChannelEventFiles(planDir)) {
349
+ try {
350
+ importChannelEvent(filePath, planDir);
351
+ } catch (err) {
352
+ warnings.push(`Failed to import channel event ${import_node_path.default.basename(filePath)}: ${err instanceof Error ? err.message : err}`);
353
+ }
354
+ }
355
+ } catch {
356
+ }
357
+ try {
358
+ const checked = (0, import_signed_approvals.importApprovals)(planDir, planUid, projectPath, {
359
+ criterion: (uid) => {
360
+ const c = criteriaService.getCriterion(uid);
361
+ const item = c ? planItemService.getItem(c.itemUid) : null;
362
+ return c && item ? { text: c.text, itemUid: c.itemUid, planUid: item.planUid } : null;
363
+ },
364
+ addSignoff: (input) => criteriaService.addVerifiedSignoff(input)
365
+ });
366
+ if (checked.unverified) warnings.push(`${checked.unverified} approval record${checked.unverified === 1 ? "" : "s"} could not be verified and count for nothing.`);
367
+ } catch (err) {
368
+ warnings.push(`Approval records were not read: ${err instanceof Error ? err.message : err}`);
369
+ }
370
+ if (planImportedListener) {
371
+ try {
372
+ planImportedListener(planUid, projectPath);
373
+ } catch (err) {
374
+ warnings.push(`Task-state records were not read: ${err instanceof Error ? err.message : err}`);
375
+ }
376
+ }
377
+ return result;
378
+ }
379
+ let planImportedListener = null;
380
+ function setPlanImportedListener(fn) {
381
+ planImportedListener = fn;
382
+ }
383
+ function importPlanV1(planDir, planUid, warnings, skipOurs) {
384
+ const phaseDir = import_node_path.default.join(planDir, "phases");
385
+ if (import_node_fs.default.existsSync(phaseDir)) {
386
+ for (const fname of import_node_fs.default.readdirSync(phaseDir)) {
387
+ if (!fname.endsWith(".yaml") && !fname.endsWith(".yml")) continue;
388
+ const fpath = import_node_path.default.join(phaseDir, fname);
389
+ if (skipOurs(fpath)) continue;
390
+ try {
391
+ const raw = (0, import_yaml.parse)(readPlanFile(planDir, fpath));
392
+ if (!raw?.uid) {
393
+ warnings.push(`Skipping ${fpath} \u2014 missing uid`);
394
+ continue;
395
+ }
396
+ upsertPhase(planUid, raw);
397
+ } catch (err) {
398
+ warnings.push(`Failed to import phase ${fname}: ${err instanceof Error ? err.message : err}`);
399
+ }
400
+ }
401
+ }
402
+ const taskDir = import_node_path.default.join(planDir, "tasks");
403
+ if (import_node_fs.default.existsSync(taskDir)) {
404
+ for (const fname of import_node_fs.default.readdirSync(taskDir)) {
405
+ if (!fname.endsWith(".yaml") && !fname.endsWith(".yml")) continue;
406
+ const fpath = import_node_path.default.join(taskDir, fname);
407
+ if (skipOurs(fpath)) continue;
408
+ try {
409
+ const raw = (0, import_yaml.parse)(readPlanFile(planDir, fpath));
410
+ if (!raw?.uid) {
411
+ warnings.push(`Skipping ${fpath} \u2014 missing uid`);
412
+ continue;
413
+ }
414
+ upsertTask(planUid, raw);
415
+ } catch (err) {
416
+ warnings.push(`Failed to import task ${fname}: ${err instanceof Error ? err.message : err}`);
417
+ }
418
+ }
419
+ }
420
+ const docDir = import_node_path.default.join(planDir, "docs");
421
+ if (import_node_fs.default.existsSync(docDir)) {
422
+ for (const fname of import_node_fs.default.readdirSync(docDir)) {
423
+ if (!fname.endsWith(".md")) continue;
424
+ const fpath = import_node_path.default.join(docDir, fname);
425
+ if (skipOurs(fpath)) continue;
426
+ try {
427
+ const { meta, body } = parseFrontMatter(readPlanFile(planDir, fpath));
428
+ if (!meta.uid) {
429
+ warnings.push(`Skipping ${fpath} \u2014 missing uid in front-matter`);
430
+ continue;
431
+ }
432
+ upsertDoc(planUid, meta, body);
433
+ } catch (err) {
434
+ warnings.push(`Failed to import doc ${fname}: ${err instanceof Error ? err.message : err}`);
435
+ }
436
+ }
437
+ }
438
+ const updatedPlan = planService.getPlan(planUid);
439
+ if (!updatedPlan) throw new Error(`Plan ${planUid} disappeared after import`);
440
+ return {
441
+ plan: updatedPlan,
442
+ phases: planPhasesService.listPhases(planUid),
443
+ tasks: updatedPlan.tasks,
444
+ docs: planDocsService.listPlanDocuments(planUid),
445
+ items: [],
446
+ version: 1,
447
+ warnings
448
+ };
449
+ }
450
+ function importPlanV2(planDir, planUid, warnings, skipOurs) {
451
+ const itemsDir = import_node_path.default.join(planDir, "items");
452
+ const importedItems = [];
453
+ if (import_node_fs.default.existsSync(itemsDir)) {
454
+ importItemsFromDir(planDir, itemsDir, planUid, null, warnings, importedItems, skipOurs);
455
+ }
456
+ const updatedPlan = planService.getPlan(planUid);
457
+ if (!updatedPlan) throw new Error(`Plan ${planUid} disappeared after import`);
458
+ return {
459
+ plan: updatedPlan,
460
+ phases: [],
461
+ tasks: [],
462
+ docs: [],
463
+ items: importedItems,
464
+ version: 2,
465
+ warnings
466
+ };
467
+ }
468
+ function importItemsFromDir(planDir, dir, planUid, parentUid, warnings, collected, skipOurs = () => false) {
469
+ const entries = import_node_fs.default.readdirSync(dir).sort();
470
+ for (const entry of entries) {
471
+ if (entry === "_self.yaml") continue;
472
+ const fullPath = import_node_path.default.join(dir, entry);
473
+ try {
474
+ const stat = import_node_fs.default.lstatSync(fullPath);
475
+ if (stat.isFile() && (entry.endsWith(".yaml") || entry.endsWith(".yml"))) {
476
+ if ((0, import_cloud_files.isPlaceholder)(fullPath)) {
477
+ warnings.push(`${import_node_path.default.relative(planDir, fullPath)} is not on this device`);
478
+ continue;
479
+ }
480
+ const raw = (0, import_yaml.parse)(readPlanFile(planDir, fullPath));
481
+ if (!raw?.uid) {
482
+ warnings.push(`Skipping ${fullPath} \u2014 missing uid`);
483
+ continue;
484
+ }
485
+ const item = skipOurs(fullPath) ? planItemService.getItem(raw.uid) : upsertItem(planUid, parentUid, raw, warnings, fullPath);
486
+ if (item) collected.push(item);
487
+ } else if (stat.isDirectory()) {
488
+ const selfPath = import_node_path.default.join(fullPath, "_self.yaml");
489
+ if (!import_node_fs.default.existsSync(selfPath)) {
490
+ warnings.push(`Skipping directory ${fullPath} \u2014 no _self.yaml`);
491
+ continue;
492
+ }
493
+ if ((0, import_cloud_files.isPlaceholder)(selfPath)) {
494
+ warnings.push(`${import_node_path.default.relative(planDir, selfPath)} is not on this device`);
495
+ continue;
496
+ }
497
+ const raw = (0, import_yaml.parse)(readPlanFile(planDir, selfPath));
498
+ if (!raw?.uid) {
499
+ warnings.push(`Skipping ${selfPath} \u2014 missing uid`);
500
+ continue;
501
+ }
502
+ const item = skipOurs(selfPath) ? planItemService.getItem(raw.uid) : upsertItem(planUid, parentUid, raw, warnings, selfPath);
503
+ if (item) {
504
+ collected.push(item);
505
+ importItemsFromDir(planDir, fullPath, planUid, item.uid, warnings, collected, skipOurs);
506
+ }
507
+ }
508
+ } catch (err) {
509
+ warnings.push(`Failed to import ${entry}: ${err instanceof Error ? err.message : err}`);
510
+ }
511
+ }
512
+ }
513
+ function upsertItem(planUid, parentUid, raw, warnings, file) {
514
+ const uid = String(raw.uid);
515
+ const existing = planItemService.getItem(uid);
516
+ const skillsBefore = existing?.skills ?? [];
517
+ const kind = raw.kind === "object" ? "object" : "action";
518
+ const now = Date.now();
519
+ if (existing) {
520
+ planItemService.withoutStateRecords(() => planItemService.updateItem(uid, {
521
+ title: typeof raw.title === "string" ? raw.title : void 0,
522
+ body: typeof raw.body === "string" ? raw.body : void 0,
523
+ template: raw.template ?? null,
524
+ // State is no longer written (C2.4b). A file from before that still
525
+ // carries it is read; a file without it leaves this machine's alone.
526
+ status: isTaskStatus(raw.status) ? raw.status : void 0,
527
+ assignee: "assignee" in raw ? raw.assignee ?? null : void 0,
528
+ assigneeType: "assignee" in raw ? raw.assigneeType ?? null : void 0,
529
+ assigneeModel: "assignee" in raw ? raw.assigneeModel ?? null : void 0,
530
+ progressPercent: typeof raw.progressPercent === "number" ? raw.progressPercent : void 0,
531
+ blockedReason: "blockedReason" in raw ? raw.blockedReason ?? null : void 0,
532
+ scopePath: raw.scopePath ?? null,
533
+ fileSpecs: Array.isArray(raw.fileSpecs) ? raw.fileSpecs : void 0,
534
+ symbolSpecs: Array.isArray(raw.symbolSpecs) ? raw.symbolSpecs : void 0,
535
+ newConnections: Array.isArray(raw.newConnections) ? raw.newConnections : void 0,
536
+ removedConnections: Array.isArray(raw.removedConnections) ? raw.removedConnections : void 0,
537
+ dependencies: Array.isArray(raw.dependencies) ? raw.dependencies : void 0,
538
+ skills: Array.isArray(raw.skills) ? raw.skills : void 0,
539
+ skillsMode: raw.skillsMode ?? void 0,
540
+ claimPolicy: raw.claimPolicy ?? void 0,
541
+ claimPolicyMode: raw.claimPolicyMode ?? void 0,
542
+ // Phase 32 C5.1 — a branch name, or nothing: a file is anyone's text.
543
+ workstream: (0, import_section_workstreams.cleanBranch)(raw.workstream),
544
+ executionConfig: raw.executionConfig ?? void 0,
545
+ executionConfigMode: raw.executionConfigMode ?? void 0,
546
+ constraints: raw.constraints ?? void 0,
547
+ constraintsMode: raw.constraintsMode ?? void 0,
548
+ requiresApproval: typeof raw.requiresApproval === "boolean" ? raw.requiresApproval : void 0,
549
+ // Phase 3.2 — per-item sharing
550
+ visibility: raw.visibility === "local" ? "local" : "shared",
551
+ overrideParentVisibility: raw.overrideParentVisibility === true,
552
+ parentUid,
553
+ author: "file-import",
554
+ authorType: "system"
555
+ }));
556
+ } else {
557
+ planItemService.createItem({
558
+ uid,
559
+ planUid,
560
+ parentUid,
561
+ sortOrder: typeof raw.sortOrder === "number" ? raw.sortOrder : void 0,
562
+ kind,
563
+ title: String(raw.title ?? ""),
564
+ body: typeof raw.body === "string" ? raw.body : "",
565
+ template: raw.template ?? null,
566
+ status: isTaskStatus(raw.status) ? raw.status : "pending",
567
+ assignee: raw.assignee ?? null,
568
+ assigneeType: raw.assigneeType ?? null,
569
+ assigneeModel: raw.assigneeModel ?? null,
570
+ progressPercent: typeof raw.progressPercent === "number" ? raw.progressPercent : null,
571
+ blockedReason: raw.blockedReason ?? null,
572
+ scopePath: raw.scopePath ?? null,
573
+ fileSpecs: Array.isArray(raw.fileSpecs) ? raw.fileSpecs : [],
574
+ symbolSpecs: Array.isArray(raw.symbolSpecs) ? raw.symbolSpecs : [],
575
+ newConnections: Array.isArray(raw.newConnections) ? raw.newConnections : [],
576
+ removedConnections: Array.isArray(raw.removedConnections) ? raw.removedConnections : [],
577
+ dependencies: Array.isArray(raw.dependencies) ? raw.dependencies : [],
578
+ skills: Array.isArray(raw.skills) ? raw.skills : [],
579
+ skillsMode: raw.skillsMode ?? "inherit",
580
+ claimPolicy: raw.claimPolicy ?? null,
581
+ claimPolicyMode: raw.claimPolicyMode ?? "inherit",
582
+ workstream: (0, import_section_workstreams.cleanBranch)(raw.workstream),
583
+ executionConfig: raw.executionConfig ?? null,
584
+ executionConfigMode: raw.executionConfigMode ?? "inherit",
585
+ constraints: raw.constraints ?? null,
586
+ constraintsMode: raw.constraintsMode ?? "inherit",
587
+ requiresApproval: raw.requiresApproval === true,
588
+ // Phase 3.2 — per-item sharing
589
+ visibility: raw.visibility === "local" ? "local" : "shared",
590
+ overrideParentVisibility: raw.overrideParentVisibility === true,
591
+ author: String(raw.author ?? "human"),
592
+ authorType: String(raw.authorType ?? "human"),
593
+ createdAt: toEpoch(raw.createdAt) ?? now,
594
+ updatedAt: toEpoch(raw.updatedAt) ?? now
595
+ });
596
+ }
597
+ const item = planItemService.getItem(uid);
598
+ if (Array.isArray(raw.attachments)) {
599
+ for (const a of raw.attachments) {
600
+ if (!a?.uid || !a?.kind || a?.value == null) continue;
601
+ const valid = taskAttachmentsService.validateImportedAttachment(a, uid);
602
+ if ("refused" in valid) {
603
+ warnings.push(`Skipped attachment ${String(a.uid)} on item ${uid}: ${valid.refused}`);
604
+ continue;
605
+ }
606
+ const stored = taskAttachmentsService.upsertAttachment({
607
+ uid: String(a.uid),
608
+ targetType: "item",
609
+ targetUid: uid,
610
+ kind: valid.kind,
611
+ value: valid.value,
612
+ label: typeof a.label === "string" ? a.label : null,
613
+ contentType: valid.contentType,
614
+ author: String(a.author ?? "human"),
615
+ authorType: String(a.authorType ?? "human"),
616
+ createdAt: toEpoch(a.createdAt) ?? Date.now(),
617
+ role: typeof a.role === "string" ? a.role : null
618
+ });
619
+ if (!stored) warnings.push(`Skipped attachment ${String(a.uid)}: it belongs to another item`);
620
+ }
621
+ }
622
+ if (item && Array.isArray(raw.criteria)) {
623
+ criteriaService.importCriteria(item.uid, raw.criteria);
624
+ }
625
+ if (item && raw.refs !== void 0) (0, import_plan_file_refs.importItemRefs)(item.uid, raw.refs);
626
+ if (Array.isArray(raw.comments)) {
627
+ for (const c of raw.comments) {
628
+ if (!c?.uid || !c?.body) continue;
629
+ upsertComment({
630
+ uid: String(c.uid),
631
+ targetType: "item",
632
+ targetUid: uid,
633
+ parentUid: c.parentUid ?? null,
634
+ author: String(c.author ?? "human"),
635
+ authorType: String(c.authorType ?? "human"),
636
+ body: String(c.body),
637
+ commentType: c.commentType ?? "comment",
638
+ kind: c.kind ?? null,
639
+ source: c.source ?? null,
640
+ metadata: c.metadata ?? null,
641
+ createdAt: toEpoch(c.createdAt) ?? Date.now()
642
+ });
643
+ }
644
+ }
645
+ if (item) {
646
+ try {
647
+ (0, import_skill_arrival_service.noteArrivals)({ itemUid: item.uid, before: skillsBefore, after: item.skills ?? [], file });
648
+ } catch {
649
+ }
650
+ }
651
+ return item;
652
+ }
653
+ function discoverPlanDirs(projectRoot) {
654
+ const home = (0, import_plans_home.plansHome)(projectRoot);
655
+ if (!home) return [];
656
+ const root = import_node_path.default.join(home, ".codetrellis", "plans");
657
+ if (!import_node_fs.default.existsSync(root)) return [];
658
+ return import_node_fs.default.readdirSync(root).map((name) => import_node_path.default.join(root, name)).filter((p) => {
659
+ try {
660
+ return import_node_fs.default.lstatSync(p).isDirectory() && import_node_fs.default.existsSync(import_node_path.default.join(p, "plan.yaml")) && !(0, import_cloud_files.isPlaceholder)(import_node_path.default.join(p, "plan.yaml"));
661
+ } catch {
662
+ return false;
663
+ }
664
+ });
665
+ }
666
+ function getLinkedPlanDir(planUid, projectRoot) {
667
+ const plan = planService.getPlan(planUid);
668
+ if (!plan) return null;
669
+ const home = (0, import_plans_home.plansHome)(projectRoot);
670
+ if (!home) return null;
671
+ const plansRoot = import_node_path.default.join(home, ".codetrellis", "plans");
672
+ const dir = import_node_path.default.join(plansRoot, makePlanSlug(plan));
673
+ if (import_node_fs.default.existsSync(import_node_path.default.join(dir, "plan.yaml"))) return dir;
674
+ const suffix = `-${plan.uid.split("-")[0]}`;
675
+ let entries;
676
+ try {
677
+ entries = import_node_fs.default.readdirSync(plansRoot, { withFileTypes: true });
678
+ } catch {
679
+ return null;
680
+ }
681
+ for (const entry of entries) {
682
+ if (!entry.isDirectory() || !entry.name.endsWith(suffix)) continue;
683
+ const candidate = import_node_path.default.join(plansRoot, entry.name);
684
+ try {
685
+ const raw = (0, import_yaml.parse)((0, import_confined_fs.readTextWithin)(candidate, "plan.yaml", "plan file"));
686
+ if (raw?.uid === plan.uid) return candidate;
687
+ } catch {
688
+ }
689
+ }
690
+ return null;
691
+ }
692
+ function unlinkPlan(planUid, projectRoot) {
693
+ cancelWriteThrough(planUid);
694
+ const dir = getLinkedPlanDir(planUid, projectRoot);
695
+ if (!dir) return { removed: false, planDir: null };
696
+ import_node_fs.default.rmSync(dir, { recursive: true, force: true });
697
+ return { removed: true, planDir: dir };
698
+ }
699
+ function reconcilePlanState(projectRoot) {
700
+ const diskDirs = discoverPlanDirs(projectRoot);
701
+ const totalDisk = diskDirs.length;
702
+ const dbPlans = planService.listPlans(projectRoot);
703
+ const totalDb = dbPlans.length;
704
+ const dbUidPrefixes = /* @__PURE__ */ new Set();
705
+ const dbUidFull = /* @__PURE__ */ new Set();
706
+ for (const p of dbPlans) {
707
+ dbUidFull.add(p.uid);
708
+ dbUidPrefixes.add(p.uid.split("-")[0]);
709
+ }
710
+ const orphanedOnDisk = [];
711
+ for (const dirPath of diskDirs) {
712
+ const dirName = import_node_path.default.basename(dirPath);
713
+ const parts = dirName.split("-");
714
+ const uidPrefix = parts.length > 0 ? parts[parts.length - 1] : null;
715
+ if (uidPrefix && dbUidPrefixes.has(uidPrefix)) continue;
716
+ let title = null;
717
+ try {
718
+ const yaml = import_node_fs.default.readFileSync(import_node_path.default.join(dirPath, "plan.yaml"), "utf-8");
719
+ const parsed = (0, import_yaml.parse)(yaml);
720
+ title = parsed?.title ?? null;
721
+ } catch {
722
+ }
723
+ orphanedOnDisk.push({ dirPath, dirName, uidPrefix, title });
724
+ }
725
+ const diskUidPrefixes = /* @__PURE__ */ new Set();
726
+ for (const dirPath of diskDirs) {
727
+ const dirName = import_node_path.default.basename(dirPath);
728
+ const parts = dirName.split("-");
729
+ if (parts.length > 0) diskUidPrefixes.add(parts[parts.length - 1]);
730
+ }
731
+ const orphanedInDb = [];
732
+ for (const p of dbPlans) {
733
+ const prefix = p.uid.split("-")[0];
734
+ if (!diskUidPrefixes.has(prefix)) {
735
+ orphanedInDb.push({ uid: p.uid, title: p.title, status: p.status });
736
+ }
737
+ }
738
+ return { orphanedOnDisk, orphanedInDb, totalDisk, totalDb };
739
+ }
740
+ function pruneOrphanedDirs(projectRoot, dirPaths) {
741
+ const orphans = new Set(reconcilePlanState(projectRoot).orphanedOnDisk.map((o) => o.dirPath));
742
+ const home = (0, import_plans_home.plansHome)(projectRoot);
743
+ if (!home) return { removed: 0, skipped: [...dirPaths] };
744
+ const plansRoot = import_node_path.default.join(home, ".codetrellis", "plans");
745
+ let removed = 0;
746
+ const skipped = [];
747
+ for (const dir of dirPaths) {
748
+ try {
749
+ if (typeof dir !== "string" || !orphans.has(dir)) {
750
+ skipped.push(String(dir));
751
+ continue;
752
+ }
753
+ (0, import_confined_fs.resolveWithin)(plansRoot, import_node_path.default.basename(dir), "plan directory");
754
+ if (import_node_path.default.dirname(dir) !== plansRoot || !import_node_fs.default.lstatSync(dir).isDirectory()) {
755
+ skipped.push(dir);
756
+ continue;
757
+ }
758
+ import_node_fs.default.rmSync(dir, { recursive: true, force: true });
759
+ removed++;
760
+ } catch {
761
+ skipped.push(String(dir));
762
+ }
763
+ }
764
+ return { removed, skipped };
765
+ }
766
+ const writeThroughTimers = /* @__PURE__ */ new Map();
767
+ const WRITE_THROUGH_DEBOUNCE_MS = 200;
768
+ let importDepth = 0;
769
+ function cancelWriteThrough(planUid) {
770
+ const pending = writeThroughTimers.get(planUid);
771
+ if (pending) clearTimeout(pending);
772
+ writeThroughTimers.delete(planUid);
773
+ }
774
+ function scheduleWriteThrough(planUid, projectRoot) {
775
+ if (importDepth > 0) return;
776
+ const plan = planService.getPlan(planUid);
777
+ if (!plan) return;
778
+ const root = projectRoot ?? plan.projectPath;
779
+ if (!root) return;
780
+ if (!getLinkedPlanDir(planUid, root)) return;
781
+ const existing = writeThroughTimers.get(planUid);
782
+ if (existing) clearTimeout(existing);
783
+ writeThroughTimers.set(planUid, setTimeout(() => {
784
+ writeThroughTimers.delete(planUid);
785
+ const linkedDir = getLinkedPlanDir(planUid, root);
786
+ if (!linkedDir) return;
787
+ flushPlanImports(linkedDir);
788
+ try {
789
+ const result = exportPlan(planUid, root);
790
+ try {
791
+ const { broadcast } = _lazy____server;
792
+ broadcast("plan-exported", { planUid, planDir: result.planDir, source: "auto-sync", files: result.files.length });
793
+ } catch {
794
+ }
795
+ } catch (err) {
796
+ console.warn(`[Auto-sync] Failed to export plan ${planUid}:`, err);
797
+ }
798
+ }, WRITE_THROUGH_DEBOUNCE_MS));
799
+ }
800
+ const watchersByProject = /* @__PURE__ */ new Map();
801
+ const watcherReady = /* @__PURE__ */ new Map();
802
+ const PLAN_WATCHER_READY_TIMEOUT_MS = 1e4;
803
+ const NEW_FOLDER_SWEEP_MS = 500;
804
+ const PLAN_IMPORT_QUIET_MS = 250;
805
+ const PLAN_IMPORT_MAX_WAIT_MS = 1e3;
806
+ const importBursts = /* @__PURE__ */ new Map();
807
+ const realDir = (p) => {
808
+ try {
809
+ return import_node_fs.default.realpathSync.native(p);
810
+ } catch {
811
+ return import_node_path.default.resolve(p);
812
+ }
813
+ };
814
+ function flushPlanImports(planDir) {
815
+ const want = realDir(planDir);
816
+ for (const b of importBursts.values()) {
817
+ for (const key of b.pending()) if (key === planDir || realDir(key) === want) b.flush(key);
818
+ }
819
+ }
820
+ function startPlanFileWatcher(projectRoot) {
821
+ if (watchersByProject.has(projectRoot)) {
822
+ return watcherReady.get(projectRoot) ?? Promise.resolve();
823
+ }
824
+ const home = (0, import_plans_home.plansHome)(projectRoot);
825
+ if (!home) return Promise.resolve();
826
+ const plansRoot = import_node_path.default.join(home, ".codetrellis", "plans");
827
+ try {
828
+ import_node_fs.default.mkdirSync(plansRoot, { recursive: true });
829
+ } catch (err) {
830
+ console.warn("[Auto-sync] Could not pre-create plans dir:", err);
831
+ }
832
+ const imports = (0, import_burst.burst)((planDir, files) => {
833
+ if (watchersByProject.get(projectRoot) !== watcher) return;
834
+ try {
835
+ const result = importOutsideChanges(planDir);
836
+ try {
837
+ const { broadcast } = _lazy____server;
838
+ broadcast("plan-imported", {
839
+ planUid: result.plan.uid,
840
+ source: "file-watcher",
841
+ planDir,
842
+ files: files.length,
843
+ warnings: result.warnings
844
+ });
845
+ } catch {
846
+ }
847
+ } catch (err) {
848
+ console.warn(`[Auto-sync] Failed to re-import ${planDir}:`, err);
849
+ }
850
+ }, { quietMs: PLAN_IMPORT_QUIET_MS, maxWaitMs: PLAN_IMPORT_MAX_WAIT_MS });
851
+ importBursts.set(projectRoot, imports);
852
+ const watcher = (0, import_tree_watcher.watchTree)(plansRoot, {
853
+ ignoreInitial: true,
854
+ persistent: true,
855
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
856
+ depth: 10
857
+ // V2 items/ tree can nest deeply
858
+ });
859
+ const handledAt = /* @__PURE__ */ new Map();
860
+ const sweepNewFolder = (dir, seenAt) => {
861
+ setTimeout(() => {
862
+ if (watchersByProject.get(projectRoot) !== watcher) return;
863
+ const missed = /* @__PURE__ */ new Map();
864
+ const walk = (d, depth) => {
865
+ if (depth > 10) return;
866
+ let entries;
867
+ try {
868
+ entries = import_node_fs.default.readdirSync(d, { withFileTypes: true });
869
+ } catch {
870
+ return;
871
+ }
872
+ for (const e of entries) {
873
+ const p = import_node_path.default.join(d, e.name);
874
+ if (e.isDirectory()) {
875
+ walk(p, depth + 1);
876
+ continue;
877
+ }
878
+ if (!e.isFile()) continue;
879
+ if ((handledAt.get(p) ?? 0) >= seenAt || (0, import_self_write_tracker.wasJustWrittenByUs)(p)) continue;
880
+ const planDir = findContainingPlanDir(p, plansRoot);
881
+ if (!planDir) continue;
882
+ (missed.get(planDir) ?? missed.set(planDir, []).get(planDir)).push(p);
883
+ }
884
+ };
885
+ walk(dir, 0);
886
+ const { isChannelEventFile } = _lazy___channel_event_file_service;
887
+ for (const files of missed.values()) {
888
+ const other = files.find((f) => !isChannelEventFile(f));
889
+ for (const f of other ? [other] : files) handle("add", f);
890
+ }
891
+ }, NEW_FOLDER_SWEEP_MS);
892
+ };
893
+ const handle = (event, filePath) => {
894
+ if (event !== "add" && event !== "change" && event !== "unlink") return;
895
+ if (!filePath) return;
896
+ if ((0, import_self_write_tracker.wasJustWrittenByUs)(filePath)) return;
897
+ if (handledAt.size > 5e3) handledAt.clear();
898
+ handledAt.set(filePath, Date.now());
899
+ const planDir = findContainingPlanDir(filePath, plansRoot);
900
+ if (!planDir) return;
901
+ if (filePath.endsWith(".yaml") || filePath.endsWith(".md")) {
902
+ try {
903
+ const content = import_node_fs.default.readFileSync(filePath, "utf-8");
904
+ if (hasGitConflictMarkers(content)) {
905
+ try {
906
+ const { broadcast } = _lazy____server;
907
+ broadcast("plan-file-conflict", { filePath, planDir });
908
+ } catch {
909
+ }
910
+ return;
911
+ }
912
+ } catch {
913
+ }
914
+ }
915
+ try {
916
+ const { isChannelEventFile, importChannelEvent } = _lazy___channel_event_file_service;
917
+ if (isChannelEventFile(filePath)) {
918
+ if (event === "unlink") return;
919
+ const imported = importChannelEvent(filePath, planDir);
920
+ if (imported) {
921
+ try {
922
+ const { broadcast } = _lazy____server;
923
+ broadcast("channel-event-imported", {
924
+ uid: imported.uid,
925
+ planUid: imported.planUid,
926
+ eventType: imported.eventType,
927
+ source: "file-watcher"
928
+ });
929
+ } catch {
930
+ }
931
+ }
932
+ return;
933
+ }
934
+ } catch (err) {
935
+ console.warn(`[Auto-sync] Channel-event import failed for ${filePath}:`, err);
936
+ return;
937
+ }
938
+ imports.add(planDir, filePath);
939
+ };
940
+ watcher.on("all", (event, filePath) => {
941
+ if (event === "addDir") {
942
+ if (filePath && filePath !== plansRoot) sweepNewFolder(filePath, Date.now());
943
+ return;
944
+ }
945
+ handle(event, filePath);
946
+ });
947
+ watcher.on("error", (err) => {
948
+ console.warn("[Auto-sync] Plan file watcher error:", err);
949
+ });
950
+ watchersByProject.set(projectRoot, watcher);
951
+ const ready = new Promise((resolve) => {
952
+ let settled = false;
953
+ const done = () => {
954
+ if (settled) return;
955
+ settled = true;
956
+ clearTimeout(guard);
957
+ resolve();
958
+ };
959
+ const guard = setTimeout(() => {
960
+ console.warn(`[Auto-sync] Plan watcher still starting after ${PLAN_WATCHER_READY_TIMEOUT_MS}ms \u2014 continuing`);
961
+ done();
962
+ }, PLAN_WATCHER_READY_TIMEOUT_MS);
963
+ guard.unref?.();
964
+ watcher.once("ready", done);
965
+ });
966
+ watcherReady.set(projectRoot, ready);
967
+ console.log(`[Auto-sync] Watching ${plansRoot}`);
968
+ return ready;
969
+ }
970
+ function stopPlanFileWatcher(projectRoot) {
971
+ const w = watchersByProject.get(projectRoot);
972
+ if (w) {
973
+ w.close().catch(() => {
974
+ });
975
+ watchersByProject.delete(projectRoot);
976
+ watcherReady.delete(projectRoot);
977
+ }
978
+ importBursts.get(projectRoot)?.cancel();
979
+ importBursts.delete(projectRoot);
980
+ }
981
+ function findContainingPlanDir(filePath, plansRoot) {
982
+ if (!filePath.startsWith(plansRoot)) return null;
983
+ const rel = import_node_path.default.relative(plansRoot, filePath);
984
+ const firstSegment = rel.split(import_node_path.default.sep)[0];
985
+ if (!firstSegment) return null;
986
+ const planDir = import_node_path.default.join(plansRoot, firstSegment);
987
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(planDir, "plan.yaml"))) return null;
988
+ return planDir;
989
+ }
990
+ function hasGitConflictMarkers(content) {
991
+ return /^<{7} |^={7}$|^>{7} /m.test(content);
992
+ }
993
+ function serializePlan(plan, version) {
994
+ const obj = {
995
+ ...version === 2 ? { version: 2 } : {},
996
+ uid: plan.uid,
997
+ title: plan.title,
998
+ description: plan.description,
999
+ status: plan.status,
1000
+ author: plan.author,
1001
+ authorType: plan.authorType,
1002
+ projectPath: plan.projectPath,
1003
+ createdAt: new Date(plan.createdAt).toISOString(),
1004
+ updatedAt: new Date(plan.updatedAt).toISOString()
1005
+ };
1006
+ if (plan.homeRepo) obj.homeRepo = plan.homeRepo;
1007
+ if (plan.scope && plan.scope.length > 0) {
1008
+ obj.scope = [...plan.scope].sort();
1009
+ }
1010
+ const refs = (0, import_plan_file_refs.refsForPlan)(plan.uid);
1011
+ if (refs.length) obj.refs = refs;
1012
+ return obj;
1013
+ }
1014
+ function serializeItem(item) {
1015
+ const attachments = taskAttachmentsService.listItemAttachments(item.uid);
1016
+ const comments = commentService.listItemComments(item.uid).filter((c) => c.kind !== "progress");
1017
+ const obj = {
1018
+ uid: item.uid,
1019
+ kind: item.kind,
1020
+ sortOrder: item.sortOrder,
1021
+ title: item.title
1022
+ };
1023
+ if (item.body) obj.body = item.body;
1024
+ if (item.template) obj.template = item.template;
1025
+ if (item.kind === "action") {
1026
+ if (item.scopePath) obj.scopePath = item.scopePath;
1027
+ if (item.fileSpecs?.length) obj.fileSpecs = item.fileSpecs;
1028
+ if (item.symbolSpecs?.length) obj.symbolSpecs = item.symbolSpecs;
1029
+ if (item.newConnections?.length) obj.newConnections = item.newConnections;
1030
+ if (item.removedConnections?.length) obj.removedConnections = item.removedConnections;
1031
+ if (item.dependencies?.length) obj.dependencies = item.dependencies;
1032
+ }
1033
+ if (item.skills?.length) {
1034
+ obj.skills = item.skills;
1035
+ if (item.skillsMode && item.skillsMode !== "inherit") obj.skillsMode = item.skillsMode;
1036
+ }
1037
+ if (item.workstream) obj.workstream = item.workstream;
1038
+ if (item.claimPolicy) {
1039
+ obj.claimPolicy = item.claimPolicy;
1040
+ if (item.claimPolicyMode && item.claimPolicyMode !== "inherit") obj.claimPolicyMode = item.claimPolicyMode;
1041
+ }
1042
+ if (item.executionConfig) {
1043
+ obj.executionConfig = item.executionConfig;
1044
+ if (item.executionConfigMode && item.executionConfigMode !== "inherit") obj.executionConfigMode = item.executionConfigMode;
1045
+ }
1046
+ if (item.constraints) {
1047
+ obj.constraints = item.constraints;
1048
+ if (item.constraintsMode && item.constraintsMode !== "inherit") obj.constraintsMode = item.constraintsMode;
1049
+ }
1050
+ if (item.requiresApproval) obj.requiresApproval = true;
1051
+ if (item.visibility === "local") obj.visibility = "local";
1052
+ if (item.overrideParentVisibility) obj.overrideParentVisibility = true;
1053
+ obj.author = item.author;
1054
+ obj.authorType = item.authorType;
1055
+ obj.createdAt = new Date(item.createdAt).toISOString();
1056
+ const refs = (0, import_plan_file_refs.refsForItem)(item.uid);
1057
+ if (refs.length) obj.refs = refs;
1058
+ if (attachments.length) {
1059
+ obj.attachments = attachments.map((a) => ({
1060
+ uid: a.uid,
1061
+ kind: a.kind,
1062
+ value: a.value,
1063
+ label: a.label ?? null,
1064
+ contentType: a.contentType ?? null,
1065
+ author: a.author,
1066
+ authorType: a.authorType,
1067
+ createdAt: new Date(a.createdAt).toISOString(),
1068
+ // A file reference's role travels (C3.4c), so a material is one on a
1069
+ // teammate's machine too. Its hash does not: each device takes its own.
1070
+ ...a.kind === "file_ref" && a.role ? { role: a.role } : {}
1071
+ }));
1072
+ }
1073
+ if (comments.length) {
1074
+ obj.comments = comments.map((c) => ({
1075
+ uid: c.uid,
1076
+ parentUid: c.parentUid,
1077
+ author: c.author,
1078
+ authorType: c.authorType,
1079
+ body: c.body,
1080
+ commentType: c.commentType,
1081
+ kind: c.kind ?? null,
1082
+ source: c.source ?? null,
1083
+ metadata: c.metadata ?? null,
1084
+ createdAt: new Date(c.createdAt).toISOString()
1085
+ }));
1086
+ }
1087
+ const criteria = criteriaService.criteriaForExport(item.uid);
1088
+ if (criteria.length) obj.criteria = criteria;
1089
+ return obj;
1090
+ }
1091
+ function serializePhase(phase) {
1092
+ return {
1093
+ uid: phase.uid,
1094
+ phaseNumber: phase.phaseNumber,
1095
+ title: phase.title,
1096
+ status: phase.status,
1097
+ gitCheckpoint: phase.gitCheckpoint,
1098
+ scope: phase.scope,
1099
+ prerequisites: phase.prerequisites,
1100
+ acceptanceCriteria: phase.acceptanceCriteria,
1101
+ createdAt: new Date(phase.createdAt).toISOString(),
1102
+ updatedAt: new Date(phase.updatedAt).toISOString()
1103
+ };
1104
+ }
1105
+ function serializeTask(task, attachments, comments) {
1106
+ return {
1107
+ uid: task.uid,
1108
+ sortOrder: task.sortOrder,
1109
+ phaseUid: task.phaseUid,
1110
+ parentTaskUid: task.parentTaskUid ?? null,
1111
+ description: task.description,
1112
+ status: task.status,
1113
+ assignee: task.assignee,
1114
+ assigneeType: task.assigneeType,
1115
+ assigneeModel: task.assigneeModel,
1116
+ affectedFiles: task.affectedFiles,
1117
+ affectedSymbols: task.affectedSymbols,
1118
+ newConnections: task.newConnections,
1119
+ removedConnections: task.removedConnections,
1120
+ dependencies: task.dependencies,
1121
+ fileSpec: task.fileSpec ?? null,
1122
+ symbolSpecs: task.symbolSpecs ?? [],
1123
+ // Phase 14 §A
1124
+ body: task.body ?? null,
1125
+ prompt: task.prompt ?? null,
1126
+ scopePath: task.scopePath ?? null,
1127
+ fileSpecs: task.fileSpecs ?? [],
1128
+ progressPercent: task.progressPercent ?? null,
1129
+ blockedReason: task.blockedReason ?? null,
1130
+ attachments: attachments.map((a) => ({
1131
+ uid: a.uid,
1132
+ kind: a.kind,
1133
+ value: a.value,
1134
+ label: a.label ?? null,
1135
+ contentType: a.contentType ?? null,
1136
+ author: a.author,
1137
+ authorType: a.authorType,
1138
+ createdAt: new Date(a.createdAt).toISOString(),
1139
+ ...a.kind === "file_ref" && a.role ? { role: a.role } : {}
1140
+ })),
1141
+ comments: comments.map((c) => ({
1142
+ uid: c.uid,
1143
+ parentUid: c.parentUid,
1144
+ author: c.author,
1145
+ authorType: c.authorType,
1146
+ body: c.body,
1147
+ commentType: c.commentType,
1148
+ kind: c.kind ?? null,
1149
+ source: c.source ?? null,
1150
+ metadata: c.metadata ?? null,
1151
+ createdAt: new Date(c.createdAt).toISOString()
1152
+ })),
1153
+ createdAt: new Date(task.createdAt).toISOString(),
1154
+ updatedAt: new Date(task.updatedAt).toISOString()
1155
+ };
1156
+ }
1157
+ function serializeDoc(doc) {
1158
+ const meta = {
1159
+ uid: doc.uid,
1160
+ docType: doc.docType,
1161
+ title: doc.title,
1162
+ orderHint: doc.orderHint,
1163
+ parentDocUid: doc.parentDocUid,
1164
+ version: doc.version,
1165
+ author: doc.author,
1166
+ authorType: doc.authorType,
1167
+ createdAt: new Date(doc.createdAt).toISOString(),
1168
+ updatedAt: new Date(doc.updatedAt).toISOString()
1169
+ };
1170
+ return `---
1171
+ ${(0, import_yaml.stringify)(meta)}---
1172
+
1173
+ ${doc.body || ""}`;
1174
+ }
1175
+ function checkoutOfPlanDir(planDir) {
1176
+ const slugDir = import_node_path.default.resolve(planDir);
1177
+ const plansDir = import_node_path.default.dirname(slugDir);
1178
+ const dotDir = import_node_path.default.dirname(plansDir);
1179
+ return import_node_path.default.basename(plansDir) === "plans" && import_node_path.default.basename(dotDir) === ".codetrellis" ? import_node_path.default.dirname(dotDir) : null;
1180
+ }
1181
+ function importedPlanRoot(planUid, planDir, declared) {
1182
+ const existing = planService.getPlan(planUid)?.projectPath;
1183
+ if (existing && import_node_path.default.isAbsolute(existing) && import_node_fs.default.existsSync(existing)) return existing;
1184
+ const slugDir = import_node_path.default.resolve(planDir);
1185
+ const plansDir = import_node_path.default.dirname(slugDir);
1186
+ const dotDir = import_node_path.default.dirname(plansDir);
1187
+ if (import_node_path.default.basename(plansDir) === "plans" && import_node_path.default.basename(dotDir) === ".codetrellis") {
1188
+ return (0, import_plans_home.projectOfPlansHome)(import_node_path.default.dirname(dotDir)) ?? import_node_path.default.dirname(dotDir);
1189
+ }
1190
+ if (typeof declared === "string" && import_node_path.default.isAbsolute(declared)) return declared;
1191
+ return ".";
1192
+ }
1193
+ function upsertPlan(planUid, raw, projectPath) {
1194
+ const db = (0, import_database.getDb)();
1195
+ const exists = db.exec(`SELECT uid FROM plans WHERE uid = ?`, [planUid]);
1196
+ const now = Date.now();
1197
+ const hasHomeRepo = Object.prototype.hasOwnProperty.call(raw, "homeRepo");
1198
+ const hasScope = Object.prototype.hasOwnProperty.call(raw, "scope");
1199
+ const homeRepoVal = hasHomeRepo ? typeof raw.homeRepo === "string" && raw.homeRepo ? String(raw.homeRepo) : null : null;
1200
+ const scopeArray = hasScope && Array.isArray(raw.scope) ? raw.scope.filter((s) => typeof s === "string") : [];
1201
+ const scopeJson = JSON.stringify(scopeArray);
1202
+ if (exists[0]?.values[0]) {
1203
+ const sets = [
1204
+ "title = ?",
1205
+ "description = ?",
1206
+ "status = ?",
1207
+ "author = ?",
1208
+ "author_type = ?",
1209
+ "project_path = ?",
1210
+ "updated_at = ?"
1211
+ ];
1212
+ const params = [
1213
+ String(raw.title),
1214
+ String(raw.description ?? ""),
1215
+ String(raw.status ?? "draft"),
1216
+ String(raw.author ?? "human"),
1217
+ String(raw.authorType ?? "human"),
1218
+ projectPath,
1219
+ toEpoch(raw.updatedAt) ?? now
1220
+ ];
1221
+ if (hasHomeRepo) {
1222
+ sets.push("home_repo = ?");
1223
+ params.push(homeRepoVal);
1224
+ }
1225
+ if (hasScope) {
1226
+ sets.push("scope = ?");
1227
+ params.push(scopeJson);
1228
+ }
1229
+ params.push(planUid);
1230
+ db.run(`UPDATE plans SET ${sets.join(", ")} WHERE uid = ?`, params);
1231
+ } else {
1232
+ db.run(
1233
+ `INSERT INTO plans (uid, title, description, status, author, author_type, project_path, created_at, updated_at, home_repo, scope) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1234
+ [
1235
+ planUid,
1236
+ String(raw.title),
1237
+ String(raw.description ?? ""),
1238
+ String(raw.status ?? "draft"),
1239
+ String(raw.author ?? "human"),
1240
+ String(raw.authorType ?? "human"),
1241
+ projectPath,
1242
+ toEpoch(raw.createdAt) ?? now,
1243
+ toEpoch(raw.updatedAt) ?? now,
1244
+ homeRepoVal,
1245
+ scopeJson
1246
+ ]
1247
+ );
1248
+ }
1249
+ }
1250
+ function upsertPhase(planUid, raw) {
1251
+ const existing = planPhasesService.getPhase(raw.uid);
1252
+ if (existing) {
1253
+ planPhasesService.updatePhase(raw.uid, {
1254
+ phaseNumber: typeof raw.phaseNumber === "number" ? raw.phaseNumber : void 0,
1255
+ title: typeof raw.title === "string" ? raw.title : void 0,
1256
+ status: isPhaseStatus(raw.status) ? raw.status : void 0,
1257
+ gitCheckpoint: raw.gitCheckpoint ?? null,
1258
+ scope: typeof raw.scope === "string" ? raw.scope : void 0,
1259
+ prerequisites: typeof raw.prerequisites === "string" ? raw.prerequisites : void 0,
1260
+ acceptanceCriteria: typeof raw.acceptanceCriteria === "string" ? raw.acceptanceCriteria : void 0
1261
+ });
1262
+ return;
1263
+ }
1264
+ const db = (0, import_database.getDb)();
1265
+ const now = Date.now();
1266
+ db.run(
1267
+ `INSERT INTO plan_phases (uid, plan_uid, phase_number, title, scope, prerequisites, git_checkpoint, acceptance_criteria, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1268
+ [
1269
+ String(raw.uid),
1270
+ planUid,
1271
+ Number(raw.phaseNumber ?? 1),
1272
+ String(raw.title ?? ""),
1273
+ String(raw.scope ?? ""),
1274
+ String(raw.prerequisites ?? ""),
1275
+ raw.gitCheckpoint ?? null,
1276
+ String(raw.acceptanceCriteria ?? ""),
1277
+ isPhaseStatus(raw.status) ? raw.status : "pending",
1278
+ toEpoch(raw.createdAt) ?? now,
1279
+ toEpoch(raw.updatedAt) ?? now
1280
+ ]
1281
+ );
1282
+ }
1283
+ function upsertTask(planUid, raw) {
1284
+ const existing = planService.getTaskByUid(String(raw.uid));
1285
+ const fileSpecs = Array.isArray(raw.fileSpecs) ? raw.fileSpecs : void 0;
1286
+ if (existing) {
1287
+ planService.updateTask(String(raw.uid), {
1288
+ description: typeof raw.description === "string" ? raw.description : void 0,
1289
+ status: isTaskStatus(raw.status) ? raw.status : void 0,
1290
+ phaseUid: raw.phaseUid ?? null,
1291
+ assignee: raw.assignee ?? null,
1292
+ assigneeType: raw.assigneeType ?? null,
1293
+ assigneeModel: raw.assigneeModel ?? null,
1294
+ affectedFiles: Array.isArray(raw.affectedFiles) ? raw.affectedFiles : void 0,
1295
+ affectedSymbols: Array.isArray(raw.affectedSymbols) ? raw.affectedSymbols : void 0,
1296
+ newConnections: Array.isArray(raw.newConnections) ? raw.newConnections : void 0,
1297
+ removedConnections: Array.isArray(raw.removedConnections) ? raw.removedConnections : void 0,
1298
+ dependencies: Array.isArray(raw.dependencies) ? raw.dependencies : void 0,
1299
+ fileSpec: typeof raw.fileSpec === "string" ? raw.fileSpec : void 0,
1300
+ symbolSpecs: Array.isArray(raw.symbolSpecs) ? raw.symbolSpecs : void 0,
1301
+ // Phase 14 §A
1302
+ parentTaskUid: raw.parentTaskUid ?? null,
1303
+ body: typeof raw.body === "string" ? raw.body : raw.body === null ? null : void 0,
1304
+ prompt: typeof raw.prompt === "string" ? raw.prompt : raw.prompt === null ? null : void 0,
1305
+ scopePath: raw.scopePath ?? null,
1306
+ fileSpecs,
1307
+ progressPercent: typeof raw.progressPercent === "number" ? raw.progressPercent : raw.progressPercent === null ? null : void 0,
1308
+ blockedReason: typeof raw.blockedReason === "string" ? raw.blockedReason : raw.blockedReason === null ? null : void 0
1309
+ });
1310
+ } else {
1311
+ const db = (0, import_database.getDb)();
1312
+ const now = Date.now();
1313
+ db.run(
1314
+ `INSERT INTO tasks (uid, plan_uid, sort_order, description, status, assignee, assignee_type, assignee_model, affected_files, affected_symbols, new_connections, removed_connections, dependencies, file_spec, symbol_specs, phase_uid, parent_task_uid, body, prompt, scope_path, file_specs, progress_percent, blocked_reason, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1315
+ [
1316
+ String(raw.uid),
1317
+ planUid,
1318
+ Number(raw.sortOrder ?? 0),
1319
+ String(raw.description ?? ""),
1320
+ isTaskStatus(raw.status) ? raw.status : "pending",
1321
+ raw.assignee ?? null,
1322
+ raw.assigneeType ?? null,
1323
+ raw.assigneeModel ?? null,
1324
+ JSON.stringify(raw.affectedFiles ?? []),
1325
+ JSON.stringify(raw.affectedSymbols ?? []),
1326
+ JSON.stringify(raw.newConnections ?? []),
1327
+ JSON.stringify(raw.removedConnections ?? []),
1328
+ JSON.stringify(raw.dependencies ?? []),
1329
+ raw.fileSpec ?? null,
1330
+ JSON.stringify(raw.symbolSpecs ?? []),
1331
+ raw.phaseUid ?? null,
1332
+ raw.parentTaskUid ?? null,
1333
+ raw.body ?? null,
1334
+ raw.prompt ?? null,
1335
+ raw.scopePath ?? null,
1336
+ JSON.stringify(fileSpecs ?? []),
1337
+ typeof raw.progressPercent === "number" ? raw.progressPercent : null,
1338
+ raw.blockedReason ?? null,
1339
+ toEpoch(raw.createdAt) ?? now,
1340
+ toEpoch(raw.updatedAt) ?? now
1341
+ ]
1342
+ );
1343
+ }
1344
+ if (Array.isArray(raw.attachments)) {
1345
+ for (const a of raw.attachments) {
1346
+ if (!a?.uid || !a?.kind || a?.value == null) continue;
1347
+ const valid = taskAttachmentsService.validateImportedAttachment(a, String(raw.uid));
1348
+ if ("refused" in valid) {
1349
+ console.warn(`[Import] Skipped attachment ${String(a.uid)} on task ${String(raw.uid)}: ${valid.refused}`);
1350
+ continue;
1351
+ }
1352
+ const stored = taskAttachmentsService.upsertAttachment({
1353
+ uid: String(a.uid),
1354
+ targetType: "task",
1355
+ targetUid: String(raw.uid),
1356
+ kind: valid.kind,
1357
+ value: valid.value,
1358
+ label: typeof a.label === "string" ? a.label : null,
1359
+ contentType: valid.contentType,
1360
+ author: String(a.author ?? "human"),
1361
+ authorType: String(a.authorType ?? "human"),
1362
+ createdAt: toEpoch(a.createdAt) ?? Date.now()
1363
+ });
1364
+ if (!stored) console.warn(`[Import] Skipped attachment ${String(a.uid)}: it belongs to another item`);
1365
+ }
1366
+ }
1367
+ if (Array.isArray(raw.comments)) {
1368
+ for (const c of raw.comments) {
1369
+ if (!c?.uid || !c?.body) continue;
1370
+ upsertComment({
1371
+ uid: String(c.uid),
1372
+ targetType: "task",
1373
+ targetUid: String(raw.uid),
1374
+ parentUid: c.parentUid ?? null,
1375
+ author: String(c.author ?? "human"),
1376
+ authorType: String(c.authorType ?? "human"),
1377
+ body: String(c.body),
1378
+ commentType: c.commentType ?? "comment",
1379
+ kind: c.kind ?? null,
1380
+ source: c.source ?? null,
1381
+ metadata: c.metadata ?? null,
1382
+ createdAt: toEpoch(c.createdAt) ?? Date.now()
1383
+ });
1384
+ }
1385
+ }
1386
+ }
1387
+ function upsertComment(input) {
1388
+ const db = (0, import_database.getDb)();
1389
+ const exists = db.exec(`SELECT uid FROM comments WHERE uid = ?`, [input.uid]);
1390
+ const metadataJson = input.metadata ? JSON.stringify(input.metadata) : null;
1391
+ if (exists[0]?.values[0]) {
1392
+ db.run(
1393
+ `UPDATE comments SET target_type = ?, target_uid = ?, parent_uid = ?, author = ?, author_type = ?, body = ?, comment_type = ?, kind = ?, source = ?, metadata = ?, created_at = ? WHERE uid = ?`,
1394
+ [
1395
+ input.targetType,
1396
+ input.targetUid,
1397
+ input.parentUid,
1398
+ input.author,
1399
+ input.authorType,
1400
+ input.body,
1401
+ input.commentType,
1402
+ input.kind,
1403
+ input.source,
1404
+ metadataJson,
1405
+ input.createdAt,
1406
+ input.uid
1407
+ ]
1408
+ );
1409
+ } else {
1410
+ db.run(
1411
+ `INSERT INTO comments (uid, target_type, target_uid, parent_uid, author, author_type, body, comment_type, kind, source, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1412
+ [
1413
+ input.uid,
1414
+ input.targetType,
1415
+ input.targetUid,
1416
+ input.parentUid,
1417
+ input.author,
1418
+ input.authorType,
1419
+ input.body,
1420
+ input.commentType,
1421
+ input.kind,
1422
+ input.source,
1423
+ metadataJson,
1424
+ input.createdAt
1425
+ ]
1426
+ );
1427
+ }
1428
+ }
1429
+ function upsertDoc(planUid, meta, body) {
1430
+ const docUid = String(meta.uid);
1431
+ const existing = planDocsService.getPlanDocument(docUid);
1432
+ if (existing) {
1433
+ const title = typeof meta.title === "string" ? meta.title : void 0;
1434
+ if ((0, import_plan_doc_guard.guardDiskEdit)(docUid, { title, body }) === "held") return;
1435
+ planDocsService.updatePlanDocument(docUid, {
1436
+ title: typeof meta.title === "string" ? meta.title : void 0,
1437
+ body,
1438
+ docType: typeof meta.docType === "string" ? meta.docType : void 0,
1439
+ orderHint: meta.orderHint ?? null,
1440
+ parentDocUid: meta.parentDocUid ?? null
1441
+ });
1442
+ return;
1443
+ }
1444
+ const db = (0, import_database.getDb)();
1445
+ const now = Date.now();
1446
+ db.run(
1447
+ `INSERT INTO plan_documents (uid, plan_uid, doc_type, title, body, version, author, author_type, order_hint, parent_doc_uid, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1448
+ [
1449
+ docUid,
1450
+ planUid,
1451
+ String(meta.docType ?? "custom"),
1452
+ String(meta.title ?? ""),
1453
+ body,
1454
+ Number(meta.version ?? 1),
1455
+ String(meta.author ?? "human"),
1456
+ String(meta.authorType ?? "human"),
1457
+ meta.orderHint ?? null,
1458
+ meta.parentDocUid ?? null,
1459
+ toEpoch(meta.createdAt) ?? now,
1460
+ toEpoch(meta.updatedAt) ?? now
1461
+ ]
1462
+ );
1463
+ }
1464
+ let settling = false;
1465
+ function registerDiskHoldSettling() {
1466
+ if (settling) return;
1467
+ settling = true;
1468
+ (0, import_breakpoint_service.onHitAnswered)((hit) => {
1469
+ if (hit.action !== "disk") return;
1470
+ const outcome = (0, import_plan_doc_guard.settleDiskHold)(hit);
1471
+ if (outcome === "restore" && hit.planUid) scheduleWriteThrough(hit.planUid);
1472
+ });
1473
+ }
1474
+ function makePlanSlug(plan) {
1475
+ const titlePart = slugify(plan.title) || "plan";
1476
+ const uidSuffix = plan.uid.split("-")[0];
1477
+ return `${titlePart}-${uidSuffix}`;
1478
+ }
1479
+ function slugify(s) {
1480
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
1481
+ }
1482
+ function pad2(n) {
1483
+ return String(n).padStart(2, "0");
1484
+ }
1485
+ function pad3(n) {
1486
+ return String(n).padStart(3, "0");
1487
+ }
1488
+ function ensureDir(dir) {
1489
+ if (!import_node_fs.default.existsSync(dir)) import_node_fs.default.mkdirSync(dir, { recursive: true });
1490
+ }
1491
+ function writeFileAtomic(filePath, content) {
1492
+ if ((0, import_cloud_files.isPlaceholder)(filePath)) {
1493
+ console.warn(`[Plans] ${filePath} is not on this device yet; not written, so a newer copy in the cloud is kept.`);
1494
+ return;
1495
+ }
1496
+ try {
1497
+ if (import_node_fs.default.readFileSync(filePath, "utf-8") === content) return;
1498
+ } catch {
1499
+ }
1500
+ const tmp = `${filePath}.tmp`;
1501
+ import_node_fs.default.writeFileSync(tmp, content, "utf-8");
1502
+ import_node_fs.default.renameSync(tmp, filePath);
1503
+ (0, import_self_write_tracker.stampSelfWrite)(filePath);
1504
+ }
1505
+ function ensureCodetrellisGitignore(codetrellisDir) {
1506
+ const gitignorePath = import_node_path.default.join(codetrellisDir, ".gitignore");
1507
+ if (import_node_fs.default.existsSync(gitignorePath)) return;
1508
+ const content = [
1509
+ "# CodeTrellis runtime cache \u2014 do not commit",
1510
+ "cache/",
1511
+ ""
1512
+ ].join("\n");
1513
+ try {
1514
+ import_node_fs.default.writeFileSync(gitignorePath, content, "utf-8");
1515
+ } catch {
1516
+ }
1517
+ }
1518
+ function toEpoch(s) {
1519
+ if (typeof s === "number") return s;
1520
+ if (typeof s !== "string") return null;
1521
+ const t = Date.parse(s);
1522
+ return Number.isFinite(t) ? t : null;
1523
+ }
1524
+ function isPhaseStatus(s) {
1525
+ return s === "pending" || s === "in_progress" || s === "done" || s === "blocked";
1526
+ }
1527
+ function isTaskStatus(s) {
1528
+ return s === "pending" || s === "assigned" || s === "in_progress" || s === "done" || s === "blocked" || s === "skipped";
1529
+ }
1530
+ function parseFrontMatter(source) {
1531
+ if (!source.startsWith("---")) return { meta: {}, body: source };
1532
+ const end = source.indexOf("\n---", 3);
1533
+ if (end < 0) return { meta: {}, body: source };
1534
+ const yamlBlock = source.slice(3, end).replace(/^\n/, "");
1535
+ const body = source.slice(end + 4).replace(/^\n+/, "");
1536
+ let meta = {};
1537
+ try {
1538
+ meta = (0, import_yaml.parse)(yamlBlock) ?? {};
1539
+ } catch {
1540
+ meta = {};
1541
+ }
1542
+ return { meta, body };
1543
+ }
1544
+ // Annotate the CommonJS export names for ESM import in node:
1545
+ 0 && (module.exports = {
1546
+ PLACEHOLDER_PLAN_TITLE,
1547
+ cancelWriteThrough,
1548
+ discoverPlanDirs,
1549
+ exportIfSharedByDefault,
1550
+ exportOnFirstTitle,
1551
+ exportPlan,
1552
+ getLinkedPlanDir,
1553
+ importPlan,
1554
+ importedPlanRoot,
1555
+ makePlanSlug,
1556
+ pruneOrphanedDirs,
1557
+ reconcilePlanState,
1558
+ registerDiskHoldSettling,
1559
+ scheduleWriteThrough,
1560
+ setPlanImportedListener,
1561
+ startPlanFileWatcher,
1562
+ stopPlanFileWatcher,
1563
+ unlinkPlan
1564
+ });