otacon 0.1.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/dist/cli/client.js +188 -0
  4. package/dist/cli/client.js.map +1 -0
  5. package/dist/cli/commands/answer.js +63 -0
  6. package/dist/cli/commands/answer.js.map +1 -0
  7. package/dist/cli/commands/ask.js +117 -0
  8. package/dist/cli/commands/ask.js.map +1 -0
  9. package/dist/cli/commands/clean.js +48 -0
  10. package/dist/cli/commands/clean.js.map +1 -0
  11. package/dist/cli/commands/doctor.js +86 -0
  12. package/dist/cli/commands/doctor.js.map +1 -0
  13. package/dist/cli/commands/expose.js +104 -0
  14. package/dist/cli/commands/expose.js.map +1 -0
  15. package/dist/cli/commands/implement-done.js +53 -0
  16. package/dist/cli/commands/implement-done.js.map +1 -0
  17. package/dist/cli/commands/install.js +113 -0
  18. package/dist/cli/commands/install.js.map +1 -0
  19. package/dist/cli/commands/open.js +37 -0
  20. package/dist/cli/commands/open.js.map +1 -0
  21. package/dist/cli/commands/progress.js +45 -0
  22. package/dist/cli/commands/progress.js.map +1 -0
  23. package/dist/cli/commands/start.js +66 -0
  24. package/dist/cli/commands/start.js.map +1 -0
  25. package/dist/cli/commands/status.js +44 -0
  26. package/dist/cli/commands/status.js.map +1 -0
  27. package/dist/cli/commands/submit.js +64 -0
  28. package/dist/cli/commands/submit.js.map +1 -0
  29. package/dist/cli/commands/wait.js +66 -0
  30. package/dist/cli/commands/wait.js.map +1 -0
  31. package/dist/cli/install/assets.js +285 -0
  32. package/dist/cli/install/assets.js.map +1 -0
  33. package/dist/cli/install/locations.js +92 -0
  34. package/dist/cli/install/locations.js.map +1 -0
  35. package/dist/cli/install/tailscale.js +39 -0
  36. package/dist/cli/install/tailscale.js.map +1 -0
  37. package/dist/cli/main.js +73 -0
  38. package/dist/cli/main.js.map +1 -0
  39. package/dist/cli/output.js +39 -0
  40. package/dist/cli/output.js.map +1 -0
  41. package/dist/cli/session.js +77 -0
  42. package/dist/cli/session.js.map +1 -0
  43. package/dist/daemon/activity.js +56 -0
  44. package/dist/daemon/activity.js.map +1 -0
  45. package/dist/daemon/anchor.js +143 -0
  46. package/dist/daemon/anchor.js.map +1 -0
  47. package/dist/daemon/app.js +1081 -0
  48. package/dist/daemon/app.js.map +1 -0
  49. package/dist/daemon/approve.js +71 -0
  50. package/dist/daemon/approve.js.map +1 -0
  51. package/dist/daemon/desktop-notify.js +69 -0
  52. package/dist/daemon/desktop-notify.js.map +1 -0
  53. package/dist/daemon/diff.js +187 -0
  54. package/dist/daemon/diff.js.map +1 -0
  55. package/dist/daemon/linter/index.js +19 -0
  56. package/dist/daemon/linter/index.js.map +1 -0
  57. package/dist/daemon/linter/parse.js +350 -0
  58. package/dist/daemon/linter/parse.js.map +1 -0
  59. package/dist/daemon/linter/rules.js +359 -0
  60. package/dist/daemon/linter/rules.js.map +1 -0
  61. package/dist/daemon/main.js +48 -0
  62. package/dist/daemon/main.js.map +1 -0
  63. package/dist/daemon/notify.js +23 -0
  64. package/dist/daemon/notify.js.map +1 -0
  65. package/dist/daemon/presence.js +37 -0
  66. package/dist/daemon/presence.js.map +1 -0
  67. package/dist/daemon/queue.js +160 -0
  68. package/dist/daemon/queue.js.map +1 -0
  69. package/dist/daemon/store.js +393 -0
  70. package/dist/daemon/store.js.map +1 -0
  71. package/dist/daemon/threads.js +153 -0
  72. package/dist/daemon/threads.js.map +1 -0
  73. package/dist/daemon/transcript.js +89 -0
  74. package/dist/daemon/transcript.js.map +1 -0
  75. package/dist/daemon/ui.js +175 -0
  76. package/dist/daemon/ui.js.map +1 -0
  77. package/dist/shared/config.js +93 -0
  78. package/dist/shared/config.js.map +1 -0
  79. package/dist/shared/gwt.js +69 -0
  80. package/dist/shared/gwt.js.map +1 -0
  81. package/dist/shared/paths.js +67 -0
  82. package/dist/shared/paths.js.map +1 -0
  83. package/dist/shared/question-spec.js +44 -0
  84. package/dist/shared/question-spec.js.map +1 -0
  85. package/dist/shared/types.js +35 -0
  86. package/dist/shared/types.js.map +1 -0
  87. package/dist/shared/version.js +5 -0
  88. package/dist/shared/version.js.map +1 -0
  89. package/dist/ui/assets/arc-HhPfdCPZ.js +1 -0
  90. package/dist/ui/assets/architecture-7EHR7CIX-BPLblcyi.js +1 -0
  91. package/dist/ui/assets/architectureDiagram-3BPJPVTR-D2PIxGOb.js +36 -0
  92. package/dist/ui/assets/array-BifhSqXX.js +1 -0
  93. package/dist/ui/assets/blockDiagram-GPEHLZMM-DQ3Dn17h.js +132 -0
  94. package/dist/ui/assets/c4Diagram-AAUBKEIU-DxITrQgS.js +10 -0
  95. package/dist/ui/assets/channel-ipcU8ZNI.js +1 -0
  96. package/dist/ui/assets/chunk-2J33WTMH-Du1JoPx5.js +1 -0
  97. package/dist/ui/assets/chunk-3OPIFGDE-Dn7x2Yqf.js +62 -0
  98. package/dist/ui/assets/chunk-4BX2VUAB-DVnrE-4n.js +1 -0
  99. package/dist/ui/assets/chunk-55IACEB6-BAhFAimA.js +1 -0
  100. package/dist/ui/assets/chunk-5ZQYHXKU-0hEZptem.js +2 -0
  101. package/dist/ui/assets/chunk-727SXJPM-C1FN_cI3.js +206 -0
  102. package/dist/ui/assets/chunk-AQP2D5EJ-A656OBd4.js +231 -0
  103. package/dist/ui/assets/chunk-BSJP7CBP-D8oMbjm8.js +1 -0
  104. package/dist/ui/assets/chunk-CSCIHK7Q-DjIL8GLi.js +122 -0
  105. package/dist/ui/assets/chunk-FMBD7UC4-Otblfqvz.js +15 -0
  106. package/dist/ui/assets/chunk-KSCS5N6A-BOjTvm3H.js +10 -0
  107. package/dist/ui/assets/chunk-L5ZTLDWV-CaTLaw6L.js +1 -0
  108. package/dist/ui/assets/chunk-LZXEDZCA-Dq5p7qrD.js +2 -0
  109. package/dist/ui/assets/chunk-ND2GUHAM-jZ_NNnWi.js +1 -0
  110. package/dist/ui/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
  111. package/dist/ui/assets/chunk-NZK2D7GU-U_7l_sCh.js +1 -0
  112. package/dist/ui/assets/chunk-O5CBEL6O-MewqqNB7.js +70 -0
  113. package/dist/ui/assets/chunk-QZHKN3VN-DzGPH44B.js +1 -0
  114. package/dist/ui/assets/chunk-WU5MYG2G-DyEIVjoo.js +1 -0
  115. package/dist/ui/assets/chunk-XPW4576I-D5ArxNEF.js +32 -0
  116. package/dist/ui/assets/classDiagram-4FO5ZUOK-Byg2Hl9D.js +1 -0
  117. package/dist/ui/assets/classDiagram-v2-Q7XG4LA2-Byg2Hl9D.js +1 -0
  118. package/dist/ui/assets/cose-bilkent-S5V4N54A-PFXzf7WV.js +1 -0
  119. package/dist/ui/assets/cytoscape.esm-h6BdjjI9.js +321 -0
  120. package/dist/ui/assets/dagre-BM42HDAG-xrCfjZuZ.js +4 -0
  121. package/dist/ui/assets/dagre-Bx709z4p.js +1 -0
  122. package/dist/ui/assets/defaultLocale-C8Fc0cco.js +1 -0
  123. package/dist/ui/assets/diagram-2AECGRRQ-BFf-cyKY.js +43 -0
  124. package/dist/ui/assets/diagram-5GNKFQAL-kNPV4NfV.js +10 -0
  125. package/dist/ui/assets/diagram-KO2AKTUF-ByC1IUwG.js +3 -0
  126. package/dist/ui/assets/diagram-LMA3HP47-DZIJMPK0.js +24 -0
  127. package/dist/ui/assets/diagram-OG6HWLK6-CSDED9A-.js +24 -0
  128. package/dist/ui/assets/dist-YwjsDswi.js +1 -0
  129. package/dist/ui/assets/erDiagram-TEJ5UH35-yuzvjE6J.js +85 -0
  130. package/dist/ui/assets/eventmodeling-FCH6USID-CZR4eNG-.js +1 -0
  131. package/dist/ui/assets/flowDiagram-I6XJVG4X-ApPtVyYM.js +162 -0
  132. package/dist/ui/assets/ganttDiagram-6RSMTGT7-BeMLXtAr.js +292 -0
  133. package/dist/ui/assets/gitGraph-WXDBUCRP-JmTTBa7j.js +1 -0
  134. package/dist/ui/assets/gitGraphDiagram-PVQCEYII-Cjjnjs71.js +106 -0
  135. package/dist/ui/assets/graphlib-B8gBHxth.js +1 -0
  136. package/dist/ui/assets/index-BFQVRcSI.js +11 -0
  137. package/dist/ui/assets/index-Bj_kTrwP.css +1 -0
  138. package/dist/ui/assets/info-J43DQDTF-8vZ3gome.js +1 -0
  139. package/dist/ui/assets/infoDiagram-5YYISTIA-CnMk1cA-.js +2 -0
  140. package/dist/ui/assets/init-D6jRqBbL.js +1 -0
  141. package/dist/ui/assets/ishikawaDiagram-YF4QCWOH-Bl8z6huD.js +70 -0
  142. package/dist/ui/assets/journeyDiagram-JHISSGLW-DYIVfMpS.js +139 -0
  143. package/dist/ui/assets/kanban-definition-UN3LZRKU-BnR0ZzOz.js +89 -0
  144. package/dist/ui/assets/katex-Vhh-h91d.js +257 -0
  145. package/dist/ui/assets/line-DcBdQit6.js +1 -0
  146. package/dist/ui/assets/linear-HKjRHFAO.js +1 -0
  147. package/dist/ui/assets/mermaid-parser.core-DkYXrPlA.js +4 -0
  148. package/dist/ui/assets/mermaid.core-BmkfCI3b.js +9 -0
  149. package/dist/ui/assets/mindmap-definition-RKZ34NQL-sIAd4nDi.js +96 -0
  150. package/dist/ui/assets/ordinal-hYBb2elL.js +1 -0
  151. package/dist/ui/assets/otacon-DPXGiaVj.svg +11 -0
  152. package/dist/ui/assets/packet-YPE3B663-BxbxcfXN.js +1 -0
  153. package/dist/ui/assets/path-BWPyau1x.js +1 -0
  154. package/dist/ui/assets/pie-LRSECV5Y-BJxazjNs.js +1 -0
  155. package/dist/ui/assets/pieDiagram-4H26LBE5-BiOhc9GR.js +30 -0
  156. package/dist/ui/assets/plan-view-CH6NzUDb.js +74 -0
  157. package/dist/ui/assets/purify.es-CDvCXckx.js +3 -0
  158. package/dist/ui/assets/quadrantDiagram-W4KKPZXB-CVyHbWgo.js +7 -0
  159. package/dist/ui/assets/radar-GUYGQ44K-D9ohbnbV.js +1 -0
  160. package/dist/ui/assets/requirementDiagram-4Y6WPE33-Ba24_hqc.js +84 -0
  161. package/dist/ui/assets/rough.esm-CSKSodPl.js +1 -0
  162. package/dist/ui/assets/sankeyDiagram-5OEKKPKP-CxD4wiPL.js +40 -0
  163. package/dist/ui/assets/sequenceDiagram-3UESZ5HK-7qA7lD61.js +162 -0
  164. package/dist/ui/assets/src-IM8AE8MK.js +1 -0
  165. package/dist/ui/assets/stateDiagram-AJRCARHV-DNElRCuH.js +1 -0
  166. package/dist/ui/assets/stateDiagram-v2-BHNVJYJU-D6qTYpY3.js +1 -0
  167. package/dist/ui/assets/timeline-definition-PNZ67QCA-ChYC4Grd.js +120 -0
  168. package/dist/ui/assets/treeView-BLDUP644-Il0KnMi_.js +1 -0
  169. package/dist/ui/assets/treemap-LRROVOQU-CIiKcdRo.js +1 -0
  170. package/dist/ui/assets/vennDiagram-CIIHVFJN-Ulhkum9i.js +34 -0
  171. package/dist/ui/assets/wardley-L42UT6IY-BNd4ljz7.js +1 -0
  172. package/dist/ui/assets/wardleyDiagram-YWT4CUSO-BicXxh84.js +78 -0
  173. package/dist/ui/assets/xychartDiagram-2RQKCTM6-Duf-m_th.js +7 -0
  174. package/dist/ui/index.html +20 -0
  175. package/package.json +66 -0
@@ -0,0 +1,153 @@
1
+ // Per-session thread persistence: .otacon/<id>/threads.json holds every
2
+ // comment and question thread so the review UI's rail can render the whole
3
+ // conversation on any load (DESIGN.md §9, §12) — the event queue drains, so
4
+ // it cannot be the rail's source. Same storage posture as the rest of the
5
+ // daemon: atomic writes, corrupt files quarantined and rebuilt empty, never
6
+ // fatal (DECISIONS.md "Threads: one threads.json per session").
7
+ import { existsSync } from "node:fs";
8
+ import { relocateAnchor } from "./anchor.js";
9
+ import { segmentPlan } from "./diff.js";
10
+ import { quarantineCorruptFile, readJsonOr, stringify, writeFileAtomic } from "./store.js";
11
+ function isAnchor(raw) {
12
+ if (raw === null)
13
+ return true;
14
+ const anchor = raw;
15
+ return typeof anchor === "object" && typeof anchor.section === "string";
16
+ }
17
+ function isThread(raw) {
18
+ const thread = raw;
19
+ if (typeof thread !== "object" || thread === null)
20
+ return false;
21
+ if (typeof thread.id !== "string" || typeof thread.body !== "string")
22
+ return false;
23
+ if (typeof thread.createdAt !== "string" || !isAnchor(thread.anchor))
24
+ return false;
25
+ if (thread.anchorState !== undefined && thread.anchorState !== "orphaned")
26
+ return false;
27
+ if (thread.kind === "comment") {
28
+ if (typeof thread.batch !== "string")
29
+ return false;
30
+ const { resolution } = thread;
31
+ if (resolution === undefined)
32
+ return true;
33
+ return (typeof resolution === "object" &&
34
+ resolution !== null &&
35
+ typeof resolution.body === "string" &&
36
+ typeof resolution.revision === "number" &&
37
+ typeof resolution.resolvedAt === "string");
38
+ }
39
+ if (thread.kind === "question") {
40
+ if (thread.replyTo !== undefined && typeof thread.replyTo !== "string")
41
+ return false;
42
+ const { answer } = thread;
43
+ if (answer === undefined)
44
+ return true;
45
+ return (typeof answer === "object" &&
46
+ answer !== null &&
47
+ typeof answer.body === "string" &&
48
+ typeof answer.answeredAt === "string");
49
+ }
50
+ return false;
51
+ }
52
+ // Every element is validated, not just the envelope: a JSON-valid file with a
53
+ // corrupt element would otherwise flow a non-Thread into answerQuestion (500)
54
+ // and the rail (render crash) — exactly the "never fatal" failures quarantine
55
+ // exists to absorb.
56
+ function parseThreads(raw) {
57
+ const file = raw;
58
+ const valid = typeof file === "object" &&
59
+ file !== null &&
60
+ file.version === 1 &&
61
+ Array.isArray(file.threads) &&
62
+ file.threads.every(isThread);
63
+ return valid ? file : undefined;
64
+ }
65
+ /** All threads, oldest first. Missing file = no threads yet; corrupt = quarantined, []. */
66
+ export function readThreads(path) {
67
+ if (!existsSync(path))
68
+ return [];
69
+ const file = parseThreads(readJsonOr(path));
70
+ if (!file) {
71
+ quarantineCorruptFile(path, "threads file");
72
+ return [];
73
+ }
74
+ return file.threads;
75
+ }
76
+ /** Durably append new threads (a comment batch's items, or one question). */
77
+ export function appendThreads(path, threads) {
78
+ const file = { version: 1, threads: [...readThreads(path), ...threads] };
79
+ writeFileAtomic(path, stringify(file));
80
+ }
81
+ /**
82
+ * Record the agent's answer on a question thread; returns the updated thread,
83
+ * or undefined when no question with that id exists (comment ids included —
84
+ * comments are resolved via resubmit, never answered). Re-answering
85
+ * overwrites: at-least-once delivery means the agent may legitimately answer
86
+ * the same question twice, and the newer text is the better one.
87
+ */
88
+ export function answerQuestion(path, id, body) {
89
+ const threads = readThreads(path);
90
+ const thread = threads.find((t) => t.id === id && t.kind === "question");
91
+ if (!thread)
92
+ return undefined;
93
+ const answer = { body, answeredAt: new Date().toISOString() };
94
+ thread.answer = answer;
95
+ writeFileAtomic(path, stringify({ version: 1, threads }));
96
+ return { ...thread, answer };
97
+ }
98
+ /**
99
+ * Apply an accepted revision to the threads file, in one read + one atomic
100
+ * write: record the resolution replies on their comment threads (lint L5 has
101
+ * already vouched for them; re-resolving overwrites — at-least-once), then
102
+ * re-locate every thread's anchor in the new plan (DESIGN.md §4) — lost or
103
+ * ambiguous quotes turn anchorState "orphaned", re-found ones recover.
104
+ * Returns the threads that changed, for the daemon's SSE `thread` upserts.
105
+ */
106
+ export function applyRevisionToThreads(path, opts) {
107
+ const threads = readThreads(path);
108
+ if (threads.length === 0)
109
+ return [];
110
+ const changed = new Map();
111
+ const resolvedAt = new Date().toISOString();
112
+ // Segment the new plan once for the whole pass (lazily — a rail of
113
+ // whole-plan threads never needs it), not per anchored thread.
114
+ let units;
115
+ for (const thread of threads) {
116
+ if (thread.kind === "comment" && opts.replies[thread.id] !== undefined) {
117
+ thread.resolution = {
118
+ body: opts.replies[thread.id],
119
+ revision: opts.revision,
120
+ resolvedAt,
121
+ };
122
+ changed.set(thread.id, thread);
123
+ }
124
+ if (thread.anchor === null)
125
+ continue; // whole-plan threads have no place to lose
126
+ const result = relocateAnchor(thread.anchor, opts.plan, (units ??= segmentPlan(opts.plan)));
127
+ if (result.state === "orphaned") {
128
+ if (thread.anchorState !== "orphaned") {
129
+ thread.anchorState = "orphaned";
130
+ changed.set(thread.id, thread);
131
+ }
132
+ }
133
+ else {
134
+ const moved = JSON.stringify(result.anchor) !== JSON.stringify(thread.anchor);
135
+ if (thread.anchorState === "orphaned" || moved) {
136
+ delete thread.anchorState;
137
+ thread.anchor = result.anchor;
138
+ changed.set(thread.id, thread);
139
+ }
140
+ }
141
+ }
142
+ if (changed.size > 0) {
143
+ writeFileAtomic(path, stringify({ version: 1, threads }));
144
+ }
145
+ return [...changed.values()];
146
+ }
147
+ /** Comment threads with their resolved state — the daemon's L5 context input. */
148
+ export function commentThreadStates(path) {
149
+ return readThreads(path)
150
+ .filter((t) => t.kind === "comment")
151
+ .map((t) => ({ id: t.id, resolved: t.resolution !== undefined }));
152
+ }
153
+ //# sourceMappingURL=threads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threads.js","sourceRoot":"","sources":["../../src/daemon/threads.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,0EAA0E;AAC1E,4EAA4E;AAC5E,gEAAgE;AAEhE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAM3F,SAAS,QAAQ,CAAC,GAAY;IAC5B,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,MAAM,GAAG,GAAa,CAAC;IAC7B,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC;AAC1E,CAAC;AAED,SAAS,QAAQ,CAAC,GAAY;IAC5B,MAAM,MAAM,GAAG,GAAa,CAAC;IAC7B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAChE,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACnF,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACnF,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACxF,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACnD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;QAC9B,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO,CACL,OAAO,UAAU,KAAK,QAAQ;YAC9B,UAAU,KAAK,IAAI;YACnB,OAAO,UAAU,CAAC,IAAI,KAAK,QAAQ;YACnC,OAAO,UAAU,CAAC,QAAQ,KAAK,QAAQ;YACvC,OAAO,UAAU,CAAC,UAAU,KAAK,QAAQ,CAC1C,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACrF,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;QAC1B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACtC,OAAO,CACL,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACf,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CACtC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,8EAA8E;AAC9E,oBAAoB;AACpB,SAAS,YAAY,CAAC,GAAY;IAChC,MAAM,IAAI,GAAG,GAAkB,CAAC;IAChC,MAAM,KAAK,GACT,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,IAAI,CAAC,OAAO,KAAK,CAAC;QAClB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAClC,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,qBAAqB,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QAC5C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC;AACtB,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAiB;IAC3D,MAAM,IAAI,GAAgB,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAC;IACtF,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,EAAU,EAAE,IAAY;IACnE,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CACzB,CAAC,CAAC,EAAuB,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,CACjE,CAAC;IACF,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAC9D,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAwB,CAAC,CAAC,CAAC;IAChF,OAAO,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAY,EACZ,IAAyE;IAEzE,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,mEAAmE;IACnE,+DAA+D;IAC/D,IAAI,KAA6B,CAAC;IAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,SAAS,EAAE,CAAC;YACvE,MAAM,CAAC,UAAU,GAAG;gBAClB,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAW;gBACvC,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU;aACX,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;YAAE,SAAS,CAAC,2CAA2C;QACjF,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5F,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAChC,IAAI,MAAM,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;gBACtC,MAAM,CAAC,WAAW,GAAG,UAAU,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC9E,IAAI,MAAM,CAAC,WAAW,KAAK,UAAU,IAAI,KAAK,EAAE,CAAC;gBAC/C,OAAO,MAAM,CAAC,WAAW,CAAC;gBAC1B,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACrB,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAwB,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,WAAW,CAAC,IAAI,CAAC;SACrB,MAAM,CAAC,CAAC,CAAC,EAAsB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC;SACvD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,89 @@
1
+ // Per-session grill transcript: .otacon/<id>/transcript.json holds every
2
+ // agent question and the user's answer (DESIGN.md §8) — distinct from
3
+ // user-question threads (threads.json), because the Interview panel and the
4
+ // threads rail are different surfaces with different lifecycles, and the
5
+ // transcript ships with the approved artifact while threads stay review
6
+ // exhaust. Same storage posture as the rest of the daemon: atomic writes,
7
+ // corrupt files quarantined and rebuilt empty, never fatal.
8
+ import { existsSync } from "node:fs";
9
+ import { quarantineCorruptFile, readJsonOr, stringify, writeFileAtomic } from "./store.js";
10
+ function isAnswer(raw) {
11
+ const answer = raw;
12
+ if (typeof answer !== "object" || answer === null)
13
+ return false;
14
+ if (typeof answer.answeredAt !== "string")
15
+ return false;
16
+ if (answer.choice !== undefined && typeof answer.choice !== "string")
17
+ return false;
18
+ if (answer.choices !== undefined &&
19
+ !(Array.isArray(answer.choices) && answer.choices.every((c) => typeof c === "string"))) {
20
+ return false;
21
+ }
22
+ return answer.text === undefined || typeof answer.text === "string";
23
+ }
24
+ // Every entry is validated, not just the envelope (same argument as
25
+ // threads.ts): a JSON-valid file with a corrupt entry would otherwise flow a
26
+ // non-entry into the answers endpoint (500) and the Interview panel.
27
+ function isEntry(raw) {
28
+ const entry = raw;
29
+ if (typeof entry !== "object" || entry === null)
30
+ return false;
31
+ if (typeof entry.id !== "string" || typeof entry.question !== "string")
32
+ return false;
33
+ if (typeof entry.askedAt !== "string")
34
+ return false;
35
+ if (entry.options !== undefined &&
36
+ !(Array.isArray(entry.options) && entry.options.every((o) => typeof o === "string"))) {
37
+ return false;
38
+ }
39
+ if (entry.recommend !== undefined && typeof entry.recommend !== "string")
40
+ return false;
41
+ if (entry.multi !== undefined && typeof entry.multi !== "boolean")
42
+ return false;
43
+ return entry.answer === undefined || isAnswer(entry.answer);
44
+ }
45
+ function parseTranscript(raw) {
46
+ const file = raw;
47
+ const valid = typeof file === "object" &&
48
+ file !== null &&
49
+ file.version === 1 &&
50
+ Array.isArray(file.entries) &&
51
+ file.entries.every(isEntry);
52
+ return valid ? file : undefined;
53
+ }
54
+ /** All entries, oldest first. Missing file = no transcript yet; corrupt = quarantined, []. */
55
+ export function readTranscript(path) {
56
+ if (!existsSync(path))
57
+ return [];
58
+ const file = parseTranscript(readJsonOr(path));
59
+ if (!file) {
60
+ quarantineCorruptFile(path, "grill transcript");
61
+ return [];
62
+ }
63
+ return file.entries;
64
+ }
65
+ /** Durably append one asked question (otacon ask). */
66
+ export function appendEntry(path, entry) {
67
+ appendEntries(path, [entry]);
68
+ }
69
+ /** Durably append a batch of asked questions in one write (otacon ask --batch). */
70
+ export function appendEntries(path, entries) {
71
+ const file = { version: 1, entries: [...readTranscript(path), ...entries] };
72
+ writeFileAtomic(path, stringify(file));
73
+ }
74
+ /**
75
+ * Record the user's answer on a transcript entry; returns the updated entry,
76
+ * or undefined when no question with that id exists. Re-answering overwrites
77
+ * — at-least-once delivery makes a duplicate answer legitimate, and the newer
78
+ * one wins (same posture as threads.ts answerQuestion).
79
+ */
80
+ export function answerEntry(path, id, answer) {
81
+ const entries = readTranscript(path);
82
+ const entry = entries.find((e) => e.id === id);
83
+ if (!entry)
84
+ return undefined;
85
+ entry.answer = answer;
86
+ writeFileAtomic(path, stringify({ version: 1, entries }));
87
+ return { ...entry };
88
+ }
89
+ //# sourceMappingURL=transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/daemon/transcript.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ // The daemon's browser-facing surface: the built SPA (static files from
2
+ // dist/ui) and the SSE streams the UI watches (DESIGN.md §6, §10). Static
3
+ // serving is hand-rolled because @hono/node-server's serveStatic resolves
4
+ // roots against process.cwd(), which is meaningless for a daemon spawned from
5
+ // any repo (DECISIONS.md "Daemon serves the built SPA from dist/ui").
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ const HEARTBEAT_MS = 25_000;
10
+ /** Vite's output is flat hashed names — no slashes means no traversal. */
11
+ const ASSET_NAME = /^[A-Za-z0-9_.-]+$/;
12
+ const CONTENT_TYPES = {
13
+ ".html": "text/html; charset=utf-8",
14
+ ".js": "text/javascript; charset=utf-8",
15
+ ".css": "text/css; charset=utf-8",
16
+ ".map": "application/json",
17
+ ".json": "application/json",
18
+ ".svg": "image/svg+xml",
19
+ ".png": "image/png",
20
+ ".ico": "image/x-icon",
21
+ ".txt": "text/plain; charset=utf-8",
22
+ ".woff2": "font/woff2",
23
+ };
24
+ /**
25
+ * dist/daemon/ui.js → ../ui = dist/ui; a source-tree run under bun falls back
26
+ * to <root>/dist/ui. The `assets/` guard is load-bearing: since ui/ moved under
27
+ * src/ui/ (commit fac1349), the `../ui/` candidate also matches src/ui/ in a
28
+ * source run — but that dir holds only the Vite *dev* template (a `/main.tsx`
29
+ * script tag the daemon can't serve), not a build. Requiring a sibling
30
+ * `assets/` dir disqualifies the source template across every layout (published
31
+ * package, repo dist/, source run) so only a real build is ever served.
32
+ */
33
+ function resolveBuiltUiDir() {
34
+ for (const candidate of ["../ui/", "../../dist/ui/"]) {
35
+ const dir = fileURLToPath(new URL(candidate, import.meta.url));
36
+ if (isBuiltUiDir(dir))
37
+ return dir;
38
+ }
39
+ return undefined;
40
+ }
41
+ /**
42
+ * True only for a real Vite build: both `index.html` and a sibling `assets/`
43
+ * dir. The source `src/ui/` holds the dev template (index.html, no assets/), so
44
+ * this predicate is what keeps a source run from serving it (see
45
+ * resolveBuiltUiDir). Exported for the regression test.
46
+ */
47
+ export function isBuiltUiDir(dir) {
48
+ return existsSync(join(dir, "index.html")) && existsSync(join(dir, "assets"));
49
+ }
50
+ const encoder = new TextEncoder();
51
+ function frame(event, data) {
52
+ return encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
53
+ }
54
+ /**
55
+ * One SSE response: a `snapshot` frame immediately (so subscribers never race
56
+ * a separate fetch), then every published UiEvent — optionally filtered to one
57
+ * session — plus a comment heartbeat that keeps idle proxies from severing the
58
+ * stream. No event ids: a reconnect re-syncs from the fresh snapshot.
59
+ */
60
+ function sse(c, deps, snapshot, onlySession) {
61
+ // Materializes node-server's lazy AbortController so client disconnects
62
+ // abort the signal (same trick as the events long-poll in app.ts).
63
+ const signal = c.req.raw.signal;
64
+ let cleanup;
65
+ const dispose = () => {
66
+ cleanup?.();
67
+ cleanup = undefined;
68
+ };
69
+ const stream = new ReadableStream({
70
+ start(controller) {
71
+ // Built before any resource exists: if snapshot() throws (a session read
72
+ // gone wrong), the error propagates out of sse() into app.onError with
73
+ // no subscription or heartbeat left behind to leak.
74
+ const snapshotFrame = frame("snapshot", snapshot());
75
+ const close = () => {
76
+ dispose();
77
+ try {
78
+ controller.close();
79
+ }
80
+ catch {
81
+ // already closed or cancelled
82
+ }
83
+ };
84
+ const send = (chunk) => {
85
+ try {
86
+ controller.enqueue(chunk);
87
+ }
88
+ catch {
89
+ close(); // reader vanished mid-enqueue
90
+ }
91
+ };
92
+ const unsubscribe = deps.notifier.subscribe((event) => {
93
+ if (onlySession !== undefined && event.session !== onlySession)
94
+ return;
95
+ send(frame(event.type, event.data));
96
+ // `removed` is terminal for a single-session stream: the session left
97
+ // the registry, so nothing can ever be published for it again. End the
98
+ // response server-side too — otherwise a client that ignores the frame
99
+ // pins this connection (subscription + heartbeat) forever. The index
100
+ // stream stays open: other sessions keep flowing.
101
+ if (onlySession !== undefined && event.type === "removed")
102
+ close();
103
+ });
104
+ const heartbeat = setInterval(() => send(encoder.encode(": hb\n\n")), deps.heartbeatMs ?? HEARTBEAT_MS);
105
+ heartbeat.unref?.(); // never holds the process open
106
+ const onAbort = () => close();
107
+ cleanup = () => {
108
+ unsubscribe();
109
+ clearInterval(heartbeat);
110
+ signal.removeEventListener("abort", onAbort);
111
+ };
112
+ send(snapshotFrame);
113
+ if (signal.aborted)
114
+ close();
115
+ else
116
+ signal.addEventListener("abort", onAbort);
117
+ },
118
+ cancel() {
119
+ dispose();
120
+ },
121
+ });
122
+ return new Response(stream, {
123
+ headers: {
124
+ "content-type": "text/event-stream; charset=utf-8",
125
+ "cache-control": "no-cache, no-transform",
126
+ connection: "keep-alive",
127
+ },
128
+ });
129
+ }
130
+ export function registerUiRoutes(app, deps) {
131
+ const uiDir = deps.uiDir === undefined ? resolveBuiltUiDir() : deps.uiDir;
132
+ // The SPA shell. /s/:id always answers 200 — a static shell cannot know
133
+ // session ids, so unknown sessions render a client-side not-found state.
134
+ const shell = (c) => {
135
+ if (uiDir == null) {
136
+ return c.text("otacon: web UI is not built — run `bun run build` (output: dist/ui)\n", 503);
137
+ }
138
+ return c.html(readFileSync(join(uiDir, "index.html"), "utf8"), 200, {
139
+ "cache-control": "no-cache",
140
+ });
141
+ };
142
+ app.get("/", shell);
143
+ app.get("/s/:id", shell);
144
+ app.get("/assets/*", (c) => {
145
+ const name = c.req.path.slice("/assets/".length);
146
+ const type = CONTENT_TYPES[name.slice(name.lastIndexOf("."))];
147
+ const path = uiDir != null && ASSET_NAME.test(name) && type !== undefined
148
+ ? join(uiDir, "assets", name)
149
+ : undefined;
150
+ if (path === undefined || !existsSync(path))
151
+ return c.text("not found\n", 404);
152
+ // Vite asset names are content-hashed: immutable forever.
153
+ return new Response(new Uint8Array(readFileSync(path)), {
154
+ headers: { "content-type": type, "cache-control": "public, max-age=31536000, immutable" },
155
+ });
156
+ });
157
+ app.get("/api/stream", (c) => sse(c, deps, () => ({ sessions: deps.listSummaries() })));
158
+ app.get("/api/sessions/:id/stream", (c) => {
159
+ const id = c.req.param("id");
160
+ if (!deps.getSummary(id)) {
161
+ return c.json({ error: { code: "E_NOT_FOUND", message: `unknown session: ${id}` } }, 404);
162
+ }
163
+ // Threads, transcript, and the activity feed ride the snapshot so the rail,
164
+ // the Interview panel, and the activity log never race a separate fetch
165
+ // against `thread`/`grill`/`activity` frames (same argument as
166
+ // snapshot-first itself).
167
+ return sse(c, deps, () => ({
168
+ session: deps.getSummary(id),
169
+ threads: deps.getThreads(id),
170
+ transcript: deps.getTranscript(id),
171
+ activity: deps.getActivity(id),
172
+ }), id);
173
+ });
174
+ }
175
+ //# sourceMappingURL=ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/daemon/ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { globalConfigPath, repoConfigPath } from "./paths.js";
3
+ export const DEFAULT_CONFIG = {
4
+ budgets: {
5
+ summaryLines: 5,
6
+ contractLines: 12,
7
+ impactLines: 10,
8
+ decisionEntryLines: 3,
9
+ phaseGoalLines: 3,
10
+ phaseVerificationLines: 3,
11
+ gwtMaxScenarios: 6,
12
+ risksMaxItems: 5,
13
+ riskEntryLines: 2,
14
+ maxFencesPerReadSection: 1,
15
+ maxVisualsPerReadSection: 2,
16
+ detailsSoftCapLines: 80,
17
+ },
18
+ activity: {
19
+ cap: 20,
20
+ noteMaxChars: 200,
21
+ },
22
+ notifications: { desktop: true },
23
+ };
24
+ function readJsonFile(path) {
25
+ try {
26
+ return JSON.parse(readFileSync(path, "utf8"));
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ /**
33
+ * Overlay one config file's `<section>` (all positive-number keys); invalid
34
+ * values are ignored with a notice. Both `budgets` and `activity` are flat
35
+ * maps of positive numbers, so they share this merge.
36
+ */
37
+ function mergeSection(base, raw, section, source) {
38
+ if (typeof raw !== "object" || raw === null)
39
+ return base;
40
+ const obj = raw[section];
41
+ if (typeof obj !== "object" || obj === null)
42
+ return base;
43
+ const merged = { ...base };
44
+ for (const key of Object.keys(base)) {
45
+ const value = obj[key];
46
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
47
+ merged[key] = value;
48
+ }
49
+ else if (value !== undefined) {
50
+ process.stderr.write(`otacon: ignoring invalid ${section}.${String(key)} in ${source}\n`);
51
+ }
52
+ }
53
+ return merged;
54
+ }
55
+ /** Overlay one config file's notifications; a non-boolean is ignored with a notice. */
56
+ function mergeNotifications(base, raw, source) {
57
+ if (typeof raw !== "object" || raw === null)
58
+ return base;
59
+ const notifications = raw.notifications;
60
+ if (typeof notifications !== "object" || notifications === null)
61
+ return base;
62
+ const merged = { ...base };
63
+ for (const key of Object.keys(base)) {
64
+ const value = notifications[key];
65
+ if (typeof value === "boolean") {
66
+ merged[key] = value;
67
+ }
68
+ else if (value !== undefined) {
69
+ process.stderr.write(`otacon: ignoring invalid notifications.${key} in ${source}\n`);
70
+ }
71
+ }
72
+ return merged;
73
+ }
74
+ /**
75
+ * defaults ← $OTACON_HOME/config.json ← <repo>/otacon.config.json.
76
+ * Loaded fresh on every use so tuning takes effect immediately. Each file is
77
+ * overlaid section by section (budgets, activity, notifications).
78
+ */
79
+ export function loadConfig(repoRoot) {
80
+ const overlay = (source, into) => {
81
+ const raw = readJsonFile(source);
82
+ return {
83
+ budgets: mergeSection(into.budgets, raw, "budgets", source),
84
+ activity: mergeSection(into.activity, raw, "activity", source),
85
+ notifications: mergeNotifications(into.notifications, raw, source),
86
+ };
87
+ };
88
+ let config = overlay(globalConfigPath(), DEFAULT_CONFIG);
89
+ if (repoRoot)
90
+ config = overlay(repoConfigPath(repoRoot), config);
91
+ return config;
92
+ }
93
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA4C9D,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C,OAAO,EAAE;QACP,YAAY,EAAE,CAAC;QACf,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,kBAAkB,EAAE,CAAC;QACrB,cAAc,EAAE,CAAC;QACjB,sBAAsB,EAAE,CAAC;QACzB,eAAe,EAAE,CAAC;QAClB,aAAa,EAAE,CAAC;QAChB,cAAc,EAAE,CAAC;QACjB,uBAAuB,EAAE,CAAC;QAC1B,wBAAwB,EAAE,CAAC;QAC3B,mBAAmB,EAAE,EAAE;KACxB;IACD,QAAQ,EAAE;QACR,GAAG,EAAE,EAAE;QACP,YAAY,EAAE,GAAG;KAClB;IACD,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;CACjC,CAAC;AAEF,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CACnB,IAAO,EACP,GAAY,EACZ,OAAe,EACf,MAAc;IAEd,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,GAAG,GAAI,GAA+B,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAgB,EAAE,CAAC;QACnD,MAAM,KAAK,GAAI,GAA+B,CAAC,GAAa,CAAC,CAAC;QAC9D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAmB,CAAC;QACpC,CAAC;aAAM,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,MAAM,IAAI,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,uFAAuF;AACvF,SAAS,kBAAkB,CAAC,IAAmB,EAAE,GAAY,EAAE,MAAc;IAC3E,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,aAAa,GAAI,GAA+B,CAAC,aAAa,CAAC;IACrE,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAA4B,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAI,aAAyC,CAAC,GAAG,CAAC,CAAC;QAC9D,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;aAAM,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,GAAG,OAAO,MAAM,IAAI,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,QAAiB;IAC1C,MAAM,OAAO,GAAG,CAAC,MAAc,EAAE,IAAkB,EAAgB,EAAE;QACnE,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACjC,OAAO;YACL,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC;YAC3D,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,CAAC;YAC9D,aAAa,EAAE,kBAAkB,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE,MAAM,CAAC;SACnE,CAAC;IACJ,CAAC,CAAC;IACF,IAAI,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,EAAE,cAAc,CAAC,CAAC;IACzD,IAAI,QAAQ;QAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,69 @@
1
+ // The behavioral-assertion grammar (DESIGN.md §4, §9): a ```gwt fence inside a
2
+ // phase's Verification holds one or more Given/When/Then scenarios that double
3
+ // as the human's Test-Driven Review approve checklist. This tokenizer is the
4
+ // single source of truth for what a scenario *is*, shared by the daemon linter
5
+ // (shape + budget validation, rules.ts) and the UI scenario cards
6
+ // (scenario-card.tsx) so the two can never disagree about the grammar — unlike
7
+ // the line grammar, which is deliberately ported (DECISIONS.md), this construct
8
+ // is new, so one shared parser is the simpler, drift-proof choice. Purely
9
+ // structural: it tokenizes and flags well-formedness; the *verdicts* (empty,
10
+ // malformed, over budget) are the linter's (rules.ts).
11
+ // A step line: a leading keyword (case-insensitive) plus the rest as text.
12
+ // `And`/`But` continue whichever clause is open.
13
+ const STEP_RE = /^(given|when|then|and|but)\b\s*(.*)$/i;
14
+ const RANK = { given: 0, when: 1, then: 2 };
15
+ /** Tokenize one scenario's non-blank lines into a `GwtScenario`. */
16
+ function parseScenario(lines) {
17
+ const scenario = { given: [], when: [], then: [], valid: true };
18
+ let clause = null;
19
+ let maxRank = -1;
20
+ for (const line of lines) {
21
+ const match = STEP_RE.exec(line.trim());
22
+ if (!match) {
23
+ scenario.valid = false; // a line that isn't a Given/When/Then/And/But step
24
+ continue;
25
+ }
26
+ const keyword = match[1].toLowerCase();
27
+ const text = match[2].trim();
28
+ if (keyword === "given" || keyword === "when" || keyword === "then") {
29
+ // Scenarios read top-down: a keyword may not regress (When then Given).
30
+ if (RANK[keyword] < maxRank)
31
+ scenario.valid = false;
32
+ maxRank = Math.max(maxRank, RANK[keyword]);
33
+ clause = keyword;
34
+ }
35
+ else if (clause === null) {
36
+ scenario.valid = false; // a dangling And/But with no clause to continue
37
+ continue;
38
+ }
39
+ if (clause)
40
+ scenario[clause].push(text);
41
+ }
42
+ if (scenario.given.length === 0 || scenario.when.length === 0 || scenario.then.length === 0) {
43
+ scenario.valid = false;
44
+ }
45
+ return scenario;
46
+ }
47
+ /**
48
+ * Parse a ```gwt fence body into scenarios. Scenarios are separated by one or
49
+ * more blank lines; empty groups (leading/trailing blanks) are dropped.
50
+ */
51
+ export function parseGwt(body) {
52
+ const groups = [];
53
+ let current = [];
54
+ for (const line of body.split("\n")) {
55
+ if (line.trim() === "") {
56
+ if (current.length > 0) {
57
+ groups.push(current);
58
+ current = [];
59
+ }
60
+ }
61
+ else {
62
+ current.push(line);
63
+ }
64
+ }
65
+ if (current.length > 0)
66
+ groups.push(current);
67
+ return { scenarios: groups.map(parseScenario) };
68
+ }
69
+ //# sourceMappingURL=gwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gwt.js","sourceRoot":"","sources":["../../src/shared/gwt.ts"],"names":[],"mappings":"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"}