@webpresso/agent-kit 3.3.2 → 3.3.4
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/THIRD-PARTY-NOTICES.md +12 -10
- package/catalog/AGENTS.md.tpl +3 -5
- package/catalog/agent/rules/changeset-release.md +15 -4
- package/catalog/agent/rules/managed-tool-binaries.md +67 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
- package/catalog/agent/rules/pre-implementation.md +36 -20
- package/catalog/agent/rules/rtk-routing.md +22 -16
- package/catalog/agent/skills/autopilot/SKILL.md +4 -3
- package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
- package/catalog/agent/skills/verify/SKILL.md +12 -12
- package/catalog/compose/admin-blocks-registry.json +659 -0
- package/dist/esm/audit/hook-surface.js +5 -1
- package/dist/esm/audit/repo-guardrails.d.ts +8 -0
- package/dist/esm/audit/repo-guardrails.js +71 -1
- package/dist/esm/blueprint/core/parser.js +40 -16
- package/dist/esm/blueprint/core/validation/criteria.js +2 -1
- package/dist/esm/blueprint/execution/artifacts.js +8 -4
- package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
- package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
- package/dist/esm/blueprint/execution/metadata.js +8 -4
- package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
- package/dist/esm/blueprint/lifecycle/audit.js +89 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +148 -40
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
- package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
- package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
- package/dist/esm/blueprint/markdown/helpers.js +10 -4
- package/dist/esm/blueprint/tracked-document/parser.js +3 -3
- package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
- package/dist/esm/blueprint/trust/gate-row.js +113 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
- package/dist/esm/blueprint/trust/promotion.js +19 -19
- package/dist/esm/build/atomic-file-copy.d.ts +32 -0
- package/dist/esm/build/atomic-file-copy.js +44 -0
- package/dist/esm/build/cli-mcp-parity.js +3 -2
- package/dist/esm/build/package-manifest.js +7 -2
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
- package/dist/esm/cli/commands/audit-core.js +9 -1
- package/dist/esm/cli/commands/audit.js +16 -5
- package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
- package/dist/esm/cli/commands/compile.d.ts +13 -0
- package/dist/esm/cli/commands/compile.js +182 -62
- package/dist/esm/cli/commands/format.js +17 -1
- package/dist/esm/cli/commands/gain/index.js +10 -3
- package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
- package/dist/esm/cli/commands/init/index.js +29 -48
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +54 -4
- package/dist/esm/cli/commands/init/mcp-spec.js +126 -22
- package/dist/esm/cli/commands/init/merge.js +30 -10
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
- package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
- package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
- package/dist/esm/cli/commands/package-manager.js +8 -2
- package/dist/esm/cli/commands/pr-help.js +1 -1
- package/dist/esm/cli/commands/pr.js +18 -4
- package/dist/esm/cli/commands/review.js +60 -6
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
- package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
- package/dist/esm/cli/commands/worktree/router.js +5 -2
- package/dist/esm/cli/direct-provider-launch.js +12 -4
- package/dist/esm/compiler/flatten.d.ts +10 -1
- package/dist/esm/compiler/flatten.js +14 -4
- package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
- package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
- package/dist/esm/compose/compose.d.ts +46 -0
- package/dist/esm/compose/compose.js +250 -0
- package/dist/esm/compose/escape-html.d.ts +5 -0
- package/dist/esm/compose/escape-html.js +12 -0
- package/dist/esm/compose/ops-report-render.d.ts +5 -0
- package/dist/esm/compose/ops-report-render.js +71 -0
- package/dist/esm/compose/ops-report-schema.d.ts +42 -0
- package/dist/esm/compose/ops-report-schema.js +60 -0
- package/dist/esm/compose/preview.d.ts +14 -0
- package/dist/esm/compose/preview.js +32 -0
- package/dist/esm/compose/registry-resolve.d.ts +64 -0
- package/dist/esm/compose/registry-resolve.js +200 -0
- package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
- package/dist/esm/config/oxlint/import-hygiene.js +21 -8
- package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
- package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
- package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
- package/dist/esm/config/oxlint/oxlintrc.js +18 -21
- package/dist/esm/config/oxlint/path-roles.js +53 -0
- package/dist/esm/config/oxlint/query-patterns.js +2 -5
- package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
- package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
- package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
- package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
- package/dist/esm/daemon/domains/launch.d.ts +25 -0
- package/dist/esm/daemon/domains/launch.js +30 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
- package/dist/esm/daemon/handlers/product.d.ts +10 -0
- package/dist/esm/daemon/handlers/product.js +10 -1
- package/dist/esm/daemon/methods.d.ts +21 -1
- package/dist/esm/daemon/methods.js +20 -0
- package/dist/esm/daemon/protocol/contract.d.ts +58 -0
- package/dist/esm/daemon/protocol/contract.js +38 -0
- package/dist/esm/daemon/server.js +7 -0
- package/dist/esm/docs-linter/blueprint-plan.js +2 -2
- package/dist/esm/errors/wp-error.js +21 -2
- package/dist/esm/git/changed-files.d.ts +20 -0
- package/dist/esm/git/changed-files.js +63 -0
- package/dist/esm/hooks/doctor.d.ts +11 -0
- package/dist/esm/hooks/doctor.js +114 -45
- package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
- package/dist/esm/hooks/pretool-guard/runner.js +6 -0
- package/dist/esm/hooks/status/index.js +1 -1
- package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
- package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
- package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
- package/dist/esm/mcp/blueprint/registration.js +1 -1
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +4 -0
- package/dist/esm/mcp/tools/_registry.js +8 -0
- package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
- package/dist/esm/mcp/tools/_shared/redact.js +114 -8
- package/dist/esm/mcp/tools/gain.js +28 -2
- package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
- package/dist/esm/mcp/tools/release-progress.js +110 -0
- package/dist/esm/mcp/tools/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
- package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-compose.js +96 -0
- package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-preview.js +59 -0
- package/dist/esm/package.json +2 -0
- package/dist/esm/review/lifecycle-writers.d.ts +76 -0
- package/dist/esm/review/lifecycle-writers.js +330 -0
- package/dist/esm/review/subject.js +66 -32
- package/dist/esm/status/snapshot.d.ts +5 -0
- package/dist/esm/status/snapshot.js +27 -2
- package/dist/esm/test/shard-durations.json +0 -1
- package/dist/esm/test-helpers/global-setup.js +38 -3
- package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
- package/dist/esm/tool-runtime/managed-rtk.js +275 -0
- package/dist/esm/tool-runtime/resolve-runner.js +18 -194
- package/dist/esm/utils/package-root.d.ts +32 -0
- package/dist/esm/utils/package-root.js +82 -0
- package/dist/esm/worktrees/git-metadata.d.ts +41 -0
- package/dist/esm/worktrees/git-metadata.js +48 -0
- package/package.json +27 -21
- package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
- package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
- package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
- package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
- package/dist/esm/config/oxlint/index.d.ts +0 -22
- package/dist/esm/config/oxlint/index.js +0 -28
- package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
- package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
- package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
- package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
- package/dist/esm/output-transforms/rulesync.d.ts +0 -2
- package/dist/esm/output-transforms/rulesync.js +0 -79
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ResolvedBlockRegistry } from "./registry-resolve.js";
|
|
3
|
+
export declare const composeInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4
|
+
profile: z.ZodLiteral<"admin-crud">;
|
|
5
|
+
kind: z.ZodEnum<{
|
|
6
|
+
detail: "detail";
|
|
7
|
+
form: "form";
|
|
8
|
+
list: "list";
|
|
9
|
+
}>;
|
|
10
|
+
spec: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
11
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
12
|
+
profile: z.ZodLiteral<"ops-report">;
|
|
13
|
+
spec: z.ZodUnknown;
|
|
14
|
+
}, z.core.$strict>], "profile">;
|
|
15
|
+
export type ComposeInput = z.infer<typeof composeInputSchema>;
|
|
16
|
+
export interface ComposeDiagnostic {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ComposeResult {
|
|
21
|
+
readonly ok: boolean;
|
|
22
|
+
readonly profile: "admin-crud" | "ops-report" | "unknown";
|
|
23
|
+
/** Legal admin-crud returns the input spec unchanged (validate-only). */
|
|
24
|
+
readonly spec: unknown;
|
|
25
|
+
readonly diagnostics: readonly ComposeDiagnostic[];
|
|
26
|
+
readonly registrySource?: "project" | "snapshot";
|
|
27
|
+
readonly registryVersion?: string;
|
|
28
|
+
readonly warnings: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Admin-crud only: true when top-level `blocks[]` types were checked against
|
|
31
|
+
* the registry. false when paint-path shape succeeded without blocks[] checks.
|
|
32
|
+
* Omitted for ops-report / unknown profile.
|
|
33
|
+
*/
|
|
34
|
+
readonly blockTypesValidated?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Structural paint-path shape used by platform JsonRender list consumers. */
|
|
37
|
+
export declare function isPaintPathListSpec(spec: Record<string, unknown>): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Profile-discriminated compose validation (tool-level).
|
|
40
|
+
* Registry resolve may walk the filesystem from `cwd` / `process.cwd()` when
|
|
41
|
+
* `resolvedRegistry` is not injected.
|
|
42
|
+
*/
|
|
43
|
+
export declare function composeValidate(raw: unknown, options?: {
|
|
44
|
+
readonly cwd?: string;
|
|
45
|
+
readonly resolvedRegistry?: ResolvedBlockRegistry;
|
|
46
|
+
}): ComposeResult;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { opsReportSpecSchema } from "./ops-report-schema.js";
|
|
3
|
+
import { blockNamesForView, resolveBlockRegistry, } from "./registry-resolve.js";
|
|
4
|
+
const adminKindSchema = z.enum(["list", "detail", "form"]);
|
|
5
|
+
const adminCrudInputSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
profile: z.literal("admin-crud"),
|
|
8
|
+
kind: adminKindSchema,
|
|
9
|
+
/**
|
|
10
|
+
* Page-definition `blocks[]` and/or JsonRender* list/detail/form shapes.
|
|
11
|
+
* Empty objects and non-records are illegal (honesty contract).
|
|
12
|
+
*/
|
|
13
|
+
spec: z.record(z.string(), z.unknown()),
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
const opsReportInputSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
profile: z.literal("ops-report"),
|
|
19
|
+
spec: z.unknown(),
|
|
20
|
+
})
|
|
21
|
+
.strict();
|
|
22
|
+
export const composeInputSchema = z.discriminatedUnion("profile", [
|
|
23
|
+
adminCrudInputSchema,
|
|
24
|
+
opsReportInputSchema,
|
|
25
|
+
]);
|
|
26
|
+
function zodToDiagnostics(error) {
|
|
27
|
+
return error.issues.map((issue) => ({
|
|
28
|
+
path: issue.path.length > 0 ? issue.path.join(".") : "(root)",
|
|
29
|
+
message: issue.message,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
/** Structural paint-path shape used by platform JsonRender list consumers. */
|
|
33
|
+
export function isPaintPathListSpec(spec) {
|
|
34
|
+
return typeof spec["entity"] === "string" && Array.isArray(spec["columns"]);
|
|
35
|
+
}
|
|
36
|
+
function isPaintPathDetailOrFormSpec(spec) {
|
|
37
|
+
// Lightweight honesty: non-empty object with at least one recognized key.
|
|
38
|
+
return (typeof spec["entity"] === "string" ||
|
|
39
|
+
typeof spec["title"] === "string" ||
|
|
40
|
+
Array.isArray(spec["fields"]) ||
|
|
41
|
+
Array.isArray(spec["sections"]));
|
|
42
|
+
}
|
|
43
|
+
function hasRecognizedAdminShape(kind, spec) {
|
|
44
|
+
if (Array.isArray(spec["blocks"]))
|
|
45
|
+
return true;
|
|
46
|
+
if (kind === "list")
|
|
47
|
+
return isPaintPathListSpec(spec);
|
|
48
|
+
return isPaintPathDetailOrFormSpec(spec);
|
|
49
|
+
}
|
|
50
|
+
function slotDefsForBlock(registry, kind, type) {
|
|
51
|
+
const def = registry.blocks.find((b) => b.name === type && b.view === kind);
|
|
52
|
+
if (!def?.slots || !Array.isArray(def.slots))
|
|
53
|
+
return [];
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const raw of def.slots) {
|
|
56
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
57
|
+
continue;
|
|
58
|
+
const rec = raw;
|
|
59
|
+
const name = rec["name"];
|
|
60
|
+
if (typeof name !== "string" || name.length === 0)
|
|
61
|
+
continue;
|
|
62
|
+
const acceptsRaw = rec["accepts"];
|
|
63
|
+
const accepts = Array.isArray(acceptsRaw)
|
|
64
|
+
? acceptsRaw.filter((a) => typeof a === "string")
|
|
65
|
+
: [];
|
|
66
|
+
out.push({ name, accepts });
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Slot children are only hard-checked when they look like typed registry blocks
|
|
72
|
+
* (`{ type: string, ... }`). String enums (JsonRender list actions) and ActionSpec
|
|
73
|
+
* objects (`id`/`label`) are legal paint shapes — warn that they were not
|
|
74
|
+
* registry-type-checked instead of dishonestly rejecting them.
|
|
75
|
+
*/
|
|
76
|
+
function validateSlotChildren(blockPath, block, slots, diagnostics, warnings) {
|
|
77
|
+
for (const slot of slots) {
|
|
78
|
+
const value = block[slot.name];
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
continue;
|
|
81
|
+
const items = Array.isArray(value) ? value : [value];
|
|
82
|
+
const acceptsList = [...slot.accepts].sort().join(", ");
|
|
83
|
+
for (const [childIndex, child] of items.entries()) {
|
|
84
|
+
const path = `${blockPath}.${slot.name}.${childIndex}`;
|
|
85
|
+
// JsonRender-style string action enums (e.g. "create" | "export")
|
|
86
|
+
if (typeof child === "string") {
|
|
87
|
+
warnings.push(`${path}: slot "${slot.name}" string child not registry-type-checked (paint-path action enum shape)`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (child === null || typeof child !== "object" || Array.isArray(child)) {
|
|
91
|
+
warnings.push(`${path}: slot "${slot.name}" child is not a typed block object; not registry-type-checked`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const childType = child["type"];
|
|
95
|
+
if (typeof childType !== "string" || childType.length === 0) {
|
|
96
|
+
// ActionSpec-like { id, label, ... } without registry type
|
|
97
|
+
warnings.push(`${path}: slot "${slot.name}" object has no string type; not registry-type-checked`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (slot.accepts.length > 0 && !slot.accepts.includes(childType)) {
|
|
101
|
+
diagnostics.push({
|
|
102
|
+
path: `${path}.type`,
|
|
103
|
+
message: `illegal slot type "${childType}" for "${slot.name}"; accepts: ${acceptsList}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function validateAdminBlocks(kind, spec, resolved) {
|
|
110
|
+
const diagnostics = [];
|
|
111
|
+
const warnings = [];
|
|
112
|
+
const allowed = blockNamesForView(resolved.registry, kind);
|
|
113
|
+
const allowedList = [...allowed].sort().join(", ");
|
|
114
|
+
const blocks = spec["blocks"];
|
|
115
|
+
if (Object.keys(spec).length === 0) {
|
|
116
|
+
diagnostics.push({
|
|
117
|
+
path: "spec",
|
|
118
|
+
message: "admin-crud spec is empty; provide blocks[] (page composition) or a paint-path shape (e.g. list entity+columns)",
|
|
119
|
+
});
|
|
120
|
+
return { diagnostics, warnings, blockTypesValidated: false };
|
|
121
|
+
}
|
|
122
|
+
if (blocks === undefined) {
|
|
123
|
+
if (!hasRecognizedAdminShape(kind, spec)) {
|
|
124
|
+
diagnostics.push({
|
|
125
|
+
path: "spec",
|
|
126
|
+
message: "admin-crud spec has no blocks[] and no recognized paint-path fields; cannot claim registry validation",
|
|
127
|
+
});
|
|
128
|
+
return { diagnostics, warnings, blockTypesValidated: false };
|
|
129
|
+
}
|
|
130
|
+
warnings.push("admin-crud spec has no top-level blocks[]; registry block types were not checked (paint-path / JsonRender shape).");
|
|
131
|
+
return { diagnostics, warnings, blockTypesValidated: false };
|
|
132
|
+
}
|
|
133
|
+
if (!Array.isArray(blocks)) {
|
|
134
|
+
diagnostics.push({ path: "spec.blocks", message: "blocks must be an array" });
|
|
135
|
+
return { diagnostics, warnings, blockTypesValidated: false };
|
|
136
|
+
}
|
|
137
|
+
for (const [index, block] of blocks.entries()) {
|
|
138
|
+
if (block === null || typeof block !== "object" || Array.isArray(block)) {
|
|
139
|
+
diagnostics.push({ path: `spec.blocks.${index}`, message: "block must be an object" });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const record = block;
|
|
143
|
+
const type = record["type"];
|
|
144
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
145
|
+
diagnostics.push({
|
|
146
|
+
path: `spec.blocks.${index}.type`,
|
|
147
|
+
message: "block requires string type",
|
|
148
|
+
});
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!allowed.has(type)) {
|
|
152
|
+
diagnostics.push({
|
|
153
|
+
path: `spec.blocks.${index}.type`,
|
|
154
|
+
message: `illegal block type "${type}" for ${kind}; allowed: ${allowedList}`,
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const def = resolved.registry.blocks.find((b) => b.name === type && b.view === kind);
|
|
159
|
+
if (def?.deprecated === true) {
|
|
160
|
+
warnings.push(`spec.blocks.${index}.type "${type}" is deprecated in the registry`);
|
|
161
|
+
}
|
|
162
|
+
const slots = slotDefsForBlock(resolved.registry, kind, type);
|
|
163
|
+
if (slots.length > 0) {
|
|
164
|
+
validateSlotChildren(`spec.blocks.${index}`, record, slots, diagnostics, warnings);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { diagnostics, warnings, blockTypesValidated: true };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Profile-discriminated compose validation (tool-level).
|
|
171
|
+
* Registry resolve may walk the filesystem from `cwd` / `process.cwd()` when
|
|
172
|
+
* `resolvedRegistry` is not injected.
|
|
173
|
+
*/
|
|
174
|
+
export function composeValidate(raw, options = {}) {
|
|
175
|
+
const parsed = composeInputSchema.safeParse(raw);
|
|
176
|
+
if (!parsed.success) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
profile: "unknown",
|
|
180
|
+
spec: null,
|
|
181
|
+
diagnostics: zodToDiagnostics(parsed.error),
|
|
182
|
+
warnings: [],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const input = parsed.data;
|
|
186
|
+
/** Prefer original caller object identity for admin-crud passthrough. */
|
|
187
|
+
const originalSpec = raw !== null && typeof raw === "object" && !Array.isArray(raw) && "spec" in raw
|
|
188
|
+
? raw.spec
|
|
189
|
+
: input.spec;
|
|
190
|
+
if (input.profile === "ops-report") {
|
|
191
|
+
const ops = opsReportSpecSchema.safeParse(input.spec);
|
|
192
|
+
if (!ops.success) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
profile: "ops-report",
|
|
196
|
+
spec: originalSpec,
|
|
197
|
+
diagnostics: zodToDiagnostics(ops.error),
|
|
198
|
+
warnings: [],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
ok: true,
|
|
203
|
+
profile: "ops-report",
|
|
204
|
+
spec: ops.data,
|
|
205
|
+
diagnostics: [],
|
|
206
|
+
warnings: [],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// admin-crud: validate-only — return caller's spec object unchanged when legal
|
|
210
|
+
let resolved = options.resolvedRegistry;
|
|
211
|
+
if (!resolved) {
|
|
212
|
+
try {
|
|
213
|
+
resolved = resolveBlockRegistry({ cwd: options.cwd });
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
profile: "admin-crud",
|
|
220
|
+
spec: originalSpec,
|
|
221
|
+
diagnostics: [{ path: "registry", message }],
|
|
222
|
+
warnings: [],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const { diagnostics: blockDiagnostics, warnings: blockWarnings, blockTypesValidated, } = validateAdminBlocks(input.kind, input.spec, resolved);
|
|
227
|
+
const warnings = [...resolved.warnings, ...blockWarnings];
|
|
228
|
+
if (blockDiagnostics.length > 0) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
profile: "admin-crud",
|
|
232
|
+
spec: originalSpec,
|
|
233
|
+
diagnostics: blockDiagnostics,
|
|
234
|
+
registrySource: resolved.source,
|
|
235
|
+
registryVersion: resolved.registryVersion,
|
|
236
|
+
warnings,
|
|
237
|
+
blockTypesValidated,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
ok: true,
|
|
242
|
+
profile: "admin-crud",
|
|
243
|
+
spec: originalSpec,
|
|
244
|
+
diagnostics: [],
|
|
245
|
+
registrySource: resolved.source,
|
|
246
|
+
registryVersion: resolved.registryVersion,
|
|
247
|
+
warnings,
|
|
248
|
+
blockTypesValidated,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single HTML encode chokepoint for compose/ops-report output.
|
|
3
|
+
* All untrusted strings must pass through here before interpolation.
|
|
4
|
+
*/
|
|
5
|
+
export function escapeHtml(value) {
|
|
6
|
+
return value
|
|
7
|
+
.replaceAll("&", "&")
|
|
8
|
+
.replaceAll("<", "<")
|
|
9
|
+
.replaceAll(">", ">")
|
|
10
|
+
.replaceAll('"', """)
|
|
11
|
+
.replaceAll("'", "'");
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { escapeHtml } from "./escape-html.js";
|
|
2
|
+
import { opsReportSpecSchema } from "./ops-report-schema.js";
|
|
3
|
+
const CSP = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'";
|
|
4
|
+
/**
|
|
5
|
+
* Render a validated OpsReportSpec to self-contained HTML.
|
|
6
|
+
* Callers should pass already-validated specs; this re-parses for safety.
|
|
7
|
+
*/
|
|
8
|
+
export function renderOpsReportHtml(input) {
|
|
9
|
+
const spec = opsReportSpecSchema.parse(input);
|
|
10
|
+
return renderValidatedOpsReport(spec);
|
|
11
|
+
}
|
|
12
|
+
function renderValidatedOpsReport(spec) {
|
|
13
|
+
const metrics = spec.metrics
|
|
14
|
+
.map((metric) => `<div class="card"><div class="metric">${escapeHtml(String(metric.value))}</div><div>${escapeHtml(metric.label)}</div></div>`)
|
|
15
|
+
.join("");
|
|
16
|
+
const warnings = spec.warnings.length > 0
|
|
17
|
+
? `<h2>Warnings</h2><ul>${spec.warnings
|
|
18
|
+
.map((warning) => `<li>${escapeHtml(warning)}</li>`)
|
|
19
|
+
.join("")}</ul>`
|
|
20
|
+
: "";
|
|
21
|
+
const sections = spec.sections
|
|
22
|
+
.map((section) => {
|
|
23
|
+
const body = section.body ? `<p>${escapeHtml(section.body)}</p>` : "";
|
|
24
|
+
const list = section.list && section.list.length > 0
|
|
25
|
+
? `<ul>${section.list.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>`
|
|
26
|
+
: "";
|
|
27
|
+
return `<section><h2>${escapeHtml(section.heading)}</h2>${body}${list}</section>`;
|
|
28
|
+
})
|
|
29
|
+
.join("");
|
|
30
|
+
const tables = spec.tables
|
|
31
|
+
.map((table) => {
|
|
32
|
+
const title = table.title ? `<h2>${escapeHtml(table.title)}</h2>` : "";
|
|
33
|
+
const head = table.headers.map((header) => `<th>${escapeHtml(header)}</th>`).join("");
|
|
34
|
+
const body = table.rows
|
|
35
|
+
.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(String(cell))}</td>`).join("")}</tr>`)
|
|
36
|
+
.join("");
|
|
37
|
+
return `${title}<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`;
|
|
38
|
+
})
|
|
39
|
+
.join("");
|
|
40
|
+
return `<!doctype html>
|
|
41
|
+
<html lang="en">
|
|
42
|
+
<head>
|
|
43
|
+
<meta charset="utf-8">
|
|
44
|
+
<meta http-equiv="Content-Security-Policy" content="${CSP}">
|
|
45
|
+
<title>${escapeHtml(spec.title)}</title>
|
|
46
|
+
<style>
|
|
47
|
+
:root{color-scheme:light dark;font-family:Inter,system-ui,sans-serif;--bg:#f8fafc;--fg:#0f172a;--card:#fff;--muted:#64748b;--line:#cbd5e1}
|
|
48
|
+
@media (prefers-color-scheme:dark){:root{--bg:#020617;--fg:#e2e8f0;--card:#0f172a;--muted:#94a3b8;--line:#334155}}
|
|
49
|
+
body{margin:0;background:var(--bg);color:var(--fg);padding:2rem}
|
|
50
|
+
main{max-width:1200px;margin:auto}
|
|
51
|
+
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:1rem}
|
|
52
|
+
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:1rem}
|
|
53
|
+
.metric{font-size:2rem;font-weight:800}
|
|
54
|
+
table{width:100%;border-collapse:collapse;background:var(--card);border:1px solid var(--line);border-radius:14px;overflow:hidden;margin:1rem 0}
|
|
55
|
+
th,td{text-align:left;padding:.75rem;border-bottom:1px solid var(--line);vertical-align:top}
|
|
56
|
+
th{color:var(--muted);font-size:.8rem;text-transform:uppercase}
|
|
57
|
+
.muted{color:var(--muted)}
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<main>
|
|
62
|
+
<h1>${escapeHtml(spec.title)}</h1>
|
|
63
|
+
<p class="muted">Generated ${escapeHtml(spec.generatedAt)}. Self-contained local report; no external assets.</p>
|
|
64
|
+
${warnings}
|
|
65
|
+
${metrics ? `<section class="cards">${metrics}</section>` : ""}
|
|
66
|
+
${sections}
|
|
67
|
+
${tables}
|
|
68
|
+
</main>
|
|
69
|
+
</body>
|
|
70
|
+
</html>`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Flat section only — no nesting (plan Fx17). */
|
|
3
|
+
export declare const opsReportSectionSchema: z.ZodObject<{
|
|
4
|
+
heading: z.ZodString;
|
|
5
|
+
body: z.ZodOptional<z.ZodString>;
|
|
6
|
+
list: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export declare const opsReportMetricSchema: z.ZodObject<{
|
|
9
|
+
label: z.ZodString;
|
|
10
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export declare const opsReportTableSchema: z.ZodObject<{
|
|
13
|
+
title: z.ZodOptional<z.ZodString>;
|
|
14
|
+
headers: z.ZodArray<z.ZodString>;
|
|
15
|
+
rows: z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
/**
|
|
18
|
+
* Minimal ops-report IR. Not a general page AST.
|
|
19
|
+
* Nesting under sections is rejected by the section schema (flat only).
|
|
20
|
+
*/
|
|
21
|
+
/** Flat ops-report only; section objects are `.strict()` so nested keys fail at the section boundary. */
|
|
22
|
+
export declare const opsReportSpecSchema: z.ZodObject<{
|
|
23
|
+
title: z.ZodString;
|
|
24
|
+
generatedAt: z.ZodString;
|
|
25
|
+
metrics: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
26
|
+
label: z.ZodString;
|
|
27
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
|
|
28
|
+
}, z.core.$strict>>>>;
|
|
29
|
+
tables: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
30
|
+
title: z.ZodOptional<z.ZodString>;
|
|
31
|
+
headers: z.ZodArray<z.ZodString>;
|
|
32
|
+
rows: z.ZodArray<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
33
|
+
}, z.core.$strict>>>>;
|
|
34
|
+
warnings: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
35
|
+
sections: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
36
|
+
heading: z.ZodString;
|
|
37
|
+
body: z.ZodOptional<z.ZodString>;
|
|
38
|
+
list: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
|
+
}, z.core.$strict>>>>;
|
|
40
|
+
}, z.core.$strict>;
|
|
41
|
+
export type OpsReportSpec = z.infer<typeof opsReportSpecSchema>;
|
|
42
|
+
export type OpsReportSection = z.infer<typeof opsReportSectionSchema>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Flat section only — no nesting (plan Fx17). */
|
|
3
|
+
export const opsReportSectionSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
heading: z.string().min(1),
|
|
6
|
+
body: z.string().optional(),
|
|
7
|
+
list: z.array(z.string()).optional(),
|
|
8
|
+
})
|
|
9
|
+
.strict()
|
|
10
|
+
.superRefine((section, ctx) => {
|
|
11
|
+
if (section.body === undefined && section.list === undefined) {
|
|
12
|
+
ctx.addIssue({
|
|
13
|
+
code: "custom",
|
|
14
|
+
message: "section requires body and/or list",
|
|
15
|
+
path: ["body"],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const finiteNumber = z.number().finite();
|
|
20
|
+
const cellValue = z.union([z.string(), finiteNumber]);
|
|
21
|
+
export const opsReportMetricSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
label: z.string().min(1),
|
|
24
|
+
value: cellValue,
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
export const opsReportTableSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
title: z.string().optional(),
|
|
30
|
+
headers: z.array(z.string()).min(1),
|
|
31
|
+
rows: z.array(z.array(cellValue)),
|
|
32
|
+
})
|
|
33
|
+
.strict()
|
|
34
|
+
.superRefine((table, ctx) => {
|
|
35
|
+
const expected = table.headers.length;
|
|
36
|
+
for (const [rowIndex, row] of table.rows.entries()) {
|
|
37
|
+
if (row.length !== expected) {
|
|
38
|
+
ctx.addIssue({
|
|
39
|
+
code: "custom",
|
|
40
|
+
message: `row has ${row.length} cell(s); expected ${expected} to match headers`,
|
|
41
|
+
path: ["rows", rowIndex],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Minimal ops-report IR. Not a general page AST.
|
|
48
|
+
* Nesting under sections is rejected by the section schema (flat only).
|
|
49
|
+
*/
|
|
50
|
+
/** Flat ops-report only; section objects are `.strict()` so nested keys fail at the section boundary. */
|
|
51
|
+
export const opsReportSpecSchema = z
|
|
52
|
+
.object({
|
|
53
|
+
title: z.string().min(1),
|
|
54
|
+
generatedAt: z.string().min(1),
|
|
55
|
+
metrics: z.array(opsReportMetricSchema).optional().default([]),
|
|
56
|
+
tables: z.array(opsReportTableSchema).optional().default([]),
|
|
57
|
+
warnings: z.array(z.string()).optional().default([]),
|
|
58
|
+
sections: z.array(opsReportSectionSchema).optional().default([]),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PreviewOpsReportOptions {
|
|
2
|
+
readonly cwd?: string;
|
|
3
|
+
readonly spec: unknown;
|
|
4
|
+
/** Override output path (tests). */
|
|
5
|
+
readonly outputPath?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PreviewOpsReportResult {
|
|
8
|
+
readonly path: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate ops-report spec, write HTML under state-root (or override), return path only.
|
|
12
|
+
* No browser open.
|
|
13
|
+
*/
|
|
14
|
+
export declare function previewOpsReport(options: PreviewOpsReportOptions): PreviewOpsReportResult;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { getSurfacePath, NotInGitRepoError } from "#paths/state-root.js";
|
|
4
|
+
import { composeValidate } from "./compose.js";
|
|
5
|
+
import { renderOpsReportHtml } from "./ops-report-render.js";
|
|
6
|
+
/**
|
|
7
|
+
* Validate ops-report spec, write HTML under state-root (or override), return path only.
|
|
8
|
+
* No browser open.
|
|
9
|
+
*/
|
|
10
|
+
export function previewOpsReport(options) {
|
|
11
|
+
const composed = composeValidate({ profile: "ops-report", spec: options.spec }, { cwd: options.cwd });
|
|
12
|
+
if (!composed.ok) {
|
|
13
|
+
const detail = composed.diagnostics.map((d) => `${d.path}: ${d.message}`).join("; ");
|
|
14
|
+
throw new Error(`ops-report compose failed: ${detail}`);
|
|
15
|
+
}
|
|
16
|
+
const html = renderOpsReportHtml(composed.spec);
|
|
17
|
+
const path = options.outputPath ??
|
|
18
|
+
(() => {
|
|
19
|
+
try {
|
|
20
|
+
return getSurfacePath("compose/ops-report.html", "worktree", options.cwd);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (error instanceof NotInGitRepoError) {
|
|
24
|
+
return join(options.cwd ?? process.cwd(), ".webpresso", "compose", "ops-report.html");
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
writeFileSync(path, html, "utf8");
|
|
31
|
+
return { path };
|
|
32
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Local reader capability floor for project registries (agent-kit consumer).
|
|
4
|
+
* Bump in the same PR as catalog/compose/admin-blocks-registry.json major/minor
|
|
5
|
+
* advances. Does not import @webpresso/schema (SEA / nondeterminism lock).
|
|
6
|
+
*/
|
|
7
|
+
export declare const READER_REGISTRY_VERSION: "1.0.0";
|
|
8
|
+
export declare const blockRegistrySchema: z.ZodObject<{
|
|
9
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
10
|
+
version: z.ZodString;
|
|
11
|
+
minVersion: z.ZodOptional<z.ZodString>;
|
|
12
|
+
blocks: z.ZodArray<z.ZodObject<{
|
|
13
|
+
name: z.ZodString;
|
|
14
|
+
view: z.ZodEnum<{
|
|
15
|
+
detail: "detail";
|
|
16
|
+
form: "form";
|
|
17
|
+
list: "list";
|
|
18
|
+
}>;
|
|
19
|
+
description: z.ZodOptional<z.ZodString>;
|
|
20
|
+
props: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
21
|
+
variants: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
slots: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
23
|
+
permissions: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
24
|
+
deprecated: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
}, z.core.$loose>>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export type BlockRegistry = z.infer<typeof blockRegistrySchema>;
|
|
28
|
+
export type RegistrySource = "project" | "snapshot";
|
|
29
|
+
export interface ResolvedBlockRegistry {
|
|
30
|
+
readonly registry: BlockRegistry;
|
|
31
|
+
readonly source: RegistrySource;
|
|
32
|
+
readonly registryVersion: string;
|
|
33
|
+
readonly path: string;
|
|
34
|
+
readonly warnings: readonly string[];
|
|
35
|
+
/** True when a `.webpresso/` tree was found but project registry was missing. */
|
|
36
|
+
readonly webpressoPresentWithoutRegistry: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function defaultSnapshotPath(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a loaded project registry is readable by this agent-kit consumer.
|
|
41
|
+
* - registry.version major must match the reader major
|
|
42
|
+
* - if registry.minVersion is set, reader must be >= that floor (semver order)
|
|
43
|
+
*/
|
|
44
|
+
export declare function isProjectRegistryCompatibleWithReader(registry: BlockRegistry, readerVersion?: string): {
|
|
45
|
+
readonly ok: true;
|
|
46
|
+
} | {
|
|
47
|
+
readonly ok: false;
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Two-tier registry resolution (v0):
|
|
52
|
+
* 1. Nearest project `.webpresso/` (within git/webpressorc boundary) — if it has
|
|
53
|
+
* generated blocks registry, use it
|
|
54
|
+
* 2. Else catalog snapshot (with warning when that project `.webpresso/` lacks registry)
|
|
55
|
+
*
|
|
56
|
+
* Never dynamically imports @webpresso/schema.
|
|
57
|
+
* Never climbs past the project boundary into `~/.webpresso` (managed worktree host)
|
|
58
|
+
* or other ancestor projects (avoids silent wrong-project catalog).
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveBlockRegistry(options?: {
|
|
61
|
+
readonly cwd?: string;
|
|
62
|
+
readonly snapshotPath?: string;
|
|
63
|
+
}): ResolvedBlockRegistry;
|
|
64
|
+
export declare function blockNamesForView(registry: BlockRegistry, view: "list" | "detail" | "form"): ReadonlySet<string>;
|