@webpresso/agent-kit 0.21.4 → 0.23.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 (194) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +93 -66
  4. package/bin/_run.js +143 -1
  5. package/bin/runtime-manifest.json +40 -0
  6. package/catalog/AGENTS.md.tpl +7 -6
  7. package/catalog/agent/commands/plan-refine.md +3 -3
  8. package/catalog/agent/commands/pll.md +2 -0
  9. package/catalog/agent/guides/parallel-execution.md +2 -0
  10. package/catalog/agent/rules/extraction-parity.md +27 -1
  11. package/catalog/agent/rules/public-package-safety.md +24 -1
  12. package/catalog/agent/skills/plan-refine/SKILL.md +5 -4
  13. package/catalog/agent/skills/pll/SKILL.md +1 -0
  14. package/catalog/base-kit/.github/workflows/ci.webpresso.yml.tmpl +33 -0
  15. package/catalog/base-kit/commitlint.config.ts.tmpl +1 -3
  16. package/catalog/base-kit/e2e/fixtures/smoke.html.tmpl +13 -0
  17. package/catalog/base-kit/e2e/smoke.spec.ts.tmpl +13 -0
  18. package/catalog/base-kit/oxlint.config.ts.tmpl +26 -0
  19. package/catalog/base-kit/playwright.config.ts.tmpl +10 -0
  20. package/catalog/base-kit/src/quality-sample.test.ts.tmpl +19 -0
  21. package/catalog/base-kit/src/quality-sample.ts.tmpl +11 -0
  22. package/catalog/base-kit/stryker.config.ts.tmpl +14 -0
  23. package/catalog/base-kit/tsconfig.json.tmpl +9 -0
  24. package/catalog/base-kit/vitest.config.ts.tmpl +10 -0
  25. package/catalog/docs/templates/adr.md +1 -1
  26. package/catalog/docs/templates/blueprint.md +2 -0
  27. package/catalog/docs/templates/blueprint.yaml +16 -15
  28. package/catalog/docs/templates/guide.md +1 -1
  29. package/catalog/docs/templates/postmortem.md +1 -1
  30. package/catalog/docs/templates/research.md +1 -1
  31. package/catalog/docs/templates/runbook.md +1 -1
  32. package/catalog/docs/templates/system.md +12 -3
  33. package/catalog/docs/templates/tech-debt.md +1 -0
  34. package/commands/blueprint.md +10 -12
  35. package/dist/esm/audit/blueprint-db-consistency.d.ts +1 -1
  36. package/dist/esm/audit/blueprint-db-consistency.js +6 -8
  37. package/dist/esm/audit/blueprint-lifecycle-sql.js +10 -3
  38. package/dist/esm/audit/cloudflare-deploy-contract.d.ts +3 -0
  39. package/dist/esm/audit/cloudflare-deploy-contract.js +64 -0
  40. package/dist/esm/audit/no-legacy-cli-bin.d.ts +3 -0
  41. package/dist/esm/audit/no-legacy-cli-bin.js +100 -0
  42. package/dist/esm/audit/package-surface.js +14 -1
  43. package/dist/esm/audit/repo-guardrails.js +40 -13
  44. package/dist/esm/audit/resolve-audit-script.d.ts +24 -0
  45. package/dist/esm/audit/resolve-audit-script.js +27 -0
  46. package/dist/esm/audit/roadmap-links.js +23 -10
  47. package/dist/esm/blueprint/core/schema.d.ts +8 -8
  48. package/dist/esm/blueprint/core/schema.js +2 -2
  49. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  50. package/dist/esm/blueprint/db/ingester.js +18 -10
  51. package/dist/esm/blueprint/index.d.ts +0 -1
  52. package/dist/esm/blueprint/index.js +0 -2
  53. package/dist/esm/blueprint/lifecycle/audit.js +9 -2
  54. package/dist/esm/blueprint/lifecycle/local.js +15 -4
  55. package/dist/esm/blueprint/local.d.ts +0 -3
  56. package/dist/esm/blueprint/local.js +0 -2
  57. package/dist/esm/blueprint/service/BlueprintCreationService.js +16 -8
  58. package/dist/esm/blueprint/service/BlueprintService.js +37 -19
  59. package/dist/esm/blueprint/service/scanner.js +73 -9
  60. package/dist/esm/blueprint/tracked-document/schema.d.ts +2 -2
  61. package/dist/esm/blueprint/utils/document-paths.d.ts +23 -0
  62. package/dist/esm/blueprint/utils/document-paths.js +91 -0
  63. package/dist/esm/blueprint/utils/package-assets.d.ts +11 -0
  64. package/dist/esm/blueprint/utils/package-assets.js +33 -4
  65. package/dist/esm/build/package-manifest.js +7 -0
  66. package/dist/esm/build/release-policy.d.ts +27 -0
  67. package/dist/esm/build/release-policy.js +29 -0
  68. package/dist/esm/build/runtime-targets.d.ts +13 -0
  69. package/dist/esm/build/runtime-targets.js +48 -0
  70. package/dist/esm/build/sync-catalog-doc-templates.d.ts +23 -0
  71. package/dist/esm/build/sync-catalog-doc-templates.js +93 -0
  72. package/dist/esm/cli/auto-update/detect-pm.d.ts +15 -0
  73. package/dist/esm/cli/auto-update/detect-pm.js +24 -9
  74. package/dist/esm/cli/auto-update/skip.js +9 -1
  75. package/dist/esm/cli/bundle/agent-command-inventory.d.ts +120 -0
  76. package/dist/esm/cli/bundle/agent-command-inventory.js +100 -0
  77. package/dist/esm/cli/bundle/index.d.ts +17 -0
  78. package/dist/esm/cli/bundle/index.js +15 -0
  79. package/dist/esm/cli/cli.d.ts +1 -1
  80. package/dist/esm/cli/cli.js +49 -5
  81. package/dist/esm/cli/commands/audit-core.d.ts +1 -1
  82. package/dist/esm/cli/commands/audit.js +4 -7
  83. package/dist/esm/cli/commands/blueprint/router.js +16 -10
  84. package/dist/esm/cli/commands/blueprint/template-resolver.js +8 -4
  85. package/dist/esm/cli/commands/hook.d.ts +8 -0
  86. package/dist/esm/cli/commands/hook.js +47 -0
  87. package/dist/esm/cli/commands/init/host-visibility.js +4 -2
  88. package/dist/esm/cli/commands/init/index.js +80 -7
  89. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +12 -0
  90. package/dist/esm/cli/commands/init/scaffold-base-kit.js +142 -7
  91. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/codex-ownership.js +9 -1
  92. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +130 -20
  93. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.d.ts +65 -0
  94. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +64 -0
  95. package/dist/esm/cli/commands/package-manager.d.ts +15 -0
  96. package/dist/esm/cli/commands/package-manager.js +42 -0
  97. package/dist/esm/cli/commands/test.d.ts +1 -0
  98. package/dist/esm/cli/commands/test.js +2 -1
  99. package/dist/esm/cli/commands/typecheck.js +10 -19
  100. package/dist/esm/cli/package-scripts.d.ts +12 -0
  101. package/dist/esm/cli/package-scripts.js +59 -0
  102. package/dist/esm/cli/utils.js +3 -22
  103. package/dist/esm/cli/wp-extensions.d.ts +14 -0
  104. package/dist/esm/cli/wp-extensions.js +34 -0
  105. package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
  106. package/dist/esm/config/docs-lint/schemas/implementation-plan.d.ts +2 -2
  107. package/dist/esm/config/docs-lint/schemas/parent-roadmap.d.ts +1 -1
  108. package/dist/esm/config/stryker/index.d.ts +85 -0
  109. package/dist/esm/config/stryker/index.js +31 -0
  110. package/dist/esm/e2e/command-builder.js +35 -7
  111. package/dist/esm/e2e/config.d.ts +56 -0
  112. package/dist/esm/e2e/config.js +114 -0
  113. package/dist/esm/e2e/execution.js +8 -0
  114. package/dist/esm/e2e/run-planner.js +2 -0
  115. package/dist/esm/e2e/types.d.ts +3 -0
  116. package/dist/esm/format/index.js +5 -1
  117. package/dist/esm/hooks/guard-switch/index.d.ts +1 -1
  118. package/dist/esm/hooks/guard-switch/index.js +22 -14
  119. package/dist/esm/hooks/post-tool/lint-after-edit.d.ts +1 -0
  120. package/dist/esm/hooks/post-tool/lint-after-edit.js +5 -2
  121. package/dist/esm/hooks/pretool-guard/validators/file-conventions.js +1 -1
  122. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.d.ts +6 -0
  123. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +27 -2
  124. package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +2 -1
  125. package/dist/esm/hooks/pretool-guard/validators/path-contract.js +59 -34
  126. package/dist/esm/hooks/pretool-guard/validators/plan-frontmatter.js +3 -3
  127. package/dist/esm/hooks/shared/routing-block.js +18 -4
  128. package/dist/esm/hooks/shared/validators/blueprint.js +3 -0
  129. package/dist/esm/hooks/stop/qa-changed-files.d.ts +1 -0
  130. package/dist/esm/hooks/stop/qa-changed-files.js +5 -2
  131. package/dist/esm/lint/index.js +3 -1
  132. package/dist/esm/mcp/auto-discover.d.ts +2 -0
  133. package/dist/esm/mcp/auto-discover.js +14 -6
  134. package/dist/esm/mcp/blueprint-server.js +379 -80
  135. package/dist/esm/mcp/cli.js +21 -0
  136. package/dist/esm/mcp/runners/test.js +15 -0
  137. package/dist/esm/mcp/server.d.ts +7 -0
  138. package/dist/esm/mcp/server.js +16 -27
  139. package/dist/esm/mcp/tools/_registry.d.ts +3 -0
  140. package/dist/esm/mcp/tools/_registry.js +21 -0
  141. package/dist/esm/mcp/tools/audit.d.ts +1 -0
  142. package/dist/esm/mcp/tools/audit.js +13 -8
  143. package/dist/esm/mcp/tools/typecheck.js +4 -2
  144. package/dist/esm/mutation/affected.d.ts +9 -0
  145. package/dist/esm/mutation/affected.js +36 -0
  146. package/dist/esm/package.json +8 -0
  147. package/dist/esm/runtime/package-version.d.ts +2 -0
  148. package/dist/esm/runtime/package-version.js +43 -0
  149. package/dist/esm/test/command-builder.d.ts +4 -0
  150. package/dist/esm/test/command-builder.js +28 -3
  151. package/dist/esm/test-helpers/hermetic-env.d.ts +25 -0
  152. package/dist/esm/test-helpers/hermetic-env.js +31 -0
  153. package/dist/esm/tool-runtime/index.d.ts +5 -0
  154. package/dist/esm/tool-runtime/index.js +24 -0
  155. package/dist/esm/tool-runtime/resolve-runner.d.ts +16 -0
  156. package/dist/esm/tool-runtime/resolve-runner.js +42 -0
  157. package/dist/esm/typecheck/index.js +4 -2
  158. package/dist/esm/wp-extension/index.d.ts +50 -0
  159. package/dist/esm/wp-extension/index.js +268 -0
  160. package/package.json +75 -46
  161. package/skills/plan-refine/SKILL.md +5 -4
  162. package/skills/pll/SKILL.md +1 -0
  163. package/dist/esm/blueprint/dag/cycle-detector.d.ts +0 -12
  164. package/dist/esm/blueprint/dag/cycle-detector.js +0 -46
  165. package/dist/esm/blueprint/dag/executor.d.ts +0 -140
  166. package/dist/esm/blueprint/dag/executor.js +0 -292
  167. package/dist/esm/blueprint/dag/index.d.ts +0 -20
  168. package/dist/esm/blueprint/dag/index.js +0 -17
  169. package/dist/esm/blueprint/dag/interfaces.d.ts +0 -56
  170. package/dist/esm/blueprint/dag/interfaces.js +0 -13
  171. package/dist/esm/blueprint/dag/local/independence.d.ts +0 -107
  172. package/dist/esm/blueprint/dag/local/independence.js +0 -231
  173. package/dist/esm/blueprint/dag/local/index.d.ts +0 -14
  174. package/dist/esm/blueprint/dag/local/index.js +0 -14
  175. package/dist/esm/blueprint/dag/local/package-graph.d.ts +0 -66
  176. package/dist/esm/blueprint/dag/local/package-graph.js +0 -148
  177. package/dist/esm/blueprint/dag/plan-parser.d.ts +0 -54
  178. package/dist/esm/blueprint/dag/plan-parser.js +0 -236
  179. package/dist/esm/blueprint/dag/task-graph-algorithms.d.ts +0 -13
  180. package/dist/esm/blueprint/dag/task-graph-algorithms.js +0 -236
  181. package/dist/esm/blueprint/dag/task-graph.d.ts +0 -171
  182. package/dist/esm/blueprint/dag/task-graph.js +0 -370
  183. package/dist/esm/blueprint/dag/types.d.ts +0 -17
  184. package/dist/esm/blueprint/dag/types.js +0 -2
  185. package/dist/esm/blueprint/graph/index.d.ts +0 -5
  186. package/dist/esm/blueprint/graph/index.js +0 -5
  187. package/dist/esm/blueprint/graph/mermaid-parser.d.ts +0 -3
  188. package/dist/esm/blueprint/graph/mermaid-parser.js +0 -93
  189. package/dist/esm/blueprint/graph/mermaid-serializer.d.ts +0 -3
  190. package/dist/esm/blueprint/graph/mermaid-serializer.js +0 -20
  191. package/dist/esm/blueprint/graph/schema.d.ts +0 -89
  192. package/dist/esm/blueprint/graph/schema.js +0 -104
  193. package/dist/esm/blueprint/graph/task-graph-adapter.d.ts +0 -6
  194. package/dist/esm/blueprint/graph/task-graph-adapter.js +0 -30
@@ -5,8 +5,9 @@ import { parseBlueprintForDb } from '#db/parser/blueprint-db-parser';
5
5
  import { blueprintToSpecKit } from '#export/spec-kit/index';
6
6
  import { getProjectRoot } from '#cli/utils';
7
7
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
8
+ import { getBlueprintDocumentPaths } from '#utils/document-paths.js';
8
9
  import { applyBlueprintLifecycleToFile, BlueprintCreationService, BlueprintService, complexitySchema, relativeBlueprintSlug, parseBlueprint, planStatusSchema, runBlueprintAudit, resolveBlueprintFile, serializeBlueprint, validateAllTasksDone, } from '#local';
9
- import { resolvePackageAsset } from '#utils/package-assets';
10
+ import { resolvePackageAssetPreferred } from '#utils/package-assets';
10
11
  import { describeBlueprintExecutionRuntime, buildBlueprintLaunchSpec, buildStoppedRuntimeEvidence, controlBlueprintExecution, initializeBlueprintExecutionProgressBridge, launchBlueprintExecution, persistBlueprintExecutionArtifacts, persistBlueprintExecutionMetadata, recordLaunchFailure, reconcileBlueprintRuntimeSnapshot, readBlueprintExecutionState, syncBlueprintExecutionProgress, writeBlueprintRuntimeSnapshot, } from './execution.js';
11
12
  import { advanceTask as advanceTaskMutation, finalizeBlueprint as finalizeBlueprintMutation, promoteBlueprint as promoteBlueprintMutation, } from './mutations.js';
12
13
  import { BlueprintAuditFailedError, executeBlueprintSubcommand } from './router-dispatch.js';
@@ -40,7 +41,10 @@ function assertBlueprintCanMoveToStatus(blueprint, nextStatus) {
40
41
  * when the lookup fails in unrelated contexts (e.g. `wp --help`).
41
42
  */
42
43
  function resolveRepoBlueprintTemplatePath() {
43
- return resolvePackageAsset('docs/templates/blueprint.md');
44
+ return resolvePackageAssetPreferred([
45
+ 'docs/templates/blueprint.md',
46
+ 'catalog/docs/templates/blueprint.md',
47
+ ]);
44
48
  }
45
49
  function todayIsoDate() {
46
50
  return new Date().toISOString().split('T')[0] ?? new Date().toISOString();
@@ -306,10 +310,12 @@ export async function moveBlueprint(slug, status, options = {}) {
306
310
  const projectRoot = resolveProjectRoot(options.projectRoot);
307
311
  const nextStatus = normalizeBlueprintStatus(status);
308
312
  const location = await resolveBlueprintLocation(slug, projectRoot);
313
+ const isFlatFile = path.basename(location.path) !== '_overview.md';
309
314
  const sourceDir = path.dirname(location.path);
310
- const targetDir = path.join(resolveBlueprintRoot(projectRoot), nextStatus, relativeBlueprintSlug(location.slug));
311
- const targetPath = path.join(targetDir, '_overview.md');
312
- if (sourceDir === targetDir && location.blueprint.status === nextStatus) {
315
+ const targetPaths = getBlueprintDocumentPaths(resolveBlueprintRoot(projectRoot), nextStatus, relativeBlueprintSlug(location.slug));
316
+ const targetDir = targetPaths.directory;
317
+ const targetPath = isFlatFile ? targetPaths.flat : targetPaths.folder;
318
+ if (location.path === targetPath && location.blueprint.status === nextStatus) {
313
319
  return {
314
320
  fromPath: location.path,
315
321
  fromStatus: location.blueprint.status,
@@ -325,18 +331,18 @@ export async function moveBlueprint(slug, status, options = {}) {
325
331
  throw new Error('Blueprint move is recovery-only. Use wp blueprint start/task/finalize for normal lifecycle changes, or pass --force-recovery.');
326
332
  }
327
333
  assertBlueprintCanMoveToStatus(location.blueprint, nextStatus);
328
- if (sourceDir !== targetDir) {
329
- await mkdir(path.dirname(targetDir), { recursive: true });
330
- await rename(sourceDir, targetDir);
334
+ if (location.path !== targetPath) {
335
+ await mkdir(path.dirname(targetPath), { recursive: true });
336
+ await rename(isFlatFile ? location.path : sourceDir, isFlatFile ? targetPath : targetDir);
331
337
  }
332
338
  const updated = await writeBlueprintWithStatus(targetPath, location.blueprint, nextStatus);
333
339
  return {
334
340
  fromPath: location.path,
335
341
  fromStatus: location.blueprint.status,
336
- message: sourceDir === targetDir
342
+ message: location.path === targetPath
337
343
  ? `Updated blueprint ${location.slug} to ${nextStatus}.`
338
344
  : `Moved blueprint ${location.slug} to ${nextStatus}.`,
339
- moved: sourceDir !== targetDir,
345
+ moved: location.path !== targetPath,
340
346
  slug: location.slug,
341
347
  toPath: targetPath,
342
348
  toStatus: nextStatus,
@@ -1,12 +1,16 @@
1
1
  import { readdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { resolvePackageAsset } from '#utils/package-assets.js';
3
+ import { resolvePackageAssetPreferred } from '#utils/package-assets.js';
4
4
  /**
5
- * Default templates directory: docs/templates/ resolved relative to the
6
- * package root (same strategy as resolveRepoBlueprintTemplatePath in router.ts).
5
+ * Default templates directory: docs/templates/ in a source checkout, falling
6
+ * back to the shipped catalog/docs/templates/ in the published package (same
7
+ * strategy as resolveRepoBlueprintTemplatePath in router.ts).
7
8
  */
8
9
  function defaultTemplatesDir() {
9
- return path.dirname(resolvePackageAsset('docs/templates/blueprint.md'));
10
+ return path.dirname(resolvePackageAssetPreferred([
11
+ 'docs/templates/blueprint.md',
12
+ 'catalog/docs/templates/blueprint.md',
13
+ ]));
10
14
  }
11
15
  /**
12
16
  * List available templates from `templatesDir` (defaults to docs/templates/).
@@ -0,0 +1,8 @@
1
+ import type { CAC } from 'cac';
2
+ declare const HOOK_NAMES: readonly ["pretool-guard", "post-tool", "stop-qa", "guard-switch", "sessionstart-routing"];
3
+ export type HookName = (typeof HOOK_NAMES)[number];
4
+ export declare function isHookName(value: string): value is HookName;
5
+ export declare function runHookCommand(name: string): Promise<void>;
6
+ export declare function registerHookCommand(cli: CAC): void;
7
+ export {};
8
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1,47 @@
1
+ const HOOK_NAMES = [
2
+ 'pretool-guard',
3
+ 'post-tool',
4
+ 'stop-qa',
5
+ 'guard-switch',
6
+ 'sessionstart-routing',
7
+ ];
8
+ const HOOK_HANDLERS = {
9
+ 'pretool-guard': async () => {
10
+ const { main } = await import('#hooks/pretool-guard/index');
11
+ await main();
12
+ },
13
+ 'post-tool': async () => {
14
+ const { main } = await import('#hooks/post-tool/lint-after-edit');
15
+ await main();
16
+ },
17
+ 'stop-qa': async () => {
18
+ const { main } = await import('#hooks/stop/qa-changed-files');
19
+ await main();
20
+ },
21
+ 'guard-switch': async () => {
22
+ const { main } = await import('#hooks/guard-switch/index');
23
+ await main();
24
+ },
25
+ 'sessionstart-routing': async () => {
26
+ const { main } = await import('#hooks/sessionstart/index');
27
+ await main();
28
+ },
29
+ };
30
+ export function isHookName(value) {
31
+ return value in HOOK_HANDLERS;
32
+ }
33
+ export async function runHookCommand(name) {
34
+ if (!isHookName(name)) {
35
+ throw new Error(`Unknown hook "${name}". Expected one of: ${HOOK_NAMES.join(', ')}`);
36
+ }
37
+ await HOOK_HANDLERS[name]();
38
+ }
39
+ export function registerHookCommand(cli) {
40
+ cli
41
+ .command('hook <name>', 'Run an internal plugin hook entrypoint')
42
+ .action(async (name) => {
43
+ await runHookCommand(name);
44
+ return 0;
45
+ });
46
+ }
47
+ //# sourceMappingURL=hook.js.map
@@ -5,6 +5,8 @@ export const AGENT_HOSTS = ['codex', 'claude', 'opencode'];
5
5
  export const REQUIRED_CORE_CAPABILITIES = ['verify', 'plan-refine'];
6
6
  export const VISIBILITY_STATUSES = ['visible-now', 'visible-after-restart', 'not-visible'];
7
7
  export function parseAgentHosts(value) {
8
+ if (value?.trim() === 'none')
9
+ return [];
8
10
  if (!value || value.trim().length === 0 || value.trim() === 'all')
9
11
  return [...AGENT_HOSTS];
10
12
  const out = [];
@@ -19,7 +21,7 @@ export function parseAgentHosts(value) {
19
21
  unknown.push(token);
20
22
  }
21
23
  if (unknown.length > 0) {
22
- throw new Error(`Unknown host(s): ${unknown.join(', ')}. Expected one of: ${AGENT_HOSTS.join(', ')}, all.`);
24
+ throw new Error(`Unknown host(s): ${unknown.join(', ')}. Expected one of: ${AGENT_HOSTS.join(', ')}, all, none.`);
23
25
  }
24
26
  return [...new Set(out)];
25
27
  }
@@ -54,7 +56,7 @@ export function hostSkillRoots(repoRoot, host, homeDir = homedir()) {
54
56
  }
55
57
  }
56
58
  export function auditHostSkillVisibility(input) {
57
- const selectedHosts = input.hosts && input.hosts.length > 0 ? [...input.hosts] : [...AGENT_HOSTS];
59
+ const selectedHosts = input.hosts ? [...input.hosts] : [...AGENT_HOSTS];
58
60
  const requiredCapabilities = input.requiredCapabilities && input.requiredCapabilities.length > 0
59
61
  ? [...input.requiredCapabilities]
60
62
  : [...REQUIRED_CORE_CAPABILITIES];
@@ -6,7 +6,7 @@
6
6
  * config keys, and generated surfaces it owns while leaving consumer-owned
7
7
  * divergent files untouched unless `--overwrite` is passed.
8
8
  */
9
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
10
  import { basename, dirname, join, relative } from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
12
  import { isTelemetryEnabled, reportTthw } from '#telemetry/setup-tthw';
@@ -26,10 +26,11 @@ import { GENERATED_PATHS_BLOCK, patchGitignore } 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';
29
- import { scaffoldBaseKit } from './scaffold-base-kit.js';
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';
32
32
  import { scaffoldAgentHooks, trustCodexWebpressoHooksForRepo, trustCodexPresetHooksForUser, } from './scaffolders/agent-hooks/index.js';
33
+ import { ensureAgentKitGlobal } from './scaffolders/agent-kit-global/index.js';
33
34
  import { scaffoldAuditHooks } from './scaffolders/audit-hooks/index.js';
34
35
  import { ensureClaudeCodeUserPlugin } from './scaffolders/claude-plugin/index.js';
35
36
  import { scaffoldClaudeRules } from './scaffolders/claude-rules/index.js';
@@ -103,6 +104,31 @@ function inferBlueprintsDirOverride(repoRoot, existingConfig) {
103
104
  }
104
105
  return relativePath;
105
106
  }
107
+ function readPackageJsonSafe(repoRoot) {
108
+ const packageJsonPath = join(repoRoot, 'package.json');
109
+ if (!existsSync(packageJsonPath)) {
110
+ return null;
111
+ }
112
+ try {
113
+ return JSON.parse(readFileSync(packageJsonPath, 'utf8'));
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ function printRuntimeContractGuidance(packageJson) {
120
+ const guidance = collectRuntimeContractGuidance(packageJson);
121
+ console.log('\nRuntime-owned tooling contract:');
122
+ console.log(' wp now owns execution for test, e2e, lint, format, and typecheck.');
123
+ console.log(' Keep local dependencies that your tests, configs, or tsconfig types import directly.');
124
+ if (guidance.keepLocalAuthoringDeps.length > 0) {
125
+ console.log(` Keep local authoring deps when imported directly: ${guidance.keepLocalAuthoringDeps.join(', ')}`);
126
+ }
127
+ if (guidance.reviewForRemovalDeps.length > 0) {
128
+ console.log(` Review execution-only deps for removal if they only powered local binaries: ${guidance.reviewForRemovalDeps.join(', ')}`);
129
+ }
130
+ console.log(' Do not blanket-remove devDependencies just because wp can execute the tool.');
131
+ }
106
132
  export async function runInit(flags) {
107
133
  const startMs = Date.now();
108
134
  const cwd = flags.cwd ?? process.cwd();
@@ -470,6 +496,37 @@ export async function runInit(flags) {
470
496
  console.log(` codex webpresso mcp: ⚠ no install root found (checked ${webpressoMcpResult.checked.length} paths). Install webpresso globally (\`bun add -g webpresso\`) or via the Claude plugin to wire up codex MCP.`);
471
497
  break;
472
498
  }
499
+ // Self-update the ONE globally-distributed agent-kit binary (PATH `wp`,
500
+ // plugin MCP, hooks all resolve to it), mirroring omx/omc/codex/claude.
501
+ // Non-fatal: a failed refresh never fails consumer setup, and it skips
502
+ // cleanly on a source/git clone, on `WP_SKIP_AUTO_INSTALL=1`, and in CI.
503
+ if (isCiEnvironment) {
504
+ console.log(' agent-kit global: - skipped (CI environment)');
505
+ }
506
+ else {
507
+ const agentKitGlobalResult = ensureAgentKitGlobal({ options });
508
+ switch (agentKitGlobalResult.kind) {
509
+ case 'agent-kit-global-updated':
510
+ console.log(' agent-kit global: ✓ refreshed via vp install -g');
511
+ break;
512
+ case 'agent-kit-global-skipped-dry-run':
513
+ console.log(' agent-kit global: skipped (--dry-run)');
514
+ break;
515
+ case 'agent-kit-global-skipped-opt-out':
516
+ console.log(' agent-kit global: skipped (WP_SKIP_AUTO_INSTALL=1)');
517
+ break;
518
+ case 'agent-kit-global-skipped-source-clone':
519
+ console.log(` agent-kit global: - skipped (running from source clone ${agentKitGlobalResult.repoRoot})`);
520
+ break;
521
+ case 'agent-kit-global-skipped-no-vp':
522
+ console.warn(` agent-kit global: ⚠ ${agentKitGlobalResult.hint}`);
523
+ break;
524
+ case 'agent-kit-global-failed':
525
+ console.warn(` agent-kit global: ⚠ \`${agentKitGlobalResult.command.join(' ')}\` exited with ${agentKitGlobalResult.exitCode}; ` +
526
+ 'the existing global binary is unchanged. Re-run `wp setup` once the registry is reachable.');
527
+ break;
528
+ }
529
+ }
473
530
  const claudePluginResult = ensureClaudeCodeUserPlugin({
474
531
  options,
475
532
  packageRoot,
@@ -612,6 +669,14 @@ export async function runInit(flags) {
612
669
  console.log(` drifted: ${summary.drifted}`);
613
670
  if (options.dryRun)
614
671
  console.log(` would-change: ${summary['skipped-dry']}`);
672
+ if (tier3Selection.includes('base-kit')) {
673
+ const qualityTargets = new Set(BASE_KIT_QUALITY_TARGETS);
674
+ const qualityResults = baseKitResults.filter((result) => qualityTargets.has(relative(consumer.repoRoot, result.targetPath).replaceAll('\\', '/')));
675
+ const qualityCreated = qualityResults.filter((result) => result.action === 'created').length;
676
+ const qualityPreserved = qualityResults.filter((result) => result.action === 'identical').length;
677
+ const qualityDryRun = qualityResults.filter((result) => result.action === 'skipped-dry').length;
678
+ console.log(` repo quality scaffold: ${options.dryRun ? `${qualityDryRun} would be created` : `${qualityCreated} created, ${qualityPreserved} preserved`}`);
679
+ }
615
680
  if (summary.drifted > 0) {
616
681
  console.log('\n Note: some consumer-owned files exist with different content and were left unchanged.\n' +
617
682
  ' Review the drift or re-run with `--overwrite` to force eligible managed files.');
@@ -629,8 +694,13 @@ export async function runInit(flags) {
629
694
  };
630
695
  writeConfig(consumer.repoRoot, config);
631
696
  console.log('\nHost skill visibility:');
632
- for (const line of summarizeHostVisibility(consumer.repoRoot, visibilityAudit)) {
633
- console.log(line);
697
+ if (visibilityAudit.selectedHosts.length === 0) {
698
+ console.log(' hosts: - skipped (--host none)');
699
+ }
700
+ else {
701
+ for (const line of summarizeHostVisibility(consumer.repoRoot, visibilityAudit)) {
702
+ console.log(line);
703
+ }
634
704
  }
635
705
  const missing = visibilityAudit.results.filter((result) => result.status === 'not-visible');
636
706
  if (missing.length > 0) {
@@ -659,7 +729,10 @@ export async function runInit(flags) {
659
729
  }
660
730
  }
661
731
  }
662
- console.log('\nwp init: done.');
732
+ printRuntimeContractGuidance(options.dryRun
733
+ ? consumer.packageJson
734
+ : (readPackageJsonSafe(consumer.repoRoot) ?? consumer.packageJson));
735
+ console.log('\nwp init: setup phases finished.');
663
736
  if (omxFailure === 'not-found')
664
737
  return EXIT_SETUP_FAIL;
665
738
  if (omxFailure === 'spawn-failed')
@@ -698,7 +771,7 @@ export async function runInit(flags) {
698
771
  if (!options.dryRun) {
699
772
  console.log([
700
773
  '',
701
- '✅ Setup complete.',
774
+ '✅ Setup complete for the verified phases above.',
702
775
  '',
703
776
  ' Next: wp blueprint new "your first task"',
704
777
  ' wp gain # token savings after your first session',
@@ -730,7 +803,7 @@ export function registerInitCommand(cli, commandName = 'init') {
730
803
  .command(commandName, description)
731
804
  .option('--with <skills>', withHelp)
732
805
  .option('--without <skills>', withoutHelp)
733
- .option('--host <hosts>', 'Comma-separated host targets: codex, claude, opencode, all')
806
+ .option('--host <hosts>', 'Comma-separated host targets: codex, claude, opencode, all, none')
734
807
  .option('--all', 'Install every skill (Tier-1 + Tier-2 + all Tier-3)')
735
808
  .option('--overwrite', 'Force full-file replacement for eligible managed files (default: reconcile owned content and preserve divergent consumer files)')
736
809
  .option('--dry-run', 'Show what would change without writing anything')
@@ -5,5 +5,17 @@ export interface ScaffoldBaseKitInput {
5
5
  options: MergeOptions;
6
6
  globalInstall?: boolean;
7
7
  }
8
+ export interface RuntimeContractGuidance {
9
+ keepLocalAuthoringDeps: string[];
10
+ reviewForRemovalDeps: string[];
11
+ }
12
+ interface PackageJsonLike {
13
+ dependencies?: Record<string, string>;
14
+ devDependencies?: Record<string, string>;
15
+ [key: string]: unknown;
16
+ }
17
+ export declare function collectRuntimeContractGuidance(packageJson: PackageJsonLike | null | undefined): RuntimeContractGuidance;
18
+ export declare const BASE_KIT_QUALITY_TARGETS: string[];
8
19
  export declare function scaffoldBaseKit(input: ScaffoldBaseKitInput): MergeResult[];
20
+ export {};
9
21
  //# sourceMappingURL=scaffold-base-kit.d.ts.map
@@ -1,6 +1,30 @@
1
1
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { writeFileMerged } from './merge.js';
4
+ const AUTHORING_TIME_DEPENDENCIES = [
5
+ 'vitest',
6
+ '@playwright/test',
7
+ '@testing-library/jest-dom',
8
+ 'typescript',
9
+ ];
10
+ const EXECUTION_ONLY_REVIEW_DEPENDENCIES = [
11
+ 'oxlint',
12
+ 'oxfmt',
13
+ 'prettier',
14
+ 'markdownlint-cli2',
15
+ 'stryker',
16
+ ];
17
+ export function collectRuntimeContractGuidance(packageJson) {
18
+ const deps = {
19
+ ...readDependencyBucket(packageJson?.['dependencies']),
20
+ ...readDependencyBucket(packageJson?.['devDependencies']),
21
+ };
22
+ const installed = new Set(Object.keys(deps));
23
+ return {
24
+ keepLocalAuthoringDeps: AUTHORING_TIME_DEPENDENCIES.filter((name) => installed.has(name)),
25
+ reviewForRemovalDeps: EXECUTION_ONLY_REVIEW_DEPENDENCIES.filter((name) => installed.has(name)),
26
+ };
27
+ }
4
28
  /** Template files relative to `catalog/base-kit/`, and their target paths relative to repoRoot. */
5
29
  const TEMPLATE_MAP = [
6
30
  ['.editorconfig.tmpl', '.editorconfig'],
@@ -18,6 +42,19 @@ const TEMPLATE_MAP = [
18
42
  ['test/.gitkeep.tmpl', 'test/.gitkeep'],
19
43
  ['e2e/.gitkeep.tmpl', 'e2e/.gitkeep'],
20
44
  ];
45
+ /** Consumer-owned quality scaffold: create for fresh repos, never clobber. */
46
+ const QUALITY_BOOTSTRAP_ONLY_MAP = [
47
+ ['tsconfig.json.tmpl', 'tsconfig.json'],
48
+ ['vitest.config.ts.tmpl', 'vitest.config.ts'],
49
+ ['oxlint.config.ts.tmpl', 'oxlint.config.ts'],
50
+ ['stryker.config.ts.tmpl', 'stryker.config.ts'],
51
+ ['playwright.config.ts.tmpl', 'playwright.config.ts'],
52
+ ['src/quality-sample.ts.tmpl', 'src/quality-sample.ts'],
53
+ ['src/quality-sample.test.ts.tmpl', 'src/quality-sample.test.ts'],
54
+ ['e2e/fixtures/smoke.html.tmpl', 'e2e/fixtures/smoke.html'],
55
+ ['e2e/smoke.spec.ts.tmpl', 'e2e/smoke.spec.ts'],
56
+ ];
57
+ export const BASE_KIT_QUALITY_TARGETS = QUALITY_BOOTSTRAP_ONLY_MAP.map(([, targetRel]) => targetRel);
21
58
  /**
22
59
  * Bootstrap-only templates: the scaffolder writes them when absent (so a
23
60
  * fresh repo gets sane defaults) but NEVER overwrites them once they exist
@@ -75,35 +112,85 @@ function mergePackageJson(repoRoot, options, globalInstall = false) {
75
112
  const hasVerifySecrets = typeof scripts['verify:secrets'] === 'string';
76
113
  const hasSecretQuarantineAudit = typeof scripts['audit:secret-provider-quarantine'] === 'string';
77
114
  const hasPrepareScript = typeof scripts['prepare'] === 'string';
78
- const verifyPathsScript = 'WP_SKIP_UPDATE_CHECK=1 wp audit absolute-path-policy --root .';
115
+ const hasLintScript = typeof scripts['lint'] === 'string';
116
+ const hasTypecheckScript = typeof scripts['typecheck'] === 'string';
117
+ const hasTestScript = typeof scripts['test'] === 'string' && !isNpmInitPlaceholderTestScript(scripts['test']);
118
+ const hasMutationScript = typeof scripts['mutation'] === 'string';
119
+ const hasTestMutationScript = typeof scripts['test:mutation'] === 'string';
120
+ const hasE2eScript = typeof scripts['e2e'] === 'string';
121
+ const hasQaScript = typeof scripts['qa'] === 'string';
122
+ const verifyPathsScript = 'wp audit absolute-path-policy --root .';
79
123
  const verifySecretsScript = 'bun scripts/check-no-dev-vars.ts';
80
124
  const secretQuarantineAuditScript = 'bun scripts/audit-secret-provider-quarantine.ts';
125
+ const lintScript = 'wp lint src e2e *.config.ts';
126
+ const typecheckScript = 'wp typecheck';
127
+ const testScript = 'wp test --file vitest.config.ts';
128
+ const mutationScript = 'wp test --mutation';
129
+ const testMutationScript = 'stryker run stryker.config.ts';
130
+ const e2eScript = 'wp e2e --config playwright.config.ts';
131
+ const qaScript = [
132
+ 'wp lint src e2e *.config.ts',
133
+ 'wp typecheck',
134
+ 'wp test --file vitest.config.ts',
135
+ 'wp test --mutation',
136
+ 'wp e2e --config playwright.config.ts',
137
+ ].join(' && ');
81
138
  const devDeps = (pkg['devDependencies'] ?? {});
82
- const hasAgentKitDevDep = typeof devDeps['webpresso'] === 'string';
83
- const shouldSkipSelfInstall = packageName === 'webpresso';
139
+ const hasAgentKitDevDep = typeof devDeps['@webpresso/agent-kit'] === 'string';
140
+ const hasLegacyAgentKitDevDep = typeof devDeps['webpresso'] === 'string';
141
+ const shouldSkipSelfInstall = packageName === '@webpresso/agent-kit' || packageName === 'webpresso';
84
142
  const shouldManageAgentKitAsGlobal = globalInstall && !shouldSkipSelfInstall;
143
+ const requiredAuthoringDeps = {
144
+ '@playwright/test': 'latest',
145
+ '@stryker-mutator/core': 'latest',
146
+ '@stryker-mutator/vitest-runner': 'latest',
147
+ '@types/node': 'latest',
148
+ typescript: 'latest',
149
+ vitest: 'latest',
150
+ };
85
151
  if (alreadyHasEngines &&
86
152
  alreadyHasPm &&
87
- (shouldSkipSelfInstall || shouldManageAgentKitAsGlobal || hasAgentKitDevDep) &&
153
+ (shouldSkipSelfInstall ||
154
+ shouldManageAgentKitAsGlobal ||
155
+ hasAgentKitDevDep ||
156
+ hasLegacyAgentKitDevDep) &&
157
+ Object.keys(requiredAuthoringDeps).every((name) => typeof devDeps[name] === 'string') &&
88
158
  (shouldSkipSelfInstall || hasSetupAgent) &&
89
159
  (shouldSkipSelfInstall || hasVerifyPaths) &&
90
160
  (shouldSkipSelfInstall || hasVerifySecrets) &&
91
161
  (shouldSkipSelfInstall || hasSecretQuarantineAudit) &&
92
- (shouldSkipSelfInstall || hasPrepareScript)) {
162
+ (shouldSkipSelfInstall || hasPrepareScript) &&
163
+ hasLintScript &&
164
+ hasTypecheckScript &&
165
+ hasTestScript &&
166
+ hasMutationScript &&
167
+ hasTestMutationScript &&
168
+ hasE2eScript &&
169
+ hasQaScript) {
93
170
  return { targetPath: pkgPath, action: 'identical' };
94
171
  }
95
172
  pkg['engines'] = { ...existing, node: engines.node };
96
173
  if (!alreadyHasPm)
97
174
  pkg['packageManager'] = packageManager;
175
+ if (typeof pkg['type'] !== 'string')
176
+ pkg['type'] = 'module';
98
177
  // Ensure husky is in devDependencies so `vp exec husky init` works
99
178
  if (!devDeps['husky']) {
100
179
  devDeps['husky'] = '^9.0.0';
101
180
  }
102
- if (!shouldSkipSelfInstall && !shouldManageAgentKitAsGlobal && !hasAgentKitDevDep) {
181
+ if (!shouldSkipSelfInstall &&
182
+ !shouldManageAgentKitAsGlobal &&
183
+ !hasAgentKitDevDep &&
184
+ !hasLegacyAgentKitDevDep) {
103
185
  // Keep consumers on the currently published dist-tag rather than a
104
186
  // repo-internal path. Do not wire this through `prepare`: `wp` is not
105
187
  // reliably on PATH during `vp install`, so `setup:agent` stays opt-in.
106
- devDeps['webpresso'] = 'latest';
188
+ devDeps['@webpresso/agent-kit'] = 'latest';
189
+ }
190
+ for (const [name, version] of Object.entries(requiredAuthoringDeps)) {
191
+ if (!devDeps[name]) {
192
+ devDeps[name] = version;
193
+ }
107
194
  }
108
195
  pkg['devDependencies'] = devDeps;
109
196
  if (!shouldSkipSelfInstall && !hasSetupAgent) {
@@ -121,6 +208,27 @@ function mergePackageJson(repoRoot, options, globalInstall = false) {
121
208
  if (!shouldSkipSelfInstall && !hasPrepareScript) {
122
209
  scripts['prepare'] = 'husky';
123
210
  }
211
+ if (!hasLintScript) {
212
+ scripts['lint'] = lintScript;
213
+ }
214
+ if (!hasTypecheckScript) {
215
+ scripts['typecheck'] = typecheckScript;
216
+ }
217
+ if (!hasTestScript) {
218
+ scripts['test'] = testScript;
219
+ }
220
+ if (!hasMutationScript) {
221
+ scripts['mutation'] = mutationScript;
222
+ }
223
+ if (!hasTestMutationScript) {
224
+ scripts['test:mutation'] = testMutationScript;
225
+ }
226
+ if (!hasE2eScript) {
227
+ scripts['e2e'] = e2eScript;
228
+ }
229
+ if (!hasQaScript) {
230
+ scripts['qa'] = qaScript;
231
+ }
124
232
  if (Object.keys(scripts).length > 0) {
125
233
  pkg['scripts'] = scripts;
126
234
  }
@@ -161,6 +269,24 @@ export function scaffoldBaseKit(input) {
161
269
  writeFileSync(targetPath, content);
162
270
  results.push({ targetPath, action: 'created' });
163
271
  }
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;
285
+ }
286
+ mkdirSync(dirname(targetPath), { recursive: true });
287
+ writeFileSync(targetPath, content);
288
+ results.push({ targetPath, action: 'created' });
289
+ }
164
290
  // Make husky hook files executable
165
291
  if (!options.dryRun) {
166
292
  for (const [tmplRel, targetRel] of TEMPLATE_MAP) {
@@ -180,4 +306,13 @@ export function scaffoldBaseKit(input) {
180
306
  results.push(mergePackageJson(repoRoot, options, globalInstall));
181
307
  return results;
182
308
  }
309
+ function readDependencyBucket(value) {
310
+ if (!value || typeof value !== 'object') {
311
+ return {};
312
+ }
313
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[0] === 'string' && typeof entry[1] === 'string'));
314
+ }
315
+ function isNpmInitPlaceholderTestScript(value) {
316
+ return /^echo ['"]?Error: no test specified['"]? && exit 1$/u.test(value.trim());
317
+ }
183
318
  //# sourceMappingURL=scaffold-base-kit.js.map
@@ -10,6 +10,8 @@ export const KNOWN_WEBPRESSO_CODEX_BINS = [
10
10
  const KNOWN_WEBPRESSO_CODEX_BIN_SET = new Set(KNOWN_WEBPRESSO_CODEX_BINS);
11
11
  const NODE_MODULES_BIN_PATTERN = /^(?:\.\/|\/.*\/)?node_modules\/\.bin\/([\w-]+)$/u;
12
12
  const GUARDED_NODE_MODULES_BIN_PATTERN = /^\[ -x (["']?)((?:\.\/|\/.*\/)?node_modules\/\.bin\/([\w-]+))\1 \] && \1\2\1 \|\| (?:true|printf .+)$/u;
13
+ const MANAGED_LAUNCHER_PATTERN = /^(?:["']?)((?:\.\/|\/.*\/)?\.codex\/managed-hooks\/((?:wp|ak)-[\w-]+)\.sh)(?:["']?)$/u;
14
+ const GUARDED_MANAGED_LAUNCHER_PATTERN = /^\[ -x (["']?)((?:\.\/|\/.*\/)?\.codex\/managed-hooks\/((?:wp|ak)-[\w-]+)\.sh)\1 \] && \1\2\1 \|\| (?:true|printf .+)$/u;
13
15
  export function isWebpressoOwnedCodexHook(metadata, expectedSourcePaths) {
14
16
  if (!isObject(metadata))
15
17
  return false;
@@ -46,8 +48,14 @@ function extractDirectNodeModulesBin(command) {
46
48
  const match = NODE_MODULES_BIN_PATTERN.exec(normalizedCommand);
47
49
  if (match?.[1])
48
50
  return match[1];
51
+ const managedLauncherMatch = MANAGED_LAUNCHER_PATTERN.exec(normalizedCommand);
52
+ if (managedLauncherMatch?.[2])
53
+ return managedLauncherMatch[2];
49
54
  const guardedMatch = GUARDED_NODE_MODULES_BIN_PATTERN.exec(command.trim());
50
- return guardedMatch?.[3] ?? null;
55
+ if (guardedMatch?.[3])
56
+ return guardedMatch[3];
57
+ const guardedManagedLauncherMatch = GUARDED_MANAGED_LAUNCHER_PATTERN.exec(command.trim());
58
+ return guardedManagedLauncherMatch?.[3] ?? null;
51
59
  }
52
60
  function stripSingleShellQuotePair(value) {
53
61
  if (value.length < 2)