@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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3-06 (DRO-262) — genie's own `.genie/sync.json` verification anchor.
|
|
3
|
+
*
|
|
4
|
+
* `.genie/sync.json` is the LAST file the atomic sync sequence writes: it
|
|
5
|
+
* records the source/render hashes and the
|
|
6
|
+
* `@genie`-validated component list for whatever the sync just wrote, so the
|
|
7
|
+
* *next* sync can diff against it and repair a half-completed prior run. A
|
|
8
|
+
* mid-plan failure must leave this file unwritten (or stale) — never
|
|
9
|
+
* partially written — which is why {@link writeAnchor} always does a
|
|
10
|
+
* temp-file + rename commit (AC7), the same durability pattern
|
|
11
|
+
* `store/local.ts`'s `stageAndCommit` and `plans/index.ts`'s plan snapshots
|
|
12
|
+
* use elsewhere in this codebase.
|
|
13
|
+
*
|
|
14
|
+
* This is genie's **native** anchor shape — NOT the Anthropic `_ds_sync.json`
|
|
15
|
+
* interop shape (CLAUDE.md hard rule 1 / AGENTS.md hard rule 1). An interop
|
|
16
|
+
* adapter may one day map this to `_ds_sync.json`; that mapping is out of
|
|
17
|
+
* scope here.
|
|
18
|
+
*
|
|
19
|
+
* ── Coordination with M3-05 (DRO-261) ───────────────────────────────────────
|
|
20
|
+
* The 5-step atomic orchestrator (M3-05) calls {@link writeAnchor} as its
|
|
21
|
+
* Step 5 (last write). That issue was still unbuilt on `main` as of this PR,
|
|
22
|
+
* so {@link PlanResult} is this module's own minimal seam — "the set of
|
|
23
|
+
* writes a sync touched, plus which components passed validation" — rather
|
|
24
|
+
* than importing a type from a module that doesn't exist yet. Whichever of
|
|
25
|
+
* M3-05 / M3-06 lands second adapts to the other's shape; the signature
|
|
26
|
+
* `writeAnchor(projectRoot, planResult)` itself (AC1) is the stable contract
|
|
27
|
+
* both issues were told to converge on.
|
|
28
|
+
*/
|
|
29
|
+
import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { sriSha256 } from "../store/kit-files.js";
|
|
33
|
+
// ─── Public shape (AC2) ──────────────────────────────────────────────────────
|
|
34
|
+
/** Kit-root-relative path of genie's verification anchor (D-C). */
|
|
35
|
+
export const ANCHOR_PATH = ".genie/sync.json";
|
|
36
|
+
/** Env var overriding {@link Anchor.by} (AC6) — for forks of genie. */
|
|
37
|
+
export const GENIE_BY_ENV = "GENIE_BY";
|
|
38
|
+
/** Default {@link Anchor.by} value when `GENIE_BY` is unset (AC6). */
|
|
39
|
+
export const DEFAULT_BY = "genie";
|
|
40
|
+
/**
|
|
41
|
+
* Source-file extensions that count toward {@link Anchor.sourceHashes} (AC3).
|
|
42
|
+
* The issue's literal AC3 text says ".tsx/.jsx", written when React was the
|
|
43
|
+
* only shipped framework; Vue landed as a first-class framework in M2-08
|
|
44
|
+
* (merged to `main` the day before this PR) with `.vue` as its canonical
|
|
45
|
+
* single-file-component source (`framework/vue.ts`'s `renderSource` →
|
|
46
|
+
* `<Name>.vue`). Omitting `.vue` here would leave every Vue component's
|
|
47
|
+
* source invisible to the anchor's drift/tamper detection (RFC §10 T-05) —
|
|
48
|
+
* the exact failure mode this file exists to prevent — so the extension list
|
|
49
|
+
* covers both shipped component-source frameworks rather than only the one
|
|
50
|
+
* named in the issue's illustrative example.
|
|
51
|
+
*
|
|
52
|
+
* `.html` is deliberately **not** in this list even though vanilla HTML is now a
|
|
53
|
+
* first-class framework (`HtmlAdapter.renderSource` → `<Name>.html`, DRO-617).
|
|
54
|
+
* For a vanilla-HTML component the source file *is* the browser-ready
|
|
55
|
+
* `<Name>.html` preview — so it is already hash-covered by {@link Anchor.renderHashes}
|
|
56
|
+
* (which hashes every `.html` write, AC4), giving the anchor's drift/tamper
|
|
57
|
+
* detection full coverage of that source with no gap. Adding `.html` here instead
|
|
58
|
+
* would double-count it *and* mislabel every React/Vue `<Name>.html` — which is a
|
|
59
|
+
* *rendered* preview, not source — as `sourceHashes`, so the extension split stays
|
|
60
|
+
* "compiled-source suffixes → sourceHashes, `.html` → renderHashes" and HTML's
|
|
61
|
+
* source rides the render side by construction.
|
|
62
|
+
*/
|
|
63
|
+
const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".vue"];
|
|
64
|
+
/** Thrown by {@link readAnchor} when `.genie/sync.json` exists but is not valid genie anchor JSON. */
|
|
65
|
+
export class AnchorParseError extends Error {
|
|
66
|
+
projectRoot;
|
|
67
|
+
reason;
|
|
68
|
+
constructor(projectRoot, reason) {
|
|
69
|
+
super(`Anchor at "${projectRoot}/${ANCHOR_PATH}" is malformed: ${reason}`);
|
|
70
|
+
this.projectRoot = projectRoot;
|
|
71
|
+
this.reason = reason;
|
|
72
|
+
this.name = "AnchorParseError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ─── Schema (validated on read; see AnchorParseError above) ─────────────────
|
|
76
|
+
const anchorSchema = z.object({
|
|
77
|
+
version: z.literal(1),
|
|
78
|
+
writtenAt: z.string(),
|
|
79
|
+
by: z.string(),
|
|
80
|
+
sourceHashes: z.record(z.string(), z.string()),
|
|
81
|
+
renderHashes: z.record(z.string(), z.string()),
|
|
82
|
+
verified: z.array(z.string()),
|
|
83
|
+
});
|
|
84
|
+
// ─── writeAnchor (AC1, AC3–AC7) ──────────────────────────────────────────────
|
|
85
|
+
/**
|
|
86
|
+
* Compute and atomically write `${projectRoot}/.genie/sync.json` (AC1).
|
|
87
|
+
*
|
|
88
|
+
* - `sourceHashes` covers every {@link SOURCE_EXTENSIONS} path in
|
|
89
|
+
* `planResult.writes` (AC3 — `.tsx`/`.jsx`, plus `.vue` for the shipped Vue
|
|
90
|
+
* framework); `renderHashes` covers every `.html` path (AC4). A path with
|
|
91
|
+
* none of those extensions (e.g. `meta.json`) appears in neither map — this
|
|
92
|
+
* anchor is scoped to source + render provenance, not a general write log.
|
|
93
|
+
* - `verified` is `planResult.verified` verbatim (AC5) — the caller (the
|
|
94
|
+
* M3-05 orchestrator, or a direct test) is the one that ran the M3-04
|
|
95
|
+
* validator and knows which `<group>/<Name>` ids passed.
|
|
96
|
+
* - `by` is {@link DEFAULT_BY} unless overridden by `env.GENIE_BY` (AC6).
|
|
97
|
+
* - The write is atomic (AC7): content lands in a temp file staged under
|
|
98
|
+
* `<projectRoot>/.genie-tmp/` — a different directory from the `.genie/`
|
|
99
|
+
* destination, but the same filesystem/mount, which is the one guarantee
|
|
100
|
+
* POSIX `rename()` actually needs — then renamed over the destination. A
|
|
101
|
+
* crash between the temp write and the rename leaves the PRIOR anchor (or
|
|
102
|
+
* no anchor) intact — never a half-written one.
|
|
103
|
+
*
|
|
104
|
+
* @param env Injectable environment (defaults to `process.env`), so tests can
|
|
105
|
+
* assert the `GENIE_BY` override without mutating global state — the same
|
|
106
|
+
* pattern `write_files.ts`'s `resolveByteCap(env)` uses for
|
|
107
|
+
* `GENIE_WRITE_BYTE_CAP`.
|
|
108
|
+
*/
|
|
109
|
+
export async function writeAnchor(projectRoot, planResult, env = process.env) {
|
|
110
|
+
const anchor = {
|
|
111
|
+
version: 1,
|
|
112
|
+
writtenAt: new Date().toISOString(),
|
|
113
|
+
by: env[GENIE_BY_ENV] ?? DEFAULT_BY,
|
|
114
|
+
sourceHashes: hashByExtension(planResult.writes, SOURCE_EXTENSIONS),
|
|
115
|
+
renderHashes: hashByExtension(planResult.writes, [".html"]),
|
|
116
|
+
verified: [...planResult.verified],
|
|
117
|
+
};
|
|
118
|
+
const destPath = join(projectRoot, ANCHOR_PATH);
|
|
119
|
+
await mkdir(join(projectRoot, ".genie"), { recursive: true });
|
|
120
|
+
// Stage under `<projectRoot>/.genie-tmp/` (NOT os.tmpdir()) so the final
|
|
121
|
+
// rename stays on the same filesystem — mirrors the load-bearing reasoning
|
|
122
|
+
// in store/local.ts's stageAndCommit (a kit dir and /tmp are commonly
|
|
123
|
+
// different mounts, and rename() is only atomic within one filesystem; it
|
|
124
|
+
// does NOT require source and destination to share a directory). Reusing
|
|
125
|
+
// `.genie-tmp` (rather than a new `.genie/`-nested
|
|
126
|
+
// staging dir) matters beyond consistency: `store/kit-files.ts`'s
|
|
127
|
+
// `DEFAULT_IGNORED_SEGMENTS` already excludes `.genie-tmp` from every kit
|
|
128
|
+
// listing, so a `list_files` call landing mid-write never surfaces this
|
|
129
|
+
// staging directory — the same guarantee `write_files`' own commit already
|
|
130
|
+
// gets from that shared exclusion.
|
|
131
|
+
const genieTmpRoot = join(projectRoot, ".genie-tmp");
|
|
132
|
+
await mkdir(genieTmpRoot, { recursive: true });
|
|
133
|
+
const stagingRoot = await mkdtemp(join(genieTmpRoot, "sync-"));
|
|
134
|
+
try {
|
|
135
|
+
const stagedPath = join(stagingRoot, "sync.json");
|
|
136
|
+
await writeFile(stagedPath, JSON.stringify(anchor, null, 2), "utf-8");
|
|
137
|
+
await rename(stagedPath, destPath);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await rm(stagingRoot, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build a `path -> sha256-<base64>` map over every `planResult.writes` entry
|
|
145
|
+
* whose path ends in one of `extensions` (AC3/AC4). Hashes the exact bytes
|
|
146
|
+
* handed in (`sriSha256` accepts either a `Buffer` or a `string`), never a
|
|
147
|
+
* fresh disk read — the anchor vouches for what THIS sync wrote, not
|
|
148
|
+
* whatever happens to be on disk afterward.
|
|
149
|
+
*/
|
|
150
|
+
function hashByExtension(writes, extensions) {
|
|
151
|
+
const result = {};
|
|
152
|
+
for (const { path, content } of writes) {
|
|
153
|
+
if (extensions.some((ext) => path.endsWith(ext))) {
|
|
154
|
+
result[path] = sriSha256(content);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
// ─── readAnchor (AC1, AC8) ────────────────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Read + parse `${projectRoot}/.genie/sync.json` (AC1).
|
|
162
|
+
*
|
|
163
|
+
* - Returns `null` — never throws — when the file (or the whole `.genie`
|
|
164
|
+
* dir, or `projectRoot` itself) does not exist (AC8): a fresh kit with no
|
|
165
|
+
* prior sync is a normal, expected state, not an error.
|
|
166
|
+
* - Throws {@link AnchorParseError} when the file EXISTS but is not valid
|
|
167
|
+
* JSON, or is valid JSON that doesn't match the {@link Anchor} shape
|
|
168
|
+
* (including a `version` other than the literal `1`). This is
|
|
169
|
+
* deliberately NOT folded into the `null` case: `null` means "no anchor
|
|
170
|
+
* yet, that's fine"; a corrupt-but-present anchor is a real operability
|
|
171
|
+
* problem for a file whose entire purpose is verification (RFC §10 T-05
|
|
172
|
+
* flags anchor tampering) and must surface rather than silently
|
|
173
|
+
* masquerade as "nothing to verify yet" — the same reasoning
|
|
174
|
+
* `store/manifest.ts`'s `ManifestParseError` already applies to
|
|
175
|
+
* `manifest.json`.
|
|
176
|
+
*/
|
|
177
|
+
export async function readAnchor(projectRoot) {
|
|
178
|
+
const destPath = join(projectRoot, ANCHOR_PATH);
|
|
179
|
+
let raw;
|
|
180
|
+
try {
|
|
181
|
+
raw = await readFile(destPath, "utf-8");
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (isMissingPathError(error))
|
|
185
|
+
return null;
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
let json;
|
|
189
|
+
try {
|
|
190
|
+
json = JSON.parse(raw);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
throw new AnchorParseError(projectRoot, describeError(error, "invalid JSON"));
|
|
194
|
+
}
|
|
195
|
+
const parsed = anchorSchema.safeParse(json);
|
|
196
|
+
if (!parsed.success) {
|
|
197
|
+
throw new AnchorParseError(projectRoot, parsed.error.issues[0]?.message ?? "schema mismatch");
|
|
198
|
+
}
|
|
199
|
+
return parsed.data;
|
|
200
|
+
}
|
|
201
|
+
/** ENOENT (missing file) and ENOTDIR (a parent component is a file) both mean "not there". */
|
|
202
|
+
function isMissingPathError(error) {
|
|
203
|
+
return (error instanceof Error &&
|
|
204
|
+
"code" in error &&
|
|
205
|
+
(error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
206
|
+
}
|
|
207
|
+
function describeError(error, fallback) {
|
|
208
|
+
return error instanceof Error ? error.message : fallback;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=anchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor.js","sourceRoot":"","sources":["../../src/sync/anchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public re-export barrel for genie's sync layer:
|
|
3
|
+
* - M3-06 (`anchor.ts`) — the `.genie/sync.json` verification anchor.
|
|
4
|
+
* - M3-05 (`orchestrator.ts`) — the 5-step atomic write-sequence orchestrator
|
|
5
|
+
* that writes that anchor last.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the existing `store/index.ts` / `validate/index.ts` barrel pattern:
|
|
8
|
+
* downstream consumers (the sync-flow caller wiring generation → commit, and any
|
|
9
|
+
* future consumer outside this directory) import from `../sync/index.js` rather
|
|
10
|
+
* than reaching into `./anchor.js` / `./orchestrator.js` directly, so the
|
|
11
|
+
* module's internal file layout can change without breaking callers.
|
|
12
|
+
*/
|
|
13
|
+
export { ANCHOR_PATH, GENIE_BY_ENV, DEFAULT_BY, writeAnchor, readAnchor, AnchorParseError, type Anchor, type PlanResult, } from "./anchor.js";
|
|
14
|
+
export { RECOMPILE_SENTINEL_PATH, RECOMPILE_SENTINEL_BODY, runAtomicSync, detectResumeStep, type StepNumber, type StepEvent, type SyncResult, type WriteInput, type SyncArgs, type SyncDeps, } from "./orchestrator.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,KAAK,MAAM,EACX,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public re-export barrel for genie's sync layer:
|
|
3
|
+
* - M3-06 (`anchor.ts`) — the `.genie/sync.json` verification anchor.
|
|
4
|
+
* - M3-05 (`orchestrator.ts`) — the 5-step atomic write-sequence orchestrator
|
|
5
|
+
* that writes that anchor last.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the existing `store/index.ts` / `validate/index.ts` barrel pattern:
|
|
8
|
+
* downstream consumers (the sync-flow caller wiring generation → commit, and any
|
|
9
|
+
* future consumer outside this directory) import from `../sync/index.js` rather
|
|
10
|
+
* than reaching into `./anchor.js` / `./orchestrator.js` directly, so the
|
|
11
|
+
* module's internal file layout can change without breaking callers.
|
|
12
|
+
*/
|
|
13
|
+
export { ANCHOR_PATH, GENIE_BY_ENV, DEFAULT_BY, writeAnchor, readAnchor, AnchorParseError, } from "./anchor.js";
|
|
14
|
+
export { RECOMPILE_SENTINEL_PATH, RECOMPILE_SENTINEL_BODY, runAtomicSync, detectResumeStep, } from "./orchestrator.js";
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EACL,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,UAAU,EACV,gBAAgB,GAGjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,GAOjB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3-05 (DRO-261) — genie's 5-step atomic write-sequence orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* `runAtomicSync` sequences one sync commit as five ordered, stop-on-first-
|
|
5
|
+
* failure steps. The atomic write sequence is load-bearing and unchanged in
|
|
6
|
+
* shape:
|
|
7
|
+
*
|
|
8
|
+
* 1. Write the `.genie/recompile` sentinel FIRST, to fence the manifest/copy
|
|
9
|
+
* machinery — a consumer that sees the sentinel knows a sync is mid-flight
|
|
10
|
+
* and the tree may be inconsistent until the anchor lands.
|
|
11
|
+
* 2. Apply every content write, chunked into batches of ≤ 256 per
|
|
12
|
+
* `write_files` call (the M1-08 `MAX_FILES_PER_CALL` ceiling — a 257-file
|
|
13
|
+
* call would be rejected as `TooManyFilesError`).
|
|
14
|
+
* 3. Apply every delete via one `delete_files` call. A path that is
|
|
15
|
+
* authorized-but-absent is NOT a failure (M1-09 AC5 returns it in
|
|
16
|
+
* `notFoundPaths`) — only a real error (out-of-plan path, unlink failure)
|
|
17
|
+
* trips stop-on-first-failure.
|
|
18
|
+
* 4. RE-ARM the sentinel (write it again), so it still fences the tree after
|
|
19
|
+
* the writes/deletes landed and right up until the anchor is committed.
|
|
20
|
+
* 5. Write `.genie/sync.json` LAST, via M3-06's {@link writeAnchor}. It is the
|
|
21
|
+
* completion proof: its presence means every prior step succeeded.
|
|
22
|
+
*
|
|
23
|
+
* ── Why the anchor is last (AC7) ─────────────────────────────────────────────
|
|
24
|
+
* A mid-plan failure (steps 1–4) returns before step 5, so `.genie/sync.json`
|
|
25
|
+
* is never written for an incomplete sync. The NEXT sync sees "sentinel present,
|
|
26
|
+
* anchor absent" ({@link detectResumeStep} → 2) and knows the tree is a
|
|
27
|
+
* half-write to repair from step 2 — rather than trusting a stale anchor that
|
|
28
|
+
* would mask the incomplete state. This is the whole reason the write order is
|
|
29
|
+
* load-bearing.
|
|
30
|
+
*
|
|
31
|
+
* ── Native surface (CLAUDE.md hard rule 1) ───────────────────────────────────
|
|
32
|
+
* `.genie/recompile` and `.genie/sync.json` are genie-NATIVE artifacts. They are
|
|
33
|
+
* written fs-native to `projectRoot` (NOT through the plan-gated `write_files`
|
|
34
|
+
* verb — they would never match a user plan's `writes` globs, and they are
|
|
35
|
+
* genie's own bookkeeping, not user content). They must NOT be renamed to the
|
|
36
|
+
* Anthropic `_ds_recompile` / `_ds_sync.json` interop shapes; an interop bridge
|
|
37
|
+
* that maps them is a separate, opt-in concern.
|
|
38
|
+
*
|
|
39
|
+
* ── Composition (Kiln's plan, DRO-696) ───────────────────────────────────────
|
|
40
|
+
* Steps 2–3 call the in-process `writeFiles` / `deleteFiles` CORE functions
|
|
41
|
+
* directly (not over the MCP transport) — the same way `conjure_screen.ts`
|
|
42
|
+
* composes tool-layer logic — so the orchestrator reuses their full plan-gating,
|
|
43
|
+
* atomicity, and rollback contracts without re-implementing any of it.
|
|
44
|
+
*/
|
|
45
|
+
import type { KitStore } from "../store/interface.js";
|
|
46
|
+
/** Kit-root-relative path of genie's recompile fence sentinel (native surface). */
|
|
47
|
+
export declare const RECOMPILE_SENTINEL_PATH = ".genie/recompile";
|
|
48
|
+
/**
|
|
49
|
+
* The exact sentinel body (AC2). genie's native `{"by":"genie"}` marker — NOT
|
|
50
|
+
* the Anthropic `_ds_recompile` interop shape (CLAUDE.md hard rule 1). Kept as
|
|
51
|
+
* a compact single-line literal so a byte-for-byte assertion is trivial and the
|
|
52
|
+
* re-arm in step 4 reproduces it exactly.
|
|
53
|
+
*/
|
|
54
|
+
export declare const RECOMPILE_SENTINEL_BODY = "{\"by\":\"genie\"}";
|
|
55
|
+
/** The five ordered steps of one atomic sync. */
|
|
56
|
+
export type StepNumber = 1 | 2 | 3 | 4 | 5;
|
|
57
|
+
/**
|
|
58
|
+
* One observability event per attempted step (AC9): which `step`, whether it
|
|
59
|
+
* succeeded (`ok`), and how long it took (`ms`, wall-clock via
|
|
60
|
+
* `performance.now()`). Emitted for EVERY attempted step — including the one
|
|
61
|
+
* that failed — and never for a step the sequence stopped short of.
|
|
62
|
+
*/
|
|
63
|
+
export interface StepEvent {
|
|
64
|
+
step: StepNumber;
|
|
65
|
+
ok: boolean;
|
|
66
|
+
ms: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Result of one `runAtomicSync` call (AC1, AC7).
|
|
70
|
+
* - Success — every step landed; `.genie/sync.json` was written last.
|
|
71
|
+
* - Failure — a step BEFORE the anchor failed; `.genie/sync.json` was NOT
|
|
72
|
+
* written. `failedStep` is the 1-based step that threw, `error` is the
|
|
73
|
+
* underlying cause, and `events` records every step attempted up to and
|
|
74
|
+
* including the failure.
|
|
75
|
+
*/
|
|
76
|
+
export type SyncResult = {
|
|
77
|
+
ok: true;
|
|
78
|
+
events: StepEvent[];
|
|
79
|
+
} | {
|
|
80
|
+
ok: false;
|
|
81
|
+
failedStep: StepNumber;
|
|
82
|
+
error: Error;
|
|
83
|
+
events: StepEvent[];
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* One content write for the sync. Structurally the same as `write_files`'
|
|
87
|
+
* per-file input, so the batch passes straight through to the plan-gated
|
|
88
|
+
* `writeFiles` core fn: exactly one of `data` (inline bytes — the common
|
|
89
|
+
* case for genie-generated component source/render) or `localPath` (a source
|
|
90
|
+
* path resolved against the plan's `localDir`) must be set.
|
|
91
|
+
*/
|
|
92
|
+
export interface WriteInput {
|
|
93
|
+
path: string;
|
|
94
|
+
data?: string;
|
|
95
|
+
encoding?: "utf-8" | "base64";
|
|
96
|
+
localPath?: string;
|
|
97
|
+
mimeType?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Arguments to {@link runAtomicSync} (AC1). The issue's literal signature is
|
|
101
|
+
* `{ planId, writes, deletes }`; `verified` is threaded through additively so
|
|
102
|
+
* step 5's anchor can carry the `<group>/<Name>` ids that passed the M3-04
|
|
103
|
+
* validator within THIS sync (M3-06's `PlanResult.verified`). It is optional and
|
|
104
|
+
* defaults to `[]`, so the literal AC1 shape still type-checks and runs — the
|
|
105
|
+
* two issues were told to converge on each other's shape (see `anchor.ts`'s
|
|
106
|
+
* module-doc coordination note), and this is that convergence.
|
|
107
|
+
*/
|
|
108
|
+
export interface SyncArgs {
|
|
109
|
+
planId: string;
|
|
110
|
+
writes: WriteInput[];
|
|
111
|
+
deletes: string[];
|
|
112
|
+
/** `<group>/<Name>` ids that passed validation this sync (M3-06 anchor). */
|
|
113
|
+
verified?: string[];
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Injected collaborators (mirrors `conjure_screen.ts`'s `ConjureScreenDeps`).
|
|
117
|
+
*
|
|
118
|
+
* - `store` — the kit backend steps 2–3 write/delete through (LocalFs or
|
|
119
|
+
* GitHost), behind the same `KitStore` seam every M1 verb uses.
|
|
120
|
+
* - `projectRoot` — the on-disk kit root the native sentinel + anchor are
|
|
121
|
+
* written to. CONTRACT: this MUST be the same directory the injected `store`
|
|
122
|
+
* resolves the plan's `kitId` to (for `LocalFsKitStore`,
|
|
123
|
+
* `<baseDir>/<kitId>`), so the fs-native sentinel/anchor and the
|
|
124
|
+
* store-written content land in ONE tree. The sync-flow caller computes it;
|
|
125
|
+
* the orchestrator takes it explicitly, matching `writeAnchor`'s own
|
|
126
|
+
* `projectRoot`-first signature (M3-06).
|
|
127
|
+
*/
|
|
128
|
+
export interface SyncDeps {
|
|
129
|
+
store: KitStore;
|
|
130
|
+
projectRoot: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Run the 5-step atomic sync sequence. Stops on the first failing step (AC7),
|
|
134
|
+
* leaving `.genie/sync.json` unwritten so the next sync's diff repairs the
|
|
135
|
+
* half-write; a not-found delete inside step 3 is NOT a failure (AC4). Every
|
|
136
|
+
* attempted step emits a {@link StepEvent} (AC9).
|
|
137
|
+
*/
|
|
138
|
+
export declare function runAtomicSync(deps: SyncDeps, args: SyncArgs): Promise<SyncResult>;
|
|
139
|
+
/**
|
|
140
|
+
* Inspect a kit root and report where the NEXT sync should resume from (AC8):
|
|
141
|
+
*
|
|
142
|
+
* - `2` — sentinel present, anchor ABSENT: a prior sync fenced the tree
|
|
143
|
+
* (step 1) then crashed before the anchor landed (steps 2–4). The
|
|
144
|
+
* tree is a half-write; resume from step 2 to re-apply
|
|
145
|
+
* writes/deletes and re-arm before writing the anchor.
|
|
146
|
+
* - `1` — sentinel AND anchor both absent: a fresh, never-synced kit.
|
|
147
|
+
* Start a normal sync from step 1.
|
|
148
|
+
* - `null` — the anchor is present (a prior sync completed cleanly): there is
|
|
149
|
+
* no partial-write to resume; the next sync is a NEW plan from step
|
|
150
|
+
* 1 with fresh inputs, not a resume.
|
|
151
|
+
*
|
|
152
|
+
* Never mutates the tree. Propagates {@link readAnchor}'s `AnchorParseError`
|
|
153
|
+
* for a corrupt-but-present anchor — a corrupt anchor is a real operability
|
|
154
|
+
* problem, not a "nothing to resume" signal (same reasoning `readAnchor` itself
|
|
155
|
+
* applies). Likewise propagates any non-"missing" error from the sentinel
|
|
156
|
+
* `stat` (e.g. `EACCES` on `.genie/`) rather than treating it as "sentinel
|
|
157
|
+
* absent" — see {@link pathExists}.
|
|
158
|
+
*/
|
|
159
|
+
export declare function detectResumeStep(projectRoot: string): Promise<StepNumber | null>;
|
|
160
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/sync/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOtD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,uBAAmB,CAAC;AAIxD,iDAAiD;AACjD,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,OAAO,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,SAAS,EAAE,CAAA;CAAE,GACjC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAE7E;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAID;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CA8BvF;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAMtF"}
|