@webpresso/agent-kit 0.21.5 → 0.23.0

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 (130) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +87 -124
  4. package/bin/_run.js +143 -1
  5. package/bin/runtime-manifest.json +40 -0
  6. package/catalog/AGENTS.md.tpl +7 -6
  7. package/catalog/agent/commands/plan-refine.md +3 -3
  8. package/catalog/agent/commands/pll.md +2 -0
  9. package/catalog/agent/guides/parallel-execution.md +2 -0
  10. package/catalog/agent/rules/extraction-parity.md +27 -1
  11. package/catalog/agent/rules/public-package-safety.md +24 -1
  12. package/catalog/agent/skills/pll/SKILL.md +1 -0
  13. package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
  14. package/catalog/base-kit/stryker.config.ts.tmpl +2 -2
  15. package/catalog/docs/templates/blueprint.md +1 -0
  16. package/catalog/docs/templates/blueprint.yaml +10 -12
  17. package/commands/blueprint.md +8 -43
  18. package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
  19. package/dist/esm/audit/blueprint-db-consistency.js +6 -8
  20. package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
  21. package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
  22. package/dist/esm/audit/cloudflare-deploy-contract.js +64 -0
  23. package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
  24. package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
  25. package/dist/esm/audit/package-surface.js +14 -1
  26. package/dist/esm/audit/repo-guardrails.js +40 -13
  27. package/dist/esm/audit/roadmap-links.js +23 -10
  28. package/dist/esm/blueprint/core/schema.d.ts +8 -8
  29. package/dist/esm/blueprint/core/schema.js +2 -2
  30. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  31. package/dist/esm/blueprint/db/ingester.js +18 -10
  32. package/dist/esm/blueprint/lifecycle/audit.js +9 -2
  33. package/dist/esm/blueprint/lifecycle/local.js +15 -4
  34. package/dist/esm/blueprint/service/BlueprintCreationService.js +11 -6
  35. package/dist/esm/blueprint/service/BlueprintService.js +37 -19
  36. package/dist/esm/blueprint/service/scanner.js +73 -9
  37. package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
  38. package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
  39. package/dist/esm/blueprint/utils/document-paths.js +91 -0
  40. package/dist/esm/build/package-manifest.js +7 -0
  41. package/dist/esm/build/release-policy.d.ts +27 -0
  42. package/dist/esm/build/release-policy.js +29 -0
  43. package/dist/esm/build/runtime-targets.d.ts +13 -0
  44. package/dist/esm/build/runtime-targets.js +48 -0
  45. package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
  46. package/dist/esm/cli/auto-update/detect-pm.js +24 -9
  47. package/dist/esm/cli/auto-update/skip.js +9 -1
  48. package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
  49. package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
  50. package/dist/esm/cli/bundle/index.d.ts +17 -0
  51. package/dist/esm/cli/bundle/index.js +15 -0
  52. package/dist/esm/cli/cli.d.ts +1 -1
  53. package/dist/esm/cli/cli.js +49 -5
  54. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  55. package/dist/esm/cli/commands/audit.js +2 -0
  56. package/dist/esm/cli/commands/blueprint/router.js +11 -8
  57. package/dist/esm/cli/commands/hook.d.ts +8 -0
  58. package/dist/esm/cli/commands/hook.js +47 -0
  59. package/dist/esm/cli/commands/init/index.js +35 -1
  60. package/dist/esm/cli/commands/init/scaffold-base-kit.js +1 -1
  61. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
  62. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
  63. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
  64. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
  65. package/dist/esm/cli/commands/package-manager.d.ts +15 -0
  66. package/dist/esm/cli/commands/package-manager.js +42 -0
  67. package/dist/esm/cli/commands/test.d.ts +1 -0
  68. package/dist/esm/cli/commands/test.js +2 -1
  69. package/dist/esm/cli/commands/typecheck.js +5 -20
  70. package/dist/esm/cli/package-scripts.d.ts +12 -0
  71. package/dist/esm/cli/package-scripts.js +59 -0
  72. package/dist/esm/cli/utils.js +3 -22
  73. package/dist/esm/cli/wp-extensions.d.ts +14 -0
  74. package/dist/esm/cli/wp-extensions.js +34 -0
  75. package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
  76. package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
  77. package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
  78. package/dist/esm/config/stryker/index.d.ts +85 -0
  79. package/dist/esm/config/stryker/index.js +31 -0
  80. package/dist/esm/e2e/command-builder.js +11 -2
  81. package/dist/esm/e2e/config.d.ts +56 -0
  82. package/dist/esm/e2e/config.js +114 -0
  83. package/dist/esm/e2e/execution.js +4 -0
  84. package/dist/esm/e2e/run-planner.js +1 -0
  85. package/dist/esm/e2e/types.d.ts +2 -0
  86. package/dist/esm/format/index.js +1 -3
  87. package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
  88. package/dist/esm/hooks/guard-switch/index.js +22 -14
  89. package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
  90. package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
  91. package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
  92. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
  93. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
  94. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
  95. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
  96. package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
  97. package/dist/esm/hooks/shared/routing-block.js +18 -4
  98. package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
  99. package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
  100. package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
  101. package/dist/esm/lint/index.js +1 -1
  102. package/dist/esm/mcp/auto-discover.d.ts +2 -0
  103. package/dist/esm/mcp/auto-discover.js +14 -6
  104. package/dist/esm/mcp/blueprint-server.js +30 -26
  105. package/dist/esm/mcp/cli.js +21 -0
  106. package/dist/esm/mcp/runners/test.js +15 -0
  107. package/dist/esm/mcp/server.d.ts +7 -0
  108. package/dist/esm/mcp/server.js +16 -27
  109. package/dist/esm/mcp/tools/_registry.d.ts +3 -0
  110. package/dist/esm/mcp/tools/_registry.js +21 -0
  111. package/dist/esm/mcp/tools/audit.d.ts +1 -0
  112. package/dist/esm/mcp/tools/audit.js +11 -0
  113. package/dist/esm/mcp/tools/e2e.d.ts +1 -1
  114. package/dist/esm/mcp/tools/typecheck.js +4 -2
  115. package/dist/esm/mutation/affected.d.ts +9 -0
  116. package/dist/esm/mutation/affected.js +36 -0
  117. package/dist/esm/package.json +5 -0
  118. package/dist/esm/runtime/package-version.d.ts +2 -0
  119. package/dist/esm/runtime/package-version.js +43 -0
  120. package/dist/esm/test/command-builder.d.ts +3 -0
  121. package/dist/esm/test/command-builder.js +22 -3
  122. package/dist/esm/tool-runtime/index.d.ts +2 -2
  123. package/dist/esm/tool-runtime/index.js +2 -1
  124. package/dist/esm/tool-runtime/resolve-runner.d.ts +3 -0
  125. package/dist/esm/tool-runtime/resolve-runner.js +7 -5
  126. package/dist/esm/typecheck/index.js +4 -2
  127. package/dist/esm/wp-extension/index.d.ts +50 -0
  128. package/dist/esm/wp-extension/index.js +268 -0
  129. package/package.json +67 -31
  130. package/skills/pll/SKILL.md +1 -0
@@ -1,7 +1,6 @@
1
1
  import { getManagedRunner } from '#tool-runtime';
2
+ import { getPackageScript, isRecursiveWpScript } from '#cli/package-scripts.js';
2
3
  import { spawnSync } from 'node:child_process';
3
- import { existsSync, readFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
4
  export const TYPECHECK_COMMAND_HELP = [
6
5
  'Typecheck the current workspace through the portable wp surface.',
7
6
  '',
@@ -17,17 +16,15 @@ export function registerTypecheckCommand(cli) {
17
16
  }
18
17
  export function buildTypecheckCommand(options = {}) {
19
18
  const cwd = options.cwd ?? process.cwd();
20
- if (hasCheckTypesScript(cwd)) {
21
- const resolution = getManagedRunner('vp');
19
+ const checkTypesScript = getPackageScript(cwd, 'check-types');
20
+ if (checkTypesScript && !isRecursiveWpScript(checkTypesScript, 'typecheck')) {
21
+ const resolution = getManagedRunner('vp', { outputPolicy: 'structured' });
22
22
  return {
23
23
  command: resolution.command,
24
24
  args: [...resolution.args, 'run', 'check-types'],
25
25
  };
26
26
  }
27
- const resolution = getManagedRunner('tsc', {
28
- fallbackCommand: 'tsc',
29
- fallbackArgs: [],
30
- });
27
+ const resolution = getManagedRunner('tsc', { outputPolicy: 'structured' });
31
28
  return {
32
29
  command: resolution.command,
33
30
  args: [...resolution.args, '--noEmit', ...(options.pretty ? [] : ['--pretty', 'false'])],
@@ -48,16 +45,4 @@ function defaultRun(command, args) {
48
45
  windowsHide: true,
49
46
  });
50
47
  }
51
- function hasCheckTypesScript(cwd) {
52
- const packageJsonPath = join(cwd, 'package.json');
53
- if (!existsSync(packageJsonPath))
54
- return false;
55
- try {
56
- const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
57
- return typeof parsed.scripts?.['check-types'] === 'string';
58
- }
59
- catch {
60
- return false;
61
- }
62
- }
63
48
  //# sourceMappingURL=typecheck.js.map
@@ -0,0 +1,12 @@
1
+ export interface PackageJsonLike {
2
+ readonly scripts?: Record<string, unknown>;
3
+ readonly dependencies?: Record<string, unknown>;
4
+ readonly devDependencies?: Record<string, unknown>;
5
+ readonly optionalDependencies?: Record<string, unknown>;
6
+ }
7
+ export declare function readPackageJson(cwd: string): PackageJsonLike | undefined;
8
+ export declare function getPackageScript(cwd: string, name: string): string | undefined;
9
+ export declare function packageHasDependency(cwd: string, dependencyName: string): boolean;
10
+ export declare function packageUsesVitest(cwd: string): boolean;
11
+ export declare function isRecursiveWpScript(script: string, verb: string): boolean;
12
+ //# sourceMappingURL=package-scripts.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export function readPackageJson(cwd) {
4
+ const packageJsonPath = join(cwd, 'package.json');
5
+ if (!existsSync(packageJsonPath))
6
+ return;
7
+ try {
8
+ const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
9
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
10
+ return;
11
+ return parsed;
12
+ }
13
+ catch {
14
+ return;
15
+ }
16
+ }
17
+ export function getPackageScript(cwd, name) {
18
+ const parsed = readPackageJson(cwd);
19
+ const candidate = parsed?.scripts?.[name];
20
+ return typeof candidate === 'string' ? candidate : undefined;
21
+ }
22
+ export function packageHasDependency(cwd, dependencyName) {
23
+ const parsed = readPackageJson(cwd);
24
+ if (!parsed)
25
+ return false;
26
+ return ['dependencies', 'devDependencies', 'optionalDependencies'].some((section) => {
27
+ const dependencies = parsed[section];
28
+ return Boolean(dependencies &&
29
+ typeof dependencies === 'object' &&
30
+ !Array.isArray(dependencies) &&
31
+ dependencyName in dependencies);
32
+ });
33
+ }
34
+ export function packageUsesVitest(cwd) {
35
+ return packageHasDependency(cwd, 'vitest');
36
+ }
37
+ export function isRecursiveWpScript(script, verb) {
38
+ const normalized = stripLeadingEnvAssignments(script.trim());
39
+ if (!normalized)
40
+ return false;
41
+ const patterns = [
42
+ new RegExp(`^(?:vp\\s+exec\\s+)?wp\\s+${escapeRegExp(verb)}(?:\\s|$)`),
43
+ new RegExp(`^(?:bunx?|npx)\\s+(?:--yes\\s+)?(?:@webpresso/agent-kit\\s+)?wp\\s+${escapeRegExp(verb)}(?:\\s|$)`),
44
+ ];
45
+ return patterns.some((pattern) => pattern.test(normalized));
46
+ }
47
+ function stripLeadingEnvAssignments(input) {
48
+ let remaining = input.replace(/^env\s+/u, '');
49
+ while (true) {
50
+ const next = remaining.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)\s+)/u, '');
51
+ if (next === remaining)
52
+ return remaining.trim();
53
+ remaining = next;
54
+ }
55
+ }
56
+ function escapeRegExp(value) {
57
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
58
+ }
59
+ //# sourceMappingURL=package-scripts.js.map
@@ -5,8 +5,9 @@
5
5
  * unknown-command formatting) and webpresso/packages/cli/cli-utils
6
6
  * (getProjectRoot) so this package has no @webpresso/* runtime dependencies.
7
7
  */
8
- import { existsSync, readFileSync } from 'node:fs';
9
8
  import path from 'node:path';
9
+ import { existsSync } from 'node:fs';
10
+ import { readOwnedPackageVersion } from '#runtime/package-version.js';
10
11
  // ---------------------------------------------------------------------------
11
12
  // Project root resolution
12
13
  // ---------------------------------------------------------------------------
@@ -131,26 +132,6 @@ export function formatUnknownCommandError(input, commands, binName = 'wp') {
131
132
  * having to know how many `..` segments to append.
132
133
  */
133
134
  export function readPackageVersion(metaUrl) {
134
- const url = new URL(metaUrl);
135
- let dir = path.dirname(url.pathname);
136
- for (let i = 0; i < 6; i++) {
137
- const candidate = path.join(dir, 'package.json');
138
- if (existsSync(candidate)) {
139
- try {
140
- const parsed = JSON.parse(readFileSync(candidate, 'utf-8'));
141
- if (parsed.name === 'webpresso') {
142
- return parsed.version ?? '0.0.0';
143
- }
144
- }
145
- catch {
146
- // keep walking
147
- }
148
- }
149
- const parent = path.dirname(dir);
150
- if (parent === dir)
151
- break;
152
- dir = parent;
153
- }
154
- return '0.0.0';
135
+ return readOwnedPackageVersion(metaUrl);
155
136
  }
156
137
  //# sourceMappingURL=utils.js.map
@@ -0,0 +1,14 @@
1
+ import type { CAC } from 'cac';
2
+ import { type LoadWpExtensionsOptions, type WpExtensionAliasV1 } from '#wp-extension';
3
+ export interface RegisterWpExtensionsOptions extends LoadWpExtensionsOptions {
4
+ readonly cli: CAC;
5
+ readonly baseCommands: readonly string[];
6
+ }
7
+ export interface RegisteredWpExtensions {
8
+ readonly aliasMap: ReadonlyMap<string, WpExtensionAliasV1>;
9
+ readonly warnings: readonly string[];
10
+ readonly commandNames: readonly string[];
11
+ }
12
+ export declare function registerWpExtensions(options: RegisterWpExtensionsOptions): Promise<RegisteredWpExtensions>;
13
+ export declare function resolveWpCommandAlias(command: string | undefined, aliasMap: ReadonlyMap<string, WpExtensionAliasV1>): string | undefined;
14
+ //# sourceMappingURL=wp-extensions.d.ts.map
@@ -0,0 +1,34 @@
1
+ import { loadWpExtensions, resolveAcceptedExtensionAliases, } from '#wp-extension';
2
+ export async function registerWpExtensions(options) {
3
+ const loaded = await loadWpExtensions(options);
4
+ const warnings = loaded.flatMap((entry) => entry.warnings);
5
+ const commandNames = [];
6
+ const blockedCommandNames = new Set(options.baseCommands);
7
+ const registeredCommandNames = new Set();
8
+ for (const extension of loaded) {
9
+ if (!extension.extension || !extension.compatible || !extension.detected)
10
+ continue;
11
+ for (const command of extension.extension.commands) {
12
+ if (blockedCommandNames.has(command.name)) {
13
+ warnings.push(`${extension.packageName}: skipped command "${command.name}" because it collides with an existing command`);
14
+ continue;
15
+ }
16
+ command.register(options.cli);
17
+ commandNames.push(command.name);
18
+ blockedCommandNames.add(command.name);
19
+ registeredCommandNames.add(command.name);
20
+ }
21
+ }
22
+ const aliasResolution = resolveAcceptedExtensionAliases(loaded, options.baseCommands, registeredCommandNames);
23
+ return {
24
+ aliasMap: aliasResolution.aliases,
25
+ warnings: [...warnings, ...aliasResolution.warnings],
26
+ commandNames,
27
+ };
28
+ }
29
+ export function resolveWpCommandAlias(command, aliasMap) {
30
+ if (!command)
31
+ return command;
32
+ return aliasMap.get(command)?.commandName ?? command;
33
+ }
34
+ //# sourceMappingURL=wp-extensions.js.map
@@ -46,8 +46,8 @@ export declare const implementationStatus: z.ZodEnum<{
46
46
  completed: "completed";
47
47
  draft: "draft";
48
48
  planned: "planned";
49
- "in-progress": "in-progress";
50
49
  parked: "parked";
50
+ "in-progress": "in-progress";
51
51
  archived: "archived";
52
52
  current: "current";
53
53
  complete: "complete";
@@ -15,8 +15,8 @@ export declare const implementationPlanFrontmatter: z.ZodObject<{
15
15
  completed: "completed";
16
16
  draft: "draft";
17
17
  planned: "planned";
18
- "in-progress": "in-progress";
19
18
  parked: "parked";
19
+ "in-progress": "in-progress";
20
20
  archived: "archived";
21
21
  current: "current";
22
22
  complete: "complete";
@@ -40,8 +40,8 @@ export declare const implementationPlanFrontmatter: z.ZodObject<{
40
40
  completed: "completed";
41
41
  draft: "draft";
42
42
  planned: "planned";
43
- "in-progress": "in-progress";
44
43
  parked: "parked";
44
+ "in-progress": "in-progress";
45
45
  archived: "archived";
46
46
  current: "current";
47
47
  complete: "complete";
@@ -14,8 +14,8 @@ export declare const parentRoadmapFrontmatter: z.ZodObject<{
14
14
  completed: "completed";
15
15
  draft: "draft";
16
16
  planned: "planned";
17
- "in-progress": "in-progress";
18
17
  parked: "parked";
18
+ "in-progress": "in-progress";
19
19
  archived: "archived";
20
20
  current: "current";
21
21
  complete: "complete";
@@ -40,5 +40,90 @@ export declare const baseConfig: {
40
40
  incremental: boolean;
41
41
  incrementalFile: string;
42
42
  };
43
+ /**
44
+ * Extends baseConfig with TypeScript checker defaults.
45
+ * Use this in any TypeScript package instead of baseConfig directly.
46
+ *
47
+ * @example
48
+ * import { typescriptBaseConfig } from '@webpresso/agent-kit/stryker'
49
+ *
50
+ * export default { ...typescriptBaseConfig }
51
+ * // or, for packages with a CF-pool vitest config:
52
+ * export default { ...typescriptBaseConfig, vitest: { configFile: 'vitest.stryker.config.ts' } }
53
+ */
54
+ export declare const typescriptBaseConfig: {
55
+ checkers: string[];
56
+ tsconfigFile: string;
57
+ packageManager: string;
58
+ testRunner: string;
59
+ plugins: string[];
60
+ ignorePatterns: string[];
61
+ mutate: string[];
62
+ concurrency: number;
63
+ timeoutMS: number;
64
+ dryRunTimeoutMinutes: number;
65
+ ignoreStatic: boolean;
66
+ thresholds: {
67
+ high: number;
68
+ low: number;
69
+ break: number;
70
+ };
71
+ mutator: {
72
+ excludedMutations: string[];
73
+ };
74
+ reporters: string[];
75
+ htmlReporter: {
76
+ fileName: string;
77
+ };
78
+ jsonReporter: {
79
+ fileName: string;
80
+ };
81
+ incremental: boolean;
82
+ incrementalFile: string;
83
+ };
84
+ /**
85
+ * Extends typescriptBaseConfig for Cloudflare Workers packages whose vitest config
86
+ * uses @cloudflare/vitest-pool-workers (incompatible with Stryker's pool injection).
87
+ * Points to a per-package vitest.stryker.config.ts that uses the standard forks pool
88
+ * and excludes any tests that require CF runtime globals (cloudflare:test).
89
+ *
90
+ * @example
91
+ * import { typescriptWorkersBaseConfig } from '@webpresso/agent-kit/stryker'
92
+ *
93
+ * export default { ...typescriptWorkersBaseConfig }
94
+ */
95
+ export declare const typescriptWorkersBaseConfig: {
96
+ vitest: {
97
+ configFile: string;
98
+ };
99
+ checkers: string[];
100
+ tsconfigFile: string;
101
+ packageManager: string;
102
+ testRunner: string;
103
+ plugins: string[];
104
+ ignorePatterns: string[];
105
+ mutate: string[];
106
+ concurrency: number;
107
+ timeoutMS: number;
108
+ dryRunTimeoutMinutes: number;
109
+ ignoreStatic: boolean;
110
+ thresholds: {
111
+ high: number;
112
+ low: number;
113
+ break: number;
114
+ };
115
+ mutator: {
116
+ excludedMutations: string[];
117
+ };
118
+ reporters: string[];
119
+ htmlReporter: {
120
+ fileName: string;
121
+ };
122
+ jsonReporter: {
123
+ fileName: string;
124
+ };
125
+ incremental: boolean;
126
+ incrementalFile: string;
127
+ };
43
128
  export default baseConfig;
44
129
  //# sourceMappingURL=index.d.ts.map
@@ -75,5 +75,36 @@ export const baseConfig = {
75
75
  incremental: true,
76
76
  incrementalFile: 'reports/stryker-incremental.json',
77
77
  };
78
+ /**
79
+ * Extends baseConfig with TypeScript checker defaults.
80
+ * Use this in any TypeScript package instead of baseConfig directly.
81
+ *
82
+ * @example
83
+ * import { typescriptBaseConfig } from '@webpresso/agent-kit/stryker'
84
+ *
85
+ * export default { ...typescriptBaseConfig }
86
+ * // or, for packages with a CF-pool vitest config:
87
+ * export default { ...typescriptBaseConfig, vitest: { configFile: 'vitest.stryker.config.ts' } }
88
+ */
89
+ export const typescriptBaseConfig = {
90
+ ...baseConfig,
91
+ checkers: ['typescript'],
92
+ tsconfigFile: 'tsconfig.json',
93
+ };
94
+ /**
95
+ * Extends typescriptBaseConfig for Cloudflare Workers packages whose vitest config
96
+ * uses @cloudflare/vitest-pool-workers (incompatible with Stryker's pool injection).
97
+ * Points to a per-package vitest.stryker.config.ts that uses the standard forks pool
98
+ * and excludes any tests that require CF runtime globals (cloudflare:test).
99
+ *
100
+ * @example
101
+ * import { typescriptWorkersBaseConfig } from '@webpresso/agent-kit/stryker'
102
+ *
103
+ * export default { ...typescriptWorkersBaseConfig }
104
+ */
105
+ export const typescriptWorkersBaseConfig = {
106
+ ...typescriptBaseConfig,
107
+ vitest: { configFile: 'vitest.stryker.config.ts' },
108
+ };
78
109
  export default baseConfig;
79
110
  //# sourceMappingURL=index.js.map
@@ -16,7 +16,9 @@ function buildPlaywrightCommand(options) {
16
16
  throw new Error(`Step ${step.logName} uses runner "playwright" but does not define configPath.`);
17
17
  }
18
18
  const { baseDir, configArg, files } = resolveRunnerPaths(step.configPath, options.files ?? []);
19
- const resolution = withBaseDir(getManagedRunner('playwright', { filterOutput: options.filterOutput }), baseDir);
19
+ const resolution = withBaseDir(getManagedRunner('playwright', {
20
+ outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
21
+ }), baseDir);
20
22
  const args = [...resolution.args, 'test', '--config', configArg];
21
23
  appendPlaywrightFlags(args, options);
22
24
  args.push(...(step.fixedArgs ?? []), ...files, ...(options.passthrough ?? []));
@@ -28,7 +30,9 @@ function buildVitestE2eCommand(options) {
28
30
  throw new Error(`Step ${step.logName} uses runner "vitest" but does not define configPath.`);
29
31
  }
30
32
  const { baseDir, configArg, files } = resolveRunnerPaths(step.configPath, options.files ?? []);
31
- const resolution = withBaseDir(getManagedRunner('vitest', { filterOutput: options.filterOutput }), baseDir);
33
+ const resolution = withBaseDir(getManagedRunner('vitest', {
34
+ outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
35
+ }), baseDir);
32
36
  const args = [...resolution.args, 'run', '--config', configArg];
33
37
  if (options.workers !== undefined) {
34
38
  args.push('--poolOptions.threads.maxThreads', String(options.workers));
@@ -114,4 +118,9 @@ function withBaseDir(resolution, baseDir) {
114
118
  }
115
119
  return { command: resolution.command, args: [...resolution.args] };
116
120
  }
121
+ function resolveOutputPolicy(outputPolicy, filterOutput) {
122
+ if (outputPolicy)
123
+ return outputPolicy;
124
+ return filterOutput === false ? 'structured' : 'rtk-filtered';
125
+ }
117
126
  //# sourceMappingURL=command-builder.js.map
@@ -6,6 +6,62 @@ declare const webpressoConfigSchema: z.ZodObject<{
6
6
  hostAdapterModule: z.ZodString;
7
7
  hostAdapterExport: z.ZodOptional<z.ZodString>;
8
8
  }, z.core.$strict>>;
9
+ deploy: z.ZodOptional<z.ZodObject<{
10
+ cloudflare: z.ZodOptional<z.ZodObject<{
11
+ lanes: z.ZodObject<{
12
+ dev: z.ZodObject<{
13
+ wranglerEnvName: z.ZodString;
14
+ }, z.core.$strict>;
15
+ preview_main: z.ZodObject<{
16
+ wranglerEnvName: z.ZodString;
17
+ }, z.core.$strict>;
18
+ preview_pr: z.ZodObject<{
19
+ wranglerEnvNamePattern: z.ZodString;
20
+ }, z.core.$strict>;
21
+ prd: z.ZodObject<{
22
+ wranglerEnvName: z.ZodString & z.ZodType<"production", string, z.core.$ZodTypeInternals<"production", string>>;
23
+ deployedWorkerNameMode: z.ZodLiteral<"top_level_name">;
24
+ }, z.core.$strict>;
25
+ }, z.core.$strict>;
26
+ production: z.ZodObject<{
27
+ metadataPath: z.ZodLiteral<"infra/release-metadata.production.json">;
28
+ }, z.core.$strict>;
29
+ targets: z.ZodArray<z.ZodObject<{
30
+ id: z.ZodString;
31
+ type: z.ZodEnum<{
32
+ single_worker: "single_worker";
33
+ worker_plus_assets: "worker_plus_assets";
34
+ monorepo_multi_target: "monorepo_multi_target";
35
+ }>;
36
+ topLevelWorkerName: z.ZodString;
37
+ previewTransport: z.ZodEnum<{
38
+ custom_domain_env: "custom_domain_env";
39
+ workers_dev_env: "workers_dev_env";
40
+ }>;
41
+ routeSpec: z.ZodOptional<z.ZodObject<{
42
+ pattern: z.ZodString;
43
+ }, z.core.$strict>>;
44
+ durableObjectBindings: z.ZodOptional<z.ZodArray<z.ZodObject<{
45
+ name: z.ZodString;
46
+ className: z.ZodString;
47
+ scriptName: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strict>>>;
49
+ vars: z.ZodRecord<z.ZodString, z.ZodUnknown>;
50
+ requiredSecrets: z.ZodArray<z.ZodString>;
51
+ storageMode: z.ZodEnum<{
52
+ isolated: "isolated";
53
+ shared_via_script_name: "shared_via_script_name";
54
+ }>;
55
+ destroyMode: z.ZodLiteral<"wrangler_delete_env">;
56
+ repoCleanupHook: z.ZodOptional<z.ZodString>;
57
+ blastRadiusDoc: z.ZodOptional<z.ZodString>;
58
+ productionStrategyDefault: z.ZodEnum<{
59
+ direct: "direct";
60
+ gradual: "gradual";
61
+ }>;
62
+ }, z.core.$strict>>;
63
+ }, z.core.$strict>>;
64
+ }, z.core.$strict>>;
9
65
  }, z.core.$strict>;
10
66
  export type WebpressoConfig = z.infer<typeof webpressoConfigSchema>;
11
67
  export type WebpressoE2eConfig = NonNullable<WebpressoConfig['e2e']>;
@@ -1,15 +1,129 @@
1
1
  import { z } from 'zod';
2
2
  export const WEBPRESSO_CONFIG_FILE_NAME = 'webpresso.config.ts';
3
3
  export const WEBPRESSO_CONFIG_EXPORT_NAME = 'webpressoConfig';
4
+ const wranglerEnvNameSchema = z
5
+ .string()
6
+ .min(1, 'wranglerEnvName must not be empty.')
7
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'wranglerEnvName must be dash-safe lowercase letters, numbers, and hyphens only.');
4
8
  const e2eWebpressoConfigSchema = z
5
9
  .object({
6
10
  hostAdapterModule: z.string().min(1, 'e2e.hostAdapterModule must not be empty.'),
7
11
  hostAdapterExport: z.string().min(1, 'e2e.hostAdapterExport must not be empty.').optional(),
8
12
  })
9
13
  .strict();
14
+ const cloudflareDeployLaneSchema = z
15
+ .object({
16
+ wranglerEnvName: wranglerEnvNameSchema,
17
+ })
18
+ .strict();
19
+ const previewPrCloudflareDeployLaneSchema = z
20
+ .object({
21
+ wranglerEnvNamePattern: z
22
+ .string()
23
+ .min(1, 'wranglerEnvNamePattern must not be empty.')
24
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*-<n>$/, 'wranglerEnvNamePattern must be dash-safe and end with -<n>.'),
25
+ })
26
+ .strict();
27
+ const productionCloudflareDeployLaneSchema = cloudflareDeployLaneSchema.extend({
28
+ wranglerEnvName: wranglerEnvNameSchema.refine((value) => value === 'production', {
29
+ message: 'deploy.cloudflare.lanes.prd.wranglerEnvName must be "production".',
30
+ }),
31
+ deployedWorkerNameMode: z.literal('top_level_name'),
32
+ });
33
+ const cloudflareRouteSpecSchema = z
34
+ .object({
35
+ pattern: z.string().min(1, 'routeSpec.pattern must not be empty.'),
36
+ })
37
+ .strict();
38
+ const cloudflareDurableObjectBindingSchema = z
39
+ .object({
40
+ name: z.string().min(1, 'durableObjectBindings[].name must not be empty.'),
41
+ className: z.string().min(1, 'durableObjectBindings[].className must not be empty.'),
42
+ scriptName: z.string().min(1, 'durableObjectBindings[].scriptName must not be empty.').optional(),
43
+ })
44
+ .strict();
45
+ const cloudflareTargetSchema = z
46
+ .object({
47
+ id: z.string().min(1, 'deploy.cloudflare.targets[].id must not be empty.'),
48
+ type: z.enum(['single_worker', 'worker_plus_assets', 'monorepo_multi_target']),
49
+ topLevelWorkerName: z.string().min(1, 'topLevelWorkerName must not be empty.'),
50
+ previewTransport: z.enum(['custom_domain_env', 'workers_dev_env']),
51
+ routeSpec: cloudflareRouteSpecSchema.optional(),
52
+ durableObjectBindings: z.array(cloudflareDurableObjectBindingSchema).optional(),
53
+ vars: z.record(z.string(), z.unknown()),
54
+ requiredSecrets: z.array(z.string().min(1, 'requiredSecrets[] must not be empty.')),
55
+ storageMode: z.enum(['isolated', 'shared_via_script_name']),
56
+ destroyMode: z.literal('wrangler_delete_env'),
57
+ repoCleanupHook: z.string().min(1, 'repoCleanupHook must not be empty.').optional(),
58
+ blastRadiusDoc: z.string().min(1, 'blastRadiusDoc must not be empty.').optional(),
59
+ productionStrategyDefault: z.enum(['direct', 'gradual']),
60
+ })
61
+ .strict()
62
+ .superRefine((target, ctx) => {
63
+ const isDurableObjectTarget = (target.durableObjectBindings?.length ?? 0) > 0;
64
+ if (target.previewTransport === 'custom_domain_env' && !target.routeSpec) {
65
+ ctx.addIssue({
66
+ code: z.ZodIssueCode.custom,
67
+ path: ['routeSpec'],
68
+ message: 'routeSpec is required when previewTransport is "custom_domain_env".',
69
+ });
70
+ }
71
+ if (isDurableObjectTarget && target.previewTransport !== 'custom_domain_env') {
72
+ ctx.addIssue({
73
+ code: z.ZodIssueCode.custom,
74
+ path: ['previewTransport'],
75
+ message: 'Durable Object targets must use previewTransport "custom_domain_env" unless a future explicit exception contract is introduced.',
76
+ });
77
+ }
78
+ if (isDurableObjectTarget && Object.keys(target.vars).length === 0) {
79
+ ctx.addIssue({
80
+ code: z.ZodIssueCode.custom,
81
+ path: ['vars'],
82
+ message: 'Durable Object targets must declare at least one env-specific var.',
83
+ });
84
+ }
85
+ if (isDurableObjectTarget && target.requiredSecrets.length === 0) {
86
+ ctx.addIssue({
87
+ code: z.ZodIssueCode.custom,
88
+ path: ['requiredSecrets'],
89
+ message: 'Durable Object targets must declare at least one required secret name.',
90
+ });
91
+ }
92
+ if (target.storageMode === 'shared_via_script_name' && !target.blastRadiusDoc) {
93
+ ctx.addIssue({
94
+ code: z.ZodIssueCode.custom,
95
+ path: ['blastRadiusDoc'],
96
+ message: 'blastRadiusDoc is required when storageMode is "shared_via_script_name".',
97
+ });
98
+ }
99
+ });
100
+ const cloudflareDeployConfigSchema = z
101
+ .object({
102
+ lanes: z
103
+ .object({
104
+ dev: cloudflareDeployLaneSchema,
105
+ preview_main: cloudflareDeployLaneSchema,
106
+ preview_pr: previewPrCloudflareDeployLaneSchema,
107
+ prd: productionCloudflareDeployLaneSchema,
108
+ })
109
+ .strict(),
110
+ production: z
111
+ .object({
112
+ metadataPath: z.literal('infra/release-metadata.production.json'),
113
+ })
114
+ .strict(),
115
+ targets: z.array(cloudflareTargetSchema),
116
+ })
117
+ .strict();
118
+ const deployWebpressoConfigSchema = z
119
+ .object({
120
+ cloudflare: cloudflareDeployConfigSchema.optional(),
121
+ })
122
+ .strict();
10
123
  const webpressoConfigSchema = z
11
124
  .object({
12
125
  e2e: e2eWebpressoConfigSchema.optional(),
126
+ deploy: deployWebpressoConfigSchema.optional(),
13
127
  })
14
128
  .strict();
15
129
  export class WebpressoConfigValidationError extends Error {
@@ -18,6 +18,7 @@ export async function createE2eExecutionPlan(input, cwd = process.cwd()) {
18
18
  workers: input.workers,
19
19
  testList: input.testList,
20
20
  passthrough: input.passthrough,
21
+ outputPolicy: input.outputPolicy,
21
22
  filterOutput: input.filterOutput,
22
23
  });
23
24
  }
@@ -36,6 +37,7 @@ export async function createE2eExecutionPlan(input, cwd = process.cwd()) {
36
37
  workers: input.workers,
37
38
  testList: input.testList,
38
39
  passthrough: input.passthrough,
40
+ outputPolicy: input.outputPolicy,
39
41
  filterOutput: input.filterOutput,
40
42
  });
41
43
  }
@@ -51,6 +53,7 @@ export async function createE2eExecutionPlan(input, cwd = process.cwd()) {
51
53
  workers: input.workers,
52
54
  testList: input.testList,
53
55
  passthrough: input.passthrough,
56
+ outputPolicy: input.outputPolicy,
54
57
  filterOutput: input.filterOutput,
55
58
  });
56
59
  }
@@ -63,6 +66,7 @@ export async function createE2eExecutionPlan(input, cwd = process.cwd()) {
63
66
  workers: input.workers,
64
67
  testList: input.testList,
65
68
  passthrough: input.passthrough,
69
+ outputPolicy: input.outputPolicy,
66
70
  filterOutput: input.filterOutput,
67
71
  });
68
72
  }
@@ -76,6 +76,7 @@ function planE2eRunsFromSuites(options) {
76
76
  workers: options.request.workers,
77
77
  testList: options.request.testList,
78
78
  passthrough: options.request.passthrough,
79
+ outputPolicy: options.request.outputPolicy,
79
80
  filterOutput: options.request.filterOutput,
80
81
  });
81
82
  runs.push({
@@ -1,3 +1,4 @@
1
+ import type { ManagedRunnerOutputPolicy } from '#tool-runtime';
1
2
  export type E2eRunnerKind = 'playwright' | 'vitest' | 'command';
2
3
  export interface CommandConfig {
3
4
  command: string;
@@ -49,6 +50,7 @@ export interface E2eCommandRequest {
49
50
  testList?: string;
50
51
  passthrough?: readonly string[];
51
52
  filterOutput?: boolean;
53
+ outputPolicy?: ManagedRunnerOutputPolicy;
52
54
  }
53
55
  export interface E2eExecutionRequest extends E2eCommandRequest {
54
56
  suite?: string;
@@ -35,9 +35,7 @@ export async function runFormat(options = {}) {
35
35
  if (options.files && options.files.length > 0)
36
36
  args.push(...options.files);
37
37
  const resolution = getManagedRunner('oxfmt', {
38
- fallbackCommand: 'oxfmt',
39
- fallbackArgs: [],
40
- filterOutput: false,
38
+ outputPolicy: 'structured',
41
39
  });
42
40
  const outcome = await runCommand(resolution.command, [...resolution.args, ...args], runOptions);
43
41
  if (isRunFailure(outcome)) {