@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,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool: validate (D-A — one verb, two facets).
|
|
3
|
+
*
|
|
4
|
+
* genie's `validate` verb has two facets that share this single MCP tool
|
|
5
|
+
* (`00-decisions.md` D-A folds `report_validate` + `validate_design_system`
|
|
6
|
+
* into one verb):
|
|
7
|
+
*
|
|
8
|
+
* 1. **Counter-persistence facet (M1-12).** The caller already ran validation
|
|
9
|
+
* client-side and passes aggregate `counts`; this facet persists them to a
|
|
10
|
+
* timestamped report + emits Prometheus metrics. Read-side telemetry, no
|
|
11
|
+
* planId semantics.
|
|
12
|
+
* Input: { kitId, counts: { total, bad, thin, variantsIdentical, iterations } }
|
|
13
|
+
* Output: {}
|
|
14
|
+
*
|
|
15
|
+
* 2. **Full-scan facet (M3-04 · DRO-260).** The caller passes NO `counts`;
|
|
16
|
+
* this facet does the heavyweight walk itself — the `@genie` marker check,
|
|
17
|
+
* the "thin render" check, and the "variants identical" perceptual-hash
|
|
18
|
+
* check across every `.html` file in the kit — and returns structured
|
|
19
|
+
* findings the model can act on, THEN persists the derived counters via the
|
|
20
|
+
* exact same `persistReport`/`emitMetrics` path as facet 1 (AC8: one verb,
|
|
21
|
+
* one persistence path).
|
|
22
|
+
* Input: { kitId, planId? }
|
|
23
|
+
* Output: { markerMissing, thin, variantsIdentical, total, bad }
|
|
24
|
+
*
|
|
25
|
+
* The presence of `counts` in the input discriminates the two facets — the MCP
|
|
26
|
+
* verb has one input schema, so `counts` is optional and its presence selects
|
|
27
|
+
* the persist-only path; its absence selects the full scan.
|
|
28
|
+
*/
|
|
29
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { Counter, Registry } from "prom-client";
|
|
33
|
+
import { fullScan, createDefaultRenderer, } from "../validate/index.js";
|
|
34
|
+
/** Input schema for validate (counter-persistence facet). */
|
|
35
|
+
const countsSchema = z.object({
|
|
36
|
+
total: z.number().int().nonnegative().describe("Total component count"),
|
|
37
|
+
bad: z.number().int().nonnegative().describe("Failed validation count"),
|
|
38
|
+
thin: z.number().int().nonnegative().describe("Thin component count"),
|
|
39
|
+
variantsIdentical: z.number().int().nonnegative().describe("Variants identical count"),
|
|
40
|
+
iterations: z.number().int().nonnegative().describe("Iteration count"),
|
|
41
|
+
});
|
|
42
|
+
const inputSchema = {
|
|
43
|
+
kitId: z.string().min(1).describe("UI kit identifier"),
|
|
44
|
+
// Facet 1 (counter-persistence, M1-12): when present, the caller supplies
|
|
45
|
+
// pre-computed counts and this verb only persists them. When ABSENT, facet 2
|
|
46
|
+
// (full-scan, M3-04) runs the checks itself. Optional so one MCP input schema
|
|
47
|
+
// serves both facets (D-A: one verb).
|
|
48
|
+
counts: countsSchema
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Pre-computed validation counts (counter-persistence facet). Omit to run " +
|
|
51
|
+
"the full-scan facet, which computes findings by scanning the kit."),
|
|
52
|
+
// Facet 2 (full-scan, M3-04 · AC2): optional planId, accepted per the verb's
|
|
53
|
+
// declared `validate({ kitId, planId? })` signature. Ignored by facet 1.
|
|
54
|
+
planId: z
|
|
55
|
+
.string()
|
|
56
|
+
.min(1)
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Optional plan id (full-scan facet); accepted per the verb signature."),
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Prometheus metrics registry and counters.
|
|
62
|
+
* Exposed as a module singleton so M6-01 can register it with the HTTP endpoint.
|
|
63
|
+
*/
|
|
64
|
+
export const metricsRegistry = new Registry();
|
|
65
|
+
const totalCounter = new Counter({
|
|
66
|
+
name: "genie_validate_total",
|
|
67
|
+
help: "Total components validated",
|
|
68
|
+
labelNames: ["kitId"],
|
|
69
|
+
registers: [metricsRegistry],
|
|
70
|
+
});
|
|
71
|
+
const badCounter = new Counter({
|
|
72
|
+
name: "genie_validate_bad",
|
|
73
|
+
help: "Components that failed validation",
|
|
74
|
+
labelNames: ["kitId"],
|
|
75
|
+
registers: [metricsRegistry],
|
|
76
|
+
});
|
|
77
|
+
const thinCounter = new Counter({
|
|
78
|
+
name: "genie_validate_thin",
|
|
79
|
+
help: "Thin components (minimal content)",
|
|
80
|
+
labelNames: ["kitId"],
|
|
81
|
+
registers: [metricsRegistry],
|
|
82
|
+
});
|
|
83
|
+
const variantsIdenticalCounter = new Counter({
|
|
84
|
+
name: "genie_validate_variantsIdentical",
|
|
85
|
+
help: "Components with identical variants",
|
|
86
|
+
labelNames: ["kitId"],
|
|
87
|
+
registers: [metricsRegistry],
|
|
88
|
+
});
|
|
89
|
+
const iterationsCounter = new Counter({
|
|
90
|
+
name: "genie_validate_iterations",
|
|
91
|
+
help: "Validation iterations performed",
|
|
92
|
+
labelNames: ["kitId"],
|
|
93
|
+
registers: [metricsRegistry],
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* Persist validation counts to a timestamped JSON report file.
|
|
97
|
+
*
|
|
98
|
+
* @param reportsDir - Directory where reports are stored (.genie/reports)
|
|
99
|
+
* @param kitId - Kit identifier
|
|
100
|
+
* @param counts - Validation counts to persist
|
|
101
|
+
* @returns The path to the created report file
|
|
102
|
+
*/
|
|
103
|
+
export async function persistReport(reportsDir, kitId, counts) {
|
|
104
|
+
await mkdir(reportsDir, { recursive: true });
|
|
105
|
+
const timestamp = new Date().toISOString();
|
|
106
|
+
// Sanitize for Windows-safe filenames (`:` is forbidden) and append a
|
|
107
|
+
// random hex suffix so concurrent calls within the same millisecond never
|
|
108
|
+
// collide or overwrite each other.
|
|
109
|
+
const safeTimestamp = timestamp.replace(/:/g, "-");
|
|
110
|
+
const suffix = Math.random().toString(16).slice(2, 8);
|
|
111
|
+
const reportPath = join(reportsDir, `${safeTimestamp}-${suffix}.json`);
|
|
112
|
+
const report = {
|
|
113
|
+
timestamp,
|
|
114
|
+
kitId,
|
|
115
|
+
counts,
|
|
116
|
+
};
|
|
117
|
+
await writeFile(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
118
|
+
return reportPath;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Emit Prometheus metrics for the given validation counts.
|
|
122
|
+
*
|
|
123
|
+
* @param kitId - Kit identifier (used as metric label)
|
|
124
|
+
* @param counts - Validation counts to emit
|
|
125
|
+
*/
|
|
126
|
+
export function emitMetrics(kitId, counts) {
|
|
127
|
+
totalCounter.inc({ kitId }, counts.total);
|
|
128
|
+
badCounter.inc({ kitId }, counts.bad);
|
|
129
|
+
thinCounter.inc({ kitId }, counts.thin);
|
|
130
|
+
variantsIdenticalCounter.inc({ kitId }, counts.variantsIdentical);
|
|
131
|
+
iterationsCounter.inc({ kitId }, counts.iterations);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Derive the M1-12 counter shape from a full-scan result (AC8: the full-scan
|
|
135
|
+
* facet persists its run via the SAME `persistReport`/`emitMetrics` path as the
|
|
136
|
+
* counter facet). `iterations` is 1 — a single scan pass — and the four
|
|
137
|
+
* validation dimensions come straight off the scan. `bad` is the scan's own
|
|
138
|
+
* literal sum (`markerMissing + thin + variantsIdentical`).
|
|
139
|
+
*/
|
|
140
|
+
export function countsFromScan(result) {
|
|
141
|
+
return {
|
|
142
|
+
total: result.total,
|
|
143
|
+
bad: result.bad,
|
|
144
|
+
thin: result.thin.length,
|
|
145
|
+
variantsIdentical: result.variantsIdentical.length,
|
|
146
|
+
iterations: 1,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Register the `mcp__genie__validate` tool on the given MCP server (both
|
|
151
|
+
* facets — see the module header).
|
|
152
|
+
*
|
|
153
|
+
* Backward-compatible call shape: `registerValidate(server, reportsDir?)` still
|
|
154
|
+
* works (the M1-12 wiring), and `registerValidate(server, options)` opts into
|
|
155
|
+
* the full-scan facet by supplying a `kitStore`.
|
|
156
|
+
*/
|
|
157
|
+
export function registerValidate(server, reportsDirOrOptions) {
|
|
158
|
+
const options = typeof reportsDirOrOptions === "string"
|
|
159
|
+
? { reportsDir: reportsDirOrOptions }
|
|
160
|
+
: (reportsDirOrOptions ?? {});
|
|
161
|
+
const resolvedReportsDir = options.reportsDir ?? process.env.GENIE_REPORTS_DIR ?? join(process.cwd(), ".genie", "reports");
|
|
162
|
+
const createRenderer = options.createRenderer ?? createDefaultRenderer;
|
|
163
|
+
server.registerTool("mcp__genie__validate", {
|
|
164
|
+
title: "Validate",
|
|
165
|
+
description: "genie's validate verb (two facets, one tool). WITH `counts`: persists " +
|
|
166
|
+
"pre-computed validation counts for telemetry (no planId needed — read-" +
|
|
167
|
+
"side). WITHOUT `counts`: runs the full-scan facet — @genie marker check " +
|
|
168
|
+
"+ thin-render check + variants-identical perceptual-hash check across " +
|
|
169
|
+
"the kit — and returns { markerMissing, thin, variantsIdentical, total, " +
|
|
170
|
+
"bad }, persisting the derived counters via the same path. Reach for this as an " +
|
|
171
|
+
"advisory quality audit after conjure/refine + write_files — its findings are " +
|
|
172
|
+
"counts, not blocking errors.",
|
|
173
|
+
inputSchema,
|
|
174
|
+
}, async ({ kitId, counts, planId, }) => {
|
|
175
|
+
// ── Facet 1: counter-persistence (M1-12) ────────────────────────────────
|
|
176
|
+
// Explicit `counts` → persist-only, exactly as before.
|
|
177
|
+
if (counts) {
|
|
178
|
+
await persistReport(resolvedReportsDir, kitId, counts);
|
|
179
|
+
emitMetrics(kitId, counts);
|
|
180
|
+
return { content: [{ type: "text", text: JSON.stringify({}) }] };
|
|
181
|
+
}
|
|
182
|
+
// ── Facet 2: full-scan (M3-04 · DRO-260) ────────────────────────────────
|
|
183
|
+
// No `counts` → run the checks ourselves. Needs a kit store to read the
|
|
184
|
+
// tree; without one, the facet is not available in this deployment.
|
|
185
|
+
if (!options.kitStore) {
|
|
186
|
+
return {
|
|
187
|
+
isError: true,
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: JSON.stringify({
|
|
192
|
+
error: "ERR_FULLSCAN_UNAVAILABLE",
|
|
193
|
+
message: "The full-scan facet requires a kit store; this validate tool was " +
|
|
194
|
+
"registered without one. Pass pre-computed `counts` to use the " +
|
|
195
|
+
"counter-persistence facet instead.",
|
|
196
|
+
}),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// One renderer per scan (AC7), created here and closed after — the
|
|
202
|
+
// orchestrator never closes a renderer it did not create. A `null`
|
|
203
|
+
// renderer (Playwright/Chromium unavailable) makes the scan degrade to
|
|
204
|
+
// marker-only rather than fail (same posture as `refine`'s region crop).
|
|
205
|
+
const renderer = await createRenderer();
|
|
206
|
+
let result;
|
|
207
|
+
try {
|
|
208
|
+
result = await fullScan({ kitStore: options.kitStore, renderer }, { kitId, planId });
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
if (renderer)
|
|
212
|
+
await renderer.close();
|
|
213
|
+
}
|
|
214
|
+
// AC8 — persist the derived counters through the SAME path as facet 1.
|
|
215
|
+
const derived = countsFromScan(result);
|
|
216
|
+
await persistReport(resolvedReportsDir, kitId, derived);
|
|
217
|
+
emitMetrics(kitId, derived);
|
|
218
|
+
// Return the structured findings (the heavyweight facet's whole point).
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/tools/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { KitStore } from "../store/interface.js";
|
|
3
|
+
export declare const WRITE_FILES_TOOL_NAME = "mcp__genie__write_files";
|
|
4
|
+
/** M1-08 AC3 — hard ceiling on files per call. */
|
|
5
|
+
export declare const MAX_FILES_PER_CALL = 256;
|
|
6
|
+
/**
|
|
7
|
+
* Byte-cap for a single `write_files` call's total decoded payload.
|
|
8
|
+
*
|
|
9
|
+
* RFC §17.5 flags Anthropic's exact server-side cap as unconfirmed for their
|
|
10
|
+
* hosted endpoint; PRD §EC-029 sets genie's own default at 16 MiB with a
|
|
11
|
+
* configurable ceiling. `GENIE_WRITE_BYTE_CAP` overrides the default; values
|
|
12
|
+
* that are non-numeric or ≤ 0 fall back to it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_WRITE_BYTE_CAP: number;
|
|
15
|
+
/** M1-08 AC3 — more than 256 files were supplied to `write_files` in one call. */
|
|
16
|
+
export declare class TooManyFilesError extends Error {
|
|
17
|
+
readonly count: number;
|
|
18
|
+
readonly max: number;
|
|
19
|
+
readonly code = "TooManyFilesError";
|
|
20
|
+
constructor(count: number, max: number);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Two or more files in the same call target the same `path` (Copilot review
|
|
24
|
+
* finding on PR #106). Without this check, `resolvedLocalPaths` — keyed by
|
|
25
|
+
* `file.path` — would silently drop all but the last entry's `localPath`
|
|
26
|
+
* (so an earlier duplicate would stage the WRONG file's bytes), and
|
|
27
|
+
* `writtenPaths` would list the same path twice as if two distinct files had
|
|
28
|
+
* been written, when in fact whichever entry committed last (an unspecified,
|
|
29
|
+
* input-order-dependent race between the two renames) is the only one that
|
|
30
|
+
* survives. Rejected up front, before any per-file validation, so partial
|
|
31
|
+
* ambiguity is never staged.
|
|
32
|
+
*/
|
|
33
|
+
export declare class DuplicatePathError extends Error {
|
|
34
|
+
readonly path: string;
|
|
35
|
+
readonly code = "DuplicatePathError";
|
|
36
|
+
constructor(path: string);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* M1-08 AC4 — a file's `path` is rejected by the plan boundary: either it
|
|
40
|
+
* doesn't match any glob in the plan's `writes` (`reason: "glob"`), or it
|
|
41
|
+
* resolves outside the plan's `localDir` even though a glob DID match
|
|
42
|
+
* (`reason: "escapesLocalDir"` — e.g. an absolute path under a permissive
|
|
43
|
+
* `**`; see the containment check in `writeFiles`). Both failure modes share
|
|
44
|
+
* one error class/code (`PathOutsidePlanError`) so callers can branch on a
|
|
45
|
+
* single `code`, but the message and `reason` field distinguish them —
|
|
46
|
+
* a Copilot review finding on PR #106 flagged the message as misleadingly
|
|
47
|
+
* glob-specific even when the actual cause was the containment check.
|
|
48
|
+
*/
|
|
49
|
+
export declare class PathOutsidePlanError extends Error {
|
|
50
|
+
readonly path: string;
|
|
51
|
+
readonly reason: "glob" | "escapesLocalDir";
|
|
52
|
+
readonly code = "PathOutsidePlanError";
|
|
53
|
+
constructor(path: string, reason?: "glob" | "escapesLocalDir");
|
|
54
|
+
}
|
|
55
|
+
/** M1-08 AC6 — a file's `localPath` resolves outside the plan's `localDir`. */
|
|
56
|
+
export declare class LocalPathEscapeError extends Error {
|
|
57
|
+
readonly localPath: string;
|
|
58
|
+
readonly localDir: string;
|
|
59
|
+
readonly code = "LocalPathEscapeError";
|
|
60
|
+
constructor(localPath: string, localDir: string);
|
|
61
|
+
}
|
|
62
|
+
/** M1-08 AC7 — a file set neither `localPath` nor `data`, or set both. */
|
|
63
|
+
export declare class InvalidFileInputError extends Error {
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly code = "InvalidFileInputError";
|
|
66
|
+
constructor(path: string, reason: "missing" | "both");
|
|
67
|
+
}
|
|
68
|
+
/** M1-08 AC7 — `data` was not valid base64 when `encoding: "base64"` was declared. */
|
|
69
|
+
export declare class InvalidEncodingError extends Error {
|
|
70
|
+
readonly path: string;
|
|
71
|
+
readonly code = "InvalidEncodingError";
|
|
72
|
+
constructor(path: string);
|
|
73
|
+
}
|
|
74
|
+
/** M1-08 AC9 — the call's total decoded byte size exceeds the configured cap. */
|
|
75
|
+
export declare class PayloadTooLargeError extends Error {
|
|
76
|
+
readonly totalBytes: number;
|
|
77
|
+
readonly maxBytes: number;
|
|
78
|
+
readonly retryMaxFiles: number;
|
|
79
|
+
readonly code = "PayloadTooLargeError";
|
|
80
|
+
constructor(totalBytes: number, maxBytes: number, retryMaxFiles: number);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* AC10 write-failure taxonomy now lives in the store layer (`store/interface.ts`),
|
|
84
|
+
* because the store owns the atomic write transaction after the DRO-565 re-plumb.
|
|
85
|
+
* Re-exported here so existing importers of these names from `write_files.js`
|
|
86
|
+
* (tests, in-process callers) keep resolving them.
|
|
87
|
+
*/
|
|
88
|
+
export { RollbackIncompleteError, WriteFailedError } from "../store/interface.js";
|
|
89
|
+
interface FileInput {
|
|
90
|
+
path: string;
|
|
91
|
+
localPath?: string;
|
|
92
|
+
data?: string;
|
|
93
|
+
encoding?: "utf-8" | "base64";
|
|
94
|
+
mimeType?: string;
|
|
95
|
+
}
|
|
96
|
+
interface WriteFilesArgs {
|
|
97
|
+
planId: string;
|
|
98
|
+
files: FileInput[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Register the `mcp__genie__write_files` tool.
|
|
102
|
+
*
|
|
103
|
+
* @param server The MCP server to register against. `planId`/`writes` are
|
|
104
|
+
* validated against the shared M1-07 plan registry
|
|
105
|
+
* (`../plans/index.ts`) via the M1-13 plan-guard middleware
|
|
106
|
+
* (`../middleware/plan-guard.ts`) — the guard owns AC5's planId
|
|
107
|
+
* presence/existence/expiry checks and AC4's path-vs-`writes`-
|
|
108
|
+
* glob check; this file's own remaining validation covers
|
|
109
|
+
* per-file structural rules (duplicates, localPath containment,
|
|
110
|
+
* encoding, byte cap) that the guard is intentionally
|
|
111
|
+
* tool-agnostic about.
|
|
112
|
+
* @param kitStore The injected kit backend (M1-14a-1b / DRO-565). The physical
|
|
113
|
+
* write is `kitStore.writeFiles(plan.kitId, ops)` — the kit is
|
|
114
|
+
* the destination (same surface read_file/list_files see), and
|
|
115
|
+
* the atomic rename-transaction (LocalFs) / contents-API commit
|
|
116
|
+
* (GitHost) lives behind that primitive. Mirrors how
|
|
117
|
+
* `registerDeleteFilesTool(server, kitStore)` routes deletes.
|
|
118
|
+
*/
|
|
119
|
+
export declare function registerWriteFilesTool(server: McpServer, kitStore: KitStore): void;
|
|
120
|
+
export interface WriteFilesResult extends Record<string, unknown> {
|
|
121
|
+
writtenPaths: string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate + execute a `write_files` call against a resolved plan, committing
|
|
125
|
+
* the batch into the plan's KIT via the injected store.
|
|
126
|
+
*
|
|
127
|
+
* Order of validation (fails fast, before any write) — keep this list in
|
|
128
|
+
* lockstep with the code below; downstream steps assume every prior step has
|
|
129
|
+
* passed (e.g. the `resolvedLocalPaths` map keyed by `file.path` assumes step 3
|
|
130
|
+
* rejected duplicates):
|
|
131
|
+
* 1. AC5 — planId exists + not expired (`getPlan`, from the M1-07
|
|
132
|
+
* plan registry — throws `PlanNotFoundError` for either
|
|
133
|
+
* failure mode).
|
|
134
|
+
* 2. AC3 — `files.length <= 256`.
|
|
135
|
+
* 3. Structural — no two `files[]` entries share the same destination
|
|
136
|
+
* `path` (`DuplicatePathError`; Copilot review finding
|
|
137
|
+
* on PR #106).
|
|
138
|
+
* 4. AC4 — every `path` matches a plan `writes` glob.
|
|
139
|
+
* 5. Security — every destination `path` is kit-relative and contained
|
|
140
|
+
* (no absolute path, no `..`/`.` segment). Destinations
|
|
141
|
+
* resolve against the KIT now (not `localDir`), so this is
|
|
142
|
+
* a lexical containment check (`PathOutsidePlanError` with
|
|
143
|
+
* `reason: "escapesLocalDir"`; RFC §10 T-13). The store
|
|
144
|
+
* re-checks via `safePath` as defense-in-depth.
|
|
145
|
+
* 6. AC7 — every file sets exactly one of `localPath` / `data`.
|
|
146
|
+
* 7. AC6 — every `localPath` resolves inside the plan's `localDir`
|
|
147
|
+
* (the SOURCE base — where uploads are read from).
|
|
148
|
+
* 8. AC9 — total decoded payload size <= the configured byte cap.
|
|
149
|
+
* Only once every file in the batch passes is the batch handed to
|
|
150
|
+
* `store.writeFiles(plan.kitId, ops)` for the atomic commit (AC10).
|
|
151
|
+
*
|
|
152
|
+
* @param store The injected kit backend. The destination is `plan.kitId`; the
|
|
153
|
+
* physical atomic write (rename transaction on LocalFs / contents-API commit
|
|
154
|
+
* on GitHost) lives behind `store.writeFiles`, so this function is now purely
|
|
155
|
+
* validation + WriteOp assembly.
|
|
156
|
+
*/
|
|
157
|
+
export declare function writeFiles(store: KitStore, args: WriteFilesArgs, env?: Record<string, string | undefined>): Promise<WriteFilesResult>;
|
|
158
|
+
//# sourceMappingURL=write_files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write_files.d.ts","sourceRoot":"","sources":["../../src/tools/write_files.ts"],"names":[],"mappings":"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"}
|