@webpresso/agent-kit 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/esm/audit/toolchain-isolation.d.ts +3 -0
  4. package/dist/esm/audit/toolchain-isolation.js +134 -0
  5. package/dist/esm/cli/cli.d.ts +1 -1
  6. package/dist/esm/cli/cli.js +6 -0
  7. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  8. package/dist/esm/cli/commands/audit.js +1 -0
  9. package/dist/esm/cli/commands/deploy.d.ts +4 -0
  10. package/dist/esm/cli/commands/deploy.js +35 -0
  11. package/dist/esm/cli/commands/init/gitignore-patcher.d.ts +17 -0
  12. package/dist/esm/cli/commands/init/gitignore-patcher.js +42 -0
  13. package/dist/esm/cli/commands/init/index.js +11 -1
  14. package/dist/esm/cli/commands/init/scaffold-base-kit.js +44 -17
  15. package/dist/esm/deploy/index.d.ts +5 -0
  16. package/dist/esm/deploy/index.js +5 -0
  17. package/dist/esm/deploy/load-adapter.d.ts +6 -0
  18. package/dist/esm/deploy/load-adapter.js +57 -0
  19. package/dist/esm/deploy/run.d.ts +12 -0
  20. package/dist/esm/deploy/run.js +52 -0
  21. package/dist/esm/deploy/schema.d.ts +6 -0
  22. package/dist/esm/deploy/schema.js +61 -0
  23. package/dist/esm/deploy/types.d.ts +43 -0
  24. package/dist/esm/deploy/types.js +2 -0
  25. package/dist/esm/e2e/command-builder.js +37 -18
  26. package/dist/esm/e2e/config.d.ts +2 -0
  27. package/dist/esm/e2e/config.js +6 -1
  28. package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +6 -0
  29. package/dist/esm/hooks/pretool-guard/dev-routing.js +3 -2
  30. package/dist/esm/hooks/pretool-guard/runner.js +11 -6
  31. package/dist/esm/mcp/tools/_shared/runner-failure.d.ts +30 -0
  32. package/dist/esm/mcp/tools/_shared/runner-failure.js +45 -0
  33. package/dist/esm/mcp/tools/typecheck.js +20 -3
  34. package/dist/esm/package.json +3 -1
  35. package/dist/esm/test/command-builder.d.ts +1 -0
  36. package/dist/esm/test/command-builder.js +9 -5
  37. package/dist/esm/tool-runtime/resolve-runner.js +38 -10
  38. package/package.json +32 -18
@@ -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.25.0"
9
+ "version": "0.26.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -23,5 +23,5 @@
23
23
  ]
24
24
  }
25
25
  ],
26
- "version": "0.25.0"
26
+ "version": "0.26.0"
27
27
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webpresso",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Webpresso agent-kit: blueprints, skills, lore commit protocol, tech-debt lifecycle",
5
5
  "skills": "./skills",
6
6
  "commands": "./commands",
@@ -0,0 +1,3 @@
1
+ import type { RepoAuditResult } from './repo-guardrails.js';
2
+ export declare function auditToolchainIsolation(root: string): RepoAuditResult;
3
+ //# sourceMappingURL=toolchain-isolation.d.ts.map
@@ -0,0 +1,134 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ const FORBIDDEN_DEPENDENCY_PATTERNS = [
4
+ /^typescript$/u,
5
+ /^vite$/u,
6
+ /^vitest$/u,
7
+ /^@stryker-mutator\//u,
8
+ /^@playwright\/test$/u,
9
+ /^wrangler$/u,
10
+ /^oxlint$/u,
11
+ /^oxfmt$/u,
12
+ /^tsx$/u,
13
+ ];
14
+ const FORBIDDEN_SCRIPT_PATTERNS = [
15
+ /(^|\s)(tsc|vite|vitest|stryker|playwright|wrangler|oxlint|oxfmt|tsx)(\s|$)/u,
16
+ /node\s+\.\/node_modules\/(typescript|vite|vitest|wrangler|oxlint|tsx)\//u,
17
+ ];
18
+ const ALLOWED_SCRIPT_PREFIXES = ['wp ', 'vp run ', 'vp run -r '];
19
+ export function auditToolchainIsolation(root) {
20
+ const packagePaths = findPackageJsonFiles(root);
21
+ const violations = [];
22
+ for (const packagePath of packagePaths) {
23
+ const pkg = readPackageJson(packagePath);
24
+ if (!pkg) {
25
+ violations.push({ file: packagePath, message: 'package.json must be valid JSON' });
26
+ continue;
27
+ }
28
+ if (isExemptPackage(root, packagePath, pkg))
29
+ continue;
30
+ for (const field of [
31
+ 'dependencies',
32
+ 'devDependencies',
33
+ 'optionalDependencies',
34
+ 'peerDependencies',
35
+ ]) {
36
+ for (const depName of Object.keys(pkg[field] ?? {})) {
37
+ if (!isForbiddenDependency(depName))
38
+ continue;
39
+ violations.push({
40
+ file: packagePath,
41
+ message: `${field}.${depName} is toolchain-owned; route it through @webpresso/agent-kit/wp instead of declaring it directly`,
42
+ });
43
+ }
44
+ }
45
+ for (const [scriptName, scriptValue] of Object.entries(pkg.scripts ?? {})) {
46
+ if (typeof scriptValue !== 'string')
47
+ continue;
48
+ if (isAllowedScript(scriptValue))
49
+ continue;
50
+ if (!FORBIDDEN_SCRIPT_PATTERNS.some((pattern) => pattern.test(scriptValue)))
51
+ continue;
52
+ violations.push({
53
+ file: packagePath,
54
+ message: `script "${scriptName}" invokes a toolchain binary directly; use wp-managed commands instead`,
55
+ });
56
+ }
57
+ }
58
+ return {
59
+ ok: violations.length === 0,
60
+ title: 'Toolchain isolation',
61
+ checked: packagePaths.length,
62
+ violations,
63
+ };
64
+ }
65
+ function isExemptPackage(root, packagePath, pkg) {
66
+ if (pkg.name === '@webpresso/agent-kit')
67
+ return true;
68
+ const relativePath = path.relative(root, packagePath).split(path.sep).join('/');
69
+ // Catalog skill templates are sample project manifests shipped by agent-kit,
70
+ // not live consumer package manifests. They intentionally demonstrate raw
71
+ // framework CLIs in generated starter content and should not make agent-kit's
72
+ // own audit fail.
73
+ const unpackedPath = relativePath.replace(/^\.webpresso-packed-surface\//u, '');
74
+ if (unpackedPath.startsWith('catalog/') && unpackedPath.includes('/templates/'))
75
+ return true;
76
+ return false;
77
+ }
78
+ function findPackageJsonFiles(root) {
79
+ const files = [];
80
+ walk(root, files);
81
+ return files.sort();
82
+ }
83
+ function walk(dir, files) {
84
+ for (const entry of safeReadDir(dir)) {
85
+ const absolute = path.join(dir, entry.name);
86
+ if (entry.isDirectory()) {
87
+ if (!shouldSkipDirectory(entry.name))
88
+ walk(absolute, files);
89
+ continue;
90
+ }
91
+ if (entry.isFile() && entry.name === 'package.json')
92
+ files.push(absolute);
93
+ }
94
+ }
95
+ function safeReadDir(dir) {
96
+ try {
97
+ return existsSync(dir) ? readdirSync(dir, { withFileTypes: true }) : [];
98
+ }
99
+ catch {
100
+ return [];
101
+ }
102
+ }
103
+ function shouldSkipDirectory(name) {
104
+ return [
105
+ '.git',
106
+ 'node_modules',
107
+ 'dist',
108
+ 'build',
109
+ '.turbo',
110
+ '.next',
111
+ '.wrangler',
112
+ '.agent',
113
+ '.agents',
114
+ '.omx',
115
+ '.omc',
116
+ '.codex',
117
+ ].includes(name);
118
+ }
119
+ function readPackageJson(file) {
120
+ try {
121
+ return JSON.parse(readFileSync(file, 'utf8'));
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ function isForbiddenDependency(depName) {
128
+ return FORBIDDEN_DEPENDENCY_PATTERNS.some((pattern) => pattern.test(depName));
129
+ }
130
+ function isAllowedScript(script) {
131
+ const trimmed = script.trim();
132
+ return ALLOWED_SCRIPT_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
133
+ }
134
+ //# sourceMappingURL=toolchain-isolation.js.map
@@ -5,7 +5,7 @@
5
5
  * Lazy-loads subcommand modules based on the first argv to keep startup
6
6
  * cheap. Modeled on apps/cli-wp/src/cli.ts.
7
7
  */
8
- declare const SUPPORTED_COMMANDS: readonly ["blueprint", "config", "roadmap", "sync", "audit", "compile", "rule", "skill", "skills", "docs", "setup", "init", "dev", "doctor", "err", "test", "e2e", "ci", "typecheck", "lint", "format", "tech-debt", "worktree", "mcp", "hook", "hooks", "gain", "bench", "install", "add", "remove", "update", "exec", "run"];
8
+ declare const SUPPORTED_COMMANDS: readonly ["blueprint", "config", "roadmap", "sync", "audit", "compile", "rule", "skill", "skills", "docs", "setup", "init", "dev", "deploy", "doctor", "err", "test", "e2e", "ci", "typecheck", "lint", "format", "tech-debt", "worktree", "mcp", "hook", "hooks", "gain", "bench", "install", "add", "remove", "update", "exec", "run"];
9
9
  export { SUPPORTED_COMMANDS };
10
10
  export declare function main(): Promise<number>;
11
11
  //# sourceMappingURL=cli.d.ts.map
@@ -27,6 +27,7 @@ const SUPPORTED_COMMANDS = [
27
27
  'setup',
28
28
  'init',
29
29
  'dev',
30
+ 'deploy',
30
31
  'doctor',
31
32
  'err',
32
33
  'test',
@@ -209,6 +210,11 @@ export async function main() {
209
210
  registerDevCommand(cli);
210
211
  break;
211
212
  }
213
+ case 'deploy': {
214
+ const { registerDeployCommand } = await import('./commands/deploy.js');
215
+ registerDeployCommand(cli);
216
+ break;
217
+ }
212
218
  case 'doctor': {
213
219
  const { registerDoctorCommand } = await import('./commands/doctor.js');
214
220
  registerDoctorCommand(cli);
@@ -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' | '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' | '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;
@@ -59,6 +59,7 @@ const REPO_AUDIT_REGISTRY = {
59
59
  'no-legacy-cli-bin': async (root) => (await import('#audit/no-legacy-cli-bin')).auditNoLegacyCliBin(root),
60
60
  'architecture-drift': async (root) => (await import('#audit/architecture-drift')).auditArchitectureDrift(root),
61
61
  'cloudflare-deploy-contract': async (root) => (await import('#audit/cloudflare-deploy-contract')).auditCloudflareDeployContract(root),
62
+ 'toolchain-isolation': async (root) => (await import('#audit/toolchain-isolation')).auditToolchainIsolation(root),
62
63
  'absolute-path-policy': async (root) => (await import('#audit/absolute-path-policy')).auditAbsolutePathPolicy(root),
63
64
  'agent-cost': async (root) => (await import('#audit/agent-cost')).auditAgentCost(root),
64
65
  'blueprint-db-consistency': async (root) => (await import('#audit/blueprint-db-consistency')).auditBlueprintDbConsistency(root),
@@ -0,0 +1,4 @@
1
+ import type { CAC } from 'cac';
2
+ export declare const DEPLOY_COMMAND_HELP: string;
3
+ export declare function registerDeployCommand(cli: CAC): void;
4
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { runDeployPlan } from '#deploy/run.js';
2
+ export const DEPLOY_COMMAND_HELP = [
3
+ 'Run a consumer-owned deploy adapter through the managed wp deploy surface.',
4
+ '',
5
+ 'Examples:',
6
+ ' wp deploy --lane prd --dry-run',
7
+ ' wp deploy --lane preview_pr_123 --plan-json',
8
+ ].join('\n');
9
+ export function registerDeployCommand(cli) {
10
+ cli
11
+ .command('deploy', DEPLOY_COMMAND_HELP)
12
+ .option('--lane <lane>', 'Deploy lane: dev, preview_main, preview_pr_<n>, or prd')
13
+ .option('--dry-run', 'Ask the adapter for its dry-run deploy plan')
14
+ .option('--plan-json', 'Print the validated deploy plan JSON without executing steps')
15
+ .action(async (options) => {
16
+ const lane = typeof options.lane === 'string' ? options.lane : undefined;
17
+ if (!lane) {
18
+ console.error('Usage: wp deploy --lane <dev|preview_main|preview_pr_<n>|prd>');
19
+ return 1;
20
+ }
21
+ try {
22
+ return await runDeployPlan({
23
+ cwd: process.cwd(),
24
+ lane,
25
+ dryRun: Boolean(options.dryRun),
26
+ planJson: Boolean(options.planJson),
27
+ });
28
+ }
29
+ catch (error) {
30
+ console.error(error instanceof Error ? error.message : String(error));
31
+ return 1;
32
+ }
33
+ });
34
+ }
35
+ //# sourceMappingURL=deploy.js.map
@@ -3,7 +3,24 @@ export interface GitignoreBlock {
3
3
  id: string;
4
4
  patterns: readonly string[];
5
5
  }
6
+ export type GeneratedIndexCleanupResult = {
7
+ kind: 'skipped-dry-run';
8
+ pathspecs: readonly string[];
9
+ } | {
10
+ kind: 'skipped-not-git';
11
+ pathspecs: readonly string[];
12
+ } | {
13
+ kind: 'ok';
14
+ pathspecs: readonly string[];
15
+ removedPaths: readonly string[];
16
+ } | {
17
+ kind: 'failed';
18
+ pathspecs: readonly string[];
19
+ exitCode: number | null;
20
+ stderr: string;
21
+ };
6
22
  /** Canonical gitignore block for webpresso generated/transient paths. */
7
23
  export declare const GENERATED_PATHS_BLOCK: GitignoreBlock;
24
+ export declare function untrackGeneratedGitignoredPaths(repoRoot: string, block?: GitignoreBlock, opts?: Pick<MergeOptions, 'dryRun'>): GeneratedIndexCleanupResult;
8
25
  export declare function patchGitignore(targetPath: string, block: GitignoreBlock, opts?: MergeOptions): MergeResult;
9
26
  //# sourceMappingURL=gitignore-patcher.d.ts.map
@@ -11,6 +11,7 @@
11
11
  * drift). Other content in `.gitignore` — including unrelated managed blocks
12
12
  * from other scaffolders — is preserved verbatim.
13
13
  */
14
+ import { spawnSync } from 'node:child_process';
14
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
16
  import { dirname } from 'node:path';
16
17
  const BEGIN = (id) => `# >>> managed by webpresso (${id})`;
@@ -55,6 +56,7 @@ export const GENERATED_PATHS_BLOCK = {
55
56
  '.opencode/',
56
57
  '.codex/',
57
58
  '.claude/settings.json',
59
+ '.claude/settings.local.json',
58
60
  '.claude/agents/',
59
61
  '.claude/hooks/',
60
62
  '.claude/rules/',
@@ -77,6 +79,46 @@ export const GENERATED_PATHS_BLOCK = {
77
79
  '.agent/.tail-hint-history.jsonl',
78
80
  ],
79
81
  };
82
+ function generatedPathspecs(block) {
83
+ return block.patterns
84
+ .map((pattern) => pattern.trim())
85
+ .filter((pattern) => pattern.length > 0)
86
+ .filter((pattern) => !pattern.startsWith('#'))
87
+ .filter((pattern) => !pattern.startsWith('!'));
88
+ }
89
+ function parseGitRmStdout(stdout) {
90
+ return stdout
91
+ .split('\n')
92
+ .map((line) => line.trim())
93
+ .filter((line) => line.startsWith('rm '))
94
+ .map((line) => line.replace(/^rm ['"]?/, '').replace(/['"]?$/, ''));
95
+ }
96
+ export function untrackGeneratedGitignoredPaths(repoRoot, block = GENERATED_PATHS_BLOCK, opts = {}) {
97
+ const pathspecs = generatedPathspecs(block);
98
+ if (opts.dryRun)
99
+ return { kind: 'skipped-dry-run', pathspecs };
100
+ if (pathspecs.length === 0)
101
+ return { kind: 'ok', pathspecs, removedPaths: [] };
102
+ const gitProbe = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
103
+ cwd: repoRoot,
104
+ encoding: 'utf8',
105
+ });
106
+ if (gitProbe.status !== 0)
107
+ return { kind: 'skipped-not-git', pathspecs };
108
+ const gitRm = spawnSync('git', ['rm', '--cached', '-r', '--ignore-unmatch', '--', ...pathspecs], {
109
+ cwd: repoRoot,
110
+ encoding: 'utf8',
111
+ });
112
+ if (gitRm.status !== 0) {
113
+ return {
114
+ kind: 'failed',
115
+ pathspecs,
116
+ exitCode: gitRm.status,
117
+ stderr: String(gitRm.stderr ?? '').trim(),
118
+ };
119
+ }
120
+ return { kind: 'ok', pathspecs, removedPaths: parseGitRmStdout(String(gitRm.stdout ?? '')) };
121
+ }
80
122
  export function patchGitignore(targetPath, block, opts = {}) {
81
123
  const exists = existsSync(targetPath);
82
124
  const original = exists ? readFileSync(targetPath, 'utf8') : '';
@@ -22,7 +22,7 @@ import { scaffoldAgent, RENDERED_SKILLS, TIER1_SKILLS, TIER2_SKILLS } from './sc
22
22
  import { scaffoldAgentRules } from './scaffold-agent-rules.js';
23
23
  import { scaffoldAgentSkills } from './scaffold-agent-skills.js';
24
24
  import { scaffoldCatalogIgnore } from './scaffold-catalog-ignore.js';
25
- import { GENERATED_PATHS_BLOCK, patchGitignore } from './gitignore-patcher.js';
25
+ import { GENERATED_PATHS_BLOCK, patchGitignore, untrackGeneratedGitignoredPaths, } from './gitignore-patcher.js';
26
26
  import { scaffoldAgentsMd } from './scaffold-agents-md.js';
27
27
  import { scaffoldBlueprints } from './scaffold-blueprints.js';
28
28
  import { scaffoldDocs } from './scaffold-docs.js';
@@ -244,6 +244,7 @@ export async function runInit(flags) {
244
244
  overwrite: options.overwrite,
245
245
  });
246
246
  const generatedSurfaceIgnoreResult = patchGitignore(join(consumer.repoRoot, '.gitignore'), GENERATED_PATHS_BLOCK, { dryRun: options.dryRun, overwrite: true });
247
+ const generatedIndexCleanupResult = untrackGeneratedGitignoredPaths(consumer.repoRoot, GENERATED_PATHS_BLOCK, { dryRun: options.dryRun });
247
248
  const baseKitResults = tier3Selection.includes('base-kit')
248
249
  ? scaffoldBaseKit({
249
250
  catalogDir,
@@ -669,6 +670,15 @@ export async function runInit(flags) {
669
670
  console.log(` drifted: ${summary.drifted}`);
670
671
  if (options.dryRun)
671
672
  console.log(` would-change: ${summary['skipped-dry']}`);
673
+ if (generatedIndexCleanupResult.kind === 'ok') {
674
+ console.log(` git index cleanup: ${generatedIndexCleanupResult.removedPaths.length} untracked`);
675
+ }
676
+ else if (generatedIndexCleanupResult.kind === 'failed') {
677
+ console.warn(` git index cleanup: failed (git rm --cached exited ${generatedIndexCleanupResult.exitCode})`);
678
+ if (generatedIndexCleanupResult.stderr.length > 0) {
679
+ console.warn(` git index cleanup stderr: ${generatedIndexCleanupResult.stderr}`);
680
+ }
681
+ }
672
682
  if (tier3Selection.includes('base-kit')) {
673
683
  const qualityTargets = new Set(BASE_KIT_QUALITY_TARGETS);
674
684
  const qualityResults = baseKitResults.filter((result) => qualityTargets.has(relative(consumer.repoRoot, result.targetPath).replaceAll('\\', '/')));
@@ -138,7 +138,7 @@ function mergePackageJson(repoRoot, options, globalInstall = false) {
138
138
  const devDeps = (pkg['devDependencies'] ?? {});
139
139
  const hasAgentKitDevDep = typeof devDeps['@webpresso/agent-kit'] === 'string';
140
140
  const hasLegacyAgentKitDevDep = typeof devDeps['webpresso'] === 'string';
141
- const shouldSkipSelfInstall = packageName === '@webpresso/agent-kit' || packageName === 'webpresso';
141
+ const shouldSkipSelfInstall = isSelfPackageName(packageName);
142
142
  const shouldManageAgentKitAsGlobal = globalInstall && !shouldSkipSelfInstall;
143
143
  const requiredAuthoringDeps = {
144
144
  '@playwright/test': 'latest',
@@ -236,10 +236,35 @@ function mergePackageJson(repoRoot, options, globalInstall = false) {
236
236
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
237
237
  return { targetPath: pkgPath, action: 'overwritten' };
238
238
  }
239
+ /** agent-kit's own package identities: scoped canonical + legacy unscoped. */
240
+ const SELF_PACKAGE_NAMES = ['@webpresso/agent-kit', 'webpresso'];
241
+ /** True when `name` is one of agent-kit's own package identities. */
242
+ function isSelfPackageName(name) {
243
+ return name !== undefined && SELF_PACKAGE_NAMES.includes(name);
244
+ }
245
+ /**
246
+ * True when `repoRoot` is agent-kit's own source repo (by package.json name).
247
+ * agent-kit dogfoods base-kit's scripts and shared templates, but the QUALITY
248
+ * starter samples (quality-sample.ts, e2e/smoke, sample configs) are teaching
249
+ * artifacts for FRESH consumer repos — scaffolding them into agent-kit's own
250
+ * source tree is pollution. This flag skips ONLY those samples.
251
+ */
252
+ function isAgentKitSelfRepo(repoRoot) {
253
+ try {
254
+ const pkg = JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8'));
255
+ return isSelfPackageName(pkg.name);
256
+ }
257
+ catch {
258
+ return false;
259
+ }
260
+ }
239
261
  export function scaffoldBaseKit(input) {
240
262
  const { catalogDir, repoRoot, options, globalInstall = false } = input;
241
263
  const baseKitDir = join(catalogDir, 'base-kit');
242
264
  const results = [];
265
+ // Dogfooding boundary: agent-kit gets base-kit's scripts/templates but not the
266
+ // starter quality samples scaffolded into its own source tree.
267
+ const skipStarterSamples = isAgentKitSelfRepo(repoRoot);
243
268
  for (const [tmplRel, targetRel] of TEMPLATE_MAP) {
244
269
  const tmplPath = join(baseKitDir, tmplRel);
245
270
  if (!existsSync(tmplPath))
@@ -269,23 +294,25 @@ export function scaffoldBaseKit(input) {
269
294
  writeFileSync(targetPath, content);
270
295
  results.push({ targetPath, action: 'created' });
271
296
  }
272
- for (const [tmplRel, targetRel] of QUALITY_BOOTSTRAP_ONLY_MAP) {
273
- const tmplPath = join(baseKitDir, tmplRel);
274
- if (!existsSync(tmplPath))
275
- continue;
276
- const targetPath = join(repoRoot, targetRel);
277
- if (existsSync(targetPath)) {
278
- results.push({ targetPath, action: 'identical' });
279
- continue;
280
- }
281
- const content = readFileSync(tmplPath, 'utf8');
282
- if (options.dryRun) {
283
- results.push({ targetPath, action: 'skipped-dry' });
284
- continue;
297
+ if (!skipStarterSamples) {
298
+ for (const [tmplRel, targetRel] of QUALITY_BOOTSTRAP_ONLY_MAP) {
299
+ const tmplPath = join(baseKitDir, tmplRel);
300
+ if (!existsSync(tmplPath))
301
+ continue;
302
+ const targetPath = join(repoRoot, targetRel);
303
+ if (existsSync(targetPath)) {
304
+ results.push({ targetPath, action: 'identical' });
305
+ continue;
306
+ }
307
+ const content = readFileSync(tmplPath, 'utf8');
308
+ if (options.dryRun) {
309
+ results.push({ targetPath, action: 'skipped-dry' });
310
+ continue;
311
+ }
312
+ mkdirSync(dirname(targetPath), { recursive: true });
313
+ writeFileSync(targetPath, content);
314
+ results.push({ targetPath, action: 'created' });
285
315
  }
286
- mkdirSync(dirname(targetPath), { recursive: true });
287
- writeFileSync(targetPath, content);
288
- results.push({ targetPath, action: 'created' });
289
316
  }
290
317
  // Make husky hook files executable
291
318
  if (!options.dryRun) {
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './schema.js';
3
+ export * from './load-adapter.js';
4
+ export * from './run.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './schema.js';
3
+ export * from './load-adapter.js';
4
+ export * from './run.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ import type { LoadedDeployAdapter } from './types.js';
2
+ export declare class DeployAdapterConfigError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function loadDeployAdapter(cwd?: string): Promise<LoadedDeployAdapter | null>;
6
+ //# sourceMappingURL=load-adapter.d.ts.map
@@ -0,0 +1,57 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { loadWebpressoConfigSafe } from '#e2e/load-host-adapter';
4
+ const DEFAULT_DEPLOY_ADAPTER_EXPORTS = [
5
+ 'webpressoDeployAdapter',
6
+ 'deployAdapter',
7
+ 'default',
8
+ ];
9
+ export class DeployAdapterConfigError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'DeployAdapterConfigError';
13
+ }
14
+ }
15
+ export async function loadDeployAdapter(cwd = process.cwd()) {
16
+ const loadedConfig = await loadWebpressoConfigSafe({ cwd });
17
+ const deployConfig = loadedConfig?.config.deploy;
18
+ if (!loadedConfig || !deployConfig?.adapterModule)
19
+ return null;
20
+ const moduleSpecifier = resolveModuleSpecifier(deployConfig.adapterModule, loadedConfig.configPath);
21
+ let moduleNamespace;
22
+ try {
23
+ moduleNamespace = (await import(moduleSpecifier));
24
+ }
25
+ catch (error) {
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ throw new DeployAdapterConfigError(`Failed to load deploy adapter module "${deployConfig.adapterModule}" from ${loadedConfig.configPath}: ${message}`);
28
+ }
29
+ const exportNames = deployConfig.adapterExport
30
+ ? [deployConfig.adapterExport, ...DEFAULT_DEPLOY_ADAPTER_EXPORTS]
31
+ : [...DEFAULT_DEPLOY_ADAPTER_EXPORTS];
32
+ for (const exportName of exportNames) {
33
+ const candidate = moduleNamespace[exportName];
34
+ if (isDeployAdapter(candidate)) {
35
+ return {
36
+ adapter: candidate,
37
+ config: loadedConfig.config,
38
+ configPath: loadedConfig.configPath,
39
+ moduleSpecifier,
40
+ exportName,
41
+ };
42
+ }
43
+ }
44
+ throw new DeployAdapterConfigError(`Deploy adapter module "${deployConfig.adapterModule}" does not export a deploy adapter. Tried ${exportNames.join(', ')}.`);
45
+ }
46
+ function isDeployAdapter(value) {
47
+ return Boolean(value && typeof value === 'object' && typeof value.createPlan === 'function');
48
+ }
49
+ function resolveModuleSpecifier(moduleSpecifier, configPath) {
50
+ if (moduleSpecifier.startsWith('file:'))
51
+ return moduleSpecifier;
52
+ if (moduleSpecifier.startsWith('.') || moduleSpecifier.startsWith('/')) {
53
+ return pathToFileURL(resolve(dirname(configPath), moduleSpecifier)).href;
54
+ }
55
+ return moduleSpecifier;
56
+ }
57
+ //# sourceMappingURL=load-adapter.js.map
@@ -0,0 +1,12 @@
1
+ import type { DeployPlan } from './types.js';
2
+ export interface CreateDeployPlanOptions {
3
+ readonly cwd?: string;
4
+ readonly lane: string;
5
+ readonly dryRun?: boolean;
6
+ }
7
+ export interface RunDeployPlanOptions extends CreateDeployPlanOptions {
8
+ readonly planJson?: boolean;
9
+ }
10
+ export declare function createDeployPlan(options: CreateDeployPlanOptions): Promise<DeployPlan>;
11
+ export declare function runDeployPlan(options: RunDeployPlanOptions): Promise<number>;
12
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1,52 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { loadDeployAdapter } from './load-adapter.js';
3
+ import { parseDeployLane, validateDeployPlan } from './schema.js';
4
+ import { getManagedRunner } from '#tool-runtime';
5
+ export async function createDeployPlan(options) {
6
+ const cwd = options.cwd ?? process.cwd();
7
+ const loadedAdapter = await loadDeployAdapter(cwd);
8
+ if (!loadedAdapter) {
9
+ throw new Error('No deploy adapter configured. Add deploy.adapterModule to agent-kit.config.ts.');
10
+ }
11
+ const lane = parseDeployLane(options.lane);
12
+ const request = {
13
+ cwd,
14
+ lane,
15
+ dryRun: Boolean(options.dryRun),
16
+ env: process.env,
17
+ cloudflare: loadedAdapter.config.deploy?.cloudflare,
18
+ };
19
+ return validateDeployPlan(await loadedAdapter.adapter.createPlan(request));
20
+ }
21
+ export async function runDeployPlan(options) {
22
+ const plan = await createDeployPlan(options);
23
+ if (options.planJson) {
24
+ console.log(JSON.stringify(plan, null, 2));
25
+ return 0;
26
+ }
27
+ for (const step of plan.steps) {
28
+ const code = runDeployStep(step);
29
+ if (code !== 0)
30
+ return code;
31
+ }
32
+ return 0;
33
+ }
34
+ function runDeployStep(step) {
35
+ const label = step.label ?? step.id;
36
+ console.error(`[deploy] ${label}`);
37
+ const command = resolveStepCommand(step);
38
+ const result = spawnSync(command.command, command.args, {
39
+ cwd: step.cwd,
40
+ env: { ...process.env, ...step.env },
41
+ stdio: 'inherit',
42
+ });
43
+ return result.status ?? 1;
44
+ }
45
+ function resolveStepCommand(step) {
46
+ if (step.kind === 'command') {
47
+ return { command: step.command, args: [...(step.args ?? [])] };
48
+ }
49
+ const resolution = getManagedRunner(step.tool, { outputPolicy: 'structured' });
50
+ return { command: resolution.command, args: [...resolution.args, ...(step.args ?? [])] };
51
+ }
52
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,6 @@
1
+ import type { DeployLane, DeployPlan } from './types.js';
2
+ export declare const DEPLOY_PLAN_SCHEMA_VERSION = 1;
3
+ export declare function isDeployLane(value: string): value is DeployLane;
4
+ export declare function parseDeployLane(value: string): DeployLane;
5
+ export declare function validateDeployPlan(plan: unknown): DeployPlan;
6
+ //# sourceMappingURL=schema.d.ts.map