@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,87 @@
|
|
|
1
|
+
import type { Renderer } from "./render.js";
|
|
2
|
+
/**
|
|
3
|
+
* The read slice of `KitStore` this facet needs — `listFiles` to enumerate the
|
|
4
|
+
* kit tree and `readFile` to pull each preview's bytes. Declared narrowly (not
|
|
5
|
+
* the whole `KitStore`) so a unit test injects a two-method in-memory stub,
|
|
6
|
+
* mirroring `refine.ts`'s `RefineKitStore`. A real `KitStore` (LocalFs / git
|
|
7
|
+
* host) is structurally assignable, so `server.ts` passes its shared store
|
|
8
|
+
* unchanged (the extra `readFile` fields — `encoding`, `mimeType` — are simply
|
|
9
|
+
* ignored here).
|
|
10
|
+
*/
|
|
11
|
+
export interface FullScanKitStore {
|
|
12
|
+
listFiles(kitId: string): Promise<Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
}>>;
|
|
15
|
+
readFile(kitId: string, path: string): Promise<{
|
|
16
|
+
content: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/** AC6 — the shape `validate`'s full-scan facet returns. */
|
|
20
|
+
export interface FullScanResult {
|
|
21
|
+
/** Paths whose first line fails the M3-01 `@genie` marker regex (AC3). */
|
|
22
|
+
markerMissing: string[];
|
|
23
|
+
/** Paths whose rendered content height is below `meta.json`'s
|
|
24
|
+
* `renderCheck.minHeight` (default 80px) (AC4). */
|
|
25
|
+
thin: string[];
|
|
26
|
+
/** Paths whose render perceptually hashes the same as another path's (AC5). */
|
|
27
|
+
variantsIdentical: string[];
|
|
28
|
+
/** Total `.html` files inspected — i.e. successfully read (AC6). */
|
|
29
|
+
total: number;
|
|
30
|
+
/** `markerMissing.length + thin.length + variantsIdentical.length` (AC6). */
|
|
31
|
+
bad: number;
|
|
32
|
+
}
|
|
33
|
+
/** Injected dependencies for {@link fullScan}. */
|
|
34
|
+
export interface FullScanDeps {
|
|
35
|
+
/** Read port over the kit (AC3 enumerate + read). Required. */
|
|
36
|
+
kitStore: FullScanKitStore;
|
|
37
|
+
/**
|
|
38
|
+
* The one headless renderer reused across the scan (AC4/AC5), or `null` to
|
|
39
|
+
* run marker-only (graceful degradation — see the module doc). The caller
|
|
40
|
+
* owns this instance's lifecycle (create once per scan, `close()` after).
|
|
41
|
+
*/
|
|
42
|
+
renderer: Renderer | null;
|
|
43
|
+
/** pHash tolerance override (bits) — defaults to `phash.ts`'s calibrated
|
|
44
|
+
* `DEFAULT_TOLERANCE_BITS`. Exposed for tuning/tests. */
|
|
45
|
+
toleranceBits?: number;
|
|
46
|
+
/** Hue-veto color tolerance override (L1 over mean ink RGB, DRO-717) —
|
|
47
|
+
* defaults to `phash.ts`'s calibrated `DEFAULT_COLOR_TOLERANCE_L1`. Exposed
|
|
48
|
+
* for tuning/tests; the veto only ever REMOVES a blockhash pairing, so raising
|
|
49
|
+
* this loosens the hue check back toward blockhash-only behavior. */
|
|
50
|
+
colorToleranceL1?: number;
|
|
51
|
+
}
|
|
52
|
+
/** The `validate` verb's input (AC2). `planId` is accepted per the verb's
|
|
53
|
+
* declared `validate({ kitId, planId? })` signature (a scan MAY run mid-write-
|
|
54
|
+
* sequence) but is not otherwise consumed by this facet — no AC ties scan
|
|
55
|
+
* behavior to it, and the full-kit scan reads the committed readable surface. */
|
|
56
|
+
export interface FullScanInput {
|
|
57
|
+
kitId: string;
|
|
58
|
+
planId?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run genie's full validator suite (AC3–AC6) against every readable `.html`
|
|
62
|
+
* file in `input.kitId`.
|
|
63
|
+
*
|
|
64
|
+
* Two passes:
|
|
65
|
+
* 1. **Read + marker (AC3).** List the kit, keep `.html` paths (sorted), read
|
|
66
|
+
* each. A path that `listFiles` advertised but `readFile` can't return
|
|
67
|
+
* (a race with a concurrent delete, a permission error) is SKIPPED
|
|
68
|
+
* entirely — not counted toward `total`, not reported as `markerMissing`
|
|
69
|
+
* (asserting a registration-contract violation on bytes you can't read
|
|
70
|
+
* would be a false positive). Every successfully-read file counts toward
|
|
71
|
+
* `total`; those whose first line fails the `@genie` regex land in
|
|
72
|
+
* `markerMissing`.
|
|
73
|
+
* 2. **Render → thin + variants (AC4/AC5).** When a renderer is present,
|
|
74
|
+
* render EVERY readable file (including marker-missing ones — a card can be
|
|
75
|
+
* both unregistered AND thin, and each finding is reported independently),
|
|
76
|
+
* measure content height against the per-component `minHeight`, and
|
|
77
|
+
* perceptual-hash the screenshot for cross-file duplicate clustering. A
|
|
78
|
+
* single file that fails to render is tolerated (skipped from thin/variants)
|
|
79
|
+
* rather than aborting the whole scan.
|
|
80
|
+
*
|
|
81
|
+
* `bad` is the literal `markerMissing.length + thin.length +
|
|
82
|
+
* variantsIdentical.length` the spec defines — the arrays MAY overlap (a
|
|
83
|
+
* marker-missing thin card counts in both), matching the M1-12 counter facet's
|
|
84
|
+
* `bad` field this shares a persistence path with.
|
|
85
|
+
*/
|
|
86
|
+
export declare function fullScan(deps: FullScanDeps, input: FullScanInput): Promise<FullScanResult>;
|
|
87
|
+
//# sourceMappingURL=full-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"full-scan.d.ts","sourceRoot":"","sources":["../../src/validate/full-scan.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrE;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;uDACmD;IACnD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,+EAA+E;IAC/E,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;CACb;AAED,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B;6DACyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;yEAGqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;iFAGiF;AACjF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA2LD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC,CAqFhG"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `validate`'s full-scan facet (M3-04 / DRO-260): the heavyweight walk that
|
|
3
|
+
* runs the `@genie` marker check (M3-01), the "thin render" check, and the
|
|
4
|
+
* "variants identical" perceptual-hash check across a kit's every `.html`
|
|
5
|
+
* file and returns structured findings (D-A: this is one facet of the single
|
|
6
|
+
* `validate` verb; the counter-persistence facet is M1-12 / `validate.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Orchestration only — the individual checks are pure/DI'd modules this file
|
|
9
|
+
* composes:
|
|
10
|
+
* - `validate/marker.ts` (M3-01, already shipped) → `markerMissing`
|
|
11
|
+
* - `validate/render.ts`'s injectable {@link Renderer} → `thin` + the
|
|
12
|
+
* screenshots `variantsIdentical` hashes
|
|
13
|
+
* - `validate/phash.ts` (this issue) → `variantsIdentical`
|
|
14
|
+
*
|
|
15
|
+
* ── Dependency injection: caller owns the seams (and the browser lifecycle) ───
|
|
16
|
+
* `fullScan` takes a {@link FullScanDeps} bag — a `FullScanKitStore` (the read
|
|
17
|
+
* slice of `KitStore`) and a `Renderer | null` — exactly as `refine.ts` takes
|
|
18
|
+
* a `RefineKitStore`/`RegionCropper`. The CALLER (the `validate` MCP tool, or a
|
|
19
|
+
* test) creates the one headless-Chromium `Renderer` reused across the whole
|
|
20
|
+
* scan (AC7's < 5 s / 50-component budget) and is responsible for `close()`-ing
|
|
21
|
+
* it afterward; `fullScan` never closes a renderer it did not create. Unit
|
|
22
|
+
* tests inject an in-memory store + a stub renderer, so the suite runs fully
|
|
23
|
+
* offline with no Playwright/Chromium.
|
|
24
|
+
*
|
|
25
|
+
* ── Graceful degradation (renderer === null) ─────────────────────────────────
|
|
26
|
+
* AC4 (thin) and AC5 (variantsIdentical) need a real render; Playwright is an
|
|
27
|
+
* OPTIONAL peer dependency (see `render.ts`). When the injected renderer is
|
|
28
|
+
* `null` (Playwright/Chromium unavailable — a stripped install, a CI runner
|
|
29
|
+
* missing the browser or its OS libs), the scan still returns `markerMissing`
|
|
30
|
+
* (pure regex) and `total`, with `thin`/`variantsIdentical` empty — rather than
|
|
31
|
+
* failing the whole `validate` call over an environment gap. Same posture as
|
|
32
|
+
* `refine.ts`'s AC7 region crop degrading to no image.
|
|
33
|
+
*
|
|
34
|
+
* ── Read-only: never perturbs card bytes (RFC G-5) ───────────────────────────
|
|
35
|
+
* Every check reads bytes the store already holds and renders them in a
|
|
36
|
+
* throwaway page; nothing here writes to the kit. `validate` is a read-side
|
|
37
|
+
* verb, so the G-5 "cards byte-identical across vehicles" guarantee is
|
|
38
|
+
* structurally impossible to violate from this module.
|
|
39
|
+
*/
|
|
40
|
+
import { validateMarker, extractViewport } from "./marker.js";
|
|
41
|
+
import { computePHash, computeColorSignature, findDuplicateClusters, } from "./phash.js";
|
|
42
|
+
/** Default "thin" gate (AC4) when a component's `meta.json` doesn't specify
|
|
43
|
+
* `renderCheck.minHeight`. */
|
|
44
|
+
const DEFAULT_MIN_HEIGHT = 80;
|
|
45
|
+
/** Render viewport used for both the thin-height measurement and the
|
|
46
|
+
* perceptual-hash screenshot when a component names NEITHER an `@genie`
|
|
47
|
+
* marker `viewport="WxH"` token NOR a `meta.json` `viewport` override — the
|
|
48
|
+
* last-resort fallback, same default `refine.ts`'s `deriveRenderViewport`
|
|
49
|
+
* falls back to, so a kit that specifies no size anywhere still gets a
|
|
50
|
+
* consistent, reasonably-sized render across every tool that renders it.
|
|
51
|
+
*
|
|
52
|
+
* ── DRO-711 QA finding: this is now the THIRD-choice source, not the second ──
|
|
53
|
+
* Real-Chromium QA (DRO-711, post-merge hardening of DRO-260/PR #152) found
|
|
54
|
+
* that `conjure.ts` — genie's only real component-authoring path — never
|
|
55
|
+
* synthesizes a `meta.json` itself; the LLM system prompt only *encourages*
|
|
56
|
+
* one ("You may also add … a `meta.json`") and the output JSON Schema does
|
|
57
|
+
* NOT require it (`files` needs just one `<Name>.html` match). So in
|
|
58
|
+
* practice, on any kit whose model output omitted `meta.json` — the common
|
|
59
|
+
* case, not an edge case — the OLD code (which read `meta.json` only, never
|
|
60
|
+
* the marker) silently rendered every card at this generic 400×300 canvas
|
|
61
|
+
* regardless of the component's real declared size. Two confirmed effects:
|
|
62
|
+
* - **Genuine thin false positives on responsive/percentage-sized content.**
|
|
63
|
+
* A card whose body uses `width: 100%` (or similar) wraps its text
|
|
64
|
+
* differently — and so measures a genuinely different `contentHeight` —
|
|
65
|
+
* depending on which viewport it's rendered at. A 240×120-declared card
|
|
66
|
+
* measured 120px tall at its OWN size but only 72px (under the 80px
|
|
67
|
+
* `DEFAULT_MIN_HEIGHT`) forced into the old hardcoded 400×300 canvas —
|
|
68
|
+
* a real, viewport-CAUSED thin misclassification, confirmed on real
|
|
69
|
+
* Chromium renders.
|
|
70
|
+
* - **AC5 pHash dilution.** The screenshot fed to `computePHash` is the
|
|
71
|
+
* WHOLE canvas, so an oversized, mostly-blank canvas shrinks a small
|
|
72
|
+
* component's contribution to the block-grid signal. A legitimately-
|
|
73
|
+
* distinct outline-vs-filled button variant hashed only 4 bits apart (AT
|
|
74
|
+
* `DEFAULT_TOLERANCE_BITS`, not above it — a false "identical" pairing)
|
|
75
|
+
* at 400×300, vs. 12 bits apart when rendered at the marker's own
|
|
76
|
+
* declared 320×96.
|
|
77
|
+
* (NOT claimed: that this fix changes the thin verdict for every small,
|
|
78
|
+
* FIXED-size element, e.g. a single-line button with hardcoded padding —
|
|
79
|
+
* that box measures the same height regardless of the surrounding canvas,
|
|
80
|
+
* confirmed on real renders. Whether `DEFAULT_MIN_HEIGHT` itself is well
|
|
81
|
+
* calibrated for small-but-legitimate fixed-size components is a SEPARATE,
|
|
82
|
+
* still-open question a DRO-711 follow-up issue tracks — this fix only
|
|
83
|
+
* corrects which viewport reaches the renderer, not the 80px threshold.)
|
|
84
|
+
*
|
|
85
|
+
* The fix: `extractViewport` (already shipped by M3-01's `validate/marker.ts`,
|
|
86
|
+
* but — until this fix — used only by this module's own tests; production
|
|
87
|
+
* code never called it) reads the marker line's OWN `viewport="WxH"` token —
|
|
88
|
+
* present on every well-formed `@genie` card, because the system prompt asks
|
|
89
|
+
* the model for one. That becomes the primary viewport source here; a
|
|
90
|
+
* `meta.json` `viewport` (when the model DID emit one) still wins as an
|
|
91
|
+
* explicit override, since `meta.json` is the intentionally-mutable "override
|
|
92
|
+
* the marker" surface (RFC §7.3) — this is NOT the same precedence
|
|
93
|
+
* `manifest/compiler.ts`'s `compileManifest` uses for its own `viewport`
|
|
94
|
+
* field, which reads the marker token exclusively and never lets a sibling
|
|
95
|
+
* `meta.json` supersede it (`meta.json` there only supplies `subtitle`/`tags`);
|
|
96
|
+
* two different modules answering two different questions ("what do we
|
|
97
|
+
* render at" vs. "what do we list the card's declared size as") are free to
|
|
98
|
+
* resolve viewport precedence differently, and do. `refine.ts`'s
|
|
99
|
+
* `deriveRenderViewport` is a third, independent precedent: it reads
|
|
100
|
+
* `meta.json` only and has no marker fallback at all. `DEFAULT_VIEWPORT` here
|
|
101
|
+
* is the last resort for the rare marker with no viewport attribute at all. */
|
|
102
|
+
const DEFAULT_VIEWPORT = { width: 400, height: 300 };
|
|
103
|
+
/**
|
|
104
|
+
* Resolve a component's `{ minHeight, viewport }` for the render pass.
|
|
105
|
+
*
|
|
106
|
+
* Precedence (DRO-711 fix — see {@link DEFAULT_VIEWPORT}'s doc for the real-
|
|
107
|
+
* render QA evidence behind this order):
|
|
108
|
+
* 1. `meta.json`'s `viewport` / `renderCheck.minHeight`, when present and
|
|
109
|
+
* well-formed — an explicit per-component override always wins.
|
|
110
|
+
* 2. The `@genie` marker's own `viewport="WxH"` token (`markerFirstLine`,
|
|
111
|
+
* via `extractViewport`) — present on every well-formed card in
|
|
112
|
+
* practice (the system prompt asks the model for one), so this is the
|
|
113
|
+
* REALISTIC default, not the generic fallback. Used for viewport only;
|
|
114
|
+
* the marker convention has no `minHeight` attribute, so `minHeight`
|
|
115
|
+
* still falls through to step 3 unless `meta.json` set it.
|
|
116
|
+
* 3. `DEFAULT_VIEWPORT` / `DEFAULT_MIN_HEIGHT` — the last-resort constants,
|
|
117
|
+
* now reached only when NEITHER a marker viewport NOR a meta.json
|
|
118
|
+
* viewport is available (a marker with no `viewport` attribute at all).
|
|
119
|
+
*
|
|
120
|
+
* Each field defaults INDEPENDENTLY at every step — a broken `minHeight`
|
|
121
|
+
* doesn't discard a good `viewport` and vice-versa.
|
|
122
|
+
*/
|
|
123
|
+
function parseComponentMeta(raw, markerFirstLine) {
|
|
124
|
+
const markerViewport = extractViewport(markerFirstLine);
|
|
125
|
+
const result = {
|
|
126
|
+
minHeight: DEFAULT_MIN_HEIGHT,
|
|
127
|
+
viewport: markerViewport ? { ...markerViewport } : { ...DEFAULT_VIEWPORT },
|
|
128
|
+
};
|
|
129
|
+
if (raw === undefined)
|
|
130
|
+
return result;
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(raw);
|
|
133
|
+
const minHeight = parsed.renderCheck?.minHeight;
|
|
134
|
+
if (typeof minHeight === "number" && Number.isFinite(minHeight) && minHeight >= 0) {
|
|
135
|
+
result.minHeight = minHeight;
|
|
136
|
+
}
|
|
137
|
+
const w = parsed.viewport?.width;
|
|
138
|
+
const h = parsed.viewport?.height;
|
|
139
|
+
if (typeof w === "number" &&
|
|
140
|
+
typeof h === "number" &&
|
|
141
|
+
Number.isFinite(w) &&
|
|
142
|
+
Number.isFinite(h) &&
|
|
143
|
+
w > 0 &&
|
|
144
|
+
h > 0) {
|
|
145
|
+
result.viewport = { width: w, height: h };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Malformed meta.json — keep whatever step 1/2 already resolved.
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
/** Derive a `.html` file's sibling `meta.json` kit-relative path — same
|
|
154
|
+
* directory, fixed basename (the `components/<group>/<Name>/meta.json`
|
|
155
|
+
* convention every M3-adjacent module assumes, e.g. `manifest/compiler.ts`'s
|
|
156
|
+
* sibling-read). A root-level `.html` (no `/`) maps to a root `meta.json`. */
|
|
157
|
+
function siblingMetaPath(htmlPath) {
|
|
158
|
+
const lastSlash = htmlPath.lastIndexOf("/");
|
|
159
|
+
const dir = lastSlash === -1 ? "" : htmlPath.slice(0, lastSlash + 1);
|
|
160
|
+
return `${dir}meta.json`;
|
|
161
|
+
}
|
|
162
|
+
/** Read `path` from the kit, tolerating absence (or any other read failure) as
|
|
163
|
+
* `undefined` — every caller here treats the file as optional/best-effort. */
|
|
164
|
+
async function readOptional(store, kitId, path) {
|
|
165
|
+
try {
|
|
166
|
+
const file = await store.readFile(kitId, path);
|
|
167
|
+
return file.content;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Simple bounded-concurrency map — renders are I/O + CPU bound in a real
|
|
174
|
+
* browser process, so unbounded `Promise.all` over a large kit would spin up
|
|
175
|
+
* far more concurrent pages than helps; a small worker pool keeps AC7's < 5 s
|
|
176
|
+
* / 50-component budget without serializing every render either. */
|
|
177
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
178
|
+
const results = new Array(items.length);
|
|
179
|
+
let next = 0;
|
|
180
|
+
async function worker() {
|
|
181
|
+
for (;;) {
|
|
182
|
+
const index = next;
|
|
183
|
+
next += 1;
|
|
184
|
+
if (index >= items.length)
|
|
185
|
+
return;
|
|
186
|
+
results[index] = await fn(items[index], index);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
190
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
191
|
+
return results;
|
|
192
|
+
}
|
|
193
|
+
/** Render concurrency for the thin/pHash pass (see {@link mapWithConcurrency}'s doc). */
|
|
194
|
+
const RENDER_CONCURRENCY = 6;
|
|
195
|
+
/** Stable lexicographic sort (ASCII) for deterministic finding arrays. */
|
|
196
|
+
function byPath(a, b) {
|
|
197
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Run genie's full validator suite (AC3–AC6) against every readable `.html`
|
|
201
|
+
* file in `input.kitId`.
|
|
202
|
+
*
|
|
203
|
+
* Two passes:
|
|
204
|
+
* 1. **Read + marker (AC3).** List the kit, keep `.html` paths (sorted), read
|
|
205
|
+
* each. A path that `listFiles` advertised but `readFile` can't return
|
|
206
|
+
* (a race with a concurrent delete, a permission error) is SKIPPED
|
|
207
|
+
* entirely — not counted toward `total`, not reported as `markerMissing`
|
|
208
|
+
* (asserting a registration-contract violation on bytes you can't read
|
|
209
|
+
* would be a false positive). Every successfully-read file counts toward
|
|
210
|
+
* `total`; those whose first line fails the `@genie` regex land in
|
|
211
|
+
* `markerMissing`.
|
|
212
|
+
* 2. **Render → thin + variants (AC4/AC5).** When a renderer is present,
|
|
213
|
+
* render EVERY readable file (including marker-missing ones — a card can be
|
|
214
|
+
* both unregistered AND thin, and each finding is reported independently),
|
|
215
|
+
* measure content height against the per-component `minHeight`, and
|
|
216
|
+
* perceptual-hash the screenshot for cross-file duplicate clustering. A
|
|
217
|
+
* single file that fails to render is tolerated (skipped from thin/variants)
|
|
218
|
+
* rather than aborting the whole scan.
|
|
219
|
+
*
|
|
220
|
+
* `bad` is the literal `markerMissing.length + thin.length +
|
|
221
|
+
* variantsIdentical.length` the spec defines — the arrays MAY overlap (a
|
|
222
|
+
* marker-missing thin card counts in both), matching the M1-12 counter facet's
|
|
223
|
+
* `bad` field this shares a persistence path with.
|
|
224
|
+
*/
|
|
225
|
+
export async function fullScan(deps, input) {
|
|
226
|
+
const { kitStore, renderer } = deps;
|
|
227
|
+
const { kitId } = input;
|
|
228
|
+
// ── Pass 1: enumerate + read + marker check (AC3) ───────────────────────────
|
|
229
|
+
const entries = await kitStore.listFiles(kitId);
|
|
230
|
+
const htmlPaths = entries
|
|
231
|
+
.map((e) => e.path)
|
|
232
|
+
.filter((p) => p.endsWith(".html"))
|
|
233
|
+
.sort(byPath);
|
|
234
|
+
const markerMissing = [];
|
|
235
|
+
/** Successfully-read previews carried into the render pass. */
|
|
236
|
+
const readable = [];
|
|
237
|
+
for (const path of htmlPaths) {
|
|
238
|
+
const content = await readOptional(kitStore, kitId, path);
|
|
239
|
+
if (content === undefined)
|
|
240
|
+
continue; // unreadable → not inspected, not counted
|
|
241
|
+
readable.push({ path, content });
|
|
242
|
+
if (!validateMarker(path, content).ok)
|
|
243
|
+
markerMissing.push(path);
|
|
244
|
+
}
|
|
245
|
+
// htmlPaths was sorted, so markerMissing is already ascending — keep the sort
|
|
246
|
+
// explicit so the guarantee survives any future reordering of pass 1.
|
|
247
|
+
markerMissing.sort(byPath);
|
|
248
|
+
const total = readable.length;
|
|
249
|
+
// ── Pass 2: render → thin (AC4) + variantsIdentical (AC5) ───────────────────
|
|
250
|
+
const thin = [];
|
|
251
|
+
let variantsIdentical = [];
|
|
252
|
+
if (renderer) {
|
|
253
|
+
const rendered = await mapWithConcurrency(readable, RENDER_CONCURRENCY, async ({ path, content }) => {
|
|
254
|
+
const metaRaw = await readOptional(kitStore, kitId, siblingMetaPath(path));
|
|
255
|
+
// DRO-711 fix: pass the preview's own first line so parseComponentMeta
|
|
256
|
+
// can prefer its marker `viewport="WxH"` token over the generic
|
|
257
|
+
// DEFAULT_VIEWPORT when meta.json is absent/silent on viewport (see
|
|
258
|
+
// parseComponentMeta's doc for why this is the common case, not an
|
|
259
|
+
// edge case). `content.split("\n", 1)[0]` mirrors validateMarker's own
|
|
260
|
+
// first-line extraction above.
|
|
261
|
+
const firstLine = content.split("\n", 1)[0] ?? "";
|
|
262
|
+
const meta = parseComponentMeta(metaRaw, firstLine);
|
|
263
|
+
try {
|
|
264
|
+
const card = await renderer.render(content, meta.viewport);
|
|
265
|
+
return { path, minHeight: meta.minHeight, card };
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// A single file that fails to render (malformed markup crashing the
|
|
269
|
+
// page, a screenshot timeout) must not abort the scan — drop its
|
|
270
|
+
// AC4/AC5 contribution. It still got its AC3 marker check + total above.
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
for (const r of rendered) {
|
|
275
|
+
if (r && r.card.contentHeight < r.minHeight)
|
|
276
|
+
thin.push(r.path);
|
|
277
|
+
}
|
|
278
|
+
thin.sort(byPath);
|
|
279
|
+
const hashedEntries = [];
|
|
280
|
+
for (const r of rendered) {
|
|
281
|
+
if (r) {
|
|
282
|
+
// DRO-717: carry a hue signature alongside the (hue-blind) blockhash so
|
|
283
|
+
// findDuplicateClusters can veto a same-layout / different-fill pair
|
|
284
|
+
// (e.g. a primary/danger/success button set) that the blockhash alone
|
|
285
|
+
// would falsely cluster. computeColorSignature returns undefined for a
|
|
286
|
+
// blank/near-white render, in which case the veto abstains for that entry.
|
|
287
|
+
hashedEntries.push({
|
|
288
|
+
path: r.path,
|
|
289
|
+
hash: computePHash(r.card.image),
|
|
290
|
+
color: computeColorSignature(r.card.image),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
variantsIdentical = findDuplicateClusters(hashedEntries, deps.toleranceBits, deps.colorToleranceL1);
|
|
295
|
+
}
|
|
296
|
+
const bad = markerMissing.length + thin.length + variantsIdentical.length;
|
|
297
|
+
return { markerMissing, thin, variantsIdentical, total, bad };
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=full-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"full-scan.js","sourceRoot":"","sources":["../../src/validate/full-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public re-export barrel for genie's `@genie` marker validator (M3-01 / AC6).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the existing `store/index.ts` / `plans/index.ts` barrel pattern:
|
|
5
|
+
* downstream consumers (the M3-02 watcher, the M3-03 manifest compiler, the
|
|
6
|
+
* M3-04 `validate` full-scan facet, and any future consumer outside this
|
|
7
|
+
* directory) import from `../validate/index.js` rather than reaching into
|
|
8
|
+
* `./marker.js` directly, so the module's internal file layout can change
|
|
9
|
+
* without breaking callers.
|
|
10
|
+
*/
|
|
11
|
+
export { MARKER_REGEX, validateMarker, extractViewport, type MarkerValidationResult, type MarkerViewport, } from "./marker.js";
|
|
12
|
+
export { fullScan, type FullScanDeps, type FullScanInput, type FullScanResult, type FullScanKitStore, } from "./full-scan.js";
|
|
13
|
+
export { createDefaultRenderer, type Renderer, type RenderedCard } from "./render.js";
|
|
14
|
+
export { computePHash, computeColorSignature, colorDistanceL1, hammingDistance, findDuplicateClusters, DEFAULT_TOLERANCE_BITS, DEFAULT_COLOR_TOLERANCE_L1, type RGBAImage, type HashedEntry, type ColorSignature, } from "./phash.js";
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAKrB,OAAO,EACL,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public re-export barrel for genie's `@genie` marker validator (M3-01 / AC6).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the existing `store/index.ts` / `plans/index.ts` barrel pattern:
|
|
5
|
+
* downstream consumers (the M3-02 watcher, the M3-03 manifest compiler, the
|
|
6
|
+
* M3-04 `validate` full-scan facet, and any future consumer outside this
|
|
7
|
+
* directory) import from `../validate/index.js` rather than reaching into
|
|
8
|
+
* `./marker.js` directly, so the module's internal file layout can change
|
|
9
|
+
* without breaking callers.
|
|
10
|
+
*/
|
|
11
|
+
export { MARKER_REGEX, validateMarker, extractViewport, } from "./marker.js";
|
|
12
|
+
// M3-04 (DRO-260) — the full-scan facet's orchestrator + its render/pHash
|
|
13
|
+
// helpers, surfaced here so `tools/validate.ts` (and any future consumer)
|
|
14
|
+
// composes them through the barrel rather than reaching into the files.
|
|
15
|
+
export { fullScan, } from "./full-scan.js";
|
|
16
|
+
export { createDefaultRenderer } from "./render.js";
|
|
17
|
+
export { computePHash, computeColorSignature, colorDistanceL1, hammingDistance, findDuplicateClusters, DEFAULT_TOLERANCE_BITS, DEFAULT_COLOR_TOLERANCE_L1, } from "./phash.js";
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validate/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,eAAe,GAGhB,MAAM,aAAa,CAAC;AAErB,0EAA0E;AAC1E,0EAA0E;AAC1E,wEAAwE;AACxE,OAAO,EACL,QAAQ,GAKT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAoC,MAAM,aAAa,CAAC;AACtF,OAAO,EACL,YAAY,EACZ,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,GAI3B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3-01 (DRO-257) — genie's own first-line `@genie` marker validator.
|
|
3
|
+
*
|
|
4
|
+
* This is genie's **registration contract** for a component preview file:
|
|
5
|
+
* every `<Name>.html` / `preview.html`
|
|
6
|
+
* must open with a comment matching {@link MARKER_REGEX}, e.g.
|
|
7
|
+
*
|
|
8
|
+
* <!-- @genie group="actions" viewport="400x200" -->
|
|
9
|
+
*
|
|
10
|
+
* A missing or malformed first line fails the build with `MARKER_MISSING`
|
|
11
|
+
* (AC4). The regex intentionally permits arbitrary additional attributes
|
|
12
|
+
* after `group` — `viewport`, `name`, `subtitle`, … — so {@link
|
|
13
|
+
* validateMarker} accepts any of them without needing to enumerate the full
|
|
14
|
+
* attribute set; {@link extractViewport} is the one attribute the manifest
|
|
15
|
+
* compiler (M3-03) and `list_components` (M1-15) need decomposed into
|
|
16
|
+
* `{ width, height }` integers (AC5), so it gets its own narrow helper
|
|
17
|
+
* rather than a general attribute parser.
|
|
18
|
+
*
|
|
19
|
+
* This module is pure and offline — no filesystem, no network — so both
|
|
20
|
+
* `KitStore` adapters (local FS, git host) and the future watcher (M3-02) /
|
|
21
|
+
* manifest compiler (M3-03) / `validate` full-scan facet (M3-04) can call it
|
|
22
|
+
* against whatever bytes they already have in hand.
|
|
23
|
+
*
|
|
24
|
+
* `MARKER_REGEX` is genie's **native** marker — do not substitute Anthropic's
|
|
25
|
+
* `@dsCard` shape here (CLAUDE.md hard rule 1 / AGENTS.md hard rule 1); that
|
|
26
|
+
* regex lives only in a future opt-in interop adapter.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* AC2 — the canonical `@genie` first-line marker regex. This is the single source of truth for the
|
|
30
|
+
* pattern: `../llm/validate.ts`'s `MARKER_REGEX_M2_07` re-exports this
|
|
31
|
+
* constant (it previously carried its own temporary duplicate literal,
|
|
32
|
+
* shipped ahead of this issue landing) rather than redefining it.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately permissive after `group="…"`: `[^>]*` allows any further
|
|
35
|
+
* attributes (`viewport`, `name`, `subtitle`, …) in any order, so adding a
|
|
36
|
+
* new optional attribute to the marker convention never requires touching
|
|
37
|
+
* this regex.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MARKER_REGEX: RegExp;
|
|
40
|
+
/** Discriminated-union result of {@link validateMarker} (AC4). */
|
|
41
|
+
export type MarkerValidationResult = {
|
|
42
|
+
ok: true;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
code: "MARKER_MISSING";
|
|
46
|
+
path: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* AC1/AC4 — validate that `content`'s first line carries genie's `@genie`
|
|
50
|
+
* marker. `path` is carried through into the failure result (not used for
|
|
51
|
+
* validation itself) so a caller batching many files can report exactly
|
|
52
|
+
* which one failed, matching the `[MARKER_MISSING] <relpath>` build-failure
|
|
53
|
+
* shape the `ERR_MARKER_MISSING` contract describes.
|
|
54
|
+
*
|
|
55
|
+
* Uses `content.split("\n", 1)[0]` per the spec's implementation note — the
|
|
56
|
+
* `limit` argument means `split` stops after producing one element, so this
|
|
57
|
+
* never allocates an array for the rest of the file. A trailing `\r` (CRLF
|
|
58
|
+
* line endings) stays attached to that first element; `MARKER_REGEX` has no
|
|
59
|
+
* trailing anchor, so a `\r` after the marker's `-->` does not affect the
|
|
60
|
+
* match (verified by the CRLF fixture in `marker.test.ts`).
|
|
61
|
+
*/
|
|
62
|
+
export declare function validateMarker(path: string, content: string): MarkerValidationResult;
|
|
63
|
+
/** Integer `{ width, height }` pair extracted from a marker's `viewport` attribute (AC5). */
|
|
64
|
+
export interface MarkerViewport {
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* AC5 — optional attribute parse: extract `viewport="WxH"` from a marker
|
|
70
|
+
* line into a structured `{ width, height }` (both integers). Returns
|
|
71
|
+
* `undefined` when the line has no `viewport` attribute, or its value isn't
|
|
72
|
+
* the strict `<digits>x<digits>` shape (e.g. a named token like `"desktop"`
|
|
73
|
+
* — `list_components`/the manifest compiler keep that case as an opaque
|
|
74
|
+
* string rather than a decomposed size).
|
|
75
|
+
*
|
|
76
|
+
* Deliberately independent of {@link validateMarker}: a caller can ask "does
|
|
77
|
+
* this line have a parseable viewport" without first requiring the whole
|
|
78
|
+
* marker to be well-formed, and the manifest compiler (M3-03) needs this
|
|
79
|
+
* extraction on its own, already-validated lines.
|
|
80
|
+
*
|
|
81
|
+
* The `(?:^|\s)` lookbehind-by-alternation requires `viewport` to start a
|
|
82
|
+
* fresh attribute (preceded by whitespace, or the very start of the string)
|
|
83
|
+
* rather than merely `\b`-bordering the previous character — `\b` alone
|
|
84
|
+
* would also fire mid-token on a hyphen (e.g. a hypothetical `data-viewport`
|
|
85
|
+
* attribute), which is not part of genie's marker convention and must not be
|
|
86
|
+
* mistaken for it.
|
|
87
|
+
*/
|
|
88
|
+
export declare function extractViewport(firstLine: string): MarkerViewport | undefined;
|
|
89
|
+
//# sourceMappingURL=marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.d.ts","sourceRoot":"","sources":["../../src/validate/marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,QAA2C,CAAC;AAErE,kEAAkE;AAClE,MAAM,MAAM,sBAAsB,GAC9B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAMpF;AAED,6FAA6F;AAC7F,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI7E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3-01 (DRO-257) — genie's own first-line `@genie` marker validator.
|
|
3
|
+
*
|
|
4
|
+
* This is genie's **registration contract** for a component preview file:
|
|
5
|
+
* every `<Name>.html` / `preview.html`
|
|
6
|
+
* must open with a comment matching {@link MARKER_REGEX}, e.g.
|
|
7
|
+
*
|
|
8
|
+
* <!-- @genie group="actions" viewport="400x200" -->
|
|
9
|
+
*
|
|
10
|
+
* A missing or malformed first line fails the build with `MARKER_MISSING`
|
|
11
|
+
* (AC4). The regex intentionally permits arbitrary additional attributes
|
|
12
|
+
* after `group` — `viewport`, `name`, `subtitle`, … — so {@link
|
|
13
|
+
* validateMarker} accepts any of them without needing to enumerate the full
|
|
14
|
+
* attribute set; {@link extractViewport} is the one attribute the manifest
|
|
15
|
+
* compiler (M3-03) and `list_components` (M1-15) need decomposed into
|
|
16
|
+
* `{ width, height }` integers (AC5), so it gets its own narrow helper
|
|
17
|
+
* rather than a general attribute parser.
|
|
18
|
+
*
|
|
19
|
+
* This module is pure and offline — no filesystem, no network — so both
|
|
20
|
+
* `KitStore` adapters (local FS, git host) and the future watcher (M3-02) /
|
|
21
|
+
* manifest compiler (M3-03) / `validate` full-scan facet (M3-04) can call it
|
|
22
|
+
* against whatever bytes they already have in hand.
|
|
23
|
+
*
|
|
24
|
+
* `MARKER_REGEX` is genie's **native** marker — do not substitute Anthropic's
|
|
25
|
+
* `@dsCard` shape here (CLAUDE.md hard rule 1 / AGENTS.md hard rule 1); that
|
|
26
|
+
* regex lives only in a future opt-in interop adapter.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* AC2 — the canonical `@genie` first-line marker regex. This is the single source of truth for the
|
|
30
|
+
* pattern: `../llm/validate.ts`'s `MARKER_REGEX_M2_07` re-exports this
|
|
31
|
+
* constant (it previously carried its own temporary duplicate literal,
|
|
32
|
+
* shipped ahead of this issue landing) rather than redefining it.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately permissive after `group="…"`: `[^>]*` allows any further
|
|
35
|
+
* attributes (`viewport`, `name`, `subtitle`, …) in any order, so adding a
|
|
36
|
+
* new optional attribute to the marker convention never requires touching
|
|
37
|
+
* this regex.
|
|
38
|
+
*/
|
|
39
|
+
export const MARKER_REGEX = /^<!--\s*@genie\s+group="[^"]*"[^>]*-->/;
|
|
40
|
+
/**
|
|
41
|
+
* AC1/AC4 — validate that `content`'s first line carries genie's `@genie`
|
|
42
|
+
* marker. `path` is carried through into the failure result (not used for
|
|
43
|
+
* validation itself) so a caller batching many files can report exactly
|
|
44
|
+
* which one failed, matching the `[MARKER_MISSING] <relpath>` build-failure
|
|
45
|
+
* shape the `ERR_MARKER_MISSING` contract describes.
|
|
46
|
+
*
|
|
47
|
+
* Uses `content.split("\n", 1)[0]` per the spec's implementation note — the
|
|
48
|
+
* `limit` argument means `split` stops after producing one element, so this
|
|
49
|
+
* never allocates an array for the rest of the file. A trailing `\r` (CRLF
|
|
50
|
+
* line endings) stays attached to that first element; `MARKER_REGEX` has no
|
|
51
|
+
* trailing anchor, so a `\r` after the marker's `-->` does not affect the
|
|
52
|
+
* match (verified by the CRLF fixture in `marker.test.ts`).
|
|
53
|
+
*/
|
|
54
|
+
export function validateMarker(path, content) {
|
|
55
|
+
const firstLine = content.split("\n", 1)[0] ?? "";
|
|
56
|
+
if (MARKER_REGEX.test(firstLine)) {
|
|
57
|
+
return { ok: true };
|
|
58
|
+
}
|
|
59
|
+
return { ok: false, code: "MARKER_MISSING", path };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* AC5 — optional attribute parse: extract `viewport="WxH"` from a marker
|
|
63
|
+
* line into a structured `{ width, height }` (both integers). Returns
|
|
64
|
+
* `undefined` when the line has no `viewport` attribute, or its value isn't
|
|
65
|
+
* the strict `<digits>x<digits>` shape (e.g. a named token like `"desktop"`
|
|
66
|
+
* — `list_components`/the manifest compiler keep that case as an opaque
|
|
67
|
+
* string rather than a decomposed size).
|
|
68
|
+
*
|
|
69
|
+
* Deliberately independent of {@link validateMarker}: a caller can ask "does
|
|
70
|
+
* this line have a parseable viewport" without first requiring the whole
|
|
71
|
+
* marker to be well-formed, and the manifest compiler (M3-03) needs this
|
|
72
|
+
* extraction on its own, already-validated lines.
|
|
73
|
+
*
|
|
74
|
+
* The `(?:^|\s)` lookbehind-by-alternation requires `viewport` to start a
|
|
75
|
+
* fresh attribute (preceded by whitespace, or the very start of the string)
|
|
76
|
+
* rather than merely `\b`-bordering the previous character — `\b` alone
|
|
77
|
+
* would also fire mid-token on a hyphen (e.g. a hypothetical `data-viewport`
|
|
78
|
+
* attribute), which is not part of genie's marker convention and must not be
|
|
79
|
+
* mistaken for it.
|
|
80
|
+
*/
|
|
81
|
+
export function extractViewport(firstLine) {
|
|
82
|
+
const match = /(?:^|\s)viewport="(\d+)x(\d+)"/.exec(firstLine);
|
|
83
|
+
if (!match)
|
|
84
|
+
return undefined;
|
|
85
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.js","sourceRoot":"","sources":["../../src/validate/marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,wCAAwC,CAAC;AAOrE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAe;IAC1D,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClD,IAAI,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC;AACrD,CAAC;AAQD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,MAAM,KAAK,GAAG,gCAAgC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/D,CAAC"}
|