@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.
Files changed (167) hide show
  1. package/THIRD-PARTY-NOTICES.md +12 -10
  2. package/catalog/AGENTS.md.tpl +3 -5
  3. package/catalog/agent/rules/changeset-release.md +15 -4
  4. package/catalog/agent/rules/managed-tool-binaries.md +67 -0
  5. package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
  6. package/catalog/agent/rules/pre-implementation.md +36 -20
  7. package/catalog/agent/rules/rtk-routing.md +22 -16
  8. package/catalog/agent/skills/autopilot/SKILL.md +4 -3
  9. package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
  10. package/catalog/agent/skills/verify/SKILL.md +12 -12
  11. package/catalog/compose/admin-blocks-registry.json +659 -0
  12. package/dist/esm/audit/hook-surface.js +5 -1
  13. package/dist/esm/audit/repo-guardrails.d.ts +8 -0
  14. package/dist/esm/audit/repo-guardrails.js +71 -1
  15. package/dist/esm/blueprint/core/parser.js +40 -16
  16. package/dist/esm/blueprint/core/validation/criteria.js +2 -1
  17. package/dist/esm/blueprint/execution/artifacts.js +8 -4
  18. package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
  19. package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
  20. package/dist/esm/blueprint/execution/metadata.js +8 -4
  21. package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
  22. package/dist/esm/blueprint/lifecycle/audit.js +89 -3
  23. package/dist/esm/blueprint/lifecycle/review-provenance.js +148 -40
  24. package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
  25. package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
  26. package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
  27. package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
  28. package/dist/esm/blueprint/markdown/helpers.js +10 -4
  29. package/dist/esm/blueprint/tracked-document/parser.js +3 -3
  30. package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
  31. package/dist/esm/blueprint/trust/gate-row.js +113 -0
  32. package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
  33. package/dist/esm/blueprint/trust/promotion.js +19 -19
  34. package/dist/esm/build/atomic-file-copy.d.ts +32 -0
  35. package/dist/esm/build/atomic-file-copy.js +44 -0
  36. package/dist/esm/build/cli-mcp-parity.js +3 -2
  37. package/dist/esm/build/package-manifest.js +7 -2
  38. package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
  39. package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
  40. package/dist/esm/cli/commands/audit-core.js +9 -1
  41. package/dist/esm/cli/commands/audit.js +16 -5
  42. package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
  43. package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
  44. package/dist/esm/cli/commands/compile.d.ts +13 -0
  45. package/dist/esm/cli/commands/compile.js +182 -62
  46. package/dist/esm/cli/commands/format.js +17 -1
  47. package/dist/esm/cli/commands/gain/index.js +10 -3
  48. package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
  49. package/dist/esm/cli/commands/init/index.js +29 -48
  50. package/dist/esm/cli/commands/init/mcp-spec.d.ts +54 -4
  51. package/dist/esm/cli/commands/init/mcp-spec.js +126 -22
  52. package/dist/esm/cli/commands/init/merge.js +30 -10
  53. package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
  54. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
  55. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
  56. package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
  57. package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
  58. package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
  59. package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
  60. package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
  61. package/dist/esm/cli/commands/package-manager.js +8 -2
  62. package/dist/esm/cli/commands/pr-help.js +1 -1
  63. package/dist/esm/cli/commands/pr.js +18 -4
  64. package/dist/esm/cli/commands/review.js +60 -6
  65. package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
  66. package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
  67. package/dist/esm/cli/commands/worktree/router.js +5 -2
  68. package/dist/esm/cli/direct-provider-launch.js +12 -4
  69. package/dist/esm/compiler/flatten.d.ts +10 -1
  70. package/dist/esm/compiler/flatten.js +14 -4
  71. package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
  72. package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
  73. package/dist/esm/compose/compose.d.ts +46 -0
  74. package/dist/esm/compose/compose.js +250 -0
  75. package/dist/esm/compose/escape-html.d.ts +5 -0
  76. package/dist/esm/compose/escape-html.js +12 -0
  77. package/dist/esm/compose/ops-report-render.d.ts +5 -0
  78. package/dist/esm/compose/ops-report-render.js +71 -0
  79. package/dist/esm/compose/ops-report-schema.d.ts +42 -0
  80. package/dist/esm/compose/ops-report-schema.js +60 -0
  81. package/dist/esm/compose/preview.d.ts +14 -0
  82. package/dist/esm/compose/preview.js +32 -0
  83. package/dist/esm/compose/registry-resolve.d.ts +64 -0
  84. package/dist/esm/compose/registry-resolve.js +200 -0
  85. package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
  86. package/dist/esm/config/oxlint/import-hygiene.js +21 -8
  87. package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
  88. package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
  89. package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
  90. package/dist/esm/config/oxlint/oxlintrc.js +18 -21
  91. package/dist/esm/config/oxlint/path-roles.js +53 -0
  92. package/dist/esm/config/oxlint/query-patterns.js +2 -5
  93. package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
  94. package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
  95. package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
  96. package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
  97. package/dist/esm/daemon/domains/launch.d.ts +25 -0
  98. package/dist/esm/daemon/domains/launch.js +30 -1
  99. package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
  100. package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
  101. package/dist/esm/daemon/handlers/product.d.ts +10 -0
  102. package/dist/esm/daemon/handlers/product.js +10 -1
  103. package/dist/esm/daemon/methods.d.ts +21 -1
  104. package/dist/esm/daemon/methods.js +20 -0
  105. package/dist/esm/daemon/protocol/contract.d.ts +58 -0
  106. package/dist/esm/daemon/protocol/contract.js +38 -0
  107. package/dist/esm/daemon/server.js +7 -0
  108. package/dist/esm/docs-linter/blueprint-plan.js +2 -2
  109. package/dist/esm/errors/wp-error.js +21 -2
  110. package/dist/esm/git/changed-files.d.ts +20 -0
  111. package/dist/esm/git/changed-files.js +63 -0
  112. package/dist/esm/hooks/doctor.d.ts +11 -0
  113. package/dist/esm/hooks/doctor.js +114 -45
  114. package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
  115. package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
  116. package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
  117. package/dist/esm/hooks/pretool-guard/runner.js +6 -0
  118. package/dist/esm/hooks/status/index.js +1 -1
  119. package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
  120. package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
  121. package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
  122. package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
  123. package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
  124. package/dist/esm/mcp/blueprint/registration.js +1 -1
  125. package/dist/esm/mcp/tools/_names.d.ts +1 -1
  126. package/dist/esm/mcp/tools/_names.js +4 -0
  127. package/dist/esm/mcp/tools/_registry.js +8 -0
  128. package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
  129. package/dist/esm/mcp/tools/_shared/redact.js +114 -8
  130. package/dist/esm/mcp/tools/gain.js +28 -2
  131. package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
  132. package/dist/esm/mcp/tools/release-progress.js +110 -0
  133. package/dist/esm/mcp/tools/worktree.js +65 -3
  134. package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
  135. package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
  136. package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
  137. package/dist/esm/mcp/tools/wp-ui-compose.js +96 -0
  138. package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
  139. package/dist/esm/mcp/tools/wp-ui-preview.js +59 -0
  140. package/dist/esm/package.json +2 -0
  141. package/dist/esm/review/lifecycle-writers.d.ts +76 -0
  142. package/dist/esm/review/lifecycle-writers.js +330 -0
  143. package/dist/esm/review/subject.js +66 -32
  144. package/dist/esm/status/snapshot.d.ts +5 -0
  145. package/dist/esm/status/snapshot.js +27 -2
  146. package/dist/esm/test/shard-durations.json +0 -1
  147. package/dist/esm/test-helpers/global-setup.js +38 -3
  148. package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
  149. package/dist/esm/tool-runtime/managed-rtk.js +275 -0
  150. package/dist/esm/tool-runtime/resolve-runner.js +18 -194
  151. package/dist/esm/utils/package-root.d.ts +32 -0
  152. package/dist/esm/utils/package-root.js +82 -0
  153. package/dist/esm/worktrees/git-metadata.d.ts +41 -0
  154. package/dist/esm/worktrees/git-metadata.js +48 -0
  155. package/package.json +27 -21
  156. package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
  157. package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
  158. package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
  159. package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
  160. package/dist/esm/config/oxlint/index.d.ts +0 -22
  161. package/dist/esm/config/oxlint/index.js +0 -28
  162. package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
  163. package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
  164. package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
  165. package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
  166. package/dist/esm/output-transforms/rulesync.d.ts +0 -2
  167. 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,5 @@
1
+ /**
2
+ * Single HTML encode chokepoint for compose/ops-report output.
3
+ * All untrusted strings must pass through here before interpolation.
4
+ */
5
+ export declare function escapeHtml(value: string): string;
@@ -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("&", "&amp;")
8
+ .replaceAll("<", "&lt;")
9
+ .replaceAll(">", "&gt;")
10
+ .replaceAll('"', "&quot;")
11
+ .replaceAll("'", "&#39;");
12
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Render a validated OpsReportSpec to self-contained HTML.
3
+ * Callers should pass already-validated specs; this re-parses for safety.
4
+ */
5
+ export declare function renderOpsReportHtml(input: unknown): string;
@@ -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>;