@ouro.bot/cli 0.1.0-alpha.43 → 0.1.0-alpha.431

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 (338) hide show
  1. package/README.md +123 -15
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  5. package/changelog.json +2715 -0
  6. package/dist/arc/attention-types.js +8 -0
  7. package/dist/arc/cares.js +140 -0
  8. package/dist/arc/episodes.js +117 -0
  9. package/dist/arc/intentions.js +133 -0
  10. package/dist/arc/json-store.js +117 -0
  11. package/dist/arc/obligations.js +237 -0
  12. package/dist/arc/packets.js +193 -0
  13. package/dist/arc/presence.js +185 -0
  14. package/dist/arc/task-lifecycle.js +65 -0
  15. package/dist/heart/active-work.js +832 -0
  16. package/dist/heart/agent-entry.js +58 -3
  17. package/dist/heart/attachments/image-normalize.js +194 -0
  18. package/dist/heart/attachments/materialize.js +97 -0
  19. package/dist/heart/attachments/originals.js +88 -0
  20. package/dist/heart/attachments/render.js +29 -0
  21. package/dist/heart/attachments/sources/adapter.js +2 -0
  22. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  23. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  24. package/dist/heart/attachments/sources/index.js +16 -0
  25. package/dist/heart/attachments/store.js +103 -0
  26. package/dist/heart/attachments/types.js +93 -0
  27. package/dist/heart/auth/auth-flow.js +426 -0
  28. package/dist/heart/bridges/manager.js +358 -0
  29. package/dist/heart/bridges/state-machine.js +135 -0
  30. package/dist/heart/bridges/store.js +123 -0
  31. package/dist/heart/bundle-state.js +168 -0
  32. package/dist/heart/commitments.js +111 -0
  33. package/dist/heart/config-registry.js +304 -0
  34. package/dist/heart/config.js +110 -128
  35. package/dist/heart/core.js +745 -227
  36. package/dist/heart/cross-chat-delivery.js +131 -0
  37. package/dist/heart/daemon/agent-config-check.js +424 -0
  38. package/dist/heart/daemon/agent-discovery.js +79 -3
  39. package/dist/heart/daemon/agent-service.js +360 -0
  40. package/dist/heart/daemon/agentic-repair.js +214 -0
  41. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  42. package/dist/heart/daemon/cadence.js +70 -0
  43. package/dist/heart/daemon/cli-defaults.js +604 -0
  44. package/dist/heart/daemon/cli-exec.js +5234 -0
  45. package/dist/heart/daemon/cli-help.js +428 -0
  46. package/dist/heart/daemon/cli-parse.js +1156 -0
  47. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  48. package/dist/heart/daemon/cli-render.js +561 -0
  49. package/dist/heart/daemon/cli-types.js +8 -0
  50. package/dist/heart/daemon/connect-bay.js +442 -0
  51. package/dist/heart/daemon/daemon-cli.js +28 -1582
  52. package/dist/heart/daemon/daemon-entry.js +356 -3
  53. package/dist/heart/daemon/daemon-health.js +141 -0
  54. package/dist/heart/daemon/daemon-runtime-sync.js +175 -12
  55. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  56. package/dist/heart/daemon/daemon.js +684 -58
  57. package/dist/heart/daemon/doctor-types.js +8 -0
  58. package/dist/heart/daemon/doctor.js +427 -0
  59. package/dist/heart/daemon/health-monitor.js +79 -1
  60. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  61. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  62. package/dist/heart/daemon/http-health-probe.js +80 -0
  63. package/dist/heart/daemon/human-command-screens.js +140 -0
  64. package/dist/heart/daemon/human-readiness.js +114 -0
  65. package/dist/heart/daemon/inner-status.js +89 -0
  66. package/dist/heart/daemon/interactive-repair.js +307 -0
  67. package/dist/heart/daemon/launchd.js +46 -9
  68. package/dist/heart/daemon/log-tailer.js +82 -12
  69. package/dist/heart/daemon/logs-prune.js +105 -0
  70. package/dist/heart/daemon/message-router.js +2 -2
  71. package/dist/heart/daemon/os-cron-deps.js +134 -0
  72. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  73. package/dist/heart/daemon/ouro-entry.js +3 -1
  74. package/dist/heart/daemon/process-manager.js +214 -0
  75. package/dist/heart/daemon/provider-discovery.js +137 -0
  76. package/dist/heart/daemon/pulse.js +475 -0
  77. package/dist/heart/daemon/readiness-repair.js +275 -0
  78. package/dist/heart/daemon/run-hooks.js +2 -0
  79. package/dist/heart/daemon/runtime-logging.js +67 -16
  80. package/dist/heart/daemon/runtime-metadata.js +73 -0
  81. package/dist/heart/daemon/runtime-mode.js +67 -0
  82. package/dist/heart/daemon/safe-mode.js +161 -0
  83. package/dist/heart/daemon/sense-manager.js +145 -32
  84. package/dist/heart/daemon/session-id-resolver.js +131 -0
  85. package/dist/heart/daemon/skill-management-installer.js +94 -0
  86. package/dist/heart/daemon/socket-client.js +307 -0
  87. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  88. package/dist/heart/daemon/startup-tui.js +272 -0
  89. package/dist/heart/daemon/task-scheduler.js +3 -25
  90. package/dist/heart/daemon/terminal-ui.js +169 -0
  91. package/dist/heart/daemon/thoughts.js +510 -0
  92. package/dist/heart/daemon/up-progress.js +271 -0
  93. package/dist/heart/delegation.js +62 -0
  94. package/dist/heart/habits/habit-migration.js +189 -0
  95. package/dist/heart/habits/habit-parser.js +140 -0
  96. package/dist/heart/habits/habit-runtime-state.js +100 -0
  97. package/dist/heart/habits/habit-scheduler.js +372 -0
  98. package/dist/heart/{daemon → hatch}/hatch-flow.js +52 -117
  99. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  100. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  101. package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
  102. package/dist/heart/identity.js +161 -65
  103. package/dist/heart/kept-notes.js +357 -0
  104. package/dist/heart/kicks.js +1 -1
  105. package/dist/heart/machine-identity.js +161 -0
  106. package/dist/heart/mcp/mcp-server.js +653 -0
  107. package/dist/heart/migrate-config.js +100 -0
  108. package/dist/heart/model-capabilities.js +59 -0
  109. package/dist/heart/outlook/outlook-http-hooks.js +64 -0
  110. package/dist/heart/outlook/outlook-http-response.js +7 -0
  111. package/dist/heart/outlook/outlook-http-routes.js +232 -0
  112. package/dist/heart/outlook/outlook-http-static.js +99 -0
  113. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  114. package/dist/heart/outlook/outlook-http.js +99 -0
  115. package/dist/heart/outlook/outlook-read.js +28 -0
  116. package/dist/heart/outlook/outlook-types.js +27 -0
  117. package/dist/heart/outlook/outlook-view.js +195 -0
  118. package/dist/heart/outlook/readers/agent-machine.js +359 -0
  119. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  120. package/dist/heart/outlook/readers/runtime-readers.js +644 -0
  121. package/dist/heart/outlook/readers/sessions.js +232 -0
  122. package/dist/heart/outlook/readers/shared.js +111 -0
  123. package/dist/heart/platform.js +81 -0
  124. package/dist/heart/progress-story.js +42 -0
  125. package/dist/heart/provider-attempt.js +133 -0
  126. package/dist/heart/provider-binding-resolver.js +239 -0
  127. package/dist/heart/provider-credentials.js +391 -0
  128. package/dist/heart/provider-failover.js +266 -0
  129. package/dist/heart/provider-models.js +81 -0
  130. package/dist/heart/provider-ping.js +237 -0
  131. package/dist/heart/provider-state.js +216 -0
  132. package/dist/heart/provider-visibility.js +186 -0
  133. package/dist/heart/providers/anthropic-token.js +131 -0
  134. package/dist/heart/providers/anthropic.js +193 -55
  135. package/dist/heart/providers/azure.js +103 -12
  136. package/dist/heart/providers/error-classification.js +63 -0
  137. package/dist/heart/providers/github-copilot.js +145 -0
  138. package/dist/heart/providers/minimax-vlm.js +189 -0
  139. package/dist/heart/providers/minimax.js +29 -7
  140. package/dist/heart/providers/openai-codex.js +62 -38
  141. package/dist/heart/runtime-capability-check.js +170 -0
  142. package/dist/heart/runtime-credentials.js +260 -0
  143. package/dist/heart/sense-truth.js +3 -0
  144. package/dist/heart/session-activity.js +190 -0
  145. package/dist/heart/session-events.js +855 -0
  146. package/dist/heart/session-transcript.js +167 -0
  147. package/dist/heart/start-of-turn-packet.js +345 -0
  148. package/dist/heart/streaming.js +36 -27
  149. package/dist/heart/sync.js +332 -0
  150. package/dist/heart/target-resolution.js +127 -0
  151. package/dist/heart/tempo.js +93 -0
  152. package/dist/heart/temporal-view.js +41 -0
  153. package/dist/heart/tool-activity-callbacks.js +36 -0
  154. package/dist/heart/tool-description.js +135 -0
  155. package/dist/heart/tool-friction.js +55 -0
  156. package/dist/heart/tool-loop.js +200 -0
  157. package/dist/heart/turn-context.js +351 -0
  158. package/dist/heart/turn-coordinator.js +28 -0
  159. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  160. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  161. package/dist/heart/versioning/ouro-path-installer.js +301 -0
  162. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  163. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  164. package/dist/heart/{daemon → versioning}/update-checker.js +3 -1
  165. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  166. package/dist/mind/bundle-manifest.js +7 -1
  167. package/dist/mind/context.js +132 -93
  168. package/dist/mind/diary-integrity.js +60 -0
  169. package/dist/mind/{memory.js → diary.js} +74 -93
  170. package/dist/mind/embedding-provider.js +60 -0
  171. package/dist/mind/file-state.js +179 -0
  172. package/dist/mind/friends/channel.js +21 -0
  173. package/dist/mind/friends/group-context.js +144 -0
  174. package/dist/mind/friends/resolver.js +38 -1
  175. package/dist/mind/friends/store-file.js +39 -3
  176. package/dist/mind/friends/trust-explanation.js +74 -0
  177. package/dist/mind/friends/types.js +1 -1
  178. package/dist/mind/journal-index.js +161 -0
  179. package/dist/mind/note-search.js +268 -0
  180. package/dist/mind/obligation-steering.js +221 -0
  181. package/dist/mind/pending.js +66 -7
  182. package/dist/mind/prompt-refresh.js +3 -2
  183. package/dist/mind/prompt.js +947 -165
  184. package/dist/mind/provenance-trust.js +26 -0
  185. package/dist/mind/scrutiny.js +173 -0
  186. package/dist/nerves/cli-logging.js +7 -1
  187. package/dist/nerves/coverage/audit-rules.js +15 -6
  188. package/dist/nerves/coverage/audit.js +28 -2
  189. package/dist/nerves/coverage/cli.js +1 -1
  190. package/dist/nerves/coverage/contract.js +5 -5
  191. package/dist/nerves/coverage/file-completeness.js +83 -5
  192. package/dist/nerves/coverage/run-artifacts.js +1 -1
  193. package/dist/nerves/event-buffer.js +111 -0
  194. package/dist/nerves/index.js +224 -4
  195. package/dist/nerves/observation.js +20 -0
  196. package/dist/nerves/redact.js +79 -0
  197. package/dist/nerves/runtime.js +5 -1
  198. package/dist/outlook-ui/assets/index-BAcU08c-.css +1 -0
  199. package/dist/outlook-ui/assets/index-D7l3l4vY.js +61 -0
  200. package/dist/outlook-ui/index.html +15 -0
  201. package/dist/repertoire/ado-client.js +15 -56
  202. package/dist/repertoire/ado-semantic.js +11 -10
  203. package/dist/repertoire/api-client.js +97 -0
  204. package/dist/repertoire/bitwarden-store.js +702 -0
  205. package/dist/repertoire/bundle-templates.js +72 -0
  206. package/dist/repertoire/bw-installer.js +79 -0
  207. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  208. package/dist/repertoire/coding/context-pack.js +330 -0
  209. package/dist/repertoire/coding/feedback.js +197 -30
  210. package/dist/repertoire/coding/manager.js +158 -9
  211. package/dist/repertoire/coding/spawner.js +55 -9
  212. package/dist/repertoire/coding/tools.js +170 -7
  213. package/dist/repertoire/commerce-errors.js +109 -0
  214. package/dist/repertoire/commerce-self-test.js +156 -0
  215. package/dist/repertoire/credential-access.js +111 -0
  216. package/dist/repertoire/duffel-client.js +185 -0
  217. package/dist/repertoire/github-client.js +14 -55
  218. package/dist/repertoire/graph-client.js +11 -52
  219. package/dist/repertoire/guardrails.js +371 -0
  220. package/dist/repertoire/mcp-client.js +255 -0
  221. package/dist/repertoire/mcp-manager.js +305 -0
  222. package/dist/repertoire/mcp-tools.js +63 -0
  223. package/dist/repertoire/shell-sessions.js +133 -0
  224. package/dist/repertoire/skills.js +15 -24
  225. package/dist/repertoire/stripe-client.js +131 -0
  226. package/dist/repertoire/tasks/board.js +43 -5
  227. package/dist/repertoire/tasks/fix.js +182 -0
  228. package/dist/repertoire/tasks/index.js +37 -4
  229. package/dist/repertoire/tasks/lifecycle.js +2 -2
  230. package/dist/repertoire/tasks/parser.js +3 -2
  231. package/dist/repertoire/tasks/scanner.js +194 -37
  232. package/dist/repertoire/tasks/transitions.js +16 -78
  233. package/dist/repertoire/tool-results.js +29 -0
  234. package/dist/repertoire/tools-attachments.js +317 -0
  235. package/dist/repertoire/tools-base.js +42 -690
  236. package/dist/repertoire/tools-bluebubbles.js +1 -0
  237. package/dist/repertoire/tools-bridge.js +141 -0
  238. package/dist/repertoire/tools-bundle.js +984 -0
  239. package/dist/repertoire/tools-config.js +185 -0
  240. package/dist/repertoire/tools-continuity.js +248 -0
  241. package/dist/repertoire/tools-credential.js +361 -0
  242. package/dist/repertoire/tools-files.js +342 -0
  243. package/dist/repertoire/tools-flight.js +224 -0
  244. package/dist/repertoire/tools-flow.js +105 -0
  245. package/dist/repertoire/tools-github.js +1 -7
  246. package/dist/repertoire/tools-notes.js +376 -0
  247. package/dist/repertoire/tools-session.js +739 -0
  248. package/dist/repertoire/tools-shell.js +120 -0
  249. package/dist/repertoire/tools-stripe.js +180 -0
  250. package/dist/repertoire/tools-surface.js +243 -0
  251. package/dist/repertoire/tools-teams.js +9 -39
  252. package/dist/repertoire/tools-travel.js +125 -0
  253. package/dist/repertoire/tools-user-profile.js +144 -0
  254. package/dist/repertoire/tools-vault.js +40 -0
  255. package/dist/repertoire/tools.js +144 -113
  256. package/dist/repertoire/travel-api-client.js +360 -0
  257. package/dist/repertoire/user-profile.js +131 -0
  258. package/dist/repertoire/vault-setup.js +246 -0
  259. package/dist/repertoire/vault-unlock.js +421 -0
  260. package/dist/scripts/claude-code-hook.js +41 -0
  261. package/dist/scripts/claude-code-stop-hook.js +47 -0
  262. package/dist/senses/attention-queue.js +116 -0
  263. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  264. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  265. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +260 -9
  266. package/dist/senses/bluebubbles/entry.js +73 -0
  267. package/dist/senses/bluebubbles/inbound-log.js +113 -0
  268. package/dist/senses/bluebubbles/index.js +1620 -0
  269. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  270. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  271. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +45 -3
  272. package/dist/senses/bluebubbles/replay.js +129 -0
  273. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  274. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  275. package/dist/senses/cli/bracketed-paste.js +82 -0
  276. package/dist/senses/cli/image-paste.js +287 -0
  277. package/dist/senses/cli/image-ref-navigation.js +75 -0
  278. package/dist/senses/cli/ink-app.js +156 -0
  279. package/dist/senses/cli/inline-diff.js +64 -0
  280. package/dist/senses/cli/input-keys.js +174 -0
  281. package/dist/senses/cli/kill-ring.js +86 -0
  282. package/dist/senses/cli/message-list.js +51 -0
  283. package/dist/senses/cli/ouro-tui.js +605 -0
  284. package/dist/senses/cli/spinner-imperative.js +135 -0
  285. package/dist/senses/cli/spinner.js +101 -0
  286. package/dist/senses/cli/status-line.js +60 -0
  287. package/dist/senses/cli/streaming-markdown.js +526 -0
  288. package/dist/senses/cli/tool-display.js +83 -0
  289. package/dist/senses/cli/tool-render.js +85 -0
  290. package/dist/senses/cli/tui-store.js +240 -0
  291. package/dist/senses/cli/virtual-list.js +35 -0
  292. package/dist/senses/cli-entry.js +60 -8
  293. package/dist/senses/cli-layout.js +187 -0
  294. package/dist/senses/cli.js +516 -211
  295. package/dist/senses/commands.js +66 -3
  296. package/dist/senses/habit-turn-message.js +108 -0
  297. package/dist/senses/inner-dialog-worker.js +102 -19
  298. package/dist/senses/inner-dialog.js +597 -95
  299. package/dist/senses/pipeline.js +533 -72
  300. package/dist/senses/proactive-content-guard.js +51 -0
  301. package/dist/senses/shared-turn.js +205 -0
  302. package/dist/senses/surface-tool.js +68 -0
  303. package/dist/senses/teams-entry.js +60 -8
  304. package/dist/senses/teams.js +413 -163
  305. package/dist/senses/trust-gate.js +5 -5
  306. package/package.json +32 -7
  307. package/skills/agent-commerce.md +106 -0
  308. package/skills/browser-navigation.md +117 -0
  309. package/skills/commerce-setup-guide.md +116 -0
  310. package/skills/commerce-setup.md +84 -0
  311. package/skills/configure-dev-tools.md +101 -0
  312. package/skills/travel-planning.md +138 -0
  313. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  314. package/dist/heart/daemon/subagent-installer.js +0 -134
  315. package/dist/mind/associative-recall.js +0 -209
  316. package/dist/senses/bluebubbles-entry.js +0 -11
  317. package/dist/senses/bluebubbles.js +0 -854
  318. package/dist/senses/debug-activity.js +0 -127
  319. package/subagents/README.md +0 -60
  320. package/subagents/work-doer.md +0 -235
  321. package/subagents/work-merger.md +0 -618
  322. package/subagents/work-planner.md +0 -382
  323. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  324. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  325. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  326. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  327. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  328. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  329. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  330. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  331. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  332. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  333. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  334. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  335. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  336. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  337. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  338. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,271 @@
1
+ "use strict";
2
+ /**
3
+ * UpProgress — accumulated-checklist progress renderer.
4
+ *
5
+ * Displays completed phases with checkmarks, the current phase with a
6
+ * spinner and elapsed time, and pending phases as plain text. Uses ANSI
7
+ * cursor control for in-place overwriting in TTY mode, and falls back to
8
+ * static line-per-phase output in non-TTY mode.
9
+ *
10
+ * The caller can drive animation by calling `render(now)`. In production CLI
11
+ * use, `autoRender` starts a short-lived timer while a TTY phase is active so
12
+ * long operations never leave a dead-looking cursor.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.CommandProgress = exports.UpProgress = void 0;
16
+ const runtime_1 = require("../../nerves/runtime");
17
+ // ── ANSI constants (shared with startup-tui.ts pattern) ──
18
+ const SPINNER_FRAMES = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
19
+ const RESET = "\x1b[0m";
20
+ const BOLD = "\x1b[1m";
21
+ const DIM = "\x1b[2m";
22
+ const GREEN = "\x1b[38;2;46;204;64m";
23
+ const RED = "\x1b[38;2;255;106;106m";
24
+ function splitDetailLines(detail) {
25
+ if (!detail)
26
+ return [];
27
+ return detail
28
+ .split(/\r?\n/)
29
+ .map((line) => line.trimEnd())
30
+ .filter((line) => line.length > 0);
31
+ }
32
+ // ── UpProgress class ──
33
+ class UpProgress {
34
+ write;
35
+ isTTY;
36
+ now;
37
+ autoRender;
38
+ renderIntervalMs;
39
+ setTimer;
40
+ clearTimer;
41
+ eventScope;
42
+ commandName;
43
+ completed = [];
44
+ currentPhase = null;
45
+ currentDetail = null;
46
+ prevLineCount = 0;
47
+ ended = false;
48
+ renderTimer = null;
49
+ constructor(options) {
50
+ /* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
51
+ this.write = options?.write ?? ((text) => process.stdout.write(text));
52
+ /* v8 ignore next -- thin wrapper: real isTTY check injected for testability @preserve */
53
+ this.isTTY = options?.isTTY ?? (process.stdout.isTTY === true);
54
+ /* v8 ignore next -- thin wrapper: real Date.now injected for testability @preserve */
55
+ this.now = options?.now ?? (() => Date.now());
56
+ this.autoRender = options?.autoRender ?? false;
57
+ this.renderIntervalMs = options?.renderIntervalMs ?? 80;
58
+ /* v8 ignore start -- real timers are injected in tests when needed @preserve */
59
+ this.setTimer = options?.setInterval ?? ((callback, ms) => setInterval(callback, ms));
60
+ this.clearTimer = options?.clearInterval ?? ((handle) => clearInterval(handle));
61
+ /* v8 ignore stop */
62
+ this.eventScope = options?.eventScope ?? "up";
63
+ this.commandName = options?.commandName ?? null;
64
+ }
65
+ /**
66
+ * Begin a new phase with spinner. If a phase is already active, it is
67
+ * auto-completed (no detail text).
68
+ */
69
+ startPhase(label) {
70
+ if (this.currentPhase) {
71
+ this.completePhase(this.currentPhase.label);
72
+ }
73
+ this.currentPhase = { label, startedAt: this.now() };
74
+ this.currentDetail = null;
75
+ if (this.isTTY) {
76
+ this.ensureAutoRender();
77
+ this.flushRender();
78
+ }
79
+ else {
80
+ this.write(` ... ${label}\n`);
81
+ }
82
+ }
83
+ /**
84
+ * Emit a one-line status breadcrumb in non-TTY mode without affecting the
85
+ * accumulated checklist state. Used for daemon startup sub-steps.
86
+ */
87
+ announceStep(label) {
88
+ if (this.currentPhase) {
89
+ this.updateDetail(label);
90
+ return;
91
+ }
92
+ if (this.isTTY)
93
+ return;
94
+ this.write(` ${label}\n`);
95
+ }
96
+ /**
97
+ * Update the sub-step detail on the current spinner phase. Rendered as
98
+ * "label (Xs) -- detail" in TTY mode. In non-TTY mode, writes changed
99
+ * detail lines so long operations remain visible in logs and captured output.
100
+ */
101
+ updateDetail(detail) {
102
+ if (!this.currentPhase || detail === this.currentDetail)
103
+ return;
104
+ this.currentDetail = detail;
105
+ this.currentPhase.detail = detail;
106
+ if (this.isTTY) {
107
+ this.flushRender();
108
+ return;
109
+ }
110
+ for (const line of splitDetailLines(detail)) {
111
+ this.write(` ${line}\n`);
112
+ }
113
+ }
114
+ /**
115
+ * Mark the current phase as done. In non-TTY mode, immediately writes
116
+ * a static line. Emits a nerves event for observability.
117
+ */
118
+ completePhase(label, detail) {
119
+ if (!this.currentPhase) {
120
+ return;
121
+ }
122
+ const elapsedMs = this.now() - this.currentPhase.startedAt;
123
+ this.completed.push({ status: "success", label, detail });
124
+ this.currentPhase = null;
125
+ this.currentDetail = null;
126
+ this.stopAutoRender();
127
+ if (this.eventScope === "command") {
128
+ (0, runtime_1.emitNervesEvent)({
129
+ component: "daemon",
130
+ event: "daemon.cli_progress_phase_complete",
131
+ message: `phase complete: ${label}`,
132
+ meta: { command: this.commandName, phase: label, detail: detail ?? null, elapsedMs },
133
+ });
134
+ }
135
+ else {
136
+ (0, runtime_1.emitNervesEvent)({
137
+ component: "daemon",
138
+ event: "daemon.up_phase_complete",
139
+ message: `phase complete: ${label}`,
140
+ meta: { phase: label, detail: detail ?? null, elapsedMs },
141
+ });
142
+ }
143
+ if (this.isTTY) {
144
+ this.flushRender();
145
+ }
146
+ else {
147
+ const detailStr = detail ? ` \u2014 ${detail}` : "";
148
+ this.write(` \u2713 ${label}${detailStr}\n`);
149
+ }
150
+ }
151
+ failPhase(label, detail) {
152
+ if (!this.currentPhase) {
153
+ return;
154
+ }
155
+ const elapsedMs = this.now() - this.currentPhase.startedAt;
156
+ this.completed.push({ status: "failure", label, detail });
157
+ this.currentPhase = null;
158
+ this.currentDetail = null;
159
+ this.stopAutoRender();
160
+ if (this.eventScope === "command") {
161
+ (0, runtime_1.emitNervesEvent)({
162
+ level: "warn",
163
+ component: "daemon",
164
+ event: "daemon.cli_progress_phase_failed",
165
+ message: `phase failed: ${label}`,
166
+ meta: { command: this.commandName, phase: label, detail: detail ?? null, elapsedMs },
167
+ });
168
+ }
169
+ else {
170
+ (0, runtime_1.emitNervesEvent)({
171
+ level: "warn",
172
+ component: "daemon",
173
+ event: "daemon.up_phase_failed",
174
+ message: `phase failed: ${label}`,
175
+ meta: { phase: label, detail: detail ?? null, elapsedMs },
176
+ });
177
+ }
178
+ if (this.isTTY) {
179
+ this.flushRender();
180
+ }
181
+ else {
182
+ const detailStr = detail ? ` \u2014 ${detail}` : "";
183
+ this.write(` \u2717 ${label}${detailStr}\n`);
184
+ }
185
+ }
186
+ /**
187
+ * Build an ANSI string for in-place terminal display. Returns empty
188
+ * string in non-TTY mode (output is written eagerly in completePhase).
189
+ */
190
+ render(now) {
191
+ if (!this.isTTY) {
192
+ return "";
193
+ }
194
+ const lines = [];
195
+ // Completed phases
196
+ for (const phase of this.completed) {
197
+ const detailStr = phase.detail ? ` ${DIM}\u2014 ${phase.detail}${RESET}` : "";
198
+ if (phase.status === "failure") {
199
+ lines.push(` ${RED}\u2717${RESET} ${phase.label}${detailStr}`);
200
+ }
201
+ else {
202
+ lines.push(` ${GREEN}\u2713${RESET} ${phase.label}${detailStr}`);
203
+ }
204
+ }
205
+ // Current phase with spinner
206
+ if (this.currentPhase) {
207
+ const elapsed = now - this.currentPhase.startedAt;
208
+ const elapsedSec = (elapsed / 1000).toFixed(1);
209
+ const frameIndex = Math.floor(elapsed / 80) % SPINNER_FRAMES.length;
210
+ const spinner = SPINNER_FRAMES[frameIndex];
211
+ lines.push(` ${BOLD}${spinner}${RESET} ${this.currentPhase.label} ${DIM}(${elapsedSec}s)${RESET}`);
212
+ for (const detailLine of splitDetailLines(this.currentPhase.detail)) {
213
+ lines.push(` ${DIM}${detailLine}${RESET}`);
214
+ }
215
+ }
216
+ let output = "";
217
+ if (this.prevLineCount > 0) {
218
+ output += `\x1b[${this.prevLineCount}A`;
219
+ }
220
+ for (const line of lines) {
221
+ output += `\x1b[2K${line}\n`;
222
+ }
223
+ // Clear any leftover lines from previous render that are no longer needed
224
+ if (lines.length < this.prevLineCount) {
225
+ for (let i = 0; i < this.prevLineCount - lines.length; i++) {
226
+ output += `\x1b[2K\n`;
227
+ }
228
+ }
229
+ this.prevLineCount = lines.length;
230
+ return output;
231
+ }
232
+ /**
233
+ * Finalize the progress display. Clears the current phase (if any) and
234
+ * writes the final checklist state. Idempotent.
235
+ */
236
+ end() {
237
+ if (this.ended) {
238
+ return;
239
+ }
240
+ this.ended = true;
241
+ if (this.currentPhase) {
242
+ this.currentPhase = null;
243
+ this.currentDetail = null;
244
+ }
245
+ this.stopAutoRender();
246
+ if (this.isTTY) {
247
+ this.flushRender();
248
+ }
249
+ }
250
+ ensureAutoRender() {
251
+ if (!this.autoRender || !this.isTTY || this.renderTimer !== null) {
252
+ return;
253
+ }
254
+ this.renderTimer = this.setTimer(() => this.flushRender(), this.renderIntervalMs);
255
+ }
256
+ stopAutoRender() {
257
+ if (this.renderTimer === null) {
258
+ return;
259
+ }
260
+ this.clearTimer(this.renderTimer);
261
+ this.renderTimer = null;
262
+ }
263
+ flushRender() {
264
+ const output = this.render(this.now());
265
+ if (output) {
266
+ this.write(output);
267
+ }
268
+ }
269
+ }
270
+ exports.UpProgress = UpProgress;
271
+ exports.CommandProgress = UpProgress;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decideDelegation = decideDelegation;
4
+ const runtime_1 = require("../nerves/runtime");
5
+ const CROSS_SESSION_TOOLS = new Set(["query_session", "send_message", "bridge_manage"]);
6
+ const FAST_PATH_TOOLS = new Set(["settle"]);
7
+ const REFLECTION_PATTERN = /\b(think|reflect|ponder|surface|surfaces|surfaced|sit with|metaboli[sz]e)\b/i;
8
+ const CROSS_SESSION_PATTERN = /\b(other chat|other session|across chats?|across sessions?|keep .* aligned|relay|carry .* across)\b/i;
9
+ function hasExplicitReflection(ingressTexts) {
10
+ return ingressTexts.some((text) => REFLECTION_PATTERN.test(text));
11
+ }
12
+ function hasCrossSessionPressure(ingressTexts, requestedToolNames) {
13
+ if (requestedToolNames.some((name) => CROSS_SESSION_TOOLS.has(name))) {
14
+ return true;
15
+ }
16
+ return ingressTexts.some((text) => CROSS_SESSION_PATTERN.test(text));
17
+ }
18
+ function hasNonFastPathToolRequest(requestedToolNames) {
19
+ return requestedToolNames.some((name) => !FAST_PATH_TOOLS.has(name));
20
+ }
21
+ function decideDelegation(input) {
22
+ const requestedToolNames = (input.requestedToolNames ?? [])
23
+ .map((name) => name.trim())
24
+ .filter((name) => name.length > 0);
25
+ const reasons = [];
26
+ if (hasExplicitReflection(input.ingressTexts)) {
27
+ reasons.push("explicit_reflection");
28
+ }
29
+ if (hasCrossSessionPressure(input.ingressTexts, requestedToolNames)) {
30
+ reasons.push("cross_session");
31
+ }
32
+ if (input.activeWork.centerOfGravity === "shared-work" || input.activeWork.bridges.some((bridge) => bridge.lifecycle === "active")) {
33
+ reasons.push("bridge_state");
34
+ }
35
+ if (input.activeWork.taskPressure.liveTaskNames.length > 0) {
36
+ reasons.push("task_state");
37
+ }
38
+ if (hasNonFastPathToolRequest(requestedToolNames)) {
39
+ reasons.push("non_fast_path_tool");
40
+ }
41
+ if (input.mustResolveBeforeHandoff || input.activeWork.mustResolveBeforeHandoff) {
42
+ reasons.push("unresolved_obligation");
43
+ }
44
+ const target = reasons.length === 0 ? "fast-path" : "delegate-inward";
45
+ const decision = {
46
+ target,
47
+ reasons,
48
+ outwardClosureRequired: target === "delegate-inward" && input.channel !== "inner",
49
+ };
50
+ (0, runtime_1.emitNervesEvent)({
51
+ component: "engine",
52
+ event: "engine.delegation_decide",
53
+ message: "computed delegation hint",
54
+ meta: {
55
+ channel: input.channel,
56
+ target: decision.target,
57
+ reasons: decision.reasons,
58
+ outwardClosureRequired: decision.outwardClosureRequired,
59
+ },
60
+ });
61
+ return decision;
62
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.migrateHabitsFromTaskSystem = migrateHabitsFromTaskSystem;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const runtime_1 = require("../../nerves/runtime");
40
+ const habit_parser_1 = require("./habit-parser");
41
+ const habit_runtime_state_1 = require("./habit-runtime-state");
42
+ const parser_1 = require("../../repertoire/tasks/parser");
43
+ /** Fields that belong to the task system and should be stripped from migrated habits. */
44
+ const TASK_ONLY_FIELDS = new Set([
45
+ "type",
46
+ "category",
47
+ "requester",
48
+ "validator",
49
+ "scheduledAt",
50
+ "updated",
51
+ "depends_on",
52
+ "parent_task",
53
+ "artifacts",
54
+ ]);
55
+ /** Regex matching the YYYY-MM-DD-HHMM- timestamp prefix in task filenames. */
56
+ const TIMESTAMP_PREFIX = /^\d{4}-\d{2}-\d{2}-\d{4}-/;
57
+ /** Map old task statuses to habit statuses. */
58
+ function mapStatus(taskStatus) {
59
+ if (taskStatus === "processing")
60
+ return "active";
61
+ if (taskStatus === "paused")
62
+ return "paused";
63
+ if (taskStatus === "done")
64
+ return null; // skip done habits
65
+ return "active"; // default to active for unknown statuses
66
+ }
67
+ /** Strip timestamp prefix from filename to get the slug name. */
68
+ function stripTimestampPrefix(filename) {
69
+ return filename.replace(TIMESTAMP_PREFIX, "");
70
+ }
71
+ /**
72
+ * Migrate habit files from the old `tasks/habits/` location to the new `habits/` bundle root.
73
+ * - Strips timestamp prefix from filenames
74
+ * - Maps task statuses to habit statuses
75
+ * - Strips task-only frontmatter fields
76
+ * - Preserves body text
77
+ * - Skips done habits, README files, and already-migrated habits
78
+ * - No-op if `tasks/habits/` does not exist
79
+ */
80
+ function migrateHabitsFromTaskSystem(bundleRoot) {
81
+ const oldHabitsDir = path.join(bundleRoot, "tasks", "habits");
82
+ if (!fs.existsSync(oldHabitsDir)) {
83
+ return;
84
+ }
85
+ let files;
86
+ try {
87
+ files = fs.readdirSync(oldHabitsDir);
88
+ }
89
+ catch {
90
+ /* v8 ignore next -- race condition: dir removed between existsSync and readdirSync @preserve */
91
+ return;
92
+ }
93
+ const mdFiles = files.filter((f) => f.endsWith(".md") && f !== "README.md");
94
+ if (mdFiles.length === 0)
95
+ return;
96
+ const newHabitsDir = path.join(bundleRoot, "habits");
97
+ fs.mkdirSync(newHabitsDir, { recursive: true });
98
+ let migratedCount = 0;
99
+ for (const file of mdFiles) {
100
+ const slugName = stripTimestampPrefix(file);
101
+ const targetPath = path.join(newHabitsDir, slugName);
102
+ // Skip if already migrated
103
+ if (fs.existsSync(targetPath)) {
104
+ (0, runtime_1.emitNervesEvent)({
105
+ component: "daemon",
106
+ event: "daemon.habit_migration_skip",
107
+ message: "habit already exists at target, skipping",
108
+ meta: { file, targetPath },
109
+ });
110
+ continue;
111
+ }
112
+ const sourcePath = path.join(oldHabitsDir, file);
113
+ let content;
114
+ try {
115
+ content = fs.readFileSync(sourcePath, "utf-8");
116
+ }
117
+ catch {
118
+ continue;
119
+ }
120
+ // Parse frontmatter and body
121
+ const lines = content.split(/\r?\n/);
122
+ if (lines[0]?.trim() !== "---")
123
+ continue;
124
+ const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
125
+ if (closing === -1)
126
+ continue;
127
+ const rawFrontmatter = lines.slice(1, closing).join("\n");
128
+ const body = lines.slice(closing + 1).join("\n").trim();
129
+ const frontmatter = (0, parser_1.parseFrontmatter)(rawFrontmatter);
130
+ // Check status — skip done habits
131
+ const rawStatus = typeof frontmatter.status === "string" ? frontmatter.status : "processing";
132
+ const habitStatus = mapStatus(rawStatus);
133
+ if (habitStatus === null) {
134
+ (0, runtime_1.emitNervesEvent)({
135
+ component: "daemon",
136
+ event: "daemon.habit_migration_skip",
137
+ message: "skipping done habit",
138
+ meta: { file, status: rawStatus },
139
+ });
140
+ continue;
141
+ }
142
+ const legacyLastRun = typeof frontmatter.lastRun === "string" && frontmatter.lastRun !== "null"
143
+ ? frontmatter.lastRun
144
+ : typeof frontmatter.last_run === "string" && frontmatter.last_run !== "null"
145
+ ? frontmatter.last_run
146
+ : null;
147
+ // Build new frontmatter, stripping task-only fields
148
+ const newFrontmatter = {};
149
+ if (typeof frontmatter.title === "string")
150
+ newFrontmatter.title = frontmatter.title;
151
+ if (typeof frontmatter.cadence === "string")
152
+ newFrontmatter.cadence = frontmatter.cadence;
153
+ newFrontmatter.status = habitStatus;
154
+ newFrontmatter.created = typeof frontmatter.created === "string" ? frontmatter.created : "null";
155
+ // Add any other non-task fields from original
156
+ for (const [key, value] of Object.entries(frontmatter)) {
157
+ if (TASK_ONLY_FIELDS.has(key))
158
+ continue;
159
+ if (key === "lastRun" || key === "last_run")
160
+ continue;
161
+ if (key in newFrontmatter)
162
+ continue;
163
+ /* v8 ignore next -- dead code: status is caught by `key in newFrontmatter` above since newFrontmatter.status is always set @preserve */
164
+ if (key === "status")
165
+ continue; // already mapped
166
+ newFrontmatter[key] = value;
167
+ }
168
+ const rendered = (0, habit_parser_1.renderHabitFile)(newFrontmatter, body);
169
+ fs.writeFileSync(targetPath, rendered, "utf-8");
170
+ if (legacyLastRun) {
171
+ (0, habit_runtime_state_1.writeHabitLastRun)(bundleRoot, path.basename(slugName, ".md"), legacyLastRun);
172
+ }
173
+ migratedCount++;
174
+ (0, runtime_1.emitNervesEvent)({
175
+ component: "daemon",
176
+ event: "daemon.habit_migrated",
177
+ message: "migrated habit from task system",
178
+ meta: { from: sourcePath, to: targetPath },
179
+ });
180
+ }
181
+ if (migratedCount > 0) {
182
+ (0, runtime_1.emitNervesEvent)({
183
+ component: "daemon",
184
+ event: "daemon.habit_migration_complete",
185
+ message: "habit migration complete",
186
+ meta: { bundleRoot, count: migratedCount },
187
+ });
188
+ }
189
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseHabitFile = parseHabitFile;
37
+ exports.renderHabitFile = renderHabitFile;
38
+ const path = __importStar(require("path"));
39
+ const parser_1 = require("../../repertoire/tasks/parser");
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ function isHabitStatus(value) {
42
+ return value === "active" || value === "paused";
43
+ }
44
+ function parseToolsField(raw) {
45
+ if (raw === undefined || raw === null)
46
+ return undefined;
47
+ // YAML dash-list: parseFrontmatter returns unknown[]
48
+ if (Array.isArray(raw)) {
49
+ return raw.filter((item) => typeof item === "string");
50
+ }
51
+ // Inline bracket format: parseFrontmatter returns string like "[a, b, c]"
52
+ if (typeof raw === "string" && raw.startsWith("[") && raw.endsWith("]")) {
53
+ const inner = raw.slice(1, -1);
54
+ if (inner.trim().length === 0)
55
+ return [];
56
+ return inner.split(",").map((s) => s.trim()).filter(Boolean);
57
+ }
58
+ return undefined;
59
+ }
60
+ function extractFrontmatterAndBody(content) {
61
+ const lines = content.split(/\r?\n/);
62
+ if (lines[0]?.trim() !== "---") {
63
+ return null;
64
+ }
65
+ const closing = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
66
+ if (closing === -1) {
67
+ return null;
68
+ }
69
+ const rawFrontmatter = lines.slice(1, closing).join("\n");
70
+ const body = lines.slice(closing + 1).join("\n").trim();
71
+ return { frontmatter: (0, parser_1.parseFrontmatter)(rawFrontmatter), body };
72
+ }
73
+ function parseHabitFile(content, filePath) {
74
+ (0, runtime_1.emitNervesEvent)({
75
+ event: "daemon.habit_parse",
76
+ component: "daemon",
77
+ message: "parsing habit file",
78
+ meta: { filePath },
79
+ });
80
+ const stem = path.basename(filePath, ".md");
81
+ const parsed = extractFrontmatterAndBody(content);
82
+ if (!parsed) {
83
+ return {
84
+ name: stem,
85
+ title: stem,
86
+ cadence: null,
87
+ status: "active",
88
+ lastRun: null,
89
+ created: null,
90
+ tools: undefined,
91
+ body: content.trim(),
92
+ };
93
+ }
94
+ const { frontmatter, body } = parsed;
95
+ const rawTitle = frontmatter.title;
96
+ const title = typeof rawTitle === "string" && rawTitle.length > 0 ? rawTitle : stem;
97
+ const rawCadence = frontmatter.cadence;
98
+ const cadence = typeof rawCadence === "string" && rawCadence.length > 0 ? rawCadence : null;
99
+ const rawStatus = frontmatter.status;
100
+ const status = typeof rawStatus === "string" && isHabitStatus(rawStatus) ? rawStatus : "active";
101
+ const rawLastRun = frontmatter.lastRun ?? frontmatter.last_run;
102
+ const lastRun = typeof rawLastRun === "string" && rawLastRun.length > 0 ? rawLastRun : null;
103
+ const rawCreated = frontmatter.created;
104
+ const created = typeof rawCreated === "string" && rawCreated.length > 0 ? rawCreated : null;
105
+ const tools = parseToolsField(frontmatter.tools);
106
+ return {
107
+ name: stem,
108
+ title,
109
+ cadence,
110
+ status,
111
+ lastRun,
112
+ created,
113
+ tools,
114
+ body,
115
+ };
116
+ }
117
+ function formatFrontmatterValue(value) {
118
+ if (value === null || value === undefined)
119
+ return "null";
120
+ if (Array.isArray(value))
121
+ return `[${value.join(", ")}]`;
122
+ return String(value);
123
+ }
124
+ function renderHabitFile(frontmatter, body) {
125
+ (0, runtime_1.emitNervesEvent)({
126
+ event: "daemon.habit_render",
127
+ component: "daemon",
128
+ message: "rendering habit file",
129
+ meta: {},
130
+ });
131
+ const lines = ["---"];
132
+ for (const key of Object.keys(frontmatter)) {
133
+ lines.push(`${key}: ${formatFrontmatterValue(frontmatter[key])}`);
134
+ }
135
+ lines.push("---");
136
+ lines.push("");
137
+ lines.push(body.trim());
138
+ lines.push("");
139
+ return lines.join("\n");
140
+ }