@visual-z/xeno 0.8.4 → 0.9.1

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 (350) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2071
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1818
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3108
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,154 +0,0 @@
1
- /** O.2: identity and lifecycle are explicit; only command evidence can verify completion. */
2
- import type { LogEvent } from "./events.ts";
3
- import type { Evidence } from "./handoff.ts";
4
- import { isCommandResult, resolveEvidence, evidenceStale } from "./evidence.ts";
5
-
6
- export type WorkItem = {
7
- id: string;
8
- what: string;
9
- status: "active" | "blocked" | "completed" | "cancelled";
10
- verification: "not_run" | "verified" | "unverified" | "stale";
11
- opened_at: string;
12
- updated_at: string;
13
- next_action?: string;
14
- reason?: string;
15
- evidence?: Evidence;
16
- /** Reopening keeps provenance without carrying a second copy of old proof. */
17
- previous_evidence?: string;
18
- depends_on?: readonly string[];
19
- owner?: string;
20
- owner_event?: string;
21
- };
22
-
23
- export type WorkReadiness = { ready: boolean; reason: "ready" | "waiting" | "blocked" | "closed" | "owned" | "missing"; waiting_for: string[] };
24
- export function workReadiness(items: readonly WorkItem[], id: string, actor?: string): WorkReadiness {
25
- const item = items.find(work => work.id === id);
26
- if (!item) return { ready: false, reason: "missing", waiting_for: [id] };
27
- if (item.status === "completed" || item.status === "cancelled") return { ready: false, reason: "closed", waiting_for: [] };
28
- const waiting = (item.depends_on ?? []).filter(dep => items.find(work => work.id === dep)?.status !== "completed");
29
- const reason = item.status === "blocked" ? "blocked" : waiting.length ? "waiting" : item.owner && item.owner !== actor ? "owned" : "ready";
30
- return { ready: reason === "ready", reason, waiting_for: [...waiting] };
31
- }
32
-
33
- function dependencyError(items: readonly WorkItem[], id: string, dependencies: readonly string[]): string | null {
34
- if (new Set(dependencies).size !== dependencies.length) return "duplicate work dependencies";
35
- if (dependencies.includes(id)) return "work dependency cycle: self dependency";
36
- if (dependencies.some(dep => !items.some(item => item.id === dep))) return "unknown work dependency";
37
- const seen = new Set<string>(), pending = [...dependencies];
38
- while (pending.length) {
39
- const dep = pending.pop()!;
40
- if (dep === id) return "work dependency cycle";
41
- if (seen.has(dep)) continue;
42
- seen.add(dep); pending.push(...items.find(item => item.id === dep)?.depends_on ?? []);
43
- }
44
- return null;
45
- }
46
-
47
- /** O.2: sibling sessions may choose the same local label without aliasing work. */
48
- export function scopedWorkId(namespace: string, requested: string, items: readonly WorkItem[]): string {
49
- return items.some(item => item.id === requested) || requested.startsWith(`${namespace}:`)
50
- ? requested : `${namespace}:${requested}`;
51
- }
52
-
53
- function unknownWork(id: string): string {
54
- return `unknown work_id ${id}; create it with a next note (kind=next) first`;
55
- }
56
-
57
- export function workChangeError(items: readonly WorkItem[], event: LogEvent): string | null {
58
- if (!("work_id" in event) || event.work_id === undefined) return null;
59
- const item = items.find(item => item.id === event.work_id);
60
- if ((event.t === "next_action" || event.t === "work_dependencies") && event.depends_on !== undefined) {
61
- const error = dependencyError(items, event.work_id, event.depends_on);
62
- if (error) return error;
63
- }
64
- // Every unknown-id rejection names the way to make the id exist, or the model retries the same call.
65
- if (event.t === "work_dependencies" && !item) return unknownWork(event.work_id);
66
- if (event.t === "work_owner") {
67
- if (!item) return unknownWork(event.work_id);
68
- if (event.operation === "release") return item.owner === event.owner ? null : "only the recorded owner can release work";
69
- const ready = workReadiness(items, item.id, event.owner);
70
- return ready.ready ? null : `work ${item.id} is ${ready.reason}; cannot claim it`;
71
- }
72
- if (event.t === "claim" && item && (item.depends_on ?? []).some(dep => items.find(work => work.id === dep)?.status !== "completed")) return "work dependencies are not completed";
73
- if (event.t === "work_status") {
74
- // D15 carries post-fork instructions, not their whole history. A harness-
75
- // authenticated transition may seed intent in the adopted branch, never proof.
76
- if (!item && !event.carried_from) return `unknown work_id ${event.work_id}; create it with a next note first`;
77
- if (!item) return event.operation !== "cancel" && !event.next_action?.trim()
78
- ? "reactivating work requires a current next_action" : null;
79
- if (event.operation === "unblock" && item.status !== "blocked") return `work ${item.id} is not blocked`;
80
- if (event.operation === "reopen" && item.status !== "completed" && item.status !== "cancelled") {
81
- return `work ${item.id} is not closed; update its next action or unblock it`;
82
- }
83
- if (event.operation !== "cancel" && !event.next_action?.trim()) return "reactivating work requires a current next_action";
84
- } else if (event.t === "claim" && !item) {
85
- return `unknown work_id ${event.work_id}; record a next note before doing and verifying the work`;
86
- } else if (item && (item.status === "completed" || item.status === "cancelled")) {
87
- return `work ${item.id} is ${item.status}; explicitly reopen it before updating it`;
88
- } else if (event.t === "claim" && item?.status === "blocked") {
89
- return `work ${item.id} is blocked; explicitly unblock it before completing it`;
90
- }
91
- return null;
92
- }
93
-
94
- export function deriveWorkItems(previous: readonly WorkItem[], events: readonly LogEvent[]): WorkItem[] {
95
- const items = new Map(previous.map(item => [item.id, { ...item }]));
96
- for (let index = 0; index < events.length; index++) {
97
- const event = events[index]!;
98
- if ((event.t !== "next_action" && event.t !== "blocked" && event.t !== "work_status" && event.t !== "claim" && event.t !== "work_dependencies" && event.t !== "work_owner") ||
99
- event.work_id === undefined || workChangeError([...items.values()], event)) continue;
100
- const old = items.get(event.work_id);
101
- const item: WorkItem = old ? { ...old, what: event.t === "work_owner" || event.t === "work_dependencies" ? old.what : event.what, updated_at: event.id } : {
102
- id: event.work_id, what: event.what, status: "active", verification: "not_run",
103
- opened_at: event.id, updated_at: event.id,
104
- };
105
- if (event.t === "next_action") { item.next_action = event.next_action; if (event.depends_on !== undefined) item.depends_on = [...event.depends_on]; }
106
- else if (event.t === "work_dependencies") item.depends_on = [...event.depends_on];
107
- else if (event.t === "work_owner") {
108
- if (event.operation === "claim") { item.owner = event.owner; item.owner_event = event.id; }
109
- else { delete item.owner; delete item.owner_event; }
110
- }
111
- else if (event.t === "blocked") {
112
- item.status = "blocked";
113
- item.reason = event.reason;
114
- } else if (event.t === "work_status") {
115
- if (event.operation === "cancel") {
116
- item.status = "cancelled";
117
- item.reason = event.reason ?? "cancelled";
118
- delete item.next_action;
119
- } else {
120
- item.status = "active";
121
- item.next_action = event.next_action!;
122
- delete item.reason;
123
- if (event.operation === "reopen") {
124
- item.opened_at = event.id;
125
- item.verification = "not_run";
126
- if (item.evidence) item.previous_evidence = item.evidence.event_id;
127
- delete item.evidence;
128
- }
129
- }
130
- } else if (event.t === "claim") {
131
- // Preserve D11's same-segment rule and require proof from this activation.
132
- const resolved = resolveEvidence(events.slice(0, index), event.evidence, item.opened_at);
133
- if (resolved.ok) {
134
- item.status = "completed";
135
- item.verification = "verified";
136
- item.evidence = resolved.evidence;
137
- delete item.reason;
138
- delete item.next_action;
139
- } else { item.verification = "unverified"; item.reason = resolved.reason; }
140
- }
141
- items.set(item.id, item);
142
- if (item.status === "completed" || item.status === "cancelled") { delete item.owner; delete item.owner_event; }
143
- }
144
- return [...items.values()].map(item => {
145
- if (!item.evidence) return item;
146
- const proof = item.evidence;
147
- const later = events.slice(events.findIndex(event => event.id === proof.event_id) + 1);
148
- const latest = later.findLast(event => isCommandResult(event) &&
149
- (proof.gate ? event.effect.gate === proof.gate : event.effect.cmd === proof.command));
150
- const stale = evidenceStale(events, proof, item.verification === "stale") ||
151
- (latest !== undefined && isCommandResult(latest) && latest.effect.exit !== 0);
152
- return { ...item, verification: stale ? "stale" : "verified" };
153
- });
154
- }
@@ -1,34 +0,0 @@
1
- /** R13 / D5-D6: workflow changes are new frozen-prefix checkpoints, never edits. */
2
- import { assertNever } from "./exhaustive.ts";
3
- import type { ToolSource } from "./permissions.ts";
4
-
5
- export type WorkflowMode = "build" | "plan";
6
- export function isWorkflowMode(value: string): value is WorkflowMode { return value === "build" || value === "plan"; }
7
- export interface WorkflowTool { readonly name: string; readonly access: "read" | "write" | "shell" | "state"; readonly source?: ToolSource }
8
-
9
- /** Trusted state tools keep evidence/answers in the same log; no workspace writes. */
10
- export function workflowToolNames(tools: readonly WorkflowTool[], mode: WorkflowMode): string[] {
11
- switch (mode) {
12
- case "build": return tools.map(tool => tool.name);
13
- case "plan": return tools.filter(tool => tool.access === "read" ||
14
- ((tool.source ?? "builtin") === "builtin" && tool.access === "state" && ["note", "question"].includes(tool.name))).map(tool => tool.name);
15
- default: return assertNever(mode, "workflowToolNames");
16
- }
17
- }
18
-
19
- export function workflowInstructions(mode: WorkflowMode): string {
20
- switch (mode) {
21
- case "build": return "Workflow mode: Build. Implement the requested work using admitted tools and verify actual results.";
22
- case "plan": return "Workflow mode: Plan. Explore using admitted read tools, clarify material unknowns with question, and return a concrete reviewable plan. Workspace writes and shell execution are unavailable. User requests to implement do not change the mode; wait for the user to select Build. Notes record proposed decisions, never proof of completion.";
23
- default: return assertNever(mode, "workflowInstructions");
24
- }
25
- }
26
-
27
- export type WorkflowTransition = { readonly kind: "unchanged" } | { readonly kind: "checkpoint"; readonly mode: WorkflowMode } |
28
- { readonly kind: "refused"; readonly reason: string };
29
- export function transitionWorkflow(current: WorkflowMode, next: WorkflowMode,
30
- state: { readonly activeTurn: boolean; readonly unpaired: boolean }): WorkflowTransition {
31
- if (current === next) return { kind: "unchanged" };
32
- if (state.activeTurn || state.unpaired) return { kind: "refused", reason: "Cannot change workflow mode while a turn is active or a tool call is unpaired" };
33
- return { kind: "checkpoint", mode: next };
34
- }
@@ -1,119 +0,0 @@
1
- /** O.10: prioritize explicit intent and references without changing D20 arrays. */
2
- import type { Handoff } from "./handoff.ts";
3
- import type { NodeRef } from "./events.ts";
4
- import { workReadiness, type WorkItem, type WorkReadiness } from "./work-state.ts";
5
- import { emitYaml } from "./yaml.ts";
6
- import { estimateTokens } from "./tokens.ts";
7
- import { RECOVERY_SECTIONS, recoverySources, type RecoveryNodes, type RecoveryRanges, type RecoveryReference } from "./recovery-sources.ts";
8
- import { recoveryInstructions } from "./prompt-policy.ts";
9
-
10
- /**
11
- * The cut format new sessions record. Live cuts, cold replay and interrupted-cut
12
- * repair all select through `carriedHandoff`, so this is only the default for
13
- * cuts not yet written; a recorded version is never reinterpreted.
14
- */
15
- export const CURRENT_WORKING_SET_VERSION = 3;
16
-
17
- export type WorkingSet = {
18
- version: 1 | 2 | 3; source: NodeRef;
19
- /** v3 omits `what`, `next_action` and `reason` when the carried `state.work_items` holds the same item. */
20
- work: (Pick<WorkItem, "id" | "status" | "next_action" | "reason" | "depends_on" | "owner"> & { what?: string; readiness?: WorkReadiness })[];
21
- references: ({ node?: number; event: string; kind: string; work?: string } & Partial<Omit<RecoveryReference, "node" | "event" | "kind" | "work">>)[];
22
- omitted: { work: number; references: number };
23
- recovery_sections?: RecoveryRanges;
24
- recovery: string;
25
- };
26
-
27
- export function workingSet(full: Handoff, budget: number): WorkingSet | null {
28
- if (!full.predecessor) return null;
29
- const items = (full.state.work_items ?? []).filter(item => item.status === "blocked" || item.status === "active")
30
- .toSorted((a, b) => Number(b.status === "blocked") - Number(a.status === "blocked") || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
31
- const active = new Set(items.map(item => item.id));
32
- const references: WorkingSet["references"] = [
33
- ...(full.evidence_links ?? []).filter(link => active.has(link.work) && !link.superseded_by)
34
- .map(link => ({ event: link.event, kind: "evidence", work: link.work })),
35
- ...full.decisions.filter(item => item.work_id && active.has(item.work_id) && item.source)
36
- .map(item => ({ node: item.source!.node, event: item.source!.event_id, kind: "decision", work: item.work_id! })),
37
- ...full.pitfalls.filter(item => item.work_id && active.has(item.work_id) && item.source)
38
- .map(item => ({ node: item.source!.node, event: item.source!.event_id, kind: "pitfall", work: item.work_id! })),
39
- ...(full.progress_evidence ?? []).toReversed().map(item => ({ node: item.source.node, event: item.source.event_id, kind: item.kind })),
40
- ];
41
- const view: WorkingSet = { version: 1, source: full.predecessor, work: [], references: [],
42
- omitted: { work: items.length, references: references.length }, recovery: "history(work_id=ID), history(event=ID), or history(node=source.node, section=...)" };
43
- const fits = () => budget === Infinity || estimateTokens.count([], emitYaml(view)) <= budget;
44
- if (!fits()) return null;
45
- for (const item of items) {
46
- const { id, what, status, next_action, reason, depends_on, owner } = item;
47
- view.work.push({ id, what, status, ...(next_action ? { next_action } : {}), ...(reason ? { reason } : {}),
48
- ...(depends_on ? { depends_on, readiness: workReadiness(full.state.work_items ?? [], id) } : {}), ...(owner ? { owner } : {}) });
49
- view.omitted.work--;
50
- if (!fits()) { view.work.pop(); view.omitted.work++; }
51
- }
52
- for (const reference of references) {
53
- view.references.push(reference); view.omitted.references--;
54
- if (!fits()) { view.references.pop(); view.omitted.references++; }
55
- }
56
- return view;
57
- }
58
-
59
- /** R9: a new projection version keeps the original v1 ordering byte-stable. */
60
- export function recoveryWorkingSet(full: Handoff, nodes: RecoveryNodes, budget: number, tools?: readonly string[]): WorkingSet | null {
61
- return sourceView(full, nodes, { version: 2, users: true,
62
- fits: view => estimateTokens.count([], emitYaml(view)) <= budget,
63
- recovery: tools === undefined ? "Read original user messages before continuing: history(node=source.node,recovery=users,offset=0). Other pages: failures, environment, next_actions; event=ID reads one original. Abandoned messages are cancelled; annotations are not user instructions. Observations are historical: recheck resource, host and current permissions before acting. history(work_id=ID) recovers work." : recoveryInstructions(tools) });
64
- }
65
-
66
- /**
67
- * v3 shares v2's source ordering. Two things differ, both decided by the caller
68
- * that owns the whole carried handoff: `fits` measures the view inside that
69
- * handoff rather than standalone, and `users: false` leaves user sources out
70
- * because the carried `user_turns` already hold every one verbatim.
71
- */
72
- export function recoveryWorkingSetV3(full: Handoff, nodes: RecoveryNodes, options: {
73
- users: boolean; recovery: string; fits: (view: WorkingSet) => boolean;
74
- }): WorkingSet | null {
75
- return sourceView(full, nodes, { version: 3, ...options });
76
- }
77
-
78
- function sourceView(full: Handoff, nodes: RecoveryNodes, options: {
79
- version: 2 | 3; users: boolean; recovery: string; fits: (view: WorkingSet) => boolean;
80
- }): WorkingSet | null {
81
- const legacy = workingSet(full, Infinity);
82
- if (!legacy) return null;
83
- const sources = recoverySources(nodes);
84
- const view: WorkingSet = { version: options.version, source: legacy.source, work: [], references: [],
85
- omitted: { work: legacy.work.length, references: legacy.references.length + RECOVERY_SECTIONS.reduce((sum, section) => sum + sources[section].length, 0) },
86
- recovery_sections: Object.fromEntries(RECOVERY_SECTIONS.map(section => [section, { total: sources[section].length, omitted: sources[section].length }])) as RecoveryRanges,
87
- recovery: options.recovery };
88
- const fits = () => options.fits(view);
89
- if (!fits()) return null;
90
- const queues = RECOVERY_SECTIONS.map(section => ({ section, items: [...sources[section]], at: 0 }));
91
- // The latest and first live user messages anchor correction and initial scope;
92
- // remaining user sources are newest first. Cancelled turns stay pageable.
93
- const live = sources.users.filter(ref => !ref.abandoned), first = live.at(-1);
94
- queues[0]!.items = !options.users ? [] : [...live.slice(0, 1), ...(first && first !== live[0] ? [first] : []),
95
- ...live.slice(1, -1), ...sources.users.filter(ref => ref.abandoned)];
96
- const takeReference = (reference: WorkingSet["references"][number], section?: keyof RecoveryRanges) => {
97
- view.references.push(reference); view.omitted.references--;
98
- if (section) view.recovery_sections![section].omitted--;
99
- if (!fits()) {
100
- view.references.pop(); view.omitted.references++;
101
- if (section) view.recovery_sections![section].omitted++;
102
- }
103
- };
104
- // One source per role precedes work prose; round-robin growth thereafter
105
- // prevents a long task list or repeated failures starving other categories.
106
- for (const queue of queues) { const reference = queue.items[queue.at++]; if (reference) takeReference(reference, queue.section); }
107
- let at = 0;
108
- while (at < legacy.work.length || at < legacy.references.length || queues.some(queue => queue.at < queue.items.length)) {
109
- const work = legacy.work[at];
110
- if (work) {
111
- view.work.push(work); view.omitted.work--;
112
- if (!fits()) { view.work.pop(); view.omitted.work++; }
113
- }
114
- for (const queue of queues) { const reference = queue.items[queue.at++]; if (reference) takeReference(reference, queue.section); }
115
- const reference = legacy.references[at++];
116
- if (reference) takeReference(reference);
117
- }
118
- return view;
119
- }
package/src/core/yaml.ts DELETED
@@ -1,282 +0,0 @@
1
- /**
2
- * Restricted, deterministic YAML emitter.
3
- *
4
- * Why hand-written instead of a dependency: YAML emitters differ in indentation,
5
- * quoting and line-wrapping defaults, so the usual advice is "pick one writer and
6
- * pin its version". A restricted emitter removes the version-drift risk entirely --
7
- * the format is fixed by construction, not by someone else's defaults.
8
- *
9
- * Guarantees:
10
- * - Block style only (the sole exception is `[]` / `{}` for empty collections).
11
- * - Indent 2, LF endings, exactly one trailing newline, no trailing whitespace.
12
- * - No anchors or aliases, so no topological-ordering hazard.
13
- * - Key order comes from an explicit `keyOrder` resolver; unlisted keys are
14
- * appended in sorted order so a new field can never silently vanish.
15
- * - Byte-identical output for equal input: emit(x) === emit(structuredClone(x)).
16
- *
17
- * Supported value types: string, finite number, boolean, null, array, plain object.
18
- * `undefined` values are omitted (that is how optional fields disappear); `null` is
19
- * emitted explicitly, so "absent" and "present but empty" stay distinguishable.
20
- */
21
-
22
- export type YamlValue =
23
- | string
24
- | number
25
- | boolean
26
- | null
27
- | undefined
28
- | readonly YamlValue[]
29
- | { readonly [key: string]: YamlValue };
30
-
31
- export interface EmitOptions {
32
- /**
33
- * Returns the preferred key order for the object at `path`, or undefined to
34
- * sort alphabetically. Keys returned but absent from the object are skipped;
35
- * keys present but unlisted are appended in sorted order.
36
- */
37
- keyOrder?: (path: readonly string[]) => readonly string[] | undefined;
38
- }
39
-
40
- const INDENT = " ";
41
-
42
- /**
43
- * Short all-scalar arrays render in flow style (`[1, 120]`). Nested block
44
- * sequences are legal but read badly -- a line range would otherwise print as
45
- * "- - 1" / " - 120" -- and this file is meant to be reviewed by a person.
46
- * The rule is a fixed predicate, so output stays deterministic.
47
- */
48
- const FLOW_MAX_WIDTH = 60;
49
-
50
- /** YAML 1.1-era tokens that a bare scalar must not collide with. */
51
- const RESERVED_PLAIN =
52
- /^(|~|null|Null|NULL|true|True|TRUE|false|False|FALSE|yes|Yes|YES|no|No|NO|on|On|ON|off|Off|OFF|y|Y|n|N)$/;
53
-
54
- const NUMBER_LIKE =
55
- /^[+-]?(\d[\d_]*\.?[\d_]*|\.[\d_]+)([eE][+-]?\d+)?$|^[+-]?0[xXoObB][0-9a-fA-F_]+$|^[+-]?\.(inf|Inf|INF)$|^\.(nan|NaN|NAN)$/;
56
-
57
- /** Leading characters that carry structural meaning in YAML. */
58
- const LEADING_INDICATOR = new Set([
59
- "-", "?", ":", ",", "[", "]", "{", "}", "#", "&", "*", "!",
60
- "|", ">", "'", '"', "%", "@", "`",
61
- ]);
62
-
63
- function hasControlChars(s: string): boolean {
64
- for (const ch of s) {
65
- const c = ch.codePointAt(0)!;
66
- // Allow \n (handled by the block-scalar path); reject every other control char.
67
- if ((c < 0x20 && c !== 0x0a) || c === 0x7f) return true;
68
- }
69
- return false;
70
- }
71
-
72
- function isPlainSafe(s: string): boolean {
73
- if (s.length === 0) return false;
74
- if (s !== s.trim()) return false;
75
- if (s.includes("\n") || hasControlChars(s)) return false;
76
- if (LEADING_INDICATOR.has(s[0]!)) return false;
77
- if (RESERVED_PLAIN.test(s) || NUMBER_LIKE.test(s)) return false;
78
- // ": " opens a mapping; " #" opens a comment; a trailing ":" also reads as a key.
79
- if (s.includes(": ") || s.includes(" #") || s.endsWith(":")) return false;
80
- return true;
81
- }
82
-
83
- function quoteDouble(s: string): string {
84
- let out = '"';
85
- for (const ch of s) {
86
- const c = ch.codePointAt(0)!;
87
- if (ch === '"') out += '\\"';
88
- else if (ch === "\\") out += "\\\\";
89
- else if (ch === "\n") out += "\\n";
90
- else if (ch === "\t") out += "\\t";
91
- else if (ch === "\r") out += "\\r";
92
- else if (c < 0x20 || c === 0x7f) out += "\\x" + c.toString(16).padStart(2, "0");
93
- else out += ch;
94
- }
95
- return out + '"';
96
- }
97
-
98
- /**
99
- * A multi-line string is only emitted as a block scalar when every line is clean.
100
- * Leading whitespace on the first line would need an explicit indentation
101
- * indicator, and trailing whitespace on any line would violate the
102
- * no-trailing-whitespace guarantee -- both fall back to a double-quoted scalar,
103
- * which is always correct if less pretty.
104
- */
105
- function canUseBlockScalar(s: string): boolean {
106
- if (!s.includes("\n")) return false;
107
- if (hasControlChars(s)) return false;
108
- // `|+` (keep) is the most ambiguous corner of the spec -- parsers disagree about
109
- // trailing blank lines at EOF. Two or more trailing newlines therefore fall back
110
- // to a quoted scalar rather than relying on that behaviour.
111
- if (/\n\n$/.test(s)) return false;
112
- const lines = s.split("\n");
113
- const firstNonEmpty = lines.find((l) => l.length > 0);
114
- if (firstNonEmpty !== undefined && /^\s/.test(firstNonEmpty)) return false;
115
- for (const line of lines) {
116
- if (line.length > 0 && line !== line.replace(/\s+$/, "")) return false;
117
- }
118
- return true;
119
- }
120
-
121
- function blockScalar(s: string, indent: string): string {
122
- // canUseBlockScalar() guarantees at most one trailing newline here.
123
- const trailing = /\n*$/.exec(s)![0].length;
124
- const header = trailing === 0 ? "|-" : "|";
125
- const body = trailing === 0 ? s : s.slice(0, -1);
126
- const inner = indent + INDENT;
127
- const lines = body.split("\n").map((l) => (l.length === 0 ? "" : inner + l));
128
- return header + "\n" + lines.join("\n");
129
- }
130
-
131
- function scalar(v: string | number | boolean | null, indent: string): string {
132
- if (v === null) return "null";
133
- if (typeof v === "boolean") return v ? "true" : "false";
134
- if (typeof v === "number") {
135
- if (!Number.isFinite(v)) {
136
- throw new TypeError(`yaml: non-finite number is not emittable: ${v}`);
137
- }
138
- // Number#toString is the shortest round-trippable form, hence deterministic.
139
- // String(-0) drops the sign, so spell it out.
140
- return Object.is(v, -0) ? "-0" : String(v);
141
- }
142
- if (canUseBlockScalar(v)) return blockScalar(v, indent);
143
- if (isPlainSafe(v)) return v;
144
- return quoteDouble(v);
145
- }
146
-
147
- function isPlainObject(v: unknown): v is { readonly [key: string]: YamlValue } {
148
- return typeof v === "object" && v !== null && !Array.isArray(v);
149
- }
150
-
151
- type Scalar = string | number | boolean | null;
152
-
153
- function isScalar(v: YamlValue): v is Scalar {
154
- return v === null || ["string", "number", "boolean"].includes(typeof v);
155
- }
156
-
157
- /**
158
- * Returns the flow rendering (`[a, b]`) when every element is a scalar that needs
159
- * no block form and the result fits FLOW_MAX_WIDTH; otherwise undefined, and the
160
- * caller falls back to block style.
161
- */
162
- function tryFlow(items: readonly YamlValue[]): string | undefined {
163
- if (items.length === 0) return undefined;
164
- if (!items.every(isScalar)) return undefined;
165
- const parts: string[] = [];
166
- for (const item of items as readonly Scalar[]) {
167
- if (typeof item === "string" && item.includes("\n")) return undefined;
168
- parts.push(scalar(item, ""));
169
- }
170
- const text = "[" + parts.join(", ") + "]";
171
- return text.length <= FLOW_MAX_WIDTH ? text : undefined;
172
- }
173
-
174
- function orderedKeys(
175
- obj: { readonly [key: string]: YamlValue },
176
- path: readonly string[],
177
- opts: EmitOptions,
178
- ): string[] {
179
- const present = Object.keys(obj).filter((k) => obj[k] !== undefined);
180
- const preferred = opts.keyOrder?.(path);
181
- if (!preferred) return present.sort();
182
- const set = new Set(present);
183
- const ordered = preferred.filter((k) => set.has(k));
184
- const rest = present.filter((k) => !preferred.includes(k)).sort();
185
- return [...ordered, ...rest];
186
- }
187
-
188
- function emitKey(k: string): string {
189
- return isPlainSafe(k) ? k : quoteDouble(k);
190
- }
191
-
192
- function emitNode(
193
- v: YamlValue,
194
- indent: string,
195
- path: readonly string[],
196
- opts: EmitOptions,
197
- lines: string[],
198
- ): void {
199
- if (Array.isArray(v)) {
200
- const items = v.filter((x) => x !== undefined);
201
- if (items.length === 0) {
202
- lines[lines.length - 1] += " []";
203
- return;
204
- }
205
- for (const item of items) {
206
- if (Array.isArray(item)) {
207
- const flow = tryFlow(item);
208
- if (flow !== undefined) {
209
- lines.push(indent + "- " + flow);
210
- continue;
211
- }
212
- }
213
- if (Array.isArray(item) || isPlainObject(item)) {
214
- const before = lines.length;
215
- lines.push(indent + "-");
216
- emitNode(item, indent + INDENT, path, opts, lines);
217
- // Hoist the first child onto the dash line: "- key: value".
218
- const dashLine = lines[before]!;
219
- const firstChild = lines[before + 1];
220
- if (firstChild !== undefined && dashLine === indent + "-") {
221
- lines.splice(before, 2, indent + "- " + firstChild.slice(indent.length + INDENT.length));
222
- }
223
- } else {
224
- lines.push(indent + "- " + scalar(item as string | number | boolean | null, indent));
225
- }
226
- }
227
- return;
228
- }
229
-
230
- if (isPlainObject(v)) {
231
- const keys = orderedKeys(v, path, opts);
232
- if (keys.length === 0) {
233
- lines[lines.length - 1] += " {}";
234
- return;
235
- }
236
- for (const k of keys) {
237
- const child = v[k]!;
238
- const childPath = [...path, k];
239
- if (Array.isArray(child)) {
240
- const items = child.filter((x) => x !== undefined);
241
- const flow = tryFlow(items);
242
- if (items.length === 0) {
243
- lines.push(indent + emitKey(k) + ": []");
244
- } else if (flow !== undefined) {
245
- lines.push(indent + emitKey(k) + ": " + flow);
246
- } else {
247
- lines.push(indent + emitKey(k) + ":");
248
- // Indent sequences under their key; valid either way, but readable.
249
- emitNode(child, indent + INDENT, childPath, opts, lines);
250
- }
251
- } else if (isPlainObject(child)) {
252
- if (orderedKeys(child, childPath, opts).length === 0) {
253
- lines.push(indent + emitKey(k) + ": {}");
254
- } else {
255
- lines.push(indent + emitKey(k) + ":");
256
- emitNode(child, indent + INDENT, childPath, opts, lines);
257
- }
258
- } else {
259
- lines.push(
260
- indent + emitKey(k) + ": " +
261
- scalar(child as string | number | boolean | null, indent),
262
- );
263
- }
264
- }
265
- return;
266
- }
267
-
268
- lines.push(indent + scalar(v as string | number | boolean | null, indent));
269
- }
270
-
271
- /** Emit `value` as deterministic block-style YAML with a single trailing newline. */
272
- export function emitYaml(value: YamlValue, opts: EmitOptions = {}): string {
273
- if (value === undefined) return "";
274
- const lines: string[] = [];
275
- if (Array.isArray(value) || isPlainObject(value)) {
276
- emitNode(value, "", [], opts, lines);
277
- } else {
278
- lines.push(scalar(value as string | number | boolean | null, ""));
279
- }
280
- if (lines.length === 0) return "{}\n";
281
- return lines.join("\n") + "\n";
282
- }