@webpresso/agent-kit 0.26.1 → 0.26.3

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 (29) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -58
  3. package/README.md +9 -0
  4. package/catalog/AGENTS.md.tpl +3 -3
  5. package/dist/esm/audit/repo-guardrails.d.ts +17 -0
  6. package/dist/esm/audit/repo-guardrails.js +75 -0
  7. package/dist/esm/blueprint/aggregate.d.ts +1 -1
  8. package/dist/esm/blueprint/core/schema.d.ts +1 -1
  9. package/dist/esm/blueprint/db/enums.d.ts +1 -1
  10. package/dist/esm/cli/commands/audit.js +1 -0
  11. package/dist/esm/cli/commands/init/detect-consumer.d.ts +11 -0
  12. package/dist/esm/cli/commands/init/detect-consumer.js +13 -0
  13. package/dist/esm/cli/commands/init/index.d.ts +1 -0
  14. package/dist/esm/cli/commands/init/index.js +15 -1
  15. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +54 -1
  16. package/dist/esm/cli/commands/init/scaffolders/omx/index.d.ts +5 -3
  17. package/dist/esm/cli/commands/init/scaffolders/omx/index.js +10 -5
  18. package/dist/esm/config/docs-lint/schemas/agents.d.ts +2 -2
  19. package/dist/esm/config/docs-lint/schemas/audit.d.ts +1 -1
  20. package/dist/esm/config/docs-lint/schemas/common.d.ts +1 -1
  21. package/dist/esm/config/docs-lint/schemas/cookbook.d.ts +1 -1
  22. package/dist/esm/config/docs-lint/schemas/core.d.ts +4 -4
  23. package/dist/esm/config/docs-lint/schemas/evaluation.d.ts +1 -1
  24. package/dist/esm/config/docs-lint/schemas/ongoing-initiative.d.ts +2 -2
  25. package/dist/esm/config/docs-lint/schemas/rule.d.ts +1 -1
  26. package/dist/esm/hooks/doctor.d.ts +12 -0
  27. package/dist/esm/hooks/doctor.js +40 -1
  28. package/dist/esm/mcp/tools/_shared/result.d.ts +2 -2
  29. package/package.json +6 -6
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Webpresso agent-kit Claude Code plugin: blueprints, skills, hooks, MCP server",
9
- "version": "0.26.1"
9
+ "version": "0.26.3"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -23,5 +23,5 @@
23
23
  ]
24
24
  }
25
25
  ],
26
- "version": "0.26.1"
26
+ "version": "0.26.3"
27
27
  }
@@ -1,66 +1,9 @@
1
1
  {
2
2
  "name": "webpresso",
3
- "version": "0.26.1",
3
+ "version": "0.26.3",
4
4
  "description": "Webpresso agent-kit: blueprints, skills, lore commit protocol, tech-debt lifecycle",
5
5
  "skills": "./skills",
6
6
  "commands": "./commands",
7
- "hooks": {
8
- "PreToolUse": [
9
- {
10
- "matcher": "Bash|Edit|Write|MultiEdit|WebFetch|Read|Grep",
11
- "hooks": [
12
- {
13
- "type": "command",
14
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/wp-pretool-guard.js"
15
- }
16
- ]
17
- }
18
- ],
19
- "PostToolUse": [
20
- {
21
- "matcher": "Edit|Write",
22
- "hooks": [
23
- {
24
- "type": "command",
25
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/wp-post-tool.js",
26
- "timeout": 15
27
- }
28
- ]
29
- }
30
- ],
31
- "Stop": [
32
- {
33
- "hooks": [
34
- {
35
- "type": "command",
36
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/wp-stop-qa.js"
37
- }
38
- ]
39
- }
40
- ],
41
- "UserPromptSubmit": [
42
- {
43
- "hooks": [
44
- {
45
- "type": "command",
46
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/wp-guard-switch.js",
47
- "timeout": 5
48
- }
49
- ]
50
- }
51
- ],
52
- "SessionStart": [
53
- {
54
- "matcher": "startup|resume|compact",
55
- "hooks": [
56
- {
57
- "type": "command",
58
- "command": "node ${CLAUDE_PLUGIN_ROOT}/bin/wp-sessionstart-routing.js"
59
- }
60
- ]
61
- }
62
- ]
63
- },
64
7
  "mcpServers": {
65
8
  "webpresso": {
66
9
  "command": "node",
package/README.md CHANGED
@@ -44,6 +44,15 @@ Playwright, unit-test and file-based e2e smoke assets), wires `AGENTS.md` /
44
44
  execution-owned vs authoring-owned dependency migration guidance. Re-running
45
45
  refreshes the webpresso-owned pieces and preserves consumer-owned files.
46
46
 
47
+ > **`wp setup` is required for hooks.** The Claude Code hooks (PreToolUse guard,
48
+ > Stop-QA gate, SessionStart routing, …) are installed by `wp setup` into your
49
+ > repo's `.claude/settings.json`. They are intentionally **not** shipped in the
50
+ > plugin manifest — declaring them in both places double-fires every hook (Claude
51
+ > Code does not dedup across sources), and settings.json is the more reliable
52
+ > surface. So enabling the plugin alone does **not** activate hooks; run
53
+ > `wp setup`. Run `wp hooks doctor` to check — it warns if the managed hooks are
54
+ > missing from `.claude/settings.json`.
55
+
47
56
  `wp` owns **execution** for the generic tool lanes it manages (test / mutation /
48
57
  e2e / lint / format / typecheck). That does **not** mean every local
49
58
  devDependency disappears — keep dependencies your repo imports directly (e.g.
@@ -36,9 +36,9 @@ vp install && vp run setup:agent # setup:agent runs wp setup, which scaffolds .
36
36
  agent-kit's catalog is the single source of truth for generated agent surfaces.
37
37
  Agent-kit owns the generated agent surfaces in this file; the Webpresso CLI host owns the end-user command surface.
38
38
  To customize skills, commands, or workflows, edit them in agent-kit's catalog
39
- and publish — not in individual repos. The default `omx` preset chains
40
- `omx setup --yes --scope user` and installs missing OMX through
41
- `vp install -g oh-my-codex`. The default `omc` preset ensures OMC through
39
+ and publish — not in individual repos. The default `omx` preset refreshes
40
+ Vite+ through `vp upgrade`, chains `omx setup --yes --scope user`, and installs
41
+ missing OMX through `vp install -g oh-my-codex`. The default `omc` preset ensures OMC through
42
42
  Claude Code's plugin marketplace in user scope when `claude` is on `PATH`.
43
43
  `wp setup` also repairs the managed `.gitignore` block for regenerated agent
44
44
  surfaces so repo-local `.codex/`,
@@ -73,6 +73,23 @@ export interface NoRelativeParentImportsOptions {
73
73
  * tsconfig `extends`, `paths`, `references`, etc.
74
74
  */
75
75
  export declare function auditNoRelativeParentImports(root: string, options?: NoRelativeParentImportsOptions): RepoAuditResult;
76
+ export interface TestIsolationOptions {
77
+ /** Directory to scan for test files. Defaults to `src`. */
78
+ srcDir?: string;
79
+ /** Test-file suffixes to inspect. Defaults to `.test.ts` / `.test.tsx`. */
80
+ extensions?: readonly string[];
81
+ }
82
+ /**
83
+ * Fail if any `*.test.ts` reaches the repo's own `catalog/` template source
84
+ * through `process.cwd()`. The catalog tree is the source of every
85
+ * agent-surface template, so a `process.cwd()`-anchored path is both brittle
86
+ * (it depends on the runner's working directory) and the exact pattern behind
87
+ * the scaffold-agents-md footgun, where a test read the live
88
+ * `catalog/AGENTS.md.tpl` off cwd. Anchor catalog reads to the package via the
89
+ * import.meta-based `resolveCatalogDir()` helper instead, and write only into
90
+ * `mkdtemp`/`tmpdir` sandboxes.
91
+ */
92
+ export declare function auditTestIsolation(root: string, options?: TestIsolationOptions): RepoAuditResult;
76
93
  export interface NoRelativePackageScriptsOptions {
77
94
  /** Glob-style subdirectory patterns relative to root to skip. */
78
95
  excludeDirs?: readonly string[];
@@ -809,6 +809,81 @@ function walkTsconfigParentPaths(startDir, reportRoot, violations) {
809
809
  walk(startDir);
810
810
  return checked;
811
811
  }
812
+ const TEST_ISOLATION_SKIP_DIRS = new Set([
813
+ 'node_modules',
814
+ 'dist',
815
+ 'build',
816
+ '.git',
817
+ '.cache',
818
+ '.next',
819
+ '.turbo',
820
+ '.omx',
821
+ '.stryker-tmp',
822
+ '.claude',
823
+ // Scaffolding templates become a downstream consumer's tree, not ours.
824
+ 'template',
825
+ // Workspace-level scratch/archive space.
826
+ '_sandbox',
827
+ ]);
828
+ // `(join|resolve)(process.cwd(), ... 'catalog' ...)` — a test reaching into the
829
+ // live catalog template source off the runner's working directory.
830
+ const CWD_CATALOG_PATH_PATTERN = /\b(?:join|resolve)\s*\(\s*process\.cwd\(\)[^)]*\bcatalog\b/;
831
+ /**
832
+ * Fail if any `*.test.ts` reaches the repo's own `catalog/` template source
833
+ * through `process.cwd()`. The catalog tree is the source of every
834
+ * agent-surface template, so a `process.cwd()`-anchored path is both brittle
835
+ * (it depends on the runner's working directory) and the exact pattern behind
836
+ * the scaffold-agents-md footgun, where a test read the live
837
+ * `catalog/AGENTS.md.tpl` off cwd. Anchor catalog reads to the package via the
838
+ * import.meta-based `resolveCatalogDir()` helper instead, and write only into
839
+ * `mkdtemp`/`tmpdir` sandboxes.
840
+ */
841
+ export function auditTestIsolation(root, options = {}) {
842
+ const srcDir = resolve(root, options.srcDir ?? 'src');
843
+ const extensions = options.extensions ?? ['.test.ts', '.test.tsx'];
844
+ const violations = [];
845
+ let checked = 0;
846
+ function walk(dir) {
847
+ if (!existsSync(dir))
848
+ return;
849
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
850
+ const full = join(dir, entry.name);
851
+ if (entry.isDirectory()) {
852
+ if (TEST_ISOLATION_SKIP_DIRS.has(entry.name))
853
+ continue;
854
+ walk(full);
855
+ continue;
856
+ }
857
+ if (!entry.isFile())
858
+ continue;
859
+ if (!extensions.some((ext) => entry.name.endsWith(ext)))
860
+ continue;
861
+ checked++;
862
+ const content = readFileSync(full, 'utf-8');
863
+ const rel = relativePath(root, full);
864
+ const lines = content.split('\n');
865
+ for (let i = 0; i < lines.length; i++) {
866
+ const line = lines[i] ?? '';
867
+ // Skip comment lines so prose referencing the pattern doesn't trip it.
868
+ if (/^\s*(?:\/\/|\/\*|\*)/.test(line))
869
+ continue;
870
+ if (CWD_CATALOG_PATH_PATTERN.test(line)) {
871
+ violations.push({
872
+ file: rel,
873
+ message: `Line ${i + 1}: test reads the catalog template source via process.cwd() — anchor catalog reads to the package with resolveCatalogDir() (import.meta-based) so the test does not depend on the runner's working directory: ${line.trim()}`,
874
+ });
875
+ }
876
+ }
877
+ }
878
+ }
879
+ walk(srcDir);
880
+ return {
881
+ ok: violations.length === 0,
882
+ title: 'test-isolation',
883
+ checked,
884
+ violations,
885
+ };
886
+ }
812
887
  function withFilePrefix(file, auditResult) {
813
888
  return {
814
889
  ...auditResult,
@@ -50,9 +50,9 @@ export declare const readTargetSchema: z.ZodObject<{
50
50
  project_id: z.ZodOptional<z.ZodString>;
51
51
  scope: z.ZodOptional<z.ZodEnum<{
52
52
  all: "all";
53
+ workspace: "workspace";
53
54
  current: "current";
54
55
  roots: "roots";
55
- workspace: "workspace";
56
56
  }>>;
57
57
  }, z.core.$strict>;
58
58
  export type ReadTarget = z.infer<typeof readTargetSchema>;
@@ -39,8 +39,8 @@ export declare const lifecycleBlueprintStatusSchema: z.ZodEnum<{
39
39
  */
40
40
  export declare const taskStatusSchema: z.ZodEnum<{
41
41
  blocked: "blocked";
42
- todo: "todo";
43
42
  done: "done";
43
+ todo: "todo";
44
44
  in_progress: "in_progress";
45
45
  }>;
46
46
  /**
@@ -27,8 +27,8 @@ export declare const blueprintComplexitySchema: z.ZodEnum<{
27
27
  export declare const taskStatusSchema: z.ZodEnum<{
28
28
  blocked: "blocked";
29
29
  "in-progress": "in-progress";
30
- todo: "todo";
31
30
  done: "done";
31
+ todo: "todo";
32
32
  dropped: "dropped";
33
33
  }>;
34
34
  export declare const techDebtStatusSchema: z.ZodEnum<{
@@ -32,6 +32,7 @@ const REPO_AUDIT_REGISTRY = {
32
32
  }),
33
33
  'no-link-protocol': async (root) => (await import('#audit/repo-guardrails')).auditNoLinkProtocol(root),
34
34
  'no-relative-package-scripts': async (root) => (await import('#audit/repo-guardrails')).auditNoRelativePackageScripts(root),
35
+ 'test-isolation': async (root) => (await import('#audit/repo-guardrails')).auditTestIsolation(root),
35
36
  'bucket-boundary': async (root, options) => (await import('#audit/bucket-boundary')).auditBucketBoundary(root, {
36
37
  changedOnly: options.changedOnly,
37
38
  strict: options.strict,
@@ -45,5 +45,16 @@ export declare function discoverWorkspacePackages(repoRoot: string, globs: strin
45
45
  * class that the catch-wrap doesn't surface.
46
46
  */
47
47
  export declare function warnIfNonLocalCli(repoRoot: string, cliUrl?: string): void;
48
+ /**
49
+ * agent-kit's own package name — the source repo for every agent-surface
50
+ * template (`catalog/`, the tracked `.agent/`/`.claude/` surfaces). Scaffolding
51
+ * into this repo overwrites the canonical sources, so `wp setup` refuses it
52
+ * unless explicitly overridden. Distinct from base-kit's broader
53
+ * `SELF_PACKAGE_NAMES` (which also covers the legacy `webpresso` framework
54
+ * identity); only agent-kit hosts the catalog templates.
55
+ */
56
+ export declare const AGENT_KIT_PACKAGE_NAME = "@webpresso/agent-kit";
57
+ /** True when the consumer being scaffolded is agent-kit's own template-source repo. */
58
+ export declare function isAgentKitTemplateSourceRepo(packageName: string | undefined): boolean;
48
59
  export declare function detectConsumer(startDir?: string): ConsumerContext | null;
49
60
  //# sourceMappingURL=detect-consumer.d.ts.map
@@ -261,6 +261,19 @@ export function warnIfNonLocalCli(repoRoot, cliUrl = import.meta.url) {
261
261
  ? 'This repo already pins `webpresso`; rerun via the repo-local CLI (`vp run setup:agent` or `vp exec wp setup`).'
262
262
  : 'Pin `webpresso` as a local dep for reproducible setup.'));
263
263
  }
264
+ /**
265
+ * agent-kit's own package name — the source repo for every agent-surface
266
+ * template (`catalog/`, the tracked `.agent/`/`.claude/` surfaces). Scaffolding
267
+ * into this repo overwrites the canonical sources, so `wp setup` refuses it
268
+ * unless explicitly overridden. Distinct from base-kit's broader
269
+ * `SELF_PACKAGE_NAMES` (which also covers the legacy `webpresso` framework
270
+ * identity); only agent-kit hosts the catalog templates.
271
+ */
272
+ export const AGENT_KIT_PACKAGE_NAME = '@webpresso/agent-kit';
273
+ /** True when the consumer being scaffolded is agent-kit's own template-source repo. */
274
+ export function isAgentKitTemplateSourceRepo(packageName) {
275
+ return packageName === AGENT_KIT_PACKAGE_NAME;
276
+ }
264
277
  export function detectConsumer(startDir = process.cwd()) {
265
278
  const repoRoot = findGitRoot(startDir);
266
279
  if (!repoRoot)
@@ -11,6 +11,7 @@ export interface InitFlags {
11
11
  cwd?: string;
12
12
  strict?: boolean;
13
13
  project?: boolean;
14
+ allowSelfScaffold?: boolean;
14
15
  }
15
16
  export declare const EXIT_SUCCESS = 0;
16
17
  export declare const EXIT_SETUP_FAIL = 1;
@@ -14,7 +14,7 @@ import { readPackageVersion } from '#cli/utils';
14
14
  import { resolveBlueprintRoot } from '#utils/blueprint-root';
15
15
  import { runUnifiedSync } from '#symlinker/unified-sync';
16
16
  import { defaultConfig, mergeConfig, readConfig, writeConfig, } from './config.js';
17
- import { detectConsumer, warnIfNonLocalCli } from './detect-consumer.js';
17
+ import { detectConsumer, isAgentKitTemplateSourceRepo, warnIfNonLocalCli, } from './detect-consumer.js';
18
18
  import { runPreflight, DOCS_URL } from './preflight.js';
19
19
  import { summarizeResults } from './merge.js';
20
20
  import { resolveTier3Selection } from './prompts.js';
@@ -138,6 +138,19 @@ export async function runInit(flags) {
138
138
  `Run \`git init\` first, or pass --cwd pointing at a git working tree.`);
139
139
  return EXIT_SETUP_FAIL;
140
140
  }
141
+ // Self-repo guard: agent-kit is the SOURCE of every agent-surface template
142
+ // (catalog/, the tracked .agent/.claude surfaces). Scaffolding into its own
143
+ // working tree overwrites those canonical sources — the footgun where a stray
144
+ // `wp setup` reported `overwritten: 2, drifted: 11, git index cleanup: 6
145
+ // untracked` against the live repo. Refuse loudly and write nothing unless the
146
+ // maintainer explicitly opts in with --allow-self-scaffold.
147
+ if (isAgentKitTemplateSourceRepo(consumer.packageJson?.name) && flags.allowSelfScaffold !== true) {
148
+ console.error(`wp setup: refusing to scaffold @webpresso/agent-kit's own repo (${consumer.repoRoot}).\n` +
149
+ ` This repo is the source of the agent-surface templates; running setup here\n` +
150
+ ` overwrites the canonical sources under catalog/ and the tracked .agent/.claude surfaces.\n` +
151
+ ` To deliberately regenerate agent-kit's own surfaces, re-run with --allow-self-scaffold.`);
152
+ return EXIT_SETUP_FAIL;
153
+ }
141
154
  warnIfNonLocalCli(consumer.repoRoot);
142
155
  // Run the 5-point compatibility preflight before any scaffolders fire.
143
156
  const preflightResult = await runPreflight(consumer.repoRoot, flags.strict ?? false);
@@ -839,6 +852,7 @@ export function registerInitCommand(cli, commandName = 'init') {
839
852
  .option('--cwd <dir>', 'Working tree to scaffold into (default: process.cwd())')
840
853
  .option('--strict', 'Abort if any compatibility check fails (default: warn and continue)')
841
854
  .option('--project', 'Configure OMX/OMC in project scope instead of the default user scope')
855
+ .option('--allow-self-scaffold', "Override the self-repo guard to scaffold @webpresso/agent-kit's own template-source repo (maintainers only)")
842
856
  .action(async (flags) => {
843
857
  const code = await runInit(flags);
844
858
  if (code !== EXIT_SUCCESS) {
@@ -13,6 +13,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'n
13
13
  import { homedir } from 'node:os';
14
14
  import { dirname, join, resolve } from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
+ import { isHookName } from '#cli/commands/hook.js';
16
17
  import { patchJsonFile } from '#cli/commands/init/merge';
17
18
  import { CodexAppServerClient } from '#codex/app-server/client.js';
18
19
  import { normalizeGlobalCodexHooksFile, resolveBinaryOnPath, } from '#cli/commands/init/scaffolders/agent-hooks/codex-global-normalize';
@@ -623,13 +624,65 @@ function resolvePackageRoot() {
623
624
  }
624
625
  throw new Error('wp setup: could not locate @webpresso/agent-kit package root for hook launchers.');
625
626
  }
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
+ 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;
637
+ }
638
+ /**
639
+ * Absolute path to the consumer's self-contained compiled `wp` binary, when the
640
+ * platform runtime package (`@webpresso/agent-kit-runtime-<platform>`) is
641
+ * installed. The compiled binary bundles its own runtime, so preferring it makes
642
+ * the hook launcher survive node-path staleness — an nvm/version change
643
+ * invalidates the captured absolute node path but not a self-contained binary.
644
+ * Returns undefined when no compiled runtime is installed (today's default), so
645
+ * the launcher keeps its absolute-node fallback unchanged.
646
+ */
647
+ function resolveCompiledWpBinary(repoRoot) {
648
+ const packageDir = compiledRuntimePackageDir();
649
+ if (!packageDir)
650
+ return undefined;
651
+ const filename = process.platform === 'win32' ? 'wp.exe' : 'wp';
652
+ const candidate = join(repoRoot, 'node_modules', '@webpresso', packageDir, 'bin', filename);
653
+ return existsSync(candidate) ? candidate : undefined;
654
+ }
655
+ /**
656
+ * The `wp hook <sub>` subcommand a managed launcher should dispatch to via the
657
+ * compiled binary, or undefined when `binName` is not a dispatchable hook (e.g.
658
+ * `wp-check-dev-link`, which has no `wp hook` handler). The names map 1:1 by
659
+ * stripping the `wp-` prefix; `isHookName` is the single source of truth.
660
+ */
661
+ function hookSubcommandFor(binName) {
662
+ const sub = binName.startsWith('wp-') ? binName.slice(3) : binName;
663
+ return isHookName(sub) ? sub : undefined;
664
+ }
626
665
  function renderManagedWebpressoHookLauncher(repoRoot, binName) {
627
666
  const nodeBinary = quoteShell(process.execPath);
628
667
  const projectBinPath = quoteShell(resolveProjectHookBinPath(repoRoot, binName));
629
668
  const fallbackBinPath = quoteShell(resolvePackageHookBin(binName));
630
669
  const missingFallback = binName === PRETOOL_GUARD_BIN ? PRETOOL_GUARD_MISSING_DENY : 'exit 0';
670
+ // Prefer the self-contained compiled `wp` binary when it resolves: it bundles
671
+ // its own runtime, so it is immune to the node-path staleness that would break
672
+ // the absolute `$NODE_BINARY` path below. Only emitted for dispatchable hooks
673
+ // with an installed compiled runtime; otherwise the node path is unchanged.
674
+ const hookSub = hookSubcommandFor(binName);
675
+ const compiledWp = hookSub ? resolveCompiledWpBinary(repoRoot) : undefined;
676
+ const compiledPreamble = compiledWp !== undefined
677
+ ? `WP_BIN=${quoteShell(compiledWp)}
678
+ if [ -x "$WP_BIN" ]; then
679
+ exec "$WP_BIN" hook ${hookSub} "$@"
680
+ fi
681
+
682
+ `
683
+ : '';
631
684
  return `#!/bin/sh
632
- NODE_BINARY=${nodeBinary}
685
+ ${compiledPreamble}NODE_BINARY=${nodeBinary}
633
686
  PROJECT_BIN_PATH=${projectBinPath}
634
687
  FALLBACK_BIN_PATH=${fallbackBinPath}
635
688
 
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * `omx` scaffolder preset.
3
3
  *
4
- * Ensures `omx` is installed, then chains `omx setup --yes --scope user` after the
5
- * webpresso scaffold completes. OMX (oh-my-codex) is the operator-workflow
4
+ * Refreshes Vite+ (`vp`), ensures `omx` is installed, then chains
5
+ * `omx setup --yes --scope user` after the webpresso scaffold completes.
6
+ * OMX (oh-my-codex) is the operator-workflow
6
7
  * execution layer; it manages its own scaffolding idempotently.
7
8
  *
8
9
  * Required when downstream features rely on `omx team` (see
@@ -55,7 +56,8 @@ type OmxSetupScope = 'user' | 'project';
55
56
  export declare function deduplicateCodexHookTrustState(config: string): string;
56
57
  export declare function migrateDeprecatedCodexHooksFeatureFlag(raw: string): string;
57
58
  /**
58
- * Ensure `omx` is on PATH then run `omx setup --yes --scope user` in the consumer repo.
59
+ * Refresh `vp`, ensure `omx` is on PATH, then run
60
+ * `omx setup --yes --scope user` in the consumer repo.
59
61
  * Idempotent: safe to run on every `wp setup`.
60
62
  */
61
63
  export declare function ensureOmx(input: EnsureOmxInput): EnsureOmxResult;
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * `omx` scaffolder preset.
3
3
  *
4
- * Ensures `omx` is installed, then chains `omx setup --yes --scope user` after the
5
- * webpresso scaffold completes. OMX (oh-my-codex) is the operator-workflow
4
+ * Refreshes Vite+ (`vp`), ensures `omx` is installed, then chains
5
+ * `omx setup --yes --scope user` after the webpresso scaffold completes.
6
+ * OMX (oh-my-codex) is the operator-workflow
6
7
  * execution layer; it manages its own scaffolding idempotently.
7
8
  *
8
9
  * Required when downstream features rely on `omx team` (see
@@ -14,7 +15,7 @@ import { homedir } from 'node:os';
14
15
  import { dirname, join, resolve, sep } from 'node:path';
15
16
  import { defaultCodexHooksPathFromConfig, normalizeGlobalCodexHooksFile, resolveBinaryOnPath, } from '#cli/commands/init/scaffolders/agent-hooks/codex-global-normalize';
16
17
  const NOT_FOUND_HINT = 'omx (oh-my-codex) is not on PATH after `vp install -g oh-my-codex`. Install it manually and re-run.';
17
- function shouldSkipOmxRefresh(env = process.env) {
18
+ function shouldSkipManagedToolRefresh(env = process.env) {
18
19
  return env.WP_SKIP_UPDATE_CHECK === '1';
19
20
  }
20
21
  function defaultCodexConfigPath() {
@@ -190,7 +191,8 @@ function pruneEmptyProjectScopedDirs(repoRoot, relativeFile) {
190
191
  }
191
192
  }
192
193
  /**
193
- * Ensure `omx` is on PATH then run `omx setup --yes --scope user` in the consumer repo.
194
+ * Refresh `vp`, ensure `omx` is on PATH, then run
195
+ * `omx setup --yes --scope user` in the consumer repo.
194
196
  * Idempotent: safe to run on every `wp setup`.
195
197
  */
196
198
  export function ensureOmx(input) {
@@ -210,6 +212,9 @@ export function ensureOmx(input) {
210
212
  }
211
213
  }
212
214
  let installed = false;
215
+ if (!shouldSkipManagedToolRefresh()) {
216
+ spawn('vp', ['upgrade'], { stdio: 'inherit' });
217
+ }
213
218
  let probe = spawn('omx', ['--version'], { encoding: 'utf8' });
214
219
  if (probe.error || (probe.status !== null && probe.status !== 0)) {
215
220
  const install = spawn('vp', ['install', '-g', 'oh-my-codex'], { stdio: 'inherit' });
@@ -222,7 +227,7 @@ export function ensureOmx(input) {
222
227
  return { kind: 'omx-not-found', hint: NOT_FOUND_HINT };
223
228
  }
224
229
  }
225
- else if (!shouldSkipOmxRefresh()) {
230
+ else if (!shouldSkipManagedToolRefresh()) {
226
231
  spawn('vp', ['update', '-g', 'oh-my-codex'], { stdio: 'inherit' });
227
232
  }
228
233
  const result = spawn('omx', ['setup', '--yes', '--scope', scope], {
@@ -17,8 +17,8 @@ export declare const agentsFrontmatter: z.ZodObject<{
17
17
  monitoring: "monitoring";
18
18
  resolved: "resolved";
19
19
  open: "open";
20
- deprecated: "deprecated";
21
20
  current: "current";
21
+ deprecated: "deprecated";
22
22
  complete: "complete";
23
23
  active: "active";
24
24
  review: "review";
@@ -57,8 +57,8 @@ export declare const agentEntryFrontmatter: z.ZodObject<{
57
57
  monitoring: "monitoring";
58
58
  resolved: "resolved";
59
59
  open: "open";
60
- deprecated: "deprecated";
61
60
  current: "current";
61
+ deprecated: "deprecated";
62
62
  complete: "complete";
63
63
  active: "active";
64
64
  review: "review";
@@ -18,8 +18,8 @@ export declare const auditFrontmatter: z.ZodObject<{
18
18
  monitoring: "monitoring";
19
19
  resolved: "resolved";
20
20
  open: "open";
21
- deprecated: "deprecated";
22
21
  current: "current";
22
+ deprecated: "deprecated";
23
23
  complete: "complete";
24
24
  active: "active";
25
25
  review: "review";
@@ -25,8 +25,8 @@ export declare const baseFrontmatter: z.ZodObject<{
25
25
  monitoring: "monitoring";
26
26
  resolved: "resolved";
27
27
  open: "open";
28
- deprecated: "deprecated";
29
28
  current: "current";
29
+ deprecated: "deprecated";
30
30
  complete: "complete";
31
31
  active: "active";
32
32
  review: "review";
@@ -19,8 +19,8 @@ export declare const cookbookFrontmatter: z.ZodObject<{
19
19
  monitoring: "monitoring";
20
20
  resolved: "resolved";
21
21
  open: "open";
22
- deprecated: "deprecated";
23
22
  current: "current";
23
+ deprecated: "deprecated";
24
24
  complete: "complete";
25
25
  active: "active";
26
26
  review: "review";
@@ -18,8 +18,8 @@ export declare const coreFrontmatter: z.ZodObject<{
18
18
  monitoring: "monitoring";
19
19
  resolved: "resolved";
20
20
  open: "open";
21
- deprecated: "deprecated";
22
21
  current: "current";
22
+ deprecated: "deprecated";
23
23
  complete: "complete";
24
24
  active: "active";
25
25
  review: "review";
@@ -54,8 +54,8 @@ export declare const readmeFrontmatter: z.ZodObject<{
54
54
  monitoring: "monitoring";
55
55
  resolved: "resolved";
56
56
  open: "open";
57
- deprecated: "deprecated";
58
57
  current: "current";
58
+ deprecated: "deprecated";
59
59
  complete: "complete";
60
60
  active: "active";
61
61
  review: "review";
@@ -88,8 +88,8 @@ export declare const securityFrontmatter: z.ZodObject<{
88
88
  monitoring: "monitoring";
89
89
  resolved: "resolved";
90
90
  open: "open";
91
- deprecated: "deprecated";
92
91
  current: "current";
92
+ deprecated: "deprecated";
93
93
  complete: "complete";
94
94
  active: "active";
95
95
  review: "review";
@@ -169,8 +169,8 @@ export declare const agentsFrontmatter: z.ZodObject<{
169
169
  monitoring: "monitoring";
170
170
  resolved: "resolved";
171
171
  open: "open";
172
- deprecated: "deprecated";
173
172
  current: "current";
173
+ deprecated: "deprecated";
174
174
  complete: "complete";
175
175
  active: "active";
176
176
  review: "review";
@@ -19,8 +19,8 @@ export declare const evaluationFrontmatter: z.ZodObject<{
19
19
  monitoring: "monitoring";
20
20
  resolved: "resolved";
21
21
  open: "open";
22
- deprecated: "deprecated";
23
22
  current: "current";
23
+ deprecated: "deprecated";
24
24
  complete: "complete";
25
25
  active: "active";
26
26
  review: "review";
@@ -38,8 +38,8 @@ export declare const planArtifactFrontmatter: z.ZodObject<{
38
38
  monitoring: "monitoring";
39
39
  resolved: "resolved";
40
40
  open: "open";
41
- deprecated: "deprecated";
42
41
  current: "current";
42
+ deprecated: "deprecated";
43
43
  complete: "complete";
44
44
  active: "active";
45
45
  review: "review";
@@ -79,8 +79,8 @@ export declare const planReportFrontmatter: z.ZodObject<{
79
79
  monitoring: "monitoring";
80
80
  resolved: "resolved";
81
81
  open: "open";
82
- deprecated: "deprecated";
83
82
  current: "current";
83
+ deprecated: "deprecated";
84
84
  complete: "complete";
85
85
  active: "active";
86
86
  review: "review";
@@ -18,8 +18,8 @@ export declare const ruleFrontmatter: z.ZodObject<{
18
18
  monitoring: "monitoring";
19
19
  resolved: "resolved";
20
20
  open: "open";
21
- deprecated: "deprecated";
22
21
  current: "current";
22
+ deprecated: "deprecated";
23
23
  complete: "complete";
24
24
  active: "active";
25
25
  review: "review";
@@ -13,6 +13,8 @@ export interface DoctorCheck {
13
13
  name: string;
14
14
  ok: boolean;
15
15
  detail?: string;
16
+ /** Advisory checks surface a warning but do not flip the doctor's exit code. */
17
+ advisory?: boolean;
16
18
  }
17
19
  export interface DoctorResult {
18
20
  ok: boolean;
@@ -28,6 +30,16 @@ export interface RunHooksDoctorOptions {
28
30
  }
29
31
  export declare function findOwningPackageRoot(startDir: string): string | null;
30
32
  export declare function checkRtkOnPath(cwd?: string): Promise<DoctorCheck | null>;
33
+ /**
34
+ * Verify the consumer's `.claude/settings.json` carries the managed agent-kit
35
+ * hook launchers. Since the hooks are single-sourced there (not in the plugin
36
+ * manifest), a missing reference means a plugin-only install that never ran
37
+ * `wp setup` — i.e. no agent-kit hooks are active.
38
+ */
39
+ export declare function checkManagedHooksInstalled(cwd?: string): {
40
+ ok: boolean;
41
+ detail?: string;
42
+ };
31
43
  export declare function runHooksDoctor(opts?: RunHooksDoctorOptions): Promise<DoctorResult>;
32
44
  export declare function printHooksDoctor(opts?: RunHooksDoctorOptions): Promise<number>;
33
45
  export {};
@@ -590,6 +590,40 @@ function checkLiveSourceDevLink(cwd = process.cwd()) {
590
590
  detail: `${state.package} → ${state.linkedFrom}`,
591
591
  };
592
592
  }
593
+ // Marker for the managed hook launchers `wp setup` writes under
594
+ // `.claude/hooks/managed/` (CLAUDE_MANAGED_HOOK_SUBDIR in the agent-hooks
595
+ // scaffolder). The plugin manifest no longer ships hooks (they double-fired
596
+ // against these and were the less reliable surface), so settings.json is the
597
+ // single source — if it does not reference them, the hooks are not installed.
598
+ const MANAGED_HOOK_MARKER = 'hooks/managed/wp-pretool-guard';
599
+ /**
600
+ * Verify the consumer's `.claude/settings.json` carries the managed agent-kit
601
+ * hook launchers. Since the hooks are single-sourced there (not in the plugin
602
+ * manifest), a missing reference means a plugin-only install that never ran
603
+ * `wp setup` — i.e. no agent-kit hooks are active.
604
+ */
605
+ export function checkManagedHooksInstalled(cwd = process.cwd()) {
606
+ const settingsPath = join(cwd, '.claude', 'settings.json');
607
+ if (!tryAccess(settingsPath)) {
608
+ return {
609
+ ok: false,
610
+ detail: 'no .claude/settings.json — run `wp setup` to install the agent-kit hooks',
611
+ };
612
+ }
613
+ try {
614
+ const raw = readFileSync(settingsPath, 'utf-8');
615
+ if (!raw.includes(MANAGED_HOOK_MARKER)) {
616
+ return {
617
+ ok: false,
618
+ detail: 'agent-kit hooks not found in .claude/settings.json — run `wp setup`',
619
+ };
620
+ }
621
+ return { ok: true };
622
+ }
623
+ catch (err) {
624
+ return { ok: false, detail: `failed to read .claude/settings.json: ${String(err)}` };
625
+ }
626
+ }
593
627
  export async function runHooksDoctor(opts = {}) {
594
628
  const checks = [];
595
629
  const isWin = platform() === 'win32';
@@ -609,6 +643,11 @@ export async function runHooksDoctor(opts = {}) {
609
643
  }
610
644
  checks.push(checkConsumerCodexHookPaths(opts.cwd));
611
645
  checks.push({ name: 'plugin.json integrity', ...checkPluginJson() });
646
+ checks.push({
647
+ name: 'managed hooks installed (.claude/settings.json)',
648
+ advisory: true,
649
+ ...checkManagedHooksInstalled(opts.cwd),
650
+ });
612
651
  if (opts.skipMcp) {
613
652
  checks.push({ name: 'MCP server liveness', ok: true, detail: 'skipped (--skip-mcp)' });
614
653
  }
@@ -676,7 +715,7 @@ export async function runHooksDoctor(opts = {}) {
676
715
  }
677
716
  }
678
717
  }
679
- const nonMcpChecks = checks.filter((c) => !c.name.startsWith('MCP '));
718
+ const nonMcpChecks = checks.filter((c) => !c.name.startsWith('MCP ') && !c.advisory);
680
719
  const overallOk = nonMcpChecks.every((c) => c.ok);
681
720
  return { ok: overallOk, checks };
682
721
  }
@@ -9,8 +9,8 @@ export declare const transformMetadataSchema: z.ZodObject<{
9
9
  toolName: z.ZodString;
10
10
  normalizedToolName: z.ZodString;
11
11
  tier: z.ZodEnum<{
12
- registered: "registered";
13
12
  passthrough: "passthrough";
13
+ registered: "registered";
14
14
  }>;
15
15
  rawBytes: z.ZodNumber;
16
16
  }, z.core.$strip>;
@@ -38,8 +38,8 @@ export declare const summaryFirstResultSchema: z.ZodObject<{
38
38
  toolName: z.ZodString;
39
39
  normalizedToolName: z.ZodString;
40
40
  tier: z.ZodEnum<{
41
- registered: "registered";
42
41
  passthrough: "passthrough";
42
+ registered: "registered";
43
43
  }>;
44
44
  rawBytes: z.ZodNumber;
45
45
  }, z.core.$strip>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpresso/agent-kit",
3
- "version": "0.26.1",
3
+ "version": "0.26.3",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -710,10 +710,10 @@
710
710
  "@stryker-mutator/typescript-checker": "^9.6.1",
711
711
  "@playwright/test": "^1.55.0",
712
712
  "wrangler": "^4.50.0",
713
- "@webpresso/agent-kit-runtime-darwin-arm64": "0.26.1",
714
- "@webpresso/agent-kit-runtime-darwin-x64": "0.26.1",
715
- "@webpresso/agent-kit-runtime-linux-x64": "0.26.1",
716
- "@webpresso/agent-kit-runtime-linux-arm64": "0.26.1",
717
- "@webpresso/agent-kit-runtime-windows-x64": "0.26.1"
713
+ "@webpresso/agent-kit-runtime-darwin-arm64": "0.26.3",
714
+ "@webpresso/agent-kit-runtime-darwin-x64": "0.26.3",
715
+ "@webpresso/agent-kit-runtime-linux-x64": "0.26.3",
716
+ "@webpresso/agent-kit-runtime-linux-arm64": "0.26.3",
717
+ "@webpresso/agent-kit-runtime-windows-x64": "0.26.3"
718
718
  }
719
719
  }