@ambitresearch/genie 1.3.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 (301) hide show
  1. package/README.md +15 -0
  2. package/dist/auth/bearer.d.ts +49 -0
  3. package/dist/auth/bearer.d.ts.map +1 -0
  4. package/dist/auth/bearer.js +159 -0
  5. package/dist/auth/bearer.js.map +1 -0
  6. package/dist/auth/oauth/authorize.d.ts +19 -0
  7. package/dist/auth/oauth/authorize.d.ts.map +1 -0
  8. package/dist/auth/oauth/authorize.js +111 -0
  9. package/dist/auth/oauth/authorize.js.map +1 -0
  10. package/dist/auth/oauth/config.d.ts +10 -0
  11. package/dist/auth/oauth/config.d.ts.map +1 -0
  12. package/dist/auth/oauth/config.js +16 -0
  13. package/dist/auth/oauth/config.js.map +1 -0
  14. package/dist/auth/oauth/dcr.d.ts +15 -0
  15. package/dist/auth/oauth/dcr.d.ts.map +1 -0
  16. package/dist/auth/oauth/dcr.js +38 -0
  17. package/dist/auth/oauth/dcr.js.map +1 -0
  18. package/dist/auth/oauth/jwt.d.ts +13 -0
  19. package/dist/auth/oauth/jwt.d.ts.map +1 -0
  20. package/dist/auth/oauth/jwt.js +46 -0
  21. package/dist/auth/oauth/jwt.js.map +1 -0
  22. package/dist/auth/oauth/metadata.d.ts +9 -0
  23. package/dist/auth/oauth/metadata.d.ts.map +1 -0
  24. package/dist/auth/oauth/metadata.js +19 -0
  25. package/dist/auth/oauth/metadata.js.map +1 -0
  26. package/dist/auth/oauth/router.d.ts +32 -0
  27. package/dist/auth/oauth/router.d.ts.map +1 -0
  28. package/dist/auth/oauth/router.js +159 -0
  29. package/dist/auth/oauth/router.js.map +1 -0
  30. package/dist/auth/oauth/store.d.ts +55 -0
  31. package/dist/auth/oauth/store.d.ts.map +1 -0
  32. package/dist/auth/oauth/store.js +63 -0
  33. package/dist/auth/oauth/store.js.map +1 -0
  34. package/dist/auth/oauth/token.d.ts +21 -0
  35. package/dist/auth/oauth/token.d.ts.map +1 -0
  36. package/dist/auth/oauth/token.js +77 -0
  37. package/dist/auth/oauth/token.js.map +1 -0
  38. package/dist/auth/oidc/group-policy.d.ts +43 -0
  39. package/dist/auth/oidc/group-policy.d.ts.map +1 -0
  40. package/dist/auth/oidc/group-policy.js +54 -0
  41. package/dist/auth/oidc/group-policy.js.map +1 -0
  42. package/dist/auth/oidc/verifier.d.ts +42 -0
  43. package/dist/auth/oidc/verifier.d.ts.map +1 -0
  44. package/dist/auth/oidc/verifier.js +145 -0
  45. package/dist/auth/oidc/verifier.js.map +1 -0
  46. package/dist/auth/token-cli.d.ts +9 -0
  47. package/dist/auth/token-cli.d.ts.map +1 -0
  48. package/dist/auth/token-cli.js +98 -0
  49. package/dist/auth/token-cli.js.map +1 -0
  50. package/dist/cli.d.ts +3 -0
  51. package/dist/cli.d.ts.map +1 -0
  52. package/dist/cli.js +149 -0
  53. package/dist/cli.js.map +1 -0
  54. package/dist/config/redact.d.ts +20 -0
  55. package/dist/config/redact.d.ts.map +1 -0
  56. package/dist/config/redact.js +77 -0
  57. package/dist/config/redact.js.map +1 -0
  58. package/dist/config/secrets.d.ts +100 -0
  59. package/dist/config/secrets.d.ts.map +1 -0
  60. package/dist/config/secrets.js +130 -0
  61. package/dist/config/secrets.js.map +1 -0
  62. package/dist/framework/html.d.ts +103 -0
  63. package/dist/framework/html.d.ts.map +1 -0
  64. package/dist/framework/html.js +271 -0
  65. package/dist/framework/html.js.map +1 -0
  66. package/dist/framework/interface.d.ts +130 -0
  67. package/dist/framework/interface.d.ts.map +1 -0
  68. package/dist/framework/interface.js +112 -0
  69. package/dist/framework/interface.js.map +1 -0
  70. package/dist/framework/react.d.ts +133 -0
  71. package/dist/framework/react.d.ts.map +1 -0
  72. package/dist/framework/react.js +243 -0
  73. package/dist/framework/react.js.map +1 -0
  74. package/dist/framework/vue.d.ts +99 -0
  75. package/dist/framework/vue.d.ts.map +1 -0
  76. package/dist/framework/vue.js +375 -0
  77. package/dist/framework/vue.js.map +1 -0
  78. package/dist/index.d.ts +3 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +3 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/llm/client.d.ts +81 -0
  83. package/dist/llm/client.d.ts.map +1 -0
  84. package/dist/llm/client.js +141 -0
  85. package/dist/llm/client.js.map +1 -0
  86. package/dist/llm/component-response.d.ts +111 -0
  87. package/dist/llm/component-response.d.ts.map +1 -0
  88. package/dist/llm/component-response.js +167 -0
  89. package/dist/llm/component-response.js.map +1 -0
  90. package/dist/llm/generated-files.d.ts +10 -0
  91. package/dist/llm/generated-files.d.ts.map +1 -0
  92. package/dist/llm/generated-files.js +26 -0
  93. package/dist/llm/generated-files.js.map +1 -0
  94. package/dist/llm/prompts/CHANGELOG.md +70 -0
  95. package/dist/llm/prompts/generate-component.system.md +99 -0
  96. package/dist/llm/prompts/refine-component.system.md +112 -0
  97. package/dist/llm/prompts.d.ts +66 -0
  98. package/dist/llm/prompts.d.ts.map +1 -0
  99. package/dist/llm/prompts.js +78 -0
  100. package/dist/llm/prompts.js.map +1 -0
  101. package/dist/llm/retry.d.ts +183 -0
  102. package/dist/llm/retry.d.ts.map +1 -0
  103. package/dist/llm/retry.js +475 -0
  104. package/dist/llm/retry.js.map +1 -0
  105. package/dist/llm/schema.d.ts +129 -0
  106. package/dist/llm/schema.d.ts.map +1 -0
  107. package/dist/llm/schema.js +147 -0
  108. package/dist/llm/schema.js.map +1 -0
  109. package/dist/llm/validate.d.ts +107 -0
  110. package/dist/llm/validate.d.ts.map +1 -0
  111. package/dist/llm/validate.js +174 -0
  112. package/dist/llm/validate.js.map +1 -0
  113. package/dist/manifest/compiler.d.ts +112 -0
  114. package/dist/manifest/compiler.d.ts.map +1 -0
  115. package/dist/manifest/compiler.js +306 -0
  116. package/dist/manifest/compiler.js.map +1 -0
  117. package/dist/manifest/ensure.d.ts +46 -0
  118. package/dist/manifest/ensure.d.ts.map +1 -0
  119. package/dist/manifest/ensure.js +49 -0
  120. package/dist/manifest/ensure.js.map +1 -0
  121. package/dist/manifest/index.d.ts +13 -0
  122. package/dist/manifest/index.d.ts.map +1 -0
  123. package/dist/manifest/index.js +13 -0
  124. package/dist/manifest/index.js.map +1 -0
  125. package/dist/middleware/plan-guard.d.ts +167 -0
  126. package/dist/middleware/plan-guard.d.ts.map +1 -0
  127. package/dist/middleware/plan-guard.js +190 -0
  128. package/dist/middleware/plan-guard.js.map +1 -0
  129. package/dist/plans/index.d.ts +71 -0
  130. package/dist/plans/index.d.ts.map +1 -0
  131. package/dist/plans/index.js +252 -0
  132. package/dist/plans/index.js.map +1 -0
  133. package/dist/schemas/component.schema.json +114 -0
  134. package/dist/server.d.ts +96 -0
  135. package/dist/server.d.ts.map +1 -0
  136. package/dist/server.js +258 -0
  137. package/dist/server.js.map +1 -0
  138. package/dist/store/empty-manifest.d.ts +58 -0
  139. package/dist/store/empty-manifest.d.ts.map +1 -0
  140. package/dist/store/empty-manifest.js +59 -0
  141. package/dist/store/empty-manifest.js.map +1 -0
  142. package/dist/store/git-host.d.ts +120 -0
  143. package/dist/store/git-host.d.ts.map +1 -0
  144. package/dist/store/git-host.js +796 -0
  145. package/dist/store/git-host.js.map +1 -0
  146. package/dist/store/index.d.ts +5 -0
  147. package/dist/store/index.d.ts.map +1 -0
  148. package/dist/store/index.js +4 -0
  149. package/dist/store/index.js.map +1 -0
  150. package/dist/store/interface.d.ts +322 -0
  151. package/dist/store/interface.d.ts.map +1 -0
  152. package/dist/store/interface.js +100 -0
  153. package/dist/store/interface.js.map +1 -0
  154. package/dist/store/kit-files.d.ts +88 -0
  155. package/dist/store/kit-files.d.ts.map +1 -0
  156. package/dist/store/kit-files.js +199 -0
  157. package/dist/store/kit-files.js.map +1 -0
  158. package/dist/store/local.d.ts +70 -0
  159. package/dist/store/local.d.ts.map +1 -0
  160. package/dist/store/local.js +651 -0
  161. package/dist/store/local.js.map +1 -0
  162. package/dist/store/manifest.d.ts +44 -0
  163. package/dist/store/manifest.d.ts.map +1 -0
  164. package/dist/store/manifest.js +116 -0
  165. package/dist/store/manifest.js.map +1 -0
  166. package/dist/store/viewer-assets.d.ts +20 -0
  167. package/dist/store/viewer-assets.d.ts.map +1 -0
  168. package/dist/store/viewer-assets.js +118 -0
  169. package/dist/store/viewer-assets.js.map +1 -0
  170. package/dist/sync/anchor.d.ts +125 -0
  171. package/dist/sync/anchor.d.ts.map +1 -0
  172. package/dist/sync/anchor.js +210 -0
  173. package/dist/sync/anchor.js.map +1 -0
  174. package/dist/sync/index.d.ts +15 -0
  175. package/dist/sync/index.d.ts.map +1 -0
  176. package/dist/sync/index.js +15 -0
  177. package/dist/sync/index.js.map +1 -0
  178. package/dist/sync/orchestrator.d.ts +160 -0
  179. package/dist/sync/orchestrator.d.ts.map +1 -0
  180. package/dist/sync/orchestrator.js +288 -0
  181. package/dist/sync/orchestrator.js.map +1 -0
  182. package/dist/tools/bind_kit.d.ts +29 -0
  183. package/dist/tools/bind_kit.d.ts.map +1 -0
  184. package/dist/tools/bind_kit.js +70 -0
  185. package/dist/tools/bind_kit.js.map +1 -0
  186. package/dist/tools/conjure.d.ts +141 -0
  187. package/dist/tools/conjure.d.ts.map +1 -0
  188. package/dist/tools/conjure.js +655 -0
  189. package/dist/tools/conjure.js.map +1 -0
  190. package/dist/tools/conjure_screen.d.ts +174 -0
  191. package/dist/tools/conjure_screen.d.ts.map +1 -0
  192. package/dist/tools/conjure_screen.js +458 -0
  193. package/dist/tools/conjure_screen.js.map +1 -0
  194. package/dist/tools/create_kit.d.ts +32 -0
  195. package/dist/tools/create_kit.d.ts.map +1 -0
  196. package/dist/tools/create_kit.js +131 -0
  197. package/dist/tools/create_kit.js.map +1 -0
  198. package/dist/tools/create_project.d.ts +267 -0
  199. package/dist/tools/create_project.d.ts.map +1 -0
  200. package/dist/tools/create_project.js +546 -0
  201. package/dist/tools/create_project.js.map +1 -0
  202. package/dist/tools/delete_files.d.ts +93 -0
  203. package/dist/tools/delete_files.d.ts.map +1 -0
  204. package/dist/tools/delete_files.js +276 -0
  205. package/dist/tools/delete_files.js.map +1 -0
  206. package/dist/tools/delete_project.d.ts +43 -0
  207. package/dist/tools/delete_project.d.ts.map +1 -0
  208. package/dist/tools/delete_project.js +118 -0
  209. package/dist/tools/delete_project.js.map +1 -0
  210. package/dist/tools/get_kit.d.ts +28 -0
  211. package/dist/tools/get_kit.d.ts.map +1 -0
  212. package/dist/tools/get_kit.js +95 -0
  213. package/dist/tools/get_kit.js.map +1 -0
  214. package/dist/tools/get_project.d.ts +37 -0
  215. package/dist/tools/get_project.d.ts.map +1 -0
  216. package/dist/tools/get_project.js +71 -0
  217. package/dist/tools/get_project.js.map +1 -0
  218. package/dist/tools/list_components.d.ts +42 -0
  219. package/dist/tools/list_components.d.ts.map +1 -0
  220. package/dist/tools/list_components.js +125 -0
  221. package/dist/tools/list_components.js.map +1 -0
  222. package/dist/tools/list_files.d.ts +32 -0
  223. package/dist/tools/list_files.d.ts.map +1 -0
  224. package/dist/tools/list_files.js +109 -0
  225. package/dist/tools/list_files.js.map +1 -0
  226. package/dist/tools/list_kits.d.ts +14 -0
  227. package/dist/tools/list_kits.d.ts.map +1 -0
  228. package/dist/tools/list_kits.js +41 -0
  229. package/dist/tools/list_kits.js.map +1 -0
  230. package/dist/tools/list_projects.d.ts +23 -0
  231. package/dist/tools/list_projects.d.ts.map +1 -0
  232. package/dist/tools/list_projects.js +66 -0
  233. package/dist/tools/list_projects.js.map +1 -0
  234. package/dist/tools/plan.d.ts +16 -0
  235. package/dist/tools/plan.d.ts.map +1 -0
  236. package/dist/tools/plan.js +146 -0
  237. package/dist/tools/plan.js.map +1 -0
  238. package/dist/tools/preview.d.ts +289 -0
  239. package/dist/tools/preview.d.ts.map +1 -0
  240. package/dist/tools/preview.js +672 -0
  241. package/dist/tools/preview.js.map +1 -0
  242. package/dist/tools/read_file.d.ts +40 -0
  243. package/dist/tools/read_file.d.ts.map +1 -0
  244. package/dist/tools/read_file.js +114 -0
  245. package/dist/tools/read_file.js.map +1 -0
  246. package/dist/tools/refine.d.ts +220 -0
  247. package/dist/tools/refine.d.ts.map +1 -0
  248. package/dist/tools/refine.js +501 -0
  249. package/dist/tools/refine.js.map +1 -0
  250. package/dist/tools/validate.d.ts +74 -0
  251. package/dist/tools/validate.d.ts.map +1 -0
  252. package/dist/tools/validate.js +224 -0
  253. package/dist/tools/validate.js.map +1 -0
  254. package/dist/tools/write_files.d.ts +158 -0
  255. package/dist/tools/write_files.d.ts.map +1 -0
  256. package/dist/tools/write_files.js +541 -0
  257. package/dist/tools/write_files.js.map +1 -0
  258. package/dist/transport.d.ts +77 -0
  259. package/dist/transport.d.ts.map +1 -0
  260. package/dist/transport.js +448 -0
  261. package/dist/transport.js.map +1 -0
  262. package/dist/ui/card-asset-broker.d.ts +42 -0
  263. package/dist/ui/card-asset-broker.d.ts.map +1 -0
  264. package/dist/ui/card-asset-broker.js +479 -0
  265. package/dist/ui/card-asset-broker.js.map +1 -0
  266. package/dist/ui/grid-resource.d.ts +252 -0
  267. package/dist/ui/grid-resource.d.ts.map +1 -0
  268. package/dist/ui/grid-resource.js +680 -0
  269. package/dist/ui/grid-resource.js.map +1 -0
  270. package/dist/ui/viewer-static/index.html +95 -0
  271. package/dist/ui/viewer-static/viewer.css +451 -0
  272. package/dist/ui/viewer-static/viewer.js +1405 -0
  273. package/dist/validate/full-scan.d.ts +87 -0
  274. package/dist/validate/full-scan.d.ts.map +1 -0
  275. package/dist/validate/full-scan.js +299 -0
  276. package/dist/validate/full-scan.js.map +1 -0
  277. package/dist/validate/index.d.ts +15 -0
  278. package/dist/validate/index.d.ts.map +1 -0
  279. package/dist/validate/index.js +18 -0
  280. package/dist/validate/index.js.map +1 -0
  281. package/dist/validate/marker.d.ts +89 -0
  282. package/dist/validate/marker.d.ts.map +1 -0
  283. package/dist/validate/marker.js +87 -0
  284. package/dist/validate/marker.js.map +1 -0
  285. package/dist/validate/phash.d.ts +135 -0
  286. package/dist/validate/phash.d.ts.map +1 -0
  287. package/dist/validate/phash.js +307 -0
  288. package/dist/validate/phash.js.map +1 -0
  289. package/dist/validate/render.d.ts +38 -0
  290. package/dist/validate/render.d.ts.map +1 -0
  291. package/dist/validate/render.js +155 -0
  292. package/dist/validate/render.js.map +1 -0
  293. package/dist/watch/index.d.ts +12 -0
  294. package/dist/watch/index.d.ts.map +1 -0
  295. package/dist/watch/index.js +12 -0
  296. package/dist/watch/index.js.map +1 -0
  297. package/dist/watch/watcher.d.ts +65 -0
  298. package/dist/watch/watcher.d.ts.map +1 -0
  299. package/dist/watch/watcher.js +238 -0
  300. package/dist/watch/watcher.js.map +1 -0
  301. package/package.json +70 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * M3-03 (DRO-259) — the `.genie/manifest.json` writer (client-side compiler).
3
+ *
4
+ * This is genie's substitute for Anthropic's server-side manifest
5
+ * compilation (research report §6): a small, dependency-light walker that
6
+ * recompiles `<projectRoot>/.genie/manifest.json` from the `@genie` markers
7
+ * on disk whenever the M3-02 watcher fires (or on any direct call — this
8
+ * module has no opinion on WHEN it runs, only on producing a correct
9
+ * manifest for a given `projectRoot` snapshot).
10
+ *
11
+ * ── Schema reconciliation (recorded on DRO-259 before this file was written) ──
12
+ * This issue's own AC4 (and the RFC §6.8/§7.1 sketch, and the M4-03 viewer's
13
+ * `fetch` code) describe a root key `cards` with `viewport: {width,height}`
14
+ * as an object. But `../store/manifest.ts` — already merged, backing the
15
+ * shipped/tested M1-15 `list_components` tool, read by BOTH `LocalFsKitStore`
16
+ * and `GitHostKitStore` — has a zod schema that requires the root key
17
+ * `components`, with `viewport` as the raw marker STRING (e.g. `"400x200"`
18
+ * or `"desktop"`), plus `{name, group, path, hash, lastModified}`.
19
+ *
20
+ * Emitting `cards` would silently break `list_components` (a live, shipped
21
+ * P0 tool): its zod parse requires `components` and would throw
22
+ * `ManifestParseError` on a `cards`-shaped file. So this compiler emits
23
+ * `components` with a STRING `viewport`, matching the already-shipped reader
24
+ * contract. `../store/manifest.ts`'s schemas are `.passthrough()`, so the
25
+ * RFC's additional fields are layered on top without conflict: top-level
26
+ * `version: 1`, `generatedAt`, `name` (the kit/project's directory basename),
27
+ * `groups: string[]`; per-card `subtitle`/`tags` when a sibling `meta.json`
28
+ * supplies them. This is additive/forward-compatible per RFC §16.2 ("add a
29
+ * new output field — allowed in minor/patch").
30
+ *
31
+ * Pure-ish orchestration: the only side effects are reading the component
32
+ * tree + sibling `meta.json`/`_groups.json` files and the final atomic write.
33
+ * Marker PRESENCE validation and card sort order are delegated to
34
+ * already-shipped, independently-tested modules (M3-01's `validateMarker`,
35
+ * `store/manifest.ts`'s `compareComponents`); group/viewport extraction is
36
+ * a narrow regex read of the same already-validated marker line (see
37
+ * `extractGroup`/`extractViewportToken` below for why M3-01's own
38
+ * `extractViewport` isn't reused verbatim here).
39
+ */
40
+ import { mkdir, mkdtemp, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
41
+ import { basename, dirname, join, relative } from "node:path";
42
+ import { validateMarker } from "../validate/marker.js";
43
+ import { sriSha256 } from "../store/kit-files.js";
44
+ import { compareComponents, MANIFEST_PATH } from "../store/manifest.js";
45
+ /** Loose runtime guard — a `meta.json` is optional and non-renderable, so a
46
+ * malformed one degrades to "no extra metadata" rather than failing the
47
+ * whole compile (this module owns availability of the manifest; a single
48
+ * bad `meta.json` must not take that down). */
49
+ function parseComponentMeta(raw) {
50
+ try {
51
+ const json = JSON.parse(raw);
52
+ if (typeof json !== "object" || json === null)
53
+ return {};
54
+ const obj = json;
55
+ const subtitle = typeof obj["subtitle"] === "string" ? obj["subtitle"] : undefined;
56
+ const tags = Array.isArray(obj["tags"]) && obj["tags"].every((t) => typeof t === "string")
57
+ ? obj["tags"]
58
+ : undefined;
59
+ return { subtitle, tags };
60
+ }
61
+ catch {
62
+ return {};
63
+ }
64
+ }
65
+ /** Loose parse of an optional `_groups.json` sibling: an explicit group order
66
+ * (Impl Notes: "Group order: alphabetical unless a `_groups.json` sibling
67
+ * pins it"). Any shape other than a flat string array is treated as absent —
68
+ * this is a purely cosmetic ordering hint, never a hard requirement. */
69
+ function parseGroupsOrder(raw) {
70
+ try {
71
+ const json = JSON.parse(raw);
72
+ if (Array.isArray(json) && json.every((g) => typeof g === "string")) {
73
+ return json;
74
+ }
75
+ return undefined;
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ /** ENOENT/ENOTDIR both mean "not there" — the same convention `store/local.ts`
82
+ * and `sync/anchor.ts` use throughout this codebase. */
83
+ function isMissingPathError(error) {
84
+ return (error instanceof Error &&
85
+ "code" in error &&
86
+ error.code !== undefined &&
87
+ (error.code === "ENOENT" ||
88
+ error.code === "ENOTDIR"));
89
+ }
90
+ /**
91
+ * Recursively walk `${projectRoot}/components` for `*.html` files (AC2).
92
+ * Returns `[]` when the `components/` directory doesn't exist yet (a brand
93
+ * new kit/project) rather than throwing — an empty manifest is a valid,
94
+ * expected state (mirrors `store/manifest.ts`'s `selectComponents` treating
95
+ * an absent manifest as `[]`, not an error).
96
+ */
97
+ async function walkPreviewFiles(componentsRoot, projectRoot) {
98
+ let entries;
99
+ try {
100
+ entries = await readdir(componentsRoot, { withFileTypes: true });
101
+ }
102
+ catch (error) {
103
+ if (isMissingPathError(error))
104
+ return [];
105
+ throw error;
106
+ }
107
+ const out = [];
108
+ for (const entry of entries) {
109
+ const absPath = join(componentsRoot, entry.name);
110
+ if (entry.isDirectory()) {
111
+ out.push(...(await walkPreviewFiles(absPath, projectRoot)));
112
+ continue;
113
+ }
114
+ if (!entry.isFile() || !entry.name.endsWith(".html"))
115
+ continue;
116
+ out.push({ absPath, relPath: relative(projectRoot, absPath).replaceAll("\\", "/") });
117
+ }
118
+ return out;
119
+ }
120
+ /** Read a sibling file next to `htmlAbsPath` (same directory, given basename).
121
+ * Returns `undefined` on any read failure (missing, EISDIR, etc.) — every
122
+ * sibling here is optional metadata, never load-bearing for the card itself. */
123
+ async function readSibling(htmlAbsPath, siblingName) {
124
+ return readOptionalFile(join(dirname(htmlAbsPath), siblingName));
125
+ }
126
+ /** Read a file at an exact absolute path, tolerating absence (or any other
127
+ * read failure) as `undefined` rather than throwing — used for every
128
+ * optional, best-effort sidecar this compiler reads (`meta.json`,
129
+ * `_groups.json`), none of which are load-bearing for the manifest itself. */
130
+ async function readOptionalFile(absPath) {
131
+ try {
132
+ return await readFile(absPath, "utf-8");
133
+ }
134
+ catch {
135
+ return undefined;
136
+ }
137
+ }
138
+ /**
139
+ * Extract a card's `group` from the validated `@genie` marker line itself
140
+ * (AC2: "extracts group + viewport via the M3-01 helper") rather than from
141
+ * the directory path — the marker is genie's "registration contract" (M3-01's
142
+ * own module doc), so it is the authoritative source, and reading it directly
143
+ * also cards a preview file correctly even if a kit doesn't follow the
144
+ * `components/<group>/<Name>/<Name>.html` two-level convention `../store/
145
+ * manifest.ts`'s consumers otherwise assume. Safe to assume a match here:
146
+ * every card reaching this function already passed `validateMarker` against
147
+ * `MARKER_REGEX`, whose own pattern requires a `group="[^"]*"` capture.
148
+ */
149
+ function extractGroup(firstLine) {
150
+ return /^<!--\s*@genie\s+group="([^"]*)"/.exec(firstLine)?.[1] ?? "";
151
+ }
152
+ /**
153
+ * Extract a card's `viewport` token verbatim from the marker line (AC2/AC5).
154
+ * `../validate/marker.ts`'s `extractViewport` is deliberately NOT used here:
155
+ * it decomposes a strict `WxH` token into `{width,height}` integers for
156
+ * consumers doing arithmetic on the value, and returns `undefined` for
157
+ * anything else (including a named token like `"desktop"`) — exactly the
158
+ * case `extractViewport`'s own doc comment calls out as "kept as an opaque
159
+ * string" by "list_components/the manifest compiler". Re-serializing the
160
+ * parsed integers back through `${width}x${height}` would also risk silently
161
+ * normalizing a literal marker string (e.g. dropping a leading zero), which
162
+ * this compiler must not do — its job is to mirror the marker byte-for-byte,
163
+ * not reformat it. Returns `""` when the marker has no `viewport` attribute
164
+ * at all (`../store/manifest.ts`'s `viewport` is a plain `z.string()`, so an
165
+ * absent viewport still needs SOME string, and `""` is the same "no value"
166
+ * signal `store/manifest.ts`'s consumers already treat any falsy string as).
167
+ */
168
+ function extractViewportToken(firstLine) {
169
+ return /(?:^|\s)viewport="([^"]*)"/.exec(firstLine)?.[1] ?? "";
170
+ }
171
+ /**
172
+ * Derive a card's `name` from its filename (`<Name>.html` → `<Name>`) — the
173
+ * on-disk convention every M3-03-adjacent module (RFC §7.1, `conjure.ts`'s
174
+ * system prompt) assumes.
175
+ */
176
+ function deriveName(relPath) {
177
+ const file = basename(relPath);
178
+ return file.endsWith(".html") ? file.slice(0, -".html".length) : file;
179
+ }
180
+ // ─── compileManifest (AC1–AC7) ───────────────────────────────────────────────
181
+ /**
182
+ * Recompile the manifest for `projectRoot` (AC1) purely from what's on disk
183
+ * right now — no incremental state, no cache. Safe to call as often as the
184
+ * watcher likes; a debounce (M3-02's own 100 ms window) is the caller's
185
+ * concern, not this function's.
186
+ *
187
+ * Steps (AC2/AC3):
188
+ * 1. Walk `${projectRoot}/components/**\/*.html`.
189
+ * 2. For each, validate the `@genie` first line (M3-01). A file that fails
190
+ * is SKIPPED (not carded) and reported in `skipped` — mirrors the RFC
191
+ * §6.8 failure mode ("the watcher silently skips it; validate surfaces
192
+ * the omission").
193
+ * 3. Extract `viewport` from the marker line; join a sibling `meta.json`
194
+ * for `subtitle`/`tags` when present (AC3).
195
+ * 4. Hash the HTML file's bytes as `sha256-<base64>` SRI (AC5, same format
196
+ * `list_files`/`.genie/sync.json` use — `store/kit-files.ts`'s
197
+ * `sriSha256`).
198
+ * 5. Sort groups alphabetically unless a root `_groups.json` sibling pins an
199
+ * explicit order (Impl Notes); sort cards alphabetically by `name`
200
+ * within a group, ties broken by `path` (the shared `compareComponents`
201
+ * from `store/manifest.ts` — AC6-equivalent ordering used everywhere
202
+ * else a component list is sorted in this codebase).
203
+ * 6. Atomically write `.genie/manifest.json` (AC6): stage under
204
+ * `${projectRoot}/.genie-tmp/`, then rename over the destination — same
205
+ * same-filesystem-rename pattern `sync/anchor.ts` and `store/local.ts`
206
+ * already use, so a crash mid-write leaves the PRIOR manifest (or none)
207
+ * intact, never a half-written one.
208
+ */
209
+ export async function compileManifest(projectRoot) {
210
+ const componentsRoot = join(projectRoot, "components");
211
+ const previews = await walkPreviewFiles(componentsRoot, projectRoot);
212
+ const skipped = [];
213
+ const cards = [];
214
+ for (const { absPath, relPath } of previews) {
215
+ // Read the file's raw bytes ONCE: sriSha256 hashes these exact bytes
216
+ // (matching store/local.ts's own convention of hashing what was actually
217
+ // read, not a re-encoded string), and the same buffer is decoded to utf-8
218
+ // for marker/content parsing — avoiding a second disk read per file and
219
+ // any lossy-decode edge case a re-read could introduce.
220
+ const bytes = await readFile(absPath);
221
+ const content = bytes.toString("utf-8");
222
+ const validation = validateMarker(relPath, content);
223
+ if (!validation.ok) {
224
+ skipped.push({ path: relPath, reason: validation.code });
225
+ continue;
226
+ }
227
+ const firstLine = content.split("\n", 1)[0] ?? "";
228
+ const group = extractGroup(firstLine);
229
+ const viewportToken = extractViewportToken(firstLine);
230
+ const [stats, metaRaw] = await Promise.all([stat(absPath), readSibling(absPath, "meta.json")]);
231
+ const meta = metaRaw ? parseComponentMeta(metaRaw) : {};
232
+ cards.push({
233
+ name: deriveName(relPath),
234
+ group,
235
+ path: relPath,
236
+ viewport: viewportToken,
237
+ hash: sriSha256(bytes),
238
+ lastModified: stats.mtime.toISOString(),
239
+ ...(meta.subtitle !== undefined ? { subtitle: meta.subtitle } : {}),
240
+ ...(meta.tags !== undefined ? { tags: meta.tags } : {}),
241
+ });
242
+ }
243
+ cards.sort(compareComponents);
244
+ const pinnedRaw = await readOptionalFile(join(projectRoot, "_groups.json"));
245
+ const pinnedOrder = pinnedRaw ? parseGroupsOrder(pinnedRaw) : undefined;
246
+ const discoveredGroups = [...new Set(cards.map((c) => c.group))];
247
+ const groups = orderGroups(discoveredGroups, pinnedOrder);
248
+ const manifest = {
249
+ version: 1,
250
+ name: basename(projectRoot),
251
+ generatedAt: new Date().toISOString(),
252
+ groups,
253
+ components: cards,
254
+ };
255
+ await writeManifestAtomic(projectRoot, manifest);
256
+ return { manifest, skipped };
257
+ }
258
+ /**
259
+ * Alphabetical by default; when `pinnedOrder` is supplied (a parsed
260
+ * `_groups.json`), pinned groups come first in their given order, followed by
261
+ * any remaining discovered group NOT in the pin list, alphabetically — so an
262
+ * incomplete pin list never silently drops a group from the manifest.
263
+ */
264
+ function orderGroups(discovered, pinnedOrder) {
265
+ if (!pinnedOrder || pinnedOrder.length === 0) {
266
+ return [...discovered].sort(byCodeUnit);
267
+ }
268
+ const discoveredSet = new Set(discovered);
269
+ const pinned = pinnedOrder.filter((g) => discoveredSet.has(g));
270
+ const pinnedSet = new Set(pinned);
271
+ const remainder = discovered.filter((g) => !pinnedSet.has(g)).sort(byCodeUnit);
272
+ return [...pinned, ...remainder];
273
+ }
274
+ function byCodeUnit(a, b) {
275
+ if (a < b)
276
+ return -1;
277
+ if (a > b)
278
+ return 1;
279
+ return 0;
280
+ }
281
+ /**
282
+ * Atomic write (AC6): stage the serialized manifest under
283
+ * `${projectRoot}/.genie-tmp/` (same filesystem as the `.genie/` destination
284
+ * — `rename()` is only atomic within one filesystem, the same load-bearing
285
+ * reasoning `sync/anchor.ts`'s `writeAnchor` and `store/local.ts`'s
286
+ * `stageAndCommit` already rely on), then rename over
287
+ * `${projectRoot}/.genie/manifest.json`. `.genie-tmp` is already excluded
288
+ * from every kit listing (`store/kit-files.ts`'s `DEFAULT_IGNORED_SEGMENTS`),
289
+ * so a `list_files` call mid-compile never surfaces this staging directory.
290
+ */
291
+ async function writeManifestAtomic(projectRoot, manifest) {
292
+ const destPath = join(projectRoot, MANIFEST_PATH);
293
+ await mkdir(join(projectRoot, ".genie"), { recursive: true });
294
+ const genieTmpRoot = join(projectRoot, ".genie-tmp");
295
+ await mkdir(genieTmpRoot, { recursive: true });
296
+ const stagingRoot = await mkdtemp(join(genieTmpRoot, "manifest-"));
297
+ try {
298
+ const stagedPath = join(stagingRoot, "manifest.json");
299
+ await writeFile(stagedPath, JSON.stringify(manifest, null, 2), "utf-8");
300
+ await rename(stagedPath, destPath);
301
+ }
302
+ finally {
303
+ await rm(stagingRoot, { recursive: true, force: true });
304
+ }
305
+ }
306
+ //# sourceMappingURL=compiler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.js","sourceRoot":"","sources":["../../src/manifest/compiler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `ensureManifest` — the single entry point for "compile the kit's manifest and
3
+ * make sure `.genie/manifest.json` reflects it" (design: 2026-07-05 chat-
4
+ * invocation, piece A).
5
+ *
6
+ * ## Why this exists
7
+ *
8
+ * Manifest compilation ({@link compileManifest}) already writes
9
+ * `.genie/manifest.json` atomically as its final step. But historically the
10
+ * ONLY caller that triggered a compile at request time was the `ui://genie/grid`
11
+ * resource handler ({@link file://../ui/grid-resource.ts}). The `preview` tool
12
+ * booted the viewer WITHOUT compiling, so a caller that opened the Vite /
13
+ * `file://` vehicle — or an MCP Inspector that cannot read `ui://` resources at
14
+ * all — saw an EMPTY grid, and `list_components` (which reads the compiled
15
+ * manifest) returned `[]` until some unrelated `ui://` read happened to compile
16
+ * it. That order-dependence was the root of the "preview shows nothing" bug.
17
+ *
18
+ * Routing BOTH the `preview` tool and the grid resource through this one helper
19
+ * removes the divergence: there is exactly one way a manifest gets produced +
20
+ * persisted at request time, so the two surfaces can never drift on how (or
21
+ * whether) they compile.
22
+ *
23
+ * ## Contract
24
+ *
25
+ * Given a kit directory, compile it from what's on disk right now and return the
26
+ * resulting {@link Manifest}. Persistence of `.genie/manifest.json` is inherited
27
+ * from {@link compileManifest} (its atomic stage-then-rename write) — this
28
+ * wrapper adds no write of its own; it exists to (a) give callers the
29
+ * manifest-only shape they want without each re-destructuring `{ manifest }`,
30
+ * and (b) be the named seam both the tool and the resource depend on.
31
+ *
32
+ * The `skipped` diagnostics from a compile are intentionally dropped here — the
33
+ * two callers (`preview`, grid resource) only need the manifest to render;
34
+ * surfacing skipped/invalid components is `validate`'s job, not the view path's.
35
+ */
36
+ import { type Manifest } from "./compiler.js";
37
+ /**
38
+ * Compile `kitDir`'s manifest from disk and persist `.genie/manifest.json`
39
+ * (persistence via {@link compileManifest}). Returns the compiled manifest.
40
+ *
41
+ * Throws whatever {@link compileManifest} throws (e.g. an unreadable kit dir);
42
+ * callers that must degrade to an empty grid rather than error — like the grid
43
+ * resource — catch it themselves, preserving their existing failure semantics.
44
+ */
45
+ export declare function ensureManifest(kitDir: string): Promise<Manifest>;
46
+ //# sourceMappingURL=ensure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure.d.ts","sourceRoot":"","sources":["../../src/manifest/ensure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE/D;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAGtE"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `ensureManifest` — the single entry point for "compile the kit's manifest and
3
+ * make sure `.genie/manifest.json` reflects it" (design: 2026-07-05 chat-
4
+ * invocation, piece A).
5
+ *
6
+ * ## Why this exists
7
+ *
8
+ * Manifest compilation ({@link compileManifest}) already writes
9
+ * `.genie/manifest.json` atomically as its final step. But historically the
10
+ * ONLY caller that triggered a compile at request time was the `ui://genie/grid`
11
+ * resource handler ({@link file://../ui/grid-resource.ts}). The `preview` tool
12
+ * booted the viewer WITHOUT compiling, so a caller that opened the Vite /
13
+ * `file://` vehicle — or an MCP Inspector that cannot read `ui://` resources at
14
+ * all — saw an EMPTY grid, and `list_components` (which reads the compiled
15
+ * manifest) returned `[]` until some unrelated `ui://` read happened to compile
16
+ * it. That order-dependence was the root of the "preview shows nothing" bug.
17
+ *
18
+ * Routing BOTH the `preview` tool and the grid resource through this one helper
19
+ * removes the divergence: there is exactly one way a manifest gets produced +
20
+ * persisted at request time, so the two surfaces can never drift on how (or
21
+ * whether) they compile.
22
+ *
23
+ * ## Contract
24
+ *
25
+ * Given a kit directory, compile it from what's on disk right now and return the
26
+ * resulting {@link Manifest}. Persistence of `.genie/manifest.json` is inherited
27
+ * from {@link compileManifest} (its atomic stage-then-rename write) — this
28
+ * wrapper adds no write of its own; it exists to (a) give callers the
29
+ * manifest-only shape they want without each re-destructuring `{ manifest }`,
30
+ * and (b) be the named seam both the tool and the resource depend on.
31
+ *
32
+ * The `skipped` diagnostics from a compile are intentionally dropped here — the
33
+ * two callers (`preview`, grid resource) only need the manifest to render;
34
+ * surfacing skipped/invalid components is `validate`'s job, not the view path's.
35
+ */
36
+ import { compileManifest } from "./compiler.js";
37
+ /**
38
+ * Compile `kitDir`'s manifest from disk and persist `.genie/manifest.json`
39
+ * (persistence via {@link compileManifest}). Returns the compiled manifest.
40
+ *
41
+ * Throws whatever {@link compileManifest} throws (e.g. an unreadable kit dir);
42
+ * callers that must degrade to an empty grid rather than error — like the grid
43
+ * resource — catch it themselves, preserving their existing failure semantics.
44
+ */
45
+ export async function ensureManifest(kitDir) {
46
+ const { manifest } = await compileManifest(kitDir);
47
+ return manifest;
48
+ }
49
+ //# sourceMappingURL=ensure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure.js","sourceRoot":"","sources":["../../src/manifest/ensure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,EAAE,eAAe,EAAiB,MAAM,eAAe,CAAC;AAE/D;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc;IACjD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public re-export barrel for the M3-03 manifest compiler (DRO-259).
3
+ *
4
+ * Mirrors the existing `store/index.ts` / `plans/index.ts` / `validate/
5
+ * index.ts` / `watch/index.ts` barrel pattern: downstream consumers (the
6
+ * M3-02 watcher's `onChange` wiring, `validate`'s M3-04 full-scan facet, the
7
+ * M4 viewer's build tooling) import from `../manifest/index.js` rather than
8
+ * reaching into `./compiler.js` directly, so this module's internal file
9
+ * layout can change without breaking callers.
10
+ */
11
+ export { compileManifest, type CompileResult, type Manifest, type ManifestCard, type ManifestSkip, } from "./compiler.js";
12
+ export { ensureManifest } from "./ensure.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public re-export barrel for the M3-03 manifest compiler (DRO-259).
3
+ *
4
+ * Mirrors the existing `store/index.ts` / `plans/index.ts` / `validate/
5
+ * index.ts` / `watch/index.ts` barrel pattern: downstream consumers (the
6
+ * M3-02 watcher's `onChange` wiring, `validate`'s M3-04 full-scan facet, the
7
+ * M4 viewer's build tooling) import from `../manifest/index.js` rather than
8
+ * reaching into `./compiler.js` directly, so this module's internal file
9
+ * layout can change without breaking callers.
10
+ */
11
+ export { compileManifest, } from "./compiler.js";
12
+ export { ensureManifest } from "./ensure.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/manifest/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,eAAe,GAKhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * `withPlanGuard` — centralised plan-vs-write guard middleware (M1-13,
3
+ * DRO-239).
4
+ *
5
+ * BEFORE this file, every plan-gated verb (`write_files`, `delete_files`) had
6
+ * to reimplement the same four checks — planId presence, planId existence,
7
+ * planId expiry, and per-path glob membership. When they drifted (as they
8
+ * already had: `write_files` split path-outside-plan into `"glob"` vs
9
+ * `"escapesLocalDir"` sub-reasons while `delete_files` used a single message,
10
+ * and only `plan.ts` emitted a structured audit log), a request that would be
11
+ * rejected by one verb could slip through another with the same shape.
12
+ *
13
+ * This middleware makes the guard one seam: every plan-gated tool wraps its
14
+ * handler in `withPlanGuard(...)`, and the four checks (AC2 (a)–(d)) live
15
+ * here, produce one canonical JSON-RPC `-32602 InvalidParams`-shaped payload
16
+ * (AC3), and emit one canonical `plan.guard.reject` audit line (AC6). The
17
+ * refactored write/delete tools stop re-implementing these checks entirely.
18
+ *
19
+ * Tool-side error taxonomies (e.g. `write_files`' `PayloadTooLargeError`,
20
+ * `delete_files`' `DeleteFailed`) stay in the tools — this middleware only
21
+ * owns the plan-boundary check, matching the issue's scope ("one middleware
22
+ * so every write/delete/register/unregister call funnels through identical
23
+ * validation"), and stops at that boundary so tools remain responsible for
24
+ * their own downstream error shapes.
25
+ */
26
+ import { type PlanState } from "../plans/index.js";
27
+ /**
28
+ * Reasons the guard can reject a call, in the enum shape mandated by AC3
29
+ * (`data.reason` is a client-consumable string). Kept intentionally coarse:
30
+ * a client should be able to branch on this without knowing the guard's
31
+ * internals. The order here matches the order the guard performs its checks
32
+ * (see `withPlanGuard`), which is also the order they short-circuit in.
33
+ */
34
+ export type PlanGuardRejectReason =
35
+ /** AC2(a) — `planId` was missing, empty, or not a string. */
36
+ "planIdMissing"
37
+ /**
38
+ * AC2(b)+(c) — `planId` was well-formed but the plan doesn't exist, or
39
+ * has expired. Both surface identically because the underlying plan
40
+ * registry (`plans/index.ts`) already collapses them into a single
41
+ * `PlanNotFoundError` — there is no separate `PlanExpiredError` for the
42
+ * middleware to distinguish, and shipping two reasons here for what the
43
+ * registry treats as one state would just push clients to guess which one
44
+ * they got.
45
+ */
46
+ | "planNotFound"
47
+ /**
48
+ * AC2(d) — a path in the request doesn't match any glob in the plan's
49
+ * `writes` (mode `"writes"`) or `deletes` (mode `"deletes"`) list.
50
+ */
51
+ | "pathOutsidePlan";
52
+ /**
53
+ * The guard operates in one of two modes: it checks paths against either the
54
+ * plan's `writes` or its `deletes` glob list. This is a discriminator on the
55
+ * middleware options — not a runtime toggle on a single "check either" mode,
56
+ * because a `write_files` call must NOT be authorised by a `deletes` glob
57
+ * (and vice versa; a plan's two glob lists are strictly separate). Encoding
58
+ * this as an option, rather than inferring it from the tool name, keeps the
59
+ * middleware tool-agnostic and locks the strict separation as a per-call
60
+ * choice.
61
+ */
62
+ export type PlanGuardMode = "writes" | "deletes";
63
+ /**
64
+ * Options controlling how the guard extracts paths from a given tool call.
65
+ *
66
+ * The two shipping consumers name their path list differently:
67
+ * - `write_files` uses `{ files: [{ path, … }] }` (default when `mode: "writes"`)
68
+ * - `delete_files` uses `{ paths: [] }` (default when `mode: "deletes"`, or via `pathsKey: "paths"`)
69
+ *
70
+ * Future consumers (`register_assets`, per AC4's forward-compat hook) have
71
+ * their own shapes — the `extractPaths` escape hatch lets any tool plug in
72
+ * without adding another built-in shape variant here.
73
+ */
74
+ export interface PlanGuardOptions {
75
+ mode: PlanGuardMode;
76
+ /**
77
+ * For array-of-strings shapes: the top-level key whose value is a
78
+ * `string[]` of paths. Defaults to `"paths"` when `mode: "deletes"` (the
79
+ * shape delete_files uses).
80
+ */
81
+ pathsKey?: string;
82
+ /**
83
+ * For non-standard shapes: a custom function that returns the flat list
84
+ * of paths to check. Overrides `pathsKey` and the built-in
85
+ * `files[].path` extraction when set. Used e.g. by `register_assets`
86
+ * (whose paths live under `assets[].path`).
87
+ */
88
+ extractPaths?: (args: any) => string[];
89
+ }
90
+ /**
91
+ * The MCP tool-result shape returned by the SDK's `registerTool` handler
92
+ * signature — kept structural (rather than pulling in the SDK's own
93
+ * `CallToolResult` type) so this middleware doesn't force a `@modelcontextprotocol/sdk`
94
+ * import on files that don't otherwise need one. The `content` items are
95
+ * declared with a discriminated-literal `type` so the returned value slots
96
+ * directly into the SDK's own `CallToolResult` typing (which uses a union
97
+ * over `type: "text" | "image" | ...` and would reject a `string`).
98
+ */
99
+ export interface ToolResultContentText {
100
+ type: "text";
101
+ text: string;
102
+ [key: string]: unknown;
103
+ }
104
+ export interface ToolResult {
105
+ isError?: boolean;
106
+ content: Array<ToolResultContentText>;
107
+ structuredContent?: Record<string, unknown>;
108
+ [key: string]: unknown;
109
+ }
110
+ /**
111
+ * Context passed as the second argument to the wrapped handler on success —
112
+ * the resolved plan. The handler would otherwise have to call `getPlan` a
113
+ * second time to reach `plan.localDir` / `plan.kitId`, doubling the
114
+ * plan-registry hit for every request; passing it through means every
115
+ * downstream write/delete op sees the exact PlanState that authorised the
116
+ * call (not one refreshed a few ms later on a separate `getPlan`).
117
+ */
118
+ export interface PlanGuardContext {
119
+ plan: PlanState;
120
+ }
121
+ /**
122
+ * A tool handler as `withPlanGuard` sees it: takes the raw args and an
123
+ * optional guard context (the resolved plan), returns an MCP tool result.
124
+ *
125
+ * The `Args` and `Result` type parameters flow through the HOF so a
126
+ * caller-side type assertion isn't needed at the handler's signature.
127
+ */
128
+ export type GuardedHandler<Args, Result extends ToolResult> = (args: Args, ctx: PlanGuardContext) => Promise<Result>;
129
+ /**
130
+ * Wrap a plan-gated tool handler so every call funnels through one plan-vs-
131
+ * write validation seam.
132
+ *
133
+ * Order of checks — matches AC2 (a)→(d), and is intentionally the same order
134
+ * the tool would fail-fast on today:
135
+ *
136
+ * 1. **planId present** (AC2(a)) — string, non-empty. Missing values are
137
+ * rejected before touching the plan registry: a `getPlan("")` would
138
+ * throw as "not found", which would lose the distinction the reason
139
+ * enum draws between "you forgot to send a planId" and "the planId you
140
+ * sent doesn't match any plan we have." Clients act on those
141
+ * differently (the first is a client bug; the second may be an expired
142
+ * capability grant that needs re-issuing).
143
+ *
144
+ * 2. **planId exists + not expired** (AC2(b)+(c)) — `getPlan` throws
145
+ * `PlanNotFoundError` for both, so they surface as one `planNotFound`
146
+ * reason (see the enum comment above for why).
147
+ *
148
+ * 3. **path matches plan globs** (AC2(d)) — every extracted path is
149
+ * checked against `plan.writes` or `plan.deletes` per `options.mode`.
150
+ * Fails on the FIRST offending path: the exact offending path is
151
+ * surfaced (both in the returned payload and the audit log), so an
152
+ * operator investigating a rejection knows which specific path
153
+ * tripped the guard without having to diff the whole request against
154
+ * the plan's glob list.
155
+ *
156
+ * Only if all three pass does the wrapped handler run — with the resolved
157
+ * plan handed through in `ctx.plan`, so it doesn't re-fetch (see
158
+ * `PlanGuardContext`).
159
+ *
160
+ * Non-guard errors (a `getPlan` throw that isn't `PlanNotFoundError`, or
161
+ * a handler throw) are NOT swallowed — they propagate as-is, so a
162
+ * disk-corrupt plan snapshot or an internal handler bug is still visible to
163
+ * the transport layer (as an MCP internal error), rather than being masked
164
+ * as a plan-boundary rejection.
165
+ */
166
+ export declare function withPlanGuard<Args, Result extends ToolResult>(options: PlanGuardOptions, handler: GuardedHandler<Args, Result>): (args: Args) => Promise<Result | ToolResult>;
167
+ //# sourceMappingURL=plan-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-guard.d.ts","sourceRoot":"","sources":["../../src/middleware/plan-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEjG;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB;AAC/B,6DAA6D;AAC3D,eAAe;AACjB;;;;;;;;GAQG;GACD,cAAc;AAChB;;;GAGG;GACD,iBAAiB,CAAC;AAEtB;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IAEH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,EAAE,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AACD,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACtC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,IAAI,EAAE,MAAM,SAAS,UAAU,IAAI,CAC5D,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,MAAM,CAAC,CAAC;AAmGrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,SAAS,UAAU,EAC3D,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,GACpC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,UAAU,CAAC,CAuD9C"}