@aitne/daemon 0.1.4 → 0.1.6

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 (268) hide show
  1. package/dist/adapters/notification-manager.d.ts +12 -0
  2. package/dist/adapters/notification-manager.d.ts.map +1 -1
  3. package/dist/adapters/notification-manager.js +39 -1
  4. package/dist/adapters/notification-manager.js.map +1 -1
  5. package/dist/api/routes/agent.d.ts.map +1 -1
  6. package/dist/api/routes/agent.js +7 -0
  7. package/dist/api/routes/agent.js.map +1 -1
  8. package/dist/api/routes/commands.d.ts.map +1 -1
  9. package/dist/api/routes/commands.js +16 -13
  10. package/dist/api/routes/commands.js.map +1 -1
  11. package/dist/api/routes/context.d.ts.map +1 -1
  12. package/dist/api/routes/context.js +13 -2
  13. package/dist/api/routes/context.js.map +1 -1
  14. package/dist/api/routes/dashboard.d.ts.map +1 -1
  15. package/dist/api/routes/dashboard.js +28 -0
  16. package/dist/api/routes/dashboard.js.map +1 -1
  17. package/dist/api/routes/fs.d.ts +23 -0
  18. package/dist/api/routes/fs.d.ts.map +1 -0
  19. package/dist/api/routes/fs.js +156 -0
  20. package/dist/api/routes/fs.js.map +1 -0
  21. package/dist/api/routes/fs.logic.d.ts +62 -0
  22. package/dist/api/routes/fs.logic.d.ts.map +1 -0
  23. package/dist/api/routes/fs.logic.js +137 -0
  24. package/dist/api/routes/fs.logic.js.map +1 -0
  25. package/dist/api/routes/health.d.ts.map +1 -1
  26. package/dist/api/routes/health.js +4 -2
  27. package/dist/api/routes/health.js.map +1 -1
  28. package/dist/api/routes/integrations.d.ts.map +1 -1
  29. package/dist/api/routes/integrations.js +8 -6
  30. package/dist/api/routes/integrations.js.map +1 -1
  31. package/dist/api/routes/metrics.d.ts +1 -0
  32. package/dist/api/routes/metrics.d.ts.map +1 -1
  33. package/dist/api/routes/metrics.js +24 -0
  34. package/dist/api/routes/metrics.js.map +1 -1
  35. package/dist/api/routes/observations.d.ts.map +1 -1
  36. package/dist/api/routes/observations.js +538 -25
  37. package/dist/api/routes/observations.js.map +1 -1
  38. package/dist/api/routes/skills.d.ts +9 -1
  39. package/dist/api/routes/skills.d.ts.map +1 -1
  40. package/dist/api/routes/skills.js +38 -16
  41. package/dist/api/routes/skills.js.map +1 -1
  42. package/dist/api/routes/wiki.d.ts +4 -0
  43. package/dist/api/routes/wiki.d.ts.map +1 -0
  44. package/dist/api/routes/wiki.js +1075 -0
  45. package/dist/api/routes/wiki.js.map +1 -0
  46. package/dist/api/server.d.ts +13 -0
  47. package/dist/api/server.d.ts.map +1 -1
  48. package/dist/api/server.js +27 -1
  49. package/dist/api/server.js.map +1 -1
  50. package/dist/config.d.ts.map +1 -1
  51. package/dist/config.js +26 -0
  52. package/dist/config.js.map +1 -1
  53. package/dist/core/agent-core.d.ts +25 -0
  54. package/dist/core/agent-core.d.ts.map +1 -1
  55. package/dist/core/agent-core.js.map +1 -1
  56. package/dist/core/backends/backend-router.d.ts +5 -1
  57. package/dist/core/backends/backend-router.d.ts.map +1 -1
  58. package/dist/core/backends/backend-router.js +10 -1
  59. package/dist/core/backends/backend-router.js.map +1 -1
  60. package/dist/core/backends/claude-code-core.d.ts.map +1 -1
  61. package/dist/core/backends/claude-code-core.js +62 -4
  62. package/dist/core/backends/claude-code-core.js.map +1 -1
  63. package/dist/core/backends/claude-tool-collection.d.ts +1 -1
  64. package/dist/core/backends/claude-tool-collection.d.ts.map +1 -1
  65. package/dist/core/backends/claude-tool-collection.js +327 -65
  66. package/dist/core/backends/claude-tool-collection.js.map +1 -1
  67. package/dist/core/backends/codex-core.d.ts.map +1 -1
  68. package/dist/core/backends/codex-core.js +36 -0
  69. package/dist/core/backends/codex-core.js.map +1 -1
  70. package/dist/core/backends/gemini-cli-core.d.ts +24 -5
  71. package/dist/core/backends/gemini-cli-core.d.ts.map +1 -1
  72. package/dist/core/backends/gemini-cli-core.js +62 -30
  73. package/dist/core/backends/gemini-cli-core.js.map +1 -1
  74. package/dist/core/backends/plan-presets.d.ts +3 -1
  75. package/dist/core/backends/plan-presets.d.ts.map +1 -1
  76. package/dist/core/backends/plan-presets.js +42 -2
  77. package/dist/core/backends/plan-presets.js.map +1 -1
  78. package/dist/core/bang-commands/commands-help.d.ts +5 -0
  79. package/dist/core/bang-commands/commands-help.d.ts.map +1 -0
  80. package/dist/core/bang-commands/commands-help.js +69 -0
  81. package/dist/core/bang-commands/commands-help.js.map +1 -0
  82. package/dist/core/bang-commands/commands-wiki.d.ts +75 -0
  83. package/dist/core/bang-commands/commands-wiki.d.ts.map +1 -0
  84. package/dist/core/bang-commands/commands-wiki.js +574 -0
  85. package/dist/core/bang-commands/commands-wiki.js.map +1 -0
  86. package/dist/core/bang-commands/index.d.ts +4 -2
  87. package/dist/core/bang-commands/index.d.ts.map +1 -1
  88. package/dist/core/bang-commands/index.js +15 -1
  89. package/dist/core/bang-commands/index.js.map +1 -1
  90. package/dist/core/bang-commands/registry.d.ts +47 -4
  91. package/dist/core/bang-commands/registry.d.ts.map +1 -1
  92. package/dist/core/bang-commands/registry.js +85 -15
  93. package/dist/core/bang-commands/registry.js.map +1 -1
  94. package/dist/core/context-builder.d.ts +17 -0
  95. package/dist/core/context-builder.d.ts.map +1 -1
  96. package/dist/core/context-builder.js +64 -6
  97. package/dist/core/context-builder.js.map +1 -1
  98. package/dist/core/daemon-api-cli.d.ts.map +1 -1
  99. package/dist/core/daemon-api-cli.js +50 -2
  100. package/dist/core/daemon-api-cli.js.map +1 -1
  101. package/dist/core/dispatcher-message-handler.d.ts.map +1 -1
  102. package/dist/core/dispatcher-message-handler.js +10 -0
  103. package/dist/core/dispatcher-message-handler.js.map +1 -1
  104. package/dist/core/dispatcher-morning-routine.d.ts.map +1 -1
  105. package/dist/core/dispatcher-morning-routine.js +17 -2
  106. package/dist/core/dispatcher-morning-routine.js.map +1 -1
  107. package/dist/core/dispatcher-result-processor.d.ts +23 -0
  108. package/dist/core/dispatcher-result-processor.d.ts.map +1 -1
  109. package/dist/core/dispatcher-result-processor.js +124 -5
  110. package/dist/core/dispatcher-result-processor.js.map +1 -1
  111. package/dist/core/dispatcher-scheduled-tasks.d.ts.map +1 -1
  112. package/dist/core/dispatcher-scheduled-tasks.js +114 -80
  113. package/dist/core/dispatcher-scheduled-tasks.js.map +1 -1
  114. package/dist/core/dispatcher-types.d.ts +116 -1
  115. package/dist/core/dispatcher-types.d.ts.map +1 -1
  116. package/dist/core/dispatcher-types.js.map +1 -1
  117. package/dist/core/dispatcher.d.ts +36 -0
  118. package/dist/core/dispatcher.d.ts.map +1 -1
  119. package/dist/core/dispatcher.js +94 -1
  120. package/dist/core/dispatcher.js.map +1 -1
  121. package/dist/core/integration-lifecycle.d.ts.map +1 -1
  122. package/dist/core/integration-lifecycle.js +6 -8
  123. package/dist/core/integration-lifecycle.js.map +1 -1
  124. package/dist/core/metrics.d.ts +127 -0
  125. package/dist/core/metrics.d.ts.map +1 -1
  126. package/dist/core/metrics.js +256 -1
  127. package/dist/core/metrics.js.map +1 -1
  128. package/dist/core/prompts.d.ts +2 -1
  129. package/dist/core/prompts.d.ts.map +1 -1
  130. package/dist/core/prompts.js +40 -0
  131. package/dist/core/prompts.js.map +1 -1
  132. package/dist/core/roadmap-validate.js +13 -1
  133. package/dist/core/roadmap-validate.js.map +1 -1
  134. package/dist/core/routine-acquisition-plan.d.ts +51 -0
  135. package/dist/core/routine-acquisition-plan.d.ts.map +1 -1
  136. package/dist/core/routine-acquisition-plan.js +111 -12
  137. package/dist/core/routine-acquisition-plan.js.map +1 -1
  138. package/dist/core/routine-fetch-window-retry.d.ts +109 -0
  139. package/dist/core/routine-fetch-window-retry.d.ts.map +1 -0
  140. package/dist/core/routine-fetch-window-retry.js +210 -0
  141. package/dist/core/routine-fetch-window-retry.js.map +1 -0
  142. package/dist/core/routine-fetch-window-runner.d.ts +258 -32
  143. package/dist/core/routine-fetch-window-runner.d.ts.map +1 -1
  144. package/dist/core/routine-fetch-window-runner.js +1115 -185
  145. package/dist/core/routine-fetch-window-runner.js.map +1 -1
  146. package/dist/core/routine-windows.d.ts +19 -4
  147. package/dist/core/routine-windows.d.ts.map +1 -1
  148. package/dist/core/routine-windows.js +47 -0
  149. package/dist/core/routine-windows.js.map +1 -1
  150. package/dist/core/scheduler.d.ts +50 -2
  151. package/dist/core/scheduler.d.ts.map +1 -1
  152. package/dist/core/scheduler.js +88 -7
  153. package/dist/core/scheduler.js.map +1 -1
  154. package/dist/core/skill-curation/declarations.d.ts.map +1 -1
  155. package/dist/core/skill-curation/declarations.js +11 -12
  156. package/dist/core/skill-curation/declarations.js.map +1 -1
  157. package/dist/core/skill-source-paths.d.ts +14 -0
  158. package/dist/core/skill-source-paths.d.ts.map +1 -0
  159. package/dist/core/skill-source-paths.js +82 -0
  160. package/dist/core/skill-source-paths.js.map +1 -0
  161. package/dist/core/skills-compiler.d.ts +18 -0
  162. package/dist/core/skills-compiler.d.ts.map +1 -1
  163. package/dist/core/skills-compiler.js +65 -18
  164. package/dist/core/skills-compiler.js.map +1 -1
  165. package/dist/core/skills-manifest.d.ts.map +1 -1
  166. package/dist/core/skills-manifest.js +46 -0
  167. package/dist/core/skills-manifest.js.map +1 -1
  168. package/dist/core/system-reset.d.ts +25 -0
  169. package/dist/core/system-reset.d.ts.map +1 -1
  170. package/dist/core/system-reset.js +47 -0
  171. package/dist/core/system-reset.js.map +1 -1
  172. package/dist/core/wiki/approval-queue.d.ts +31 -0
  173. package/dist/core/wiki/approval-queue.d.ts.map +1 -0
  174. package/dist/core/wiki/approval-queue.js +44 -0
  175. package/dist/core/wiki/approval-queue.js.map +1 -0
  176. package/dist/core/wiki/bridge.d.ts +74 -0
  177. package/dist/core/wiki/bridge.d.ts.map +1 -0
  178. package/dist/core/wiki/bridge.js +405 -0
  179. package/dist/core/wiki/bridge.js.map +1 -0
  180. package/dist/core/wiki/compile-lock.d.ts +42 -0
  181. package/dist/core/wiki/compile-lock.d.ts.map +1 -0
  182. package/dist/core/wiki/compile-lock.js +55 -0
  183. package/dist/core/wiki/compile-lock.js.map +1 -0
  184. package/dist/core/wiki/compile-preview.d.ts +8 -0
  185. package/dist/core/wiki/compile-preview.d.ts.map +1 -0
  186. package/dist/core/wiki/compile-preview.js +200 -0
  187. package/dist/core/wiki/compile-preview.js.map +1 -0
  188. package/dist/core/wiki/cost-estimate.d.ts +30 -0
  189. package/dist/core/wiki/cost-estimate.d.ts.map +1 -0
  190. package/dist/core/wiki/cost-estimate.js +243 -0
  191. package/dist/core/wiki/cost-estimate.js.map +1 -0
  192. package/dist/core/wiki/dispatcher.d.ts +48 -0
  193. package/dist/core/wiki/dispatcher.d.ts.map +1 -0
  194. package/dist/core/wiki/dispatcher.js +92 -0
  195. package/dist/core/wiki/dispatcher.js.map +1 -0
  196. package/dist/core/wiki/git-precompile.d.ts +86 -0
  197. package/dist/core/wiki/git-precompile.d.ts.map +1 -0
  198. package/dist/core/wiki/git-precompile.js +96 -0
  199. package/dist/core/wiki/git-precompile.js.map +1 -0
  200. package/dist/core/wiki/import-migrate.d.ts +38 -0
  201. package/dist/core/wiki/import-migrate.d.ts.map +1 -0
  202. package/dist/core/wiki/import-migrate.js +310 -0
  203. package/dist/core/wiki/import-migrate.js.map +1 -0
  204. package/dist/core/wiki/import-probe.d.ts +76 -0
  205. package/dist/core/wiki/import-probe.d.ts.map +1 -0
  206. package/dist/core/wiki/import-probe.js +245 -0
  207. package/dist/core/wiki/import-probe.js.map +1 -0
  208. package/dist/core/wiki/index-cache.d.ts +39 -0
  209. package/dist/core/wiki/index-cache.d.ts.map +1 -0
  210. package/dist/core/wiki/index-cache.js +152 -0
  211. package/dist/core/wiki/index-cache.js.map +1 -0
  212. package/dist/core/wiki/multi-url-dispatch.d.ts +52 -0
  213. package/dist/core/wiki/multi-url-dispatch.d.ts.map +1 -0
  214. package/dist/core/wiki/multi-url-dispatch.js +72 -0
  215. package/dist/core/wiki/multi-url-dispatch.js.map +1 -0
  216. package/dist/core/wiki/wiki-fts.d.ts +75 -0
  217. package/dist/core/wiki/wiki-fts.d.ts.map +1 -0
  218. package/dist/core/wiki/wiki-fts.js +265 -0
  219. package/dist/core/wiki/wiki-fts.js.map +1 -0
  220. package/dist/core/wiki/workspaces.d.ts +101 -0
  221. package/dist/core/wiki/workspaces.d.ts.map +1 -0
  222. package/dist/core/wiki/workspaces.js +352 -0
  223. package/dist/core/wiki/workspaces.js.map +1 -0
  224. package/dist/core/wiki/write-strategy.d.ts +70 -0
  225. package/dist/core/wiki/write-strategy.d.ts.map +1 -0
  226. package/dist/core/wiki/write-strategy.js +112 -0
  227. package/dist/core/wiki/write-strategy.js.map +1 -0
  228. package/dist/core/workdir.d.ts +8 -1
  229. package/dist/core/workdir.d.ts.map +1 -1
  230. package/dist/core/workdir.js +4 -1
  231. package/dist/core/workdir.js.map +1 -1
  232. package/dist/db/schema.d.ts.map +1 -1
  233. package/dist/db/schema.js +122 -0
  234. package/dist/db/schema.js.map +1 -1
  235. package/dist/db/wiki-store.d.ts +3 -0
  236. package/dist/db/wiki-store.d.ts.map +1 -0
  237. package/dist/db/wiki-store.js +7 -0
  238. package/dist/db/wiki-store.js.map +1 -0
  239. package/dist/index.js +80 -4
  240. package/dist/index.js.map +1 -1
  241. package/dist/messaging/url-extract.d.ts +8 -0
  242. package/dist/messaging/url-extract.d.ts.map +1 -0
  243. package/dist/messaging/url-extract.js +41 -0
  244. package/dist/messaging/url-extract.js.map +1 -0
  245. package/dist/observers/delegated-sync-worker.d.ts +33 -25
  246. package/dist/observers/delegated-sync-worker.d.ts.map +1 -1
  247. package/dist/observers/delegated-sync-worker.js +38 -31
  248. package/dist/observers/delegated-sync-worker.js.map +1 -1
  249. package/dist/observers/imminent-event-scheduler.d.ts +20 -7
  250. package/dist/observers/imminent-event-scheduler.d.ts.map +1 -1
  251. package/dist/observers/imminent-event-scheduler.js +134 -29
  252. package/dist/observers/imminent-event-scheduler.js.map +1 -1
  253. package/dist/safety/always-disallowed.d.ts +65 -0
  254. package/dist/safety/always-disallowed.d.ts.map +1 -1
  255. package/dist/safety/always-disallowed.js +106 -10
  256. package/dist/safety/always-disallowed.js.map +1 -1
  257. package/dist/safety/audit.d.ts +46 -1
  258. package/dist/safety/audit.d.ts.map +1 -1
  259. package/dist/safety/audit.js +79 -16
  260. package/dist/safety/audit.js.map +1 -1
  261. package/dist/safety/risk-classifier.d.ts.map +1 -1
  262. package/dist/safety/risk-classifier.js +29 -0
  263. package/dist/safety/risk-classifier.js.map +1 -1
  264. package/dist/settings/runtime-settings.d.ts +12 -1
  265. package/dist/settings/runtime-settings.d.ts.map +1 -1
  266. package/dist/settings/runtime-settings.js +59 -1
  267. package/dist/settings/runtime-settings.js.map +1 -1
  268. package/package.json +2 -2
@@ -0,0 +1,75 @@
1
+ import { type WikiCompileMode } from "@aitne/shared";
2
+ import type { BangCommand, BangPrefixCommand } from "./registry.js";
3
+ export interface WorkspaceTokenSplit {
4
+ workspaceName: string | null;
5
+ rest: string;
6
+ }
7
+ export declare function splitWorkspaceToken(rest: string): WorkspaceTokenSplit;
8
+ interface CompileArgs {
9
+ mode: WikiCompileMode;
10
+ preview: boolean;
11
+ workspaceName: string | null;
12
+ }
13
+ export declare function parseCompileArgs(rest: string): CompileArgs;
14
+ interface ConnectArgs {
15
+ topicA: string;
16
+ topicB: string;
17
+ workspaceName: string | null;
18
+ }
19
+ export declare const ingestCommand: BangPrefixCommand;
20
+ export declare const compileCommand: BangPrefixCommand;
21
+ export declare const askCommand: BangPrefixCommand;
22
+ export declare const wikiStatusCommand: BangCommand;
23
+ export declare const wikiHelpCommand: BangCommand;
24
+ /**
25
+ * WIKI_BUILDER_DESIGN.md Phase 3 — `!lint` runs the wiki health audit
26
+ * (orphans, broken links, schema drift, taxonomy candidates) and writes
27
+ * `90_meta/health/<YYYY-MM-DD>.md`. No arguments. Exposed as a
28
+ * `BangPrefixCommand` rather than `BangCommand` so the surface can grow
29
+ * later (e.g. `!lint --since=2026-01-01`) without re-registering; today
30
+ * `parseArgs` rejects any trailing input.
31
+ */
32
+ export declare const lintCommand: BangPrefixCommand;
33
+ /**
34
+ * WIKI_BUILDER_DESIGN.md Phase 3 — `!trace <topic>` reconstructs the
35
+ * chronological evolution of `<topic>` across the wiki's layers and
36
+ * writes one timeline output to
37
+ * `30_outputs/<YYYY-MM-DD>-trace-<slug>.md`.
38
+ *
39
+ * The topic is free-form prose. The skill canonicalises the topic against
40
+ * `90_meta/taxonomy.md` before deriving the output slug, so the bang
41
+ * handler does not validate the topic shape beyond "non-empty after trim".
42
+ */
43
+ export declare const traceCommand: BangPrefixCommand;
44
+ /**
45
+ * Pure helper exported for tests. Parses the rest-string for `!connect`
46
+ * into exactly two topic arguments.
47
+ *
48
+ * Tokenisation rule (WIKI_BUILDER_DESIGN.md §P3.B): the args are
49
+ * "whitespace- or comma-separated". A literal comma OR run of whitespace
50
+ * is the separator. This means `!connect quantum gravity`,
51
+ * `!connect quantum, gravity`, and `!connect "quantum, gravity"` (with
52
+ * the comma being part of a topic) all need a deterministic answer; we
53
+ * pick the simplest rule that matches the design verbatim and reject any
54
+ * input that yields anything other than two non-empty topics. The owner
55
+ * gets a clean usage message; they don't have to learn shell quoting.
56
+ */
57
+ export declare function parseConnectArgs(rest: string): ConnectArgs;
58
+ /**
59
+ * WIKI_BUILDER_DESIGN.md Phase 3 — `!connect <a> <b>` bridges two
60
+ * domains. Output lands at
61
+ * `30_outputs/<YYYY-MM-DD>-connect-<slug-a>--<slug-b>.md`.
62
+ *
63
+ * Exactly two args required. See {@link parseConnectArgs}.
64
+ */
65
+ export declare const connectCommand: BangPrefixCommand;
66
+ /**
67
+ * WIKI_BUILDER_DESIGN.md §P4.B — render a compile preview into the DM
68
+ * reply shape. Truncates the added/modified/unchanged lists at 8 entries
69
+ * so the mobile reply stays scrollable; the dashboard's
70
+ * `/wiki/compile/preview` route surfaces the full lists. Exported for
71
+ * tests.
72
+ */
73
+ export declare function formatCompilePreview(preview: import("@aitne/shared").WikiCompilePreview): string;
74
+ export {};
75
+ //# sourceMappingURL=commands-wiki.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands-wiki.d.ts","sourceRoot":"","sources":["../../../src/core/bang-commands/commands-wiki.ts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAsB,iBAAiB,EAAE,MAAM,eAAe,CAAC;AA+CxF,MAAM,WAAW,mBAAmB;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAerE;AAYD,UAAU,WAAW;IACnB,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAUD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAoB1D;AAOD,UAAU,WAAW;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AA6CD,eAAO,MAAM,aAAa,EAAE,iBAyC3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,iBAsL5B,CAAC;AAYF,eAAO,MAAM,UAAU,EAAE,iBA6BxB,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WA8C/B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAoB7B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,iBA6BzB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,EAAE,iBA+B1B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAmB1D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,iBA0B5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,eAAe,EAAE,kBAAkB,GAAG,MAAM,CAoBhG"}
@@ -0,0 +1,574 @@
1
+ import { wikiCompileModeSchema } from "@aitne/shared";
2
+ import { BangArgError } from "./registry.js";
3
+ import { extractHttpUrls } from "../../messaging/url-extract.js";
4
+ import { isWikiEnabled } from "../../db/wiki-store.js";
5
+ import { buildWikiWorkspaceStats, DEFAULT_WIKI_WORKSPACE_NAME, listActiveWikiWorkspaces, readDefaultWikiWorkspace, resolveWikiWorkspace, } from "../wiki/workspaces.js";
6
+ import { createWikiCommandEvent } from "../wiki/dispatcher.js";
7
+ import { dispatchWikiUrlBatch } from "../wiki/multi-url-dispatch.js";
8
+ import { estimateFullCompileCost } from "../wiki/cost-estimate.js";
9
+ import { buildCompilePreview } from "../wiki/compile-preview.js";
10
+ import { previewGitPreCompile, runGitPreCompile, } from "../wiki/git-precompile.js";
11
+ import { releaseWikiCompileLock, tryAcquireWikiCompileLock, } from "../wiki/compile-lock.js";
12
+ /**
13
+ * WIKI_BUILDER_DESIGN.md §P5.C — `@<workspace>` prefix lets the owner
14
+ * target a non-default workspace from a DM:
15
+ *
16
+ * `!ingest @research https://example.com`
17
+ * `!compile @ops full`
18
+ * `!ask @journal what did I conclude about X?`
19
+ *
20
+ * Pure parser — call from each command's `parseArgs`. Strips the
21
+ * `@<name>` token from the head of the rest-string and returns it
22
+ * paired with the trimmed remainder. When the rest does NOT start
23
+ * with `@`, the workspace is `null` and the bang handler will fall
24
+ * back to the active default.
25
+ *
26
+ * Workspace name shape matches the DB unique-name constraint
27
+ * (`min 1 / max 64`); we accept the same `[A-Za-z0-9._-]` characters
28
+ * the wizard generates and reject anything that smells like a path
29
+ * traversal or shell injection.
30
+ */
31
+ const WORKSPACE_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
32
+ export function splitWorkspaceToken(rest) {
33
+ const trimmed = rest.trimStart();
34
+ if (!trimmed.startsWith("@"))
35
+ return { workspaceName: null, rest };
36
+ const spaceIdx = trimmed.indexOf(" ");
37
+ const token = spaceIdx === -1 ? trimmed : trimmed.slice(0, spaceIdx);
38
+ const name = token.slice(1);
39
+ if (!WORKSPACE_NAME_RE.test(name)) {
40
+ throw new BangArgError("Invalid workspace name after `@`. Use letters, numbers, `.`, `_`, or `-` only.");
41
+ }
42
+ return {
43
+ workspaceName: name,
44
+ rest: spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1).trim(),
45
+ };
46
+ }
47
+ // WIKI_BUILDER_DESIGN.md §P4.B — `!compile --preview` (alias `--dry-run`)
48
+ // produces the touch-set DM without enqueueing an agent session. Pure
49
+ // helper exported for `commands-wiki.test.ts`; the bang handler is a
50
+ // thin shim around it.
51
+ //
52
+ // §P5.C — accepts an optional leading `@<workspace>` token to target a
53
+ // non-default workspace. When absent, the bang handler resolves to the
54
+ // default workspace as before.
55
+ export function parseCompileArgs(rest) {
56
+ const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
57
+ const tokens = payload.trim().split(/\s+/).filter(Boolean);
58
+ let preview = false;
59
+ const remainder = [];
60
+ for (const tok of tokens) {
61
+ if (tok === "--preview" || tok === "--dry-run") {
62
+ preview = true;
63
+ continue;
64
+ }
65
+ remainder.push(tok);
66
+ }
67
+ const candidate = remainder.length === 0 ? "incremental" : remainder.join(" ").toLowerCase();
68
+ const parsed = wikiCompileModeSchema.safeParse(candidate);
69
+ if (!parsed.success) {
70
+ throw new BangArgError("Usage: `!compile [@<workspace>]` (incremental), `!compile [@<workspace>] full` (full rebuild — approval-gated), or `!compile [@<workspace>] [full] --preview` (dry-run preview).");
71
+ }
72
+ return { mode: parsed.data, preview, workspaceName };
73
+ }
74
+ async function enqueueOrNotify(ctx, event) {
75
+ if (!ctx.enqueueWikiEvent) {
76
+ await ctx.notify("Wiki dispatch is not available in this daemon process.");
77
+ return false;
78
+ }
79
+ await ctx.enqueueWikiEvent(event);
80
+ return true;
81
+ }
82
+ /**
83
+ * Resolve the workspace named by an `@<workspace>` token (or the
84
+ * default when null). Surfaces friendly DMs when the wiki is disabled,
85
+ * the named workspace is missing/archived, or the request was unnamed
86
+ * and no default exists. Returns the row when usable, null otherwise.
87
+ */
88
+ async function requireWikiWorkspace(ctx, workspaceName = null) {
89
+ if (!isWikiEnabled(ctx.db)) {
90
+ await ctx.notify("Wiki is not enabled. Open `/settings/wiki` and enable the internal wiki workspace first.");
91
+ return null;
92
+ }
93
+ const workspace = resolveWikiWorkspace(ctx.db, workspaceName);
94
+ if (workspace)
95
+ return workspace;
96
+ if (workspaceName) {
97
+ const active = listActiveWikiWorkspaces(ctx.db);
98
+ const names = active.map((row) => `\`${row.name}\``).join(", ");
99
+ await ctx.notify([
100
+ `Unknown wiki workspace \`@${workspaceName}\`.`,
101
+ active.length > 0 ? `Active workspaces: ${names}.` : `No active wiki workspaces.`,
102
+ ].join("\n"));
103
+ return null;
104
+ }
105
+ await ctx.notify("Wiki workspace is enabled but no active workspace row could be resolved.");
106
+ return null;
107
+ }
108
+ export const ingestCommand = {
109
+ prefix: "!ingest",
110
+ title: "Ingest URLs",
111
+ describe: "ingest one or more URLs into the wiki's raw layer (`@<workspace>` optional)",
112
+ parseArgs(rest) {
113
+ try {
114
+ const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
115
+ const { urls } = extractHttpUrls(payload);
116
+ return { urls, workspaceName };
117
+ }
118
+ catch (err) {
119
+ if (err instanceof BangArgError)
120
+ throw err;
121
+ throw new BangArgError(err instanceof Error ? err.message : "Provide at least one URL.");
122
+ }
123
+ },
124
+ async handler(ctx, rawArgs) {
125
+ const args = rawArgs;
126
+ const workspace = await requireWikiWorkspace(ctx, args.workspaceName);
127
+ if (!workspace)
128
+ return;
129
+ if (!ctx.enqueueWikiEvent) {
130
+ await ctx.notify("Wiki dispatch is not available in this daemon process.");
131
+ return;
132
+ }
133
+ const result = await dispatchWikiUrlBatch({
134
+ workspace: workspace.name,
135
+ urls: args.urls,
136
+ mode: workspace.dispatch_mode,
137
+ concurrencyCap: workspace.dispatch_mode === "serial" ? 1 : workspace.concurrency_cap,
138
+ sourceEvent: ctx.event,
139
+ enqueue: ctx.enqueueWikiEvent,
140
+ });
141
+ const tail = result.mode === "serial"
142
+ ? `serially (you will get a single consolidated reply when the batch finishes)`
143
+ : `in parallel (each URL will reply on completion)`;
144
+ await ctx.notify(`Queued ${result.queued} URL${result.queued === 1 ? "" : "s"} for wiki ingestion in workspace \`${workspace.name}\` ${tail}.`);
145
+ },
146
+ };
147
+ export const compileCommand = {
148
+ prefix: "!compile",
149
+ title: "Compile wiki",
150
+ describe: "compile pending raw notes into wiki notes (`!compile full` rebuilds; `--preview` is dry-run)",
151
+ parseArgs(rest) {
152
+ return parseCompileArgs(rest);
153
+ },
154
+ async handler(ctx, rawArgs) {
155
+ const args = rawArgs;
156
+ const workspace = await requireWikiWorkspace(ctx, args.workspaceName);
157
+ if (!workspace)
158
+ return;
159
+ // §P4.B — `--preview` short-circuits to the dry-run summary regardless
160
+ // of mode. No agent session is dispatched, no approval row is queued,
161
+ // no git commit is made — the operator just gets the touch set + cost.
162
+ if (args.preview) {
163
+ const preview = buildCompilePreview({ workspace, mode: args.mode });
164
+ await ctx.notify(formatCompilePreview(preview));
165
+ return;
166
+ }
167
+ // WIKI_BUILDER_DESIGN.md §3.5 / §14 Q4 — second `!compile` mid-`!compile`
168
+ // is rejected with the running session's correlation id. Queuing a
169
+ // second compile would land on a vault state already reflecting the
170
+ // queued user's intent (the in-flight run picked up the same raw
171
+ // items), producing duplicate work. The lock is acquired here at
172
+ // enqueue time; the dispatcher releases it in `executeDefault`'s
173
+ // `finally` for `wiki.compile` events. The approval-tier path below
174
+ // also acquires after the operator approves, not here, so a pending
175
+ // approval doesn't block a different operator from running compile.
176
+ if (args.mode === "incremental") {
177
+ const lock = tryAcquireWikiCompileLock(workspace.name, ctx.event.correlationId);
178
+ if (!lock.ok) {
179
+ await ctx.notify(renderCompileInProgressDm(lock.holder, workspace.name));
180
+ return;
181
+ }
182
+ const queued = await enqueueOrNotify(ctx, createWikiCommandEvent({
183
+ processKey: "wiki.compile",
184
+ workspace: workspace.name,
185
+ sourceEvent: ctx.event,
186
+ data: { mode: "incremental" },
187
+ }));
188
+ if (queued) {
189
+ await ctx.notify(`Queued incremental wiki compile for workspace \`${workspace.name}\`.`);
190
+ }
191
+ else {
192
+ // Enqueue path declined (no `enqueueWikiEvent` wired) — release
193
+ // the lock immediately so the next attempt can proceed.
194
+ releaseWikiCompileLock(workspace.name);
195
+ }
196
+ return;
197
+ }
198
+ // Full-rebuild path. §5.5 / §P2.E:
199
+ // 1. Preview the git pre-compile gate (no mutation). A dirty tree
200
+ // is the cheapest reason to reject; we surface it before any
201
+ // cost work.
202
+ // 2. Estimate cost (pure JS, no agent session).
203
+ // 3. Decide:
204
+ // - Above threshold → escalate to Approve tier. **No commit yet**
205
+ // — if the operator never approves, the git log stays clean.
206
+ // - Below threshold → run the actual `runGitPreCompile` mutator,
207
+ // then enqueue the autonomous compile session.
208
+ const gitPreview = await previewGitPreCompile(workspace);
209
+ if (gitPreview.status === "refused") {
210
+ const preview = gitPreview.dirtyPaths.slice(0, 5).map((p) => `\`${p}\``).join(", ");
211
+ await ctx.notify([
212
+ `Cannot run \`!compile full\` — the external vault has uncommitted changes.`,
213
+ `Please commit or stash first. Dirty paths: ${preview}${gitPreview.dirtyPaths.length > 5 ? ` (+${gitPreview.dirtyPaths.length - 5} more)` : ""}.`,
214
+ ].join("\n"));
215
+ return;
216
+ }
217
+ const estimate = estimateFullCompileCost(workspace);
218
+ const lines = [
219
+ `Full compile estimate for \`${workspace.name}\`:`,
220
+ `- raw notes: ${estimate.rawCount}`,
221
+ `- est. input tokens: ${estimate.estimatedInputTokens.toLocaleString()}`,
222
+ `- cost range: $${estimate.optimisticUsd.toFixed(2)} (optimistic) – $${estimate.pessimisticUsd.toFixed(2)} (pessimistic), expected $${estimate.expectedUsd.toFixed(2)}`,
223
+ `- approval threshold: $${estimate.thresholdUsd.toFixed(2)}`,
224
+ ];
225
+ if (gitPreview.status === "clean_would_commit") {
226
+ lines.push(`- pre-compile git snapshot: will commit before compile starts`);
227
+ }
228
+ else if (gitPreview.status === "skipped" && gitPreview.reason === "no_git_repo") {
229
+ lines.push(`- pre-compile git snapshot: not taken (no git repo)`);
230
+ }
231
+ else if (gitPreview.status === "skipped" && gitPreview.reason === "disabled") {
232
+ lines.push(`- pre-compile git snapshot: disabled by setting`);
233
+ }
234
+ else if (gitPreview.status === "skipped" && gitPreview.reason === "internal_mode") {
235
+ lines.push(`- pre-compile git snapshot: not applicable (internal mode uses md_file_snapshots)`);
236
+ }
237
+ if (estimate.exceedsThreshold) {
238
+ if (!ctx.enqueueWikiApproval) {
239
+ await ctx.notify([
240
+ ...lines,
241
+ "",
242
+ "This estimate exceeds the approval threshold. Approve from the dashboard `/settings/wiki` → Approvals queue.",
243
+ "(Approval handoff is not wired in this daemon process.)",
244
+ ].join("\n"));
245
+ return;
246
+ }
247
+ // Pass the preview (not a real commit) — the approval consumer
248
+ // re-runs `runGitPreCompile` when the operator approves so the
249
+ // snapshot is taken right before the compile session starts. This
250
+ // keeps the git log clean when the operator declines.
251
+ await ctx.enqueueWikiApproval({
252
+ workspace: workspace.name,
253
+ processKey: "wiki.compile",
254
+ sourceEvent: ctx.event,
255
+ estimate,
256
+ gitOutcome: gitPreview,
257
+ });
258
+ await ctx.notify([
259
+ ...lines,
260
+ "",
261
+ "Sent for approval. Open `/settings/wiki` → Approvals to confirm and the compile will start.",
262
+ ].join("\n"));
263
+ return;
264
+ }
265
+ // Below threshold — we WILL run the autonomous compile. Acquire the
266
+ // compile lock BEFORE committing the git snapshot so a racing
267
+ // `!compile` does not slip past us and double-commit. If the lock is
268
+ // already held, surface the in-progress holder and bail without
269
+ // touching git.
270
+ const lock = tryAcquireWikiCompileLock(workspace.name, ctx.event.correlationId);
271
+ if (!lock.ok) {
272
+ await ctx.notify(renderCompileInProgressDm(lock.holder, workspace.name));
273
+ return;
274
+ }
275
+ const gitOutcome = await runGitPreCompile(workspace);
276
+ if (gitOutcome.status === "refused") {
277
+ // Extremely narrow race: the tree turned dirty between preview and
278
+ // commit (e.g. a parallel `!ingest` writing to `10_raw/`). Surface and
279
+ // bail rather than silently committing whatever new mess landed.
280
+ releaseWikiCompileLock(workspace.name);
281
+ const preview = gitOutcome.dirtyPaths.slice(0, 5).map((p) => `\`${p}\``).join(", ");
282
+ await ctx.notify([
283
+ `Pre-compile git commit aborted — the working tree turned dirty between estimate and commit.`,
284
+ `Dirty paths: ${preview}${gitOutcome.dirtyPaths.length > 5 ? ` (+${gitOutcome.dirtyPaths.length - 5} more)` : ""}.`,
285
+ `Please commit/stash and rerun \`!compile full\`.`,
286
+ ].join("\n"));
287
+ return;
288
+ }
289
+ if (gitOutcome.status === "committed") {
290
+ lines.push(`- pre-compile git commit: ${gitOutcome.commitSha.slice(0, 7)}`);
291
+ }
292
+ const queued = await enqueueOrNotify(ctx, createWikiCommandEvent({
293
+ processKey: "wiki.compile",
294
+ workspace: workspace.name,
295
+ sourceEvent: ctx.event,
296
+ data: { mode: "full", estimate, git: gitOutcome },
297
+ }));
298
+ if (queued) {
299
+ await ctx.notify([
300
+ ...lines,
301
+ "",
302
+ "Below approval threshold — running autonomously.",
303
+ ].join("\n"));
304
+ }
305
+ else {
306
+ // Enqueue declined — release the lock so the next attempt can run.
307
+ releaseWikiCompileLock(workspace.name);
308
+ }
309
+ },
310
+ };
311
+ function renderCompileInProgressDm(holder, workspaceName) {
312
+ const minutesAgo = Math.max(0, Math.round((Date.now() - holder.startedAt.getTime()) / 60000));
313
+ const ago = minutesAgo === 0 ? "just now" : `${minutesAgo}m ago`;
314
+ const corr = holder.correlationId ? ` (correlation \`${holder.correlationId}\`)` : "";
315
+ return [
316
+ `A \`wiki.compile\` is already running for workspace \`${workspaceName}\` — started ${ago}${corr}.`,
317
+ `Queueing a second compile would land on a vault state already reflecting the first run's intent. Wait for the in-flight session to finish and re-run \`!compile\` afterwards.`,
318
+ ].join("\n");
319
+ }
320
+ export const askCommand = {
321
+ prefix: "!ask",
322
+ title: "Ask wiki",
323
+ describe: "ask a question against the wiki (`@<workspace>` optional)",
324
+ parseArgs(rest) {
325
+ const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
326
+ const question = payload.trim();
327
+ if (!question) {
328
+ throw new BangArgError("Usage: `!ask [@<workspace>] <question>`.");
329
+ }
330
+ return { question, workspaceName };
331
+ },
332
+ async handler(ctx, rawArgs) {
333
+ const args = rawArgs;
334
+ const workspace = await requireWikiWorkspace(ctx, args.workspaceName);
335
+ if (!workspace)
336
+ return;
337
+ const queued = await enqueueOrNotify(ctx, createWikiCommandEvent({
338
+ processKey: "wiki.ask",
339
+ workspace: workspace.name,
340
+ sourceEvent: ctx.event,
341
+ data: { question: args.question },
342
+ }));
343
+ if (queued) {
344
+ await ctx.notify(`Queued wiki answer for workspace \`${workspace.name}\`.`);
345
+ }
346
+ },
347
+ };
348
+ export const wikiStatusCommand = {
349
+ name: "!wiki",
350
+ title: "Wiki status",
351
+ describe: "show wiki workspace status (one line per active workspace)",
352
+ async handler(ctx) {
353
+ if (!isWikiEnabled(ctx.db)) {
354
+ await ctx.notify("Wiki is not enabled. Open `/settings/wiki` to enable the internal workspace.");
355
+ return;
356
+ }
357
+ const workspaces = listActiveWikiWorkspaces(ctx.db);
358
+ if (workspaces.length === 0) {
359
+ // Should not happen — isWikiEnabled said there is at least one
360
+ // active row — but defend against a race that archived it.
361
+ await ctx.notify("Wiki is enabled but no active workspace rows could be resolved.");
362
+ return;
363
+ }
364
+ if (workspaces.length === 1) {
365
+ const workspace = workspaces[0];
366
+ const stats = buildWikiWorkspaceStats(workspace);
367
+ await ctx.notify([
368
+ `Workspace: \`${workspace.name}\` (${workspace.kind})`,
369
+ `Root: \`${workspace.root_path}\``,
370
+ `Language: ${workspace.language}`,
371
+ `Dispatch: ${workspace.dispatch_mode}, concurrency ${workspace.concurrency_cap}`,
372
+ `Notes: ${stats.rawCount} raw, ${stats.wikiCount} wiki, ${stats.outputCount} outputs`,
373
+ ].join("\n"));
374
+ return;
375
+ }
376
+ // Multi-workspace: condense each row to a single line so the DM
377
+ // reply stays mobile-friendly. The owner can pivot to the dashboard
378
+ // for detail or `!wiki @<name>` for a focused view in a follow-up.
379
+ const defaultRow = readDefaultWikiWorkspace(ctx.db);
380
+ const lines = [`${workspaces.length} active wiki workspaces:`];
381
+ for (const workspace of workspaces) {
382
+ const stats = buildWikiWorkspaceStats(workspace);
383
+ const marker = defaultRow && defaultRow.id === workspace.id ? " (default)" : "";
384
+ lines.push(`- \`${workspace.name}\`${marker} — ${stats.rawCount}r/${stats.wikiCount}w/${stats.outputCount}o (${workspace.kind})`);
385
+ }
386
+ lines.push("");
387
+ lines.push("Target a non-default workspace with `@<name>` (e.g. `!ask @research <question>`).");
388
+ await ctx.notify(lines.join("\n"));
389
+ },
390
+ };
391
+ export const wikiHelpCommand = {
392
+ name: "!wiki help",
393
+ title: "Wiki help",
394
+ describe: "show wiki command help",
395
+ async handler(ctx) {
396
+ await ctx.notify([
397
+ "Wiki commands:",
398
+ "- `!ingest [@<workspace>] <url> [url...]` queues URL ingestion.",
399
+ "- `!compile [@<workspace>]` compiles pending raw notes incrementally; add `full` for a rebuild, `--preview` for a dry-run touch list.",
400
+ "- `!ask [@<workspace>] <question>` answers from the wiki.",
401
+ "- `!lint [@<workspace>]` audits the wiki and writes a dated health report.",
402
+ "- `!trace [@<workspace>] <topic>` traces how an idea has evolved across the wiki.",
403
+ "- `!connect [@<workspace>] <a> <b>` finds bridges between two domains.",
404
+ `- \`!wiki\` shows the ${DEFAULT_WIKI_WORKSPACE_NAME} workspace status (or every active workspace if you run more than one).`,
405
+ "",
406
+ "All commands target the default workspace when `@<workspace>` is omitted.",
407
+ ].join("\n"));
408
+ },
409
+ };
410
+ /**
411
+ * WIKI_BUILDER_DESIGN.md Phase 3 — `!lint` runs the wiki health audit
412
+ * (orphans, broken links, schema drift, taxonomy candidates) and writes
413
+ * `90_meta/health/<YYYY-MM-DD>.md`. No arguments. Exposed as a
414
+ * `BangPrefixCommand` rather than `BangCommand` so the surface can grow
415
+ * later (e.g. `!lint --since=2026-01-01`) without re-registering; today
416
+ * `parseArgs` rejects any trailing input.
417
+ */
418
+ export const lintCommand = {
419
+ prefix: "!lint",
420
+ title: "Lint wiki",
421
+ describe: "audit the wiki and write a dated health report (`@<workspace>` optional)",
422
+ parseArgs(rest) {
423
+ const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
424
+ if (payload.trim().length > 0) {
425
+ throw new BangArgError("Usage: `!lint [@<workspace>]` (no other arguments).");
426
+ }
427
+ return { workspaceName };
428
+ },
429
+ async handler(ctx, rawArgs) {
430
+ const args = rawArgs;
431
+ const workspace = await requireWikiWorkspace(ctx, args.workspaceName);
432
+ if (!workspace)
433
+ return;
434
+ const queued = await enqueueOrNotify(ctx, createWikiCommandEvent({
435
+ processKey: "wiki.lint",
436
+ workspace: workspace.name,
437
+ sourceEvent: ctx.event,
438
+ }));
439
+ if (queued) {
440
+ await ctx.notify(`Queued wiki lint for workspace \`${workspace.name}\`. The report will land at \`90_meta/health/<today>.md\` when complete.`);
441
+ }
442
+ },
443
+ };
444
+ /**
445
+ * WIKI_BUILDER_DESIGN.md Phase 3 — `!trace <topic>` reconstructs the
446
+ * chronological evolution of `<topic>` across the wiki's layers and
447
+ * writes one timeline output to
448
+ * `30_outputs/<YYYY-MM-DD>-trace-<slug>.md`.
449
+ *
450
+ * The topic is free-form prose. The skill canonicalises the topic against
451
+ * `90_meta/taxonomy.md` before deriving the output slug, so the bang
452
+ * handler does not validate the topic shape beyond "non-empty after trim".
453
+ */
454
+ export const traceCommand = {
455
+ prefix: "!trace",
456
+ title: "Trace wiki topic",
457
+ describe: "reconstruct an idea's evolution across raw / wiki / outputs (`@<workspace>` optional)",
458
+ parseArgs(rest) {
459
+ const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
460
+ const topic = payload.trim();
461
+ if (!topic) {
462
+ throw new BangArgError("Usage: `!trace [@<workspace>] <topic>`.");
463
+ }
464
+ return { topic, workspaceName };
465
+ },
466
+ async handler(ctx, rawArgs) {
467
+ const args = rawArgs;
468
+ const workspace = await requireWikiWorkspace(ctx, args.workspaceName);
469
+ if (!workspace)
470
+ return;
471
+ const queued = await enqueueOrNotify(ctx, createWikiCommandEvent({
472
+ processKey: "wiki.trace",
473
+ workspace: workspace.name,
474
+ sourceEvent: ctx.event,
475
+ data: { topic: args.topic },
476
+ }));
477
+ if (queued) {
478
+ await ctx.notify(`Queued wiki trace for \`${args.topic}\` in workspace \`${workspace.name}\`.`);
479
+ }
480
+ },
481
+ };
482
+ /**
483
+ * Pure helper exported for tests. Parses the rest-string for `!connect`
484
+ * into exactly two topic arguments.
485
+ *
486
+ * Tokenisation rule (WIKI_BUILDER_DESIGN.md §P3.B): the args are
487
+ * "whitespace- or comma-separated". A literal comma OR run of whitespace
488
+ * is the separator. This means `!connect quantum gravity`,
489
+ * `!connect quantum, gravity`, and `!connect "quantum, gravity"` (with
490
+ * the comma being part of a topic) all need a deterministic answer; we
491
+ * pick the simplest rule that matches the design verbatim and reject any
492
+ * input that yields anything other than two non-empty topics. The owner
493
+ * gets a clean usage message; they don't have to learn shell quoting.
494
+ */
495
+ export function parseConnectArgs(rest) {
496
+ const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
497
+ const trimmed = payload.trim();
498
+ if (!trimmed) {
499
+ throw new BangArgError("Usage: `!connect [@<workspace>] <a> <b>`.");
500
+ }
501
+ // Split on commas first (explicit boundary), then on whitespace. The
502
+ // explicit comma path lets the operator use multi-word topics:
503
+ // `!connect quantum computing, classical computing` becomes
504
+ // ["quantum computing", "classical computing"].
505
+ const parts = trimmed.includes(",")
506
+ ? trimmed.split(",").map((p) => p.trim()).filter(Boolean)
507
+ : trimmed.split(/\s+/u).filter(Boolean);
508
+ if (parts.length !== 2) {
509
+ throw new BangArgError("Usage: `!connect [@<workspace>] <a> <b>` — exactly two topics, separated by a comma or whitespace.");
510
+ }
511
+ return { topicA: parts[0], topicB: parts[1], workspaceName };
512
+ }
513
+ /**
514
+ * WIKI_BUILDER_DESIGN.md Phase 3 — `!connect <a> <b>` bridges two
515
+ * domains. Output lands at
516
+ * `30_outputs/<YYYY-MM-DD>-connect-<slug-a>--<slug-b>.md`.
517
+ *
518
+ * Exactly two args required. See {@link parseConnectArgs}.
519
+ */
520
+ export const connectCommand = {
521
+ prefix: "!connect",
522
+ title: "Connect wiki domains",
523
+ describe: "bridge two domains in the wiki (`@<workspace>` optional)",
524
+ parseArgs(rest) {
525
+ return parseConnectArgs(rest);
526
+ },
527
+ async handler(ctx, rawArgs) {
528
+ const args = rawArgs;
529
+ const workspace = await requireWikiWorkspace(ctx, args.workspaceName);
530
+ if (!workspace)
531
+ return;
532
+ const queued = await enqueueOrNotify(ctx, createWikiCommandEvent({
533
+ processKey: "wiki.connect",
534
+ workspace: workspace.name,
535
+ sourceEvent: ctx.event,
536
+ data: { topic_a: args.topicA, topic_b: args.topicB },
537
+ }));
538
+ if (queued) {
539
+ await ctx.notify(`Queued wiki connect between \`${args.topicA}\` and \`${args.topicB}\` in workspace \`${workspace.name}\`.`);
540
+ }
541
+ },
542
+ };
543
+ /**
544
+ * WIKI_BUILDER_DESIGN.md §P4.B — render a compile preview into the DM
545
+ * reply shape. Truncates the added/modified/unchanged lists at 8 entries
546
+ * so the mobile reply stays scrollable; the dashboard's
547
+ * `/wiki/compile/preview` route surfaces the full lists. Exported for
548
+ * tests.
549
+ */
550
+ export function formatCompilePreview(preview) {
551
+ const lines = [];
552
+ lines.push(`Compile preview for \`${preview.workspace}\` (${preview.mode}):`);
553
+ lines.push(`- ${preview.added.length} added, ${preview.modified.length} modified, ${preview.unchanged.length} unchanged`);
554
+ if (preview.added.length > 0) {
555
+ lines.push(`- add: ${preview.added.slice(0, 8).map((p) => `\`${p}\``).join(", ")}${preview.added.length > 8 ? ` (+${preview.added.length - 8} more)` : ""}`);
556
+ }
557
+ if (preview.modified.length > 0) {
558
+ lines.push(`- modify: ${preview.modified.slice(0, 8).map((p) => `\`${p}\``).join(", ")}${preview.modified.length > 8 ? ` (+${preview.modified.length - 8} more)` : ""}`);
559
+ }
560
+ lines.push(`- est. cost: $${preview.estimate.optimisticUsd.toFixed(2)}–$${preview.estimate.pessimisticUsd.toFixed(2)} (expected $${preview.estimate.expectedUsd.toFixed(2)})`);
561
+ lines.push(`- est. duration: ${formatDuration(preview.estimatedDurationSeconds)}`);
562
+ lines.push(`No agent session ran. Reply \`!compile${preview.mode === "full" ? " full" : ""}\` to start the compile.`);
563
+ return lines.join("\n");
564
+ }
565
+ function formatDuration(seconds) {
566
+ if (seconds <= 0)
567
+ return "< 1s";
568
+ if (seconds < 60)
569
+ return `${seconds}s`;
570
+ const m = Math.floor(seconds / 60);
571
+ const s = seconds % 60;
572
+ return s === 0 ? `${m}m` : `${m}m ${s}s`;
573
+ }
574
+ //# sourceMappingURL=commands-wiki.js.map