@webpresso/agent-kit 0.26.3 → 0.27.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/dist/esm/audit/no-first-party-mjs.d.ts +5 -0
- package/dist/esm/audit/no-first-party-mjs.js +83 -0
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit.js +1 -0
- package/dist/esm/cli/commands/init/config.d.ts +8 -0
- package/dist/esm/cli/commands/init/config.js +24 -0
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +66 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +13 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.js +34 -0
- package/dist/esm/mcp/tools/_shared/project-root.d.ts +9 -4
- package/dist/esm/mcp/tools/_shared/project-root.js +35 -11
- package/dist/esm/mcp/tools/audit.d.ts +7 -5
- package/dist/esm/mcp/tools/audit.js +23 -20
- package/package.json +6 -6
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Webpresso agent-kit Claude Code plugin: blueprints, skills, hooks, MCP server",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.27.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
]
|
|
24
24
|
}
|
|
25
25
|
],
|
|
26
|
-
"version": "0.
|
|
26
|
+
"version": "0.27.0"
|
|
27
27
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RepoAuditResult } from './repo-guardrails.js';
|
|
2
|
+
export declare function isIgnoredNoFirstPartyMjsPath(relativePath: string): boolean;
|
|
3
|
+
export declare function findTrackedFirstPartyMjsPaths(trackedPaths: readonly string[]): string[];
|
|
4
|
+
export declare function auditNoFirstPartyMjs(rootDirectory?: string): RepoAuditResult;
|
|
5
|
+
//# sourceMappingURL=no-first-party-mjs.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
const IGNORED_PATH_PREFIXES = [
|
|
5
|
+
'node_modules',
|
|
6
|
+
'dist',
|
|
7
|
+
'build',
|
|
8
|
+
'coverage',
|
|
9
|
+
'.wrangler',
|
|
10
|
+
'.codex',
|
|
11
|
+
'.omx',
|
|
12
|
+
'.omc',
|
|
13
|
+
'logs',
|
|
14
|
+
'.test-reports',
|
|
15
|
+
'.webpresso/generated',
|
|
16
|
+
];
|
|
17
|
+
export function isIgnoredNoFirstPartyMjsPath(relativePath) {
|
|
18
|
+
const normalized = relativePath.replace(/\\/gu, '/');
|
|
19
|
+
return IGNORED_PATH_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
|
|
20
|
+
}
|
|
21
|
+
export function findTrackedFirstPartyMjsPaths(trackedPaths) {
|
|
22
|
+
return trackedPaths
|
|
23
|
+
.map((path) => path.replace(/\\/gu, '/'))
|
|
24
|
+
.filter((path) => path.endsWith('.mjs'))
|
|
25
|
+
.filter((path) => !isIgnoredNoFirstPartyMjsPath(path))
|
|
26
|
+
.toSorted();
|
|
27
|
+
}
|
|
28
|
+
function fail(root, message) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
title: 'no first-party .mjs',
|
|
32
|
+
checked: 0,
|
|
33
|
+
violations: [{ file: root, message }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function resolveCanonicalRepoRoot(rootDirectory) {
|
|
37
|
+
const requestedRoot = resolve(rootDirectory);
|
|
38
|
+
let gitRoot;
|
|
39
|
+
try {
|
|
40
|
+
gitRoot = resolve(execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
41
|
+
cwd: requestedRoot,
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
44
|
+
}).trim());
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return fail(requestedRoot, `run no-first-party-mjs from a canonical repo root; ${requestedRoot} is not a git repository root`);
|
|
48
|
+
}
|
|
49
|
+
if (requestedRoot !== gitRoot) {
|
|
50
|
+
return fail(requestedRoot, `run no-first-party-mjs from the canonical repo root ${gitRoot}; refusing to scan ${requestedRoot}`);
|
|
51
|
+
}
|
|
52
|
+
if (!existsSync(join(gitRoot, 'package.json'))) {
|
|
53
|
+
return fail(requestedRoot, `run no-first-party-mjs from a canonical repo root with package.json; ${gitRoot} does not qualify`);
|
|
54
|
+
}
|
|
55
|
+
return { ok: true, root: gitRoot };
|
|
56
|
+
}
|
|
57
|
+
function listTrackedFiles(root) {
|
|
58
|
+
const output = execFileSync('git', ['ls-files'], {
|
|
59
|
+
cwd: root,
|
|
60
|
+
encoding: 'utf8',
|
|
61
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
62
|
+
});
|
|
63
|
+
return output
|
|
64
|
+
.split('\n')
|
|
65
|
+
.map((line) => line.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
export function auditNoFirstPartyMjs(rootDirectory = process.cwd()) {
|
|
69
|
+
const canonical = resolveCanonicalRepoRoot(rootDirectory);
|
|
70
|
+
if ('violations' in canonical)
|
|
71
|
+
return canonical;
|
|
72
|
+
const violations = findTrackedFirstPartyMjsPaths(listTrackedFiles(canonical.root)).map((file) => ({
|
|
73
|
+
file,
|
|
74
|
+
message: 'tracked first-party .mjs files are forbidden; rename this file to .ts',
|
|
75
|
+
}));
|
|
76
|
+
return {
|
|
77
|
+
ok: violations.length === 0,
|
|
78
|
+
title: 'no first-party .mjs',
|
|
79
|
+
checked: violations.length,
|
|
80
|
+
violations,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=no-first-party-mjs.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RepoAuditResult } from '#audit/repo-guardrails';
|
|
2
|
-
export type AuditKind = 'tph' | 'tph-e2e' | 'bundle-budget' | 'commit-message' | 'blueprint-lifecycle' | 'roadmap-links' | 'docs-frontmatter' | 'catalog-drift' | 'package-surface' | 'agents' | 'tech-debt' | 'no-relative-parent-imports' | 'no-link-protocol' | 'vision' | 'bucket-boundary' | 'skill-sizes' | 'broken-refs' | 'memory-rotation' | 'gitignore-agent-surfaces' | 'memory-unified' | 'compile-drift' | 'architecture-drift' | 'cloudflare-deploy-contract' | 'toolchain-isolation' | 'absolute-path-policy' | 'agent-cost' | 'blueprint-db-consistency' | 'blueprint-lifecycle-sql' | 'tech-debt-cadence' | 'cross-repo-correlation' | 'ai-contracts' | 'mutation' | 'quality' | 'guardrails' | 'hook-surface' | 'no-relative-package-scripts';
|
|
2
|
+
export type AuditKind = 'tph' | 'tph-e2e' | 'bundle-budget' | 'commit-message' | 'blueprint-lifecycle' | 'roadmap-links' | 'docs-frontmatter' | 'catalog-drift' | 'package-surface' | 'agents' | 'tech-debt' | 'no-relative-parent-imports' | 'no-link-protocol' | 'vision' | 'bucket-boundary' | 'skill-sizes' | 'broken-refs' | 'memory-rotation' | 'gitignore-agent-surfaces' | 'memory-unified' | 'compile-drift' | 'architecture-drift' | 'cloudflare-deploy-contract' | 'toolchain-isolation' | 'absolute-path-policy' | 'no-first-party-mjs' | 'agent-cost' | 'blueprint-db-consistency' | 'blueprint-lifecycle-sql' | 'tech-debt-cadence' | 'cross-repo-correlation' | 'ai-contracts' | 'mutation' | 'quality' | 'guardrails' | 'hook-surface' | 'no-relative-package-scripts';
|
|
3
3
|
export type AuditOutcome = {
|
|
4
4
|
kind: 'invalid-usage';
|
|
5
5
|
message: string;
|
|
@@ -62,6 +62,7 @@ const REPO_AUDIT_REGISTRY = {
|
|
|
62
62
|
'cloudflare-deploy-contract': async (root) => (await import('#audit/cloudflare-deploy-contract')).auditCloudflareDeployContract(root),
|
|
63
63
|
'toolchain-isolation': async (root) => (await import('#audit/toolchain-isolation')).auditToolchainIsolation(root),
|
|
64
64
|
'absolute-path-policy': async (root) => (await import('#audit/absolute-path-policy')).auditAbsolutePathPolicy(root),
|
|
65
|
+
'no-first-party-mjs': async (root) => (await import('#audit/no-first-party-mjs')).auditNoFirstPartyMjs(root),
|
|
65
66
|
'agent-cost': async (root) => (await import('#audit/agent-cost')).auditAgentCost(root),
|
|
66
67
|
'blueprint-db-consistency': async (root) => (await import('#audit/blueprint-db-consistency')).auditBlueprintDbConsistency(root),
|
|
67
68
|
'blueprint-lifecycle-sql': async (root) => (await import('#audit/blueprint-lifecycle-sql')).auditBlueprintLifecycleSql(root),
|
|
@@ -16,6 +16,14 @@ export interface AgentkitConfig {
|
|
|
16
16
|
serverName?: string;
|
|
17
17
|
toolPrefix?: string;
|
|
18
18
|
};
|
|
19
|
+
/** Pretool-guard routing policy. `mechanism` lives in agent-kit; this is the
|
|
20
|
+
* per-repo `data`. `scriptRoutes` maps a package-script name (e.g.
|
|
21
|
+
* `docs:check`) to a `wp_audit` kind; `packageManager: 'vp-only'` opts into
|
|
22
|
+
* routing all raw `pnpm`/`npm` invocations to the `vp` facade. */
|
|
23
|
+
guard?: {
|
|
24
|
+
packageManager?: 'vp-only';
|
|
25
|
+
scriptRoutes?: Record<string, string>;
|
|
26
|
+
};
|
|
19
27
|
rules: {
|
|
20
28
|
overrides: string[];
|
|
21
29
|
};
|
|
@@ -48,6 +48,18 @@ export function readConfig(repoRoot) {
|
|
|
48
48
|
const normalizedMcp = serverName || toolPrefix
|
|
49
49
|
? { ...(serverName ? { serverName } : {}), ...(toolPrefix ? { toolPrefix } : {}) }
|
|
50
50
|
: undefined;
|
|
51
|
+
const guard = parsed.guard;
|
|
52
|
+
const packageManager = guard?.packageManager === 'vp-only' ? 'vp-only' : undefined;
|
|
53
|
+
const rawScriptRoutes = guard?.scriptRoutes && typeof guard.scriptRoutes === 'object'
|
|
54
|
+
? Object.fromEntries(Object.entries(guard.scriptRoutes).filter(([key, value]) => typeof key === 'string' && typeof value === 'string'))
|
|
55
|
+
: undefined;
|
|
56
|
+
const scriptRoutes = rawScriptRoutes && Object.keys(rawScriptRoutes).length > 0 ? rawScriptRoutes : undefined;
|
|
57
|
+
const normalizedGuard = packageManager || scriptRoutes
|
|
58
|
+
? {
|
|
59
|
+
...(packageManager ? { packageManager } : {}),
|
|
60
|
+
...(scriptRoutes ? { scriptRoutes } : {}),
|
|
61
|
+
}
|
|
62
|
+
: undefined;
|
|
51
63
|
const selectedHosts = Array.isArray(hosts?.selected)
|
|
52
64
|
? hosts.selected.filter((s) => ['codex', 'claude', 'opencode'].includes(String(s)))
|
|
53
65
|
: [];
|
|
@@ -66,6 +78,7 @@ export function readConfig(repoRoot) {
|
|
|
66
78
|
...(visibility ? { visibility } : {}),
|
|
67
79
|
},
|
|
68
80
|
...(normalizedMcp ? { mcp: normalizedMcp } : {}),
|
|
81
|
+
...(normalizedGuard ? { guard: normalizedGuard } : {}),
|
|
69
82
|
rules: { overrides: overrides.filter((s) => typeof s === 'string') },
|
|
70
83
|
scripts: {
|
|
71
84
|
'setup-agent': readOptionalString(scripts?.['setup-agent']),
|
|
@@ -93,11 +106,22 @@ export function mergeConfig(existing, incoming) {
|
|
|
93
106
|
...incoming.mcp,
|
|
94
107
|
}
|
|
95
108
|
: undefined;
|
|
109
|
+
const mergedScriptRoutes = existing.guard?.scriptRoutes || incoming.guard?.scriptRoutes
|
|
110
|
+
? { ...existing.guard?.scriptRoutes, ...incoming.guard?.scriptRoutes }
|
|
111
|
+
: undefined;
|
|
112
|
+
const mergedGuard = existing.guard || incoming.guard
|
|
113
|
+
? {
|
|
114
|
+
...existing.guard,
|
|
115
|
+
...incoming.guard,
|
|
116
|
+
...(mergedScriptRoutes ? { scriptRoutes: mergedScriptRoutes } : {}),
|
|
117
|
+
}
|
|
118
|
+
: undefined;
|
|
96
119
|
return {
|
|
97
120
|
version: incoming.version,
|
|
98
121
|
installed: { tier3Skills: tier3 },
|
|
99
122
|
hosts: incoming.hosts ?? existing.hosts,
|
|
100
123
|
...(mergedMcp ? { mcp: mergedMcp } : {}),
|
|
124
|
+
...(mergedGuard ? { guard: mergedGuard } : {}),
|
|
101
125
|
rules: { overrides },
|
|
102
126
|
scripts: {
|
|
103
127
|
'setup-agent': incoming.scripts['setup-agent'] ?? existing.scripts['setup-agent'],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readConfig } from '#cli/commands/init/config';
|
|
2
2
|
import { getCommand, isBashInput } from '#hooks/shared/types';
|
|
3
|
+
import { AUDIT_KINDS } from '#mcp/tools/_shared/audit-kinds';
|
|
3
4
|
import { createSkipResult } from './skip-result.js';
|
|
4
5
|
import { buildRedirectMessage } from './mcp-redirect.js';
|
|
5
6
|
export const VALIDATOR_NAME = 'forbidden-commands';
|
|
@@ -445,6 +446,57 @@ export function createAuditResult(command, rule, options = {}) {
|
|
|
445
446
|
matchedPattern: rule.pattern.source,
|
|
446
447
|
};
|
|
447
448
|
}
|
|
449
|
+
const AUDIT_KIND_SET = new Set(AUDIT_KINDS);
|
|
450
|
+
const WP_AUDIT_RE = /^wp\s+audit\s+([a-z0-9-]+)\b/u;
|
|
451
|
+
const SCRIPT_INVOCATION_RE = /^(?:pnpm run|vp run|npm run|pnpm|npm)\s+([A-Za-z0-9:_-]+)/u;
|
|
452
|
+
const RAW_PM_RE = /^(?:pnpm|npm)\b/u;
|
|
453
|
+
function loadGuardConfig() {
|
|
454
|
+
const repoRoot = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
455
|
+
return readConfig(repoRoot)?.guard;
|
|
456
|
+
}
|
|
457
|
+
/** Build a guard redirect result; non-blocking ("[AUDIT] Would block") in audit mode. */
|
|
458
|
+
function guardRedirect(message) {
|
|
459
|
+
if (process.env[AUDIT_MODE_ENV] === '1') {
|
|
460
|
+
return { validator: VALIDATOR_NAME, passed: true, message: `[AUDIT] Would block:\n${message}` };
|
|
461
|
+
}
|
|
462
|
+
return { validator: VALIDATOR_NAME, passed: false, message };
|
|
463
|
+
}
|
|
464
|
+
/** `wp audit <kind>` (CLI) → `wp_audit(kind=...)` (MCP). Generic; not gated on config. */
|
|
465
|
+
function findWpAuditRedirect(command) {
|
|
466
|
+
for (const variant of getCommandVariants(command)) {
|
|
467
|
+
const kind = WP_AUDIT_RE.exec(variant)?.[1];
|
|
468
|
+
if (kind && AUDIT_KIND_SET.has(kind)) {
|
|
469
|
+
return `"${variant}" denied — use the MCP audit tool: mcp__webpresso__wp_audit(kind="${kind}"). Returns structured, summary-first results.`;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
/** Repo-declared `guard.scriptRoutes`: a package script mapped to an audit kind. */
|
|
475
|
+
function findScriptRouteRedirect(command, routes) {
|
|
476
|
+
for (const variant of getCommandVariants(command)) {
|
|
477
|
+
const script = SCRIPT_INVOCATION_RE.exec(variant)?.[1];
|
|
478
|
+
if (!script)
|
|
479
|
+
continue;
|
|
480
|
+
const kind = routes[script];
|
|
481
|
+
if (!kind)
|
|
482
|
+
continue;
|
|
483
|
+
if (!AUDIT_KIND_SET.has(kind)) {
|
|
484
|
+
process.stderr.write(`[forbidden-commands] guard.scriptRoutes["${script}"] -> "${kind}" is not a known audit kind; ignoring\n`);
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
return `"${variant}" denied — this repo routes \`${script}\` to an audit: mcp__webpresso__wp_audit(kind="${kind}").`;
|
|
488
|
+
}
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
/** `guard.packageManager: 'vp-only'`: route any remaining raw pnpm/npm to the vp facade. */
|
|
492
|
+
function findVpOnlyRedirect(command) {
|
|
493
|
+
for (const variant of getCommandVariants(command)) {
|
|
494
|
+
if (RAW_PM_RE.test(variant)) {
|
|
495
|
+
return `"${variant}" denied — this repo is vp-only. Use the vp facade (\`vp install\`, \`vp run <script>\`, \`vp exec <bin>\`) or the matching wp_* MCP tool.`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
448
500
|
export function validateForbiddenCommands(input) {
|
|
449
501
|
if (process.env[SKIP_ENV_VAR] === '1')
|
|
450
502
|
return createSkipResult(VALIDATOR_NAME);
|
|
@@ -459,6 +511,20 @@ export function validateForbiddenCommands(input) {
|
|
|
459
511
|
return createAuditResult(command, rule);
|
|
460
512
|
return createBlockedResult(command, rule);
|
|
461
513
|
}
|
|
514
|
+
const wpAuditRedirect = findWpAuditRedirect(command);
|
|
515
|
+
if (wpAuditRedirect)
|
|
516
|
+
return guardRedirect(wpAuditRedirect);
|
|
517
|
+
const guard = loadGuardConfig();
|
|
518
|
+
if (guard?.scriptRoutes) {
|
|
519
|
+
const redirect = findScriptRouteRedirect(command, guard.scriptRoutes);
|
|
520
|
+
if (redirect)
|
|
521
|
+
return guardRedirect(redirect);
|
|
522
|
+
}
|
|
523
|
+
if (guard?.packageManager === 'vp-only') {
|
|
524
|
+
const redirect = findVpOnlyRedirect(command);
|
|
525
|
+
if (redirect)
|
|
526
|
+
return guardRedirect(redirect);
|
|
527
|
+
}
|
|
462
528
|
return { validator: VALIDATOR_NAME, passed: true };
|
|
463
529
|
}
|
|
464
530
|
//# sourceMappingURL=forbidden-commands.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical list of `wp_audit` kinds — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Imported by the `wp_audit` MCP tool (for its `kind` enum + dispatch) and by
|
|
5
|
+
* the pretool-guard (to validate repo `guard.scriptRoutes` targets and to
|
|
6
|
+
* redirect `wp audit <kind>` CLI calls to the MCP tool). Kept as a tiny
|
|
7
|
+
* dependency-free module so the hook runtime, which runs on every tool call,
|
|
8
|
+
* doesn't pull in the whole audit tool graph.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AUDIT_KINDS: readonly ["tph", "tph-e2e", "agents", "catalog-drift", "package-surface", "docs-frontmatter", "blueprint-lifecycle", "architecture-drift", "cloudflare-deploy-contract", "absolute-path-policy", "no-first-party-mjs", "roadmap-links", "bundle-budget", "commit-message", "tech-debt", "hook-surface", "ai-contracts", "no-relative-package-scripts", "toolchain-isolation"];
|
|
11
|
+
export type AuditKind = (typeof AUDIT_KINDS)[number];
|
|
12
|
+
export declare function isAuditKind(value: string): value is AuditKind;
|
|
13
|
+
//# sourceMappingURL=audit-kinds.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical list of `wp_audit` kinds — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Imported by the `wp_audit` MCP tool (for its `kind` enum + dispatch) and by
|
|
5
|
+
* the pretool-guard (to validate repo `guard.scriptRoutes` targets and to
|
|
6
|
+
* redirect `wp audit <kind>` CLI calls to the MCP tool). Kept as a tiny
|
|
7
|
+
* dependency-free module so the hook runtime, which runs on every tool call,
|
|
8
|
+
* doesn't pull in the whole audit tool graph.
|
|
9
|
+
*/
|
|
10
|
+
export const AUDIT_KINDS = [
|
|
11
|
+
'tph',
|
|
12
|
+
'tph-e2e',
|
|
13
|
+
'agents',
|
|
14
|
+
'catalog-drift',
|
|
15
|
+
'package-surface',
|
|
16
|
+
'docs-frontmatter',
|
|
17
|
+
'blueprint-lifecycle',
|
|
18
|
+
'architecture-drift',
|
|
19
|
+
'cloudflare-deploy-contract',
|
|
20
|
+
'absolute-path-policy',
|
|
21
|
+
'no-first-party-mjs',
|
|
22
|
+
'roadmap-links',
|
|
23
|
+
'bundle-budget',
|
|
24
|
+
'commit-message',
|
|
25
|
+
'tech-debt',
|
|
26
|
+
'hook-surface',
|
|
27
|
+
'ai-contracts',
|
|
28
|
+
'no-relative-package-scripts',
|
|
29
|
+
'toolchain-isolation',
|
|
30
|
+
];
|
|
31
|
+
export function isAuditKind(value) {
|
|
32
|
+
return AUDIT_KINDS.includes(value);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=audit-kinds.js.map
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
* `oxlint` spawned with the inherited cwd would lint the wrong tree.
|
|
9
9
|
*
|
|
10
10
|
* Resolution order, first hit wins:
|
|
11
|
-
* 1. `
|
|
12
|
-
* 2.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* 1. `explicitCwd` — caller says "use exactly this", no walk.
|
|
12
|
+
* 2. An explicitly-passed `cwd` walked up to a marker (`.git`,
|
|
13
|
+
* `pnpm-workspace.yaml`, then `package.json`). A deliberate caller cwd
|
|
14
|
+
* outranks `CLAUDE_PROJECT_DIR`, which for a plugin-scope MCP server is
|
|
15
|
+
* the whole session/workspace root. If the passed cwd has no marker, fall
|
|
16
|
+
* back to `CLAUDE_PROJECT_DIR`, then throw — do not widen to `process.cwd()`.
|
|
17
|
+
* 3. `CLAUDE_PROJECT_DIR` env var (when no explicit cwd was passed).
|
|
18
|
+
* 4. Walk up from `process.cwd()` looking for a marker.
|
|
19
|
+
* 5. Loud throw — diagnosing a wrong-tree lint silently is worse than
|
|
15
20
|
* forcing the caller to pass an explicit cwd.
|
|
16
21
|
*
|
|
17
22
|
* The walk searches `.git` and `pnpm-workspace.yaml` *before* `package.json`
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
* `oxlint` spawned with the inherited cwd would lint the wrong tree.
|
|
9
9
|
*
|
|
10
10
|
* Resolution order, first hit wins:
|
|
11
|
-
* 1. `
|
|
12
|
-
* 2.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* 1. `explicitCwd` — caller says "use exactly this", no walk.
|
|
12
|
+
* 2. An explicitly-passed `cwd` walked up to a marker (`.git`,
|
|
13
|
+
* `pnpm-workspace.yaml`, then `package.json`). A deliberate caller cwd
|
|
14
|
+
* outranks `CLAUDE_PROJECT_DIR`, which for a plugin-scope MCP server is
|
|
15
|
+
* the whole session/workspace root. If the passed cwd has no marker, fall
|
|
16
|
+
* back to `CLAUDE_PROJECT_DIR`, then throw — do not widen to `process.cwd()`.
|
|
17
|
+
* 3. `CLAUDE_PROJECT_DIR` env var (when no explicit cwd was passed).
|
|
18
|
+
* 4. Walk up from `process.cwd()` looking for a marker.
|
|
19
|
+
* 5. Loud throw — diagnosing a wrong-tree lint silently is worse than
|
|
15
20
|
* forcing the caller to pass an explicit cwd.
|
|
16
21
|
*
|
|
17
22
|
* The walk searches `.git` and `pnpm-workspace.yaml` *before* `package.json`
|
|
@@ -42,20 +47,39 @@ function walkUp(start, markers) {
|
|
|
42
47
|
}
|
|
43
48
|
return null;
|
|
44
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Walk up from `start` to the nearest project root. Strong markers
|
|
52
|
+
* (`.git`, `pnpm-workspace.yaml`) anchor at the workspace root in preference to
|
|
53
|
+
* a closer weak marker (`package.json`) in a nested package dir.
|
|
54
|
+
*/
|
|
55
|
+
function walkToProjectRoot(start) {
|
|
56
|
+
return walkUp(start, STRONG_MARKERS) ?? walkUp(start, WEWP_MARKERS);
|
|
57
|
+
}
|
|
45
58
|
export function resolveProjectRoot(options = {}) {
|
|
46
59
|
if (options.explicitCwd)
|
|
47
60
|
return options.explicitCwd;
|
|
48
61
|
const env = options.env ?? process.env;
|
|
49
62
|
const fromEnv = env.CLAUDE_PROJECT_DIR;
|
|
63
|
+
// A caller-supplied cwd is a deliberate "scope to this project" signal and
|
|
64
|
+
// must outrank the ambient CLAUDE_PROJECT_DIR — for a plugin-scope MCP server
|
|
65
|
+
// that env var is the whole session/workspace root, so without this an
|
|
66
|
+
// explicit `wp_lint`/`wp_test` cwd would scan every sibling repo. Anchor at
|
|
67
|
+
// the cwd's project root; if it has no marker, defer to CLAUDE_PROJECT_DIR,
|
|
68
|
+
// then throw — never silently widen the search to process.cwd().
|
|
69
|
+
if (options.cwd) {
|
|
70
|
+
const fromCwd = walkToProjectRoot(options.cwd);
|
|
71
|
+
if (fromCwd)
|
|
72
|
+
return fromCwd;
|
|
73
|
+
if (fromEnv && fromEnv.length > 0)
|
|
74
|
+
return fromEnv;
|
|
75
|
+
throw new ProjectRootNotFoundError(options.cwd);
|
|
76
|
+
}
|
|
50
77
|
if (fromEnv && fromEnv.length > 0)
|
|
51
78
|
return fromEnv;
|
|
52
|
-
const start =
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
return
|
|
56
|
-
const fromWeak = walkUp(start, WEWP_MARKERS);
|
|
57
|
-
if (fromWeak)
|
|
58
|
-
return fromWeak;
|
|
79
|
+
const start = process.cwd();
|
|
80
|
+
const fromCwd = walkToProjectRoot(start);
|
|
81
|
+
if (fromCwd)
|
|
82
|
+
return fromCwd;
|
|
59
83
|
throw new ProjectRootNotFoundError(start);
|
|
60
84
|
}
|
|
61
85
|
//# sourceMappingURL=project-root.js.map
|
|
@@ -24,17 +24,19 @@ declare const inputSchema: z.ZodObject<{
|
|
|
24
24
|
"tech-debt": "tech-debt";
|
|
25
25
|
tph: "tph";
|
|
26
26
|
"tph-e2e": "tph-e2e";
|
|
27
|
-
"bundle-budget": "bundle-budget";
|
|
28
|
-
"commit-message": "commit-message";
|
|
29
|
-
"blueprint-lifecycle": "blueprint-lifecycle";
|
|
30
|
-
"roadmap-links": "roadmap-links";
|
|
31
|
-
"docs-frontmatter": "docs-frontmatter";
|
|
32
27
|
"catalog-drift": "catalog-drift";
|
|
33
28
|
"package-surface": "package-surface";
|
|
29
|
+
"docs-frontmatter": "docs-frontmatter";
|
|
30
|
+
"blueprint-lifecycle": "blueprint-lifecycle";
|
|
34
31
|
"architecture-drift": "architecture-drift";
|
|
35
32
|
"cloudflare-deploy-contract": "cloudflare-deploy-contract";
|
|
36
33
|
"absolute-path-policy": "absolute-path-policy";
|
|
34
|
+
"no-first-party-mjs": "no-first-party-mjs";
|
|
35
|
+
"roadmap-links": "roadmap-links";
|
|
36
|
+
"bundle-budget": "bundle-budget";
|
|
37
|
+
"commit-message": "commit-message";
|
|
37
38
|
"ai-contracts": "ai-contracts";
|
|
39
|
+
"toolchain-isolation": "toolchain-isolation";
|
|
38
40
|
}>;
|
|
39
41
|
cwd: z.ZodOptional<z.ZodString>;
|
|
40
42
|
directory: z.ZodOptional<z.ZodString>;
|
|
@@ -18,26 +18,9 @@ import { spawn } from 'node:child_process';
|
|
|
18
18
|
import { z } from 'zod';
|
|
19
19
|
import { resolveAuditScriptPath } from '#audit/resolve-audit-script';
|
|
20
20
|
import { applyOutputTransform } from '#output-transforms/index';
|
|
21
|
+
import { AUDIT_KINDS } from './_shared/audit-kinds.js';
|
|
21
22
|
import { createSummaryOutputSchema, createSummaryResult } from './_shared/result.js';
|
|
22
|
-
const KINDS =
|
|
23
|
-
'tph',
|
|
24
|
-
'tph-e2e',
|
|
25
|
-
'agents',
|
|
26
|
-
'catalog-drift',
|
|
27
|
-
'package-surface',
|
|
28
|
-
'docs-frontmatter',
|
|
29
|
-
'blueprint-lifecycle',
|
|
30
|
-
'architecture-drift',
|
|
31
|
-
'cloudflare-deploy-contract',
|
|
32
|
-
'absolute-path-policy',
|
|
33
|
-
'roadmap-links',
|
|
34
|
-
'bundle-budget',
|
|
35
|
-
'commit-message',
|
|
36
|
-
'tech-debt',
|
|
37
|
-
'hook-surface',
|
|
38
|
-
'ai-contracts',
|
|
39
|
-
'no-relative-package-scripts',
|
|
40
|
-
];
|
|
23
|
+
const KINDS = AUDIT_KINDS;
|
|
41
24
|
const inputSchema = z.object({
|
|
42
25
|
kind: z.enum(KINDS),
|
|
43
26
|
/** Working tree to run the audit against. Alias kept as `directory` for back-compat. */
|
|
@@ -176,6 +159,26 @@ async function dispatch(input) {
|
|
|
176
159
|
details: auditResult,
|
|
177
160
|
};
|
|
178
161
|
}
|
|
162
|
+
case 'no-first-party-mjs': {
|
|
163
|
+
const { auditNoFirstPartyMjs } = await import('#audit/no-first-party-mjs');
|
|
164
|
+
const auditResult = auditNoFirstPartyMjs(input.cwd ?? input.directory ?? process.cwd());
|
|
165
|
+
return {
|
|
166
|
+
passed: auditResult.ok,
|
|
167
|
+
summary: summarizeRepoAudit(kind, auditResult),
|
|
168
|
+
kind,
|
|
169
|
+
details: auditResult,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
case 'toolchain-isolation': {
|
|
173
|
+
const { auditToolchainIsolation } = await import('#audit/toolchain-isolation');
|
|
174
|
+
const auditResult = auditToolchainIsolation(input.cwd ?? input.directory ?? process.cwd());
|
|
175
|
+
return {
|
|
176
|
+
passed: auditResult.ok,
|
|
177
|
+
summary: summarizeRepoAudit(kind, auditResult),
|
|
178
|
+
kind,
|
|
179
|
+
details: auditResult,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
179
182
|
case 'roadmap-links': {
|
|
180
183
|
const { auditRoadmapLinks } = await import('#audit/roadmap-links');
|
|
181
184
|
const auditResult = auditRoadmapLinks(input.cwd ?? input.directory ?? process.cwd());
|
|
@@ -304,7 +307,7 @@ async function dispatch(input) {
|
|
|
304
307
|
}
|
|
305
308
|
const tool = {
|
|
306
309
|
name: 'wp_audit',
|
|
307
|
-
description: 'Run a packaged repo audit. `kind` selects the audit (tph, tph-e2e, catalog-drift, docs-frontmatter, blueprint-lifecycle, architecture-drift, absolute-path-policy, roadmap-links, bundle-budget, commit-message, tech-debt, hook-surface, package-surface, no-relative-package-scripts). Returns {passed, kind, details}.',
|
|
310
|
+
description: 'Run a packaged repo audit. `kind` selects the audit (tph, tph-e2e, catalog-drift, docs-frontmatter, blueprint-lifecycle, architecture-drift, absolute-path-policy, no-first-party-mjs, roadmap-links, bundle-budget, commit-message, tech-debt, hook-surface, package-surface, no-relative-package-scripts). Returns {passed, kind, details}.',
|
|
308
311
|
inputSchema,
|
|
309
312
|
outputSchema,
|
|
310
313
|
annotations: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpresso/agent-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -710,10 +710,10 @@
|
|
|
710
710
|
"@stryker-mutator/typescript-checker": "^9.6.1",
|
|
711
711
|
"@playwright/test": "^1.55.0",
|
|
712
712
|
"wrangler": "^4.50.0",
|
|
713
|
-
"@webpresso/agent-kit-runtime-darwin-arm64": "0.
|
|
714
|
-
"@webpresso/agent-kit-runtime-darwin-x64": "0.
|
|
715
|
-
"@webpresso/agent-kit-runtime-linux-x64": "0.
|
|
716
|
-
"@webpresso/agent-kit-runtime-linux-arm64": "0.
|
|
717
|
-
"@webpresso/agent-kit-runtime-windows-x64": "0.
|
|
713
|
+
"@webpresso/agent-kit-runtime-darwin-arm64": "0.27.0",
|
|
714
|
+
"@webpresso/agent-kit-runtime-darwin-x64": "0.27.0",
|
|
715
|
+
"@webpresso/agent-kit-runtime-linux-x64": "0.27.0",
|
|
716
|
+
"@webpresso/agent-kit-runtime-linux-arm64": "0.27.0",
|
|
717
|
+
"@webpresso/agent-kit-runtime-windows-x64": "0.27.0"
|
|
718
718
|
}
|
|
719
719
|
}
|