* fix(world-local): prevent path traversal via request-supplied IDs (#1829)
* fix(world-local): prevent path traversal via request-supplied IDs
Request-supplied identifiers (runId, eventId, stepId, hookId, correlationId,
stream names, and tags) flowed directly into path.join() calls, allowing a
client to send values like '../../../package' and cause the backend to read
or write files outside the workflow data directory.
Add a centralized validator (assertSafeEntityId) that rejects IDs which are
empty, start with '.', or contain path separators or NUL bytes. Apply it at
each storage-layer entry point that composes IDs into filesystem paths:
fs.taggedPath / readJSONWithFallback / paginatedFileSystemQuery, the runs /
steps / events / hooks storage methods, and the streamer.
* address review feedback
- UnsafeEntityIdError now extends WorkflowWorldError for consistency with
other storage-layer errors and the platform error-to-HTTP mapping.
- Add resolveWithinBase(basedir, ...segments) containment helper and
apply it at every taggedPath / readJSONWithFallback / .locks path
construction site in events-storage and legacy, so a forgotten
assertSafeEntityId at a future call site can't silently regress.
- Truncate attacker-controlled values in the error message.
- Drop unused assertSafeEntityIds helper and the unreachable typeof
check under the TS signature.
- Fix docstrings on assertSafeEntityId / taggedPath JSDoc example /
filePrefix validation comment to match what the code actually does.
- handleLegacyEvent now re-asserts runId locally so the invariant is
documented at the call site instead of implicitly inherited from
events.create.
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
* fix(world-local): tighten ID validation and add streamer regression tests
Addresses code review feedback on the path-traversal backport:
- Reject dots inside entity IDs in `assertSafeEntityId`. Internal IDs
(ULIDs, step_N, etc.) never contain dots, but `stripTag()` /
`getObjectCreatedAt()` strip a trailing `.[tag]` suffix from filenames,
so a request-supplied runId like `wrun_123.foo` would be silently
mangled during listing/pagination.
- Reject empty `correlationId` on events that include one. The event
schemas only require `z.string()`, so without this check a
step_created / hook_created / wait_created request with
`correlationId: ''` would silently be written under a malformed
composite key like `${runId}-`.
- Add streamer regression tests covering writeToStream, closeStream,
listStreamsByRunId, and getStreamChunks (the v4-shape surface that
this backport touches independently of main).
---------
Co-authored-by: JJ Kasper <jj@jjsweb.site>
Turborepo replays nextjs-turbopack:build from cache without restoring the
Vercel diagnostics manifest (.vercel/output/diagnostics/workflows-manifest.json),
which causes the Vercel deployment to fail post-build. Add .vercel/output/**
to the workbench's Turbo outputs so it is persisted and replayed. Applies to
both nextjs-turbopack and nextjs-webpack (whose turbo.json is a symlink).
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Per-deployment SDK tarballs are now built by the tarballs/ app, not by
docs/. Remove the outdated reference to docs/scripts/pack.ts (which no
longer exists) and point readers at tarballs/README.md.
The fixture was introduced by the backport of #1935 (#1945) with the
pre-namespacing step ID "step//./input//step" in the __internal_workflows
manifest comment. The subsequent backport of #1944 (#1946) shipped the
namespacing fix in lib.rs but did not update this fixture, since on main
the two PRs landed in the reverse order and #1944's diff already covered
the fixture there.
Update the expected output to match what the plugin now emits:
"step//./input//outer/step" — same value already present in main.
* [ci] Attribute backport changelog entries to original PR author
`@changesets/changelog-github` resolves a changeset commit to its
associated PR via the GitHub GraphQL `associatedPullRequests` field. For
commits landed on `stable` via our backport workflow, that resolves to
the backport PR authored by `github-actions[bot]` (since the backport
workflow uses `createCommitOnBranch` to produce signed commits), so the
generated changelog ends up with "Thanks @github-actions!" instead of
the original contributor.
This adds a small `.changeset/changelog.mjs` wrapper around
`@changesets/changelog-github` that detects backport PRs by matching the
title (`Backport #N: ...`) or body (`Automated backport of #N to
` + '`stable`' + `...`) produced by `.github/workflows/backport.yml`, resolves the
original PR number, and injects `pr:`/`commit:` directives into the
changeset summary before delegating to the upstream generator. The
result is that the rendered changelog entry attributes the change to the
original PR and author, while the commit link still points at the
backport commit on the release branch.
* Address review feedback
- Use `Bearer` instead of `Token` for the GitHub GraphQL auth header
for consistency with the rest of the repo (Copilot review on PR #2091).
- Add a defensive `formatError` helper so `console.warn` in the catch
blocks doesn't itself throw when a non-Error value is thrown (Copilot
review on PR #2091).
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* remove toast on succesful decrypt
* show hook name
* fix hook on detail view
Signed-off-by: Mitul Shah <mitulxshah@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* [swc-plugin] Preserve imports referenced by hoisted nested steps
Dead-code elimination ran before nested step functions were hoisted out of workflow bodies, so imports referenced only by hoisted step bodies were incorrectly stripped from the step bundle, causing a ReferenceError at runtime. Move DCE to run after hoisting in visit_mut_program.
* [swc-plugin] Namespace nested step IDs under non-exported workflow functions
Anonymous steps nested inside callback properties of a non-exported workflow function were registered with an unnamespaced step ID in step mode while the workflow-mode proxy looked them up under the workflow function name, causing a runtime 'step not found' failure. Set current_workflow_function_name in visit_mut_fn_decl for non-exported workflow functions to match the behavior in visit_mut_export_decl. Also clarify the fixture comment to distinguish step-mode and workflow-mode behavior per reviewer feedback.
* [swc-plugin] Namespace nested step IDs across all workflow declaration shapes
Extends the previous fix to cover all three non-exported workflow declaration forms (async function decl, const arrow, const fn-expr) by visiting the workflow body with workflow context before replacing it, and corrects the __internal_workflows manifest comment to report the same prefixed step IDs that are registered at runtime and looked up by the workflow-mode WORKFLOW_USE_STEP proxy. Adds a dedicated regression fixture covering all three shapes.
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
* [swc-plugin] Capture lexical `this` for nested arrow step functions
When a nested arrow `"use step"` references the enclosing function/method's
`this`, plumb that `this` through the workflow runtime so the step body
sees the correct receiver.
- Workflow mode wraps the step proxy with `.bind(this)`, so invoking the
proxy captures the caller's `this` as `thisVal` on the queue item.
- Step mode hoists the body as a regular `function` (not an arrow) so the
runtime's `stepFn.apply(thisVal, args)` rebinds `this` inside the
hoisted body.
Detection only fires for arrows, since arrows inherit `this` lexically.
Nested non-arrow functions/methods/getters/setters introduce their own
`this`, so the detector stops at those boundaries.
The runtime already supported `thisVal` for instance-method steps; this
PR is purely a compiler change to feed the existing pipeline.
Caveat: capture works at runtime only when the captured value is
serializable across the workflow->step boundary (i.e. the enclosing
class implements `WORKFLOW_SERIALIZE`/`WORKFLOW_DESERIALIZE`).
Refs vercel/workflow#1865
* Address PR review: preserve step proxy metadata + tighter `this` detection
- core: Override `.bind` on step proxies so the bound function retains
`stepId` and `__closureVarsFn`. Without this, a bound proxy that flows
through workflow serialization (e.g. as a step argument) would be
treated as a non-serializable plain function by `getStepFunctionReducer`.
- swc-plugin: Detector now also walks `arrow.params` so `this` references
in default values / destructuring initializers (e.g. `(x = this.foo) =>
...`) trigger the `.bind(this)` path.
- swc-plugin: Class bodies inside the arrow body are now treated as
`this`-binding boundaries — `this` inside class field initializers,
methods, etc. is bound to the class instance, not the outer arrow. The
detector still walks `extends` clauses and computed property keys
because those are evaluated in the surrounding scope.
- spec.md: Sharpen the note about `this` in step bodies — it's
syntactically allowed but only meaningful for instance-method steps and
lexical-`this` arrow steps; other shapes compile but `this` will be
whatever the caller of the step proxy passes.
- Add `lexical-this-detector-edge-cases` fixture covering both the
default-param positive case and the inner-class false-positive guard.
- Strengthen the runtime test to assert `stepId` / `__closureVarsFn`
survive `.bind(...)`.
* [swc-plugin] Fix `arguments` closure-var capture; drop dead `this`/`arguments` checks
- Add `arguments` to `is_global_identifier` so it's not captured as a
closure variable. Previously a nested `function`-form step like
function step() { 'use step'; return arguments[0]; }
was hoisted with `const { arguments } = ...` (a strict-mode syntax
error) and the body's `arguments[0]` resolved against the destructured
binding instead of the function's intrinsic `arguments` object.
- Remove dead `ForbiddenExpression` checks for `this` and `arguments` in
`visit_mut_this_expr` / `visit_mut_ident`. The `'use step'` /
`'use workflow'` directives are stripped during the module-level
traversal before children are visited, so `in_step_function` /
`in_workflow_function` are never observed as true here in practice.
The existing `step-with-this-arguments-super` fixture explicitly
documents that all three identifiers are allowed in step bodies.
- Tighten the spec note about `arguments` accordingly: it works in
`function`-form steps (reflecting positional args) but is not captured
for arrow-form steps; use `...args` for that case.
- Add `nested-step-arguments` fixture pinning down the new behavior.
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
* Update workflow-trace-view.tsx
* Update trace viewer layout to be in a row
---------
Signed-off-by: Mitul Shah <mitulxshah@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Adds a unique partial index on workflow_events(run_id, correlation_id, type)
filtered to step_created/hook_created/wait_created, and translates the
resulting unique-violation (pg code 23505, surfaced via DrizzleQueryError.cause)
into EntityConflictError. The steps table already deduped via
onConflictDoNothing, but the event row still inserted, leaving duplicate
events in the log. Now both rows are kept consistent and the runtime's
existing dedup catch path handles concurrent writers cleanly.
(cherry picked from commit 7c45e9e213)
Co-authored-by: Nathan Rajlich <n@n8.io>
Sets pnpm's `minimumReleaseAge` to 2 days (company-wide standard) and
excludes internal scopes from the gate.
Signed-off-by: Karthik Kalyan <105607645+karthikscale3@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(world-postgres): bootstrap graphile-worker schema in setup CLI
`workflow-postgres-setup` now installs the `graphile_worker` schema in
addition to the drizzle migrations so that by the time any consumer
calls `world.start()`, both schemas already exist. This eliminates the
inter-process race on graphile-worker's `installSchema` where
concurrent `CREATE SCHEMA IF NOT EXISTS` calls could both pass the
MVCC-snapshotted existence check and one would fail with
`duplicate key value violates unique constraint "pg_namespace_nspname_index"`.
Reproduced locally against a fresh postgres:18-alpine with 8 parallel
`makeWorkerUtils().migrate()` calls — 7/8 fail without the pre-bootstrap,
0/8 fail after running `workflow-postgres-setup` first.
* Apply suggestions from code review
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
No need to version this app, it doesn't get published to npm.
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* Fix stable CI test harness failures
* Fix dev e2e cleanup races
* Restore non-Next dev cleanup
* Keep Next dev temp workflow files intact
* Keep dev test placeholders on disk
* Speed up workflow port detection
* Probe workflow health with POST
* Support HEAD workflow health checks
* Stabilize local CI health checks
* Retry flaky Vercel agent e2e
* Wrap generated framework route exports
* Relax remote addTen e2e timeout
* Materialize manual webhook responses
* Give remote CLI inspect more time
* Stabilize remote sleep and hook e2e checks
* Wait for step return streams before completion
* Stabilize hook and stream e2e waits
* Bound queue health check timeouts
* Address stable CI review feedback
* Fix route export replacement with embedded source maps
The AI SDK renamed `Experimental_Agent` to `ToolLoopAgent`. Update the
"Building Durable AI Agents" page's API route snippet (v4 and v5) so it
matches the current AI SDK API.
Signed-off-by: Karthik Kalyan <105607645+karthikscale3@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* Warn when serverExternalPackages hides workflow-enabled packages
Add a build-time warning when packages in serverExternalPackages contain
workflow code ('use step', 'use workflow', or serialization classes).
These packages are completely invisible to the workflow compiler when
externalized, causing silent runtime failures.
The warning detects workflow patterns via two methods:
- Fast path: check package.json dependencies for @workflow/serde
- Thorough path: read the package entry file and run pattern detection
Also adds documentation in the serialization guide about the
externalization footgun for 3rd-party packages.
* Auto-remove workflow packages from serverExternalPackages
When workflow-enabled dependencies are externalized in Next.js, compiler transforms are skipped and runtime failures follow. Detect those packages in withWorkflow, remove them from serverExternalPackages for the current build, and keep a generalized externalPackages warning fallback for non-Next builders.
* Address review feedback: add entry-point limitation comment and missing test case
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* tarballs: redesign preview tarballs index page
Rebuild the static index page produced by `tarballs/scripts/pack.ts`:
- Featured `workflow` package up top with prominent install command,
copy button, and direct tarball download
- Top-of-page metadata chips: short SHA (linked to commit), branch,
PR number, build timestamp, package count + total size
- Collapsible "What is this?" explainer
- Package-manager tab toggle (pnpm / npm / yarn / bun) that swaps the
install command for every row in place
- Live filter input over the rest of the package list (with `/` shortcut)
- Per-row install command, copy button, and direct download
- Modern dark/light theme with system preference, Geist-inspired styling
Also captures tarball size during pack and renders human-readable byte counts.
* tarballs: fix client-side interactivity broken by HTML-encoded JSON
`escapeHtml(JSON.stringify(catalog))` was HTML-encoding every quote in
the embedded catalog JSON to `"`, so `JSON.parse(textContent)` threw
on the first character and the IIFE bailed before attaching any event
listeners — package-manager toggle, search filter, copy buttons, and the
`/` shortcut were all dead UI on the deployed page.
`<script type="application/json">` content is treated as text by the HTML
parser; the only sequence that can break out is `</script>` (or `</`
in legacy parsers). Replace `<` with the JSON `<` escape, which is
legal per the JSON spec and prevents the breakout without needing entity
encoding.
Also switch `formatBytes` from `KB`/`MB` to `KiB`/`MiB` since the
divisor is 1024.
* tarballs: rewrite as Vite + Preact SPA with file breakdown, fix bundling
Address TooTallNate's review feedback by replacing the hand-rolled HTML-
in-template-literal approach with a small Vite + Preact SPA. The old
~600 lines of inlined HTML/CSS/JS in `pack.ts` is now `~80 lines of TSX`,
fully type-checked.
Layout:
- `tarballs/index.html`, `vite.config.ts`, `tsconfig.json` at the root
- `src/main.tsx` mounts the Preact app and fetches `/catalog.json`
- `src/app.tsx` is the page (Header, FeaturedCard, PackageRow, etc.)
- `src/catalog.ts` is the shared types + helpers (`buildInstallCommand`,
`formatBytes`)
- `src/icons.tsx`, `src/styles.css`
- `scripts/pack.ts` is now data-only — it scans packages, packs
tarballs, and writes `public/catalog.json`
The eliminates several smells the reviewer called out:
- The interactive script is now TypeScript with strict mode and JSX
type checking instead of an inline `<script>` block
- The `escapeHtml`-around-JSON-blob hack that broke client-side JS in
the prior commit is gone; the SPA fetches `catalog.json` and parses
it natively
- Pack-time logic and presentation logic no longer share a file
While verifying real tarball sizes I noticed `workflow-serde.tgz` was
only 828 bytes — it had `package.json`, `LICENSE.md`, `README.md` and
*nothing* else, because each package's `files: ["dist"]` excludes
sources but `dist/` hadn't been built. The Vercel build was running
`pnpm --filter tarballs build`, which only builds the `tarballs`
package itself — its workspace dependencies were never built.
Switch `vercel.json#buildCommand` to `pnpm turbo run build
--filter=tarballs`, which transitively builds dependencies first via
the `dependsOn: ["^build"]` rule already in the root `turbo.json`. With
the fix:
workflow: 241 KiB → 252 KiB tarball, 916 KiB unpacked, 205 files
@workflow/core: 59 KiB → 493 KiB tarball, 1.70 MiB unpacked, 236 files
@workflow/serde: 828 B → 1.4 KiB tarball, 4.6 KiB unpacked, 7 files
Add a smoke check that the `workflow` package has at least 5 files in
its tarball — catches the regression directly.
`pack.ts` now also runs `tar -tvzf` on each tarball and records the
file list with sizes. The SPA renders this as an expandable
"What's inside?" disclosure per package, grouped by top-level
directory (e.g. `dist/`, `docs/`) with proportional bars showing
each group's share of the unpacked size, and the largest files
listed below.
* tarballs: replace tar shell-out with in-process tar reader
The smoke check broke in CI: `'workflow' tarball only has 0 files`.
Root cause is that `tar -tvzf` emits a different verbose layout on GNU
tar (Linux, what CI runs) vs BSD tar (macOS, where I tested locally) —
the parser only matched the BSD column ordering, so on Linux every line
was rejected and `fileCount` came out as 0.
Replace the shell-out with a small in-process tar reader using
`zlib.gunzipSync` + manual 512-byte block walk. ustar headers are
trivially structured (name at offset 0, octal size at 124, typeflag at
156, ustar prefix at 345). We emit regular files only (`typeflag` `0`
or NUL) and consume but skip pax extended headers (`x`/`g`) and GNU
long-name entries (`L`). Result is identical on every platform.
Verified locally: 206 files / 998413 bytes for `workflow.tgz` matches
`tar -tvzf` exactly.
* tarballs: redesign per-package details with packagephobia-style stats
The previous "What's inside?" view crammed nested directory groups,
proportional bars, and per-group file lists into a `<details>` inside
an already-narrow row. It was hard to read and harder to compare.
Replace it with the layout packagephobia uses on its result page:
- Two large headline metric tiles (Publish size / Unpacked size)
with a big bold value, smaller unit, and small uppercase label.
Modeled directly on packagephobia's `Stats` component but using
our existing CSS variables so it tracks light/dark theme.
- A single sortable file table beneath. Default is size-descending so
the contributors to package size are immediately visible. Click a
header to flip direction or switch sort key. Sticky header keeps
the columns visible inside the scrollable region.
Drop the `groupByTopLevel`, `ContentsGroup`, and bar-chart styles —
they were the source of the "hard to use" feedback and don't add
information that the flat sortable table doesn't already convey.
* tarballs: address Copilot review feedback (a11y, dev script, caching)
- main.tsx: drop `cache: 'no-store'` from the catalog fetch. Each
tarballs deployment is immutable per commit, so HTTP caching is
appropriate; forcing no-store made every visit re-download the full
catalog (which now includes per-package file lists).
- app.tsx (search input): add `aria-label="Filter packages"`. The
visible label only contained an icon and placeholder, so screen
readers had no name for the control.
- app.tsx (PmTabs): replace `role="tablist"` / `role="tab"` /
`aria-selected` with plain buttons that use `aria-pressed`. The
ARIA tab pattern requires arrow-key roving focus we never wired
up; toggle buttons are the honest representation. Each button
also gets an explicit `aria-label`.
- app.tsx (row buttons): include the package name in the accessible
label of every per-row copy/download button (and on the featured
card too), so the screen reader buttons/links list distinguishes
them. Added an `accessibleName` prop to `CopyButton`.
- app.tsx (CopyButton): only flip to the "Copied" state when the
write actually succeeded. Both the modern `navigator.clipboard`
path and the `execCommand` fallback can fail; the new
`writeToClipboard` helper returns success and the button shows a
short "Failed" state if both paths fail.
The previous `dev: vite` couldn't actually serve the page because
`/catalog.json` 404s and the SPA boots into the error fallback.
Restructure the build layout to vite's conventional shape:
- `public/` is now a true vite public dir — pack writes tarballs and
catalog.json there. In dev, vite serves these at the root.
- `dist/` is the production build output (vite copies public/ into it
and adds index.html + assets/).
- `vercel.json#outputDirectory` switches from `public` → `dist`.
- `turbo.json` outputs updated to match.
- `dev` chains pack before vite so the catalog exists when the dev
server starts.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: upgrade pnpm/action-setup to v5 and read version from package.json (#1785)
* ci: upgrade pnpm/action-setup to v6 and read version from package.json
Removes hardcoded pnpm version (10.14.0) from all workflows and instead
reads the version from the packageManager field in package.json, so CI
stays in sync with the version used locally.
* ci: update setup-workflow-dev composite action to use pnpm/action-setup@v6
Also removes the pnpm-version input since the action now reads the
version from package.json#packageManager.
* ci: downgrade pnpm/action-setup to v5
v6 installs pnpm 11 RC/beta, which has a regression
(pnpm/pnpm#11264, pnpm/action-setup#225/#227/#228) that causes
'ERR_PNPM_BROKEN_LOCKFILE: expected a single document in the stream'
when the project's packageManager pins a 10.x pnpm version. v5 is the
latest stable release before v6 and supports reading the version from
package.json#packageManager.
* ci: stop using Release App token in release workflows (#1866)
The Release App has been temporarily removed. Switch the Release and
Backport workflows to use the default GITHUB_TOKEN, and disable the
cross-repo Front dispatch workflow until the App is restored.
Also add a workflow_dispatch trigger to release.yml so the Version
Packages PR can be created/updated manually (since pushes made by
GITHUB_TOKEN do not trigger downstream workflow runs).
* ci: use GitHub API commit mode for changesets action (#1867)
The repo enforces "Commits must have verified signatures" via an
org/enterprise-level ruleset, which blocks unsigned commits pushed via
the Git CLI by GITHUB_TOKEN. Switching the changesets action to
commitMode: github-api makes commits GPG-signed by GitHub.
* Add changeset for backport
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Nathan Rajlich <n@n8.io>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Bump vite (#1827)
* test: enable step source-map assertions for vite local dev (#1862)
* test: enable step source-map assertions for vite local dev
Vite ^7.3.2 (bumped in #1827) preserves step bundle source maps in
dev mode, so stack traces now contain original file paths. Update
hasStepSourceMaps() so vite returns true in local dev and stays false
only in local prod, fixing the consistently failing 'basic step error'
and 'cross-file step error' e2e tests.
* chore: drop body from empty changeset
* address review: drop redundant vite local-prod guard
The default `!DEV_TEST_CONFIG` fall-through already returns false for
vite local prod, so the vite-specific guard is dead code. Just remove
the vite block entirely now that vite local dev matches the default
'has source maps' behavior.
---------
Co-authored-by: Karthik Kalyan <105607645+karthikscale3@users.noreply.github.com>
The skills/ directory is not maintained on stable — it is only kept
current on main. Removing these files prevents stale skills from
being packaged with stable releases and matches how docs/ is handled.