@webpresso/agent-kit 0.24.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -2
- package/dist/esm/audit/toolchain-isolation.d.ts +3 -0
- package/dist/esm/audit/toolchain-isolation.js +134 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +6 -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/deploy.d.ts +4 -0
- package/dist/esm/cli/commands/deploy.js +35 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.d.ts +17 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +42 -0
- package/dist/esm/cli/commands/init/index.js +11 -1
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +44 -17
- package/dist/esm/deploy/index.d.ts +5 -0
- package/dist/esm/deploy/index.js +5 -0
- package/dist/esm/deploy/load-adapter.d.ts +6 -0
- package/dist/esm/deploy/load-adapter.js +57 -0
- package/dist/esm/deploy/run.d.ts +12 -0
- package/dist/esm/deploy/run.js +52 -0
- package/dist/esm/deploy/schema.d.ts +6 -0
- package/dist/esm/deploy/schema.js +61 -0
- package/dist/esm/deploy/types.d.ts +43 -0
- package/dist/esm/deploy/types.js +2 -0
- package/dist/esm/e2e/command-builder.js +37 -18
- package/dist/esm/e2e/config.d.ts +2 -0
- package/dist/esm/e2e/config.js +6 -1
- package/dist/esm/e2e/load-host-adapter.d.ts +0 -4
- package/dist/esm/e2e/load-host-adapter.js +5 -14
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +3 -2
- package/dist/esm/hooks/pretool-guard/runner.js +11 -6
- package/dist/esm/mcp/tools/_shared/runner-failure.d.ts +30 -0
- package/dist/esm/mcp/tools/_shared/runner-failure.js +45 -0
- package/dist/esm/mcp/tools/typecheck.js +20 -3
- package/dist/esm/package.json +3 -1
- package/dist/esm/test/command-builder.d.ts +1 -0
- package/dist/esm/test/command-builder.js +9 -5
- package/dist/esm/tool-runtime/resolve-runner.js +38 -10
- 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.
|
|
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.
|
|
26
|
+
"version": "0.26.0"
|
|
27
27
|
}
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
> TypeScript infrastructure for AI-agent-driven development. One `wp` runtime
|
|
7
7
|
> gives agents planning, tests, mutation, e2e, CI, docs, and debt tracking —
|
|
8
8
|
> all summary-first so they keep context, and enforced as contracts so docs,
|
|
9
|
-
> intent, and code can't drift. MIT.
|
|
9
|
+
> intent, and code can't drift. MIT. Active development.
|
|
10
10
|
|
|
11
11
|
## What it is
|
|
12
12
|
|
|
@@ -125,7 +125,8 @@ validate` when `claude` is present).
|
|
|
125
125
|
|
|
126
126
|
## Status
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Active development. Review [CHANGELOG.md](./CHANGELOG.md) before upgrading
|
|
129
|
+
across minor versions.
|
|
129
130
|
|
|
130
131
|
## License
|
|
131
132
|
|
|
@@ -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
|
package/dist/esm/cli/cli.d.ts
CHANGED
|
@@ -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
|
package/dist/esm/cli/cli.js
CHANGED
|
@@ -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,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
|
|
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
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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,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
|