@webpresso/agent-kit 0.28.0 → 0.29.1

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 (117) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/README.md +2 -2
  4. package/bin/_run.js +6 -0
  5. package/bin/wp +5 -0
  6. package/catalog/base-kit/.github/actions/setup-webpresso/action.yml.tmpl +21 -0
  7. package/catalog/base-kit/.github/workflows/{ci.webpresso.yml.tmpl → ci.yml.tmpl} +17 -7
  8. package/catalog/base-kit/tsconfig.json.tmpl +1 -1
  9. package/catalog/docs/templates/blueprint.yaml +1 -1
  10. package/dist/esm/audit/_budgets.d.ts +9 -1
  11. package/dist/esm/audit/_budgets.js +8 -1
  12. package/dist/esm/audit/blueprint-db-consistency.js +2 -2
  13. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +17 -7
  14. package/dist/esm/audit/blueprint-lifecycle-sql.js +298 -48
  15. package/dist/esm/audit/blueprint-readme-drift.d.ts +6 -0
  16. package/dist/esm/audit/blueprint-readme-drift.js +110 -0
  17. package/dist/esm/audit/no-first-party-mjs.js +5 -4
  18. package/dist/esm/audit/package-surface.js +79 -10
  19. package/dist/esm/audit/repo-guardrails.d.ts +1 -1
  20. package/dist/esm/audit/repo-guardrails.js +43 -3
  21. package/dist/esm/audit/tech-debt-cadence.js +2 -3
  22. package/dist/esm/audit/toolchain-isolation.js +2 -3
  23. package/dist/esm/blueprint/core/parser.js +3 -2
  24. package/dist/esm/blueprint/core/schema.d.ts +3 -2
  25. package/dist/esm/blueprint/core/schema.js +1 -1
  26. package/dist/esm/blueprint/cross-repo/audit.js +3 -4
  27. package/dist/esm/blueprint/db/cold-start.js +2 -3
  28. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  29. package/dist/esm/blueprint/db/ephemeral-projection.d.ts +25 -0
  30. package/dist/esm/blueprint/db/ephemeral-projection.js +36 -0
  31. package/dist/esm/blueprint/db/gc.d.ts +11 -0
  32. package/dist/esm/blueprint/db/gc.js +55 -0
  33. package/dist/esm/blueprint/db/ingester.js +39 -1
  34. package/dist/esm/blueprint/db/migrations/run.js +5 -3
  35. package/dist/esm/blueprint/db/paths.d.ts +13 -24
  36. package/dist/esm/blueprint/db/paths.js +25 -33
  37. package/dist/esm/blueprint/execution/progress-bridge.js +5 -4
  38. package/dist/esm/blueprint/freshness.d.ts +2 -0
  39. package/dist/esm/blueprint/freshness.js +3 -1
  40. package/dist/esm/blueprint/lifecycle/audit.js +6 -6
  41. package/dist/esm/blueprint/lifecycle/engine.d.ts +1 -1
  42. package/dist/esm/blueprint/lifecycle/engine.js +13 -9
  43. package/dist/esm/blueprint/lifecycle/transition-matrix.d.ts +5 -0
  44. package/dist/esm/blueprint/lifecycle/transition-matrix.js +20 -0
  45. package/dist/esm/blueprint/markdown/helpers.d.ts +1 -1
  46. package/dist/esm/blueprint/projection-ready.js +2 -0
  47. package/dist/esm/blueprint/service/BlueprintService.js +1 -1
  48. package/dist/esm/blueprint/service/blueprint-records.js +1 -1
  49. package/dist/esm/blueprint/tracked-document/parser.js +1 -1
  50. package/dist/esm/blueprint/utils/archive.d.ts +2 -2
  51. package/dist/esm/blueprint/utils/archive.js +5 -2
  52. package/dist/esm/blueprint/utils/package-assets.d.ts +13 -0
  53. package/dist/esm/blueprint/utils/package-assets.js +38 -6
  54. package/dist/esm/build/normalize-tsconfig-json-exports.d.ts +13 -0
  55. package/dist/esm/build/normalize-tsconfig-json-exports.js +39 -0
  56. package/dist/esm/build/package-manifest.js +12 -4
  57. package/dist/esm/build/release-policy.d.ts +9 -18
  58. package/dist/esm/build/release-policy.js +10 -19
  59. package/dist/esm/build/runtime-surface-policy.d.ts +14 -0
  60. package/dist/esm/build/runtime-surface-policy.js +13 -0
  61. package/dist/esm/cli/commands/audit-core.d.ts +2 -2
  62. package/dist/esm/cli/commands/audit.js +7 -3
  63. package/dist/esm/cli/commands/blueprint/db-commands.js +0 -3
  64. package/dist/esm/cli/commands/blueprint/mutations.d.ts +3 -2
  65. package/dist/esm/cli/commands/blueprint/mutations.js +45 -39
  66. package/dist/esm/cli/commands/blueprint/router-output.js +2 -2
  67. package/dist/esm/cli/commands/doctor.d.ts +1 -1
  68. package/dist/esm/cli/commands/doctor.js +4 -5
  69. package/dist/esm/cli/commands/init/config.d.ts +6 -10
  70. package/dist/esm/cli/commands/init/config.js +36 -20
  71. package/dist/esm/cli/commands/init/gitignore-patcher.js +0 -1
  72. package/dist/esm/cli/commands/init/index.d.ts +8 -1
  73. package/dist/esm/cli/commands/init/index.js +17 -19
  74. package/dist/esm/cli/commands/init/package-root.d.ts +20 -0
  75. package/dist/esm/cli/commands/init/package-root.js +110 -0
  76. package/dist/esm/cli/commands/init/scaffold-base-kit.js +5 -1
  77. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +3 -0
  78. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +8 -24
  79. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +9 -0
  80. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +79 -1
  81. package/dist/esm/cli/commands/init/scaffolders/claude-rules/index.js +2 -12
  82. package/dist/esm/cli/commands/init/scaffolders/subagents/index.js +2 -12
  83. package/dist/esm/config/tsconfig/cloudflare.json +1 -1
  84. package/dist/esm/config/tsconfig/library.json +1 -1
  85. package/dist/esm/config/tsconfig/react-library.json +3 -2
  86. package/dist/esm/config/tsconfig/react-router.json +1 -1
  87. package/dist/esm/dev/restore-dev-links/index.js +3 -4
  88. package/dist/esm/docs-linter/blueprint-plan.js +46 -4
  89. package/dist/esm/hooks/check-dev-link/index.js +3 -4
  90. package/dist/esm/hooks/doctor.d.ts +11 -0
  91. package/dist/esm/hooks/doctor.js +174 -30
  92. package/dist/esm/hooks/guard-switch/index.js +3 -5
  93. package/dist/esm/hooks/post-tool/lint-after-edit.js +4 -5
  94. package/dist/esm/hooks/pretool-guard/index.js +2 -4
  95. package/dist/esm/hooks/pretool-guard/runner.js +2 -4
  96. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +47 -6
  97. package/dist/esm/hooks/sessionstart/index.js +3 -4
  98. package/dist/esm/hooks/shared/direct-entrypoint.d.ts +10 -0
  99. package/dist/esm/hooks/shared/direct-entrypoint.js +21 -0
  100. package/dist/esm/hooks/stop/qa-changed-files.js +3 -5
  101. package/dist/esm/hooks/test-quality-check.js +3 -4
  102. package/dist/esm/mcp/blueprint-server.js +26 -3
  103. package/dist/esm/mcp/cli.js +2 -6
  104. package/dist/esm/mcp/server.d.ts +2 -0
  105. package/dist/esm/mcp/server.js +18 -3
  106. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  107. package/dist/esm/mcp/tools/_shared/audit-kinds.js +1 -0
  108. package/dist/esm/mcp/tools/audit.d.ts +2 -1
  109. package/dist/esm/mcp/tools/audit.js +13 -3
  110. package/dist/esm/package.json +2 -0
  111. package/package.json +24 -15
  112. package/tsconfig/cloudflare.json +1 -1
  113. package/tsconfig/library.json +1 -1
  114. package/tsconfig/react-library.json +3 -2
  115. package/tsconfig/react-router.json +1 -1
  116. package/dist/esm/blueprint/db/legacy-migration.d.ts +0 -41
  117. package/dist/esm/blueprint/db/legacy-migration.js +0 -122
@@ -8,7 +8,6 @@
8
8
  */
9
9
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
10
  import { basename, dirname, join, relative } from 'node:path';
11
- import { fileURLToPath } from 'node:url';
12
11
  import { isTelemetryEnabled, reportTthw } from '#telemetry/setup-tthw';
13
12
  import { readPackageVersion } from '#cli/utils';
14
13
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
@@ -26,6 +25,7 @@ import { GENERATED_PATHS_BLOCK, patchGitignore, untrackGeneratedGitignoredPaths,
26
25
  import { scaffoldAgentsMd } from './scaffold-agents-md.js';
27
26
  import { scaffoldBlueprints } from './scaffold-blueprints.js';
28
27
  import { scaffoldDocs } from './scaffold-docs.js';
28
+ import { resolveAgentKitPackageRoot } from './package-root.js';
29
29
  import { BASE_KIT_QUALITY_TARGETS, collectRuntimeContractGuidance, scaffoldBaseKit, } from './scaffold-base-kit.js';
30
30
  import { scaffoldMonorepoNav } from './scaffold-monorepo-nav.js';
31
31
  import { REQUIRED_CORE_CAPABILITIES, auditHostSkillVisibility, parseAgentHosts, serializeHostVisibility, summarizeHostVisibility, } from './host-visibility.js';
@@ -75,23 +75,14 @@ export const EXIT_SUCCESS = 0;
75
75
  export const EXIT_SETUP_FAIL = 1;
76
76
  export const EXIT_USER_ABORT = 2;
77
77
  export const EXIT_WRITE_FAIL = 3;
78
- export function resolveCatalogDir() {
79
- // The `catalog/` directory is bundled alongside `package.json` in the
80
- // published tarball (see `files` in package.json). To locate it at both
81
- // development time (src/cli/commands/init/index.ts) and at runtime (dist/cli.js),
82
- // we walk up from this module looking for the nearest package.json.
83
- let dir = dirname(fileURLToPath(import.meta.url));
84
- for (let depth = 0; depth < 8; depth++) {
85
- if (existsSync(join(dir, 'package.json'))) {
86
- const candidate = join(dir, 'catalog');
87
- if (existsSync(candidate))
88
- return candidate;
89
- }
90
- const parent = dirname(dir);
91
- if (parent === dir)
92
- break;
93
- dir = parent;
94
- }
78
+ export function resolveCatalogDir(options = {}) {
79
+ // The published native `bin/wp` binary executes from its own on-disk path,
80
+ // while the bundled module URL can resolve inside Bun's virtual filesystem.
81
+ // Probe both the module location and the real executable path so packed
82
+ // installs still find the shipped `catalog/` directory.
83
+ const root = resolveAgentKitPackageRoot({ ...options, requireCatalog: true });
84
+ if (root)
85
+ return join(root, 'catalog');
95
86
  throw new Error('wp init: could not locate the webpresso catalog directory. The package may be broken.');
96
87
  }
97
88
  function inferBlueprintsDirOverride(repoRoot, existingConfig) {
@@ -184,7 +175,10 @@ export async function runInit(flags) {
184
175
  const presets = parsePresets(flags.with);
185
176
  let selectedHosts;
186
177
  try {
187
- selectedHosts = parseAgentHosts(flags.host);
178
+ selectedHosts =
179
+ flags.host === undefined
180
+ ? (existingConfig?.hosts?.selected ?? parseAgentHosts(undefined))
181
+ : parseAgentHosts(flags.host);
188
182
  }
189
183
  catch (error) {
190
184
  console.error(error instanceof Error ? error.message : String(error));
@@ -557,6 +551,10 @@ export async function runInit(flags) {
557
551
  console.warn(` agent-kit global: ⚠ \`${agentKitGlobalResult.command.join(' ')}\` exited with ${agentKitGlobalResult.exitCode}; ` +
558
552
  'the existing global binary is unchanged. Re-run `wp setup` once the registry is reachable.');
559
553
  break;
554
+ case 'agent-kit-global-staging-failed':
555
+ console.warn(` agent-kit global: ⚠ native bin/wp staging failed (${agentKitGlobalResult.reason}); ` +
556
+ 'the Claude plugin may keep using the previous cached launcher until the runtime package is rebuilt/reinstalled.');
557
+ break;
560
558
  }
561
559
  }
562
560
  const claudePluginResult = ensureClaudeCodeUserPlugin({
@@ -0,0 +1,20 @@
1
+ export interface ResolveAgentKitPackageRootOptions {
2
+ readonly moduleUrl?: string;
3
+ readonly execPath?: string;
4
+ readonly argv0?: string;
5
+ readonly argv1?: string;
6
+ readonly pathEnv?: string;
7
+ readonly pathExtEnv?: string;
8
+ readonly platform?: NodeJS.Platform;
9
+ readonly requireCatalog?: boolean;
10
+ }
11
+ export declare function isAgentKitPackageRoot(dir: string, options?: {
12
+ readonly requireCatalog?: boolean;
13
+ }): boolean;
14
+ export declare function findAgentKitPackageRoot(startPath: string | undefined, options?: {
15
+ readonly requireCatalog?: boolean;
16
+ readonly platform?: NodeJS.Platform;
17
+ }): string | null;
18
+ export declare function resolveAgentKitPackageRoot(options?: ResolveAgentKitPackageRootOptions): string | null;
19
+ export declare function resolveAgentKitPackageRootOrThrow(errorMessage: string, options?: ResolveAgentKitPackageRootOptions): string;
20
+ //# sourceMappingURL=package-root.d.ts.map
@@ -0,0 +1,110 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { isAbsolute, join, posix, win32 } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ function existingModulePath(moduleUrl) {
5
+ if (typeof moduleUrl !== 'string' || moduleUrl.length === 0)
6
+ return null;
7
+ try {
8
+ return fileURLToPath(moduleUrl);
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ function isRunnablePath(path) {
15
+ if (typeof path !== 'string' || path.length === 0)
16
+ return false;
17
+ return isAbsolute(path) || win32.isAbsolute(path) || path.includes('/') || path.includes('\\');
18
+ }
19
+ function pathModuleForPlatform(platform) {
20
+ return platform === 'win32' ? win32 : posix;
21
+ }
22
+ function pathDelimiterForPlatform(platform) {
23
+ return platform === 'win32' ? ';' : ':';
24
+ }
25
+ function resolveBinOnPath(binName, pathEnv, options = {}) {
26
+ if (binName.length === 0 || typeof pathEnv !== 'string' || pathEnv.length === 0)
27
+ return null;
28
+ const platform = options.platform ?? process.platform;
29
+ const pathExtEnv = options.pathExtEnv ?? process.env.PATHEXT;
30
+ const pathModule = pathModuleForPlatform(platform);
31
+ const candidates = platform === 'win32' && !/\.[^./\\]+$/u.test(binName)
32
+ ? [
33
+ binName,
34
+ ...(typeof pathExtEnv === 'string' && pathExtEnv.length > 0
35
+ ? pathExtEnv
36
+ .split(';')
37
+ .map((entry) => entry.trim())
38
+ .filter((entry) => entry.length > 0)
39
+ .map((entry) => `${binName}${entry.toLowerCase()}`)
40
+ : [`${binName}.exe`, `${binName}.cmd`, `${binName}.bat`]),
41
+ ]
42
+ : [binName];
43
+ for (const entry of pathEnv.split(pathDelimiterForPlatform(platform))) {
44
+ if (entry.length === 0)
45
+ continue;
46
+ for (const binCandidate of candidates) {
47
+ for (const candidate of new Set([pathModule.join(entry, binCandidate), join(entry, binCandidate)])) {
48
+ if (existsSync(candidate))
49
+ return candidate;
50
+ }
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+ export function isAgentKitPackageRoot(dir, options = {}) {
56
+ if (!existsSync(join(dir, 'package.json')))
57
+ return false;
58
+ if (options.requireCatalog === true && !existsSync(join(dir, 'catalog')))
59
+ return false;
60
+ return (existsSync(join(dir, 'bin', 'wp')) ||
61
+ existsSync(join(dir, 'bin', 'wp.cmd')) ||
62
+ existsSync(join(dir, 'bin', 'wp.exe')) ||
63
+ existsSync(join(dir, 'bin', 'wp.js')) ||
64
+ existsSync(join(dir, '.claude-plugin', 'plugin.json')) ||
65
+ existsSync(join(dir, 'src', 'cli', 'cli.ts')) ||
66
+ existsSync(join(dir, 'dist', 'esm', 'cli', 'cli.js')));
67
+ }
68
+ export function findAgentKitPackageRoot(startPath, options = {}) {
69
+ if (!isRunnablePath(startPath))
70
+ return null;
71
+ const platform = options.platform ?? process.platform;
72
+ const pathModule = pathModuleForPlatform(platform);
73
+ let dir = pathModule.dirname(startPath);
74
+ for (let depth = 0; depth < 10; depth++) {
75
+ if (isAgentKitPackageRoot(dir, options))
76
+ return dir;
77
+ const parent = pathModule.dirname(dir);
78
+ if (parent === dir)
79
+ break;
80
+ dir = parent;
81
+ }
82
+ return null;
83
+ }
84
+ export function resolveAgentKitPackageRoot(options = {}) {
85
+ const modulePath = existingModulePath(options.moduleUrl ?? import.meta.url);
86
+ const execPath = options.execPath ?? process.execPath;
87
+ const argv0 = options.argv0 ?? process.argv[0];
88
+ const argv1 = options.argv1 ?? process.argv[1];
89
+ const pathEnv = options.pathEnv ?? process.env.PATH;
90
+ const platform = options.platform ?? process.platform;
91
+ const pathModule = pathModuleForPlatform(platform);
92
+ const pathResolvedBin = resolveBinOnPath(pathModule.basename(argv0 || 'wp'), pathEnv, {
93
+ pathExtEnv: options.pathExtEnv,
94
+ platform,
95
+ });
96
+ const requireCatalog = options.requireCatalog;
97
+ for (const startPath of [modulePath, argv1, execPath, pathResolvedBin, argv0]) {
98
+ const root = findAgentKitPackageRoot(startPath ?? undefined, { requireCatalog, platform });
99
+ if (root)
100
+ return root;
101
+ }
102
+ return null;
103
+ }
104
+ export function resolveAgentKitPackageRootOrThrow(errorMessage, options = {}) {
105
+ const root = resolveAgentKitPackageRoot(options);
106
+ if (root)
107
+ return root;
108
+ throw new Error(errorMessage);
109
+ }
110
+ //# sourceMappingURL=package-root.js.map
@@ -38,7 +38,11 @@ const TEMPLATE_MAP = [
38
38
  ],
39
39
  ['.husky/pre-commit.tmpl', '.husky/pre-commit'],
40
40
  ['.husky/commit-msg.tmpl', '.husky/commit-msg'],
41
- ['.github/workflows/ci.webpresso.yml.tmpl', '.github/workflows/ci.webpresso.yml'],
41
+ [
42
+ '.github/actions/setup-webpresso/action.yml.tmpl',
43
+ '.github/actions/setup-webpresso/action.yml',
44
+ ],
45
+ ['.github/workflows/ci.yml.tmpl', '.github/workflows/ci.yml'],
42
46
  ['test/.gitkeep.tmpl', 'test/.gitkeep'],
43
47
  ['e2e/.gitkeep.tmpl', 'e2e/.gitkeep'],
44
48
  ];
@@ -1,4 +1,5 @@
1
1
  import { type MergeOptions, type MergeResult } from '#cli/commands/init/merge';
2
+ import { type ResolveAgentKitPackageRootOptions } from '#cli/commands/init/package-root';
2
3
  import type { CodexAppServerApi } from '#codex/app-server/types.js';
3
4
  import { type SyncCodexHookTrustResult } from './codex-trust-sync.js';
4
5
  type HookEntry = {
@@ -63,6 +64,8 @@ export interface ScaffoldAgentHooksResult {
63
64
  codex: MergeResult;
64
65
  claudeUser: MergeResult;
65
66
  }
67
+ export type ResolvePackageRootForHookLaunchersOptions = ResolveAgentKitPackageRootOptions;
68
+ export declare function resolvePackageRootForHookLaunchers(options?: ResolvePackageRootForHookLaunchersOptions): string;
66
69
  export declare function scaffoldAgentHooks(input: ScaffoldAgentHooksInput): Promise<ScaffoldAgentHooksResult>;
67
70
  export {};
68
71
  //# sourceMappingURL=index.d.ts.map
@@ -11,15 +11,16 @@
11
11
  */
12
12
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
13
13
  import { homedir } from 'node:os';
14
- import { dirname, join, resolve } from 'node:path';
15
- import { fileURLToPath } from 'node:url';
14
+ import { join, resolve } from 'node:path';
16
15
  import { isHookName } from '#cli/commands/hook.js';
17
16
  import { patchJsonFile } from '#cli/commands/init/merge';
17
+ import { resolveAgentKitPackageRootOrThrow, } from '#cli/commands/init/package-root';
18
18
  import { CodexAppServerClient } from '#codex/app-server/client.js';
19
19
  import { normalizeGlobalCodexHooksFile, resolveBinaryOnPath, } from '#cli/commands/init/scaffolders/agent-hooks/codex-global-normalize';
20
20
  import { isPresetOwnedGlobalCodexHook } from './codex-global-ownership.js';
21
21
  import { syncCodexHookTrustWithAppServer, } from './codex-trust-sync.js';
22
22
  import { buildSkillTag, extractSkillHooks, isTaggedSkillHook, } from './skill-hooks.js';
23
+ import { resolveRuntimeTarget, runtimePackageDirName } from '#build/runtime-targets.js';
23
24
  // Claude Code uses $CLAUDE_PROJECT_DIR. Codex hook runners can execute while the
24
25
  // active session cwd points at a sibling repo, so Codex hook commands must be
25
26
  // path-stable and not depend on the caller's cwd.
@@ -609,31 +610,14 @@ function resolveProjectHookBinPath(repoRoot, binName) {
609
610
  return resolve(repoRoot, 'node_modules', '@webpresso', 'agent-kit', 'bin', `${binName}.js`);
610
611
  }
611
612
  function resolvePackageHookBin(binName) {
612
- return join(resolvePackageRoot(), 'bin', `${binName}.js`);
613
+ return join(resolvePackageRootForHookLaunchers(), 'bin', `${binName}.js`);
613
614
  }
614
- function resolvePackageRoot() {
615
- let dir = dirname(fileURLToPath(import.meta.url));
616
- for (let depth = 0; depth < 10; depth++) {
617
- if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'bin', 'wp.js'))) {
618
- return dir;
619
- }
620
- const parent = dirname(dir);
621
- if (parent === dir)
622
- break;
623
- dir = parent;
624
- }
625
- throw new Error('wp setup: could not locate @webpresso/agent-kit package root for hook launchers.');
615
+ export function resolvePackageRootForHookLaunchers(options = {}) {
616
+ return resolveAgentKitPackageRootOrThrow('wp setup: could not locate @webpresso/agent-kit package root for hook launchers.', options);
626
617
  }
627
- // Consumer-side runtime package directory for the current platform. Mirrors the
628
- // canonical target table in `src/build/runtime-targets.ts`; kept inline here to
629
- // avoid a deep cross-tree import for a small lookup. If that target list ever
630
- // changes, update both. Returns undefined for platforms with no compiled
631
- // runtime package.
632
618
  function compiledRuntimePackageDir() {
633
- const osLabel = process.platform === 'win32' ? 'windows' : process.platform;
634
- const id = `${osLabel}-${process.arch}`;
635
- const known = new Set(['darwin-arm64', 'darwin-x64', 'linux-x64', 'linux-arm64', 'windows-x64']);
636
- return known.has(id) ? `agent-kit-runtime-${id}` : undefined;
619
+ const target = resolveRuntimeTarget();
620
+ return target ? runtimePackageDirName(target.packageName) : undefined;
637
621
  }
638
622
  /**
639
623
  * Absolute path to the consumer's self-contained compiled `wp` binary, when the
@@ -37,12 +37,17 @@ export interface EnsureAgentKitGlobalInput {
37
37
  argv1?: string;
38
38
  /** DI seam for source/git-clone detection. */
39
39
  detectGit?: (argv1: string) => string | null;
40
+ /** DI seam for tests/global installs; defaults to the package root owning argv1/import. */
41
+ packageRoot?: string;
42
+ /** DI seam for staging-root fallback when argv1 cannot be mapped back to the owning package. */
43
+ resolvePackageRootForStaging?: (argv1: string) => string | null;
40
44
  /** DI seam for spinner. Defaults to noop when !process.stdout.isTTY. */
41
45
  spinnerFactory?: SpinnerFactory;
42
46
  }
43
47
  export type EnsureAgentKitGlobalResult = {
44
48
  kind: 'agent-kit-global-updated';
45
49
  command: readonly string[];
50
+ stagedBin?: string;
46
51
  } | {
47
52
  kind: 'agent-kit-global-skipped-dry-run';
48
53
  } | {
@@ -57,6 +62,10 @@ export type EnsureAgentKitGlobalResult = {
57
62
  kind: 'agent-kit-global-failed';
58
63
  exitCode: number;
59
64
  command: readonly string[];
65
+ } | {
66
+ kind: 'agent-kit-global-staging-failed';
67
+ reason: string;
68
+ command: readonly string[];
60
69
  };
61
70
  /**
62
71
  * Refresh the single global `@webpresso/agent-kit` install via `vp install -g`.
@@ -25,10 +25,58 @@
25
25
  * warn-only contract as the codex-cli scaffolder).
26
26
  */
27
27
  import { spawnSync } from 'node:child_process';
28
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, } from 'node:fs';
29
+ import { dirname, join } from 'node:path';
30
+ import { findAgentKitPackageRoot, resolveAgentKitPackageRoot, } from '#cli/commands/init/package-root';
28
31
  import { makeNoopSpinnerFactory } from '#cli/commands/init/scaffolders/spinner';
29
32
  import { buildVpGlobalInstallCommand, detectGitInstall, PUBLIC_PACKAGE_NAME, } from '#cli/auto-update/detect-pm.js';
30
33
  const NO_VP_HINT = 'vp (vite-plus) is not on PATH; cannot refresh the global ' +
31
34
  `${PUBLIC_PACKAGE_NAME}. Install vite-plus, then re-run \`wp setup\`.`;
35
+ function resolvePackageRootForStaging(argv1) {
36
+ const fromArgv = argv1.length > 0 ? findAgentKitPackageRoot(argv1) : null;
37
+ if (fromArgv)
38
+ return fromArgv;
39
+ return resolveAgentKitPackageRoot({ moduleUrl: import.meta.url });
40
+ }
41
+ function runtimeFilename(manifest, target) {
42
+ const binaryName = manifest.binaryName ?? 'wp';
43
+ return target.os === 'win32' ? `${binaryName}.exe` : binaryName;
44
+ }
45
+ function runtimePackageDirName(packageName) {
46
+ return packageName.split('/').at(-1) ?? packageName;
47
+ }
48
+ function resolveHostRuntimeBinary(packageRoot) {
49
+ const manifestPath = join(packageRoot, 'bin', 'runtime-manifest.json');
50
+ if (!existsSync(manifestPath))
51
+ return null;
52
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
53
+ const target = manifest.targets?.find((candidate) => candidate.os === process.platform && candidate.cpu === process.arch);
54
+ if (!target?.id || !target.packageName)
55
+ return null;
56
+ const filename = runtimeFilename(manifest, target);
57
+ const candidates = [
58
+ join(packageRoot, 'bin', 'runtime', target.id, filename),
59
+ join(packageRoot, 'dist', 'runtime', target.id, filename),
60
+ join(packageRoot, '..', runtimePackageDirName(target.packageName), 'bin', filename),
61
+ join(packageRoot, 'node_modules', '@webpresso', runtimePackageDirName(target.packageName), 'bin', filename),
62
+ ];
63
+ const source = candidates.find((candidate) => existsSync(candidate));
64
+ return source ? { source, targetId: target.id } : null;
65
+ }
66
+ function stageHostRuntimeLauncher(packageRoot) {
67
+ const runtime = resolveHostRuntimeBinary(packageRoot);
68
+ if (!runtime)
69
+ return null;
70
+ const destination = join(packageRoot, 'bin', 'wp');
71
+ mkdirSync(dirname(destination), { recursive: true });
72
+ copyFileSync(runtime.source, destination);
73
+ chmodSync(destination, 0o755);
74
+ const stat = statSync(destination);
75
+ if (!stat.isFile()) {
76
+ throw new Error(`staged ${destination} for ${runtime.targetId} is not a regular file`);
77
+ }
78
+ return destination;
79
+ }
32
80
  /**
33
81
  * Refresh the single global `@webpresso/agent-kit` install via `vp install -g`.
34
82
  */
@@ -58,7 +106,37 @@ export function ensureAgentKitGlobal(input) {
58
106
  spinner.fail('agent-kit global refresh failed');
59
107
  return { kind: 'agent-kit-global-failed', exitCode: install.status ?? -1, command };
60
108
  }
109
+ let stagedBin;
110
+ const packageRoot = input.packageRoot ??
111
+ (input.resolvePackageRootForStaging ?? resolvePackageRootForStaging)(argv1);
112
+ if (!packageRoot) {
113
+ spinner.fail('agent-kit native launcher staging failed');
114
+ return {
115
+ kind: 'agent-kit-global-staging-failed',
116
+ reason: 'could not resolve the owning @webpresso/agent-kit package root for staging',
117
+ command,
118
+ };
119
+ }
120
+ try {
121
+ stagedBin = stageHostRuntimeLauncher(packageRoot) ?? undefined;
122
+ if (!stagedBin) {
123
+ spinner.fail('agent-kit native launcher staging failed');
124
+ return {
125
+ kind: 'agent-kit-global-staging-failed',
126
+ reason: `could not resolve a host runtime binary under ${packageRoot}`,
127
+ command,
128
+ };
129
+ }
130
+ }
131
+ catch (error) {
132
+ spinner.fail('agent-kit native launcher staging failed');
133
+ return {
134
+ kind: 'agent-kit-global-staging-failed',
135
+ reason: error instanceof Error ? error.message : String(error),
136
+ command,
137
+ };
138
+ }
61
139
  spinner.succeed('agent-kit global up to date');
62
- return { kind: 'agent-kit-global-updated', command };
140
+ return { kind: 'agent-kit-global-updated', command, stagedBin };
63
141
  }
64
142
  //# sourceMappingURL=index.js.map
@@ -4,9 +4,9 @@
4
4
  */
5
5
  import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync, } from 'node:fs';
6
6
  import { dirname, join, relative } from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
7
  import { readConfig } from '#cli/commands/init/config';
9
8
  import { readPackageJson } from '#cli/commands/init/detect-consumer';
9
+ import { resolveAgentKitPackageRootOrThrow } from '#cli/commands/init/package-root';
10
10
  function detectMode(repoRoot) {
11
11
  const pkg = readPackageJson(repoRoot).info;
12
12
  if (pkg?.name === 'webpresso') {
@@ -58,17 +58,7 @@ function writeOverrideRule(targetPath, sourcePath, options) {
58
58
  return { targetPath, action: 'drifted' };
59
59
  }
60
60
  function resolveCurrentPackageRoot() {
61
- let dir = dirname(fileURLToPath(import.meta.url));
62
- for (let depth = 0; depth < 8; depth++) {
63
- if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'catalog'))) {
64
- return dir;
65
- }
66
- const parent = dirname(dir);
67
- if (parent === dir)
68
- break;
69
- dir = parent;
70
- }
71
- throw new Error('wp init: could not locate the webpresso package root for claude-rules fallback.');
61
+ return resolveAgentKitPackageRootOrThrow('wp init: could not locate the webpresso package root for claude-rules fallback.', { requireCatalog: true });
72
62
  }
73
63
  export function scaffoldClaudeRules(input) {
74
64
  const { repoRoot, options } = input;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync, } from 'node:fs';
2
2
  import { dirname, join, relative } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
3
  import { readPackageJson } from '#cli/commands/init/detect-consumer';
4
+ import { resolveAgentKitPackageRootOrThrow } from '#cli/commands/init/package-root';
5
5
  function detectMode(repoRoot) {
6
6
  const pkg = readPackageJson(repoRoot).info;
7
7
  if (pkg?.name === 'webpresso') {
@@ -24,17 +24,7 @@ function detectMode(repoRoot) {
24
24
  };
25
25
  }
26
26
  function resolveCurrentPackageRoot() {
27
- let dir = dirname(fileURLToPath(import.meta.url));
28
- for (let depth = 0; depth < 8; depth++) {
29
- if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'catalog'))) {
30
- return dir;
31
- }
32
- const parent = dirname(dir);
33
- if (parent === dir)
34
- break;
35
- dir = parent;
36
- }
37
- throw new Error('wp init: could not locate the webpresso package root for subagents fallback.');
27
+ return resolveAgentKitPackageRootOrThrow('wp init: could not locate the webpresso package root for subagents fallback.', { requireCatalog: true });
38
28
  }
39
29
  export function scaffoldSubagents(input) {
40
30
  const { repoRoot, options } = input;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/tsconfig",
3
3
  "display": "Cloudflare Workers",
4
- "extends": "./base.json",
4
+ "extends": "@webpresso/agent-kit/tsconfig/base.json",
5
5
  "compilerOptions": {
6
6
  "target": "ES2024",
7
7
  "lib": ["ES2024"],
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/tsconfig",
3
3
  "display": "Library",
4
- "extends": "./base.json",
4
+ "extends": "@webpresso/agent-kit/tsconfig/base.json",
5
5
  "compilerOptions": {
6
6
  "declaration": true,
7
7
  "declarationMap": true,
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/tsconfig",
3
3
  "display": "React Library",
4
- "extends": "./library.json",
4
+ "extends": "@webpresso/agent-kit/tsconfig/library.json",
5
5
  "compilerOptions": {
6
6
  "lib": ["ES2024", "DOM", "DOM.Iterable"],
7
- "jsx": "react-jsx"
7
+ "jsx": "react-jsx",
8
+ "types": ["react", "react-dom"]
8
9
  }
9
10
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/tsconfig",
3
3
  "display": "React Router v7",
4
- "extends": "./webpresso.json",
4
+ "extends": "@webpresso/agent-kit/tsconfig/react-library.json",
5
5
  "compilerOptions": {
6
6
  "lib": ["ES2024", "DOM", "DOM.Iterable"],
7
7
  "jsx": "react-jsx",
@@ -20,11 +20,11 @@
20
20
  * to fail in CI. Postinstall is the only seam that runs deterministically
21
21
  * after install, doesn't touch the lockfile, and is observable enough to debug.
22
22
  */
23
- import { lstatSync, mkdirSync, readlinkSync, realpathSync, renameSync, symlinkSync, unlinkSync, } from 'node:fs';
23
+ import { lstatSync, mkdirSync, readlinkSync, renameSync, symlinkSync, unlinkSync, } from 'node:fs';
24
24
  import { existsSync } from 'node:fs';
25
25
  import { dirname, join } from 'node:path';
26
- import { fileURLToPath } from 'node:url';
27
26
  import { STATE_FILE_RELATIVE_PATH, readDevLinkState } from '#dev/dev-link-state';
27
+ import { isDirectEntrypoint } from '#hooks/shared/direct-entrypoint';
28
28
  export function restoreDevLinks(options = {}) {
29
29
  const cwd = options.cwd ?? process.cwd();
30
30
  const stdout = options.stdout ?? process.stdout;
@@ -95,8 +95,7 @@ function timestamp() {
95
95
  const d = new Date();
96
96
  return `${d.getFullYear()}${pad2(d.getMonth() + 1)}${pad2(d.getDate())}-${pad2(d.getHours())}${pad2(d.getMinutes())}${pad2(d.getSeconds())}`;
97
97
  }
98
- if (process.argv[1] &&
99
- realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
98
+ if (isDirectEntrypoint(import.meta.url)) {
100
99
  const result = restoreDevLinks();
101
100
  process.exit(result.exitCode);
102
101
  }
@@ -24,7 +24,7 @@ const EXECUTABLE_BLUEPRINT_STATUSES = new Set([
24
24
  'completed',
25
25
  'archived',
26
26
  ]);
27
- const TASK_STATUSES = new Set(['todo', 'in_progress', 'blocked', 'done']);
27
+ const TASK_STATUSES = new Set(['todo', 'in-progress', 'blocked', 'done', 'dropped']);
28
28
  /**
29
29
  * Find tasks using ### (3 hashes) instead of #### (4 hashes).
30
30
  * Exported for testability.
@@ -282,7 +282,7 @@ function createTaskStatusInvalidError(filePath, taskId, status) {
282
282
  file: filePath,
283
283
  severity: 'error',
284
284
  source: 'blueprint-format',
285
- message: `Task ${taskId} has invalid status "${status}". Use only: todo, in_progress, blocked, done.`,
285
+ message: `Task ${taskId} has invalid status "${status}". Use only: todo, in-progress, blocked, done, dropped.`,
286
286
  ruleId: 'blueprint-task-status-invalid',
287
287
  };
288
288
  }
@@ -318,7 +318,7 @@ function createCompletedRequiresAllDoneError(filePath, taskId, taskStatus) {
318
318
  file: filePath,
319
319
  severity: 'error',
320
320
  source: 'blueprint-format',
321
- message: `Blueprint status is completed but task ${taskId} is "${taskStatus}" (expected "done").`,
321
+ message: `Blueprint status is completed but task ${taskId} is "${taskStatus}" (expected "done" or "dropped").`,
322
322
  ruleId: 'blueprint-completed-requires-all-done',
323
323
  };
324
324
  }
@@ -330,6 +330,48 @@ function validateLifecycleContract(filePath, content) {
330
330
  errors.push(createBlueprintStatusError(filePath, status ?? '(missing)'));
331
331
  return errors;
332
332
  }
333
+ if (status !== 'draft') {
334
+ const title = frontmatter?.title?.trim();
335
+ const owner = frontmatter?.owner?.trim();
336
+ const complexity = extractComplexity(content);
337
+ const lastUpdated = frontmatter?.last_updated?.trim();
338
+ if (!title) {
339
+ errors.push({
340
+ file: filePath,
341
+ severity: 'error',
342
+ source: 'blueprint-format',
343
+ message: 'Blueprint is missing required frontmatter field: title.',
344
+ ruleId: 'blueprint-title-required',
345
+ });
346
+ }
347
+ if (!owner) {
348
+ errors.push({
349
+ file: filePath,
350
+ severity: 'error',
351
+ source: 'blueprint-format',
352
+ message: 'Blueprint is missing required frontmatter field: owner.',
353
+ ruleId: 'blueprint-owner-required',
354
+ });
355
+ }
356
+ if (!['XS', 'S', 'M', 'L', 'XL'].includes(complexity)) {
357
+ errors.push({
358
+ file: filePath,
359
+ severity: 'error',
360
+ source: 'blueprint-format',
361
+ message: `Blueprint complexity "${complexity}" is invalid. Use only: XS, S, M, L, XL.`,
362
+ ruleId: 'blueprint-complexity-invalid',
363
+ });
364
+ }
365
+ if (!lastUpdated || !/^\d{4}-\d{2}-\d{2}$/.test(lastUpdated.replace(/^['"]|['"]$/g, ''))) {
366
+ errors.push({
367
+ file: filePath,
368
+ severity: 'error',
369
+ source: 'blueprint-format',
370
+ message: 'Blueprint is missing valid frontmatter field: last_updated (YYYY-MM-DD).',
371
+ ruleId: 'blueprint-last-updated-required',
372
+ });
373
+ }
374
+ }
333
375
  const taskBlocks = extractTaskBlocks(content);
334
376
  for (const task of taskBlocks) {
335
377
  const statusMatch = task.section.match(/\*\*Status:\*\*\s*(.+)/i);
@@ -364,7 +406,7 @@ function validateLifecycleContract(filePath, content) {
364
406
  if (!taskStatus || !TASK_STATUSES.has(taskStatus)) {
365
407
  continue;
366
408
  }
367
- if (taskStatus !== 'done') {
409
+ if (taskStatus !== 'done' && taskStatus !== 'dropped') {
368
410
  errors.push(createCompletedRequiresAllDoneError(filePath, task.id, taskStatus));
369
411
  }
370
412
  }
@@ -13,10 +13,10 @@
13
13
  * instead of the live source declared in `.webpresso/webpresso-dev-link.json`.
14
14
  * Always exits 0; never blocks session start.
15
15
  */
16
- import { readlinkSync, realpathSync } from 'node:fs';
16
+ import { readlinkSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
- import { fileURLToPath } from 'node:url';
19
18
  import { STATE_FILE_RELATIVE_PATH, readDevLinkState } from '#dev/dev-link-state';
19
+ import { isDirectEntrypoint } from '#hooks/shared/direct-entrypoint';
20
20
  export function detectDevLinkBreakage(options = {}) {
21
21
  const cwd = options.cwd ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
22
22
  const state = readDevLinkState(cwd);
@@ -78,8 +78,7 @@ export async function main() {
78
78
  process.stdout.write(`${out}\n`);
79
79
  process.exit(0);
80
80
  }
81
- if (process.argv[1] &&
82
- realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
81
+ if (isDirectEntrypoint(import.meta.url)) {
83
82
  void main();
84
83
  }
85
84
  // Re-export so tests can stub the state file path if needed in future.