@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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Run Stryker only on packages changed vs. the base branch.
|
|
4
|
+
* Returns 0 on success, 1 if any package fails its break threshold.
|
|
5
|
+
*
|
|
6
|
+
* Reads GITHUB_BASE_REF (set by GitHub Actions on pull_request events) to
|
|
7
|
+
* determine the base branch; falls back to "main".
|
|
8
|
+
*/
|
|
9
|
+
export function runAffectedMutation() {
|
|
10
|
+
const base = process.env.GITHUB_BASE_REF ?? 'main';
|
|
11
|
+
const changed = execSync(`git diff --name-only origin/${base}...HEAD`)
|
|
12
|
+
.toString()
|
|
13
|
+
.trim()
|
|
14
|
+
.split('\n')
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
const affectedPkgs = new Set();
|
|
17
|
+
for (const file of changed) {
|
|
18
|
+
const match = file.match(/^(apps\/[^/]+|packages\/[^/]+)\//);
|
|
19
|
+
if (match)
|
|
20
|
+
affectedPkgs.add(match[1]);
|
|
21
|
+
}
|
|
22
|
+
if (affectedPkgs.size === 0) {
|
|
23
|
+
console.log('No affected packages — skipping mutation.');
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
for (const pkg of affectedPkgs) {
|
|
27
|
+
try {
|
|
28
|
+
execSync(`pnpm --filter ./${pkg} mutation --if-present`, { stdio: 'inherit' });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=affected.js.map
|
package/dist/esm/package.json
CHANGED
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"#tool-runtime": "./tool-runtime/index.js",
|
|
52
52
|
"#tool-runtime/*.js": "./tool-runtime/*.js",
|
|
53
53
|
"#tool-runtime/*": "./tool-runtime/*.js",
|
|
54
|
+
"#wp-extension": "./wp-extension/index.js",
|
|
55
|
+
"#wp-extension/*.js": "./wp-extension/*.js",
|
|
56
|
+
"#wp-extension/*": "./wp-extension/*.js",
|
|
57
|
+
"#runtime/*.js": "./runtime/*.js",
|
|
58
|
+
"#runtime/*": "./runtime/*.js",
|
|
54
59
|
"#output-transforms/*.js": "./output-transforms/*.js",
|
|
55
60
|
"#output-transforms/*": "./output-transforms/*.js",
|
|
56
61
|
"#lint/*.js": "./lint/*.js",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const ACCEPTED_PACKAGE_PREFIXES = ['@webpresso/agent-kit', 'webpresso'];
|
|
5
|
+
const MAX_UPWARD_LEVELS = 8;
|
|
6
|
+
function matchesOwnedPackageName(name) {
|
|
7
|
+
return (typeof name === 'string' &&
|
|
8
|
+
ACCEPTED_PACKAGE_PREFIXES.some((prefix) => name === prefix || name.startsWith(`${prefix}-`)));
|
|
9
|
+
}
|
|
10
|
+
function readVersionFromDir(startDir) {
|
|
11
|
+
let dir = path.resolve(startDir);
|
|
12
|
+
for (let i = 0; i < MAX_UPWARD_LEVELS; i++) {
|
|
13
|
+
const candidate = path.join(dir, 'package.json');
|
|
14
|
+
if (existsSync(candidate)) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
17
|
+
if (matchesOwnedPackageName(parsed.name) && typeof parsed.version === 'string') {
|
|
18
|
+
return parsed.version;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// keep walking
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const parent = path.dirname(dir);
|
|
26
|
+
if (parent === dir)
|
|
27
|
+
break;
|
|
28
|
+
dir = parent;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export function readOwnedPackageVersion(moduleUrl) {
|
|
33
|
+
const fromModule = readVersionFromDir(path.dirname(fileURLToPath(moduleUrl)));
|
|
34
|
+
if (fromModule)
|
|
35
|
+
return fromModule;
|
|
36
|
+
if (process.execPath) {
|
|
37
|
+
const fromExec = readVersionFromDir(path.dirname(process.execPath));
|
|
38
|
+
if (fromExec)
|
|
39
|
+
return fromExec;
|
|
40
|
+
}
|
|
41
|
+
return '0.0.0';
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=package-version.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResolvedTestTarget } from './target-resolver.js';
|
|
2
|
+
import { type ManagedRunnerOutputPolicy } from '#tool-runtime';
|
|
2
3
|
export interface CommandConfig {
|
|
3
4
|
command: string;
|
|
4
5
|
args: string[];
|
|
@@ -6,6 +7,7 @@ export interface CommandConfig {
|
|
|
6
7
|
}
|
|
7
8
|
export type VpRunLogMode = 'interleaved' | 'labeled' | 'grouped';
|
|
8
9
|
export interface TestCommandOptions {
|
|
10
|
+
cwd?: string;
|
|
9
11
|
watch?: boolean;
|
|
10
12
|
coverage?: boolean;
|
|
11
13
|
testNamePattern?: string;
|
|
@@ -18,6 +20,7 @@ export interface TestCommandOptions {
|
|
|
18
20
|
log?: VpRunLogMode;
|
|
19
21
|
passthrough?: readonly string[];
|
|
20
22
|
filterOutput?: boolean;
|
|
23
|
+
outputPolicy?: ManagedRunnerOutputPolicy;
|
|
21
24
|
}
|
|
22
25
|
export declare function buildTestCommand(target: ResolvedTestTarget, options?: TestCommandOptions): CommandConfig;
|
|
23
26
|
export declare function buildVpTestCommand(filters: readonly string[], options?: TestCommandOptions): CommandConfig;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { getManagedRunner } from '#tool-runtime';
|
|
2
|
+
import { getPackageScript, isRecursiveWpScript, packageUsesVitest } from '#cli/package-scripts.js';
|
|
2
3
|
export function buildTestCommand(target, options = {}) {
|
|
4
|
+
if (target.type === 'all' && shouldBypassRecursiveWpTest(options.cwd ?? process.cwd())) {
|
|
5
|
+
return buildVitestCommand([], options);
|
|
6
|
+
}
|
|
3
7
|
if (target.type === 'file') {
|
|
4
8
|
return buildVitestCommand(target.values, options);
|
|
5
9
|
}
|
|
@@ -8,7 +12,7 @@ export function buildTestCommand(target, options = {}) {
|
|
|
8
12
|
export function buildVpTestCommand(filters, options = {}) {
|
|
9
13
|
const task = getVpTestTask(options);
|
|
10
14
|
const resolvedFilters = filters.map((filter) => formatVpRunFilter(filter, task));
|
|
11
|
-
const explicitTargets = resolvedFilters.every(isExplicitVpTaskTarget);
|
|
15
|
+
const explicitTargets = resolvedFilters.length > 0 && resolvedFilters.every(isExplicitVpTaskTarget);
|
|
12
16
|
const args = ['run', ...resolvedFilters];
|
|
13
17
|
appendVpRunOptions(args, options);
|
|
14
18
|
if (!explicitTargets) {
|
|
@@ -18,7 +22,9 @@ export function buildVpTestCommand(filters, options = {}) {
|
|
|
18
22
|
if (passthrough.length > 0) {
|
|
19
23
|
args.push('--', ...passthrough);
|
|
20
24
|
}
|
|
21
|
-
const resolution = getManagedRunner('vp', {
|
|
25
|
+
const resolution = getManagedRunner('vp', {
|
|
26
|
+
outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
|
|
27
|
+
});
|
|
22
28
|
const env = buildVpRunEnv(options);
|
|
23
29
|
const mergedArgs = [...resolution.args, ...args];
|
|
24
30
|
return env
|
|
@@ -45,7 +51,9 @@ export function buildVitestCommand(files, options = {}) {
|
|
|
45
51
|
args.push('--config', configFile);
|
|
46
52
|
}
|
|
47
53
|
args.push(...buildVitestPassthrough(options), ...testFiles);
|
|
48
|
-
const resolution = getManagedRunner('vitest', {
|
|
54
|
+
const resolution = getManagedRunner('vitest', {
|
|
55
|
+
outputPolicy: resolveOutputPolicy(options.outputPolicy, options.filterOutput),
|
|
56
|
+
});
|
|
49
57
|
return { command: resolution.command, args: [...resolution.args, ...args] };
|
|
50
58
|
}
|
|
51
59
|
export function getVpTestTask(options) {
|
|
@@ -101,4 +109,15 @@ function buildVitestPassthrough(options) {
|
|
|
101
109
|
function isVitestConfigFile(file) {
|
|
102
110
|
return /^vitest(?:\.[\w-]+)?\.config\.(?:ts|mts|cts|js|mjs|cjs)$/u.test(file);
|
|
103
111
|
}
|
|
112
|
+
function resolveOutputPolicy(outputPolicy, filterOutput) {
|
|
113
|
+
if (outputPolicy)
|
|
114
|
+
return outputPolicy;
|
|
115
|
+
return filterOutput === false ? 'structured' : 'rtk-filtered';
|
|
116
|
+
}
|
|
117
|
+
function shouldBypassRecursiveWpTest(cwd) {
|
|
118
|
+
const testScript = getPackageScript(cwd, 'test');
|
|
119
|
+
if (!testScript || !isRecursiveWpScript(testScript, 'test'))
|
|
120
|
+
return false;
|
|
121
|
+
return packageUsesVitest(cwd);
|
|
122
|
+
}
|
|
104
123
|
//# sourceMappingURL=command-builder.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ManagedRunnerResolution, type ResolveRunnerOptions } from './resolve-runner.js';
|
|
1
|
+
import { type ManagedRunnerResolution, type ResolveRunnerOptions, type ManagedRunnerOutputPolicy } from './resolve-runner.js';
|
|
2
2
|
export declare function getManagedRunner(tool: string, options?: ResolveRunnerOptions): ManagedRunnerResolution;
|
|
3
3
|
export declare function clearManagedRunnerCache(): void;
|
|
4
|
-
export type { ManagedRunnerResolution, ResolveRunnerOptions };
|
|
4
|
+
export type { ManagedRunnerOutputPolicy, ManagedRunnerResolution, ResolveRunnerOptions };
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { resolveRunner, } from './resolve-runner.js';
|
|
2
2
|
const runtimeCache = new Map();
|
|
3
3
|
function cacheKey(tool, options) {
|
|
4
|
+
const outputPolicy = options.outputPolicy ?? (options.filterOutput === false ? 'structured' : 'rtk-filtered');
|
|
4
5
|
return JSON.stringify({
|
|
5
6
|
tool,
|
|
6
|
-
|
|
7
|
+
outputPolicy,
|
|
7
8
|
fallbackCommand: options.fallbackCommand ?? null,
|
|
8
9
|
fallbackArgs: options.fallbackArgs ?? [],
|
|
9
10
|
});
|
|
@@ -4,10 +4,13 @@ export interface ManagedRunnerResolution {
|
|
|
4
4
|
readonly args: readonly string[];
|
|
5
5
|
readonly source: 'managed' | 'fallback';
|
|
6
6
|
}
|
|
7
|
+
export type ManagedRunnerOutputPolicy = 'rtk-filtered' | 'structured';
|
|
7
8
|
export interface ResolveRunnerOptions {
|
|
8
9
|
readonly fallbackCommand?: string;
|
|
9
10
|
readonly fallbackArgs?: readonly string[];
|
|
11
|
+
/** @deprecated Use {@link outputPolicy} for explicit output routing. */
|
|
10
12
|
readonly filterOutput?: boolean;
|
|
13
|
+
readonly outputPolicy?: ManagedRunnerOutputPolicy;
|
|
11
14
|
}
|
|
12
15
|
export declare function resolveRunner(tool: string, options?: ResolveRunnerOptions): ManagedRunnerResolution;
|
|
13
16
|
//# sourceMappingURL=resolve-runner.d.ts.map
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const MANAGED_TOOL_PREFIX = {
|
|
2
|
+
oxfmt: { command: 'vp', args: ['exec', 'oxfmt'] },
|
|
2
3
|
playwright: { command: 'vp', args: ['exec', 'playwright'] },
|
|
4
|
+
tsc: { command: 'vp', args: ['exec', 'tsc'] },
|
|
3
5
|
vitest: { command: 'vp', args: ['exec', 'vitest'] },
|
|
4
6
|
vp: { command: 'vp', args: [] },
|
|
5
7
|
};
|
|
6
|
-
function withOptionalRtk(resolution,
|
|
7
|
-
if (
|
|
8
|
+
function withOptionalRtk(resolution, outputPolicy) {
|
|
9
|
+
if (outputPolicy !== 'rtk-filtered')
|
|
8
10
|
return resolution;
|
|
9
11
|
return {
|
|
10
12
|
...resolution,
|
|
@@ -17,7 +19,7 @@ export function resolveRunner(tool, options = {}) {
|
|
|
17
19
|
if (!normalized) {
|
|
18
20
|
throw new Error('tool runtime resolution requires a non-empty tool name');
|
|
19
21
|
}
|
|
20
|
-
const
|
|
22
|
+
const outputPolicy = options.outputPolicy ?? (options.filterOutput === false ? 'structured' : 'rtk-filtered');
|
|
21
23
|
const managed = MANAGED_TOOL_PREFIX[normalized];
|
|
22
24
|
if (managed) {
|
|
23
25
|
return withOptionalRtk({
|
|
@@ -25,7 +27,7 @@ export function resolveRunner(tool, options = {}) {
|
|
|
25
27
|
command: managed.command,
|
|
26
28
|
args: [...managed.args],
|
|
27
29
|
source: 'managed',
|
|
28
|
-
},
|
|
30
|
+
}, outputPolicy);
|
|
29
31
|
}
|
|
30
32
|
if (options.fallbackCommand) {
|
|
31
33
|
return withOptionalRtk({
|
|
@@ -33,7 +35,7 @@ export function resolveRunner(tool, options = {}) {
|
|
|
33
35
|
command: options.fallbackCommand,
|
|
34
36
|
args: [...(options.fallbackArgs ?? [])],
|
|
35
37
|
source: 'fallback',
|
|
36
|
-
},
|
|
38
|
+
}, outputPolicy);
|
|
37
39
|
}
|
|
38
40
|
throw new Error(`No managed runtime runner is defined for tool "${normalized}"`);
|
|
39
41
|
}
|
|
@@ -12,6 +12,7 @@ import { join } from 'node:path';
|
|
|
12
12
|
import { globSync } from 'glob';
|
|
13
13
|
import { isRunFailure, runCommand } from '#mcp/tools/_shared/run-command';
|
|
14
14
|
import { resolveProjectRoot } from '#mcp/tools/_shared/project-root';
|
|
15
|
+
import { getManagedRunner } from '#tool-runtime';
|
|
15
16
|
const DEFAULT_TYPECHECK_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
16
17
|
// Matches both standard tsc formats:
|
|
17
18
|
// src/foo.ts(5,12): error TS2304: Cannot find name 'bar'.
|
|
@@ -99,11 +100,12 @@ export async function runTypecheck(options = {}) {
|
|
|
99
100
|
const targets = options.packages && options.packages.length > 0 ? options.packages : null;
|
|
100
101
|
const workspaceGlobs = targets ? readWorkspaceGlobs(cwd) : null;
|
|
101
102
|
const runs = [];
|
|
103
|
+
const resolution = getManagedRunner('tsc', { outputPolicy: 'structured' });
|
|
102
104
|
if (targets) {
|
|
103
105
|
for (const pkg of targets) {
|
|
104
106
|
const resolvedTarget = resolveTypecheckTarget(cwd, pkg, workspaceGlobs);
|
|
105
107
|
const tsconfig = join(resolvedTarget, 'tsconfig.json');
|
|
106
|
-
const outcome = await runCommand(
|
|
108
|
+
const outcome = await runCommand(resolution.command, [...resolution.args, '--noEmit', '-p', tsconfig], runOptions);
|
|
107
109
|
if (isRunFailure(outcome)) {
|
|
108
110
|
throw outcome.error;
|
|
109
111
|
}
|
|
@@ -111,7 +113,7 @@ export async function runTypecheck(options = {}) {
|
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
else {
|
|
114
|
-
const outcome = await runCommand(
|
|
116
|
+
const outcome = await runCommand(resolution.command, [...resolution.args, '--noEmit'], runOptions);
|
|
115
117
|
if (isRunFailure(outcome)) {
|
|
116
118
|
throw outcome.error;
|
|
117
119
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CAC } from 'cac';
|
|
2
|
+
export interface WpExtensionContext {
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
readonly env: NodeJS.ProcessEnv;
|
|
5
|
+
}
|
|
6
|
+
export interface WpExtensionCommandV1 {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly register: (cli: CAC) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface WpExtensionAliasV1 {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly commandName: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WpExtensionV1 {
|
|
16
|
+
readonly apiVersion: '1';
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly version: string;
|
|
19
|
+
readonly hostRange: string;
|
|
20
|
+
readonly detect: (context: WpExtensionContext) => boolean | Promise<boolean>;
|
|
21
|
+
readonly commands: readonly WpExtensionCommandV1[];
|
|
22
|
+
readonly aliases?: readonly WpExtensionAliasV1[];
|
|
23
|
+
}
|
|
24
|
+
export interface LoadedWpExtension {
|
|
25
|
+
readonly packageName: string;
|
|
26
|
+
readonly specifier: string;
|
|
27
|
+
readonly extension?: WpExtensionV1;
|
|
28
|
+
readonly compatible: boolean;
|
|
29
|
+
readonly detected: boolean;
|
|
30
|
+
readonly warnings: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
export interface WpExtensionAliasResolution {
|
|
33
|
+
readonly aliases: ReadonlyMap<string, WpExtensionAliasV1>;
|
|
34
|
+
readonly warnings: readonly string[];
|
|
35
|
+
readonly acceptedCommandNames: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface LoadWpExtensionsOptions {
|
|
38
|
+
readonly cwd?: string;
|
|
39
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
40
|
+
readonly hostVersion: string;
|
|
41
|
+
readonly importModule?: (specifier: string) => Promise<{
|
|
42
|
+
default?: unknown;
|
|
43
|
+
}>;
|
|
44
|
+
readonly resolveFrom?: (fromFile: string, specifier: string) => string;
|
|
45
|
+
readonly readJsonFile?: (path: string) => unknown;
|
|
46
|
+
}
|
|
47
|
+
export declare function loadWpExtensions(options: LoadWpExtensionsOptions): Promise<readonly LoadedWpExtension[]>;
|
|
48
|
+
export declare function resolveAcceptedExtensionAliases(extensions: readonly LoadedWpExtension[], baseCommands: Iterable<string>, acceptedCommandNames: Iterable<string>): WpExtensionAliasResolution;
|
|
49
|
+
export declare function isWpExtensionV1(value: unknown): value is WpExtensionV1;
|
|
50
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
export async function loadWpExtensions(options) {
|
|
6
|
+
const cwd = options.cwd ?? process.cwd();
|
|
7
|
+
const env = options.env ?? process.env;
|
|
8
|
+
const readJsonFile = options.readJsonFile ?? defaultReadJsonFile;
|
|
9
|
+
const resolveFrom = options.resolveFrom ?? defaultResolveFrom;
|
|
10
|
+
const importModule = options.importModule ?? defaultImportModule;
|
|
11
|
+
const rootManifestPath = join(cwd, 'package.json');
|
|
12
|
+
const rootManifest = readJsonFile(rootManifestPath);
|
|
13
|
+
const enabled = collectEnabledExtensionPackageNames(rootManifest);
|
|
14
|
+
const loaded = [];
|
|
15
|
+
for (const warning of enabled.warnings) {
|
|
16
|
+
loaded.push({
|
|
17
|
+
packageName: rootManifest?.name ?? '<root>',
|
|
18
|
+
specifier: 'webpresso.wpExtensions',
|
|
19
|
+
compatible: false,
|
|
20
|
+
detected: false,
|
|
21
|
+
warnings: [warning],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
for (const packageName of enabled.packageNames) {
|
|
25
|
+
const packageJsonPath = tryResolve(() => resolveFrom(rootManifestPath, `${packageName}/package.json`));
|
|
26
|
+
if (!packageJsonPath)
|
|
27
|
+
continue;
|
|
28
|
+
const dependencyManifest = readJsonFile(packageJsonPath);
|
|
29
|
+
const extensionSpecifier = dependencyManifest?.webpresso?.wpExtension;
|
|
30
|
+
if (typeof extensionSpecifier !== 'string' || extensionSpecifier.trim().length === 0)
|
|
31
|
+
continue;
|
|
32
|
+
const normalizedSpecifier = extensionSpecifier.trim();
|
|
33
|
+
const modulePath = tryResolve(() => resolveFrom(packageJsonPath, normalizedSpecifier.startsWith('.') ? normalizedSpecifier : normalizedSpecifier));
|
|
34
|
+
if (!modulePath) {
|
|
35
|
+
loaded.push({
|
|
36
|
+
packageName,
|
|
37
|
+
specifier: normalizedSpecifier,
|
|
38
|
+
compatible: false,
|
|
39
|
+
detected: false,
|
|
40
|
+
warnings: [`${packageName}: could not resolve wp extension "${normalizedSpecifier}"`],
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
let mod;
|
|
45
|
+
try {
|
|
46
|
+
mod = await importModule(modulePath);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
loaded.push({
|
|
50
|
+
packageName,
|
|
51
|
+
specifier: normalizedSpecifier,
|
|
52
|
+
compatible: false,
|
|
53
|
+
detected: false,
|
|
54
|
+
warnings: [`${packageName}: failed to load wp extension — ${formatError(error)}`],
|
|
55
|
+
});
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!isWpExtensionV1(mod.default)) {
|
|
59
|
+
loaded.push({
|
|
60
|
+
packageName,
|
|
61
|
+
specifier: normalizedSpecifier,
|
|
62
|
+
compatible: false,
|
|
63
|
+
detected: false,
|
|
64
|
+
warnings: [
|
|
65
|
+
`${packageName}: wp extension module must default-export a WpExtensionV1 object`,
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const extension = mod.default;
|
|
71
|
+
const compatible = satisfiesHostRange(options.hostVersion, extension.hostRange);
|
|
72
|
+
if (!compatible) {
|
|
73
|
+
loaded.push({
|
|
74
|
+
packageName,
|
|
75
|
+
specifier: normalizedSpecifier,
|
|
76
|
+
extension,
|
|
77
|
+
compatible: false,
|
|
78
|
+
detected: false,
|
|
79
|
+
warnings: [
|
|
80
|
+
`${packageName}: wp extension requires host ${extension.hostRange} but current host is ${options.hostVersion}`,
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
let detected = false;
|
|
86
|
+
try {
|
|
87
|
+
detected = await extension.detect({ cwd, env });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
loaded.push({
|
|
91
|
+
packageName,
|
|
92
|
+
specifier: normalizedSpecifier,
|
|
93
|
+
extension,
|
|
94
|
+
compatible: true,
|
|
95
|
+
detected: false,
|
|
96
|
+
warnings: [`${packageName}: extension detect() threw: ${formatError(error)}`],
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
loaded.push({
|
|
101
|
+
packageName,
|
|
102
|
+
specifier: normalizedSpecifier,
|
|
103
|
+
extension,
|
|
104
|
+
compatible: true,
|
|
105
|
+
detected,
|
|
106
|
+
warnings: [],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return loaded;
|
|
110
|
+
}
|
|
111
|
+
export function resolveAcceptedExtensionAliases(extensions, baseCommands, acceptedCommandNames) {
|
|
112
|
+
const blocked = new Set(baseCommands);
|
|
113
|
+
const knownCommands = new Set(acceptedCommandNames);
|
|
114
|
+
for (const commandName of acceptedCommandNames) {
|
|
115
|
+
blocked.add(commandName);
|
|
116
|
+
}
|
|
117
|
+
const aliases = new Map();
|
|
118
|
+
const warnings = [];
|
|
119
|
+
for (const extension of extensions) {
|
|
120
|
+
if (!extension.extension || !extension.compatible || !extension.detected)
|
|
121
|
+
continue;
|
|
122
|
+
for (const alias of extension.extension.aliases ?? []) {
|
|
123
|
+
if (!knownCommands.has(alias.commandName)) {
|
|
124
|
+
warnings.push(`${extension.packageName}: skipped alias "${alias.name}" because command "${alias.commandName}" is not registered`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (blocked.has(alias.name)) {
|
|
128
|
+
warnings.push(`${extension.packageName}: skipped alias "${alias.name}" because it collides with an existing wp command`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (aliases.has(alias.name)) {
|
|
132
|
+
warnings.push(`${extension.packageName}: skipped alias "${alias.name}" because another extension already claimed it`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
aliases.set(alias.name, alias);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { aliases, warnings, acceptedCommandNames: [...acceptedCommandNames] };
|
|
139
|
+
}
|
|
140
|
+
export function isWpExtensionV1(value) {
|
|
141
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
142
|
+
return false;
|
|
143
|
+
const candidate = value;
|
|
144
|
+
if (candidate.apiVersion !== '1')
|
|
145
|
+
return false;
|
|
146
|
+
if (typeof candidate.name !== 'string' || candidate.name.length === 0)
|
|
147
|
+
return false;
|
|
148
|
+
if (typeof candidate.version !== 'string' || candidate.version.length === 0)
|
|
149
|
+
return false;
|
|
150
|
+
if (typeof candidate.hostRange !== 'string' || candidate.hostRange.length === 0)
|
|
151
|
+
return false;
|
|
152
|
+
if (typeof candidate.detect !== 'function')
|
|
153
|
+
return false;
|
|
154
|
+
if (!Array.isArray(candidate.commands))
|
|
155
|
+
return false;
|
|
156
|
+
return candidate.commands.every((command) => command &&
|
|
157
|
+
typeof command === 'object' &&
|
|
158
|
+
typeof command.name === 'string' &&
|
|
159
|
+
typeof command.description === 'string' &&
|
|
160
|
+
typeof command.register === 'function');
|
|
161
|
+
}
|
|
162
|
+
function collectEnabledExtensionPackageNames(manifest) {
|
|
163
|
+
if (!manifest)
|
|
164
|
+
return { packageNames: [], warnings: [] };
|
|
165
|
+
const enabled = manifest.webpresso?.wpExtensions;
|
|
166
|
+
if (enabled === undefined || enabled === false)
|
|
167
|
+
return { packageNames: [], warnings: [] };
|
|
168
|
+
const dependencyNames = collectDependencyNames(manifest);
|
|
169
|
+
if (enabled === true)
|
|
170
|
+
return { packageNames: dependencyNames, warnings: [] };
|
|
171
|
+
if (!Array.isArray(enabled)) {
|
|
172
|
+
return {
|
|
173
|
+
packageNames: [],
|
|
174
|
+
warnings: ['root package webpresso.wpExtensions must be true or an array of package names'],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const dependencySet = new Set(dependencyNames);
|
|
178
|
+
const packageNames = [];
|
|
179
|
+
const warnings = [];
|
|
180
|
+
for (const entry of enabled) {
|
|
181
|
+
if (typeof entry !== 'string' || entry.trim().length === 0) {
|
|
182
|
+
warnings.push('root package webpresso.wpExtensions contains a non-string package name');
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const packageName = entry.trim();
|
|
186
|
+
if (!dependencySet.has(packageName)) {
|
|
187
|
+
warnings.push(`root package enables wp extension "${packageName}" but it is not a direct dependency`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
packageNames.push(packageName);
|
|
191
|
+
}
|
|
192
|
+
return { packageNames: [...new Set(packageNames)], warnings };
|
|
193
|
+
}
|
|
194
|
+
function collectDependencyNames(manifest) {
|
|
195
|
+
const packageNames = new Set();
|
|
196
|
+
for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
197
|
+
const dependencies = manifest[section];
|
|
198
|
+
if (!dependencies || typeof dependencies !== 'object' || Array.isArray(dependencies))
|
|
199
|
+
continue;
|
|
200
|
+
for (const packageName of Object.keys(dependencies))
|
|
201
|
+
packageNames.add(packageName);
|
|
202
|
+
}
|
|
203
|
+
return [...packageNames];
|
|
204
|
+
}
|
|
205
|
+
function defaultReadJsonFile(path) {
|
|
206
|
+
if (!existsSync(path))
|
|
207
|
+
return;
|
|
208
|
+
try {
|
|
209
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function defaultResolveFrom(fromFile, specifier) {
|
|
216
|
+
const localRequire = createRequire(fromFile);
|
|
217
|
+
if (specifier.startsWith('.')) {
|
|
218
|
+
return localRequire.resolve(join(dirname(fromFile), specifier));
|
|
219
|
+
}
|
|
220
|
+
return localRequire.resolve(specifier);
|
|
221
|
+
}
|
|
222
|
+
async function defaultImportModule(specifier) {
|
|
223
|
+
return import(pathToFileURL(specifier).href);
|
|
224
|
+
}
|
|
225
|
+
function satisfiesHostRange(version, range) {
|
|
226
|
+
const normalizedRange = range.trim();
|
|
227
|
+
if (normalizedRange.startsWith('^')) {
|
|
228
|
+
const actual = parseVersion(version);
|
|
229
|
+
const expected = parseVersion(normalizedRange.slice(1));
|
|
230
|
+
if (!actual || !expected)
|
|
231
|
+
return false;
|
|
232
|
+
const [actualMajor, actualMinor, actualPatch] = actual;
|
|
233
|
+
const [expectedMajor, expectedMinor, expectedPatch] = expected;
|
|
234
|
+
if (expectedMajor === 0) {
|
|
235
|
+
return actualMajor === 0 && actualMinor === expectedMinor && actualPatch >= expectedPatch;
|
|
236
|
+
}
|
|
237
|
+
if (actualMajor !== expectedMajor)
|
|
238
|
+
return false;
|
|
239
|
+
if (actualMinor > expectedMinor)
|
|
240
|
+
return true;
|
|
241
|
+
if (actualMinor < expectedMinor)
|
|
242
|
+
return false;
|
|
243
|
+
return actualPatch >= expectedPatch;
|
|
244
|
+
}
|
|
245
|
+
const actual = parseVersion(version);
|
|
246
|
+
const expected = parseVersion(normalizedRange);
|
|
247
|
+
if (!actual || !expected)
|
|
248
|
+
return false;
|
|
249
|
+
return actual[0] === expected[0] && actual[1] === expected[1] && actual[2] === expected[2];
|
|
250
|
+
}
|
|
251
|
+
function parseVersion(value) {
|
|
252
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)/u.exec(value.trim());
|
|
253
|
+
if (!match)
|
|
254
|
+
return null;
|
|
255
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
256
|
+
}
|
|
257
|
+
function tryResolve(callback) {
|
|
258
|
+
try {
|
|
259
|
+
return callback();
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function formatError(error) {
|
|
266
|
+
return error instanceof Error ? error.message : String(error);
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=index.js.map
|