Compiler-Free Plugin Runtime
Design Log #179 — Compiler-Free Plugin Runtime (runtime vs tools entry split)
Written for AI agents. See Log Methodology Note below for details.
Part 1 — Runtime/tools entry split: keep compiler deps out of a plugin's serve (
.) bundle by moving all tools-time capabilities (validators, agent-kit, setup, tools-actions) behind a./toolsentry. Part 2 — Capability-awarevalidate-plugin: stop assuming every plugin renders components.
Background
DL#178 removed compiler dependencies from the production serve/rebuild path by splitting runtime vs build packages. This log addresses the same problem one layer out: plugins.
A plugin package ships code that runs in two very different phases:
- Serve-time (runtime) — the headless component, production routes, server actions, client
bundle, global
init. Loaded on the production request path. Must stay compiler-free (DL#178 goal: small Wix BaaS deploy bundle). - Tools-time — validators (DL#145), CLI commands, agent-kit generators,
jay-stack setuphandlers, and dev-only route components. Run only under the Jay toolchain (jay-stack validate/agent-kit/setup/run, dev-server). These legitimately use compiler APIs (parseTemplateParts,walkElements,compileContractFile, …).
The two phases share one npm package but must not share one module graph.
Actions vs commands — the load-bearing distinction
The framework already has the right two primitives; we just used them interchangeably because both can be triggered from the CLI:
- Actions are the serving primitive — request-time handlers that run in production. They
live in
index.jsand must be compiler-free. - CLI commands are the tools primitive — invoked under the toolchain (
jay-stack run …). They live in./toolsand may use the compiler.
So the rule is simple and non-ambiguous: if a handler needs the compiler, it is a command, not an
action. This replaces the earlier "actions try ./tools then ." fallback with a clean split.
Problem
Every tools-time handler is currently loaded from the plugin's main entry (. → dist/index.js),
the same entry the serve path imports. So any tools handler that touches the compiler drags the
compiler into the serve bundle. This is systemic across three loaders, not specific to validators:
| Capability | Loader | Current entry | Target entry |
|---|---|---|---|
validators |
stack-cli/lib/validate.ts:903 |
import(plugin.packageName) — . |
./tools |
commands |
stack-server-build/lib/plugin-commands.ts:213 (loadCommandHandler) |
import(command.packageName) — . |
./tools |
agentkit / setup |
stack-server-build/lib/plugin-setup.ts:315 (loadHandler) |
import(plugin.packageName) — . |
./tools |
actions |
stack-server-build/lib/action-discovery.ts (registerNpmPluginActions) |
package main module — . |
. (unchanged; compiler-free) |
Because these handlers are named exports of the plugin, index.ts re-exports them:
// index.ts (main / serve entry) — design-system-validator, abbreviated
export { validateTokens } from './validators/design-tokens.js'; // compiler-shared
export { generateDesignSystemAgentKit } from './generate-add-menu.js'; // agentkit
export { runDesignSystemAnalysis } from './settings-actions.js'; // → run-design-analysis → compiler-jay-html
export { designSystemSettingsPage } from './pages/settings/page.js'; // devOnly route → compiler-jay-html
Now import('@jay-framework/design-system-validator') (the serve-time entry) transitively loads
compiler-jay-html. The compiler enters the deploy trace and becomes a runtime dependency.
Key correction from the first draft of this log: the leak is not the validator. In the
in-repo mixed plugin (design-system-validator) the validator only uses compiler-shared
(walkElements + erased import types); the real compiler-jay-html leak comes through
run-design-analysis.ts, reached via the actions, agent-kit, and dev-only page — all exported
from .. Splitting only the validator would leave index.js still importing the compiler. The fix
must move all tools-time handlers off ..
In-repo reality check
a11y-validator,seo-validator— validator-only. They never serve anything, so today's compiler import in theirindex.jsis never in a production trace. The split is correctness/hygiene (and consistency), not a bundle win.design-system-validator— tools plugin (agentkit + actions + validators +devOnlyroute). Its entire public surface is tools-time; it has no production serve component. After the split its.entry is empty (or near-empty) and therefore compiler-free.- The plugin that motivated this log — a genuine serve component + validator in one package — lives in the external Wix repo. In-repo we can prove the mechanism + loaders; the pure serve-mixed bundle win is demonstrated by the Wix owners.
Questions and Answers
Q1. One extra entry, or per-capability entries?
A: One ./tools entry for all tools-time handlers (source lib/tools.ts → dist/tools.js),
parallel to the existing ./client (index.client.ts). Reuses a proven mechanism; one boundary for
plugin authors to reason about ("does this run at serve or under the toolchain?").
Q2. Do compiler-* packages stay plugin dependencies?
A: Declared as peerDependency (provided by the toolchain / stack-cli at tools time), plus a
devDependency so the plugin's own build/test resolve them. Keeps runtime installs lean. The
decisive win is the bundle trace: index.js no longer imports any tools handler, so the serve
trace never reaches the compiler regardless of what's installed.
Q3. Keep backward-compat with handlers exported off .?
A: No — clean cut for all tools capabilities. Validators, commands, agent-kit, and setup load
only from ./tools; no .-fallback. Any plugin declaring one of these must expose a ./tools
export (Part 2 enforces it). This keeps the loaders trivial and the boundary unambiguous: tools
handlers always live in tools.ts, serve handlers in index.ts. Actions stay on . and must be
compiler-free (a compiler-using "action" is really a command).
Q4. Where does handler point now?
A: handler keeps its meaning — the export name within the loaded module. Only the module we
import changes. plugin.yaml is unchanged.
Design
Three source entries per plugin (validator-only plugins may have an empty .):
lib/index.ts → dist/index.js (".") serve-time, compiler-free
lib/index.client.ts → dist/index.client.js ("./client") client, compiler-free
lib/tools.ts → dist/tools.js ("./tools") tools-time, compiler OK
index.tsmust not importtools.ts. Compiler imports live only behind./tools.tools.tsre-exports the plugin's validators, agent-kit generator, setup handler, and any compiler-dependent actions.
Capability → entry routing
| Capability (manifest field) | Phase | Loaded from |
|---|---|---|
contracts / dynamic_contracts |
serve | . (+ subpaths) |
production routes component |
serve | route component/compPath (compiler-free) |
contexts, init/global |
serve | . / ./client |
actions |
serve | . (compiler-free) |
validators |
tools | ./tools |
commands |
tools | ./tools |
agentkit, setup |
tools | ./tools |
devOnly / setup routes |
tools | route path (excluded from deploy; compiler OK) |
Loader changes
stack-cli/lib/validate.ts (published branch, ~line 903) — hard cut:
} else {
handlerModule = await import(`${plugin.packageName}/tools`);
}
stack-server-build/lib/plugin-setup.ts loadHandler (agent-kit/setup, npm branch ~line 315) and
stack-server-build/lib/plugin-commands.ts loadCommandHandler (npm branch ~line 213) — load
directly from ./tools (clean cut, no fallback):
// npm branch
module = viteServer
? await viteServer.ssrLoadModule(`${packageName}/tools`)
: await import(`${packageName}/tools`);
action-discovery.ts registerNpmPluginActions is unchanged — actions keep loading from . and
must be compiler-free (the leak scan enforces it).
(Local-plugin paths are file-based and unaffected.)
Plugin package.json
"exports": {
".": "./dist/index.js",
"./client": "./dist/index.client.js",
"./tools": "./dist/tools.js",
"./plugin.yaml": "./plugin.yaml"
}
Plugin vite.config (add tools SSR entry, externalize compiler)
lib: {
entry: isSsrBuild
? {
index: resolve(__dirname, 'lib/index.ts'),
tools: resolve(__dirname, 'lib/tools.ts'),
}
: { 'index.client': resolve(__dirname, 'lib/index.client.ts') },
},
rollupOptions: {
external: [/* runtime externals */, /^@jay-framework\/compiler-/],
}
Because the build externalizes @jay-framework/compiler-*, any leak into . surfaces as a literal
import '@jay-framework/compiler-…' string in dist/index.js — which makes leak detection a text
scan, no bundler/graph tracing (see Part 2).
plugin-validator enforcement — prevention first
Per CLAUDE.md prevention order, catch the leak at validation time:
- New lint rule (error):
dist/index.jsmust contain no@jay-framework/compiler-import. Implemented as a text scan of the built.bundle (plugins build withminify: false), naming the offending re-export. This is now achievable fordesign-system-validatorbecause all its compiler-using handlers move to./tools. - New lint rule (error): if
plugin.yamldeclaresvalidators, the package must expose a./toolsexport (Part 2 rule 2).
Diagram
Implementation Plan
Phase 1 — Loaders
validate.ts: published validators load from${packageName}/tools(hard cut).plugin-setup.tsloadHandler(agent-kit/setup) +plugin-commands.tsloadCommandHandler: load npm handlers from./tools(clean cut).action-discovery.tsunchanged (actions stay.).
Phase 2 — Plugin template + in-repo plugins
For each plugin: add lib/tools.ts (re-export tools handlers), remove those re-exports from
index.ts, add the ./tools export + vite tools entry + build:types entry, and move compiler-*
to peerDependencies (keep a devDependency).
In-repo plugin inventory (all under packages/plugins/):
| Plugin | Declared capabilities | Action for #179 |
|---|---|---|
a11y-validator |
validators |
Move validator → ./tools; . becomes empty. Uses compiler-shared. |
seo-validator |
validators |
Same as a11y. |
design-system-validator |
agentkit, actions, routes, validators |
In-repo mixed case. fontFallback stays a genuine (compiler-free) action on .. The compiler-using analysis handlers (runDesignSystemAnalysis, …, via run-design-analysis.ts → parseJayFile/JAY_IMPORT_RESOLVER) become devOnly actions with handlers in ./tools (DL#180) — kept as actions so the settings page's browser RPC still works — together with validators + generateDesignSystemAgentKit. Split settings-actions.ts so compiler-free actions stay in . (see module-taint note). index.js then ends compiler-free. Verify with the leak scan. |
data-files |
dynamic_contracts, commands |
commands → ./tools (clean cut, even if compiler-free). Verify interactive gating. |
markdown |
contracts |
Verify interactive gating; keeps ./client iff interactive. |
ui-kit |
contracts, agentkit |
agentkit → ./tools. Verify interactive gating. |
gemini-agent |
contracts, actions, setup |
setup → ./tools; actions stay .. Verify interactive gating. |
webmcp |
global: true (no manifest fields) |
Must not false-warn — global+init export counts (Part 2 rule 1). |
External plugin inventory (scanned 2026-09-03; fixed by their owners, but they set the migration
scope and confirm the mechanism). Two repos: ../wix/packages/wix-* and ../aiditor/packages/*.
| Plugin (repo) | Leaks compiler into .? |
Via | #179 action |
|---|---|---|---|
wix-media (wix) |
Yes | validate re-export → media-validator.ts value-imports walkElements,resolveBinding (compiler-shared), parseTemplateParts (compiler-jay-html) |
validator → ./tools; commands → ./tools. Settings actions are compiler-free. |
wix-deploy (wix) |
Yes | validate re-export → static-filename-validator.ts value-imports walkElements (compiler-shared), parseTemplateParts (compiler-jay-html) |
validator → ./tools; commands → ./tools. (build-entry.ts compiler refs are string stubs, not runtime imports.) |
wix-members (wix) |
No (import type only) |
validator uses type-only compiler imports (erased); runtime uses node:fs/path |
validator → ./tools for consistency; no dep change needed. |
wix-bookings/stores/stores-v1/data/forms/cart/server-client (wix) |
No | actions/setup/contracts only; no compiler imports | no leak; compiler-jay-stack stays devDep. Nothing to move. |
aiditor (aiditor) |
Yes | getContractInspectorTagsAction (re-exported and imported by page) → resolve-contract-tags-server.ts value-imports parseContract,ContractTagType (compiler-jay-html), checkValidationErrors (compiler-shared) |
Leak is via an action + page, not a validator → DL#180 devOnly action + devOnly route in ./tools. aiditor assumes a full dev environment, so its /aiditor route should be marked devOnly: true and getContractInspectorTagsAction becomes a devOnly action (handlers in ./tools). Squarely in #180's scope — the whole tool is a dev/tools surface, so its compiler use is fine there and never reaches a production serve bundle. |
aiditor-quill (aiditor) |
No | index only re-exports the agentkit handler; no compiler imports |
agentkit → ./tools. |
Two distinct leak shapes, cleanly separated across #179/#180:
- Validator leaks — the common case.
wix-media,wix-deploy(and in-repoa11y/seo/design-systemvalidators) all pull the compiler through avalidatere-export. Fully fixed by #179's validator →./toolssplit — nodevOnlyneeded. - Settings-page-with-compiler-action leaks — the rare case. Only
design-system-validator(in-repo) andaiditor(external) route the compiler through a browser-callable action / page. This is what DL#180 (devOnlyactions in./tools) exists for.
Key confirmation from the scan: no wix or aiditor-quill action imports a compiler package. The
entire wix compiler pressure is validators (→ #179). wix-media's settings page (/wix-media/settings
devOnly:true + 5 settings actions) is the reference shape for #180, but its actions happen to be
compiler-free — so #180's devOnly-action mechanism is driven solely by design-system-validator and
aiditor, validating that the two DLs carve the problem at the right joint. Nobody has a ./tools or
./validator export yet; design-system-validator is our in-repo equivalent for the leak scan.
Resolved via DL#180 — design-system-validator's dev-only page. Its devOnly settings page invokes
the analysis via actions from the browser. Rather than reclassify those as commands (which would
break the browser RPC), DL#180 introduces devOnly actions whose handlers live in ./tools
(compiler-OK) and are excluded from production. The settings page keeps calling them as actions in the
dev server; production build strips both the devOnly route and the devOnly actions (finishing
DL#171 Phase 2). Note: devOnly routes are not excluded from the production build today
(build-pipeline.ts:274 still compiles them; only sitemap skips them) — DL#180 implements that
exclusion.
Module-taint note. A compiler import taints the whole module, not just the function that uses
it: settings-actions.ts imports run-design-analysis.ts at module top, so every handler exported
from settings-actions.ts drags in the compiler even if it never calls the analysis. Migration must
therefore split modules — keep compiler-free handlers (e.g. fontFallback) in modules reachable
from index.ts, and move compiler-using handlers into modules reachable only from tools.ts. The
leak scan (literal @jay-framework/compiler- in dist/index.js) catches an incomplete split.
Phase 3 — Capability-aware validation (Part 2)
validate-plugin: replace the two unconditional checks (:330,:1055) with the capability → required-export table.- Add the at-least-one-capability warning with a
suggestionlink toagent-kit/plugin/plugin-structure.md. ./clienterror iff a provided component has an interactive phase (static scan forwithInteractiveMark() orcontextsdeclared../toolserror iff any tools capability (validators/commands/agentkit/setup) declared.- Leak scan:
dist/index.jstext contains no@jay-framework/compiler-.
Phase 4 — Agent-kit guides (Part 3)
- Update
validation.md(validators export from./tools, never fromindex.ts),plugin-structure.md(three-entry model + capability matrix + runtime/tools split),contracts-guide.md(interactive →./client).
Phase 5 — Verify
jay-stack validaterunsa11y/seo/design-systemvalidators loaded via./tools.- Leak scan on
design-system-validatordist/index.js: zero@jay-framework/compiler-imports. validate-pluginona11y/seo: no false "no contracts" / "missing ./client" warnings; error if./toolsmissing whilevalidatorsdeclared.validate-pluginonwebmcp(global: true): no "declares no capabilities" warning.validate-pluginondata-files/markdown/ui-kit/gemini-agent:./clientrequired only where a component is interactive; no new false errors.yarn confirmgreen (all 8 in-repo plugins build + validate).
Verification Criteria
- A plugin with a serve component + tools handlers ships an
index.jswhose text contains no@jay-framework/compiler-import. jay-stack validateloads validators via./toolsand reports findings.validate-pluginfails a plugin that re-exports a compiler-using handler fromindex.ts.
Part 2 — Capability-Aware Plugin Validation
Problem
validate-plugin assumes every plugin is a "standard component plugin" and applies two checks
unconditionally, producing false warnings for validator-only (and action-only, service-only) plugins:
# design-system-validator, seo-validator (no serve component):
Plugin has no contracts or dynamic_contracts defined # validate-plugin.ts:330
package.json exports missing "./client" entry point # validate-plugin.ts:1055
Neither is a real problem: such plugins correctly have no contracts and no client bundle. The checks encode "a plugin must render components," which is only true for some plugins.
Design — validate against declared capabilities
Key off the manifest fields the plugin actually declares. Capability → what it needs:
| Capability (manifest field) | Needs ./client? |
Required package.json export(s) |
|---|---|---|
contracts |
only if interactive phase | ., ./<contract> per item; ./client iff interactive |
dynamic_contracts |
only if interactive phase | .; ./client iff interactive |
routes |
only if interactive phase | ., ./<jayHtml> (+./<css>); ./client iff interactive |
contexts |
✅ always (client by def) | ., ./client |
validators |
❌ no | ./tools (Part 1) |
commands |
❌ no | ./tools (tools; compiler OK) |
agentkit / setup |
❌ no | ./tools |
actions |
❌ no | . (compiler-free) |
services |
❌ no | . |
init / global |
❌ no | . |
Error vs warning principle
Single rule (consistent with DL#176 — every warning must be suppressible):
- Error — the plugin does not work: a declared capability cannot function. Not suppressible.
(e.g.
validatorsdeclared but no./tools; interactive component but no./client.) - Warning — advisory the author can suppress: works but likely unintended. (e.g. declares no capabilities.)
Rules:
At-least-one-capability. A plugin declaring none → single warning:
Plugin declares no capabilities (contracts, dynamic_contracts, actions, validators, routes, services, contexts, init, setup, agentkit, commands). Include asuggestionlinking toagent-kit/plugin/plugin-structure.md. Replaces the contracts-specific warning at line 330.global: truecounts. A global plugin (webmcp) runs on every page via itsinit/setupexport even with no contracts/actions — treatglobal: true(paired with a resolvableinit/setupexport) as satisfying the rule;global: truewith no such export is an error.
./toolsis required iff any tools capability is declared —validators,commands,agentkit, orsetup(Part 1). Error, not a warning: these load only from./tools, so a missing export means the handler can't be loaded../clientis required iff a provided component has an interactive phase, or the plugin declarescontexts. Server-only (slow/fast) component plugins need no./client. Replaces the unconditional check at line 1055.- Detection = static text scan.
validate-pluginis a purely static analyzer today (fs.readFileSync+ regex; it does not import plugin code). Keep it that way: scan the built, un-minified server.bundle for the interactive markwithInteractiveMark(. Jay's runtime-mode code-deletion transform swapswithInteractive→withInteractiveMarkfor the server build (verified:compiler-jay-stack/.../check-method-should-remove.ts, DL#72a; both sethasInteractive = true), so the mark is reliably present in the server bundle when — and only when — a component declared an interactive phase. No dynamic import, no side effects. - Precondition: the plugin must be built (
dist/) before validate. In-repovalidateruns as the lastbuildstep, sodist/exists. - Caveat: never infer interactivity from the client bundle — it may be absent (the very thing we're checking).
- Degrade: if interactivity can't be determined, warn, don't error.
- Detection = static text scan.
Contract/route sub-exports stay as-is (already conditional on
contracts/routes).
Table-driven: derive the required export set from the union of declared capabilities (plus the
interactive scan), then diff against package.json.exports.
Verification (Part 2)
validate-pluginonseo-validator/design-system-validator: no "no contracts" and no "missing ./client" warnings; still requires the./toolsexport.validate-pluginon an empty plugin: exactly one "declares no capabilities" warning.- A component plugin with an interactive phase but no
./clientstill errors.
Part 3 — Agent-Kit Guide Updates (plugin developer)
The guides currently teach the pattern that causes this bug and assume a component-shaped plugin.
agent-kit-template/plugin/validation.md
Plugin Validators(~line 142): currently "The function must be exported fromlib/index.ts." — the exact instruction that leaks the compiler. Replace: validators live inlib/tools.ts, exported via the./toolssubpath; never re-export a tools handler fromindex.ts(compiler APIs must not enter the serve entry).handler= export name within./tools.What It Checks/Plugin Structure(~line 39): document the capability-aware rules —./clientonly when a component has an interactive phase;./toolswhenvalidators(or any compiler-using handler) declared; the at-least-one-capability warning.
agent-kit-template/plugin/plugin-structure.md
Dual Entry Points→Entry Points(~line 266): addlib/tools.ts→dist/tools.js(./tools), compiler-allowed, toolchain-only. Update the entry table (~278).- Build scripts (
292) + vite config (317): add thetoolsSSR entry withcompiler-*externalized; addlib/tools.tstobuild:types. package.json Exports(~334): add"./tools": "./dist/tools.js".- New
Capabilitiesoverview near the top: the runtime-vs-tools split + the actions-vs-commands distinction (actions serve/compiler-free on.; commands tools/compiler on./tools) + the capability → required-export matrix; a plugin needs ≥1 capability;./clientis interactive-gated.
agent-kit-template/plugin/contracts-guide.md
- Note the interactive-phase →
./clientrequirement.
Documentation (tier 2) paired with the validate-plugin rules (tier 1) so guidance is enforced.
Trade-offs
- + Reuses the established
./clientsplit; one runtime/tools boundary for authors. - + Fixes the systemic leak (all tools handlers), not just validators.
- + Leak detection is a cheap text scan (compiler externalized), no bundler graph tracing; keeps
validate-pluginfully static. - + Prevention rule stops regressions instead of relying on discipline.
- − Every plugin with tools handlers needs a build-config change (one-time; mitigated by template).
- − Three entries per plugin is more boilerplate.
- Alternative rejected: rely on consumer-side tree-shaking of unused tools exports — fragile
(side-effectful import chains;
compiler-*can still be traced into the bundle).
Resolved Decisions
- Runtime vs tools split (supersedes the validator-only framing).
index.js(.) is serve-time and compiler-free; all tools-time handlers (validators, commands, agent-kit, setup) move behind a single./toolsentry (lib/tools.ts→dist/tools.js), which may depend on the compiler. - Actions vs commands. Actions are the serving primitive (
., compiler-free); CLI commands are the tools primitive (./tools, compiler OK). A handler that needs the compiler must be a command, not an action. - OQ1 → peerDependency.
compiler-*used by./toolsis apeerDependency(provided by the toolchain at tools time) +devDependencyfor the plugin's own build/test. - OQ2 → clean cut for all tools. No
.-fallback anywhere. Validators, commands, agent-kit, and setup load only from./tools; any plugin declaring one must expose./tools. Actions stay on.. - Detection = static text scan. Interactivity via
withInteractiveMark(in the server bundle; leak via@jay-framework/compiler-indist/index.js. No dynamic import of plugin code.
Implementation Results
Implemented on branch dl179-180-compiler-free-runtime (paired with DL#180). yarn confirm
(rebuild + type-check + test + format) passes clean.
The ./tools entry split
Every in-repo plugin with tools-time handlers gained a third entry alongside . and ./client:
| Entry | File | Phase | Compiler |
|---|---|---|---|
. |
lib/index.ts |
serve | ❌ free |
./client |
lib/index.client.ts |
client | ❌ free |
./tools |
lib/tools.ts |
tools | ✅ OK |
Migrated plugins (each got lib/tools.ts, a ./tools package export, a tools SSR entry in
vite.config.ts, build:types covering lib/tools.ts, and compiler-* moved from dependencies
to peerDependencies + devDependencies): design-system-validator, a11y-validator,
seo-validator, data-files, gemini-agent, ui-kit. webmcp needs no ./tools (global init
on ., compiler-free).
Compiler externals in each vite.config.ts switched from string lists to the regex
/^@jay-framework\/compiler-/ so the leak scan holds regardless of which compiler subpackage a tools
handler pulls in.
Loaders switched to ./tools (clean cut, no . fallback)
stack-server-build/lib/plugin-commands.ts— CLI command handlers load from<pkg>/tools.stack-server-build/lib/plugin-setup.ts—setup/agentkithandlers load from<pkg>/tools(error messages name the tools entry).plugin-validator— validators / setup / agentkit resolve against the./toolsexport.
Capability-aware validate-plugin
plugin-validator/lib/validate-plugin.ts now derives required exports from declared capabilities:
- ≥1 capability rule (a
global: trueplugin counts only if it exports a resolvableinit/setupfrom.). ./toolsrequired iff any tools capability (validators,commands,agentkit,setup) or adevOnlyaction is declared (needsToolsEntry)../clientrequired gated on interactive-phase detection (detectInteractivePhase, a static scan forwithInteractiveMark(in the.bundle) or a declaredcontextscapability — replaces the old unconditional./clientwarning.- Leak scan (
validateNoCompilerLeak, newcompiler-leakerror type): text-scansdist/index.jsfor@jay-framework/compiler-.
checkExportExists / validateHandlerRef gained an EntryKey (. | ./tools | ./client)
parameter so each capability is checked against the correct entry, with suggestions naming the
correct source file. 19 tests pass. stack-cli must be rebuilt after plugin-validator changes — it
bundles plugin-validator via vite build.
Verification
design-system-validator:dist/index.js= 0.98 kB, 0 compiler references;dist/tools.js= 69 kB with the externalized compiler imports.jay-stack-cli validate-plugin→ success.- All 8 in-repo plugins validate under the new rules (a11y-validator emits only a pre-existing agent-kit-files warning unrelated to this work).
Deviations from the original design
None material. The design's "validator entry split" framing was superseded (in-file, before implementation) by the broader runtime-vs-tools split covering commands / agent-kit / setup as well; implementation follows that resolved design.
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.