@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.
- package/README.md +15 -0
- package/dist/auth/bearer.d.ts +49 -0
- package/dist/auth/bearer.d.ts.map +1 -0
- package/dist/auth/bearer.js +159 -0
- package/dist/auth/bearer.js.map +1 -0
- package/dist/auth/oauth/authorize.d.ts +19 -0
- package/dist/auth/oauth/authorize.d.ts.map +1 -0
- package/dist/auth/oauth/authorize.js +111 -0
- package/dist/auth/oauth/authorize.js.map +1 -0
- package/dist/auth/oauth/config.d.ts +10 -0
- package/dist/auth/oauth/config.d.ts.map +1 -0
- package/dist/auth/oauth/config.js +16 -0
- package/dist/auth/oauth/config.js.map +1 -0
- package/dist/auth/oauth/dcr.d.ts +15 -0
- package/dist/auth/oauth/dcr.d.ts.map +1 -0
- package/dist/auth/oauth/dcr.js +38 -0
- package/dist/auth/oauth/dcr.js.map +1 -0
- package/dist/auth/oauth/jwt.d.ts +13 -0
- package/dist/auth/oauth/jwt.d.ts.map +1 -0
- package/dist/auth/oauth/jwt.js +46 -0
- package/dist/auth/oauth/jwt.js.map +1 -0
- package/dist/auth/oauth/metadata.d.ts +9 -0
- package/dist/auth/oauth/metadata.d.ts.map +1 -0
- package/dist/auth/oauth/metadata.js +19 -0
- package/dist/auth/oauth/metadata.js.map +1 -0
- package/dist/auth/oauth/router.d.ts +32 -0
- package/dist/auth/oauth/router.d.ts.map +1 -0
- package/dist/auth/oauth/router.js +159 -0
- package/dist/auth/oauth/router.js.map +1 -0
- package/dist/auth/oauth/store.d.ts +55 -0
- package/dist/auth/oauth/store.d.ts.map +1 -0
- package/dist/auth/oauth/store.js +63 -0
- package/dist/auth/oauth/store.js.map +1 -0
- package/dist/auth/oauth/token.d.ts +21 -0
- package/dist/auth/oauth/token.d.ts.map +1 -0
- package/dist/auth/oauth/token.js +77 -0
- package/dist/auth/oauth/token.js.map +1 -0
- package/dist/auth/oidc/group-policy.d.ts +43 -0
- package/dist/auth/oidc/group-policy.d.ts.map +1 -0
- package/dist/auth/oidc/group-policy.js +54 -0
- package/dist/auth/oidc/group-policy.js.map +1 -0
- package/dist/auth/oidc/verifier.d.ts +42 -0
- package/dist/auth/oidc/verifier.d.ts.map +1 -0
- package/dist/auth/oidc/verifier.js +145 -0
- package/dist/auth/oidc/verifier.js.map +1 -0
- package/dist/auth/token-cli.d.ts +9 -0
- package/dist/auth/token-cli.d.ts.map +1 -0
- package/dist/auth/token-cli.js +98 -0
- package/dist/auth/token-cli.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +149 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/redact.d.ts +20 -0
- package/dist/config/redact.d.ts.map +1 -0
- package/dist/config/redact.js +77 -0
- package/dist/config/redact.js.map +1 -0
- package/dist/config/secrets.d.ts +100 -0
- package/dist/config/secrets.d.ts.map +1 -0
- package/dist/config/secrets.js +130 -0
- package/dist/config/secrets.js.map +1 -0
- package/dist/framework/html.d.ts +103 -0
- package/dist/framework/html.d.ts.map +1 -0
- package/dist/framework/html.js +271 -0
- package/dist/framework/html.js.map +1 -0
- package/dist/framework/interface.d.ts +130 -0
- package/dist/framework/interface.d.ts.map +1 -0
- package/dist/framework/interface.js +112 -0
- package/dist/framework/interface.js.map +1 -0
- package/dist/framework/react.d.ts +133 -0
- package/dist/framework/react.d.ts.map +1 -0
- package/dist/framework/react.js +243 -0
- package/dist/framework/react.js.map +1 -0
- package/dist/framework/vue.d.ts +99 -0
- package/dist/framework/vue.d.ts.map +1 -0
- package/dist/framework/vue.js +375 -0
- package/dist/framework/vue.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +81 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +141 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/llm/component-response.d.ts +111 -0
- package/dist/llm/component-response.d.ts.map +1 -0
- package/dist/llm/component-response.js +167 -0
- package/dist/llm/component-response.js.map +1 -0
- package/dist/llm/generated-files.d.ts +10 -0
- package/dist/llm/generated-files.d.ts.map +1 -0
- package/dist/llm/generated-files.js +26 -0
- package/dist/llm/generated-files.js.map +1 -0
- package/dist/llm/prompts/CHANGELOG.md +70 -0
- package/dist/llm/prompts/generate-component.system.md +99 -0
- package/dist/llm/prompts/refine-component.system.md +112 -0
- package/dist/llm/prompts.d.ts +66 -0
- package/dist/llm/prompts.d.ts.map +1 -0
- package/dist/llm/prompts.js +78 -0
- package/dist/llm/prompts.js.map +1 -0
- package/dist/llm/retry.d.ts +183 -0
- package/dist/llm/retry.d.ts.map +1 -0
- package/dist/llm/retry.js +475 -0
- package/dist/llm/retry.js.map +1 -0
- package/dist/llm/schema.d.ts +129 -0
- package/dist/llm/schema.d.ts.map +1 -0
- package/dist/llm/schema.js +147 -0
- package/dist/llm/schema.js.map +1 -0
- package/dist/llm/validate.d.ts +107 -0
- package/dist/llm/validate.d.ts.map +1 -0
- package/dist/llm/validate.js +174 -0
- package/dist/llm/validate.js.map +1 -0
- package/dist/manifest/compiler.d.ts +112 -0
- package/dist/manifest/compiler.d.ts.map +1 -0
- package/dist/manifest/compiler.js +306 -0
- package/dist/manifest/compiler.js.map +1 -0
- package/dist/manifest/ensure.d.ts +46 -0
- package/dist/manifest/ensure.d.ts.map +1 -0
- package/dist/manifest/ensure.js +49 -0
- package/dist/manifest/ensure.js.map +1 -0
- package/dist/manifest/index.d.ts +13 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +13 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/middleware/plan-guard.d.ts +167 -0
- package/dist/middleware/plan-guard.d.ts.map +1 -0
- package/dist/middleware/plan-guard.js +190 -0
- package/dist/middleware/plan-guard.js.map +1 -0
- package/dist/plans/index.d.ts +71 -0
- package/dist/plans/index.d.ts.map +1 -0
- package/dist/plans/index.js +252 -0
- package/dist/plans/index.js.map +1 -0
- package/dist/schemas/component.schema.json +114 -0
- package/dist/server.d.ts +96 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +258 -0
- package/dist/server.js.map +1 -0
- package/dist/store/empty-manifest.d.ts +58 -0
- package/dist/store/empty-manifest.d.ts.map +1 -0
- package/dist/store/empty-manifest.js +59 -0
- package/dist/store/empty-manifest.js.map +1 -0
- package/dist/store/git-host.d.ts +120 -0
- package/dist/store/git-host.d.ts.map +1 -0
- package/dist/store/git-host.js +796 -0
- package/dist/store/git-host.js.map +1 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +4 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/interface.d.ts +322 -0
- package/dist/store/interface.d.ts.map +1 -0
- package/dist/store/interface.js +100 -0
- package/dist/store/interface.js.map +1 -0
- package/dist/store/kit-files.d.ts +88 -0
- package/dist/store/kit-files.d.ts.map +1 -0
- package/dist/store/kit-files.js +199 -0
- package/dist/store/kit-files.js.map +1 -0
- package/dist/store/local.d.ts +70 -0
- package/dist/store/local.d.ts.map +1 -0
- package/dist/store/local.js +651 -0
- package/dist/store/local.js.map +1 -0
- package/dist/store/manifest.d.ts +44 -0
- package/dist/store/manifest.d.ts.map +1 -0
- package/dist/store/manifest.js +116 -0
- package/dist/store/manifest.js.map +1 -0
- package/dist/store/viewer-assets.d.ts +20 -0
- package/dist/store/viewer-assets.d.ts.map +1 -0
- package/dist/store/viewer-assets.js +118 -0
- package/dist/store/viewer-assets.js.map +1 -0
- package/dist/sync/anchor.d.ts +125 -0
- package/dist/sync/anchor.d.ts.map +1 -0
- package/dist/sync/anchor.js +210 -0
- package/dist/sync/anchor.js.map +1 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +15 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/orchestrator.d.ts +160 -0
- package/dist/sync/orchestrator.d.ts.map +1 -0
- package/dist/sync/orchestrator.js +288 -0
- package/dist/sync/orchestrator.js.map +1 -0
- package/dist/tools/bind_kit.d.ts +29 -0
- package/dist/tools/bind_kit.d.ts.map +1 -0
- package/dist/tools/bind_kit.js +70 -0
- package/dist/tools/bind_kit.js.map +1 -0
- package/dist/tools/conjure.d.ts +141 -0
- package/dist/tools/conjure.d.ts.map +1 -0
- package/dist/tools/conjure.js +655 -0
- package/dist/tools/conjure.js.map +1 -0
- package/dist/tools/conjure_screen.d.ts +174 -0
- package/dist/tools/conjure_screen.d.ts.map +1 -0
- package/dist/tools/conjure_screen.js +458 -0
- package/dist/tools/conjure_screen.js.map +1 -0
- package/dist/tools/create_kit.d.ts +32 -0
- package/dist/tools/create_kit.d.ts.map +1 -0
- package/dist/tools/create_kit.js +131 -0
- package/dist/tools/create_kit.js.map +1 -0
- package/dist/tools/create_project.d.ts +267 -0
- package/dist/tools/create_project.d.ts.map +1 -0
- package/dist/tools/create_project.js +546 -0
- package/dist/tools/create_project.js.map +1 -0
- package/dist/tools/delete_files.d.ts +93 -0
- package/dist/tools/delete_files.d.ts.map +1 -0
- package/dist/tools/delete_files.js +276 -0
- package/dist/tools/delete_files.js.map +1 -0
- package/dist/tools/delete_project.d.ts +43 -0
- package/dist/tools/delete_project.d.ts.map +1 -0
- package/dist/tools/delete_project.js +118 -0
- package/dist/tools/delete_project.js.map +1 -0
- package/dist/tools/get_kit.d.ts +28 -0
- package/dist/tools/get_kit.d.ts.map +1 -0
- package/dist/tools/get_kit.js +95 -0
- package/dist/tools/get_kit.js.map +1 -0
- package/dist/tools/get_project.d.ts +37 -0
- package/dist/tools/get_project.d.ts.map +1 -0
- package/dist/tools/get_project.js +71 -0
- package/dist/tools/get_project.js.map +1 -0
- package/dist/tools/list_components.d.ts +42 -0
- package/dist/tools/list_components.d.ts.map +1 -0
- package/dist/tools/list_components.js +125 -0
- package/dist/tools/list_components.js.map +1 -0
- package/dist/tools/list_files.d.ts +32 -0
- package/dist/tools/list_files.d.ts.map +1 -0
- package/dist/tools/list_files.js +109 -0
- package/dist/tools/list_files.js.map +1 -0
- package/dist/tools/list_kits.d.ts +14 -0
- package/dist/tools/list_kits.d.ts.map +1 -0
- package/dist/tools/list_kits.js +41 -0
- package/dist/tools/list_kits.js.map +1 -0
- package/dist/tools/list_projects.d.ts +23 -0
- package/dist/tools/list_projects.d.ts.map +1 -0
- package/dist/tools/list_projects.js +66 -0
- package/dist/tools/list_projects.js.map +1 -0
- package/dist/tools/plan.d.ts +16 -0
- package/dist/tools/plan.d.ts.map +1 -0
- package/dist/tools/plan.js +146 -0
- package/dist/tools/plan.js.map +1 -0
- package/dist/tools/preview.d.ts +289 -0
- package/dist/tools/preview.d.ts.map +1 -0
- package/dist/tools/preview.js +672 -0
- package/dist/tools/preview.js.map +1 -0
- package/dist/tools/read_file.d.ts +40 -0
- package/dist/tools/read_file.d.ts.map +1 -0
- package/dist/tools/read_file.js +114 -0
- package/dist/tools/read_file.js.map +1 -0
- package/dist/tools/refine.d.ts +220 -0
- package/dist/tools/refine.d.ts.map +1 -0
- package/dist/tools/refine.js +501 -0
- package/dist/tools/refine.js.map +1 -0
- package/dist/tools/validate.d.ts +74 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +224 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/tools/write_files.d.ts +158 -0
- package/dist/tools/write_files.d.ts.map +1 -0
- package/dist/tools/write_files.js +541 -0
- package/dist/tools/write_files.js.map +1 -0
- package/dist/transport.d.ts +77 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +448 -0
- package/dist/transport.js.map +1 -0
- package/dist/ui/card-asset-broker.d.ts +42 -0
- package/dist/ui/card-asset-broker.d.ts.map +1 -0
- package/dist/ui/card-asset-broker.js +479 -0
- package/dist/ui/card-asset-broker.js.map +1 -0
- package/dist/ui/grid-resource.d.ts +252 -0
- package/dist/ui/grid-resource.d.ts.map +1 -0
- package/dist/ui/grid-resource.js +680 -0
- package/dist/ui/grid-resource.js.map +1 -0
- package/dist/ui/viewer-static/index.html +95 -0
- package/dist/ui/viewer-static/viewer.css +451 -0
- package/dist/ui/viewer-static/viewer.js +1405 -0
- package/dist/validate/full-scan.d.ts +87 -0
- package/dist/validate/full-scan.d.ts.map +1 -0
- package/dist/validate/full-scan.js +299 -0
- package/dist/validate/full-scan.js.map +1 -0
- package/dist/validate/index.d.ts +15 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +18 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate/marker.d.ts +89 -0
- package/dist/validate/marker.d.ts.map +1 -0
- package/dist/validate/marker.js +87 -0
- package/dist/validate/marker.js.map +1 -0
- package/dist/validate/phash.d.ts +135 -0
- package/dist/validate/phash.d.ts.map +1 -0
- package/dist/validate/phash.js +307 -0
- package/dist/validate/phash.js.map +1 -0
- package/dist/validate/render.d.ts +38 -0
- package/dist/validate/render.d.ts.map +1 -0
- package/dist/validate/render.js +155 -0
- package/dist/validate/render.js.map +1 -0
- package/dist/watch/index.d.ts +12 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +12 -0
- package/dist/watch/index.js.map +1 -0
- package/dist/watch/watcher.d.ts +65 -0
- package/dist/watch/watcher.d.ts.map +1 -0
- package/dist/watch/watcher.js +238 -0
- package/dist/watch/watcher.js.map +1 -0
- 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"}
|