@webpresso/agent-kit 0.21.4 → 0.21.5

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 (94) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +105 -41
  3. package/catalog/agent/skills/plan-refine/SKILL.md +5 -4
  4. package/catalog/base-kit/commitlint.config.ts.tmpl +1 -3
  5. package/catalog/base-kit/e2e/fixtures/smoke.html.tmpl +13 -0
  6. package/catalog/base-kit/e2e/smoke.spec.ts.tmpl +13 -0
  7. package/catalog/base-kit/oxlint.config.ts.tmpl +26 -0
  8. package/catalog/base-kit/playwright.config.ts.tmpl +10 -0
  9. package/catalog/base-kit/src/quality-sample.test.ts.tmpl +19 -0
  10. package/catalog/base-kit/src/quality-sample.ts.tmpl +11 -0
  11. package/catalog/base-kit/stryker.config.ts.tmpl +14 -0
  12. package/catalog/base-kit/tsconfig.json.tmpl +9 -0
  13. package/catalog/base-kit/vitest.config.ts.tmpl +10 -0
  14. package/catalog/docs/templates/adr.md +1 -1
  15. package/catalog/docs/templates/blueprint.md +1 -0
  16. package/catalog/docs/templates/blueprint.yaml +6 -3
  17. package/catalog/docs/templates/guide.md +1 -1
  18. package/catalog/docs/templates/postmortem.md +1 -1
  19. package/catalog/docs/templates/research.md +1 -1
  20. package/catalog/docs/templates/runbook.md +1 -1
  21. package/catalog/docs/templates/system.md +12 -3
  22. package/catalog/docs/templates/tech-debt.md +1 -0
  23. package/commands/blueprint.md +37 -4
  24. package/dist/esm/audit/resolve-audit-script.d.ts +24 -0
  25. package/dist/esm/audit/resolve-audit-script.js +27 -0
  26. package/dist/esm/blueprint/index.d.ts +0 -1
  27. package/dist/esm/blueprint/index.js +0 -2
  28. package/dist/esm/blueprint/local.d.ts +0 -3
  29. package/dist/esm/blueprint/local.js +0 -2
  30. package/dist/esm/blueprint/service/BlueprintCreationService.js +5 -2
  31. package/dist/esm/blueprint/utils/package-assets.d.ts +11 -0
  32. package/dist/esm/blueprint/utils/package-assets.js +33 -4
  33. package/dist/esm/build/sync-catalog-doc-templates.d.ts +23 -0
  34. package/dist/esm/build/sync-catalog-doc-templates.js +93 -0
  35. package/dist/esm/cli/commands/audit.js +2 -7
  36. package/dist/esm/cli/commands/blueprint/router.js +5 -2
  37. package/dist/esm/cli/commands/blueprint/template-resolver.js +8 -4
  38. package/dist/esm/cli/commands/init/host-visibility.js +4 -2
  39. package/dist/esm/cli/commands/init/index.js +46 -7
  40. package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +12 -0
  41. package/dist/esm/cli/commands/init/scaffold-base-kit.js +141 -6
  42. package/dist/esm/cli/commands/typecheck.js +10 -4
  43. package/dist/esm/e2e/command-builder.js +26 -7
  44. package/dist/esm/e2e/execution.js +4 -0
  45. package/dist/esm/e2e/run-planner.js +1 -0
  46. package/dist/esm/e2e/types.d.ts +1 -0
  47. package/dist/esm/format/index.js +7 -1
  48. package/dist/esm/lint/index.js +3 -1
  49. package/dist/esm/mcp/blueprint-server.js +361 -66
  50. package/dist/esm/mcp/tools/audit.js +2 -8
  51. package/dist/esm/mcp/tools/e2e.d.ts +1 -1
  52. package/dist/esm/package.json +3 -0
  53. package/dist/esm/test/command-builder.d.ts +1 -0
  54. package/dist/esm/test/command-builder.js +8 -2
  55. package/dist/esm/test-helpers/hermetic-env.d.ts +25 -0
  56. package/dist/esm/test-helpers/hermetic-env.js +31 -0
  57. package/dist/esm/tool-runtime/index.d.ts +5 -0
  58. package/dist/esm/tool-runtime/index.js +23 -0
  59. package/dist/esm/tool-runtime/resolve-runner.d.ts +13 -0
  60. package/dist/esm/tool-runtime/resolve-runner.js +40 -0
  61. package/package.json +12 -19
  62. package/skills/plan-refine/SKILL.md +5 -4
  63. package/dist/esm/blueprint/dag/cycle-detector.d.ts +0 -12
  64. package/dist/esm/blueprint/dag/cycle-detector.js +0 -46
  65. package/dist/esm/blueprint/dag/executor.d.ts +0 -140
  66. package/dist/esm/blueprint/dag/executor.js +0 -292
  67. package/dist/esm/blueprint/dag/index.d.ts +0 -20
  68. package/dist/esm/blueprint/dag/index.js +0 -17
  69. package/dist/esm/blueprint/dag/interfaces.d.ts +0 -56
  70. package/dist/esm/blueprint/dag/interfaces.js +0 -13
  71. package/dist/esm/blueprint/dag/local/independence.d.ts +0 -107
  72. package/dist/esm/blueprint/dag/local/independence.js +0 -231
  73. package/dist/esm/blueprint/dag/local/index.d.ts +0 -14
  74. package/dist/esm/blueprint/dag/local/index.js +0 -14
  75. package/dist/esm/blueprint/dag/local/package-graph.d.ts +0 -66
  76. package/dist/esm/blueprint/dag/local/package-graph.js +0 -148
  77. package/dist/esm/blueprint/dag/plan-parser.d.ts +0 -54
  78. package/dist/esm/blueprint/dag/plan-parser.js +0 -236
  79. package/dist/esm/blueprint/dag/task-graph-algorithms.d.ts +0 -13
  80. package/dist/esm/blueprint/dag/task-graph-algorithms.js +0 -236
  81. package/dist/esm/blueprint/dag/task-graph.d.ts +0 -171
  82. package/dist/esm/blueprint/dag/task-graph.js +0 -370
  83. package/dist/esm/blueprint/dag/types.d.ts +0 -17
  84. package/dist/esm/blueprint/dag/types.js +0 -2
  85. package/dist/esm/blueprint/graph/index.d.ts +0 -5
  86. package/dist/esm/blueprint/graph/index.js +0 -5
  87. package/dist/esm/blueprint/graph/mermaid-parser.d.ts +0 -3
  88. package/dist/esm/blueprint/graph/mermaid-parser.js +0 -93
  89. package/dist/esm/blueprint/graph/mermaid-serializer.d.ts +0 -3
  90. package/dist/esm/blueprint/graph/mermaid-serializer.js +0 -20
  91. package/dist/esm/blueprint/graph/schema.d.ts +0 -89
  92. package/dist/esm/blueprint/graph/schema.js +0 -104
  93. package/dist/esm/blueprint/graph/task-graph-adapter.d.ts +0 -6
  94. package/dist/esm/blueprint/graph/task-graph-adapter.js +0 -30
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { parseBlueprint } from '#core/parser';
5
5
  import { scanBlueprintDirectory } from '#service/scanner';
6
6
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
7
- import { resolvePackageAsset } from '#utils/package-assets';
7
+ import { resolvePackageAssetPreferred } from '#utils/package-assets';
8
8
  const RESERVED_BLUEPRINT_SLUGS = new Set([
9
9
  'draft',
10
10
  'planned',
@@ -13,7 +13,10 @@ const RESERVED_BLUEPRINT_SLUGS = new Set([
13
13
  'completed',
14
14
  'archived',
15
15
  ]);
16
- const DEFAULT_TEMPLATE_PATH = resolvePackageAsset('docs/templates/blueprint.md');
16
+ const DEFAULT_TEMPLATE_PATH = resolvePackageAssetPreferred([
17
+ 'docs/templates/blueprint.md',
18
+ 'catalog/docs/templates/blueprint.md',
19
+ ]);
17
20
  function formatDate(date) {
18
21
  return date.toISOString().split('T')[0] ?? date.toISOString();
19
22
  }
@@ -3,4 +3,15 @@
3
3
  * package root) is found. Works whether running from src/ or dist/esm/.
4
4
  */
5
5
  export declare function resolvePackageAsset(relativeFromRoot: string): string;
6
+ /**
7
+ * Resolve the first existing candidate, in priority order. Use when an asset
8
+ * lives at one path in the source checkout but a different path in the
9
+ * published tarball — e.g. templates authored under repo-root `docs/templates/`
10
+ * but shipped under `catalog/docs/templates/` because the npm `files` list
11
+ * includes `catalog/` and not `docs/`. Prefers the dev/source location and
12
+ * falls back to the shipped one, mirroring `bin/_run.js`'s source→built path
13
+ * translation. Falls back to cwd-relative on the first candidate when none
14
+ * exist, matching `resolvePackageAsset`'s last-resort behavior.
15
+ */
16
+ export declare function resolvePackageAssetPreferred(candidates: readonly string[]): string;
6
17
  //# sourceMappingURL=package-assets.d.ts.map
@@ -1,10 +1,10 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  /**
4
- * Walk up from this file's location until the given path (relative to the
5
- * package root) is found. Works whether running from src/ or dist/esm/.
4
+ * Walk up from this file's location looking for `relativeFromRoot`. Returns the
5
+ * first existing match, or `null` if none is found within the ancestor budget.
6
6
  */
7
- export function resolvePackageAsset(relativeFromRoot) {
7
+ function findPackageAsset(relativeFromRoot) {
8
8
  let dir = path.dirname(new URL(import.meta.url).pathname);
9
9
  for (let i = 0; i < 8; i++) {
10
10
  const candidate = path.join(dir, relativeFromRoot);
@@ -15,6 +15,35 @@ export function resolvePackageAsset(relativeFromRoot) {
15
15
  break;
16
16
  dir = parent;
17
17
  }
18
- return path.join(process.cwd(), relativeFromRoot);
18
+ return null;
19
+ }
20
+ /**
21
+ * Walk up from this file's location until the given path (relative to the
22
+ * package root) is found. Works whether running from src/ or dist/esm/.
23
+ */
24
+ export function resolvePackageAsset(relativeFromRoot) {
25
+ return findPackageAsset(relativeFromRoot) ?? path.join(process.cwd(), relativeFromRoot);
26
+ }
27
+ /**
28
+ * Resolve the first existing candidate, in priority order. Use when an asset
29
+ * lives at one path in the source checkout but a different path in the
30
+ * published tarball — e.g. templates authored under repo-root `docs/templates/`
31
+ * but shipped under `catalog/docs/templates/` because the npm `files` list
32
+ * includes `catalog/` and not `docs/`. Prefers the dev/source location and
33
+ * falls back to the shipped one, mirroring `bin/_run.js`'s source→built path
34
+ * translation. Falls back to cwd-relative on the first candidate when none
35
+ * exist, matching `resolvePackageAsset`'s last-resort behavior.
36
+ */
37
+ export function resolvePackageAssetPreferred(candidates) {
38
+ for (const relativeFromRoot of candidates) {
39
+ const found = findPackageAsset(relativeFromRoot);
40
+ if (found)
41
+ return found;
42
+ }
43
+ const primary = candidates[0];
44
+ if (primary === undefined) {
45
+ throw new Error('resolvePackageAssetPreferred requires at least one candidate path');
46
+ }
47
+ return path.join(process.cwd(), primary);
19
48
  }
20
49
  //# sourceMappingURL=package-assets.js.map
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * sync-catalog-doc-templates.ts
4
+ *
5
+ * `docs/templates/` (repo root) is the editable source of truth for the
6
+ * blueprint / doc templates. `catalog/docs/templates/` is the published +
7
+ * scaffolded mirror: the npm `files` list ships `catalog/` but NOT repo-root
8
+ * `docs/` (which holds internal docs that must stay private), and `wp init`
9
+ * scaffolds templates into a consumer from `catalog/docs/templates/`.
10
+ *
11
+ * The two MUST stay byte-identical. Nothing synced them before, so they
12
+ * drifted — this script regenerates the mirror from the source and is wired
13
+ * into the build. The colocated drift test fails CI if they ever diverge.
14
+ *
15
+ * Default: regenerate the mirror from the source.
16
+ * `--check`: exit non-zero and list drift without writing (CI / pre-publish).
17
+ */
18
+ /**
19
+ * Names that are out of sync between source and mirror: content mismatch,
20
+ * missing from the mirror, or orphaned in the mirror.
21
+ */
22
+ export declare function diffDocTemplateMirror(): string[];
23
+ //# sourceMappingURL=sync-catalog-doc-templates.d.ts.map
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * sync-catalog-doc-templates.ts
4
+ *
5
+ * `docs/templates/` (repo root) is the editable source of truth for the
6
+ * blueprint / doc templates. `catalog/docs/templates/` is the published +
7
+ * scaffolded mirror: the npm `files` list ships `catalog/` but NOT repo-root
8
+ * `docs/` (which holds internal docs that must stay private), and `wp init`
9
+ * scaffolds templates into a consumer from `catalog/docs/templates/`.
10
+ *
11
+ * The two MUST stay byte-identical. Nothing synced them before, so they
12
+ * drifted — this script regenerates the mirror from the source and is wired
13
+ * into the build. The colocated drift test fails CI if they ever diverge.
14
+ *
15
+ * Default: regenerate the mirror from the source.
16
+ * `--check`: exit non-zero and list drift without writing (CI / pre-publish).
17
+ */
18
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
+ import { argv, exit } from 'node:process';
21
+ import { fileURLToPath } from 'node:url';
22
+ const PACKAGE_ROOT = dirname(dirname(import.meta.dirname));
23
+ const SOURCE_DIR = join(PACKAGE_ROOT, 'docs', 'templates');
24
+ const MIRROR_DIR = join(PACKAGE_ROOT, 'catalog', 'docs', 'templates');
25
+ function listFiles(dir) {
26
+ if (!existsSync(dir))
27
+ return [];
28
+ return readdirSync(dir, { withFileTypes: true })
29
+ .filter((entry) => entry.isFile())
30
+ .map((entry) => entry.name)
31
+ .sort();
32
+ }
33
+ /**
34
+ * Names that are out of sync between source and mirror: content mismatch,
35
+ * missing from the mirror, or orphaned in the mirror.
36
+ */
37
+ export function diffDocTemplateMirror() {
38
+ const sourceFiles = listFiles(SOURCE_DIR);
39
+ const mirrorFiles = listFiles(MIRROR_DIR);
40
+ const names = [...new Set([...sourceFiles, ...mirrorFiles])].sort();
41
+ const drift = [];
42
+ for (const name of names) {
43
+ const inSource = sourceFiles.includes(name);
44
+ const inMirror = mirrorFiles.includes(name);
45
+ if (!inSource || !inMirror) {
46
+ drift.push(name);
47
+ continue;
48
+ }
49
+ if (readFileSync(join(SOURCE_DIR, name), 'utf8') !== readFileSync(join(MIRROR_DIR, name), 'utf8')) {
50
+ drift.push(name);
51
+ }
52
+ }
53
+ return drift;
54
+ }
55
+ /** Regenerate the mirror from the source: copy every file, drop orphans. */
56
+ function syncDocTemplateMirror() {
57
+ mkdirSync(MIRROR_DIR, { recursive: true });
58
+ const sourceFiles = listFiles(SOURCE_DIR);
59
+ for (const name of listFiles(MIRROR_DIR)) {
60
+ if (!sourceFiles.includes(name))
61
+ rmSync(join(MIRROR_DIR, name));
62
+ }
63
+ for (const name of sourceFiles) {
64
+ cpSync(join(SOURCE_DIR, name), join(MIRROR_DIR, name));
65
+ }
66
+ return sourceFiles.length;
67
+ }
68
+ function main() {
69
+ if (!existsSync(SOURCE_DIR)) {
70
+ console.error(`doc templates source not found: ${SOURCE_DIR}`);
71
+ exit(1);
72
+ }
73
+ if (argv.includes('--check')) {
74
+ const drift = diffDocTemplateMirror();
75
+ if (drift.length > 0) {
76
+ console.error([
77
+ 'catalog/docs/templates/ is out of sync with docs/templates/:',
78
+ ...drift.map((name) => ` - ${name}`),
79
+ 'Run `bun src/build/sync-catalog-doc-templates.ts` and commit the result.',
80
+ ].join('\n'));
81
+ exit(1);
82
+ }
83
+ console.log('catalog/docs/templates/ is in sync with docs/templates/');
84
+ return;
85
+ }
86
+ const count = syncDocTemplateMirror();
87
+ console.log(`Synced ${count} template(s) → catalog/docs/templates/`);
88
+ }
89
+ // Run as a script, but stay side-effect-free when imported (e.g. by the test).
90
+ if (fileURLToPath(import.meta.url) === argv[1]) {
91
+ main();
92
+ }
93
+ //# sourceMappingURL=sync-catalog-doc-templates.js.map
@@ -9,6 +9,7 @@ import { existsSync, readFileSync } from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { runAuditDispatch } from './audit-core.js';
11
11
  import { runStryker } from '#audit/run-stryker';
12
+ import { resolveAuditScriptPath } from '#audit/resolve-audit-script';
12
13
  const REPO_AUDIT_REGISTRY = {
13
14
  'catalog-drift': async (root) => (await import('#audit/repo-guardrails')).auditCatalogDrift(root),
14
15
  'package-surface': async (root) => (await import('#audit/package-surface')).auditPackageSurface(root),
@@ -119,13 +120,7 @@ const AUDIT_KINDS = [
119
120
  ];
120
121
  const AUDIT_KIND_LIST = AUDIT_KINDS.join(', ');
121
122
  function resolveAuditScript(name) {
122
- const fromSource = new URL(`../../audit/${name}`, import.meta.url);
123
- if (existsSync(fromSource)) {
124
- return fromSource.pathname;
125
- }
126
- const bundleDir = path.dirname(new URL(import.meta.url).pathname);
127
- const packageRoot = path.resolve(bundleDir, '..');
128
- return path.join(packageRoot, 'src', 'audit', name);
123
+ return resolveAuditScriptPath(name, { moduleUrl: import.meta.url });
129
124
  }
130
125
  async function runAuditScript(script, extraArgs) {
131
126
  const runtime = process.env.BUN_INSTALL ? 'bun' : 'bun';
@@ -6,7 +6,7 @@ import { blueprintToSpecKit } from '#export/spec-kit/index';
6
6
  import { getProjectRoot } from '#cli/utils';
7
7
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
8
8
  import { applyBlueprintLifecycleToFile, BlueprintCreationService, BlueprintService, complexitySchema, relativeBlueprintSlug, parseBlueprint, planStatusSchema, runBlueprintAudit, resolveBlueprintFile, serializeBlueprint, validateAllTasksDone, } from '#local';
9
- import { resolvePackageAsset } from '#utils/package-assets';
9
+ import { resolvePackageAssetPreferred } from '#utils/package-assets';
10
10
  import { describeBlueprintExecutionRuntime, buildBlueprintLaunchSpec, buildStoppedRuntimeEvidence, controlBlueprintExecution, initializeBlueprintExecutionProgressBridge, launchBlueprintExecution, persistBlueprintExecutionArtifacts, persistBlueprintExecutionMetadata, recordLaunchFailure, reconcileBlueprintRuntimeSnapshot, readBlueprintExecutionState, syncBlueprintExecutionProgress, writeBlueprintRuntimeSnapshot, } from './execution.js';
11
11
  import { advanceTask as advanceTaskMutation, finalizeBlueprint as finalizeBlueprintMutation, promoteBlueprint as promoteBlueprintMutation, } from './mutations.js';
12
12
  import { BlueprintAuditFailedError, executeBlueprintSubcommand } from './router-dispatch.js';
@@ -40,7 +40,10 @@ function assertBlueprintCanMoveToStatus(blueprint, nextStatus) {
40
40
  * when the lookup fails in unrelated contexts (e.g. `wp --help`).
41
41
  */
42
42
  function resolveRepoBlueprintTemplatePath() {
43
- return resolvePackageAsset('docs/templates/blueprint.md');
43
+ return resolvePackageAssetPreferred([
44
+ 'docs/templates/blueprint.md',
45
+ 'catalog/docs/templates/blueprint.md',
46
+ ]);
44
47
  }
45
48
  function todayIsoDate() {
46
49
  return new Date().toISOString().split('T')[0] ?? new Date().toISOString();
@@ -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/).
@@ -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,7 +26,7 @@ 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';
@@ -103,6 +103,31 @@ function inferBlueprintsDirOverride(repoRoot, existingConfig) {
103
103
  }
104
104
  return relativePath;
105
105
  }
106
+ function readPackageJsonSafe(repoRoot) {
107
+ const packageJsonPath = join(repoRoot, 'package.json');
108
+ if (!existsSync(packageJsonPath)) {
109
+ return null;
110
+ }
111
+ try {
112
+ return JSON.parse(readFileSync(packageJsonPath, 'utf8'));
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ function printRuntimeContractGuidance(packageJson) {
119
+ const guidance = collectRuntimeContractGuidance(packageJson);
120
+ console.log('\nRuntime-owned tooling contract:');
121
+ console.log(' wp now owns execution for test, e2e, lint, format, and typecheck.');
122
+ console.log(' Keep local dependencies that your tests, configs, or tsconfig types import directly.');
123
+ if (guidance.keepLocalAuthoringDeps.length > 0) {
124
+ console.log(` Keep local authoring deps when imported directly: ${guidance.keepLocalAuthoringDeps.join(', ')}`);
125
+ }
126
+ if (guidance.reviewForRemovalDeps.length > 0) {
127
+ console.log(` Review execution-only deps for removal if they only powered local binaries: ${guidance.reviewForRemovalDeps.join(', ')}`);
128
+ }
129
+ console.log(' Do not blanket-remove devDependencies just because wp can execute the tool.');
130
+ }
106
131
  export async function runInit(flags) {
107
132
  const startMs = Date.now();
108
133
  const cwd = flags.cwd ?? process.cwd();
@@ -612,6 +637,14 @@ export async function runInit(flags) {
612
637
  console.log(` drifted: ${summary.drifted}`);
613
638
  if (options.dryRun)
614
639
  console.log(` would-change: ${summary['skipped-dry']}`);
640
+ if (tier3Selection.includes('base-kit')) {
641
+ const qualityTargets = new Set(BASE_KIT_QUALITY_TARGETS);
642
+ const qualityResults = baseKitResults.filter((result) => qualityTargets.has(relative(consumer.repoRoot, result.targetPath).replaceAll('\\', '/')));
643
+ const qualityCreated = qualityResults.filter((result) => result.action === 'created').length;
644
+ const qualityPreserved = qualityResults.filter((result) => result.action === 'identical').length;
645
+ const qualityDryRun = qualityResults.filter((result) => result.action === 'skipped-dry').length;
646
+ console.log(` repo quality scaffold: ${options.dryRun ? `${qualityDryRun} would be created` : `${qualityCreated} created, ${qualityPreserved} preserved`}`);
647
+ }
615
648
  if (summary.drifted > 0) {
616
649
  console.log('\n Note: some consumer-owned files exist with different content and were left unchanged.\n' +
617
650
  ' Review the drift or re-run with `--overwrite` to force eligible managed files.');
@@ -629,8 +662,13 @@ export async function runInit(flags) {
629
662
  };
630
663
  writeConfig(consumer.repoRoot, config);
631
664
  console.log('\nHost skill visibility:');
632
- for (const line of summarizeHostVisibility(consumer.repoRoot, visibilityAudit)) {
633
- console.log(line);
665
+ if (visibilityAudit.selectedHosts.length === 0) {
666
+ console.log(' hosts: - skipped (--host none)');
667
+ }
668
+ else {
669
+ for (const line of summarizeHostVisibility(consumer.repoRoot, visibilityAudit)) {
670
+ console.log(line);
671
+ }
634
672
  }
635
673
  const missing = visibilityAudit.results.filter((result) => result.status === 'not-visible');
636
674
  if (missing.length > 0) {
@@ -659,7 +697,8 @@ export async function runInit(flags) {
659
697
  }
660
698
  }
661
699
  }
662
- console.log('\nwp init: done.');
700
+ printRuntimeContractGuidance(options.dryRun ? consumer.packageJson : readPackageJsonSafe(consumer.repoRoot) ?? consumer.packageJson);
701
+ console.log('\nwp init: setup phases finished.');
663
702
  if (omxFailure === 'not-found')
664
703
  return EXIT_SETUP_FAIL;
665
704
  if (omxFailure === 'spawn-failed')
@@ -698,7 +737,7 @@ export async function runInit(flags) {
698
737
  if (!options.dryRun) {
699
738
  console.log([
700
739
  '',
701
- '✅ Setup complete.',
740
+ '✅ Setup complete for the verified phases above.',
702
741
  '',
703
742
  ' Next: wp blueprint new "your first task"',
704
743
  ' wp gain # token savings after your first session',
@@ -730,7 +769,7 @@ export function registerInitCommand(cli, commandName = 'init') {
730
769
  .command(commandName, description)
731
770
  .option('--with <skills>', withHelp)
732
771
  .option('--without <skills>', withoutHelp)
733
- .option('--host <hosts>', 'Comma-separated host targets: codex, claude, opencode, all')
772
+ .option('--host <hosts>', 'Comma-separated host targets: codex, claude, opencode, all, none')
734
773
  .option('--all', 'Install every skill (Tier-1 + Tier-2 + all Tier-3)')
735
774
  .option('--overwrite', 'Force full-file replacement for eligible managed files (default: reconcile owned content and preserve divergent consumer files)')
736
775
  .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';
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';
78
122
  const verifyPathsScript = 'WP_SKIP_UPDATE_CHECK=1 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