@webpresso/agent-kit 0.21.5 → 0.24.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +87 -124
- package/bin/_run.js +143 -1
- package/bin/runtime-manifest.json +40 -0
- package/catalog/AGENTS.md.tpl +7 -6
- package/catalog/agent/commands/plan-refine.md +3 -3
- package/catalog/agent/commands/pll.md +2 -0
- package/catalog/agent/guides/parallel-execution.md +2 -0
- package/catalog/agent/rules/extraction-parity.md +27 -1
- package/catalog/agent/rules/public-package-safety.md +24 -1
- package/catalog/agent/skills/pll/SKILL.md +1 -0
- package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
- package/catalog/base-kit/stryker.config.ts.tmpl +2 -2
- package/catalog/docs/templates/blueprint.md +1 -0
- package/catalog/docs/templates/blueprint.yaml +10 -12
- package/commands/blueprint.md +8 -43
- package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
- package/dist/esm/audit/blueprint-db-consistency.js +6 -8
- package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
- package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
- package/dist/esm/audit/cloudflare-deploy-contract.js +80 -0
- package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
- package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
- package/dist/esm/audit/package-surface.js +14 -1
- package/dist/esm/audit/repo-guardrails.js +40 -13
- package/dist/esm/audit/roadmap-links.js +23 -10
- package/dist/esm/blueprint/core/schema.d.ts +8 -8
- package/dist/esm/blueprint/core/schema.js +2 -2
- package/dist/esm/blueprint/db/enums.d.ts +1 -1
- package/dist/esm/blueprint/db/ingester.js +18 -10
- package/dist/esm/blueprint/lifecycle/audit.js +9 -2
- package/dist/esm/blueprint/lifecycle/local.js +15 -4
- package/dist/esm/blueprint/service/BlueprintCreationService.js +11 -6
- package/dist/esm/blueprint/service/BlueprintService.js +37 -19
- package/dist/esm/blueprint/service/scanner.js +73 -9
- package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
- package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
- package/dist/esm/blueprint/utils/document-paths.js +91 -0
- package/dist/esm/build/package-manifest.js +7 -0
- package/dist/esm/build/release-policy.d.ts +27 -0
- package/dist/esm/build/release-policy.js +29 -0
- package/dist/esm/build/runtime-targets.d.ts +13 -0
- package/dist/esm/build/runtime-targets.js +48 -0
- package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
- package/dist/esm/cli/auto-update/detect-pm.js +24 -9
- package/dist/esm/cli/auto-update/skip.js +9 -1
- package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
- package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
- package/dist/esm/cli/bundle/index.d.ts +17 -0
- package/dist/esm/cli/bundle/index.js +15 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +49 -5
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit.js +2 -0
- package/dist/esm/cli/commands/blueprint/router.js +11 -8
- package/dist/esm/cli/commands/hook.d.ts +8 -0
- package/dist/esm/cli/commands/hook.js +47 -0
- package/dist/esm/cli/commands/init/index.js +35 -1
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +1 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
- package/dist/esm/cli/commands/package-manager.d.ts +15 -0
- package/dist/esm/cli/commands/package-manager.js +42 -0
- package/dist/esm/cli/commands/test.d.ts +1 -0
- package/dist/esm/cli/commands/test.js +2 -1
- package/dist/esm/cli/commands/typecheck.js +5 -20
- package/dist/esm/cli/package-scripts.d.ts +12 -0
- package/dist/esm/cli/package-scripts.js +59 -0
- package/dist/esm/cli/utils.js +3 -22
- package/dist/esm/cli/wp-extensions.d.ts +14 -0
- package/dist/esm/cli/wp-extensions.js +34 -0
- package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
- package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
- package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
- package/dist/esm/config/stryker/index.d.ts +85 -0
- package/dist/esm/config/stryker/index.js +31 -0
- package/dist/esm/e2e/command-builder.js +11 -2
- package/dist/esm/e2e/config.d.ts +65 -0
- package/dist/esm/e2e/config.js +126 -0
- package/dist/esm/e2e/execution.js +4 -0
- package/dist/esm/e2e/load-host-adapter.d.ts +6 -1
- package/dist/esm/e2e/load-host-adapter.js +27 -9
- package/dist/esm/e2e/run-planner.js +1 -0
- package/dist/esm/e2e/types.d.ts +2 -0
- package/dist/esm/format/index.js +1 -3
- package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
- package/dist/esm/hooks/guard-switch/index.js +22 -14
- package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
- package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
- package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
- package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
- package/dist/esm/hooks/shared/routing-block.js +18 -4
- package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
- package/dist/esm/lint/index.js +1 -1
- package/dist/esm/mcp/auto-discover.d.ts +2 -0
- package/dist/esm/mcp/auto-discover.js +14 -6
- package/dist/esm/mcp/blueprint-server.js +30 -26
- package/dist/esm/mcp/cli.js +21 -0
- package/dist/esm/mcp/runners/test.js +15 -0
- package/dist/esm/mcp/server.d.ts +7 -0
- package/dist/esm/mcp/server.js +16 -27
- package/dist/esm/mcp/tools/_registry.d.ts +3 -0
- package/dist/esm/mcp/tools/_registry.js +21 -0
- package/dist/esm/mcp/tools/audit.d.ts +1 -0
- package/dist/esm/mcp/tools/audit.js +11 -0
- package/dist/esm/mcp/tools/e2e.d.ts +1 -1
- package/dist/esm/mcp/tools/typecheck.js +4 -2
- package/dist/esm/mutation/affected.d.ts +9 -0
- package/dist/esm/mutation/affected.js +36 -0
- package/dist/esm/package.json +5 -0
- package/dist/esm/runtime/package-version.d.ts +2 -0
- package/dist/esm/runtime/package-version.js +43 -0
- package/dist/esm/test/command-builder.d.ts +3 -0
- package/dist/esm/test/command-builder.js +22 -3
- package/dist/esm/tool-runtime/index.d.ts +2 -2
- package/dist/esm/tool-runtime/index.js +2 -1
- package/dist/esm/tool-runtime/resolve-runner.d.ts +3 -0
- package/dist/esm/tool-runtime/resolve-runner.js +7 -5
- package/dist/esm/typecheck/index.js +4 -2
- package/dist/esm/wp-extension/index.d.ts +50 -0
- package/dist/esm/wp-extension/index.js +268 -0
- package/package.json +67 -31
- package/skills/pll/SKILL.md +1 -0
package/dist/esm/e2e/config.js
CHANGED
|
@@ -1,15 +1,141 @@
|
|
|
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
|
+
export const AGENT_KIT_CONFIG_FILE_NAME = 'agent-kit.config.ts';
|
|
5
|
+
export const AGENT_KIT_CONFIG_EXPORT_NAME = 'agentKitConfig';
|
|
6
|
+
export const WEBPRESSO_CONFIG_CANDIDATES = [
|
|
7
|
+
{
|
|
8
|
+
fileName: AGENT_KIT_CONFIG_FILE_NAME,
|
|
9
|
+
exportName: AGENT_KIT_CONFIG_EXPORT_NAME,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
fileName: WEBPRESSO_CONFIG_FILE_NAME,
|
|
13
|
+
exportName: WEBPRESSO_CONFIG_EXPORT_NAME,
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const wranglerEnvNameSchema = z
|
|
17
|
+
.string()
|
|
18
|
+
.min(1, 'wranglerEnvName must not be empty.')
|
|
19
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'wranglerEnvName must be dash-safe lowercase letters, numbers, and hyphens only.');
|
|
4
20
|
const e2eWebpressoConfigSchema = z
|
|
5
21
|
.object({
|
|
6
22
|
hostAdapterModule: z.string().min(1, 'e2e.hostAdapterModule must not be empty.'),
|
|
7
23
|
hostAdapterExport: z.string().min(1, 'e2e.hostAdapterExport must not be empty.').optional(),
|
|
8
24
|
})
|
|
9
25
|
.strict();
|
|
26
|
+
const cloudflareDeployLaneSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
wranglerEnvName: wranglerEnvNameSchema,
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
const previewPrCloudflareDeployLaneSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
wranglerEnvNamePattern: z
|
|
34
|
+
.string()
|
|
35
|
+
.min(1, 'wranglerEnvNamePattern must not be empty.')
|
|
36
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*-<n>$/, 'wranglerEnvNamePattern must be dash-safe and end with -<n>.'),
|
|
37
|
+
})
|
|
38
|
+
.strict();
|
|
39
|
+
const productionCloudflareDeployLaneSchema = cloudflareDeployLaneSchema.extend({
|
|
40
|
+
wranglerEnvName: wranglerEnvNameSchema.refine((value) => value === 'production', {
|
|
41
|
+
message: 'deploy.cloudflare.lanes.prd.wranglerEnvName must be "production".',
|
|
42
|
+
}),
|
|
43
|
+
deployedWorkerNameMode: z.literal('top_level_name'),
|
|
44
|
+
});
|
|
45
|
+
const cloudflareRouteSpecSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
pattern: z.string().min(1, 'routeSpec.pattern must not be empty.'),
|
|
48
|
+
})
|
|
49
|
+
.strict();
|
|
50
|
+
const cloudflareDurableObjectBindingSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
name: z.string().min(1, 'durableObjectBindings[].name must not be empty.'),
|
|
53
|
+
className: z.string().min(1, 'durableObjectBindings[].className must not be empty.'),
|
|
54
|
+
scriptName: z.string().min(1, 'durableObjectBindings[].scriptName must not be empty.').optional(),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
const cloudflareTargetSchema = z
|
|
58
|
+
.object({
|
|
59
|
+
id: z.string().min(1, 'deploy.cloudflare.targets[].id must not be empty.'),
|
|
60
|
+
type: z.enum(['single_worker', 'worker_plus_assets', 'monorepo_multi_target']),
|
|
61
|
+
topLevelWorkerName: z.string().min(1, 'topLevelWorkerName must not be empty.'),
|
|
62
|
+
previewTransport: z.enum(['custom_domain_env', 'workers_dev_env']),
|
|
63
|
+
routeSpec: cloudflareRouteSpecSchema.optional(),
|
|
64
|
+
durableObjectBindings: z.array(cloudflareDurableObjectBindingSchema).optional(),
|
|
65
|
+
vars: z.record(z.string(), z.unknown()),
|
|
66
|
+
requiredSecrets: z.array(z.string().min(1, 'requiredSecrets[] must not be empty.')),
|
|
67
|
+
storageMode: z.enum(['isolated', 'shared_via_script_name']),
|
|
68
|
+
destroyMode: z.literal('wrangler_delete_env'),
|
|
69
|
+
repoCleanupHook: z.string().min(1, 'repoCleanupHook must not be empty.').optional(),
|
|
70
|
+
blastRadiusDoc: z.string().min(1, 'blastRadiusDoc must not be empty.').optional(),
|
|
71
|
+
productionStrategyDefault: z.enum(['direct', 'gradual']),
|
|
72
|
+
})
|
|
73
|
+
.strict()
|
|
74
|
+
.superRefine((target, ctx) => {
|
|
75
|
+
const isDurableObjectTarget = (target.durableObjectBindings?.length ?? 0) > 0;
|
|
76
|
+
if (target.previewTransport === 'custom_domain_env' && !target.routeSpec) {
|
|
77
|
+
ctx.addIssue({
|
|
78
|
+
code: z.ZodIssueCode.custom,
|
|
79
|
+
path: ['routeSpec'],
|
|
80
|
+
message: 'routeSpec is required when previewTransport is "custom_domain_env".',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (isDurableObjectTarget && target.previewTransport !== 'custom_domain_env') {
|
|
84
|
+
ctx.addIssue({
|
|
85
|
+
code: z.ZodIssueCode.custom,
|
|
86
|
+
path: ['previewTransport'],
|
|
87
|
+
message: 'Durable Object targets must use previewTransport "custom_domain_env" unless a future explicit exception contract is introduced.',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (isDurableObjectTarget && Object.keys(target.vars).length === 0) {
|
|
91
|
+
ctx.addIssue({
|
|
92
|
+
code: z.ZodIssueCode.custom,
|
|
93
|
+
path: ['vars'],
|
|
94
|
+
message: 'Durable Object targets must declare at least one env-specific var.',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (isDurableObjectTarget && target.requiredSecrets.length === 0) {
|
|
98
|
+
ctx.addIssue({
|
|
99
|
+
code: z.ZodIssueCode.custom,
|
|
100
|
+
path: ['requiredSecrets'],
|
|
101
|
+
message: 'Durable Object targets must declare at least one required secret name.',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (target.storageMode === 'shared_via_script_name' && !target.blastRadiusDoc) {
|
|
105
|
+
ctx.addIssue({
|
|
106
|
+
code: z.ZodIssueCode.custom,
|
|
107
|
+
path: ['blastRadiusDoc'],
|
|
108
|
+
message: 'blastRadiusDoc is required when storageMode is "shared_via_script_name".',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const cloudflareDeployConfigSchema = z
|
|
113
|
+
.object({
|
|
114
|
+
lanes: z
|
|
115
|
+
.object({
|
|
116
|
+
dev: cloudflareDeployLaneSchema,
|
|
117
|
+
preview_main: cloudflareDeployLaneSchema,
|
|
118
|
+
preview_pr: previewPrCloudflareDeployLaneSchema,
|
|
119
|
+
prd: productionCloudflareDeployLaneSchema,
|
|
120
|
+
})
|
|
121
|
+
.strict(),
|
|
122
|
+
production: z
|
|
123
|
+
.object({
|
|
124
|
+
metadataPath: z.literal('infra/release-metadata.production.json'),
|
|
125
|
+
})
|
|
126
|
+
.strict(),
|
|
127
|
+
targets: z.array(cloudflareTargetSchema),
|
|
128
|
+
})
|
|
129
|
+
.strict();
|
|
130
|
+
const deployWebpressoConfigSchema = z
|
|
131
|
+
.object({
|
|
132
|
+
cloudflare: cloudflareDeployConfigSchema.optional(),
|
|
133
|
+
})
|
|
134
|
+
.strict();
|
|
10
135
|
const webpressoConfigSchema = z
|
|
11
136
|
.object({
|
|
12
137
|
e2e: e2eWebpressoConfigSchema.optional(),
|
|
138
|
+
deploy: deployWebpressoConfigSchema.optional(),
|
|
13
139
|
})
|
|
14
140
|
.strict();
|
|
15
141
|
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
|
}
|
|
@@ -19,7 +19,12 @@ export declare class WebpressoConfigLoadError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
export declare class WebpressoConfigExportError extends Error {
|
|
21
21
|
readonly configPath: string;
|
|
22
|
-
|
|
22
|
+
readonly exportName: string;
|
|
23
|
+
constructor(configPath: string, exportName?: string);
|
|
24
|
+
}
|
|
25
|
+
export declare class WebpressoConfigAmbiguousError extends Error {
|
|
26
|
+
readonly configPaths: readonly string[];
|
|
27
|
+
constructor(configPaths: readonly string[]);
|
|
23
28
|
}
|
|
24
29
|
export declare class HostAdapterModuleLoadError extends Error {
|
|
25
30
|
readonly moduleSpecifier: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { dirname, resolve, parse } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { WEBPRESSO_CONFIG_EXPORT_NAME, WEBPRESSO_CONFIG_FILE_NAME, validateWebpressoConfig, } from './config.js';
|
|
4
|
+
import { WEBPRESSO_CONFIG_CANDIDATES, WEBPRESSO_CONFIG_EXPORT_NAME, WEBPRESSO_CONFIG_FILE_NAME, validateWebpressoConfig, } from './config.js';
|
|
5
5
|
import { FALLBACK_HOST_ADAPTER_EXPORT_NAMES, isE2eHostAdapter } from './host-adapter.js';
|
|
6
6
|
export class WebpressoConfigLoadError extends Error {
|
|
7
7
|
configPath;
|
|
@@ -15,12 +15,22 @@ export class WebpressoConfigLoadError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
export class WebpressoConfigExportError extends Error {
|
|
17
17
|
configPath;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
exportName;
|
|
19
|
+
constructor(configPath, exportName = WEBPRESSO_CONFIG_EXPORT_NAME) {
|
|
20
|
+
super(`Expected config at ${configPath} to export ${exportName}.`);
|
|
20
21
|
this.configPath = configPath;
|
|
22
|
+
this.exportName = exportName;
|
|
21
23
|
this.name = 'WebpressoConfigExportError';
|
|
22
24
|
}
|
|
23
25
|
}
|
|
26
|
+
export class WebpressoConfigAmbiguousError extends Error {
|
|
27
|
+
configPaths;
|
|
28
|
+
constructor(configPaths) {
|
|
29
|
+
super(`Multiple Webpresso config files found: ${configPaths.join(', ')}`);
|
|
30
|
+
this.configPaths = configPaths;
|
|
31
|
+
this.name = 'WebpressoConfigAmbiguousError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
24
34
|
export class HostAdapterModuleLoadError extends Error {
|
|
25
35
|
moduleSpecifier;
|
|
26
36
|
configPath;
|
|
@@ -55,9 +65,12 @@ export function resolveWebpressoConfigPath(cwd = process.cwd()) {
|
|
|
55
65
|
}
|
|
56
66
|
export function findWebpressoConfigPath(cwd = process.cwd()) {
|
|
57
67
|
for (const searchDir of getSearchDirectories(cwd)) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
68
|
+
const configPaths = WEBPRESSO_CONFIG_CANDIDATES.map((candidate) => resolve(searchDir, candidate.fileName)).filter((configPath) => existsSync(configPath));
|
|
69
|
+
if (configPaths.length > 1) {
|
|
70
|
+
throw new WebpressoConfigAmbiguousError(configPaths);
|
|
71
|
+
}
|
|
72
|
+
if (configPaths.length === 1) {
|
|
73
|
+
return configPaths[0];
|
|
61
74
|
}
|
|
62
75
|
}
|
|
63
76
|
return null;
|
|
@@ -67,11 +80,12 @@ export async function loadWebpressoConfig(options = {}) {
|
|
|
67
80
|
const configModule = await loadModuleNamespace(pathToFileURL(configPath).href, (cause) => {
|
|
68
81
|
throw new WebpressoConfigLoadError(configPath, cause);
|
|
69
82
|
});
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
const exportName = expectedConfigExportName(configPath);
|
|
84
|
+
if (!(exportName in configModule)) {
|
|
85
|
+
throw new WebpressoConfigExportError(configPath, exportName);
|
|
72
86
|
}
|
|
73
87
|
return {
|
|
74
|
-
config: validateWebpressoConfig(configModule[
|
|
88
|
+
config: validateWebpressoConfig(configModule[exportName], configPath),
|
|
75
89
|
configPath,
|
|
76
90
|
};
|
|
77
91
|
}
|
|
@@ -138,6 +152,10 @@ function resolveModuleSpecifier(moduleSpecifier, configPath) {
|
|
|
138
152
|
}
|
|
139
153
|
return moduleSpecifier;
|
|
140
154
|
}
|
|
155
|
+
function expectedConfigExportName(configPath) {
|
|
156
|
+
const candidate = WEBPRESSO_CONFIG_CANDIDATES.find((item) => configPath.endsWith(item.fileName));
|
|
157
|
+
return candidate?.exportName ?? WEBPRESSO_CONFIG_EXPORT_NAME;
|
|
158
|
+
}
|
|
141
159
|
async function loadModuleNamespace(moduleSpecifier, onError) {
|
|
142
160
|
try {
|
|
143
161
|
const moduleNamespace = await import(moduleSpecifier);
|
|
@@ -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({
|
package/dist/esm/e2e/types.d.ts
CHANGED
|
@@ -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;
|
package/dist/esm/format/index.js
CHANGED
|
@@ -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
|
-
|
|
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)) {
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { runHook } from '#hooks/shared/hook-bootstrap';
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { setGuardEnabled } from './state.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
export async function main() {
|
|
7
|
+
runHook((input) => {
|
|
8
|
+
const normalized = (input.prompt ?? '').toLowerCase().trim();
|
|
9
|
+
if (normalized === 'guard off') {
|
|
10
|
+
setGuardEnabled(false);
|
|
11
|
+
console.error('🛡️ Guard disabled — pretool validators will be skipped');
|
|
12
|
+
process.exit(2);
|
|
13
|
+
}
|
|
14
|
+
if (normalized === 'guard on') {
|
|
15
|
+
setGuardEnabled(true);
|
|
16
|
+
console.error('🛡️ Guard enabled — pretool validators active');
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}, () => '{}');
|
|
21
|
+
}
|
|
22
|
+
if (process.argv[1] &&
|
|
23
|
+
realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
|
|
24
|
+
void main();
|
|
25
|
+
}
|
|
18
26
|
//# sourceMappingURL=index.js.map
|
|
@@ -14,4 +14,5 @@ export declare function shouldLintFile(input: ToolInput): boolean;
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function lintFile(filePath: string, _projectDir: string): boolean;
|
|
16
16
|
export declare function processPostToolUse(input: ToolInput, projectDir: string): boolean;
|
|
17
|
+
export declare function main(): Promise<void>;
|
|
17
18
|
//# sourceMappingURL=lint-after-edit.d.ts.map
|
|
@@ -46,12 +46,15 @@ export function processPostToolUse(input, projectDir) {
|
|
|
46
46
|
const filePath = input.tool_input.file_path;
|
|
47
47
|
return lintFile(filePath, projectDir);
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
|
|
49
|
+
export async function main() {
|
|
51
50
|
runHook((input) => {
|
|
52
51
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
53
52
|
processPostToolUse(input, projectDir);
|
|
54
53
|
return null;
|
|
55
54
|
}, () => '{}');
|
|
56
55
|
}
|
|
56
|
+
if (process.argv[1] &&
|
|
57
|
+
realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
|
|
58
|
+
void main();
|
|
59
|
+
}
|
|
57
60
|
//# sourceMappingURL=lint-after-edit.js.map
|
|
@@ -46,7 +46,7 @@ export function validateFileConventions(input) {
|
|
|
46
46
|
if (nonCanonicalPlanningViolation) {
|
|
47
47
|
return { validator: 'file-conventions', passed: false, message: nonCanonicalPlanningViolation };
|
|
48
48
|
}
|
|
49
|
-
const blueprintPathViolation = getBlueprintPathViolation(normalized);
|
|
49
|
+
const blueprintPathViolation = getBlueprintPathViolation(normalized, undefined, input.cwd);
|
|
50
50
|
if (blueprintPathViolation) {
|
|
51
51
|
return { validator: 'file-conventions', passed: false, message: blueprintPathViolation };
|
|
52
52
|
}
|
|
@@ -29,6 +29,11 @@ interface BlockedScriptSpec {
|
|
|
29
29
|
category: CommandCategory;
|
|
30
30
|
suggestion: string;
|
|
31
31
|
}
|
|
32
|
+
interface BlockedRawNodeModulesToolSpec {
|
|
33
|
+
modulePath: string;
|
|
34
|
+
category: CommandCategory;
|
|
35
|
+
suggestion: string;
|
|
36
|
+
}
|
|
32
37
|
interface RedirectOptions {
|
|
33
38
|
mcpReady?: boolean;
|
|
34
39
|
mcp?: MCPRedirectConfig;
|
|
@@ -39,6 +44,7 @@ export declare const AUDIT_MODE_ENV = "FORBIDDEN_COMMANDS_AUDIT";
|
|
|
39
44
|
export declare const DOCS_REF = "AGENTS.md \"Forbidden Commands (CRITICAL)\" section";
|
|
40
45
|
export declare const BLOCKED_TOOLS: BlockedToolSpec[];
|
|
41
46
|
export declare const BLOCKED_SCRIPTS: BlockedScriptSpec[];
|
|
47
|
+
export declare const BLOCKED_RAW_NODE_MODULE_TOOLS: BlockedRawNodeModulesToolSpec[];
|
|
42
48
|
export declare function generateRules(): CommandRule[];
|
|
43
49
|
export declare const COMMAND_RULES: CommandRule[];
|
|
44
50
|
export declare const SUGGESTION_MODIFIERS: SuggestionModifier[];
|
|
@@ -19,8 +19,18 @@ const TYPECHECK_HINT = 'wp_typecheck MCP tool with package/file scope';
|
|
|
19
19
|
const E2E_HINT = 'wp_e2e MCP tool';
|
|
20
20
|
const ENV_HINT = 'Use the repo-approved environment wrapper for secret-bearing commands';
|
|
21
21
|
const TASK_TARGET_HINT = 'Use the repo-approved vp facade or MCP tool instead of raw execution';
|
|
22
|
-
const EXEC_RUNNERS = [
|
|
23
|
-
|
|
22
|
+
const EXEC_RUNNERS = [
|
|
23
|
+
'vp exec',
|
|
24
|
+
'pnpm exec',
|
|
25
|
+
'npm exec',
|
|
26
|
+
'npm exec --',
|
|
27
|
+
'npx',
|
|
28
|
+
'pnpx',
|
|
29
|
+
'yarn exec',
|
|
30
|
+
'yarn dlx',
|
|
31
|
+
'bunx',
|
|
32
|
+
];
|
|
33
|
+
const DIRECT_RUNNERS = ['vp', 'pnpm', 'yarn', 'yarnpkg'];
|
|
24
34
|
const SCRIPT_RUNNERS = ['vp run', 'vp', 'pnpm', 'pnpm run', 'just'];
|
|
25
35
|
export const BLOCKED_TOOLS = [
|
|
26
36
|
{
|
|
@@ -59,6 +69,11 @@ export const BLOCKED_SCRIPTS = [
|
|
|
59
69
|
{ script: 'e2e', category: 'e2e', suggestion: E2E_HINT },
|
|
60
70
|
{ script: 'qa', category: 'unknown', suggestion: QA_HINT },
|
|
61
71
|
];
|
|
72
|
+
export const BLOCKED_RAW_NODE_MODULE_TOOLS = [
|
|
73
|
+
{ modulePath: 'vitest/vitest.mjs', category: 'test', suggestion: TEST_HINT },
|
|
74
|
+
{ modulePath: 'typescript/bin/tsc', category: 'typecheck', suggestion: TYPECHECK_HINT },
|
|
75
|
+
{ modulePath: 'oxlint/bin/oxlint', category: 'lint', suggestion: LINT_HINT },
|
|
76
|
+
];
|
|
62
77
|
function escapeRegex(s) {
|
|
63
78
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
64
79
|
}
|
|
@@ -66,6 +81,9 @@ function buildToolPattern(prefix, tool) {
|
|
|
66
81
|
const escaped = prefix ? `${escapeRegex(prefix)} ${escapeRegex(tool)}` : escapeRegex(tool);
|
|
67
82
|
return new RegExp(`^${escaped}(\\s|$)`);
|
|
68
83
|
}
|
|
84
|
+
function buildRawNodeModulesToolPattern(modulePath) {
|
|
85
|
+
return new RegExp(`^node\\s+(?:\\.\\/)?node_modules\\/${escapeRegex(modulePath)}(?:\\s|$)`);
|
|
86
|
+
}
|
|
69
87
|
export function generateRules() {
|
|
70
88
|
const rules = [];
|
|
71
89
|
for (const spec of BLOCKED_TOOLS) {
|
|
@@ -104,6 +122,13 @@ export function generateRules() {
|
|
|
104
122
|
});
|
|
105
123
|
}
|
|
106
124
|
}
|
|
125
|
+
for (const spec of BLOCKED_RAW_NODE_MODULE_TOOLS) {
|
|
126
|
+
rules.push({
|
|
127
|
+
pattern: buildRawNodeModulesToolPattern(spec.modulePath),
|
|
128
|
+
category: spec.category,
|
|
129
|
+
suggestion: spec.suggestion,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
107
132
|
rules.push({
|
|
108
133
|
pattern: /^vp exec markdownlint-cli2\b/,
|
|
109
134
|
category: 'unknown',
|
|
@@ -11,5 +11,6 @@ export declare function getNonCanonicalPlanningPathViolation(filePath: string, b
|
|
|
11
11
|
* accepted blueprints root layout (or the explicitly provided root).
|
|
12
12
|
*/
|
|
13
13
|
export declare function isCanonicalBlueprintOverviewPath(filePath: string, blueprintsRoot?: string): boolean;
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function isCanonicalBlueprintDocumentPath(filePath: string, blueprintsRoot?: string): boolean;
|
|
15
|
+
export declare function getBlueprintPathViolation(filePath: string, blueprintsRoot?: string, cwd?: string): string | null;
|
|
15
16
|
//# sourceMappingURL=path-contract.d.ts.map
|
|
@@ -1,16 +1,10 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { BLUEPRINT_OVERVIEW_FILENAME, getBlueprintAlternateDocumentPath, isBlueprintSupportingMarkdownRelativePath, isBlueprintStatus, parseBlueprintDocumentRelativePath, } from '#utils/document-paths.js';
|
|
1
4
|
export const BLUEPRINTS_ROOT = 'webpresso/blueprints';
|
|
2
5
|
const DEFAULT_BLUEPRINTS_ROOT = 'blueprints';
|
|
3
6
|
export const TECH_DEBT_ROOT = 'webpresso/tech-debt';
|
|
4
7
|
const DEFAULT_TECH_DEBT_ROOT = 'tech-debt';
|
|
5
|
-
const BLUEPRINT_STATUSES = new Set([
|
|
6
|
-
'draft',
|
|
7
|
-
'planned',
|
|
8
|
-
'parked',
|
|
9
|
-
'in-progress',
|
|
10
|
-
'completed',
|
|
11
|
-
'archived',
|
|
12
|
-
]);
|
|
13
|
-
const KEBAB_CASE_SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
14
8
|
// Both canonical blueprint-root layouts accepted by default.
|
|
15
9
|
const CANONICAL_BLUEPRINTS_ROOTS = [BLUEPRINTS_ROOT, DEFAULT_BLUEPRINTS_ROOT];
|
|
16
10
|
const CANONICAL_TECH_DEBT_ROOTS = [TECH_DEBT_ROOT, DEFAULT_TECH_DEBT_ROOT];
|
|
@@ -20,6 +14,16 @@ function normalizePlanningPath(filePath) {
|
|
|
20
14
|
function matchesRoot(normalized, root) {
|
|
21
15
|
return normalized === root || normalized.startsWith(`${root}/`);
|
|
22
16
|
}
|
|
17
|
+
function stripBlueprintRoot(normalized, roots) {
|
|
18
|
+
for (const root of roots) {
|
|
19
|
+
if (normalized === root)
|
|
20
|
+
return { relativePath: '', root };
|
|
21
|
+
if (normalized.startsWith(`${root}/`)) {
|
|
22
|
+
return { relativePath: normalized.slice(root.length + 1), root };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
23
27
|
/**
|
|
24
28
|
* Returns true if the path is under any accepted blueprints root.
|
|
25
29
|
* Pass `blueprintsRoot` to restrict to a single configured root.
|
|
@@ -66,39 +70,60 @@ export function getNonCanonicalPlanningPathViolation(filePath, blueprintsRoot, t
|
|
|
66
70
|
* accepted blueprints root layout (or the explicitly provided root).
|
|
67
71
|
*/
|
|
68
72
|
export function isCanonicalBlueprintOverviewPath(filePath, blueprintsRoot) {
|
|
73
|
+
const parsed = getCanonicalBlueprintDocument(filePath, blueprintsRoot);
|
|
74
|
+
return parsed?.shape === 'folder';
|
|
75
|
+
}
|
|
76
|
+
export function isCanonicalBlueprintDocumentPath(filePath, blueprintsRoot) {
|
|
77
|
+
return getCanonicalBlueprintDocument(filePath, blueprintsRoot) !== null;
|
|
78
|
+
}
|
|
79
|
+
function getCanonicalBlueprintDocument(filePath, blueprintsRoot) {
|
|
69
80
|
const normalized = normalizePlanningPath(filePath);
|
|
70
81
|
const roots = blueprintsRoot ? [blueprintsRoot] : CANONICAL_BLUEPRINTS_ROOTS;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const parts = normalized.split('/');
|
|
74
|
-
const n = rootParts.length;
|
|
75
|
-
return (parts.length === n + 3 &&
|
|
76
|
-
parts.slice(0, n).join('/') === root &&
|
|
77
|
-
BLUEPRINT_STATUSES.has(parts[n] ?? '') &&
|
|
78
|
-
KEBAB_CASE_SEGMENT.test(parts[n + 1] ?? '') &&
|
|
79
|
-
parts[n + 2] === '_overview.md');
|
|
80
|
-
});
|
|
82
|
+
const stripped = stripBlueprintRoot(normalized, roots);
|
|
83
|
+
return stripped ? parseBlueprintDocumentRelativePath(stripped.relativePath) : null;
|
|
81
84
|
}
|
|
82
|
-
export function getBlueprintPathViolation(filePath, blueprintsRoot) {
|
|
85
|
+
export function getBlueprintPathViolation(filePath, blueprintsRoot, cwd = process.cwd()) {
|
|
83
86
|
const normalized = normalizePlanningPath(filePath);
|
|
84
87
|
if (!isBlueprintPath(normalized, blueprintsRoot))
|
|
85
88
|
return null;
|
|
86
|
-
if (normalized.endsWith('/_overview.md') &&
|
|
87
|
-
!isCanonicalBlueprintOverviewPath(normalized, blueprintsRoot)) {
|
|
88
|
-
const root = blueprintsRoot ?? BLUEPRINTS_ROOT;
|
|
89
|
-
return `Blueprint overview files must live at ${root}/<status>/<slug>/_overview.md. Got: ${normalized}`;
|
|
90
|
-
}
|
|
91
89
|
const roots = blueprintsRoot ? [blueprintsRoot] : CANONICAL_BLUEPRINTS_ROOTS;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
const stripped = stripBlueprintRoot(normalized, roots);
|
|
91
|
+
if (!stripped) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const parsed = parseBlueprintDocumentRelativePath(stripped.relativePath);
|
|
95
|
+
if (parsed) {
|
|
96
|
+
const blueprintRoot = path.isAbsolute(filePath)
|
|
97
|
+
? path.join(path.parse(filePath).root, stripped.root)
|
|
98
|
+
: path.join(cwd, stripped.root);
|
|
99
|
+
const currentPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, normalized);
|
|
100
|
+
const alternate = getBlueprintAlternateDocumentPath(blueprintRoot, currentPath);
|
|
101
|
+
if (alternate && existsSync(alternate)) {
|
|
102
|
+
return `Blueprint slug "${parsed.state}/${parsed.slug}" cannot exist in both flat and folder forms. Remove either ${path.relative(cwd, filePath).replace(/\\/g, '/')} or ${path.relative(cwd, alternate).replace(/\\/g, '/')}.`;
|
|
101
103
|
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const parts = stripped.relativePath.split('/').filter((segment) => segment.length > 0);
|
|
107
|
+
const [state, slug, doc] = parts;
|
|
108
|
+
const root = stripped.root;
|
|
109
|
+
if (parts.length === 2 &&
|
|
110
|
+
typeof doc === 'undefined' &&
|
|
111
|
+
typeof slug === 'string' &&
|
|
112
|
+
slug.endsWith('.md')) {
|
|
113
|
+
return `Blueprint markdown under ${root}/<status>/ must be either <slug>.md or <slug>/${BLUEPRINT_OVERVIEW_FILENAME}. Got: ${normalized}`;
|
|
114
|
+
}
|
|
115
|
+
if (parts.length === 3 && doc === BLUEPRINT_OVERVIEW_FILENAME) {
|
|
116
|
+
return `Blueprint overview files must live at ${root}/<status>/<slug>/${BLUEPRINT_OVERVIEW_FILENAME}. Got: ${normalized}`;
|
|
117
|
+
}
|
|
118
|
+
if (parts.length === 3 && isBlueprintSupportingMarkdownRelativePath(stripped.relativePath)) {
|
|
119
|
+
const canonicalOverviewPath = path.join(cwd, root, state ?? '', slug ?? '', BLUEPRINT_OVERVIEW_FILENAME);
|
|
120
|
+
if (!existsSync(canonicalOverviewPath)) {
|
|
121
|
+
return `Supporting blueprint markdown requires ${root}/${state}/${slug}/${BLUEPRINT_OVERVIEW_FILENAME}. Got: ${normalized}`;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (parts.length >= 3 && isBlueprintStatus(state)) {
|
|
126
|
+
return `Blueprint markdown must use one of ${root}/<status>/<slug>.md or ${root}/<status>/<slug>/${BLUEPRINT_OVERVIEW_FILENAME}. Supporting markdown is only allowed beside ${BLUEPRINT_OVERVIEW_FILENAME}. Got: ${normalized}`;
|
|
102
127
|
}
|
|
103
128
|
return null;
|
|
104
129
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import jsYaml from 'js-yaml';
|
|
2
2
|
import { getContent, getFilePath } from '#hooks/shared/types';
|
|
3
|
-
import { getNonCanonicalPlanningPathViolation, isBlueprintPath } from './path-contract.js';
|
|
3
|
+
import { getNonCanonicalPlanningPathViolation, isBlueprintPath, isCanonicalBlueprintDocumentPath, } from './path-contract.js';
|
|
4
4
|
import { createSkipResult } from './skip-result.js';
|
|
5
5
|
// Keep aligned with webpresso/blueprint planStatusSchema + plan type enum.
|
|
6
6
|
const VALID_TYPES = ['blueprint', 'parent-roadmap'];
|
|
@@ -10,8 +10,8 @@ function shouldValidatePath(filePath) {
|
|
|
10
10
|
const normalized = filePath.startsWith('/') ? filePath.slice(1) : filePath;
|
|
11
11
|
const nonCanonicalPlanningPath = getNonCanonicalPlanningPathViolation(normalized);
|
|
12
12
|
const currentPath = isBlueprintPath(normalized);
|
|
13
|
-
const
|
|
14
|
-
return !nonCanonicalPlanningPath && currentPath &&
|
|
13
|
+
const isCanonicalBlueprintDoc = isCanonicalBlueprintDocumentPath(normalized);
|
|
14
|
+
return !nonCanonicalPlanningPath && currentPath && isCanonicalBlueprintDoc;
|
|
15
15
|
}
|
|
16
16
|
export function extractFrontmatterBlock(content) {
|
|
17
17
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|