mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-07-04 03:19:38 +08:00
Compare commits
16 Commits
svelte-cod
...
@sveltejs/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5ce7437c4 | ||
|
|
2f422ee190 | ||
|
|
14f087cd7a | ||
|
|
1ef5ddf605 | ||
|
|
0e55ee792d | ||
|
|
84ec24b6f6 | ||
|
|
710cebe539 | ||
|
|
b2a380c4ce | ||
|
|
eef0a9b4d9 | ||
|
|
260b36e8af | ||
|
|
6df3ebe568 | ||
|
|
27a2fc5653 | ||
|
|
5cd99d8234 | ||
|
|
e9f19199cb | ||
|
|
480b46df0a | ||
|
|
5f5fb27977 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
publish-mcp:
|
||||
needs: release
|
||||
if: contains(needs.release.outputs.publishedPackages, '"@sveltejs/ai-tools"')
|
||||
if: contains(needs.release.outputs.publishedPackages, '"@sveltejs/mcp"')
|
||||
uses: ./.github/workflows/publish-mcp.yml
|
||||
secrets:
|
||||
MCP_KEY: ${{ secrets.MCP_KEY }}
|
||||
|
||||
116
.github/workflows/sync-docs-skills.yml
vendored
Normal file
116
.github/workflows/sync-docs-skills.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
name: Sync Skills
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
sync-skills:
|
||||
if: github.repository == 'sveltejs/ai-tools'
|
||||
name: Sync skills from svelte.dev
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: false # pnpm is not installed yet
|
||||
|
||||
- name: Install pnpm
|
||||
shell: bash
|
||||
run: |
|
||||
PNPM_VER=$(jq -r '.packageManager | if .[0:5] == "pnpm@" then .[5:] else "packageManager in package.json does not start with pnpm@\n" | halt_error(1) end' package.json)
|
||||
echo installing pnpm version "$PNPM_VER"
|
||||
npm i -g "pnpm@$PNPM_VER"
|
||||
|
||||
- name: Setup Node.js with pnpm cache
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: 24
|
||||
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||
|
||||
- name: Clone svelte.dev
|
||||
run: git clone --depth 2 https://github.com/sveltejs/svelte.dev.git "${{ runner.temp }}/svelte.dev"
|
||||
|
||||
- name: Discover changed skill files
|
||||
id: discover
|
||||
env:
|
||||
SVELTE_DEV_ROOT: ${{ runner.temp }}/svelte.dev
|
||||
run: |
|
||||
skill_files=$(git -C "$SVELTE_DEV_ROOT" diff --name-only --diff-filter=ACMR HEAD~1 HEAD | grep '^apps/svelte.dev/content/docs/.*\.md$' | xargs -I{} grep -l '^skill: *true' "$SVELTE_DEV_ROOT/{}" || true)
|
||||
echo "skill_files=$skill_files" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Sync skills
|
||||
if: steps.discover.outputs.skill_files != ''
|
||||
env:
|
||||
SVELTE_DEV_ROOT: ${{ runner.temp }}/svelte.dev
|
||||
DOCS_PREFIX: apps/svelte.dev/content/docs/
|
||||
run: |
|
||||
for full_path in ${{ steps.discover.outputs.skill_files }}; do
|
||||
file="${full_path#$SVELTE_DEV_ROOT/}"
|
||||
name=$(grep '^name: ' "$full_path" | head -1 | sed 's/^name: *//')
|
||||
repo="${file#$DOCS_PREFIX}"
|
||||
repo="${repo#/}"
|
||||
repo="${repo%%/*}"
|
||||
|
||||
output_dir="tools/skills/$name"
|
||||
rm -rf "$output_dir"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
pnpm resolve-references --file "$full_path" --repo "$repo" --output "$output_dir"
|
||||
done
|
||||
|
||||
- name: Sync plugins
|
||||
if: steps.discover.outputs.skill_files != ''
|
||||
run: |
|
||||
pnpm sync-claude-plugin
|
||||
pnpm sync-cursor-plugin
|
||||
pnpm sync-opencode-plugin
|
||||
pnpm generate-skill-docs
|
||||
pnpm bump-plugin-versions
|
||||
|
||||
- name: Check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git diff --exit-code -- tools/skills/ plugins/ packages/opencode/ documentation/docs/ || echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.git-check.outputs.changed == 'true'
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: sync skills from svelte.dev'
|
||||
branch: chore/sync-skills
|
||||
delete-branch: true
|
||||
title: 'chore: sync skills from svelte.dev'
|
||||
body: |
|
||||
## Summary
|
||||
Automatically synced skill markdown from `sveltejs/svelte.dev` into `tools/skills/`.
|
||||
|
||||
## Changes
|
||||
- Cloned `sveltejs/svelte.dev`
|
||||
- Filtered markdown files with `skill: true` frontmatter
|
||||
- Rebuilt synced skill folders with `scripts/resolve-references.ts`
|
||||
- Synced `plugins/claude/svelte/` (skills, agents)
|
||||
- Synced `plugins/cursor/svelte/` (skills, agents, rules)
|
||||
- Synced `packages/opencode/` (skills, instructions)
|
||||
- Updated documentation
|
||||
|
||||
## Generated by
|
||||
GitHub Action: Sync Skills
|
||||
labels: |
|
||||
chore
|
||||
automated
|
||||
16
.github/workflows/sync-plugins.yml
vendored
16
.github/workflows/sync-plugins.yml
vendored
@@ -46,21 +46,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||
|
||||
- name: Sync Claude plugin
|
||||
run: pnpm sync-claude-plugin
|
||||
|
||||
- name: Sync Cursor plugin
|
||||
run: pnpm sync-cursor-plugin
|
||||
|
||||
- name: Sync OpenCode plugin
|
||||
run: pnpm sync-opencode-plugin
|
||||
- name: Sync plugins
|
||||
run: pnpm sync-plugins
|
||||
|
||||
- name: Generate skills documentation
|
||||
run: pnpm generate-skill-docs
|
||||
|
||||
- name: Bump plugin versions for changed plugins
|
||||
run: pnpm bump-plugin-versions
|
||||
|
||||
- name: Check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
@@ -69,6 +60,7 @@ jobs:
|
||||
plugins/cursor/svelte/ \
|
||||
packages/opencode/skills/ \
|
||||
packages/opencode/instructions/ \
|
||||
packages/opencode/schema.json \
|
||||
documentation/docs/ \
|
||||
|| echo "changed=true" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -90,7 +82,7 @@ jobs:
|
||||
## Changes
|
||||
- Synced `plugins/claude/svelte/` (skills, agents with `permissionMode`)
|
||||
- Synced `plugins/cursor/svelte/` (skills, agents, rules)
|
||||
- Synced `packages/opencode/` (skills, instructions)
|
||||
- Synced `packages/opencode/` (skills, instructions, schema)
|
||||
- Updated documentation
|
||||
|
||||
## Generated by
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "**/references/*.md",
|
||||
"options": {
|
||||
"embeddedLanguageFormatting": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ To get the most out of the MCP server we recommend including the following promp
|
||||
|
||||
> [!NOTE] This is already setup for you when using `npx sv add mcp`
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
````markdown
|
||||
@include .generated/agents.md
|
||||
````
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
If your MCP client supports it, we also recommend using the [svelte-task](prompts#svelte-task) prompt to instruct the LLM on the best way to use the MCP server.
|
||||
|
||||
@@ -5,10 +5,22 @@ This prompt should be used whenever you are asking the model to work on a Svelte
|
||||
<details>
|
||||
<summary>Copy the prompt</summary>
|
||||
|
||||
```md
|
||||
<!-- prettier-ignore-start -->
|
||||
````markdown
|
||||
You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool `get-documentation` with one of the following paths. However: before invoking the `get-documentation` tool, try to answer the users query using your own knowledge and the `svelte-autofixer` tool. Be mindful of how many section you request, since it is token-intensive!
|
||||
<available-docs>
|
||||
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: ai/overview
|
||||
- title: Local setup, use_cases: use title and path to estimate use case, path: ai/local-setup
|
||||
- title: Remote setup, use_cases: use title and path to estimate use case, path: ai/remote-setup
|
||||
- title: Tools, use_cases: use title and path to estimate use case, path: ai/tools
|
||||
- title: Resources, use_cases: use title and path to estimate use case, path: ai/resources
|
||||
- title: Prompts, use_cases: use title and path to estimate use case, path: ai/prompts
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: ai/plugin
|
||||
- title: Subagent, use_cases: use title and path to estimate use case, path: ai/subagent
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: ai/opencode-plugin
|
||||
- title: Subagent, use_cases: use title and path to estimate use case, path: ai/opencode-subagent
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: ai/skills
|
||||
- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview
|
||||
- title: Frequently asked questions, use_cases: project setup, initializing new svelte projects, troubleshooting cli installation, package manager configuration, path: cli/faq
|
||||
- title: sv create, use_cases: project setup, starting new sveltekit app, initializing project, creating from playground, choosing project template, path: cli/sv-create
|
||||
@@ -18,7 +30,7 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
||||
- title: devtools-json, use_cases: development setup, chrome devtools integration, browser-based editing, local development workflow, debugging setup, path: cli/devtools-json
|
||||
- title: drizzle, use_cases: database setup, sql queries, orm integration, data modeling, postgresql, mysql, sqlite, server-side data access, database migrations, type-safe queries, path: cli/drizzle
|
||||
- title: eslint, use_cases: code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects, path: cli/eslint
|
||||
- title: lucia, use_cases: authentication, login systems, user management, registration pages, session handling, auth setup, path: cli/lucia
|
||||
- title: better-auth, use_cases: use title and path to estimate use case, path: cli/better-auth
|
||||
- title: mcp, use_cases: use title and path to estimate use case, path: cli/mcp
|
||||
- title: mdsvex, use_cases: blog, content sites, markdown rendering, documentation sites, technical writing, cms integration, article pages, path: cli/mdsvex
|
||||
- title: paraglide, use_cases: internationalization, multi-language sites, i18n, translation, localization, language switching, global apps, multilingual content, path: cli/paraglide
|
||||
@@ -29,6 +41,7 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
||||
- title: tailwindcss, use_cases: project setup, styling, css framework, rapid prototyping, utility-first css, design systems, responsive design, adding tailwind to svelte, path: cli/tailwind
|
||||
- title: vitest, use_cases: testing, unit tests, component testing, test setup, quality assurance, ci/cd pipelines, test-driven development, path: cli/vitest
|
||||
- title: add-on, use_cases: use title and path to estimate use case, path: cli/add-on
|
||||
- title: sv-utils, use_cases: use title and path to estimate use case, path: cli/sv-utils
|
||||
- title: Introduction, use_cases: learning sveltekit, project setup, understanding framework basics, choosing between svelte and sveltekit, getting started with full-stack apps, path: kit/introduction
|
||||
- title: Creating a project, use_cases: project setup, starting new sveltekit app, initial development environment, first-time sveltekit users, scaffolding projects, path: kit/creating-a-project
|
||||
- title: Project types, use_cases: deployment, project setup, choosing adapters, ssg, spa, ssr, serverless, mobile apps, desktop apps, pwa, offline apps, browser extensions, separate backend, docker containers, path: kit/project-types
|
||||
@@ -96,17 +109,6 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
||||
- title: Configuration, use_cases: project setup, configuration, adapters, deployment, build settings, environment variables, routing customization, prerendering, csp security, csrf protection, path configuration, typescript setup, path: kit/configuration
|
||||
- title: Command Line Interface, use_cases: project setup, typescript configuration, generated types, ./$types imports, initial project configuration, path: kit/cli
|
||||
- title: Types, use_cases: typescript, type safety, route parameters, api endpoints, load functions, form actions, generated types, jsconfig setup, path: kit/types
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: mcp/overview
|
||||
- title: Local setup, use_cases: use title and path to estimate use case, path: mcp/local-setup
|
||||
- title: Remote setup, use_cases: use title and path to estimate use case, path: mcp/remote-setup
|
||||
- title: Tools, use_cases: use title and path to estimate use case, path: mcp/tools
|
||||
- title: Resources, use_cases: use title and path to estimate use case, path: mcp/resources
|
||||
- title: Prompts, use_cases: use title and path to estimate use case, path: mcp/prompts
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: mcp/plugin
|
||||
- title: Skill, use_cases: use title and path to estimate use case, path: mcp/skill
|
||||
- title: Subagent, use_cases: use title and path to estimate use case, path: mcp/subagent
|
||||
- title: Overview, use_cases: use title and path to estimate use case, path: mcp/opencode-plugin
|
||||
- title: Subagent, use_cases: use title and path to estimate use case, path: mcp/opencode-subagent
|
||||
- title: Overview, use_cases: always, any svelte project, getting started, learning svelte, introduction, project setup, understanding framework basics, path: svelte/overview
|
||||
- title: Getting started, use_cases: project setup, starting new svelte project, initial installation, choosing between sveltekit and vite, editor configuration, path: svelte/getting-started
|
||||
- title: .svelte files, use_cases: always, any svelte project, component creation, project setup, learning svelte basics, path: svelte/svelte-files
|
||||
@@ -154,6 +156,7 @@ You are a Svelte expert tasked to build components and utilities for Svelte deve
|
||||
- title: Lifecycle hooks, use_cases: component initialization, cleanup tasks, timers, subscriptions, dom measurements, chat windows, autoscroll features, migration from svelte 4, path: svelte/lifecycle-hooks
|
||||
- title: Imperative component API, use_cases: project setup, client-side rendering, server-side rendering, ssr, hydration, testing, programmatic component creation, tooltips, dynamic mounting, path: svelte/imperative-component-api
|
||||
- title: Hydratable data, use_cases: use title and path to estimate use case, path: svelte/hydratable
|
||||
- title: Best practices, use_cases: use title and path to estimate use case, path: svelte/best-practices
|
||||
- title: Testing, use_cases: testing, quality assurance, unit tests, integration tests, component tests, e2e tests, vitest setup, playwright setup, test automation, path: svelte/testing
|
||||
- title: TypeScript, use_cases: typescript setup, type safety, component props typing, generic components, wrapper components, dom type augmentation, project configuration, path: svelte/typescript
|
||||
- title: Custom elements, use_cases: web components, custom elements, component library, design system, framework-agnostic components, embedding svelte in non-svelte apps, shadow dom, path: svelte/custom-elements
|
||||
@@ -204,6 +207,7 @@ This is the task you will work on:
|
||||
</task>
|
||||
|
||||
If you are not writing the code into a file, once you have the final version of the code ask the user if it wants to generate a playground link to quickly check the code in it and if it answer yes call the `playground-link` tool and return the url to the user nicely formatted. The playground link MUST be generated only once you have the final version of the code and you are ready to share it, it MUST include an entry point file called `App.svelte` where the main component should live. If you have multiple files to include in the playground link you can include them all at the root.
|
||||
```
|
||||
````
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
</details>
|
||||
|
||||
@@ -6,7 +6,7 @@ OpenCode has a [plugin system](https://opencode.ai/docs/plugins/) that allows de
|
||||
|
||||
## Installation
|
||||
|
||||
To install the plugin in OpenCode you can edit your [OpenCode config]() (either the global or the local one), adding `@sveltejs/opencode` to the list of plugins.
|
||||
To install the plugin in OpenCode you can edit your [OpenCode config](https://opencode.ai/docs/config/) (either the global or the local one), adding `@sveltejs/opencode` to the list of plugins.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -23,16 +23,24 @@ The default configuration for the Svelte OpenCode plugin looks like this...
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/sveltejs/ai-tools/refs/heads/main/packages/opencode/schema.json",
|
||||
"$schema": "https://svelte.dev/opencode/schema.json",
|
||||
"mcp": {
|
||||
"type": "remote",
|
||||
"enabled": true
|
||||
},
|
||||
"subagent": {
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"agents": {
|
||||
"svelte-file-editor": {
|
||||
"model": "other-model", // defaults to the same as main agent,
|
||||
"temperature": 1, // default to unset
|
||||
"top_p": 0.7, // default to unset,
|
||||
"maxSteps": 20 // default to unlimited
|
||||
}
|
||||
}
|
||||
},
|
||||
"skills": {
|
||||
"enabled": true
|
||||
"enabled": true // it can also be an array of all the skills to enable like ['svelte-core-bestpractices']
|
||||
},
|
||||
"instructions": {
|
||||
"enabled": true
|
||||
@@ -40,6 +48,6 @@ The default configuration for the Svelte OpenCode plugin looks like this...
|
||||
}
|
||||
```
|
||||
|
||||
...but if you prefer, you can enable only the subagent, only the MCP, only the skills, or configure the kind of MCP server you want to use (`local` or `remote`).
|
||||
...but if you prefer, you can enable only the subagent, only the MCP, only the skills (`enabled` supports both a boolean or an array containing the name of all the skills to enable), or configure the kind of MCP server you want to use (`local` or `remote`).
|
||||
|
||||
You can place this file in `./.opencode/svelte.json` (in your project), in `~/.config/opencode/svelte.json` or, if you have an `OPENCODE_CONFIG_DIR` environment variable specified, at `$OPENCODE_CONFIG_DIR/svelte.json`.
|
||||
|
||||
@@ -217,7 +217,7 @@ The CSS in a component's `<style>` is scoped to that component. If a parent comp
|
||||
</style>
|
||||
```
|
||||
|
||||
If this impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
|
||||
```svelte
|
||||
<div>
|
||||
|
||||
@@ -25,10 +25,14 @@
|
||||
"debug:generate-summaries": "pnpm --filter @sveltejs/mcp-server run debug:generate-summaries",
|
||||
"release": "pnpm --filter @sveltejs/mcp run build && changeset publish",
|
||||
"changeset:version": "changeset version && pnpm --filter @sveltejs/mcp run update:version && git add --all",
|
||||
"sync-plugins": "pnpm sync-claude-plugin && pnpm sync-cursor-plugin && pnpm sync-opencode-plugin && pnpm bump-plugin-versions",
|
||||
"sync-claude-plugin": "node scripts/sync-claude-plugin.ts",
|
||||
"sync-cursor-plugin": "node scripts/sync-cursor-plugin.ts",
|
||||
"sync-opencode-plugin": "node scripts/sync-opencode-plugin.ts",
|
||||
"bump-plugin-versions": "node scripts/bump-plugin-versions.ts"
|
||||
"sync-opencode-plugin": "node scripts/sync-opencode-plugin.ts && pnpm generate-opencode-jsonschema",
|
||||
"bump-plugin-versions": "node scripts/bump-plugin-versions.ts",
|
||||
"postbump-plugin-versions": "pnpm format",
|
||||
"resolve-references": "node scripts/resolve-references.ts",
|
||||
"postresolve-references": "pnpm format"
|
||||
},
|
||||
"keywords": [
|
||||
"svelte",
|
||||
|
||||
@@ -344,87 +344,111 @@ describe('add_autofixers_issues', () => {
|
||||
});
|
||||
|
||||
describe('imported_runes', () => {
|
||||
describe.each([{ source: 'svelte' }, { source: 'svelte/runes' }])(
|
||||
'from "$source"',
|
||||
({ source }) => {
|
||||
describe.each(dollarless_runes)('single import ($rune)', ({ rune }) => {
|
||||
it(`should add suggestions when importing '${rune}' from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
describe.each([
|
||||
{ source: 'svelte' },
|
||||
{ source: 'svelte/runes' },
|
||||
{ source: '@sveltejs/runes' },
|
||||
{ source: '@sveltejs/vite-plugin-svelte' },
|
||||
])('from "$source"', ({ source }) => {
|
||||
describe.each(dollarless_runes)('single import ($rune)', ({ rune }) => {
|
||||
it(`should add suggestions when importing '${rune}' from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { ${rune} } from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when importing "${rune}" as the default export from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
it(`should add suggestions when importing "${rune}" as the default export from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import ${rune} from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when importing '${rune}' as the namespace export from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
it(`should add suggestions when importing '${rune}' as the namespace export from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import * as ${rune} from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should add suggestions when importing multiple runes from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
it(`should add suggestions when importing multiple runes from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { onMount, state, effect } from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "state" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$state" directly.`,
|
||||
);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "effect" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$effect" directly.`,
|
||||
);
|
||||
});
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "state" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$state" directly.`,
|
||||
);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "effect" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$effect" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should not add suggestions when importing other identifiers from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
it(`should not add suggestions when importing other identifiers from '${source}'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { onMount } from '${source}';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are importing "onMount" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$onMount" directly.`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are importing "onMount" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$onMount" directly.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(dollarless_runes)('importing $rune from external lib', ({ rune }) => {
|
||||
it(`should not add suggestions when importing from packages that are not svelte`, () => {
|
||||
it(`should not add suggestions when importing from packages whose name doesn't contain svelte`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { ${rune} } from 'something-something';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are importing "${rune}" from "something-something". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions with a different hint when importing from packages whose name contains svelte but it's not official`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { ${rune} } from 'svelte-something-something';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are importing "${rune}" from "svelte-something-something". This is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly.`,
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${rune}" from "svelte-something-something". If you are trying to import runes to use them this is not necessary, all runes are globally available. Please remove this import and use "$${rune}" directly. If you are importing the function from a separate library ignore this suggestion.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not add the imported_runes suggestion when importing derived from svelte/store', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { derived } from 'svelte/store';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'You are importing "derived" from "svelte/store". This is not necessary, all runes are globally available. Please remove this import and use "$derived" directly.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('derived_with_function', () => {
|
||||
|
||||
@@ -3,10 +3,20 @@ import type { Autofixer } from './index.js';
|
||||
|
||||
const dollarless_runes = base_runes.map((r) => r.replace('$', ''));
|
||||
|
||||
function should_suggest_for_source(source: string, rune: string) {
|
||||
if (!source.includes('svelte')) {
|
||||
return false;
|
||||
}
|
||||
if (source === 'svelte/store' && rune === 'derived') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const imported_runes: Autofixer = {
|
||||
ImportDeclaration(node, { state, next }) {
|
||||
const source = (node.source.value || node.source.raw?.slice(1, -1))?.toString();
|
||||
if (source && (source === 'svelte' || source.startsWith('svelte/'))) {
|
||||
if (source) {
|
||||
for (const specifier of node.specifiers) {
|
||||
const id =
|
||||
specifier.type === 'ImportDefaultSpecifier'
|
||||
@@ -16,10 +26,25 @@ export const imported_runes: Autofixer = {
|
||||
: specifier.type === 'ImportSpecifier'
|
||||
? specifier.imported
|
||||
: null;
|
||||
if (id && id.type === 'Identifier' && dollarless_runes.includes(id.name)) {
|
||||
state.output.suggestions.push(
|
||||
`You are importing "${id.name}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly.`,
|
||||
);
|
||||
if (
|
||||
id &&
|
||||
id.type === 'Identifier' &&
|
||||
dollarless_runes.includes(id.name) &&
|
||||
should_suggest_for_source(source, id.name)
|
||||
) {
|
||||
if (
|
||||
source === 'svelte' ||
|
||||
source.startsWith('svelte/') ||
|
||||
source.startsWith('@sveltejs')
|
||||
) {
|
||||
state.output.suggestions.push(
|
||||
`You are importing "${id.name}" from "${source}". This is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly.`,
|
||||
);
|
||||
} else {
|
||||
state.output.suggestions.push(
|
||||
`You are importing "${id.name}" from "${source}". If you are trying to import runes to use them this is not necessary, all runes are globally available. Please remove this import and use "$${id.name}" directly. If you are importing the function from a separate library ignore this suggestion.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,16 +91,56 @@ export async function get_documentation_handler({
|
||||
}
|
||||
});
|
||||
|
||||
const has_any_success = results.some((result) => result.success);
|
||||
let final_text = results.map((r) => r.content).join('\n\n---\n\n');
|
||||
const successes = results.filter((r) => r.success);
|
||||
const failed_sections = sections.filter(
|
||||
(s) =>
|
||||
!available_sections.some(
|
||||
(a) => a.title.toLowerCase() === s.toLowerCase() || a.slug === s || a.url === s,
|
||||
),
|
||||
);
|
||||
|
||||
if (!has_any_success) {
|
||||
const formatted_sections = await format_sections_list();
|
||||
|
||||
final_text += `\n\n---\n\n${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`;
|
||||
if (successes.length > 0 && failed_sections.length === 0) {
|
||||
return successes.map((r) => r.content).join('\n\n---\n\n');
|
||||
}
|
||||
|
||||
return final_text;
|
||||
const parts: string[] = [];
|
||||
|
||||
if (successes.length > 0) {
|
||||
parts.push(successes.map((r) => r.content).join('\n\n---\n\n'));
|
||||
}
|
||||
|
||||
const fuzzy_results = failed_sections.map((requested) => {
|
||||
const lower = requested.toLowerCase();
|
||||
const matches = available_sections.filter(
|
||||
(a) =>
|
||||
a.title.toLowerCase().includes(lower) ||
|
||||
a.slug.includes(lower) ||
|
||||
lower.includes(a.slug.split('/').pop() ?? '') ||
|
||||
a.use_cases.toLowerCase().includes(lower),
|
||||
);
|
||||
return { requested, matches };
|
||||
});
|
||||
|
||||
const has_fuzzy = fuzzy_results.some((r) => r.matches.length > 0);
|
||||
|
||||
// Full list only when no successes and no fuzzy matches
|
||||
if (successes.length === 0 && !has_fuzzy) {
|
||||
const formatted_sections = await format_sections_list();
|
||||
parts.push(`${SECTIONS_LIST_INTRO}\n\n${formatted_sections}\n\n${SECTIONS_LIST_OUTRO}`);
|
||||
}
|
||||
|
||||
// Similar results then errors
|
||||
for (const { requested, matches } of fuzzy_results) {
|
||||
if (matches.length > 0) {
|
||||
const match_list = matches.map((m) => `- title: ${m.title}, section: ${m.slug}`).join('\n');
|
||||
parts.push(
|
||||
`${matches.length} similar result${matches.length > 1 ? 's' : ''} for "${requested}":\n${match_list}`,
|
||||
);
|
||||
}
|
||||
parts.push(`Section not found: "${requested}".`);
|
||||
}
|
||||
|
||||
return parts.join('\n\n---\n\n');
|
||||
}
|
||||
|
||||
export function get_documentation(server: SvelteMcp) {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @sveltejs/mcp
|
||||
|
||||
## 0.1.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix: broaden checks for imported runes because LLMs are unhinged ([#185](https://github.com/sveltejs/ai-tools/pull/185))
|
||||
|
||||
## 0.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- feat: display similar result & error at the end ([#161](https://github.com/sveltejs/ai-tools/pull/161))
|
||||
|
||||
## 0.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sveltejs/mcp",
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.22",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"mcpName": "dev.svelte/mcp",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"subfolder": "packages/mcp-stdio",
|
||||
"source": "github"
|
||||
},
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.22",
|
||||
"websiteUrl": "https://svelte.dev/docs/mcp/overview",
|
||||
"icons": [
|
||||
{
|
||||
@@ -25,7 +25,7 @@
|
||||
{
|
||||
"registryType": "npm",
|
||||
"identifier": "@sveltejs/mcp",
|
||||
"version": "0.1.20",
|
||||
"version": "0.1.22",
|
||||
"runtimeHint": "npx",
|
||||
"transport": {
|
||||
"type": "stdio"
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @sveltejs/opencode
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- chore: sync skills from svelte.dev ([#178](https://github.com/sveltejs/ai-tools/pull/178))
|
||||
|
||||
- fix: update svelte-file-editor agent to use proper name ([#183](https://github.com/sveltejs/ai-tools/pull/183))
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fix: merge user-configured svelte-file-editor agent settings ([#176](https://github.com/sveltejs/ai-tools/pull/176))
|
||||
|
||||
- feat(opencode): mcp enabled option is passed to opencode ([#171](https://github.com/sveltejs/ai-tools/pull/171))
|
||||
|
||||
- feat: allow enabling a specific skill in opencode plugin ([#174](https://github.com/sveltejs/ai-tools/pull/174))
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -36,39 +36,76 @@ The plugin injects instructions that teach the agent how to effectively use the
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration:
|
||||
Create `svelte.json` to customize how the plugin configures MCP, the Svelte subagent, instructions, and skills.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/sveltejs/ai-tools/refs/heads/main/packages/opencode/schema.json",
|
||||
"$schema": "https://svelte.dev/opencode/schema.json",
|
||||
"mcp": {
|
||||
"type": "remote",
|
||||
"enabled": true
|
||||
},
|
||||
"subagent": {
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"agents": {
|
||||
"svelte-file-editor": {
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
"maxSteps": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
"instructions": {
|
||||
"enabled": true
|
||||
},
|
||||
"skills": {
|
||||
"enabled": ["svelte-code-writer", "svelte-core-bestpractices"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Defaults
|
||||
|
||||
If omitted, the plugin uses these defaults:
|
||||
|
||||
- `mcp.type`: `"remote"`
|
||||
- `mcp.enabled`: `true`
|
||||
- `subagent.enabled`: `true`
|
||||
- `subagent.agents`: `{}`
|
||||
- `instructions.enabled`: `true`
|
||||
- `skills.enabled`: `true`
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| ---------------------- | ----------------------- | ---------- | -------------------------------------------------------------------------------- |
|
||||
| `mcp.type` | `"remote"` \| `"local"` | `"remote"` | Use the remote server at `mcp.svelte.dev` or run locally via `npx @sveltejs/mcp` |
|
||||
| `mcp.enabled` | `boolean` | `true` | Enable/disable the MCP server |
|
||||
| `subagent.enabled` | `boolean` | `true` | Enable/disable the Svelte file editor subagent |
|
||||
| `instructions.enabled` | `boolean` | `true` | Enable/disable agent instructions injection |
|
||||
| Option | Type | Default | Description |
|
||||
| ------------------------------------------------ | --------------------- | ---------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `mcp.type` | `"remote" \| "local"` | `"remote"` | Use `https://mcp.svelte.dev/mcp` (`remote`) or run `@sveltejs/mcp` via `npx` (`local`). |
|
||||
| `mcp.enabled` | `boolean` | `true` | Enable or disable the Svelte MCP server entry. |
|
||||
| `subagent.enabled` | `boolean` | `true` | Enable or disable registration of the `svelte-file-editor` subagent. |
|
||||
| `subagent.agents.svelte-file-editor.model` | `string` | main agent | Override the model used by the Svelte file editor subagent. |
|
||||
| `subagent.agents.svelte-file-editor.temperature` | `number` | unset | Set temperature for the subagent. |
|
||||
| `subagent.agents.svelte-file-editor.top_p` | `number` | unset | Set top-p sampling for the subagent. |
|
||||
| `subagent.agents.svelte-file-editor.maxSteps` | `number` | unlimited | Limit the number of steps the subagent can execute. |
|
||||
| `instructions.enabled` | `boolean` | `true` | Enable or disable automatic instruction-file injection. |
|
||||
| `skills.enabled` | `boolean \| string[]` | `true` | Enable all skills (`true`), disable all skills (`false`), or enable only specific skill names. |
|
||||
|
||||
### Config File Location
|
||||
### Supported Skill Names
|
||||
|
||||
Place your configuration at one of these locations:
|
||||
When using `skills.enabled` as an array, these built-in names are currently available:
|
||||
|
||||
- `~/.config/opencode/svelte.json` (global)
|
||||
- `$OPENCODE_CONFIG_DIR/svelte.json` (if `OPENCODE_CONFIG_DIR` is set, takes priority)
|
||||
- `svelte-code-writer`
|
||||
- `svelte-core-bestpractices`
|
||||
|
||||
### Config File Locations and Precedence
|
||||
|
||||
The plugin reads from these files (lowest priority first, highest priority last):
|
||||
|
||||
- `~/.config/opencode/svelte.json`
|
||||
- `$OPENCODE_CONFIG_DIR/svelte.json` (when `OPENCODE_CONFIG_DIR` is set)
|
||||
- `.opencode/svelte.json` in the current project
|
||||
|
||||
If the same key is defined in multiple files, the later location overrides earlier ones.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -4,6 +4,28 @@ import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import * as v from 'valibot';
|
||||
|
||||
// Schema for individual agent configuration
|
||||
const agent_config_schema = v.object({
|
||||
model: v.pipe(
|
||||
v.optional(v.string()),
|
||||
v.description('Model identifier for the agent (e.g., "anthropic/claude-sonnet-4-20250514")'),
|
||||
),
|
||||
temperature: v.pipe(
|
||||
v.optional(v.number()),
|
||||
v.description('Temperature setting for the agent (e.g., 0.7)'),
|
||||
),
|
||||
top_p: v.pipe(
|
||||
v.optional(v.number()),
|
||||
v.description(
|
||||
'Control response diversity with the top_p option. Alternative to temperature for controlling randomness.',
|
||||
),
|
||||
),
|
||||
maxSteps: v.pipe(
|
||||
v.optional(v.number()),
|
||||
v.description('Maximum number of steps the agent can take (e.g., 10)'),
|
||||
),
|
||||
});
|
||||
|
||||
const default_config = {
|
||||
mcp: {
|
||||
type: 'remote' as 'remote' | 'local',
|
||||
@@ -11,36 +33,61 @@ const default_config = {
|
||||
},
|
||||
subagent: {
|
||||
enabled: true,
|
||||
agents: {} as Record<string, v.InferInput<typeof agent_config_schema>>,
|
||||
},
|
||||
instructions: {
|
||||
enabled: true,
|
||||
},
|
||||
skills: {
|
||||
enabled: true,
|
||||
enabled: true as boolean | string[],
|
||||
},
|
||||
};
|
||||
|
||||
export const config_schema = v.object({
|
||||
mcp: v.optional(
|
||||
v.object({
|
||||
type: v.optional(v.picklist(['remote', 'local'])),
|
||||
enabled: v.optional(v.boolean()),
|
||||
}),
|
||||
mcp: v.pipe(
|
||||
v.optional(
|
||||
v.object({
|
||||
type: v.optional(v.picklist(['remote', 'local'])),
|
||||
enabled: v.optional(v.boolean()),
|
||||
}),
|
||||
),
|
||||
v.description(
|
||||
"Configuration for the MCP. You can chose if it should be enabled or not and the transport to use 'remote' (default) and 'local'.",
|
||||
),
|
||||
),
|
||||
subagent: v.optional(
|
||||
v.object({
|
||||
enabled: v.optional(v.boolean()),
|
||||
}),
|
||||
subagent: v.pipe(
|
||||
v.optional(
|
||||
v.object({
|
||||
enabled: v.optional(v.boolean()),
|
||||
agents: v.optional(v.record(v.string(), agent_config_schema)),
|
||||
}),
|
||||
),
|
||||
v.description('Configuration for the subagent. You can choose if it should be enabled or not.'),
|
||||
),
|
||||
instructions: v.optional(
|
||||
v.object({
|
||||
enabled: v.optional(v.boolean()),
|
||||
}),
|
||||
instructions: v.pipe(
|
||||
v.optional(
|
||||
v.object({
|
||||
enabled: v.optional(v.boolean()),
|
||||
}),
|
||||
),
|
||||
v.description(
|
||||
'Configuration for the automatic AGENTS.md injection. You can choose if it should be enabled or not.',
|
||||
),
|
||||
),
|
||||
skills: v.optional(
|
||||
v.object({
|
||||
enabled: v.optional(v.boolean()),
|
||||
}),
|
||||
skills: v.pipe(
|
||||
v.optional(
|
||||
v.object({
|
||||
enabled: v.pipe(
|
||||
v.optional(v.union([v.boolean(), v.array(v.string())])),
|
||||
v.description(
|
||||
'It can be either a boolean or an array containing the skills that you want to enable',
|
||||
),
|
||||
),
|
||||
}),
|
||||
),
|
||||
v.description(
|
||||
'Configuration for the skills. You can choose if it they should be enabled or not, or specify an array of skill names to enable only specific skills.',
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
@@ -112,8 +159,12 @@ function merge_with_defaults(user_config: Partial<McpConfig>): McpConfig {
|
||||
...user_config.mcp,
|
||||
},
|
||||
subagent: {
|
||||
...default_config.subagent,
|
||||
enabled: default_config.subagent.enabled,
|
||||
...user_config.subagent,
|
||||
agents: {
|
||||
...default_config.subagent.agents,
|
||||
...user_config.subagent?.agents,
|
||||
},
|
||||
},
|
||||
instructions: {
|
||||
...default_config.instructions,
|
||||
@@ -151,7 +202,11 @@ export function get_mcp_config(ctx: PluginInput) {
|
||||
if (parsed.success) {
|
||||
merged = {
|
||||
mcp: { ...merged.mcp, ...parsed.output.mcp },
|
||||
subagent: { ...merged.subagent, ...parsed.output.subagent },
|
||||
subagent: {
|
||||
...merged.subagent,
|
||||
...parsed.output.subagent,
|
||||
agents: { ...merged.subagent?.agents, ...parsed.output.subagent?.agents },
|
||||
},
|
||||
instructions: { ...merged.instructions, ...parsed.output.instructions },
|
||||
skills: { ...merged.skills, ...parsed.output.skills },
|
||||
};
|
||||
|
||||
@@ -39,34 +39,46 @@ export const svelte_plugin: Plugin = async (ctx) => {
|
||||
input.instructions.push(...instructions_paths.map((file) => join(instructions_dir, file)));
|
||||
}
|
||||
|
||||
if (mcp_config.skills?.enabled !== false) {
|
||||
const skills_enabled = mcp_config.skills?.enabled;
|
||||
if (skills_enabled !== false) {
|
||||
const skills_dir = join(current_dir, 'skills');
|
||||
// @ts-expect-error -- skills is a new opencode feature
|
||||
input.skills.paths.push(skills_dir);
|
||||
if (Array.isArray(skills_enabled)) {
|
||||
// only add specific skill directories by name
|
||||
for (const skill_name of skills_enabled) {
|
||||
const skill_path = join(skills_dir, skill_name);
|
||||
// @ts-expect-error -- skills is a new opencode feature
|
||||
input.skills.paths.push(skill_path);
|
||||
}
|
||||
} else {
|
||||
// @ts-expect-error -- skills is a new opencode feature
|
||||
input.skills.paths.push(skills_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// if the user doesn't have the MCP server already we add one based on config
|
||||
if (!input.mcp[svelte_mcp_name] && mcp_config.mcp?.enabled !== false) {
|
||||
if (!input.mcp[svelte_mcp_name]) {
|
||||
if (mcp_config.mcp?.type === 'remote') {
|
||||
input.mcp[svelte_mcp_name] = {
|
||||
type: 'remote',
|
||||
url: 'https://mcp.svelte.dev/mcp',
|
||||
enabled: mcp_config.mcp?.enabled ?? true,
|
||||
};
|
||||
} else {
|
||||
input.mcp[svelte_mcp_name] = {
|
||||
type: 'local',
|
||||
command: ['npx', '-y', '@sveltejs/mcp'],
|
||||
enabled: mcp_config.mcp?.enabled ?? true,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (mcp_config.subagent?.enabled !== false) {
|
||||
// we add the editor subagent that will be used when editing Svelte files to prevent wasting context on the main agent
|
||||
input.agent['svelte-file-editor'] = {
|
||||
const default_config: (typeof input.agent)[string] = {
|
||||
color: '#ff3e00',
|
||||
mode: 'subagent',
|
||||
prompt: `You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with \`get_documentation\` and validating the code with \`svelte_autofixer\`. If the autofixer returns any issue or suggestions try to solve them.
|
||||
|
||||
If the MCP tools are not available you can use the \`svelte-code-editor\` skill to learn how to use the \`@sveltejs/mcp\` cli to access the same tools.
|
||||
If the MCP tools are not available you can use the \`svelte-code-writer\` skill to learn how to use the \`@sveltejs/mcp\` cli to access the same tools.
|
||||
|
||||
If the skill is not available you can run \`npx @sveltejs/mcp@latest -y --help\` to learn how to use it.
|
||||
|
||||
@@ -138,6 +150,27 @@ After completing your work, provide:
|
||||
[`${svelte_mcp_name}_*`]: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Get per-agent config from svelte.json (if any)
|
||||
const svelte_file_editor_config = mcp_config.subagent?.agents?.['svelte-file-editor'];
|
||||
|
||||
// Configure agent from svelte.json only
|
||||
// Priority: svelte.json agent config > defaults
|
||||
input.agent['svelte-file-editor'] = {
|
||||
...default_config,
|
||||
...(svelte_file_editor_config?.model !== undefined && {
|
||||
model: svelte_file_editor_config.model,
|
||||
}),
|
||||
...(svelte_file_editor_config?.temperature !== undefined && {
|
||||
temperature: svelte_file_editor_config.temperature,
|
||||
}),
|
||||
...(svelte_file_editor_config?.maxSteps !== undefined && {
|
||||
maxSteps: svelte_file_editor_config.maxSteps,
|
||||
}),
|
||||
...(svelte_file_editor_config?.top_p !== undefined && {
|
||||
top_p: svelte_file_editor_config.top_p,
|
||||
}),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sveltejs/opencode",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/sveltejs/ai-tools#readme",
|
||||
|
||||
@@ -14,16 +14,55 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
"required": [],
|
||||
"description": "Configuration for the MCP. You can chose if it should be enabled or not and the transport to use 'remote' (default) and 'local'."
|
||||
},
|
||||
"subagent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"agents": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"svelte-file-editor"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model identifier for the agent (e.g., \"anthropic/claude-sonnet-4-20250514\")"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "number",
|
||||
"description": "Temperature setting for the agent (e.g., 0.7)"
|
||||
},
|
||||
"top_p": {
|
||||
"type": "number",
|
||||
"description": "Control response diversity with the top_p option. Alternative to temperature for controlling randomness."
|
||||
},
|
||||
"maxSteps": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of steps the agent can take (e.g., 10)"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
"required": [],
|
||||
"description": "Configuration for the subagent. You can choose if it should be enabled or not."
|
||||
},
|
||||
"instructions": {
|
||||
"type": "object",
|
||||
@@ -32,16 +71,39 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
"required": [],
|
||||
"description": "Configuration for the automatic AGENTS.md injection. You can choose if it should be enabled or not."
|
||||
},
|
||||
"skills": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"svelte-code-writer",
|
||||
"svelte-core-bestpractices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "It can be either a boolean or an array containing the skills that you want to enable"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
"required": [],
|
||||
"description": "Configuration for the skills. You can choose if it they should be enabled or not, or specify an array of skill names to enable only specific skills."
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
|
||||
@@ -3,6 +3,58 @@ import { config_schema } from '../config.js';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const json_schema = toJsonSchema(config_schema);
|
||||
// Read agent names from tools/agents/*.md files
|
||||
function get_agent_names(agents_dir: string) {
|
||||
if (!fs.existsSync(agents_dir)) return [];
|
||||
return fs
|
||||
.readdirSync(agents_dir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
||||
.map((entry) => entry.name.replace(/\.md$/, ''));
|
||||
}
|
||||
|
||||
function get_skill_names(skills_dir: string) {
|
||||
if (!fs.existsSync(skills_dir)) return [];
|
||||
return fs
|
||||
.readdirSync(skills_dir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => entry.name);
|
||||
}
|
||||
|
||||
const skills_dir = path.resolve('./skills');
|
||||
const skill_names = get_skill_names(skills_dir);
|
||||
const schema = config_schema;
|
||||
const json_schema = toJsonSchema(schema);
|
||||
|
||||
// Post-process: inject skill name suggestions into the items schema.
|
||||
// This is the JSON Schema equivalent of `"a" | "b" | (string & {})` —
|
||||
// editors will autocomplete the known names but any string is still valid.
|
||||
if (skill_names.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const enabled = (json_schema as any).properties?.skills?.properties?.enabled;
|
||||
if (enabled?.anyOf) {
|
||||
const array_branch = enabled.anyOf.find((s: Record<string, unknown>) => s.type === 'array');
|
||||
if (array_branch) {
|
||||
array_branch.items = {
|
||||
anyOf: [{ enum: skill_names }, { type: 'string' }],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-process: inject known agent names for intellisense
|
||||
// This is the JSON Schema equivalent of `"a" | "b" | (string & {})` —
|
||||
// editors will autocomplete the known names but any string is still valid.
|
||||
const agents_dir = path.resolve('../../tools/agents');
|
||||
const agent_names = get_agent_names(agents_dir);
|
||||
|
||||
if (agent_names.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const agents = (json_schema as any).properties?.subagent?.properties?.agents;
|
||||
if (agents) {
|
||||
agents.propertyNames = {
|
||||
anyOf: [{ enum: agent_names }, { type: 'string' }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve('./schema.json'), JSON.stringify(json_schema, null, '\t'));
|
||||
|
||||
@@ -134,7 +134,7 @@ The CSS in a component's `<style>` is scoped to that component. If a parent comp
|
||||
</style>
|
||||
```
|
||||
|
||||
If this impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
|
||||
```svelte
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"description": "A plugin for all things related to Svelte development, MCP, skills, and more.",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.4",
|
||||
"author": {
|
||||
"name": "Svelte"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ permissionMode: acceptEdits
|
||||
|
||||
You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
|
||||
|
||||
If the MCP tools are not available you can use the `svelte-code-editor` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
|
||||
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
|
||||
|
||||
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ The CSS in a component's `<style>` is scoped to that component. If a parent comp
|
||||
</style>
|
||||
```
|
||||
|
||||
If this impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
|
||||
```svelte
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"description": "A plugin for all things related to Svelte development, MCP, skills, and more.",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.4",
|
||||
"author": {
|
||||
"name": "Svelte"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when cre
|
||||
|
||||
You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
|
||||
|
||||
If the MCP tools are not available you can use the `svelte-code-editor` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
|
||||
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
|
||||
|
||||
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ The CSS in a component's `<style>` is scoped to that component. If a parent comp
|
||||
</style>
|
||||
```
|
||||
|
||||
If this impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
|
||||
```svelte
|
||||
<div>
|
||||
|
||||
276
scripts/resolve-references.ts
Normal file
276
scripts/resolve-references.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
file: { type: 'string', short: 'f' },
|
||||
repo: { type: 'string', short: 'r' },
|
||||
output: { type: 'string', short: 'o' },
|
||||
},
|
||||
});
|
||||
|
||||
const { file, repo, output } = values;
|
||||
|
||||
if (!file || !repo || !output) {
|
||||
console.error(
|
||||
'Usage: resolve-references --file <path-or-content> --repo <repo> --output <folder>',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export function remove_llm_ignore_blocks(content: string): string {
|
||||
return content.replace(/<!--\s*llm-ignore-start\s*-->[\s\S]*?<!--\s*llm-ignore-end\s*-->/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the input string is a file path or raw markdown content.
|
||||
* If it's a file, reads and returns its content. Otherwise returns the string as-is.
|
||||
*/
|
||||
async function get_content(input: string) {
|
||||
try {
|
||||
const stat = await fs.stat(input);
|
||||
if (stat.isFile()) {
|
||||
return await fs.readFile(input, 'utf-8');
|
||||
}
|
||||
} catch {
|
||||
// not a file path — treat as raw content
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a section from markdown content based on a heading id (hash).
|
||||
* Finds the heading whose text (lowercased, spaces replaced with `-`) matches
|
||||
* the hash and returns everything from that heading up to the next heading of
|
||||
* the same or higher level.
|
||||
*/
|
||||
function extract_section(content: string, hash: string) {
|
||||
const lines = content.split('\n');
|
||||
let start_index = -1;
|
||||
let heading_level = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]!;
|
||||
const heading_match = line.match(/^(#{1,6})\s+(.+)/);
|
||||
if (!heading_match) continue;
|
||||
|
||||
const level = heading_match[1]!.length;
|
||||
const text = heading_match[2]!;
|
||||
const slug = text.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
if (slug === hash.toLowerCase()) {
|
||||
start_index = i;
|
||||
heading_level = level;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (start_index !== -1 && level <= heading_level) {
|
||||
return lines.slice(start_index, i).join('\n').trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (start_index !== -1) {
|
||||
return lines.slice(start_index).join('\n').trim();
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the `title`, `skill`, and `NOTE` fields from markdown frontmatter, if present.
|
||||
* Removes the entire frontmatter block if they were the only fields.
|
||||
*/
|
||||
function remove_frontmatter_unneeded_fields(content: string) {
|
||||
const frontmatter_match = content.match(/^---\n([\s\S]*?)\n---\n?/);
|
||||
if (!frontmatter_match) return content;
|
||||
|
||||
const frontmatter = frontmatter_match[1]!;
|
||||
const lines = frontmatter.split('\n').filter((line) => !line.match(/^(title|skill|NOTE)\s*:/));
|
||||
|
||||
if (lines.length === 0) {
|
||||
// frontmatter is now empty — remove the whole block
|
||||
return content.slice(frontmatter_match[0].length);
|
||||
}
|
||||
|
||||
return `---\n${lines.join('\n')}\n---\n` + content.slice(frontmatter_match[0].length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a file-safe name from a URL path segment.
|
||||
* e.g. "some/deep/path" -> "path"
|
||||
*/
|
||||
function derive_name(link: string) {
|
||||
const without_hash = link.split('#')[0]!;
|
||||
const segments = without_hash.split('/').filter(Boolean);
|
||||
return segments[segments.length - 1] ?? 'reference';
|
||||
}
|
||||
|
||||
const content = remove_llm_ignore_blocks(
|
||||
remove_frontmatter_unneeded_fields(await get_content(file)),
|
||||
);
|
||||
|
||||
// Match markdown links that are either:
|
||||
// 1. Relative paths (not starting with http://, https://, mailto:, #, or /)
|
||||
// 2. Absolute /docs/ paths (e.g. /docs/svelte/each)
|
||||
const relative_link_regex = /\[([^\]]*)\]\((?!https?:\/\/|mailto:|#|\/)([^)]+)\)/g;
|
||||
const docs_link_regex = /\[([^\]]*)\]\((\/docs\/[^)]+)\)/g;
|
||||
|
||||
interface Link_Info {
|
||||
full_match: string;
|
||||
text: string;
|
||||
href: string;
|
||||
hash: string | undefined;
|
||||
clean_path: string;
|
||||
is_absolute_docs: boolean;
|
||||
}
|
||||
|
||||
const links: Link_Info[] = [];
|
||||
|
||||
let match;
|
||||
while ((match = relative_link_regex.exec(content)) !== null) {
|
||||
const href = match[2]!;
|
||||
const hash_index = href.indexOf('#');
|
||||
const has_hash = hash_index !== -1;
|
||||
|
||||
links.push({
|
||||
full_match: match[0],
|
||||
text: match[1]!,
|
||||
href,
|
||||
hash: has_hash ? href.slice(hash_index + 1) : undefined,
|
||||
clean_path: has_hash ? href.slice(0, hash_index) : href,
|
||||
is_absolute_docs: false,
|
||||
});
|
||||
}
|
||||
|
||||
while ((match = docs_link_regex.exec(content)) !== null) {
|
||||
const href = match[2]!;
|
||||
const hash_index = href.indexOf('#');
|
||||
const has_hash = hash_index !== -1;
|
||||
|
||||
links.push({
|
||||
full_match: match[0],
|
||||
text: match[1]!,
|
||||
href,
|
||||
hash: has_hash ? href.slice(hash_index + 1) : undefined,
|
||||
clean_path: has_hash ? href.slice(0, hash_index) : href,
|
||||
is_absolute_docs: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (links.length === 0) {
|
||||
console.log('No relative links found in the markdown.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Found ${links.length} relative link(s) to resolve.`);
|
||||
|
||||
const references_dir = path.join(output, 'references');
|
||||
await fs.mkdir(references_dir, { recursive: true });
|
||||
|
||||
let updated_content = content;
|
||||
|
||||
// Track names we've already used to avoid collisions
|
||||
const used_names = new Map<string, number>();
|
||||
|
||||
for (const link of links) {
|
||||
const base_name = derive_name(link.clean_path);
|
||||
const count = used_names.get(base_name) ?? 0;
|
||||
used_names.set(base_name, count + 1);
|
||||
const name = count > 0 ? `${base_name}-${count}` : base_name;
|
||||
|
||||
// For absolute /docs/ links, fetch directly from svelte.dev (supports cross-repo links).
|
||||
// For relative links, prepend /docs/{repo}/.
|
||||
const url = link.is_absolute_docs
|
||||
? `https://svelte.dev${link.clean_path}/llms.txt`
|
||||
: `https://svelte.dev/docs/${repo}/${link.clean_path}/llms.txt`;
|
||||
|
||||
console.log(`Fetching: ${url}${link.hash ? ` (section: #${link.hash})` : ''}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(` Warning: ${response.status} ${response.statusText} for ${url}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let fetched_content = await response.text();
|
||||
|
||||
if (link.hash) {
|
||||
fetched_content = extract_section(fetched_content, link.hash);
|
||||
}
|
||||
|
||||
const ref_filename = `${name}.md`;
|
||||
const ref_path = path.join(references_dir, ref_filename);
|
||||
|
||||
await fs.writeFile(ref_path, remove_llm_ignore_blocks(remove_cut_preambles(fetched_content)));
|
||||
console.log(` Saved: references/${ref_filename}`);
|
||||
|
||||
// Replace the link in the markdown
|
||||
const new_link = `[${link.text}](references/${ref_filename})`;
|
||||
updated_content = updated_content.replace(link.full_match, new_link);
|
||||
} catch (error) {
|
||||
console.warn(` Error fetching ${url}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In fenced code blocks, removes everything from the start of the block
|
||||
* up to and including a `// ---cut---` comment. If no such comment exists
|
||||
* the code block is left unchanged.
|
||||
*/
|
||||
function remove_cut_preambles(content: string) {
|
||||
const lines = content.split('\n');
|
||||
const result: string[] = [];
|
||||
let in_code_block = false;
|
||||
let code_block_buffer: string[] = [];
|
||||
let fence_line = '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (!in_code_block && line.match(/^```\w*$/)) {
|
||||
in_code_block = true;
|
||||
fence_line = line;
|
||||
code_block_buffer = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_code_block && line.match(/^```$/)) {
|
||||
// End of code block — check if there was a cut comment
|
||||
const cut_index = code_block_buffer.findIndex((l) => l.match(/^\s*\/\/\s*---cut---\s*$/));
|
||||
|
||||
result.push(fence_line);
|
||||
if (cut_index !== -1) {
|
||||
result.push(...code_block_buffer.slice(cut_index + 1));
|
||||
} else {
|
||||
result.push(...code_block_buffer);
|
||||
}
|
||||
result.push(line);
|
||||
|
||||
in_code_block = false;
|
||||
code_block_buffer = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_code_block) {
|
||||
code_block_buffer.push(line);
|
||||
} else {
|
||||
result.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
// If file ends mid-code-block, flush as-is
|
||||
if (in_code_block) {
|
||||
result.push(fence_line);
|
||||
result.push(...code_block_buffer);
|
||||
}
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
// Write the updated markdown content to the output folder
|
||||
updated_content = remove_cut_preambles(updated_content);
|
||||
|
||||
const output_filename = path.join(output, 'SKILL.md');
|
||||
await fs.writeFile(output_filename, updated_content);
|
||||
console.log(`\nUpdated markdown written to: ${output_filename}`);
|
||||
@@ -18,9 +18,11 @@ ${module.docs_description}
|
||||
<details>
|
||||
<summary>Copy the prompt</summary>
|
||||
|
||||
\`\`\`md
|
||||
<!-- prettier-ignore-start -->
|
||||
\`\`\`\`markdown
|
||||
${await module.generate_for_docs()}
|
||||
\`\`\`
|
||||
\`\`\`\`
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when cre
|
||||
|
||||
You are a Svelte 5 expert responsible for writing, editing, and validating Svelte components and modules. You have access to the Svelte MCP server which provides documentation and code analysis tools. Always use the tools from the svelte MCP server to fetch documentation with `get_documentation` and validating the code with `svelte_autofixer`. If the autofixer returns any issue or suggestions try to solve them.
|
||||
|
||||
If the MCP tools are not available you can use the `svelte-code-editor` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
|
||||
If the MCP tools are not available you can use the `svelte-code-writer` skill to learn how to use the `@sveltejs/mcp` cli to access the same tools.
|
||||
|
||||
If the skill is not available you can run `npx @sveltejs/mcp@latest -y --help` to learn how to use it.
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ The CSS in a component's `<style>` is scoped to that component. If a parent comp
|
||||
</style>
|
||||
```
|
||||
|
||||
If this impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
|
||||
```svelte
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user