mirror of
https://github.com/sveltejs/ai-tools.git
synced 2026-08-03 09:04:16 +08:00
Compare commits
477 Commits
set-update
...
@sveltejs/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6468954022 | ||
|
|
1eb06fd05a | ||
|
|
e0aa00a3a5 | ||
|
|
cbac8c2bb7 | ||
|
|
c240d44eda | ||
|
|
c2bbc2a4e7 | ||
|
|
022324b0ed | ||
|
|
d2571d81ad | ||
|
|
ed4272d55f | ||
|
|
ab14e46ec3 | ||
|
|
d6c694c778 | ||
|
|
a52c8b63bb | ||
|
|
8152ed9fd4 | ||
|
|
cccb61197f | ||
|
|
51bf537e23 | ||
|
|
35b6a5a51c | ||
|
|
8a7763868a | ||
|
|
7239d7cb13 | ||
|
|
ff78a85ddf | ||
|
|
8984bb0303 | ||
|
|
3fa951f1b6 | ||
|
|
a668188dfa | ||
|
|
59dcbff136 | ||
|
|
129a307df0 | ||
|
|
d051d7ce6c | ||
|
|
54a84c5512 | ||
|
|
2e8f79038e | ||
|
|
1b6b7589b5 | ||
|
|
fea691996f | ||
|
|
802b379f7a | ||
|
|
fc391d0128 | ||
|
|
575dcc7d2b | ||
|
|
88cea9e539 | ||
|
|
fdb1bc7370 | ||
|
|
5b4d3aa68a | ||
|
|
04c52f2b72 | ||
|
|
0ea2a617ef | ||
|
|
484453e5f8 | ||
|
|
841af5b3a4 | ||
|
|
1ce957ac72 | ||
|
|
e429cd7839 | ||
|
|
2ded13539a | ||
|
|
96c50acae2 | ||
|
|
4920088e5c | ||
|
|
8557f0af6f | ||
|
|
972d0f0bb2 | ||
|
|
7bf364a9d5 | ||
|
|
d06d758b81 | ||
|
|
e5ce7437c4 | ||
|
|
2f422ee190 | ||
|
|
14f087cd7a | ||
|
|
1ef5ddf605 | ||
|
|
0e55ee792d | ||
|
|
84ec24b6f6 | ||
|
|
710cebe539 | ||
|
|
b2a380c4ce | ||
|
|
eef0a9b4d9 | ||
|
|
260b36e8af | ||
|
|
6df3ebe568 | ||
|
|
27a2fc5653 | ||
|
|
5cd99d8234 | ||
|
|
e9f19199cb | ||
|
|
480b46df0a | ||
|
|
5f5fb27977 | ||
|
|
29cfa77c39 | ||
|
|
249923b1e5 | ||
|
|
8c67cae90f | ||
|
|
4aad2e0cfe | ||
|
|
02935b00a6 | ||
|
|
1ea98adacd | ||
|
|
494409cc42 | ||
|
|
46d8f6cce8 | ||
|
|
8dc63dca08 | ||
|
|
19fedcd35f | ||
|
|
b4eb5cc960 | ||
|
|
6676fd8116 | ||
|
|
a755a33a5f | ||
|
|
556f96cfaf | ||
|
|
77a3340c2f | ||
|
|
9ac8fd51e7 | ||
|
|
c764308d79 | ||
|
|
01a7e6a8d3 | ||
|
|
d8ed686e3a | ||
|
|
6f0390d0a9 | ||
|
|
c2c1b3e5e7 | ||
|
|
cdfbb907b6 | ||
|
|
2eb2b18008 | ||
|
|
e8554f3d8f | ||
|
|
0213bd951f | ||
|
|
2245cb2dc9 | ||
|
|
2a5f7d6314 | ||
|
|
8518e627d3 | ||
|
|
9e824da9e2 | ||
|
|
b38f2c11da | ||
|
|
ea37c7120c | ||
|
|
099e939f79 | ||
|
|
69b36eefdc | ||
|
|
15ad554f53 | ||
|
|
74477448ce | ||
|
|
6f538265d1 | ||
|
|
71295bc11f | ||
|
|
b5040ff5cf | ||
|
|
45c961417f | ||
|
|
57e2d1def1 | ||
|
|
398a703580 | ||
|
|
75c802a115 | ||
|
|
53a634cdb0 | ||
|
|
bcdc33e7a5 | ||
|
|
825ae33427 | ||
|
|
ccf940cc45 | ||
|
|
b2f195fb7b | ||
|
|
d8e4b18bff | ||
|
|
6a2198b433 | ||
|
|
2ce60c6110 | ||
|
|
0f8987fdcf | ||
|
|
42911e2631 | ||
|
|
655eb85eba | ||
|
|
89403a7d0c | ||
|
|
3747623d55 | ||
|
|
c1f230455f | ||
|
|
9dfb4dedb4 | ||
|
|
b891e4860b | ||
|
|
c9e8508dd9 | ||
|
|
9896406aff | ||
|
|
b01ae9069b | ||
|
|
b6db495242 | ||
|
|
3926310107 | ||
|
|
848627549f | ||
|
|
283164ca7e | ||
|
|
c6ee414f62 | ||
|
|
ea5bdf66dc | ||
|
|
7d707202d1 | ||
|
|
f0daadfbd0 | ||
|
|
15a7774da7 | ||
|
|
98efa1e09e | ||
|
|
e20cf2974d | ||
|
|
b2ee968a3f | ||
|
|
60297b3c49 | ||
|
|
39076da8ce | ||
|
|
fba733646a | ||
|
|
7fcd4705a5 | ||
|
|
af7d341ba5 | ||
|
|
52546551ff | ||
|
|
1f0a5f1519 | ||
|
|
0bf04bad2e | ||
|
|
f001918925 | ||
|
|
67487c324a | ||
|
|
5beeef5543 | ||
|
|
e1a03fdb85 | ||
|
|
384c1fd209 | ||
|
|
b3027fd815 | ||
|
|
849bf2ad49 | ||
|
|
314538c8e7 | ||
|
|
a9994310c0 | ||
|
|
9fb1a403b7 | ||
|
|
3c7b5033a4 | ||
|
|
b911a00bb7 | ||
|
|
f6ce89ff34 | ||
|
|
846514858e | ||
|
|
b69ea052bd | ||
|
|
e56159dda6 | ||
|
|
1e83c35faa | ||
|
|
31edfe1b5f | ||
|
|
3fabcc0f9b | ||
|
|
deb5f2670c | ||
|
|
02c951baa8 | ||
|
|
41ceb83838 | ||
|
|
3c3a26f031 | ||
|
|
6589c7e250 | ||
|
|
e09b8cd0b9 | ||
|
|
7f52a2b1be | ||
|
|
4eecd75759 | ||
|
|
9015753f77 | ||
|
|
4d6a9cb333 | ||
|
|
60aa30397f | ||
|
|
17ed3a3e23 | ||
|
|
f49bd06fbd | ||
|
|
b98c042ae3 | ||
|
|
917a93d3fd | ||
|
|
371e96befc | ||
|
|
bdfd5a109f | ||
|
|
1c6c0a9fa7 | ||
|
|
a321244543 | ||
|
|
ed25933466 | ||
|
|
72f91dfb7b | ||
|
|
d36855c447 | ||
|
|
5fa2baa270 | ||
|
|
6543150a5b | ||
|
|
4c7f7feeba | ||
|
|
579be877fa | ||
|
|
0d55c0f61a | ||
|
|
7d7b08610d | ||
|
|
c08d8d4df7 | ||
|
|
12dd3c16ac | ||
|
|
ca17a18677 | ||
|
|
cf62286912 | ||
|
|
a4dfaab1c6 | ||
|
|
7b396ad63f | ||
|
|
a9653f9c74 | ||
|
|
cabf1fd96a | ||
|
|
2d50ffd38c | ||
|
|
66c9056e0f | ||
|
|
e639e3ad5c | ||
|
|
322f416c3d | ||
|
|
c40a3fcb5c | ||
|
|
a282623cc7 | ||
|
|
8f6abc6192 | ||
|
|
d0bed3e8f0 | ||
|
|
4c98732f5f | ||
|
|
1f88817cf0 | ||
|
|
87af64f4bc | ||
|
|
c1678b2f36 | ||
|
|
a8af3c72ca | ||
|
|
534a72cae7 | ||
|
|
3b301d7d9c | ||
|
|
33a0e17ee1 | ||
|
|
bf5f31c867 | ||
|
|
62abaacbd3 | ||
|
|
87fab2d884 | ||
|
|
9cf50c9b62 | ||
|
|
9a43aaf100 | ||
|
|
c0477385b8 | ||
|
|
db283da593 | ||
|
|
7d7547f6e2 | ||
|
|
0360d00955 | ||
|
|
65d403af1e | ||
|
|
d794d6bec3 | ||
|
|
ec9c5b3415 | ||
|
|
b508a4ea49 | ||
|
|
044f0988b9 | ||
|
|
c5c6ba6580 | ||
|
|
fb2240d60c | ||
|
|
469b2071e7 | ||
|
|
fc39b44859 | ||
|
|
e12a0c90ab | ||
|
|
7234e64967 | ||
|
|
ef5241cbc2 | ||
|
|
9a74df198d | ||
|
|
f1280b9876 | ||
|
|
132943db3b | ||
|
|
5ed1454e2c | ||
|
|
bd072bd324 | ||
|
|
c35be898da | ||
|
|
b960301ced | ||
|
|
43408f5504 | ||
|
|
bf4dcda7e1 | ||
|
|
35691c464b | ||
|
|
a5decb4cf3 | ||
|
|
cb9764c234 | ||
|
|
3fbc786383 | ||
|
|
3b680232a0 | ||
|
|
c5c08ccd13 | ||
|
|
4ac35bf258 | ||
|
|
ecbbf70d98 | ||
|
|
aaac49cdef | ||
|
|
558d964919 | ||
|
|
73d7625b3c | ||
|
|
c7060c8bdb | ||
|
|
ef2d569934 | ||
|
|
7ba57b45ae | ||
|
|
fe393bf480 | ||
|
|
a63deba99d | ||
|
|
668a2e4481 | ||
|
|
04c82875f3 | ||
|
|
84601f9ab0 | ||
|
|
e01a050017 | ||
|
|
5920f7482f | ||
|
|
fa90a2be8d | ||
|
|
73bf0c3782 | ||
|
|
11cd2447fc | ||
|
|
8785ad224c | ||
|
|
75e676d928 | ||
|
|
5b16bdd80b | ||
|
|
f3ee4ed59c | ||
|
|
725f785766 | ||
|
|
485e60e245 | ||
|
|
480bfca557 | ||
|
|
06f9fc6d63 | ||
|
|
f1f85d2445 | ||
|
|
b9c0a011e2 | ||
|
|
b92dd95dee | ||
|
|
99537cfa25 | ||
|
|
f93a6cee60 | ||
|
|
e8989db548 | ||
|
|
cb316c5b3e | ||
|
|
7c762109b6 | ||
|
|
e3dbb5c098 | ||
|
|
03ce6c50c0 | ||
|
|
273c78092d | ||
|
|
891fae8127 | ||
|
|
fa969feb28 | ||
|
|
1dbd93a1ce | ||
|
|
3bb71f3963 | ||
|
|
a881cb5938 | ||
|
|
03859141ed | ||
|
|
f747c4ac65 | ||
|
|
1916c410e6 | ||
|
|
1e7ebeaf5b | ||
|
|
f82ceac79d | ||
|
|
cde6d700e6 | ||
|
|
522fae6017 | ||
|
|
1f94d33dc9 | ||
|
|
7ada706deb | ||
|
|
796c38ee23 | ||
|
|
1f296e5277 | ||
|
|
121395e98e | ||
|
|
8e7c881838 | ||
|
|
91c396e675 | ||
|
|
baad760634 | ||
|
|
b2275587ee | ||
|
|
4964303100 | ||
|
|
81901b2564 | ||
|
|
5aa1aa401a | ||
|
|
4201627f53 | ||
|
|
9a70fbe3aa | ||
|
|
dc16a42c65 | ||
|
|
3b50014b09 | ||
|
|
e9214bc470 | ||
|
|
3106305902 | ||
|
|
3c14872068 | ||
|
|
216a470bd2 | ||
|
|
b5a88c454d | ||
|
|
937216e1de | ||
|
|
6a23b9c87f | ||
|
|
8064a4f5cf | ||
|
|
005b9d45bd | ||
|
|
12a80515a0 | ||
|
|
7acbaad478 | ||
|
|
55f9009a77 | ||
|
|
02042daa02 | ||
|
|
eb5444e3b6 | ||
|
|
c3134bdfde | ||
|
|
334f9330e7 | ||
|
|
b16448b6df | ||
|
|
b0888d6ac3 | ||
|
|
6fc0419fc5 | ||
|
|
d138349c46 | ||
|
|
e1a1cb1d84 | ||
|
|
e723198db0 | ||
|
|
7b5bea6549 | ||
|
|
a36d0d17a8 | ||
|
|
70f14bddca | ||
|
|
7dedb277be | ||
|
|
de45c1b015 | ||
|
|
94f12964cb | ||
|
|
7366dad0e9 | ||
|
|
22aaad43f3 | ||
|
|
1332513847 | ||
|
|
988da97816 | ||
|
|
b086e634fe | ||
|
|
fd72afc2a3 | ||
|
|
5736e853e6 | ||
|
|
0f2747423b | ||
|
|
a49773f0ff | ||
|
|
56b47f1108 | ||
|
|
207d1f80bf | ||
|
|
0cda036ab6 | ||
|
|
a281ef4b66 | ||
|
|
5bc812e4db | ||
|
|
82319661dd | ||
|
|
ce0861c1ca | ||
|
|
5dd83d151e | ||
|
|
76a35f5dc8 | ||
|
|
54763e0f55 | ||
|
|
01d5803b5d | ||
|
|
0366bc785b | ||
|
|
6a6417d3a5 | ||
|
|
77af7ebcc6 | ||
|
|
b1a196497d | ||
|
|
fb2d19fd07 | ||
|
|
8328a3572b | ||
|
|
c05b6c257a | ||
|
|
7f9ea742d8 | ||
|
|
bf477a6ccf | ||
|
|
0f5482477a | ||
|
|
b774b463fe | ||
|
|
c49b24d36a | ||
|
|
6cb97ac11d | ||
|
|
d33a374417 | ||
|
|
1bb171cea7 | ||
|
|
e314ab57b2 | ||
|
|
19cacf7ed9 | ||
|
|
68cf69a117 | ||
|
|
917fdf63b1 | ||
|
|
972cadc410 | ||
|
|
dc6c87ce37 | ||
|
|
e7431e9024 | ||
|
|
07737a8edd | ||
|
|
fdb7689992 | ||
|
|
8483bd672d | ||
|
|
47fa0a4382 | ||
|
|
4c6232a44f | ||
|
|
8edbf2f36b | ||
|
|
6e54719f88 | ||
|
|
1a283f60bc | ||
|
|
bb16ccca3a | ||
|
|
23ddaf9495 | ||
|
|
4228302ed0 | ||
|
|
0bc4d75e13 | ||
|
|
4679549401 | ||
|
|
6b15eb0790 | ||
|
|
a7041a4c5e | ||
|
|
023bea317f | ||
|
|
7a6cba8772 | ||
|
|
fd32b67442 | ||
|
|
0e3b1ba22f | ||
|
|
0aad39d076 | ||
|
|
2fec290d54 | ||
|
|
4e59ef751a | ||
|
|
c87c9e0715 | ||
|
|
8414ffbcc8 | ||
|
|
5798b50ceb | ||
|
|
e560932211 | ||
|
|
12f8d84852 | ||
|
|
bde37da5d5 | ||
|
|
a50844e388 | ||
|
|
6a71229d56 | ||
|
|
92d8532c8a | ||
|
|
c8300bc62e | ||
|
|
de78f7663f | ||
|
|
e57b76324f | ||
|
|
09331e2c2b | ||
|
|
f1aef9ca2f | ||
|
|
6c072534ea | ||
|
|
2f8165f1d7 | ||
|
|
9504e6bac9 | ||
|
|
7086e8e55f | ||
|
|
d93d3a3507 | ||
|
|
039718f1a5 | ||
|
|
ac287a2c83 | ||
|
|
224d630a32 | ||
|
|
4a9afb5ee1 | ||
|
|
e68067e995 | ||
|
|
8258a1c9ba | ||
|
|
5aa2827c91 | ||
|
|
a35d72cc6b | ||
|
|
0c35883074 | ||
|
|
d82c20acd6 | ||
|
|
cc3ea75c7f | ||
|
|
68724731c7 | ||
|
|
bf1a4178bf | ||
|
|
050e588709 | ||
|
|
731b4f6548 | ||
|
|
582e0e1dea | ||
|
|
bb9a6e07ea | ||
|
|
0d17b81948 | ||
|
|
81640c9a16 | ||
|
|
8587bc8625 | ||
|
|
0475e3b0f9 | ||
|
|
4e1a42ab52 | ||
|
|
862f614afc | ||
|
|
a92ae954bd | ||
|
|
e3b5188c6d | ||
|
|
6b5f2092b5 | ||
|
|
089e690f3e | ||
|
|
1c60e350a6 | ||
|
|
a93a6554b5 | ||
|
|
94f7d65db3 | ||
|
|
d7492bb1cb | ||
|
|
e1e2bf68ae | ||
|
|
0ff628f5b4 | ||
|
|
ea35d600e4 | ||
|
|
dcbcd5b690 | ||
|
|
9f580a36ef | ||
|
|
d81d6a3d95 | ||
|
|
f04cb139e3 | ||
|
|
dedfd0b3b7 | ||
|
|
5d50518c3c | ||
|
|
74d2fb8f0e | ||
|
|
86675ea1d7 | ||
|
|
830fd73ab1 | ||
|
|
da995bdc69 | ||
|
|
26b3986740 | ||
|
|
8d53f56151 | ||
|
|
fd355e872d | ||
|
|
d4edef1d82 | ||
|
|
26d30c73c7 |
195
.agents/skills/writing-great-skills/GLOSSARY.md
Normal file
195
.agents/skills/writing-great-skills/GLOSSARY.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Glossary — Building Great Skills
|
||||
|
||||
The domain model for what makes a skill great. A skill exists to wrangle determinism out of a stochastic system; the root virtue is **Predictability**, and every term below is a lever on it. This is the disclosed reference for [`writing-great-skills`](SKILL.md).
|
||||
|
||||
The terms are grouped by axis: **Invocation** (how a skill is reached), **Information Hierarchy** (how its content is arranged), **Steering** (how the agent's runtime behaviour is shaped), and **Pruning** (how it is kept lean). Each **failure mode** lives beside the lever that cures it, tagged _failure mode_.
|
||||
|
||||
**Bold terms** in any definition are themselves defined in this glossary; find them by their heading.
|
||||
|
||||
## Predictability
|
||||
|
||||
The degree to which a skill makes the agent behave the same _way_ on every run — the same process, not the same output (a brainstorming skill should _predictably_ diverge; its tokens vary, its behaviour doesn't). The root virtue every other term serves — cost and maintainability are symptoms of it, not rivals.
|
||||
|
||||
_Avoid_: consistency, reliability, robustness, output-determinism
|
||||
|
||||
## Invocation
|
||||
|
||||
How a skill is reached — and the two loads you pay for the choice.
|
||||
|
||||
### Model-Invoked
|
||||
|
||||
A skill that keeps its **description** field, so the agent can see it and fire it autonomously — and the human can still type its name, so model-invocation always _includes_ user reach. There is no model-only state: a description only ever _adds_ agent discovery, never removes the human's. Pays a permanent **context load** on every turn in exchange for that discoverability. Reachable by other skills, because the description that makes it agent-discoverable makes it invocable. A model-invoked skill whose content is all **reference** is also one home for shared reference: another skill can invoke it, so reference needed by several skills lives in one place. Pick model-invocation only when the agent must reach the skill on its own; if it never fires except by hand, drop the description and pay no context load.
|
||||
|
||||
_Avoid_: ability, tool, capability
|
||||
|
||||
### User-Invoked
|
||||
|
||||
A skill with its **description** stripped — invisible to the agent and reachable only by the human typing its name (user-_only_, where **model-invoked** is user-_and-agent_). Trades agent-discoverability for zero **context load**. Because it has no description, nothing but the human can reach it: no other skill can fire it.
|
||||
|
||||
_Avoid_: procedure, workflow, command
|
||||
|
||||
### Description
|
||||
|
||||
The skill's machine-readable trigger, and the one **context pointer** a **model-invoked** skill is forced to keep loaded at all times. Its mere presence _is_ the invocation axis: keep it and the skill is model-invoked (and reachable by other skills); delete it and the skill is **user-invoked**, reachable only by the human. The source of a model-invoked skill's **context load**.
|
||||
|
||||
_Avoid_: frontmatter, summary
|
||||
|
||||
### Context Pointer
|
||||
|
||||
A reference held in the agent's context that names some out-of-context material and encodes the condition for reaching it. The **description** is the top-level context pointer (context window → skill); pointers to disclosed files are the same object one level down. Its wording, not the target, decides _when_ the agent reaches — and _how reliably_. A must-have target behind a weakly worded pointer is a variance bug: fix the wording first, and inline the material only if sharpening fails.
|
||||
|
||||
_Avoid_: link, reference, import
|
||||
|
||||
### Context Load
|
||||
|
||||
The cost a **model-invoked** skill imposes on the agent's context window — its **description**, always loaded, spending both tokens and attention. What **user-invoked** skills escape by having no description, and the brake on splitting into more model-invoked skills.
|
||||
|
||||
_Avoid_: token cost, context bloat
|
||||
|
||||
### Cognitive Load
|
||||
|
||||
The cost a **user-invoked** skill imposes on the human — what they must hold in their head: which skills exist and when to reach for each (the human is the index). What **model-invocation** removes by being agent-discoverable, and the brake on splitting into more user-invoked skills. Not a cost to minimise: it is the price of human agency, the reason some skills stay user-invoked. Spend it where human judgement matters; remove it where it does not.
|
||||
|
||||
_Avoid_: human index, burden, overhead
|
||||
|
||||
### Router Skill
|
||||
|
||||
A **user-invoked** skill whose job is to point at your other user-invoked skills — naming each and when to reach for it — so the human has one skill to remember instead of many. It can only hint, never fire them: user-invoked skills have no **description**, so nothing but the human can reach them. The cure for **cognitive load** when user-invoked skills multiply.
|
||||
|
||||
_Avoid_: dispatcher, menu, registry, index, router procedure
|
||||
|
||||
### Granularity
|
||||
|
||||
How finely you divide skills. Finer division spends one of the two loads: more **model-invoked** skills spend **context load** (more descriptions crowding the window and competing for attention); more **user-invoked** skills spend **cognitive load** (more for the human to remember and reach for). Two cuts guide the division. By **invocation**, split off a model-invoked skill where you have a distinct **leading word** to trigger it — a trigger word you actually use in your prompts. By **sequence**, split a run of **steps** where a step's **post-completion steps** need hiding, since isolating it in its own context clears what follows. Beware the reverse: merging sequences exposes each step's post-completion steps to what follows, inviting premature completion.
|
||||
|
||||
_Avoid_: chunking, modularity
|
||||
|
||||
## Information Hierarchy
|
||||
|
||||
How a skill's content is arranged, and how far down the ladder each piece sits.
|
||||
|
||||
### Information Hierarchy
|
||||
|
||||
A skill's content ranked by how immediately the agent needs it — a single ladder, produced by two cuts: in-file or behind a pointer, and step or reference. The rungs:
|
||||
|
||||
- **Steps** — in-file, primary
|
||||
- **Reference**, in-file — secondary
|
||||
- **Reference**, disclosed — behind a **context pointer**
|
||||
|
||||
A skill with no **steps** uses just the bottom two rungs — often a legitimately flat peer-set (e.g. every rule of a review on one rung), which is a fine arrangement, not a smell. The hierarchy is independent of invocation: a skill can be model- or user-invoked whether it is all steps, all reference, or both. When a skill has steps, in-file reference that should be disclosed buries them and turns attending to them into a coin-flip — a variance lever, not just a legibility one. Keep the top of the ladder legible; push down it whatever you can.
|
||||
|
||||
_Avoid_: structure, organization, layout
|
||||
|
||||
### Steps
|
||||
|
||||
The ordered actions the agent performs — when a skill has them, the primary tier of its content, and the part that earns its place in SKILL.md. Not every skill has steps: a skill can be all steps (`tdd`), all **reference** (a review), or both, independent of invocation. Every step ends on a **completion criterion**, clear or vague.
|
||||
|
||||
_Avoid_: workflow, instructions, choreography
|
||||
|
||||
### Reference
|
||||
|
||||
Material the agent refers to on demand — definitions, facts, parameters, examples, conditional instructions. When a skill has **steps** it is secondary to them; when a skill has none it is the entire content; or it lives outside any skill entirely — see **External Reference**. Reached via **context pointers**, and the prime candidate for **progressive disclosure**.
|
||||
|
||||
_Avoid_: supporting material, docs, background
|
||||
|
||||
### External Reference
|
||||
|
||||
**Reference** that lives outside the skill system — a plain file, no **description**, no **steps**, not invocable — that any skill can point at. The home for shared reference that needn't fire on its own, and the only shared home two **user-invoked** skills can use, since neither has a description and so neither can fire the other.
|
||||
|
||||
_Avoid_: doc, resource, knowledge base
|
||||
|
||||
### Progressive Disclosure
|
||||
|
||||
Moving **reference** down the ladder — out of SKILL.md and behind a **context pointer** — so the top stays legible. Not primarily a token optimisation; it is how the **information hierarchy** is protected. Licensed by **branching**: disclose what only some branches need, inline what every path needs, and if a pointer fires unreliably on must-have material, sharpen its wording, and pull it back inline only if that fails.
|
||||
|
||||
_Avoid_: lazy loading, chunking
|
||||
|
||||
### Co-location
|
||||
|
||||
Keeping the material an agent needs at once in one place — a concept's definition, rules, and caveats under a single heading, not scattered across the file — so reading one part brings its neighbours with it. The within-file companion to the **Information Hierarchy**: the hierarchy ranks _how far down_ a piece sits; co-location decides _what sits beside it_ once there. There is no formula for the right format of a body of **reference**; the test is that a skill should read like documentation written for the agent, and grouped material reads that way where scattered material does not. Distinct from **Duplication**: that repeats one meaning in two places, where scattering fragments a single meaning across many.
|
||||
|
||||
_Avoid_: grouping, clustering, cohesion
|
||||
|
||||
### Sprawl
|
||||
|
||||
_Failure mode._ A skill that is simply too long — too many lines in SKILL.md — independent of whether they are stale or repeated. Even an all-live, all-unique skill can sprawl. It costs readability (the agent wades through more before it can act, and attention thins across the excess), maintainability (every extra line is one more to keep **relevant**), and tokens. The cure is the **information hierarchy**: push **reference** down behind **context pointers**, and split by **branch** or sequence so each path carries only what it needs. Distinct from **sediment** (length from stale accumulation) and **duplication** (length from repeated meaning) — sprawl is length itself, whatever its cause.
|
||||
|
||||
_Avoid_: bloat, length, size, verbosity
|
||||
|
||||
## Steering
|
||||
|
||||
The levers that shape the agent's runtime behaviour toward **Predictability**.
|
||||
|
||||
### Branch
|
||||
|
||||
A distinct way a skill can be invoked — a case the skill handles — so different runs take different paths through it. A skill with many steps may carry many branches; a linear one has none.
|
||||
|
||||
_Avoid_: path, case, fork
|
||||
|
||||
### Leading Word
|
||||
|
||||
A compact concept — also called a _Leitwort_ — already living in the model's pretraining, that the agent thinks with while running the skill. It encodes a behavioural principle in the fewest possible tokens by invoking priors the model already holds (e.g. _lesson_, _proximal zone of development_, _fog of war_, _tracer bullets_). Repeated as a token, never as a sentence, it accumulates a distributed definition across the skill and anchors a whole region of behaviour. Coining your own works if you define it clearly, but a made-up word recruits no priors — you pay in definition tokens what a pretrained word gives free. Reach for an existing word first.
|
||||
|
||||
A leading word serves **predictability** twice. In the body it anchors **execution** — the agent reaches for the same behaviour every time the concept appears, and inside flat reference it focuses attention on a class of thing to look for, recruiting the right checks each run. In the **description** it anchors **invocation** — and not only within the skill: when the same word lives in your prompts, your docs, and your codebase, the agent links that shared language to the skill and fires it more reliably. Word a description with the leading words you actually use when you want the skill.
|
||||
|
||||
_Avoid_: keyword, term, motif
|
||||
|
||||
### Completion Criterion
|
||||
|
||||
The condition that tells the agent a unit of work is done — the target it judges against. Two properties make it a lever, not just a quality. Its **clarity** (can the agent tell done from not-done?) resists **premature completion** — a vague bound ("understanding reached") lets the agent declare done and slip to the next step; this axis needs _steps_ to bite, since premature completion is a between-steps failure. Its **demand** (how much it requires) sets **legwork** — "every modified model accounted for" forces thorough work where "produce a change list" does not — and this axis is _not_ step-bound: it can bind a body of flat reference too, which is how a skill with no steps still carries an exhaustiveness bar ("every rule applied"). The strongest criteria are both checkable and exhaustive.
|
||||
|
||||
_Avoid_: done condition, exit condition, stopping rule
|
||||
|
||||
### Legwork
|
||||
|
||||
The work an agent does behind the scenes within a single step — reading files, exploring the codebase, making changes, digging up what it needs rather than offloading to the user. It lives below the step structure: never written as its own step, latent in the wording, controlled by the agent rather than the skill. The within-step counterpart to **post-completion steps**' across-step pull. Raised by a **leading word** (_comprehensive_, _thorough_) or a **completion criterion** that demands the work be exhaustive — including the demand axis applied to flat reference, which is what drives a skill of flat reference to cover all its rungs. Goes thin either when that demand is missing or when **premature completion** cuts the step short.
|
||||
|
||||
_Avoid_: scope, effort, diligence, coverage
|
||||
|
||||
### Post-Completion Steps
|
||||
|
||||
The **steps** that follow the current step. Visible, they pull the agent forward into **premature completion** — the more it sees, the stronger the tug; the defence is to hide them by splitting the sequence of steps into two.
|
||||
|
||||
_Avoid_: horizon, fog of war, lookahead
|
||||
|
||||
### Premature Completion
|
||||
|
||||
_Failure mode._ Ending the current step before it is genuinely done, because the agent's attention slips to being done rather than to the work. A between-steps failure: it needs **steps** to occur — a skill with no steps that quits early isn't premature completion but thin **legwork** under an unmet demand. A tug-of-war between two forces: visible **post-completion steps** (the pull forward) and the **completion criterion**'s clarity (the resistance — a sharp, checkable bar holds; a vague one gives way). Fuzziness is the necessary condition: a sharp bound resists the pull no matter how many later steps are visible, so a step that never rushes needs no defending. Two levers hold a step that does, but reach for them in order: **sharpen the bound first** — it is local and cheap. Only when the criterion is irreducibly fuzzy _and_ you actually observe the rush do you **hide the later steps** — and hiding only works across a real context boundary (a user-invoked hand-off or a subagent dispatch; an inline model-invoked call leaves the later steps in context and clears nothing). One cause of thin legwork, but distinct from it: legwork can be thin even when a step runs to full completion.
|
||||
|
||||
_Avoid_: premature closure, the rush, rushing, shortcutting
|
||||
|
||||
## Pruning
|
||||
|
||||
Keeping a skill lean — each remedy paired with the failure it cures.
|
||||
|
||||
### Single Source of Truth
|
||||
|
||||
The desired state where each meaning lives in exactly one authoritative place, so a change to the skill's behaviour is a change in one place. **Duplication** is its violation.
|
||||
|
||||
_Avoid_: home, canonical location
|
||||
|
||||
### Duplication
|
||||
|
||||
_Failure mode._ The same meaning given more than one **single source of truth**. It costs maintenance (change one place, you must change the others), costs tokens, and inflates prominence — repeating a meaning weights it on the ladder past its real rank. The accidental inverse of a **leading word**, which raises attention on purpose by repeating a token, never the meaning.
|
||||
|
||||
_Avoid_: repetition, redundancy
|
||||
|
||||
### Relevance
|
||||
|
||||
Whether a line still bears on what the skill does — the lens for what to keep. A line loses relevance either by never bearing on the task (mere exposition, or a **branch** that should be disclosed) or by going stale: drifting out of date as the behaviour or world it describes changes. Shorter skills are easier to keep relevant, because each line is cheaper to check. Distinct from **no-op**: relevance asks whether a line bears on the task, not whether it changes behaviour.
|
||||
|
||||
_Avoid_: load-bearing, staleness, freshness
|
||||
|
||||
### Sediment
|
||||
|
||||
_Failure mode._ Layers of old content that settle in a skill and are never cleared, because adding feels safe and removing feels risky — so stale and irrelevant lines accumulate and you must core down through them to find what is still live. The default fate of any skill without a pruning discipline; the slow erosion of **relevance**, as opposed to **duplication**'s repeated meaning.
|
||||
|
||||
_Avoid_: accretion, bloat, cruft, rot
|
||||
|
||||
### No-Op
|
||||
|
||||
_Failure mode._ An instruction that changes nothing because the model already does it by default — you pay load to tell the agent what it would do anyway. The test: does a line change behaviour versus the default? A line can be perfectly **relevant** and still be a no-op. The same priors that make a **leading word** free make a no-op worthless.
|
||||
|
||||
A leading word is a _technique_; No-Op is a _verdict_ on a line — and they cross. A leading word too weak to beat the default is a no-op (_be thorough_ when the agent is already thorough-ish), and the fix is a stronger word that passes the verdict (_relentless_), not a different technique. So the No-Op test — does it change behaviour versus the default? — is also how you grade whether a leading word is earning its repetitions. This is model-relative, not reader-relative: two people disagreeing over whether a line is a no-op disagree about the default, and settle it by running the skill, not by debate.
|
||||
|
||||
_Avoid_: redundant instruction, restating the obvious, belaboring
|
||||
84
.agents/skills/writing-great-skills/SKILL.md
Normal file
84
.agents/skills/writing-great-skills/SKILL.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: writing-great-skills
|
||||
description: Reference for writing and editing skills well — the vocabulary and principles that make a skill predictable.
|
||||
disable-model-invocation: true
|
||||
metadata:
|
||||
internal: true
|
||||
---
|
||||
|
||||
A skill exists to wrangle determinism out of a stochastic system. **Predictability** — the agent taking the same _process_ every run, not producing the same output — is the root virtue; every lever below serves it.
|
||||
|
||||
**Bold terms** are defined in [`GLOSSARY.md`](GLOSSARY.md); look them up there for the full meaning.
|
||||
|
||||
## Invocation
|
||||
|
||||
Two choices, trading different costs:
|
||||
|
||||
- A **model-invoked** skill keeps a **description**, so the agent can fire it autonomously _and_ other skills can reach it (you can still type its name too). It contributes to **context load** — the description sits in the window every turn. Mechanics: omit `disable-model-invocation`, and write a model-facing description with rich trigger phrasing ("Use when the user wants…, mentions…").
|
||||
- A **user-invoked** skill strips the description from the agent's reach: only you, typing its name, can invoke it — and no other skill can. Zero context load, but it spends **cognitive load**: _you_ are the index that must remember it exists. Mechanics: set `disable-model-invocation: true`; the `description` becomes human-facing — a one-line summary, trigger lists stripped.
|
||||
|
||||
Pick model-invocation only when the agent must reach the skill on its own, or another skill must. If it only ever fires by hand, make it user-invoked and pay no context load.
|
||||
|
||||
When user-invoked skills multiply past what you can remember, that piled-up cognitive load is cured by a **router skill**: one user-invoked skill that names the others and when to reach for each.
|
||||
|
||||
## Writing the description
|
||||
|
||||
A model-invoked **description** does two jobs — state what the skill is, and list the **branches** that should trigger it. Every word increases **context load**, so a description earns even harder pruning than the body:
|
||||
|
||||
- **Front-load the skill's leading word** — the description is where it does its invocation work.
|
||||
- **One trigger per branch.** Synonyms that rename a single branch are **duplication** — "build features using TDD … asks for test-first development" is one branch written twice. Collapse them; keep only genuinely distinct branches.
|
||||
- **Cut identity that's already in the body.** Keep the description to triggers, plus any "when another skill needs…" reach clause.
|
||||
|
||||
## Information hierarchy
|
||||
|
||||
A skill is built from two content types — **steps** and **reference** — that mix freely: a skill can be all steps, all reference, or both. The core decision is which to use and where each sits on the **information hierarchy**, a ladder ranked by how immediately the agent needs the material:
|
||||
|
||||
1. **In-skill step** — an ordered action in `SKILL.md`, the primary tier: what the agent does, in order. Each step ends on a **completion criterion**, the condition that tells the agent the work is done. Make it _checkable_ (can the agent tell done from not-done?) and, where it matters, _exhaustive_ ("every modified model accounted for", not "produce a change list") — a vague criterion invites **premature completion**.
|
||||
2. **In-skill reference** — a definition, rule, or fact in `SKILL.md`, consulted on demand. Often a legitimately flat peer-set (every rule of a review on one rung) — a fine arrangement, not a smell. _This skill is all reference._
|
||||
3. **External reference** — reference pushed out of `SKILL.md` into a separate file, reached by a **context pointer**, loaded only when the pointer fires. (Spans _disclosed_ reference — a sibling file like `GLOSSARY.md`, still part of the skill — through fully **external reference** that lives outside the skill system and any skill can point at.)
|
||||
|
||||
A demanding completion criterion drives thorough **legwork** — the digging the agent does within the work — whether the skill has steps or not, since "every rule applied" binds flat reference just as "every step done" binds a sequence.
|
||||
|
||||
Push too little down and the top bloats; push too much and you hide material the agent actually needs. That tension is the whole decision.
|
||||
|
||||
**Progressive disclosure** is the move down the ladder — out of `SKILL.md` into a linked file — so the top stays legible. Mechanics: a linked `.md` file in the skill folder, named for what it holds (this skill discloses its full definitions to `GLOSSARY.md`). Some skills are used in more than one way, and each distinct way is a **branch** — different runs taking different paths through the skill. Branching is the cleanest disclosure test: inline what every branch needs, and push behind a pointer what only some branches reach. A **context pointer**'s _wording_, not its target, decides when and how reliably the agent reaches the material.
|
||||
|
||||
Where the ladder decides _how far down_ a piece sits, **co-location** decides _what sits beside it_ once there: keep a concept's definition, rules, and caveats under one heading rather than scattered, so reading one part brings its neighbours with it.
|
||||
|
||||
## When to split
|
||||
|
||||
**Granularity** is how finely you divide skills, and each cut spends one of the two loads, so split only when the cut earns it. Two cuts:
|
||||
|
||||
- **By invocation** — split off a **model-invoked** skill when you have a distinct **leading word** that should trigger it on its own, or another skill must reach it. You pay **context load** for the new always-loaded **description**, so that independent reach has to be worth it.
|
||||
- **By sequence** — split a run of **steps** when the steps still ahead (a step's **post-completion steps**) tempt the agent to rush the one in front of it (**premature completion**). Keeping them out of view encourages the agent to do more **legwork** on the current task.
|
||||
|
||||
## Pruning
|
||||
|
||||
Keep each meaning in a **single source of truth**: one authoritative place, so changing the behaviour is a one-place edit.
|
||||
|
||||
Check every line for **relevance**: does it still bear on what the skill does?
|
||||
|
||||
Then hunt **no-ops** sentence by sentence, not just line by line: run the no-op test on each sentence in isolation, and when one fails, delete the whole sentence rather than trim words from it. Be aggressive — most prose that fails should go, not be rewritten.
|
||||
|
||||
## Leading words
|
||||
|
||||
A **leading word** is a compact concept already living in the model's pretraining that the agent thinks with while running the skill (e.g. _lesson_, _fog of war_, _tracer bullets_). Repeated throughout the text (though not necessarily - a strong leading word might only be needed once), it accumulates a distributed definition and anchors a whole region of behaviour in the fewest tokens, by recruiting priors the model already holds.
|
||||
|
||||
It serves predictability twice. In the body it anchors _execution_: the agent reaches for the same behaviour every time the word appears. In the description it anchors _invocation_: when the same word lives in your prompts, docs, and code, the agent links that shared language to the skill and fires it more reliably.
|
||||
|
||||
Hunt for opportunities to refactor skills to use leading words. A triad spelled out at three sites (**duplication**), a description spending a sentence to gesture at one idea — each is a passage begging to **collapse** into a single token. Examples include:
|
||||
|
||||
- "fast, deterministic, low-overhead" -> _tight_ — one quality restated across a phase — into a single pretrained word (a _tight_ loop).
|
||||
- "a loop you believe in" -> _red_ — converts a fuzzy gate into a binary observable state (the loop goes _red_ on the bug, or it doesn't).
|
||||
|
||||
You win twice over: fewer tokens, _and_ a sharper hook for the agent to hang its thinking on. Assume every skill is carrying restatements that leading words retire — go find them.
|
||||
|
||||
## Failure modes
|
||||
|
||||
Use these to diagnose issues the user may be having with the skill.
|
||||
|
||||
- **Premature completion** — ending a step before it's genuinely done, attention slipping to _being done_. Defence, in order: sharpen the completion criterion first (cheap, local); only if it is irreducibly fuzzy _and_ you observe the rush, hide the post-completion steps by splitting (the sequence cut).
|
||||
- **Duplication** — the same meaning in more than one place. Costs maintenance and tokens, and inflates a meaning's prominence on the ladder past its real rank.
|
||||
- **Sediment** — stale layers that settle because adding feels safe and removing feels risky. The default fate of any skill without a pruning discipline.
|
||||
- **Sprawl** — a skill simply too long, even when every line is live and unique. Hurts readability and maintainability and wastes tokens. The cure is the ladder: disclose **reference** behind pointers, and split by **branch** or sequence so each path carries only what it needs.
|
||||
- **No-op** — a line the model already obeys by default, so you pay load to say nothing. The test: does it change behaviour versus the default? A weak leading word (_be thorough_ when the agent is already thorough-ish) is a no-op; the fix is a stronger word (_relentless_), not a different technique.
|
||||
126
.agents/skills/writing-opencode-plugins/SKILL.md
Normal file
126
.agents/skills/writing-opencode-plugins/SKILL.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
name: writing-opencode-plugins
|
||||
description: OpenCode plugins, @opencode-ai/plugin, @opencode-ai/plugin/tui, plugin hooks, custom tools, TUI routes, slots, keymaps, and packaging. Use when creating, editing, reviewing, testing, or publishing server or TUI plugins for OpenCode.
|
||||
metadata:
|
||||
internal: true
|
||||
---
|
||||
|
||||
# Writing OpenCode Plugins
|
||||
|
||||
Use this skill to implement production-quality OpenCode plugins. Treat the repository's exported types and runtime as authoritative because plugin APIs are evolving and public docs may lag.
|
||||
|
||||
## Start Here
|
||||
|
||||
1. Decide which runtime owns the feature.
|
||||
2. Read the relevant public type before writing code.
|
||||
3. Find one focused in-repository example using the same API.
|
||||
4. Implement the smallest target-specific module.
|
||||
5. Test loading, behavior, failure, and cleanup in the owning package.
|
||||
|
||||
| Need | Plugin target | Import | Configuration |
|
||||
| -------------------------------------------------------------------- | --------------------------- | ------------------------------ | ---------------------------------------------------------------- |
|
||||
| Hooks, tools, auth, providers, model parameters, shell environment | Server | `@opencode-ai/plugin` | `opencode.json` or auto-discovered `.opencode/plugins/*.{ts,js}` |
|
||||
| Commands, keybindings, routes, dialogs, slots, themes, notifications | TUI | `@opencode-ai/plugin/tui` | Explicit `tui.json` `plugin` entry |
|
||||
| Both | Two target-only entrypoints | Both imports in separate files | Package exports `./server` and `./tui` |
|
||||
|
||||
Never export `server` and `tui` from the same module. Do not use server event hooks as a substitute for interactive TUI APIs.
|
||||
|
||||
## Verify The Current Contract
|
||||
|
||||
Read these files before implementing unfamiliar behavior:
|
||||
|
||||
- `packages/plugin/src/index.ts`: authoritative server plugin and hook types.
|
||||
- `packages/plugin/src/tool.ts`: custom tool schema, context, permission, metadata, attachments, and result types.
|
||||
- `packages/plugin/src/tui.ts`: authoritative TUI API and module types.
|
||||
- `packages/opencode/specs/tui-plugins.md`: TUI loading, packaging, lifecycle, and API semantics.
|
||||
- `packages/opencode/src/plugin/shared.ts`: target validation, IDs, and entrypoint resolution.
|
||||
- `packages/opencode/src/plugin/loader.ts`: install, compatibility, and import behavior.
|
||||
|
||||
If these disagree with examples or website docs, follow exported types and runtime behavior, then update stale documentation when appropriate.
|
||||
|
||||
## Choose A Module Shape
|
||||
|
||||
Prefer the explicit module object for new server plugins:
|
||||
|
||||
```ts
|
||||
import type { Plugin, PluginModule } from '@opencode-ai/plugin';
|
||||
|
||||
const server: Plugin = async ({ client, directory }, options) => ({
|
||||
dispose: async () => {},
|
||||
});
|
||||
|
||||
export default {
|
||||
id: 'acme.example',
|
||||
server,
|
||||
} satisfies PluginModule & { id: string };
|
||||
```
|
||||
|
||||
Legacy server-only local plugins may export a plugin function directly. In a legacy module every distinct named export is interpreted as a plugin, so do not export unrelated constants. Prefer a default module object for new code.
|
||||
|
||||
TUI plugins always use a default module object:
|
||||
|
||||
```tsx
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import type { TuiPlugin, TuiPluginModule } from '@opencode-ai/plugin/tui';
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.ui.toast({ message: 'Plugin loaded' });
|
||||
};
|
||||
|
||||
export default {
|
||||
id: 'acme.example-tui',
|
||||
tui,
|
||||
} satisfies TuiPluginModule & { id: string };
|
||||
```
|
||||
|
||||
File plugins require a stable, non-empty `id`. npm plugins may derive the ID from the package name, but an explicit namespaced ID makes state, diagnostics, and collision handling clearer.
|
||||
|
||||
## Engineering Rules
|
||||
|
||||
- Use TypeScript and `satisfies` against the public plugin type.
|
||||
- Parse and validate `options`; they arrive as unvalidated `Record<string, unknown>`.
|
||||
- Namespace plugin IDs, command IDs, route names, modes, slot names, and shared KV keys.
|
||||
- Use the directory supplied by the plugin or tool context, not `process.cwd()`.
|
||||
- Honor `AbortSignal` for long-running or cancellable work.
|
||||
- Use `client.app.log()` for structured server logging instead of `console.log`.
|
||||
- Request permission before sensitive or consequential custom-tool work.
|
||||
- Keep notifications privacy-safe; do not expose prompts, secrets, paths, commands, or raw errors.
|
||||
- Register only needed hooks and UI resources. Avoid broad event subscriptions when a specific hook exists.
|
||||
- Make cleanup bounded, idempotent, and safe after partial initialization.
|
||||
- Do not depend on undocumented load order to resolve ownership conflicts.
|
||||
|
||||
## Testing Workflow
|
||||
|
||||
Server plugin tests belong under `packages/opencode/test/plugin/` or the closest owning subsystem. TUI runtime tests belong under `packages/opencode/test/cli/tui/`; component-level TUI tests may belong in `packages/tui`.
|
||||
|
||||
Test at least:
|
||||
|
||||
- valid loading and target/entrypoint selection;
|
||||
- configured options and malformed options;
|
||||
- the observable behavior, not a duplicate of implementation logic;
|
||||
- abort, failure, and partial-initialization behavior;
|
||||
- cleanup or disposal;
|
||||
- duplicate IDs or registrations when relevant;
|
||||
- local file and npm packaging behavior when publishing.
|
||||
|
||||
Run tests from the package directory, never the repository root. Use `bun typecheck` from the owning package for type checking.
|
||||
|
||||
## Review Checklist
|
||||
|
||||
- The feature is in the correct server or TUI runtime.
|
||||
- Module shape and import path match the target.
|
||||
- Server and TUI entrypoints are separate.
|
||||
- IDs and persistent keys are stable and namespaced.
|
||||
- Options and external data are validated.
|
||||
- Hook output mutation preserves other plugins' changes.
|
||||
- Tools use context directory, permission, metadata, and abort correctly.
|
||||
- TUI keybindings are mode-gated unless intentionally global.
|
||||
- TUI resources and custom side effects are disposed.
|
||||
- Package exports, `engines.opencode`, and config target are correct.
|
||||
- Tests cover behavior and lifecycle.
|
||||
|
||||
## References
|
||||
|
||||
- [Server plugins](references/server-plugins.md): hooks, custom tools, lifecycle, and examples.
|
||||
- [TUI plugins](references/tui-plugins.md): keymaps, routes, dialogs, slots, state, and lifecycle.
|
||||
- [Packaging and testing](references/packaging-testing.md): config, package exports, compatibility, and test locations.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Packaging And Testing
|
||||
|
||||
## Local Configuration
|
||||
|
||||
Server plugin in `opencode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["./plugins/server.ts", ["package-name", { "key": "value" }]]
|
||||
}
|
||||
```
|
||||
|
||||
Server files under `.opencode/plugin/` or `.opencode/plugins/` are also auto-discovered. Relative configured paths resolve from the config file that declared them.
|
||||
|
||||
TUI plugin in `tui.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"plugin": [["./plugins/tui.tsx", { "key": "value" }]],
|
||||
"plugin_enabled": {
|
||||
"acme.demo": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`plugin_enabled` uses the resolved plugin ID, not its package or file spec. Persisted runtime enablement can override config.
|
||||
|
||||
After editing plugin or config-time files, restart OpenCode; the running session keeps its loaded configuration and modules.
|
||||
|
||||
## npm Package Shape
|
||||
|
||||
Publish separate target-only entrypoints:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@acme/opencode-plugin",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": {
|
||||
"import": "./dist/server.js",
|
||||
"config": { "serverOption": true }
|
||||
},
|
||||
"./tui": {
|
||||
"import": "./dist/tui.js",
|
||||
"config": { "tuiOption": true }
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"opencode": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opencode-ai/plugin": "^1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Server resolution prefers `exports["./server"]` and may fall back to `main`.
|
||||
- TUI resolution requires `exports["./tui"]`; it does not use `main`.
|
||||
- A package supporting both targets needs separate source and output files.
|
||||
- `exports[target].config` may provide default options written during first install.
|
||||
- Use `engines.opencode` to declare tested compatibility.
|
||||
- npm compatibility is checked; local file plugins bypass the engine check.
|
||||
- Pin package versions when reproducibility matters.
|
||||
- Plugin package install runs with lifecycle scripts disabled, so do not require `postinstall`.
|
||||
- Keep resolved entrypoints and theme paths inside the package directory.
|
||||
|
||||
Theme-only TUI packages may use `oc-themes`; consult `packages/opencode/specs/tui-plugins.md` for path and synchronization rules.
|
||||
|
||||
## Resolution And Identity
|
||||
|
||||
- npm declarations deduplicate by package name; higher-precedence/later declarations win.
|
||||
- File server and TUI specs have target-specific resolution behavior.
|
||||
- External modules may resolve/import in parallel, but activate sequentially.
|
||||
- IDs must not collide with built-ins or other loaded plugins.
|
||||
- Dynamic import failures are effectively permanent for the current process because Bun caches them.
|
||||
- `--pure` or `OPENCODE_PURE` skips external plugins.
|
||||
|
||||
Read `packages/opencode/src/plugin/shared.ts`, `loader.ts`, and `install.ts` before changing packaging behavior.
|
||||
|
||||
## Test Locations
|
||||
|
||||
Server plugin coverage:
|
||||
|
||||
- `packages/opencode/test/plugin/trigger.test.ts`: hook sequencing and failures.
|
||||
- `packages/opencode/test/plugin/loader-shared.test.ts`: resolution and module validation.
|
||||
- `packages/opencode/test/plugin/shared.test.ts`: shared target rules.
|
||||
- `packages/opencode/test/plugin/install.test.ts`: package install and config patching.
|
||||
- `packages/opencode/test/plugin/install-concurrency.test.ts`: concurrent writes.
|
||||
- `packages/opencode/test/plugin/auth-override.test.ts`: auth precedence.
|
||||
- `packages/opencode/test/tool/registry.test.ts`: schemas, results, and attachments.
|
||||
|
||||
TUI plugin coverage:
|
||||
|
||||
- `packages/opencode/test/cli/tui/plugin-loader.test.ts`: loading and ordering.
|
||||
- `packages/opencode/test/cli/tui/plugin-loader-entrypoint.test.ts`: target entrypoints.
|
||||
- `packages/opencode/test/cli/tui/plugin-lifecycle.test.ts`: rollback and cleanup.
|
||||
- `packages/opencode/test/cli/tui/plugin-toggle.test.ts`: persisted enablement.
|
||||
- `packages/opencode/test/cli/tui/plugin-add.test.ts`: runtime addition.
|
||||
- `packages/opencode/test/cli/tui/plugin-install.test.ts`: installation.
|
||||
- `packages/opencode/test/cli/tui/plugin-loader-pure.test.ts`: pure mode.
|
||||
|
||||
Use fixture helpers under `packages/opencode/test/fixture/` rather than reimplementing the loader in tests.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Run from the owning package, never the repository root:
|
||||
|
||||
```sh
|
||||
cd packages/opencode
|
||||
bun typecheck
|
||||
bun test test/plugin/trigger.test.ts
|
||||
bun test test/cli/tui/plugin-lifecycle.test.ts
|
||||
```
|
||||
|
||||
Select the smallest relevant tests first, then broader plugin suites. For interactive TUI verification, follow `packages/opencode/AGENTS.md`: run `bun dev` in detached `tmux`, capture output, and explicitly stop the session.
|
||||
|
||||
## Publishing Checklist
|
||||
|
||||
- Build output is ESM-compatible and contains no source-only path aliases.
|
||||
- Every advertised target has the correct package export.
|
||||
- Each target module exports only its own target shape.
|
||||
- Peer/runtime dependencies are classified correctly.
|
||||
- `engines.opencode` matches tested versions.
|
||||
- Default options are backward-compatible and validated at runtime.
|
||||
- Local file, pinned npm, and bare npm specs have been considered.
|
||||
- Loading, failure, cleanup, and upgrade behavior are tested.
|
||||
- README examples match the exported API and config target.
|
||||
@@ -0,0 +1,122 @@
|
||||
# Server Plugins
|
||||
|
||||
## Contract And Lifecycle
|
||||
|
||||
The public contract is `packages/plugin/src/index.ts`:
|
||||
|
||||
```ts
|
||||
type Plugin = (input: PluginInput, options?: Record<string, unknown>) => Promise<Hooks>
|
||||
```
|
||||
|
||||
`PluginInput` provides the SDK `client`, `project`, `directory`, `worktree`, `serverUrl`, Bun shell `$`, and experimental workspace registration.
|
||||
|
||||
The server runtime is `packages/opencode/src/plugin/index.ts`.
|
||||
|
||||
- Built-in plugins initialize before external plugins.
|
||||
- External modules may resolve concurrently, but activation is sequential for deterministic hook order.
|
||||
- `config` hooks run sequentially against the mutable merged config.
|
||||
- `event` subscribes to location-filtered events and is fire-and-forget.
|
||||
- Ordinary hooks run sequentially and share a mutable output object.
|
||||
- Ordinary hook failures propagate and stop later hooks for that trigger.
|
||||
- Initialization, config, and disposal failures are isolated and logged by the host.
|
||||
- `dispose` runs when the per-directory plugin scope closes.
|
||||
|
||||
Mutate hook output in place. Preserve values contributed by earlier plugins: append arrays, merge maps, and change only fields the plugin owns.
|
||||
|
||||
## Hook Selection
|
||||
|
||||
Use the narrowest hook that expresses the behavior:
|
||||
|
||||
| Goal | Hook |
|
||||
| ---------------------------------- | --------------------------------- |
|
||||
| Observe SDK events | `event` |
|
||||
| Modify merged configuration | `config` |
|
||||
| Add tools | `tool` |
|
||||
| Add provider authentication | `auth` |
|
||||
| Add or change provider models | `provider` |
|
||||
| Modify incoming user message | `chat.message` |
|
||||
| Modify LLM parameters or headers | `chat.params`, `chat.headers` |
|
||||
| Modify command parts | `command.execute.before` |
|
||||
| Validate or rewrite tool arguments | `tool.execute.before` |
|
||||
| Transform tool presentation/result | `tool.execute.after` |
|
||||
| Modify model-facing tool schemas | `tool.definition` |
|
||||
| Add shell environment variables | `shell.env` |
|
||||
| Influence permission decisions | `permission.ask` |
|
||||
| Customize compaction | `experimental.session.compacting` |
|
||||
|
||||
Read the complete `Hooks` interface before using experimental hooks.
|
||||
|
||||
## Custom Tools
|
||||
|
||||
Use `tool()` and Zod schemas from `tool.schema`:
|
||||
|
||||
```ts
|
||||
import { type Plugin, tool } from "@opencode-ai/plugin"
|
||||
|
||||
export default (async () => ({
|
||||
tool: {
|
||||
lookup_issue: tool({
|
||||
description: "Look up one issue by numeric ID",
|
||||
args: {
|
||||
id: tool.schema.number().int().positive().describe("Issue ID"),
|
||||
},
|
||||
async execute(args, context) {
|
||||
await context.ask({
|
||||
permission: "lookup_issue",
|
||||
patterns: [String(args.id)],
|
||||
always: ["*"],
|
||||
metadata: { id: args.id },
|
||||
})
|
||||
context.metadata({ title: `Issue ${args.id}` })
|
||||
|
||||
return {
|
||||
title: `Issue ${args.id}`,
|
||||
output: "Result",
|
||||
metadata: { id: args.id },
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
})) satisfies Plugin
|
||||
```
|
||||
|
||||
Tool rules:
|
||||
|
||||
- Write descriptions for the model, including when to use the tool and important constraints.
|
||||
- Describe arguments individually and constrain them in the schema.
|
||||
- Use `context.directory` and `context.worktree` for path resolution.
|
||||
- Pass `context.abort` into cancellable I/O.
|
||||
- Call `context.ask()` before performing work covered by a permission boundary.
|
||||
- Use `context.metadata()` for in-progress presentation; return final metadata in the result.
|
||||
- Return attachments only as declared file attachments with a MIME type and URL.
|
||||
- Keep output useful and bounded. The host may truncate large results and add truncation metadata.
|
||||
|
||||
Plugin tools with built-in IDs take precedence, but overriding built-ins should be explicit and tested.
|
||||
|
||||
## Auth And Providers
|
||||
|
||||
Use existing built-ins as references rather than inventing OAuth behavior:
|
||||
|
||||
- `packages/opencode/src/plugin/azure.ts`: simple API-key prompt.
|
||||
- `packages/opencode/src/plugin/xai.ts`: OAuth, refresh, and custom fetch behavior.
|
||||
- `packages/opencode/src/plugin/openai/codex.ts`: auth plus chat parameter hooks.
|
||||
- `packages/opencode/src/plugin/github-copilot/copilot.ts`: full auth/provider integration.
|
||||
|
||||
Do not log credentials, tokens, authorization codes, provider headers, or raw auth responses. Preserve provider identity and refresh semantics defined by `AuthHook`.
|
||||
|
||||
## Useful Examples
|
||||
|
||||
- `.opencode/plugins/model-task.ts`: custom subagent tool with permission, abort, metadata, and SDK calls when present in the worktree.
|
||||
- `packages/plugin/src/example.ts`: minimal package example.
|
||||
- `packages/opencode/test/fixture/agent-plugin.ts`: config mutation fixture.
|
||||
- `packages/opencode/src/plugin/*.ts`: built-in auth/provider implementations.
|
||||
|
||||
## Common Failures
|
||||
|
||||
- Exporting constants beside legacy plugin functions: every exported value may be treated as a plugin.
|
||||
- Using `process.cwd()` in a multi-directory process.
|
||||
- Replacing a shared output map or array and deleting earlier plugin contributions.
|
||||
- Forgetting that `event` is not awaited like ordinary hooks.
|
||||
- Assuming thrown hook errors are isolated.
|
||||
- Installing a missing dependency after a dynamic import failed and expecting the same process to recover; Bun caches failed imports.
|
||||
- Trusting options without validation.
|
||||
@@ -0,0 +1,130 @@
|
||||
# TUI Plugins
|
||||
|
||||
## Contract And Loading
|
||||
|
||||
The public contract is `packages/plugin/src/tui.ts`; technical behavior is documented in `packages/opencode/specs/tui-plugins.md`.
|
||||
|
||||
```ts
|
||||
type TuiPlugin = (api: TuiPluginApi, options: Record<string, unknown> | undefined, meta: TuiPluginMeta) => Promise<void>
|
||||
```
|
||||
|
||||
- Import from `@opencode-ai/plugin/tui`.
|
||||
- Export one default `{ id?, tui }` object. Named exports are ignored by the loader.
|
||||
- File plugins require an explicit non-empty ID.
|
||||
- Configure TUI plugins explicitly in `tui.json`; there is no directory auto-discovery.
|
||||
- JSX uses OpenTUI Solid, normally with `/** @jsxImportSource @opentui/solid */`.
|
||||
- TUI packages resolve only `exports["./tui"]`; they do not fall back to package `main` or root exports.
|
||||
|
||||
## API Routing
|
||||
|
||||
| Need | API |
|
||||
| -------------------------------- | ------------------------------------------ |
|
||||
| Commands and shortcuts | `api.keymap.registerLayer(...)` |
|
||||
| Temporary input context | `api.mode.push(...)` |
|
||||
| Full-screen UI | `api.route.register(...)`, `navigate(...)` |
|
||||
| Host dialogs and toast | `api.ui.dialog`, `Dialog*`, `toast(...)` |
|
||||
| Reuse the host prompt | `api.ui.Prompt` |
|
||||
| Inject host UI | `api.slots.register(...)` |
|
||||
| Theme tokens and switching | `api.theme` |
|
||||
| Synced sessions/providers/status | `api.state` |
|
||||
| SDK operations | `api.client` |
|
||||
| TUI event stream | `api.event.on(...)` |
|
||||
| Persistent shared values | `api.kv` |
|
||||
| Host-mediated notification/sound | `api.attention` |
|
||||
| Extra cleanup | `api.lifecycle.onDispose(...)` |
|
||||
|
||||
Do not use deprecated `api.command` in new plugins. Register commands and bindings through keymap layers.
|
||||
|
||||
## Commands And Modes
|
||||
|
||||
```tsx
|
||||
api.keymap.registerLayer({
|
||||
mode: "base",
|
||||
commands: [
|
||||
{
|
||||
name: "acme.demo.open",
|
||||
title: "Open demo",
|
||||
category: "Plugin",
|
||||
namespace: "palette",
|
||||
slashName: "demo",
|
||||
run() {
|
||||
api.route.navigate("acme.demo")
|
||||
},
|
||||
},
|
||||
],
|
||||
bindings: [{ key: "ctrl+shift+m", cmd: "acme.demo.open", desc: "Open demo" }],
|
||||
})
|
||||
```
|
||||
|
||||
Built-in modes are `base`, `modal`, and `autocomplete`. A layer without `mode` remains active across dialogs and autocomplete, so omit mode only for intentionally global behavior.
|
||||
|
||||
For plugin-owned full-screen interaction, push a namespaced mode inside the component and dispose it with Solid cleanup:
|
||||
|
||||
```tsx
|
||||
import { onCleanup } from "solid-js"
|
||||
|
||||
const pop_mode = api.mode.push("acme.demo")
|
||||
onCleanup(pop_mode)
|
||||
```
|
||||
|
||||
## Routes, Dialogs, And Slots
|
||||
|
||||
- Reserve `home` and `session` for host routes.
|
||||
- Namespace route names; duplicate routes are last-registration-wins.
|
||||
- Unknown routes render fallback UI rather than throwing.
|
||||
- Use host dialog components for standard interactions and `api.ui.dialog.replace()` for custom dialog content.
|
||||
- Use route params for serializable navigation state; keep component-local transient state in Solid primitives when appropriate.
|
||||
- `api.slots.register(...)` returns an assigned ID, not an unregister function.
|
||||
- Slot registration and other host API resources are scope-tracked automatically.
|
||||
- Read current slot names and props from `TuiHostSlotMap`, not copied lists.
|
||||
|
||||
## State And Persistence
|
||||
|
||||
- `api.tuiConfig` and `api.state` are live views, not initialization snapshots.
|
||||
- `api.kv` is shared by all plugins. Prefix every key with the plugin ID.
|
||||
- Check readiness where the API exposes it.
|
||||
- Persist only user preferences or durable plugin state, not derived host state.
|
||||
- Runtime enablement in KV overrides `tui.json` on startup.
|
||||
|
||||
`meta.state` is `first`, `updated`, or `same`. Use it for bounded migration or asset synchronization, not normal rendering behavior.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
The host automatically scope-tracks commands, keymap resources, routes, event subscriptions, slots, pushed modes, and sound packs.
|
||||
|
||||
- `api.lifecycle.signal` aborts before cleanup begins.
|
||||
- Use `api.lifecycle.onDispose()` for timers, sockets, file watchers, workers, or other plugin-owned resources.
|
||||
- Initialization failure rolls back tracked resources and does not prevent later plugins from loading.
|
||||
- Cleanup is reverse-order, awaited, idempotent, and constrained by a total five-second budget.
|
||||
- Keep cleanup fast and independently safe after partial initialization.
|
||||
|
||||
## UI Quality
|
||||
|
||||
- Use `api.theme.current` tokens instead of hard-coded colors.
|
||||
- Use `api.keys` to display shortcuts according to host formatting.
|
||||
- Make routes responsive to terminal dimensions and usable with keyboard-only input.
|
||||
- Avoid taking over global shortcuts without a strong reason.
|
||||
- Prefer host dialogs, prompts, and slots over visually inconsistent reimplementations.
|
||||
- Send attention through `api.attention.notify()` so the host owns focus, notification, and sound policy.
|
||||
- Keep notification text privacy-safe.
|
||||
|
||||
## Useful Examples
|
||||
|
||||
- `.opencode/plugins/tui-smoke.tsx`: broad API smoke implementation.
|
||||
- `packages/tui/src/feature-plugins/system/which-key.tsx`: focused keymap UI.
|
||||
- `packages/tui/src/feature-plugins/system/notifications.ts`: attention behavior.
|
||||
- `packages/tui/src/feature-plugins/system/diff-viewer.tsx`: route/UI integration.
|
||||
- `packages/tui/src/feature-plugins/home/tips.tsx`: host slot usage.
|
||||
- `packages/tui/src/feature-plugins/sidebar/context.tsx`: sidebar extension.
|
||||
|
||||
## Common Failures
|
||||
|
||||
- Expecting `.opencode/plugins` auto-discovery for TUI modules.
|
||||
- Exporting `{ server, tui }` from one module.
|
||||
- Omitting the default export, or relying on named exports.
|
||||
- Omitting an ID for a file plugin.
|
||||
- Registering an ungated keymap layer accidentally active in modal/autocomplete modes.
|
||||
- Treating KV as plugin-private.
|
||||
- Treating `slots.register()` as returning a disposer.
|
||||
- Expecting `plugins.install()` to activate a plugin; installation and runtime addition are separate.
|
||||
- Leaking timers or network resources because host tracking only covers host registrations.
|
||||
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "sveltejs/ai-tools", "template": "\n- {summary} {ref}" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["!@sveltejs/mcp", "!@sveltejs/opencode"]
|
||||
}
|
||||
22
.claude-plugin/marketplace.json
Normal file
22
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"owner": {
|
||||
"name": "Svelte"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "svelte",
|
||||
"source": "./plugins/claude/svelte",
|
||||
"description": "A plugin for all things Svelte development, MCP, skills, and more.",
|
||||
"lspServers": {
|
||||
"svelte": {
|
||||
"command": "svelteserver",
|
||||
"args": ["--stdio"],
|
||||
"extensionToLanguage": {
|
||||
".svelte": "svelte"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.cocoignore
Normal file
3
.cocoignore
Normal file
@@ -0,0 +1,3 @@
|
||||
.claude
|
||||
.github
|
||||
.vscode
|
||||
1
.cocominify
Normal file
1
.cocominify
Normal file
@@ -0,0 +1 @@
|
||||
packages/mcp-server/src/use_cases.json
|
||||
13
.cursor-plugin/marketplace.json
Normal file
13
.cursor-plugin/marketplace.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "svelte",
|
||||
"owner": {
|
||||
"name": "Svelte"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "svelte",
|
||||
"source": "./plugins/cursor/svelte",
|
||||
"description": "A plugin for all things Svelte development, MCP, skills, and more."
|
||||
}
|
||||
]
|
||||
}
|
||||
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
/packages/**/test/** -linguist-detectable
|
||||
/packages/**/fixtures/** -linguist-detectable
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
open_collective: svelte
|
||||
36
.github/ISSUE_TEMPLATE/autofixer_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/autofixer_request.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: 'Autofixer Request'
|
||||
description: Request a new Autofixer for the MCP
|
||||
title: '[Autofixer Request] '
|
||||
labels: [enhancement, autofixer]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to request A new autofixer!
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Describe the problematic code
|
||||
description: Please provide a clear and concise description the problem. Is much better if you can provide a code snippet the AI constantly get's wrong.
|
||||
placeholder: The AI keeps messing with...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: Describe what the autofixer should suggest
|
||||
description: If you were looking at this code, what would you suggest to the AI for it to fix it?
|
||||
placeholder: You should never do this, instead do that...
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: importance
|
||||
attributes:
|
||||
label: Importance
|
||||
description: How important is this feature to you?
|
||||
options:
|
||||
- nice to have
|
||||
- would make my life easier
|
||||
- the MCP is useless to me without it
|
||||
validations:
|
||||
required: true
|
||||
52
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
52
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: "\U0001F41E Bug report"
|
||||
description: Report an issue with Svelte
|
||||
title: '[Bug] '
|
||||
labels: ['triage: bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks!
|
||||
placeholder: Bug description
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction
|
||||
description: Please provide a link to a repo or REPL that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed.
|
||||
placeholder: Reproduction
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: 'Please provide some logs or screenshot of the agentic workflow failing.'
|
||||
render: shell
|
||||
- type: input
|
||||
id: mcp-client
|
||||
attributes:
|
||||
label: MCP Client
|
||||
description: Which MCP client are you using?
|
||||
render: shell
|
||||
placeholder: claude-code, codex, opencode
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: Severity
|
||||
description: Select the severity of this issue
|
||||
options:
|
||||
- annoyance
|
||||
- minor functionality loss
|
||||
- major functionality loss
|
||||
- blocking all usage of the mcp
|
||||
validations:
|
||||
required: true
|
||||
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: 'Feature Request'
|
||||
description: Request a new MCP feature
|
||||
title: '[Feature Request] '
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to request this feature! If your feature request is complex or substantial enough to warrant in-depth discussion, maintainers may close the issue and ask you to open an [RFC](https://github.com/sveltejs/rfcs).
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Describe the problem
|
||||
description: Please provide a clear and concise description the problem this feature would solve. The more information you can provide here, the better.
|
||||
placeholder: I'm always frustrated when...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe the proposed solution
|
||||
description: Please provide a clear and concise description of what you would like to happen.
|
||||
placeholder: I would like to see...
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: importance
|
||||
attributes:
|
||||
label: Importance
|
||||
description: How important is this feature to you?
|
||||
options:
|
||||
- nice to have
|
||||
- would make my life easier
|
||||
- the MCP is useless to me without it
|
||||
validations:
|
||||
required: true
|
||||
11
.github/workflows/check.yml
vendored
11
.github/workflows/check.yml
vendored
@@ -13,17 +13,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -32,5 +30,4 @@ jobs:
|
||||
- name: Run type check
|
||||
run: pnpm run check
|
||||
env:
|
||||
DATABASE_URL: file:test.db
|
||||
VOYAGE_API_KEY: dummy-key
|
||||
|
||||
11
.github/workflows/lint.yml
vendored
11
.github/workflows/lint.yml
vendored
@@ -13,17 +13,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -32,5 +30,4 @@ jobs:
|
||||
- name: Run linting
|
||||
run: pnpm run lint
|
||||
env:
|
||||
DATABASE_URL: file:test.db
|
||||
VOYAGE_API_KEY: dummy-key
|
||||
|
||||
41
.github/workflows/publish-mcp.yml
vendored
Normal file
41
.github/workflows/publish-mcp.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Publish to MCP Registry
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
MCP_KEY:
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-mcp:
|
||||
name: Publish to MCP Registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Publish to MCP Registry
|
||||
working-directory: packages/mcp-stdio
|
||||
env:
|
||||
MCP_KEY: ${{ secrets.MCP_KEY }}
|
||||
run: |
|
||||
NAME=mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz
|
||||
# Download MCP Publisher pinned to v1.4.0 using latest https for security and save it to a file named mcp-publisher.tar.gz
|
||||
curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fL "https://github.com/modelcontextprotocol/registry/releases/download/v1.4.0/$NAME" -O
|
||||
|
||||
# Verify the SHA256 checksum of the downloaded file
|
||||
sha256sum --ignore-missing -c ./checksums/registry_1.4.0_checksums.txt
|
||||
|
||||
# Extract the tarball
|
||||
mkdir tmp
|
||||
tar -xzf $NAME --no-same-owner --no-same-permissions -C tmp
|
||||
|
||||
# Install the MCP Publisher binary
|
||||
install -m 0755 tmp/mcp-publisher .
|
||||
|
||||
# Login using DNS
|
||||
./mcp-publisher login dns --domain svelte.dev --private-key "${MCP_KEY}"
|
||||
|
||||
# Publish to MCP Registry
|
||||
./mcp-publisher publish
|
||||
66
.github/workflows/release-svelte-skill.yml
vendored
Normal file
66
.github/workflows/release-svelte-skill.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Release Svelte Skills
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'tools/skills/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
detect-skills:
|
||||
permissions:
|
||||
contents: read
|
||||
if: github.repository == 'sveltejs/ai-tools'
|
||||
name: Detect changed skills
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skills: ${{ steps.find-skills.outputs.skills }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Find all skills
|
||||
id: find-skills
|
||||
run: |
|
||||
skills=$(ls -d tools/skills/*/ | xargs -I {} basename {} | jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||
echo "skills=$skills" >> $GITHUB_OUTPUT
|
||||
|
||||
release:
|
||||
needs: detect-skills
|
||||
if: needs.detect-skills.outputs.skills != '[]'
|
||||
permissions:
|
||||
contents: write
|
||||
name: Release ${{ matrix.skill }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
skill: ${{ fromJson(needs.detect-skills.outputs.skills) }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Get version from date
|
||||
id: version
|
||||
run: echo "version=$(date +'%Y.%m.%d-%H%M%S')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create zip
|
||||
run: |
|
||||
cd tools/skills
|
||||
zip -r ${{ matrix.skill }}.zip ${{ matrix.skill }}/
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
|
||||
with:
|
||||
tag_name: ${{ matrix.skill }}-v${{ steps.version.outputs.version }}
|
||||
name: ${{ matrix.skill }} v${{ steps.version.outputs.version }}
|
||||
body: |
|
||||
Automated release of the ${{ matrix.skill }} skill.
|
||||
|
||||
This release was triggered by changes to the `tools/skills/${{ matrix.skill }}/` directory.
|
||||
files: tools/skills/${{ matrix.skill }}.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
70
.github/workflows/release.yml
vendored
Normal file
70
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: write # to create release (changesets/action)
|
||||
id-token: write # OpenID Connect token needed for provenance
|
||||
pull-requests: write # to create pull request (changesets/action)
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'sveltejs/ai-tools'
|
||||
name: Release
|
||||
runs-on: ${{ matrix.os }}
|
||||
outputs:
|
||||
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
|
||||
strategy:
|
||||
matrix:
|
||||
# pseudo-matrix for convenience, NEVER use more than a single combination
|
||||
node: [24]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
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
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
package-manager-cache: true # caches pnpm via packageManager field in package.json
|
||||
cache: 'pnpm'
|
||||
- name: install
|
||||
run: pnpm install --frozen-lockfile --prefer-offline --ignore-scripts
|
||||
- name: build
|
||||
run: pnpm run --filter ./packages/mcp-stdio/ build
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
# pinned for security, always review third party action code before updating
|
||||
uses: changesets/action@c48e67d110a68bc90ccf1098e9646092baacaa87 # v1.6.0
|
||||
with:
|
||||
# This expects you to have a script called changeset:version version that calls changeset version and updated what it needs to be updated
|
||||
version: pnpm changeset:version
|
||||
# This expects you to have a script called release which does a build for your packages and calls changeset publish
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
|
||||
publish-mcp:
|
||||
needs: release
|
||||
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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
|
||||
95
.github/workflows/sync-plugins.yml
vendored
Normal file
95
.github/workflows/sync-plugins.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
name: Sync Plugins
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'tools/**'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sync-plugins:
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'sveltejs/ai-tools'
|
||||
name: Sync Plugins from tools/
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: Sync plugins
|
||||
run: pnpm sync-plugins
|
||||
|
||||
- name: Generate skills documentation
|
||||
run: pnpm generate-skill-docs
|
||||
|
||||
- name: Generate subagent documentation
|
||||
run: pnpm generate-subagent-docs
|
||||
|
||||
- name: Check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git diff --exit-code \
|
||||
plugins/claude/svelte/ \
|
||||
plugins/cursor/svelte/ \
|
||||
packages/opencode/skills/ \
|
||||
packages/opencode/instructions/ \
|
||||
packages/opencode/schema.json \
|
||||
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 plugins from tools/'
|
||||
branch: chore/sync-plugins
|
||||
delete-branch: true
|
||||
title: 'chore: sync plugins from tools/'
|
||||
body: |
|
||||
## Summary
|
||||
Automatically synced all plugins from the `tools/` source of truth.
|
||||
|
||||
This PR was triggered by changes to `tools/**`.
|
||||
|
||||
## Changes
|
||||
- Synced `plugins/claude/svelte/` (skills, agents with `permissionMode`)
|
||||
- Synced `plugins/cursor/svelte/` (skills, agents, rules)
|
||||
- Synced `packages/opencode/` (skills, instructions, schema)
|
||||
- Updated documentation
|
||||
|
||||
## Generated by
|
||||
GitHub Action: Sync Plugins
|
||||
labels: |
|
||||
chore
|
||||
automated
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -13,17 +13,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -32,11 +30,9 @@ jobs:
|
||||
- name: Build project
|
||||
run: pnpm run build
|
||||
env:
|
||||
DATABASE_URL: file:test.db
|
||||
VOYAGE_API_KEY: dummy-key
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm run test
|
||||
env:
|
||||
DATABASE_URL: file:test.db
|
||||
VOYAGE_API_KEY: dummy-key
|
||||
|
||||
78
.github/workflows/update-opencode-jsonschema.yml
vendored
Normal file
78
.github/workflows/update-opencode-jsonschema.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
name: Update OpenCode JSON Schema
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'packages/opencode/config.js'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'sveltejs/ai-tools'
|
||||
name: Update OpenCode JSON Schema
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: Generate opencode JSON schema
|
||||
run: pnpm generate-opencode-jsonschema
|
||||
|
||||
- name: Check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git diff --exit-code packages/opencode/schema.json || 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: 'docs: update opencode JSON schema'
|
||||
branch: docs/update-opencode-jsonschema
|
||||
delete-branch: true
|
||||
title: 'docs: update opencode JSON schema'
|
||||
body: |
|
||||
## Summary
|
||||
Automatically generated update for OpenCode JSON schema.
|
||||
|
||||
This PR was triggered by changes to the OpenCode configuration file `packages/opencode/config.js`.
|
||||
|
||||
## Changes
|
||||
- Updated `packages/opencode/schema.json` with latest JSON schema
|
||||
|
||||
## Generated by
|
||||
GitHub Action: Update OpenCode JSON Schema
|
||||
labels: |
|
||||
automated
|
||||
79
.github/workflows/update-prompt-docs.yml
vendored
Normal file
79
.github/workflows/update-prompt-docs.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: Update Prompt Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'packages/mcp-server/src/mcp/handlers/prompts/**'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'sveltejs/ai-tools'
|
||||
name: Update Prompt Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 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: Generate prompt documentation
|
||||
run: pnpm generate-prompt-docs
|
||||
|
||||
- name: Check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git diff --exit-code documentation/docs/30-capabilities/30-prompts.md || 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: 'docs: update prompts documentation'
|
||||
branch: docs/update-prompt-docs
|
||||
delete-branch: true
|
||||
title: 'docs: update prompt documentation'
|
||||
body: |
|
||||
## Summary
|
||||
Automatically generated documentation update for MCP prompts.
|
||||
|
||||
This PR was triggered by changes to the prompts folder in `packages/mcp-server/src/mcp/handlers/prompts/`.
|
||||
|
||||
## Changes
|
||||
- Updated `documentation/docs/30-capabilities/30-prompts.md` with latest prompt definitions
|
||||
|
||||
## Generated by
|
||||
GitHub Action: Update Prompt Documentation
|
||||
labels: |
|
||||
documentation
|
||||
automated
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,12 +1,16 @@
|
||||
node_modules
|
||||
/apps/**/node_modules
|
||||
/packages/**/node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
/apps/**/.output
|
||||
/apps/**/.vercel
|
||||
/apps/**/.netlify
|
||||
/apps/**/.wrangler
|
||||
/**/.svelte-kit
|
||||
/apps/**/build
|
||||
/apps/**/dist
|
||||
/packages/**/dist
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
@@ -14,14 +18,25 @@ Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
/apps/**/.env
|
||||
/packages/**/.env
|
||||
.env.*
|
||||
/apps/**/.env.*
|
||||
/packages/**/.env.*
|
||||
!.env.example
|
||||
/apps/**/!.env.example
|
||||
/packages/**/!.env.example
|
||||
!.env.test
|
||||
/apps/**/!.env.test
|
||||
/packages/**/!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
/apps/**/vite.config.js.timestamp-*
|
||||
/apps/**/vite.config.ts.timestamp-*
|
||||
|
||||
# SQLite
|
||||
*.db
|
||||
dist
|
||||
/apps/**/*.db
|
||||
/packages/**/*.db
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte-llm": {
|
||||
"type": "http",
|
||||
"url": "https://svelte-llm.stanislav.garden/mcp/mcp"
|
||||
"svelte": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["packages/mcp-stdio/dist/index.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,10 @@ bun.lockb
|
||||
# Miscellaneous
|
||||
/static/
|
||||
/drizzle/
|
||||
/**/.svelte-kit/*
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
.changeset/
|
||||
|
||||
/packages/opencode/schema.json
|
||||
@@ -10,6 +10,12 @@
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "**/references/*.md",
|
||||
"options": {
|
||||
"embeddedLanguageFormatting": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
94
.vscode/mcp-snippets.code-snippets
vendored
Normal file
94
.vscode/mcp-snippets.code-snippets
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
{
|
||||
// Place your svelte-mcp workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
"Setup Function": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "!setup-mcp",
|
||||
"body": [
|
||||
"import type { SvelteMcp } from '../../index.js';",
|
||||
"import * as v from 'valibot';",
|
||||
"import { icons } from '../../icons/index.js';",
|
||||
"",
|
||||
"export function ${1:function_name}(server: SvelteMcp) {",
|
||||
"\t$0",
|
||||
"}",
|
||||
],
|
||||
"description": "Create a setup function for a tool/resource/prompt handler",
|
||||
},
|
||||
"Autofixer": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "!autofixer",
|
||||
"body": [
|
||||
"import type { Autofixer } from './index.js';",
|
||||
"export const ${1:autofixer_name}: Autofixer = {",
|
||||
"\t$0",
|
||||
"};",
|
||||
],
|
||||
"description": "Create a setup export for an autofixer",
|
||||
},
|
||||
"Prompt Generator": {
|
||||
"scope": "javascript,typescript",
|
||||
"prefix": "!prompt",
|
||||
"body": [
|
||||
"import type { SvelteMcp } from '../../index.js';",
|
||||
"import { icons } from '../../icons/index.js';",
|
||||
"",
|
||||
"/**",
|
||||
" * Function that actually generates the prompt string. You can use this in the MCP server handler to generate the prompt, it can accept arguments",
|
||||
" * if needed (it will always be invoked manually so it's up to you to provide the arguments).",
|
||||
" */",
|
||||
"function ${1:prompt_name}() {",
|
||||
"\treturn `$0`;",
|
||||
"}",
|
||||
"",
|
||||
"/**",
|
||||
" * This function is used to generate the prompt to update the docs in the script `/scripts/update-docs-prompts.ts` it should use the default export",
|
||||
" * function and pass in the arguments. Since it will be included in the documentation if it's an argument that the MCP will expose it should",
|
||||
" * be in the format [NAME_OF_THE_ARGUMENT] to signal the user that it can substitute it.",
|
||||
" * ",
|
||||
" * The name NEEDS to be `generate_for_docs`.",
|
||||
" */",
|
||||
"export async function generate_for_docs() {",
|
||||
"\treturn ${1:prompt_name}();",
|
||||
"}",
|
||||
"",
|
||||
"/**",
|
||||
" * Human readable description of what the prompt does. It will be included in the documentation.",
|
||||
" * ",
|
||||
" * The name NEEDS to be `docs_description`.",
|
||||
" */",
|
||||
"export const docs_description = '';",
|
||||
"",
|
||||
"export function setup_${1:prompt_name}(server: SvelteMcp) {",
|
||||
"\tserver.prompt(",
|
||||
"\t\t{",
|
||||
"\t\t\tname: '${1:prompt_name}',",
|
||||
"\t\t\ttitle: '${2:title}',",
|
||||
"\t\t\tdescription:",
|
||||
"\t\t\t\t'${3:llm_description}',",
|
||||
"\t\t\ticons,",
|
||||
"\t\t},",
|
||||
"\t\tasync () => {",
|
||||
"\t\t\treturn {",
|
||||
"\t\t\t\tmessages: [",
|
||||
"\t\t\t\t\t{",
|
||||
"\t\t\t\t\t\trole: 'assistant',",
|
||||
"\t\t\t\t\t\tcontent: {",
|
||||
"\t\t\t\t\t\t\ttype: 'text',",
|
||||
"\t\t\t\t\t\t\ttext: ${1:prompt_name}(),",
|
||||
"\t\t\t\t\t\t},",
|
||||
"\t\t\t\t\t},",
|
||||
"\t\t\t\t],",
|
||||
"\t\t\t};",
|
||||
"\t\t},",
|
||||
"\t);",
|
||||
"}",
|
||||
],
|
||||
"description": "Create a setup export for a prompt generator",
|
||||
},
|
||||
}
|
||||
2
.vscode/mcp.json
vendored
2
.vscode/mcp.json
vendored
@@ -3,7 +3,7 @@
|
||||
"Svelte MCP": {
|
||||
"type": "stdio",
|
||||
"command": "node",
|
||||
"args": ["dist/lib/stdio.js"]
|
||||
"args": ["packages/mcp-stdio/dist/index.js"]
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
|
||||
@@ -83,19 +83,18 @@ Located in `src/lib/server/analyze/`:
|
||||
|
||||
Required environment variables:
|
||||
|
||||
- `DATABASE_URL`: SQLite database path (default: `file:test.db`)
|
||||
- `VOYAGE_API_KEY`: API key for embeddings support (optional)
|
||||
|
||||
When connected to the svelte-llm MCP server, you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||
|
||||
## Available MCP Tools:
|
||||
|
||||
### 1. list_sections
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get_documentation
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list_sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections and then use the get_documentation tool to fetch ALL documentation sections that are relevant for the users task.
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 [Svelte Contributors](https://github.com/sveltejs/ai-tools/graphs/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -6,12 +6,15 @@ Repo for the official Svelte MCP server.
|
||||
|
||||
```
|
||||
pnpm i
|
||||
cp .env.example .env
|
||||
cp apps/mcp-remote/.env.example apps/mcp-remote/.env
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
1. Set the VOYAGE_API_KEY for embeddings support
|
||||
|
||||
> [!NOTE]
|
||||
> Currently to prevent having a bunch of Timeout logs on vercel we shut down the SSE channel immediately. This means that we can't use `server.log` and we are not sending `list-changed` notifications. We can use elicitation and sampling since those are sent on the same stream of the POST request
|
||||
|
||||
### Local dev tools
|
||||
|
||||
#### MCP inspector
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
DATABASE_URL=file:test.db
|
||||
VOYAGE_API_KEY=your_actual_api_key_here
|
||||
63
apps/mcp-remote/package.json
Normal file
63
apps/mcp-remote/package.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "@sveltejs/mcp-remote",
|
||||
"version": "0.0.1",
|
||||
"description": "The official Svelte MCP server implementation",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
"svelte-mcp": "./dist/lib/stdio.js"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"build:mcp": "tsc --project tsconfig.build.json",
|
||||
"prepublishOnly": "pnpm build:mcp",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"lint:fix": "prettier --write . && eslint . --fix",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run",
|
||||
"test:watch": "npm run test:unit -- --watch",
|
||||
"inspect": "pnpm mcp-inspector"
|
||||
},
|
||||
"keywords": [
|
||||
"svelte",
|
||||
"tmcp",
|
||||
"mcp",
|
||||
"server"
|
||||
],
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "catalog:lint",
|
||||
"@eslint/js": "catalog:lint",
|
||||
"@modelcontextprotocol/inspector": "catalog:ai",
|
||||
"@sveltejs/adapter-vercel": "catalog:svelte",
|
||||
"@sveltejs/kit": "catalog:svelte",
|
||||
"@sveltejs/vite-plugin-svelte": "catalog:svelte",
|
||||
"@types/node": "catalog:tooling",
|
||||
"@typescript-eslint/parser": "catalog:lint",
|
||||
"eslint-config-prettier": "catalog:lint",
|
||||
"eslint-plugin-svelte": "catalog:lint",
|
||||
"globals": "catalog:lint",
|
||||
"prettier": "catalog:lint",
|
||||
"prettier-plugin-svelte": "catalog:lint",
|
||||
"svelte": "catalog:svelte",
|
||||
"svelte-check": "catalog:svelte",
|
||||
"svelte-eslint-parser": "catalog:lint",
|
||||
"typescript": "catalog:tooling",
|
||||
"vite": "catalog:tooling",
|
||||
"vite-plugin-devtools-json": "catalog:tooling",
|
||||
"vitest": "catalog:tooling"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/mcp-server": "workspace:^",
|
||||
"@tmcp/transport-http": "catalog:tmcp",
|
||||
"@vercel/analytics": "catalog:tooling",
|
||||
"tmcp": "catalog:tmcp"
|
||||
}
|
||||
}
|
||||
27
apps/mcp-remote/src/hooks.server.ts
Normal file
27
apps/mcp-remote/src/hooks.server.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { dev } from '$app/environment';
|
||||
import { http_transport } from '$lib/mcp/index.js';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { track } from '@vercel/analytics/server';
|
||||
|
||||
export async function handle({ event, resolve }) {
|
||||
if (event.request.method === 'GET') {
|
||||
const accept = event.request.headers.get('accept');
|
||||
if (accept) {
|
||||
const accepts = accept.split(',');
|
||||
if (!accepts.includes('text/event-stream')) {
|
||||
// the request it's a browser request, not an MCP client request
|
||||
// it means someone probably opened it from the docs...we should redirect to the docs
|
||||
redirect(302, 'https://svelte.dev/docs/mcp/overview');
|
||||
}
|
||||
}
|
||||
}
|
||||
const mcp_response = await http_transport.respond(event.request, {
|
||||
// only add analytics in production
|
||||
track: dev
|
||||
? undefined
|
||||
: async (session_id, event, extra) => {
|
||||
await track(event, { session_id, ...(extra ? { extra } : {}) });
|
||||
},
|
||||
});
|
||||
return mcp_response ?? resolve(event);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
0
apps/mcp-remote/src/lib/index.ts
Normal file
0
apps/mcp-remote/src/lib/index.ts
Normal file
12
apps/mcp-remote/src/lib/mcp/index.ts
Normal file
12
apps/mcp-remote/src/lib/mcp/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { server } from '@sveltejs/mcp-server';
|
||||
import { HttpTransport } from '@tmcp/transport-http';
|
||||
|
||||
export const http_transport = new HttpTransport(server, {
|
||||
cors: true,
|
||||
path: '/mcp',
|
||||
// we are deploying on vercel the SSE connection will timeout after 5 minutes...for
|
||||
// the moment we are not sending back any notifications (logs, or list changed notifications)
|
||||
// so it's a waste of resources to keep a connection open that will error
|
||||
// after 5 minutes making the logs dirty.
|
||||
disableSse: true,
|
||||
});
|
||||
BIN
apps/mcp-remote/static/logo.png
Normal file
BIN
apps/mcp-remote/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
1
apps/mcp-remote/static/logo.svg
Normal file
1
apps/mcp-remote/static/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416.9 93.1c-41.1-58.9-122.4-76.3-181.2-38.9L132.5 120c-28.2 17.7-47.6 46.5-53.5 79.3-4.9 27.3-.6 55.5 12.3 80-8.8 13.4-14.9 28.5-17.7 44.2-5.9 33.4 1.8 67.8 21.6 95.4 41.2 58.9 122.4 76.3 181.2 38.9L379.6 392c28.2-17.7 47.6-46.5 53.5-79.3 4.9-27.3.6-55.5-12.3-80 8.8-13.4 14.9-28.4 17.7-44.2 5.8-33.4-1.9-67.8-21.6-95.4" style="fill:#ff3e00"/><path d="M225.6 424.5c-33.3 8.6-68.4-4.4-88-32.6-11.9-16.6-16.5-37.3-13-57.4.6-3.3 1.4-6.5 2.5-9.6l1.9-5.9 5.3 3.9c12.2 9 25.9 15.8 40.4 20.2l3.8 1.2-.4 3.8c-.5 5.4 1 10.9 4.2 15.3 5.9 8.5 16.5 12.4 26.5 9.8 2.2-.6 4.4-1.5 6.3-2.8l103.2-65.8c5.1-3.2 8.6-8.4 9.7-14.4 1.1-6.1-.3-12.3-3.9-17.3-5.9-8.5-16.5-12.4-26.5-9.8-2.2.6-4.4 1.5-6.3 2.8L252 291c-6.5 4.1-13.5 7.2-21 9.2-33.3 8.6-68.4-4.4-88-32.6-11.9-16.6-16.5-37.3-13-57.4 3.5-19.7 15.2-37 32.2-47.7l103.2-65.8c6.5-4.1 13.5-7.2 21-9.2 33.3-8.6 68.4 4.4 88 32.6 11.9 16.6 16.5 37.3 13 57.4-.6 3.3-1.4 6.5-2.5 9.6L383 193l-5.3-3.9c-12.2-9-25.9-15.8-40.4-20.2l-3.8-1.2.4-3.8c.5-5.4-1-10.9-4.2-15.3-5.9-8.5-16.5-12.4-26.5-9.8-2.2.6-4.4 1.5-6.3 2.8l-103.2 65.8c-5.1 3.2-8.6 8.4-9.7 14.4-1.1 6.1.3 12.3 3.9 17.3 5.9 8.5 16.5 12.4 26.5 9.8 2.2-.6 4.4-1.5 6.3-2.8L260 221c6.5-4.1 13.5-7.2 21-9.2 33.3-8.6 68.4 4.4 88 32.6 11.9 16.6 16.5 37.3 13 57.4-3.5 19.7-15.2 37-32.2 47.7l-103.2 65.8c-6.5 4.1-13.6 7.2-21 9.2" style="fill:#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
19
apps/mcp-remote/tsconfig.json
Normal file
19
apps/mcp-remote/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
22
apps/mcp-remote/vite.config.ts
Normal file
22
apps/mcp-remote/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import devtoolsJson from 'vite-plugin-devtools-json';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit(), devtoolsJson()],
|
||||
// we don't have tests yet so we just comment this out for now
|
||||
// test: {
|
||||
// expect: { requireAssertions: true },
|
||||
// projects: [
|
||||
// {
|
||||
// extends: './vite.config.ts',
|
||||
// test: {
|
||||
// name: 'server',
|
||||
// environment: 'node',
|
||||
// include: ['src/**/*.{test,spec}.{js,ts}'],
|
||||
// exclude: ['src/**/*.svelte.{test,spec}.{js,ts}'],
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
});
|
||||
320
docs/tmcp.md
Normal file
320
docs/tmcp.md
Normal file
@@ -0,0 +1,320 @@
|
||||
> [!WARNING]
|
||||
> Unfortunately i published the 1.0 by mistake...this package is currently under heavy development so there will be breaking changes in minors...threat this `1.x` as the `0.x` of any other package. Sorry for the disservice, every breaking will be properly labeled in the PR name.
|
||||
|
||||
# tmcp
|
||||
|
||||
A lightweight, schema-agnostic Model Context Protocol (MCP) server implementation with unified API design.
|
||||
|
||||
## Why tmcp?
|
||||
|
||||
tmcp offers significant advantages over the official MCP SDK:
|
||||
|
||||
- **🔄 Schema Agnostic**: Works with any validation library through adapters
|
||||
- **📦 No Weird Dependencies**: Minimal footprint with only essential dependencies (looking at you `express`)
|
||||
- **🎯 Unified API**: Consistent, intuitive interface across all MCP capabilities
|
||||
- **🔌 Extensible**: Easy to add support for new schema libraries
|
||||
- **⚡ Lightweight**: No bloat, just what you need
|
||||
|
||||
## Supported Schema Libraries
|
||||
|
||||
tmcp works with all major schema validation libraries through its adapter system:
|
||||
|
||||
- **Zod** - `@tmcp/adapter-zod`
|
||||
- **Valibot** - `@tmcp/adapter-valibot`
|
||||
- **ArkType** - `@tmcp/adapter-arktype`
|
||||
- **Effect Schema** - `@tmcp/adapter-effect`
|
||||
- **Zod v3** - `@tmcp/adapter-zod-v3`
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm install tmcp
|
||||
# Choose your preferred schema library adapter
|
||||
pnpm install @tmcp/adapter-zod zod
|
||||
# Choose your preferred transport
|
||||
pnpm install @tmcp/transport-stdio # For CLI/desktop apps
|
||||
pnpm install @tmcp/transport-http # For web-based clients
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Standard I/O Transport (CLI/Desktop)
|
||||
|
||||
```javascript
|
||||
import { McpServer } from 'tmcp';
|
||||
import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod';
|
||||
import { StdioTransport } from '@tmcp/transport-stdio';
|
||||
import { z } from 'zod';
|
||||
|
||||
const adapter = new ZodJsonSchemaAdapter();
|
||||
const server = new McpServer(
|
||||
{
|
||||
name: 'my-server',
|
||||
version: '1.0.0',
|
||||
description: 'My awesome MCP server',
|
||||
},
|
||||
{
|
||||
adapter,
|
||||
capabilities: {
|
||||
tools: { listChanged: true },
|
||||
prompts: { listChanged: true },
|
||||
resources: { listChanged: true },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Define a tool with type-safe schema
|
||||
server.tool(
|
||||
{
|
||||
name: 'calculate',
|
||||
description: 'Perform mathematical calculations',
|
||||
schema: z.object({
|
||||
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
},
|
||||
async ({ operation, a, b }) => {
|
||||
switch (operation) {
|
||||
case 'add':
|
||||
return a + b;
|
||||
case 'subtract':
|
||||
return a - b;
|
||||
case 'multiply':
|
||||
return a * b;
|
||||
case 'divide':
|
||||
return a / b;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Start the server with stdio transport
|
||||
const transport = new StdioTransport(server);
|
||||
transport.listen();
|
||||
```
|
||||
|
||||
### HTTP Transport (Web-based)
|
||||
|
||||
```javascript
|
||||
import { McpServer } from 'tmcp';
|
||||
import { ZodJsonSchemaAdapter } from '@tmcp/adapter-zod';
|
||||
import { HttpTransport } from '@tmcp/transport-http';
|
||||
import { z } from 'zod';
|
||||
|
||||
const adapter = new ZodJsonSchemaAdapter();
|
||||
const server = new McpServer(/* ... same server config ... */);
|
||||
|
||||
// Add tools as above...
|
||||
|
||||
// Create HTTP transport
|
||||
const transport = new HttpTransport(server);
|
||||
|
||||
// Use with your preferred HTTP server (Bun example)
|
||||
Bun.serve({
|
||||
port: 3000,
|
||||
async fetch(req) {
|
||||
const response = await transport.respond(req);
|
||||
if (response === null) {
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
return response;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### McpServer
|
||||
|
||||
The main server class that handles MCP protocol communications.
|
||||
|
||||
#### Constructor
|
||||
|
||||
```javascript
|
||||
new McpServer(serverInfo, options);
|
||||
```
|
||||
|
||||
- `serverInfo`: Server metadata (name, version, description)
|
||||
- `options`: Configuration object with adapter and capabilities
|
||||
|
||||
#### Methods
|
||||
|
||||
##### `tool(definition, handler)`
|
||||
|
||||
Register a tool with optional schema validation.
|
||||
|
||||
```javascript
|
||||
server.tool(
|
||||
{
|
||||
name: 'tool-name',
|
||||
description: 'Tool description',
|
||||
schema: yourSchema, // optional
|
||||
},
|
||||
async (input) => {
|
||||
// Tool implementation
|
||||
return result;
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
##### `prompt(definition, handler)`
|
||||
|
||||
Register a prompt template with optional schema validation.
|
||||
|
||||
```javascript
|
||||
server.prompt(
|
||||
{
|
||||
name: 'prompt-name',
|
||||
description: 'Prompt description',
|
||||
schema: yourSchema, // optional
|
||||
complete: (arg, context) => ['completion1', 'completion2'] // optional
|
||||
},
|
||||
async (input) => {
|
||||
// Prompt implementation
|
||||
return { messages: [...] };
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
##### `resource(definition, handler)`
|
||||
|
||||
Register a static resource.
|
||||
|
||||
```javascript
|
||||
server.resource(
|
||||
{
|
||||
name: 'resource-name',
|
||||
description: 'Resource description',
|
||||
uri: 'file://path/to/resource'
|
||||
},
|
||||
async (uri, params) => {
|
||||
// Resource implementation
|
||||
return { contents: [...] };
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
##### `template(definition, handler)`
|
||||
|
||||
Register a URI template for dynamic resources.
|
||||
|
||||
```javascript
|
||||
server.template(
|
||||
{
|
||||
name: 'template-name',
|
||||
description: 'Template description',
|
||||
uri: 'file://path/{id}/resource',
|
||||
complete: (arg, context) => ['id1', 'id2'] // optional
|
||||
},
|
||||
async (uri, params) => {
|
||||
// Template implementation using params.id
|
||||
return { contents: [...] };
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
##### `receive(request)`
|
||||
|
||||
Process an incoming MCP request.
|
||||
|
||||
```javascript
|
||||
const response = server.receive(jsonRpcRequest);
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Multiple Schema Libraries
|
||||
|
||||
```javascript
|
||||
// Use different schemas for different tools
|
||||
import { z } from 'zod';
|
||||
import * as v from 'valibot';
|
||||
|
||||
server.tool(
|
||||
{
|
||||
name: 'zod-tool',
|
||||
schema: z.object({ name: z.string() }),
|
||||
},
|
||||
async ({ name }) => `Hello ${name}`,
|
||||
);
|
||||
|
||||
server.tool(
|
||||
{
|
||||
name: 'valibot-tool',
|
||||
schema: v.object({ age: v.number() }),
|
||||
},
|
||||
async ({ age }) => `Age: ${age}`,
|
||||
);
|
||||
```
|
||||
|
||||
### Resource Templates with Completion
|
||||
|
||||
```javascript
|
||||
server.template(
|
||||
{
|
||||
name: 'user-profile',
|
||||
description: 'Get user profile by ID',
|
||||
uri: 'users/{userId}/profile',
|
||||
complete: (arg, context) => {
|
||||
// Provide completions for userId parameter
|
||||
return ['user1', 'user2', 'user3'];
|
||||
},
|
||||
},
|
||||
async (uri, params) => {
|
||||
const user = await getUserById(params.userId);
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri,
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify(user),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### Complex Validation
|
||||
|
||||
```javascript
|
||||
const complexSchema = z.object({
|
||||
user: z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
age: z.number().min(18).max(120),
|
||||
}),
|
||||
preferences: z
|
||||
.object({
|
||||
theme: z.enum(['light', 'dark']),
|
||||
notifications: z.boolean(),
|
||||
})
|
||||
.optional(),
|
||||
tags: z.array(z.string()).default([]),
|
||||
});
|
||||
|
||||
server.tool(
|
||||
{
|
||||
name: 'create-user',
|
||||
description: 'Create a new user with preferences',
|
||||
schema: complexSchema,
|
||||
},
|
||||
async (input) => {
|
||||
// Input is fully typed and validated
|
||||
const { user, preferences, tags } = input;
|
||||
return await createUser(user, preferences, tags);
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please see our [contributing guidelines](../../CONTRIBUTING.md) for details.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Huge thanks to Sean O'Bannon that provided us with the `@tmcp` scope on npm.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Paolo Ricciuti
|
||||
12
documentation/docs/10-introduction/10-overview.md
Normal file
12
documentation/docs/10-introduction/10-overview.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
The following pages will help you set up and use the AI tools officially maintained by the Svelte team.
|
||||
|
||||
There are four tools, designed to help your agent write correct, robust Svelte code. They are designed to work together, but each can be used individually:
|
||||
|
||||
- [Instructions](instructions): small prompt always injected into your session to make your agent more aware of the available tools
|
||||
- [MCP Server](mcp): with tools, prompts and resources to give your agent more context, by pulling directly from the official Svelte documentation and using static analysis to correct common generative AI pitfalls
|
||||
- [Skills](skills): lazy-loaded descriptions that teach your agent Svelte best practices, and how to use the [`@sveltejs/mcp` cli](cli)
|
||||
- [Subagents](subagent): focused agents that can be invoked in parallel to execute atomic operations in a separate context window
|
||||
3
documentation/docs/10-introduction/index.md
Normal file
3
documentation/docs/10-introduction/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Introduction
|
||||
---
|
||||
23
documentation/docs/20-instructions/.generated/agents.md
Normal file
23
documentation/docs/20-instructions/.generated/agents.md
Normal file
@@ -0,0 +1,23 @@
|
||||
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||
|
||||
## Available Svelte MCP Tools:
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
|
||||
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns issues and suggestions.
|
||||
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
|
||||
|
||||
### 4. playground-link
|
||||
|
||||
Generates a Svelte Playground link with the provided code.
|
||||
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
|
||||
13
documentation/docs/20-instructions/10-instructions.md
Normal file
13
documentation/docs/20-instructions/10-instructions.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: AGENTS.md
|
||||
---
|
||||
|
||||
To get the most out of the [MCP server](mcp) and [skills](skills) we recommend including the following prompt in your [`AGENTS.md`](https://agents.md) (or [`CLAUDE.md`](https://docs.claude.com/en/docs/claude-code/memory#claude-md-imports) or [`GEMINI.md`](https://geminicli.com/docs/cli/gemini-md/), if using Claude Code or Gemini). This will tell your agent which tools are available and when it is appropriate to use them.
|
||||
|
||||
> [!NOTE] This is already setup for you when using `npx sv add mcp`
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
````markdown
|
||||
@include .generated/agents.md
|
||||
````
|
||||
<!-- prettier-ignore-end -->
|
||||
3
documentation/docs/20-instructions/index.md
Normal file
3
documentation/docs/20-instructions/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Instructions
|
||||
---
|
||||
213
documentation/docs/30-mcp/.generated/prompts.md
Normal file
213
documentation/docs/30-mcp/.generated/prompts.md
Normal file
@@ -0,0 +1,213 @@
|
||||
## svelte-task
|
||||
|
||||
This prompt should be used whenever you are asking the model to work on a Svelte-related task. It will instruct the LLM which documentation sections are available, which tools to invoke, when to invoke them, and how to interpret the results.
|
||||
|
||||
<details>
|
||||
<summary>Copy the prompt</summary>
|
||||
|
||||
<!-- 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
|
||||
- title: sv add, use_cases: project setup, adding features to existing projects, integrating tools, testing setup, styling setup, authentication, database setup, deployment adapters, path: cli/sv-add
|
||||
- title: sv check, use_cases: code quality, ci/cd pipelines, error checking, typescript projects, pre-commit hooks, finding unused css, accessibility auditing, production builds, path: cli/sv-check
|
||||
- title: sv migrate, use_cases: migration, upgrading svelte versions, upgrading sveltekit versions, modernizing codebase, svelte 3 to 4, svelte 4 to 5, sveltekit 1 to 2, adopting runes, refactoring deprecated apis, path: cli/sv-migrate
|
||||
- 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: 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
|
||||
- title: playwright, use_cases: browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows, path: cli/playwright
|
||||
- title: prettier, use_cases: code formatting, project setup, code style consistency, team collaboration, linting configuration, path: cli/prettier
|
||||
- title: storybook, use_cases: component development, design systems, ui library, isolated component testing, documentation, visual testing, component showcase, path: cli/storybook
|
||||
- title: sveltekit-adapter, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, static site generation, node server, vercel, cloudflare, netlify, path: cli/sveltekit-adapter
|
||||
- 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
|
||||
- title: Project structure, use_cases: project setup, understanding file structure, organizing code, starting new project, learning sveltekit basics, path: kit/project-structure
|
||||
- title: Web standards, use_cases: always, any sveltekit project, data fetching, forms, api routes, server-side rendering, deployment to various platforms, path: kit/web-standards
|
||||
- title: Routing, use_cases: routing, navigation, multi-page apps, project setup, file structure, api endpoints, data loading, layouts, error pages, always, path: kit/routing
|
||||
- title: Loading data, use_cases: data fetching, api calls, database queries, dynamic routes, page initialization, loading states, authentication checks, ssr data, form data, content rendering, path: kit/load
|
||||
- title: Form actions, use_cases: forms, user input, data submission, authentication, login systems, user registration, progressive enhancement, validation errors, path: kit/form-actions
|
||||
- title: Page options, use_cases: prerendering static sites, ssr configuration, spa setup, client-side rendering control, url trailing slash handling, adapter deployment config, build optimization, path: kit/page-options
|
||||
- title: State management, use_cases: sveltekit, server-side rendering, ssr, state management, authentication, data persistence, load functions, context api, navigation, component lifecycle, path: kit/state-management
|
||||
- title: Remote functions, use_cases: data fetching, server-side logic, database queries, type-safe client-server communication, forms, user input, mutations, authentication, crud operations, optimistic updates, path: kit/remote-functions
|
||||
- title: Building your app, use_cases: production builds, deployment preparation, build process optimization, adapter configuration, preview before deployment, path: kit/building-your-app
|
||||
- title: Adapters, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, path: kit/adapters
|
||||
- title: Zero-config deployments, use_cases: deployment, production builds, hosting setup, choosing deployment platform, ci/cd configuration, path: kit/adapter-auto
|
||||
- title: Node servers, use_cases: deployment, production builds, node.js hosting, custom server setup, environment configuration, reverse proxy setup, docker deployment, systemd services, path: kit/adapter-node
|
||||
- title: Static site generation, use_cases: static site generation, ssg, prerendering, deployment, github pages, spa mode, blogs, documentation sites, marketing sites, path: kit/adapter-static
|
||||
- title: Single-page apps, use_cases: spa mode, single-page apps, client-only rendering, static hosting, mobile app wrappers, no server-side logic, adapter-static setup, fallback pages, path: kit/single-page-apps
|
||||
- title: Cloudflare, use_cases: deployment, cloudflare workers, cloudflare pages, hosting setup, production builds, serverless deployment, edge computing, path: kit/adapter-cloudflare
|
||||
- title: Cloudflare Workers, use_cases: deploying to cloudflare workers, cloudflare workers sites deployment, legacy cloudflare adapter, wrangler configuration, cloudflare platform bindings, path: kit/adapter-cloudflare-workers
|
||||
- title: Netlify, use_cases: deployment, netlify hosting, production builds, serverless functions, edge functions, static site hosting, path: kit/adapter-netlify
|
||||
- title: Vercel, use_cases: deployment, vercel hosting, production builds, serverless functions, edge functions, isr, image optimization, environment variables, path: kit/adapter-vercel
|
||||
- title: Writing adapters, use_cases: custom deployment, building adapters, unsupported platforms, adapter development, custom hosting environments, path: kit/writing-adapters
|
||||
- title: Advanced routing, use_cases: advanced routing, dynamic routes, file viewers, nested paths, custom 404 pages, url validation, route parameters, multi-level navigation, path: kit/advanced-routing
|
||||
- title: Hooks, use_cases: authentication, logging, error tracking, request interception, api proxying, custom routing, internationalization, database initialization, middleware logic, session management, path: kit/hooks
|
||||
- title: Errors, use_cases: error handling, custom error pages, 404 pages, api error responses, production error logging, error tracking, type-safe errors, path: kit/errors
|
||||
- title: Link options, use_cases: routing, navigation, multi-page apps, performance optimization, link preloading, forms with get method, search functionality, focus management, scroll behavior, path: kit/link-options
|
||||
- title: Service workers, use_cases: offline support, pwa, caching strategies, performance optimization, precaching assets, network resilience, progressive web apps, path: kit/service-workers
|
||||
- title: Server-only modules, use_cases: api keys, environment variables, sensitive data protection, backend security, preventing data leaks, server-side code isolation, path: kit/server-only-modules
|
||||
- title: Snapshots, use_cases: forms, user input, preserving form data, multi-step forms, navigation state, preventing data loss, textarea content, input fields, comment systems, surveys, path: kit/snapshots
|
||||
- title: Shallow routing, use_cases: modals, dialogs, image galleries, overlays, history-driven ui, mobile-friendly navigation, photo viewers, lightboxes, drawer menus, path: kit/shallow-routing
|
||||
- title: Observability, use_cases: performance monitoring, debugging, observability, tracing requests, production diagnostics, analyzing slow requests, finding bottlenecks, monitoring server-side operations, path: kit/observability
|
||||
- title: Packaging, use_cases: building component libraries, publishing npm packages, creating reusable svelte components, library development, package distribution, path: kit/packaging
|
||||
- title: Auth, use_cases: authentication, login systems, user management, session handling, jwt tokens, protected routes, user credentials, authorization checks, path: kit/auth
|
||||
- title: Performance, use_cases: performance optimization, slow loading pages, production deployment, debugging performance issues, reducing bundle size, improving load times, path: kit/performance
|
||||
- title: Icons, use_cases: icons, ui components, styling, css frameworks, tailwind, unocss, performance optimization, dependency management, path: kit/icons
|
||||
- title: Images, use_cases: image optimization, responsive images, performance, hero images, product photos, galleries, cms integration, cdn setup, asset management, path: kit/images
|
||||
- title: Accessibility, use_cases: always, any sveltekit project, screen reader support, keyboard navigation, multi-page apps, client-side routing, internationalization, multilingual sites, path: kit/accessibility
|
||||
- title: SEO, use_cases: seo optimization, search engine ranking, content sites, blogs, marketing sites, public-facing apps, sitemaps, amp pages, meta tags, performance optimization, path: kit/seo
|
||||
- title: Frequently asked questions, use_cases: troubleshooting package imports, library compatibility issues, client-side code execution, external api integration, middleware setup, database configuration, view transitions, yarn configuration, path: kit/faq
|
||||
- title: Integrations, use_cases: project setup, css preprocessors, postcss, scss, sass, less, stylus, typescript setup, adding integrations, tailwind, testing, auth, linting, formatting, path: kit/integrations
|
||||
- title: Breakpoint Debugging, use_cases: debugging, breakpoints, development workflow, troubleshooting issues, vscode setup, ide configuration, inspecting code execution, path: kit/debugging
|
||||
- title: Migrating to SvelteKit v2, use_cases: migration, upgrading from sveltekit 1 to 2, breaking changes, version updates, path: kit/migrating-to-sveltekit-2
|
||||
- title: Migrating from Sapper, use_cases: migrating from sapper, upgrading legacy projects, sapper to sveltekit conversion, project modernization, path: kit/migrating
|
||||
- title: Additional resources, use_cases: troubleshooting, getting help, finding examples, learning sveltekit, project templates, common issues, community support, path: kit/additional-resources
|
||||
- title: Glossary, use_cases: rendering strategies, performance optimization, deployment configuration, seo requirements, static sites, spas, server-side rendering, prerendering, edge deployment, pwa development, path: kit/glossary
|
||||
- title: @sveltejs/kit, use_cases: forms, form actions, server-side validation, form submission, error handling, redirects, json responses, http errors, server utilities, path: kit/@sveltejs-kit
|
||||
- title: @sveltejs/kit/hooks, use_cases: middleware, request processing, authentication chains, logging, multiple hooks, request/response transformation, path: kit/@sveltejs-kit-hooks
|
||||
- title: @sveltejs/kit/node/polyfills, use_cases: node.js environments, custom servers, non-standard runtimes, ssr setup, web api compatibility, polyfill requirements, path: kit/@sveltejs-kit-node-polyfills
|
||||
- title: @sveltejs/kit/node, use_cases: node.js adapter, custom server setup, http integration, streaming files, node deployment, server-side rendering with node, path: kit/@sveltejs-kit-node
|
||||
- title: @sveltejs/kit/vite, use_cases: project setup, vite configuration, initial sveltekit setup, build tooling, path: kit/@sveltejs-kit-vite
|
||||
- title: $app/environment, use_cases: always, conditional logic, client-side code, server-side code, build-time logic, prerendering, development vs production, environment detection, path: kit/$app-environment
|
||||
- title: $app/forms, use_cases: forms, user input, data submission, progressive enhancement, custom form handling, form validation, path: kit/$app-forms
|
||||
- title: $app/navigation, use_cases: routing, navigation, multi-page apps, programmatic navigation, data reloading, preloading, shallow routing, navigation lifecycle, scroll handling, view transitions, path: kit/$app-navigation
|
||||
- title: $app/paths, use_cases: static assets, images, fonts, public files, base path configuration, subdirectory deployment, cdn setup, asset urls, links, navigation, path: kit/$app-paths
|
||||
- title: $app/server, use_cases: remote functions, server-side logic, data fetching, form handling, api endpoints, client-server communication, prerendering, file reading, batch queries, path: kit/$app-server
|
||||
- title: $app/state, use_cases: routing, navigation, multi-page apps, loading states, url parameters, form handling, error states, version updates, page metadata, shallow routing, path: kit/$app-state
|
||||
- title: $app/stores, use_cases: legacy projects, sveltekit pre-2.12, migration from stores to runes, maintaining older codebases, accessing page data, navigation state, app version updates, path: kit/$app-stores
|
||||
- title: $app/types, use_cases: routing, navigation, type safety, route parameters, dynamic routes, link generation, pathname validation, multi-page apps, path: kit/$app-types
|
||||
- title: $env/dynamic/private, use_cases: api keys, secrets management, server-side config, environment variables, backend logic, deployment-specific settings, private data handling, path: kit/$env-dynamic-private
|
||||
- title: $env/dynamic/public, use_cases: environment variables, client-side config, runtime configuration, public api keys, deployment-specific settings, multi-environment apps, path: kit/$env-dynamic-public
|
||||
- title: $env/static/private, use_cases: server-side api keys, backend secrets, database credentials, private configuration, build-time optimization, server endpoints, authentication tokens, path: kit/$env-static-private
|
||||
- title: $env/static/public, use_cases: environment variables, public config, client-side data, api endpoints, build-time configuration, public constants, path: kit/$env-static-public
|
||||
- title: $lib, use_cases: project setup, component organization, importing shared components, reusable ui elements, code structure, path: kit/$lib
|
||||
- title: $service-worker, use_cases: offline support, pwa, service workers, caching strategies, progressive web apps, offline-first apps, path: kit/$service-worker
|
||||
- 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: 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
|
||||
- title: .svelte.js and .svelte.ts files, use_cases: shared reactive state, reusable reactive logic, state management across components, global stores, custom reactive utilities, path: svelte/svelte-js-files
|
||||
- title: What are runes?, use_cases: always, any svelte 5 project, understanding core syntax, learning svelte 5, migration from svelte 4, path: svelte/what-are-runes
|
||||
- title: $state, use_cases: always, any svelte project, core reactivity, state management, counters, forms, todo apps, interactive ui, data updates, class-based components, path: svelte/$state
|
||||
- title: $derived, use_cases: always, any svelte project, computed values, reactive calculations, derived data, transforming state, dependent values, path: svelte/$derived
|
||||
- title: $effect, use_cases: canvas drawing, third-party library integration, dom manipulation, side effects, intervals, timers, network requests, analytics tracking, path: svelte/$effect
|
||||
- title: $props, use_cases: always, any svelte project, passing data to components, component communication, reusable components, component props, path: svelte/$props
|
||||
- title: $bindable, use_cases: forms, user input, two-way data binding, custom input components, parent-child communication, reusable form fields, path: svelte/$bindable
|
||||
- title: $inspect, use_cases: debugging, development, tracking state changes, reactive state monitoring, troubleshooting reactivity issues, path: svelte/$inspect
|
||||
- title: $host, use_cases: custom elements, web components, dispatching custom events, component library, framework-agnostic components, path: svelte/$host
|
||||
- title: Basic markup, use_cases: always, any svelte project, basic markup, html templating, component structure, attributes, events, props, text rendering, path: svelte/basic-markup
|
||||
- title: {#if ...}, use_cases: always, conditional rendering, showing/hiding content, dynamic ui, user permissions, loading states, error handling, form validation, path: svelte/if
|
||||
- title: {#each ...}, use_cases: always, lists, arrays, iteration, product listings, todos, tables, grids, dynamic content, shopping carts, user lists, comments, feeds, path: svelte/each
|
||||
- title: {#key ...}, use_cases: animations, transitions, component reinitialization, forcing component remount, value-based ui updates, resetting component state, path: svelte/key
|
||||
- title: {#await ...}, use_cases: async data fetching, api calls, loading states, promises, error handling, lazy loading components, dynamic imports, path: svelte/await
|
||||
- title: {#snippet ...}, use_cases: reusable markup, component composition, passing content to components, table rows, list items, conditional rendering, reducing duplication, path: svelte/snippet
|
||||
- title: {@render ...}, use_cases: reusable ui patterns, component composition, conditional rendering, fallback content, layout components, slot alternatives, template reuse, path: svelte/@render
|
||||
- title: {@html ...}, use_cases: rendering html strings, cms content, rich text editors, markdown to html, blog posts, wysiwyg output, sanitized html injection, dynamic html content, path: svelte/@html
|
||||
- title: {@attach ...}, use_cases: tooltips, popovers, dom manipulation, third-party libraries, canvas drawing, element lifecycle, interactive ui, custom directives, wrapper components, path: svelte/@attach
|
||||
- title: {@const ...}, use_cases: computed values in loops, derived calculations in blocks, local variables in each iterations, complex list rendering, path: svelte/@const
|
||||
- title: {@debug ...}, use_cases: debugging, development, troubleshooting, tracking state changes, monitoring variables, reactive data inspection, path: svelte/@debug
|
||||
- title: bind:, use_cases: forms, user input, two-way data binding, interactive ui, media players, file uploads, checkboxes, radio buttons, select dropdowns, contenteditable, dimension tracking, path: svelte/bind
|
||||
- title: use:, use_cases: custom directives, dom manipulation, third-party library integration, tooltips, click outside, gestures, focus management, element lifecycle hooks, path: svelte/use
|
||||
- title: transition:, use_cases: animations, interactive ui, modals, dropdowns, notifications, conditional content, show/hide elements, smooth state changes, path: svelte/transition
|
||||
- title: in: and out:, use_cases: animation, transitions, interactive ui, conditional rendering, independent enter/exit effects, modals, tooltips, notifications, path: svelte/in-and-out
|
||||
- title: animate:, use_cases: sortable lists, drag and drop, reorderable items, todo lists, kanban boards, playlist editors, priority queues, animated list reordering, path: svelte/animate
|
||||
- title: style:, use_cases: dynamic styling, conditional styles, theming, dark mode, responsive design, interactive ui, component styling, path: svelte/style
|
||||
- title: class, use_cases: always, conditional styling, dynamic classes, tailwind css, component styling, reusable components, responsive design, path: svelte/class
|
||||
- title: await, use_cases: async data fetching, loading states, server-side rendering, awaiting promises in components, async validation, concurrent data loading, path: svelte/await-expressions
|
||||
- title: Scoped styles, use_cases: always, styling components, scoped css, component-specific styles, preventing style conflicts, animations, keyframes, path: svelte/scoped-styles
|
||||
- title: Global styles, use_cases: global styles, third-party libraries, css resets, animations, styling body/html, overriding component styles, shared keyframes, base styles, path: svelte/global-styles
|
||||
- title: Custom properties, use_cases: theming, custom styling, reusable components, design systems, dynamic colors, component libraries, ui customization, path: svelte/custom-properties
|
||||
- title: Nested <style> elements, use_cases: component styling, scoped styles, dynamic styles, conditional styling, nested style tags, custom styling logic, path: svelte/nested-style-elements
|
||||
- title: <svelte:boundary>, use_cases: error handling, async data loading, loading states, error recovery, flaky components, error reporting, resilient ui, path: svelte/svelte-boundary
|
||||
- title: <svelte:window>, use_cases: keyboard shortcuts, scroll tracking, window resize handling, responsive layouts, online/offline detection, viewport dimensions, global event listeners, path: svelte/svelte-window
|
||||
- title: <svelte:document>, use_cases: document events, visibility tracking, fullscreen detection, pointer lock, focus management, document-level interactions, path: svelte/svelte-document
|
||||
- title: <svelte:body>, use_cases: mouse tracking, hover effects, cursor interactions, global body events, drag and drop, custom cursors, interactive backgrounds, body-level actions, path: svelte/svelte-body
|
||||
- title: <svelte:head>, use_cases: seo optimization, page titles, meta tags, social media sharing, dynamic head content, multi-page apps, blog posts, product pages, path: svelte/svelte-head
|
||||
- title: <svelte:element>, use_cases: dynamic content, cms integration, user-generated content, configurable ui, runtime element selection, flexible components, path: svelte/svelte-element
|
||||
- title: <svelte:options>, use_cases: migration, custom elements, web components, legacy mode compatibility, runes mode setup, svg components, mathml components, css injection control, path: svelte/svelte-options
|
||||
- title: Stores, use_cases: shared state, cross-component data, reactive values, async data streams, manual control over updates, rxjs integration, extracting logic, path: svelte/stores
|
||||
- title: Context, use_cases: shared state, avoiding prop drilling, component communication, theme providers, user context, authentication state, configuration sharing, deeply nested components, path: svelte/context
|
||||
- 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
|
||||
- title: Svelte 4 migration guide, use_cases: upgrading svelte 3 to 4, version migration, updating dependencies, breaking changes, legacy project maintenance, path: svelte/v4-migration-guide
|
||||
- title: Svelte 5 migration guide, use_cases: migrating from svelte 4 to 5, upgrading projects, learning svelte 5 syntax changes, runes migration, event handler updates, path: svelte/v5-migration-guide
|
||||
- title: Frequently asked questions, use_cases: getting started, learning svelte, beginner setup, project initialization, vs code setup, formatting, testing, routing, mobile apps, troubleshooting, community support, path: svelte/faq
|
||||
- title: svelte, use_cases: migration from svelte 4 to 5, upgrading legacy code, component lifecycle hooks, context api, mounting components, event dispatchers, typescript component types, path: svelte/svelte
|
||||
- title: svelte/action, use_cases: typescript types, actions, use directive, dom manipulation, element lifecycle, custom behaviors, third-party library integration, path: svelte/svelte-action
|
||||
- title: svelte/animate, use_cases: animated lists, sortable items, drag and drop, reordering elements, todo lists, kanban boards, playlist management, smooth position transitions, path: svelte/svelte-animate
|
||||
- title: svelte/attachments, use_cases: library development, component libraries, programmatic element manipulation, migrating from actions to attachments, spreading props onto elements, path: svelte/svelte-attachments
|
||||
- title: svelte/compiler, use_cases: build tools, custom compilers, ast manipulation, preprocessors, code transformation, migration scripts, syntax analysis, bundler plugins, dev tools, path: svelte/svelte-compiler
|
||||
- title: svelte/easing, use_cases: animations, transitions, custom easing, smooth motion, interactive ui, modals, dropdowns, carousels, page transitions, scroll effects, path: svelte/svelte-easing
|
||||
- title: svelte/events, use_cases: window events, document events, global event listeners, event delegation, programmatic event handling, cleanup functions, media queries, path: svelte/svelte-events
|
||||
- title: svelte/legacy, use_cases: migration from svelte 4 to svelte 5, upgrading legacy code, event modifiers, class components, imperative component instantiation, path: svelte/svelte-legacy
|
||||
- title: svelte/motion, use_cases: animation, smooth transitions, interactive ui, sliders, counters, physics-based motion, drag gestures, accessibility, reduced motion, path: svelte/svelte-motion
|
||||
- title: svelte/reactivity/window, use_cases: responsive design, viewport tracking, scroll effects, window resize handling, online/offline detection, zoom level tracking, path: svelte/svelte-reactivity-window
|
||||
- title: svelte/reactivity, use_cases: reactive data structures, state management with maps/sets, game boards, selection tracking, url manipulation, query params, real-time clocks, media queries, responsive design, path: svelte/svelte-reactivity
|
||||
- title: svelte/server, use_cases: server-side rendering, ssr, static site generation, seo optimization, initial page load, pre-rendering, node.js server, custom server setup, path: svelte/svelte-server
|
||||
- title: svelte/store, use_cases: state management, shared data, reactive stores, cross-component communication, global state, computed values, data synchronization, legacy svelte projects, path: svelte/svelte-store
|
||||
- title: svelte/transition, use_cases: animations, transitions, interactive ui, modals, dropdowns, tooltips, notifications, svg animations, list animations, page transitions, path: svelte/svelte-transition
|
||||
- title: Compiler errors, use_cases: animation, transitions, keyed each blocks, list animations, path: svelte/compiler-errors
|
||||
- title: Compiler warnings, use_cases: accessibility, a11y compliance, wcag standards, screen readers, keyboard navigation, aria attributes, semantic html, interactive elements, path: svelte/compiler-warnings
|
||||
- title: Runtime errors, use_cases: debugging errors, error handling, troubleshooting runtime issues, migration to svelte 5, component binding, effects and reactivity, path: svelte/runtime-errors
|
||||
- title: Runtime warnings, use_cases: debugging state proxies, console logging reactive values, inspecting state changes, development troubleshooting, path: svelte/runtime-warnings
|
||||
- title: Overview, use_cases: migrating from svelte 3/4 to svelte 5, maintaining legacy components, understanding deprecated features, gradual upgrade process, path: svelte/legacy-overview
|
||||
- title: Reactive let/var declarations, use_cases: migration, legacy svelte projects, upgrading from svelte 4, understanding old reactivity, maintaining existing code, learning runes differences, path: svelte/legacy-let
|
||||
- title: Reactive $: statements, use_cases: legacy mode, migration from svelte 4, reactive statements, computed values, derived state, side effects, path: svelte/legacy-reactive-assignments
|
||||
- title: export let, use_cases: legacy mode, migration from svelte 4, maintaining older projects, component props without runes, exporting component methods, renaming reserved word props, path: svelte/legacy-export-let
|
||||
- title: $$props and $$restProps, use_cases: legacy mode migration, component wrappers, prop forwarding, button components, reusable ui components, spreading props to child elements, path: svelte/legacy-$$props-and-$$restProps
|
||||
- title: on:, use_cases: legacy mode, event handling, button clicks, forms, user interactions, component communication, event forwarding, event modifiers, path: svelte/legacy-on
|
||||
- title: <slot>, use_cases: legacy mode, migrating from svelte 4, component composition, reusable components, passing content to components, modals, layouts, wrappers, path: svelte/legacy-slots
|
||||
- title: $$slots, use_cases: legacy mode, conditional slot rendering, optional content sections, checking if slots provided, migrating from legacy to runes, path: svelte/legacy-$$slots
|
||||
- title: <svelte:fragment>, use_cases: named slots, component composition, layout systems, avoiding wrapper divs, legacy svelte projects, slot content organization, path: svelte/legacy-svelte-fragment
|
||||
- title: <svelte:component>, use_cases: dynamic components, component switching, conditional rendering, legacy mode migration, tabbed interfaces, multi-step forms, path: svelte/legacy-svelte-component
|
||||
- title: <svelte:self>, use_cases: recursive components, tree structures, nested menus, file explorers, comment threads, hierarchical data, path: svelte/legacy-svelte-self
|
||||
- title: Imperative component API, use_cases: migration from svelte 3/4 to 5, legacy component api, maintaining old projects, understanding deprecated patterns, path: svelte/legacy-component-api
|
||||
|
||||
</available-docs>
|
||||
|
||||
These are the available documentation sections that `list-sections` will return, you do not need to call it again.
|
||||
|
||||
Every time you write a Svelte component or a Svelte module you MUST invoke the `svelte-autofixer` tool providing the code. The tool will return a list of issues or suggestions. If there are any issues or suggestions you MUST fix them and call the tool again with the updated code. You MUST keep doing this until the tool returns no issues or suggestions. Only then you can return the code to the user.
|
||||
|
||||
This is the task you will work on:
|
||||
|
||||
<task>
|
||||
[YOUR TASK HERE]
|
||||
</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>
|
||||
16
documentation/docs/30-mcp/10-mcp.md
Normal file
16
documentation/docs/30-mcp/10-mcp.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
The Svelte MCP ([Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro)) server can help your agent write better Svelte code. It works by providing relevant documentation, and statically analysing generated code so that it can suggest fixes and best practices.
|
||||
|
||||
## Setup
|
||||
|
||||
The setup varies based on the version of the MCP you prefer — remote or local — and your chosen MCP client (e.g. Claude Code, Codex CLI or GitHub Copilot):
|
||||
|
||||
- [local setup](local-setup) using `@sveltejs/mcp`
|
||||
- [remote setup](remote-setup) using `https://mcp.svelte.dev/mcp`
|
||||
|
||||
## Usage
|
||||
|
||||
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.
|
||||
168
documentation/docs/30-mcp/20-local-setup.md
Normal file
168
documentation/docs/30-mcp/20-local-setup.md
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Local setup
|
||||
---
|
||||
|
||||
The local (or stdio) version of the MCP server is available via the [`@sveltejs/mcp`](https://www.npmjs.com/package/@sveltejs/mcp) npm package. You can either install it globally and then reference it in your configuration or run it with `npx`:
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp
|
||||
```
|
||||
|
||||
Here's how to set it up in some common MCP clients:
|
||||
|
||||
## Claude Code
|
||||
|
||||
To include the local MCP version in Claude Code, simply run the following command:
|
||||
|
||||
```bash
|
||||
claude mcp add -t stdio -s [scope] svelte -- npx -y @sveltejs/mcp
|
||||
```
|
||||
|
||||
The `[scope]` must be `user`, `project` or `local`.
|
||||
|
||||
## Claude Desktop
|
||||
|
||||
In the Settings > Developer section, click on Edit Config. It will open the folder with a `claude_desktop_config.json` file in it. Edit the file to include the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@sveltejs/mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Codex CLI
|
||||
|
||||
You can automatically configure the MCP server using the [Codex plugin](codex-plugin) (recommended). If you prefer to configure the MCP server manually, add the following to your `config.toml` (which defaults to `~/.codex/config.toml`, but refer to [the configuration documentation](https://github.com/openai/codex/blob/main/docs/config.md) for more advanced setups):
|
||||
|
||||
```toml
|
||||
[mcp_servers.svelte]
|
||||
command = "npx"
|
||||
args = ["-y", "@sveltejs/mcp"]
|
||||
```
|
||||
|
||||
## Copilot CLI
|
||||
|
||||
You can automatically configure the MCP server using the [Copilot plugin](copilot-plugin) (recommended). If you prefer to configure the MCP server manually, use the Copilot CLI to interactively add the MCP server:
|
||||
|
||||
```bash
|
||||
/mcp add
|
||||
```
|
||||
|
||||
Alternatively, create or edit `~/.copilot/mcp-config.json` and add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@sveltejs/mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Antigravity CLI
|
||||
|
||||
To use the local MCP version in Antigravity CLI, create or edit `~/.gemini/config/mcp_config.json` and add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@sveltejs/mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OpenCode
|
||||
|
||||
You can automatically configure the MCP server using the [OpenCode plugin](opencode-plugin) (recommended). If you prefer to configure the MCP server manually, run:
|
||||
|
||||
```bash
|
||||
opencode mcp add
|
||||
```
|
||||
|
||||
and follow the instructions, selecting 'Local' under the 'Select MCP server type' prompt:
|
||||
|
||||
```bash
|
||||
opencode mcp add
|
||||
|
||||
┌ Add MCP server
|
||||
│
|
||||
◇ Enter MCP server name
|
||||
│ svelte
|
||||
│
|
||||
◇ Select MCP server type
|
||||
│ Local
|
||||
│
|
||||
◆ Enter command to run
|
||||
│ npx -y @sveltejs/mcp
|
||||
```
|
||||
|
||||
## VS Code
|
||||
|
||||
- Open the command palette
|
||||
- Select "MCP: Add Server..."
|
||||
- Select "Command (stdio)"
|
||||
- Insert `npx -y @sveltejs/mcp` in the input and press `Enter`
|
||||
- When prompted for a name, insert `svelte`
|
||||
- Select if you want to add it as a `Global` or `Workspace` MCP server
|
||||
|
||||
## Cursor
|
||||
|
||||
You can automatically configure the MCP server using the [Cursor plugin](cursor-plugin) (recommended). If you prefer to configure the MCP server manually you can:
|
||||
|
||||
- Open the command palette
|
||||
- Select "View: Open MCP Settings"
|
||||
- Click on "Add custom MCP"
|
||||
|
||||
It will open a file with your MCP servers where you can add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@sveltejs/mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Zed
|
||||
|
||||
Install the [Svelte MCP Server extension](https://zed.dev/extensions/svelte-mcp).
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Configure Manually</summary>
|
||||
|
||||
- Open the command palette
|
||||
- Search and select "agent:open settings"
|
||||
- In settings panel look for `Model Context Protocol (MCP) Servers`
|
||||
- Click on "Add Server"
|
||||
- Select: "Add Custom Server"
|
||||
|
||||
It will open a popup with MCP server config where you can add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"svelte": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@sveltejs/mcp"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Other clients
|
||||
|
||||
If we didn't include the MCP client you are using, refer to their documentation for `stdio` servers and use `npx` as the command and `-y @sveltejs/mcp` as the arguments.
|
||||
150
documentation/docs/30-mcp/30-remote-setup.md
Normal file
150
documentation/docs/30-mcp/30-remote-setup.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
title: Remote setup
|
||||
---
|
||||
|
||||
The remote version of the MCP server is available at `https://mcp.svelte.dev/mcp`.
|
||||
|
||||
Here's how to set it up in some common MCP clients:
|
||||
|
||||
## Claude Code
|
||||
|
||||
To include the remote MCP version in Claude Code, simply run the following command:
|
||||
|
||||
```bash
|
||||
claude mcp add -t http -s [scope] svelte https://mcp.svelte.dev/mcp
|
||||
```
|
||||
|
||||
You can choose your preferred `scope` (it must be `user`, `project` or `local`) and `name`.
|
||||
|
||||
If you prefer you can also install the `svelte` plugin in [the Svelte Claude Code Marketplace](claude-plugin) that will give you both the remote server and useful [skills](skills).
|
||||
|
||||
## Claude Desktop
|
||||
|
||||
- Open Settings > Connectors
|
||||
- Click on Add Custom Connector
|
||||
- When prompted for a name, enter `svelte`
|
||||
- Under the Remote MCP server URL input, use `https://mcp.svelte.dev/mcp`
|
||||
- Click Add
|
||||
|
||||
## Codex CLI
|
||||
|
||||
You can automatically configure the MCP server using the [Codex plugin](codex-plugin) (recommended). If you prefer to configure the MCP server manually, add the following to your `config.toml` (which defaults to `~/.codex/config.toml`, but refer to [the configuration documentation](https://github.com/openai/codex/blob/main/docs/config.md) for more advanced setups):
|
||||
|
||||
```toml
|
||||
experimental_use_rmcp_client = true
|
||||
[mcp_servers.svelte]
|
||||
url = "https://mcp.svelte.dev/mcp"
|
||||
```
|
||||
|
||||
## Copilot CLI
|
||||
|
||||
You can automatically configure the MCP server using the [Copilot plugin](copilot-plugin) (recommended). If you prefer to configure the MCP server manually, use the Copilot CLI to interactively add the MCP server:
|
||||
|
||||
```bash
|
||||
/mcp add
|
||||
```
|
||||
|
||||
Alternatively, create or edit `~/.copilot/mcp-config.json` and add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Antigravity CLI
|
||||
|
||||
To use the remote MCP version in Antigravity CLI, create or edit `~/.gemini/config/mcp_config.json` and add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## OpenCode
|
||||
|
||||
You can automatically configure the MCP server using the [OpenCode plugin](opencode-plugin) (recommended). If you prefer to configure the MCP server manually, run:
|
||||
|
||||
```bash
|
||||
opencode mcp add
|
||||
```
|
||||
|
||||
and follow the instructions, selecting 'Remote' under the 'Select MCP server type' prompt:
|
||||
|
||||
```bash
|
||||
opencode mcp add
|
||||
|
||||
┌ Add MCP server
|
||||
│
|
||||
◇ Enter MCP server name
|
||||
│ svelte
|
||||
│
|
||||
◇ Select MCP server type
|
||||
│ Remote
|
||||
│
|
||||
◇ Enter MCP server URL
|
||||
│ https://mcp.svelte.dev/mcp
|
||||
```
|
||||
|
||||
## VS Code
|
||||
|
||||
- Open the command palette
|
||||
- Select "MCP: Add Server..."
|
||||
- Select "HTTP (HTTP or Server-Sent-Events)"
|
||||
- Insert `https://mcp.svelte.dev/mcp` in the input and press `Enter`
|
||||
- Insert your preferred name
|
||||
- Select if you want to add it as a `Global` or `Workspace` MCP server
|
||||
|
||||
## Cursor
|
||||
|
||||
You can automatically configure the MCP server using the [Cursor plugin](cursor-plugin) (recommended). If you prefer to configure the MCP server manually you can:
|
||||
|
||||
- Open the command palette
|
||||
- Select "View: Open MCP Settings"
|
||||
- Click on "Add custom MCP"
|
||||
|
||||
It will open a file with your MCP servers where you can add the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GitHub Coding Agent
|
||||
|
||||
- Open your repository in GitHub
|
||||
- Go to Settings
|
||||
- Open Copilot > Coding agent
|
||||
- Edit the MCP configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"svelte": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.svelte.dev/mcp",
|
||||
"tools": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Click _Save MCP configuration_
|
||||
|
||||
## Other clients
|
||||
|
||||
If we didn't include the MCP client you are using, refer to their documentation for `remote` servers and use `https://mcp.svelte.dev/mcp` as the URL.
|
||||
21
documentation/docs/30-mcp/40-tools.md
Normal file
21
documentation/docs/30-mcp/40-tools.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Tools
|
||||
---
|
||||
|
||||
The following tools are provided by the MCP server to the model you are using, which can decide to call one or more of them during a session:
|
||||
|
||||
## list-sections
|
||||
|
||||
Provides a list of all the available documentation sections.
|
||||
|
||||
## get-documentation
|
||||
|
||||
Allows the model to get the full (and up-to-date) documentation for the requested sections directly from [svelte.dev/docs](/docs).
|
||||
|
||||
## svelte-autofixer
|
||||
|
||||
Uses static analysis to provide suggestions for code that your LLM generates. It can be invoked in an agentic loop by your model until all issues and suggestions are resolved.
|
||||
|
||||
## playground-link
|
||||
|
||||
Generates an ephemeral playground link with the generated code. It's useful when the generated code is not written to a file in your project and you want to quickly test the generated solution. The code is not stored anywhere except the URL itself (which will often, as a consequence, be quite large).
|
||||
9
documentation/docs/30-mcp/50-resources.md
Normal file
9
documentation/docs/30-mcp/50-resources.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Resources
|
||||
---
|
||||
|
||||
This is the list of available resources provided by the MCP server. Resources are included by the user (not by the LLM) and are useful if you want to include specific knowledge in your session. For example, if you know that the component will need to use transitions you can include the transition documentation directly without asking the LLM to do it for you.
|
||||
|
||||
## doc-section
|
||||
|
||||
This dynamic resource allows you to add every section of the Svelte documentation as a resource. The URI looks like this `svelte://slug-of-the-docs.md` and the returned resource will contain the `llms.txt` version of the specific page you selected.
|
||||
7
documentation/docs/30-mcp/60-prompts.md
Normal file
7
documentation/docs/30-mcp/60-prompts.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Prompts
|
||||
---
|
||||
|
||||
This is the list of available prompts provided by the MCP server. Prompts are selected by the user and are sent as a user message. They can be useful to write repetitive instructions for the LLM on how to properly use the MCP server.
|
||||
|
||||
@include .generated/prompts.md
|
||||
78
documentation/docs/30-mcp/70-cli.md
Normal file
78
documentation/docs/30-mcp/70-cli.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: CLI
|
||||
---
|
||||
|
||||
The `@sveltejs/mcp` npm package normally launches the local `stdio` MCP server:
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp
|
||||
```
|
||||
|
||||
If you invoke it with a subcommand, it behaves like a regular CLI and prints the result directly in your terminal instead. This is useful for agents, scripts and quick manual checks.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp <command> [options]
|
||||
```
|
||||
|
||||
Available commands:
|
||||
|
||||
- `list-sections`
|
||||
- `get-documentation <sections>`
|
||||
- `svelte-autofixer <code_or_path>`
|
||||
|
||||
You can learn more about the commands with
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp --help
|
||||
npx -y @sveltejs/mcp <command> --help
|
||||
npx -y @sveltejs/mcp --version
|
||||
```
|
||||
|
||||
## `list-sections`
|
||||
|
||||
Lists all available Svelte and SvelteKit documentation sections.
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp list-sections
|
||||
```
|
||||
|
||||
The output is a structured text list of sections, including each section's title, `use_cases`, and documentation path. This is the same catalog the MCP tool uses before calling `get-documentation`.
|
||||
|
||||
## `get-documentation`
|
||||
|
||||
Fetches the full documentation for one or more sections.
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp get-documentation 'svelte/$state'
|
||||
# or
|
||||
npx -y @sveltejs/mcp get-documentation 'svelte/$state,svelte/await-expressions'
|
||||
```
|
||||
|
||||
Each section can be matched by title or by documentation path. If a section cannot be found, the CLI returns an error plus similar matches when available.
|
||||
|
||||
## `svelte-autofixer`
|
||||
|
||||
Runs the Svelte autofixer against either inline code or a file path:
|
||||
|
||||
```bash
|
||||
npx -y @sveltejs/mcp svelte-autofixer 'src/routes/+page.svelte'
|
||||
```
|
||||
|
||||
If the argument is an existing path, the CLI reads the file automatically. Otherwise it treats the argument as raw Svelte code.
|
||||
|
||||
Because most shells expand `$`, inline code should be quoted or escaped correctly. In practice, passing a file path is usually easier than passing source directly.
|
||||
|
||||
Available options:
|
||||
|
||||
- `--svelte-version <4|5>` - choose which Svelte version to validate against (defaults to `5`)
|
||||
- `--async` - enable async Svelte analysis for Svelte 5 projects
|
||||
|
||||
The command prints an object with:
|
||||
|
||||
- `issues`
|
||||
- `suggestions`
|
||||
- `require_another_tool_call_after_fixing`
|
||||
|
||||
This makes it easy to use in an agentic loop: run the autofixer, apply fixes, then run it again until it reports no remaining issues or suggestions.
|
||||
3
documentation/docs/30-mcp/index.md
Normal file
3
documentation/docs/30-mcp/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: MCP server
|
||||
---
|
||||
261
documentation/docs/40-skills/.generated/skills.md
Normal file
261
documentation/docs/40-skills/.generated/skills.md
Normal file
@@ -0,0 +1,261 @@
|
||||
## `svelte-code-writer`
|
||||
|
||||
CLI tools for Svelte 5 documentation lookup and code analysis. MUST be used whenever creating, editing or analyzing any Svelte component (.svelte) or Svelte module (.svelte.ts/.svelte.js). If possible, this skill should be executed within the svelte-file-editor agent for optimal results.
|
||||
|
||||
<a href="https://github.com/sveltejs/ai-tools/releases?q=svelte-code-writer" target="_blank" rel="noopener noreferrer">Open Releases page</a>
|
||||
|
||||
<details>
|
||||
<summary>View skill content</summary>
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
````markdown
|
||||
## CLI tools
|
||||
|
||||
You have access to `@sveltejs/mcp` CLI for Svelte-specific assistance. Use these commands via `npx`:
|
||||
|
||||
### List documentation sections
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp list-sections
|
||||
```
|
||||
|
||||
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths.
|
||||
|
||||
### Get documentation
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp get-documentation "<section1>,<section2>,..."
|
||||
```
|
||||
|
||||
Retrieves full documentation for specified sections. Use after `list-sections` to fetch relevant docs.
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp get-documentation "$state,$derived,$effect"
|
||||
```
|
||||
|
||||
### Svelte autofixer
|
||||
|
||||
```bash
|
||||
npx @sveltejs/mcp svelte-autofixer "<code_or_path>" [options]
|
||||
```
|
||||
|
||||
Analyzes Svelte code and suggests fixes for common issues.
|
||||
|
||||
**Options:**
|
||||
|
||||
- `--async` - Enable async Svelte mode (default: false)
|
||||
- `--svelte-version` - Target version: 4 or 5 (default: 5)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Analyze inline code (escape $ as \$)
|
||||
npx @sveltejs/mcp svelte-autofixer '<script>let count = \$state(0);</script>'
|
||||
|
||||
# Analyze a file
|
||||
npx @sveltejs/mcp svelte-autofixer ./src/lib/Component.svelte
|
||||
|
||||
# Target Svelte 4
|
||||
npx @sveltejs/mcp svelte-autofixer ./Component.svelte --svelte-version 4
|
||||
```
|
||||
|
||||
**Important:** When passing code with runes (`$state`, `$derived`, etc.) via the terminal, escape the `$` character as `\$` to prevent shell variable substitution.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Uncertain about syntax?** Run `list-sections` then `get-documentation` for relevant topics
|
||||
2. **Reviewing/debugging?** Run `svelte-autofixer` on the code to detect issues
|
||||
3. **Always validate** - Run `svelte-autofixer` before finalizing any Svelte component
|
||||
````
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
</details>
|
||||
|
||||
## `svelte-core-bestpractices`
|
||||
|
||||
Guidance on writing fast, robust, modern Svelte code. Load this skill whenever in a Svelte project and asked to write/edit or analyze a Svelte component or module. Covers reactivity, event handling, styling, integration with libraries and more.
|
||||
|
||||
<a href="https://github.com/sveltejs/ai-tools/releases?q=svelte-core-bestpractices" target="_blank" rel="noopener noreferrer">Open Releases page</a>
|
||||
|
||||
<details>
|
||||
<summary>View skill content</summary>
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
````markdown
|
||||
## `$state`
|
||||
|
||||
Only use the `$state` rune for variables that should be _reactive_ — in other words, variables that cause an `$effect`, `$derived` or template expression to update. Everything else can be a normal variable.
|
||||
|
||||
Objects and arrays (`$state({...})` or `$state([...])`) are made deeply reactive, meaning mutation will trigger updates. This has a trade-off: in exchange for fine-grained reactivity, the objects must be proxied, which has performance overhead. In cases where you're dealing with large objects that are only ever reassigned (rather than mutated), use `$state.raw` instead. This is often the case with API responses, for example.
|
||||
|
||||
## `$derived`
|
||||
|
||||
To compute something from state, use `$derived` rather than `$effect`:
|
||||
|
||||
```js
|
||||
// do this
|
||||
let square = $derived(num * num);
|
||||
|
||||
// don't do this
|
||||
let square;
|
||||
|
||||
$effect(() => {
|
||||
square = num * num;
|
||||
});
|
||||
```
|
||||
|
||||
> [!NOTE] `$derived` is given an expression, _not_ a function. If you need to use a function (because the expression is complex, for example) use `$derived.by`.
|
||||
|
||||
Deriveds are writable — you can assign to them, just like `$state`, except that they will re-evaluate when their expression changes.
|
||||
|
||||
If the derived expression is an object or array, it will be returned as-is — it is _not_ made deeply reactive. You can, however, use `$state` inside `$derived.by` in the rare cases that you need this.
|
||||
|
||||
## `$effect`
|
||||
|
||||
Effects are an escape hatch and should mostly be avoided. In particular, avoid updating state inside effects.
|
||||
|
||||
- If you need to sync state to an external library such as D3, it is often neater to use [`{@attach ...}`](references/attach.md)
|
||||
- If you need to run some code in response to user interaction, put the code directly in an event handler or use a [function binding](references/bind.md) as appropriate
|
||||
- If you need to log values for debugging purposes, use [`$inspect`](references/inspect.md)
|
||||
- If you need to observe something external to Svelte, use [`createSubscriber`](references/svelte-reactivity.md)
|
||||
|
||||
Never wrap the contents of an effect in `if (browser) {...}` or similar — effects do not run on the server.
|
||||
|
||||
## `$props`
|
||||
|
||||
Treat props as though they will change. For example, values that depend on props should usually use `$derived`:
|
||||
|
||||
```js
|
||||
// @errors: 2451
|
||||
let { type } = $props();
|
||||
|
||||
// do this
|
||||
let color = $derived(type === 'danger' ? 'red' : 'green');
|
||||
|
||||
// don't do this — `color` will not update if `type` changes
|
||||
let color = type === 'danger' ? 'red' : 'green';
|
||||
```
|
||||
|
||||
## `$inspect.trace`
|
||||
|
||||
`$inspect.trace` is a debugging tool for reactivity. If something is not updating properly or running more than it should you can add `$inspect.trace(label)` as the first line of an `$effect` or `$derived.by` (or any function they call) to trace their dependencies and discover which one triggered an update.
|
||||
|
||||
## Events
|
||||
|
||||
Any element attribute starting with `on` is treated as an event listener:
|
||||
|
||||
```svelte
|
||||
<button onclick={() => {...}}>click me</button>
|
||||
|
||||
<!-- attribute shorthand also works -->
|
||||
<button {onclick}>...</button>
|
||||
|
||||
<!-- so do spread attributes -->
|
||||
<button {...props}>...</button>
|
||||
```
|
||||
|
||||
If you need to attach listeners to `window` or `document` you can use `<svelte:window>` and `<svelte:document>`:
|
||||
|
||||
```svelte
|
||||
<svelte:window onkeydown={...} />
|
||||
<svelte:document onvisibilitychange={...} />
|
||||
```
|
||||
|
||||
Avoid using `onMount` or `$effect` for this.
|
||||
|
||||
## Snippets
|
||||
|
||||
[Snippets](references/snippet.md) are a way to define reusable chunks of markup that can be instantiated with the [`{@render ...}`](references/render.md) tag, or passed to components as props. They must be declared within the template.
|
||||
|
||||
```svelte
|
||||
{#snippet greeting(name)}
|
||||
<p>hello {name}!</p>
|
||||
{/snippet}
|
||||
|
||||
{@render greeting('world')}
|
||||
```
|
||||
|
||||
> [!NOTE] Snippets declared at the top level of a component (i.e. not inside elements or blocks) can be referenced inside `<script>`. A snippet that doesn't reference component state is also available in a `<script module>`, in which case it can be exported for use by other components.
|
||||
|
||||
## Each blocks
|
||||
|
||||
Prefer to use [keyed each blocks](references/each.md) — this improves performance by allowing Svelte to surgically insert or remove items rather than updating the DOM belonging to existing items.
|
||||
|
||||
> [!NOTE] The key _must_ uniquely identify the object. Do not use the index as a key.
|
||||
|
||||
Avoid destructuring if you need to mutate the item (with something like `bind:value={item.count}`, for example).
|
||||
|
||||
## Using JavaScript variables in CSS
|
||||
|
||||
If you have a JS variable that you want to use inside CSS you can set a CSS custom property with the `style:` directive.
|
||||
|
||||
```svelte
|
||||
<div style:--columns={columns}>...</div>
|
||||
```
|
||||
|
||||
You can then reference `var(--columns)` inside the component's `<style>`.
|
||||
|
||||
## Styling child components
|
||||
|
||||
The CSS in a component's `<style>` is scoped to that component. If a parent component needs to control the child's styles, the preferred way is to use CSS custom properties:
|
||||
|
||||
```svelte
|
||||
<!-- Parent.svelte -->
|
||||
<Child --color="red" />
|
||||
|
||||
<!-- Child.svelte -->
|
||||
<h1>Hello</h1>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
color: var(--color);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
If this is impossible (for example, the child component comes from a library) you can use `:global` to override styles:
|
||||
|
||||
```svelte
|
||||
<div>
|
||||
<Child />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div :global {
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
Consider using context instead of declaring state in a shared module. This will scope the state to the part of the app that needs it, and eliminate the possibility of it leaking between users when server-side rendering.
|
||||
|
||||
Use `createContext` rather than `setContext` and `getContext`, as it provides type safety.
|
||||
|
||||
## Async Svelte
|
||||
|
||||
If using version 5.36 or higher, you can use [await expressions](references/await-expressions.md) and [hydratable](references/hydratable.md) to use promises directly inside components. Note that these require the `experimental.async` option to be enabled in `svelte.config.js` as they are not yet considered fully stable.
|
||||
|
||||
## Avoid legacy features
|
||||
|
||||
Always use runes mode for new code, and avoid features that have more modern replacements:
|
||||
|
||||
- use `$state` instead of implicit reactivity (e.g. `let count = 0; count += 1`)
|
||||
- use `$derived` and `$effect` instead of `$:` assignments and statements (but only use effects when there is no better solution)
|
||||
- use `$props` instead of `export let`, `$$props` and `$$restProps`
|
||||
- use `onclick={...}` instead of `on:click={...}`
|
||||
- use `{#snippet ...}` and `{@render ...}` instead of `<slot>` and `$$slots` and `<svelte:fragment>`
|
||||
- use `<DynamicComponent>` instead of `<svelte:component this={DynamicComponent}>`
|
||||
- use `import Self from './ThisComponent.svelte'` and `<Self>` instead of `<svelte:self>`
|
||||
- use classes with `$state` fields to share reactivity between components, instead of using stores
|
||||
- use `{@attach ...}` instead of `use:action`
|
||||
- use clsx-style arrays and objects in `class` attributes, instead of the `class:` directive
|
||||
````
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
</details>
|
||||
11
documentation/docs/40-skills/10-skills.md
Normal file
11
documentation/docs/40-skills/10-skills.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
This is the list of available skills provided by Svelte. Skills are sets of instructions that AI agents can load on-demand to help with specific tasks.
|
||||
|
||||
Skills are available in the Claude Code plugin, the Codex CLI plugin, the GitHub Copilot CLI plugin, and the OpenCode plugin (`@sveltejs/opencode`). They can also be manually installed in your `.claude/skills`, `.copilot/skills`, or `.opencode/skills` folder.
|
||||
|
||||
You can download the latest skills from the [releases page](https://github.com/sveltejs/ai-tools/releases) of the repo, or find them in the [`tools/skills`](https://github.com/sveltejs/ai-tools/tree/main/tools/skills) folder.
|
||||
|
||||
@include .generated/skills.md
|
||||
3
documentation/docs/40-skills/index.md
Normal file
3
documentation/docs/40-skills/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Skills
|
||||
---
|
||||
67
documentation/docs/50-subagents/.generated/subagent.md
Normal file
67
documentation/docs/50-subagents/.generated/subagent.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: svelte-file-editor
|
||||
description: Specialized Svelte 5 code editor. MUST BE USED PROACTIVELY when creating, editing, or reviewing any .svelte file or .svelte.ts/.svelte.js module and MUST use the tools from the MCP server or the `svelte-file-editor` skill if they are available. Fetches relevant documentation and validates code using the Svelte MCP server tools.
|
||||
---
|
||||
|
||||
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 validate 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-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.
|
||||
|
||||
## Available MCP tools
|
||||
|
||||
### 1. list-sections
|
||||
|
||||
Lists all available Svelte 5 and SvelteKit documentation sections with titles and paths. Use this first to discover what documentation is available.
|
||||
|
||||
### 2. get-documentation
|
||||
|
||||
Retrieves full documentation for specified sections. Accepts a single section name or an array of section names. Use after `list-sections` to fetch relevant docs for the task at hand.
|
||||
|
||||
**Example sections:** `$state`, `$derived`, `$effect`, `$props`, `$bindable`, `snippets`, `routing`, `load functions`
|
||||
|
||||
### 3. svelte-autofixer
|
||||
|
||||
Analyzes Svelte code and returns suggestions to fix issues. Pass the component code directly to this tool. It will detect common mistakes like:
|
||||
|
||||
- Using `$effect` instead of `$derived` for computations
|
||||
- Missing cleanup in effects
|
||||
- Svelte 4 syntax (`on:click`, `export let`, `<slot>`)
|
||||
- Missing keys in `{#each}` blocks
|
||||
- And more
|
||||
|
||||
## Workflow
|
||||
|
||||
When invoked to work on a Svelte file:
|
||||
|
||||
### 1. Gather context (if needed)
|
||||
|
||||
If you're uncertain about Svelte 5 syntax or patterns, use the MCP tools:
|
||||
|
||||
1. Call `list-sections` to see available documentation
|
||||
2. Call `get-documentation` with relevant section names
|
||||
|
||||
### 2. Read the target file
|
||||
|
||||
Read the file to understand the current implementation.
|
||||
|
||||
### 3. Make changes
|
||||
|
||||
Apply edits following Svelte 5 best practices:
|
||||
|
||||
### 4. Validate changes
|
||||
|
||||
After editing, ALWAYS call `svelte-autofixer` with the updated code to check for issues.
|
||||
|
||||
### 5. Fix any issues
|
||||
|
||||
If the autofixer reports problems, fix them and re-validate until no issues remain.
|
||||
|
||||
## Output format
|
||||
|
||||
After completing your work, provide:
|
||||
|
||||
1. Summary of changes made
|
||||
2. Any issues found and fixed by the autofixer
|
||||
3. Recommendations for further improvements (if any)
|
||||
20
documentation/docs/50-subagents/10-subagent.md
Normal file
20
documentation/docs/50-subagents/10-subagent.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Overview
|
||||
---
|
||||
|
||||
Since creating, editing or analyzing a Svelte file is an atomic operation we recommend creating a subagent that your main agent can invoke whenever it needs to interact with a Svelte component. Subagents use a separate context window, allowing them to fetch documentation, iterate with [`svelte-autofixer`](tools#svelte-autofixer) and write to the filesystem without wasting context in the main agent.
|
||||
|
||||
Delegation should happen automatically when appropriate, but you can also explicitly request the subagent be used for Svelte-related tasks.
|
||||
|
||||
You can write your own or take inspiration from the one available in the [`sveltejs/ai-tools`](https://github.com/sveltejs/ai-tools/tree/main/tools/agents) repository: a specialized subagent called `svelte-file-editor` designed for creating, editing, and reviewing Svelte files.
|
||||
|
||||
<details>
|
||||
<summary>View subagent definition</summary>
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
````markdown
|
||||
@include .generated/subagent.md
|
||||
````
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
</details>
|
||||
3
documentation/docs/50-subagents/index.md
Normal file
3
documentation/docs/50-subagents/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Subagents
|
||||
---
|
||||
23
documentation/docs/60-plugins/10-claude-plugin.md
Normal file
23
documentation/docs/60-plugins/10-claude-plugin.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Claude Code
|
||||
---
|
||||
|
||||
The open source [repository](https://github.com/sveltejs/ai-tools) containing the code for the MCP server is also a Claude Code [plugin marketplace](https://code.claude.com/docs/en/discover-plugins).
|
||||
|
||||
The marketplace allows you to install the `svelte` plugin which will give you the remote MCP server, [skills](skills) to instruct the LLM on how to properly write Svelte 5 code, and a specialized agent for editing Svelte files.
|
||||
|
||||
If possible, we recommend that you instruct the LLM to execute MCP calls with the agent (you can explicitly mention an agent in your message to delegate work to it) when creating or editing `.svelte` files or `.svelte.ts`/`.svelte.js` modules — this will help save context by handling Svelte-specific tasks more efficiently.
|
||||
|
||||
## Installation
|
||||
|
||||
To add the repository as a marketplace, launch Claude Code and type the following:
|
||||
|
||||
```bash
|
||||
/plugin marketplace add sveltejs/ai-tools
|
||||
```
|
||||
|
||||
Then, install the Svelte plugin:
|
||||
|
||||
```bash
|
||||
/plugin install svelte
|
||||
```
|
||||
80
documentation/docs/60-plugins/20-opencode-plugin.md
Normal file
80
documentation/docs/60-plugins/20-opencode-plugin.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: OpenCode
|
||||
---
|
||||
|
||||
OpenCode has a [plugin system](https://opencode.ai/docs/plugins/) that allows developers to add MCP servers, agents and commands programmatically. Svelte has an OpenCode plugin published under `@sveltejs/opencode`.
|
||||
|
||||
## Installation
|
||||
|
||||
With OpenCode 1.3.4 or newer, install the plugin from the command line:
|
||||
|
||||
```sh
|
||||
opencode plugin @sveltejs/opencode
|
||||
```
|
||||
|
||||
Alternatively, edit your [OpenCode config](https://opencode.ai/docs/config/) (either the global or the local one) and add `@sveltejs/opencode` to the list of plugins:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["@sveltejs/opencode"]
|
||||
}
|
||||
```
|
||||
|
||||
That's it! You now have the Svelte [MCP server](mcp), [skills](skills), and the `svelte-file-editor` [subagent](subagent) configured for you.
|
||||
|
||||
### TUI configuration
|
||||
|
||||
The package also includes a TUI plugin for configuring these features interactively. Add `@sveltejs/opencode` to your global or project-local `tui.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/tui.json",
|
||||
"plugin": ["@sveltejs/opencode"]
|
||||
}
|
||||
```
|
||||
|
||||
Restart OpenCode, then run `/svelte-plugin` or select 'Configure Svelte plugin' from the command palette. Choose whether to edit the project or global configuration, then use the checkboxes and radio options to configure the plugin. Changes are saved automatically, and 'Revert changes' restores the values from when the dialog was opened.
|
||||
|
||||
## Configuration
|
||||
|
||||
By default, the MCP server, subagent, skills, instructions, and automatic updates are enabled. The TUI plugin writes the same configuration files that you can create or edit manually:
|
||||
|
||||
- locally, in `.opencode/svelte.json`
|
||||
- globally, in `~/.config/opencode/svelte.json` (or, if you have specified the environment variable, in `$OPENCODE_CONFIG_DIR/svelte.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://svelte.dev/opencode/schema.json",
|
||||
"mcp": {
|
||||
"type": "remote", // or "local" — defaults to remote
|
||||
"enabled": true
|
||||
},
|
||||
"subagent": {
|
||||
"enabled": true,
|
||||
"agents": {
|
||||
"svelte-file-editor": {
|
||||
"model": "<other-model>", // defaults to the same as main agent
|
||||
"temperature": 1, // defaults to unset
|
||||
"top_p": 0.7, // defaults to unset
|
||||
"maxSteps": 20 // defaults to unlimited
|
||||
}
|
||||
}
|
||||
},
|
||||
"skills": {
|
||||
// this can be `true`, or an array of skills to enable
|
||||
// e.g. ["svelte-core-bestpractices"]
|
||||
"enabled": true
|
||||
},
|
||||
"instructions": {
|
||||
"enabled": true
|
||||
},
|
||||
"autoupdate": true
|
||||
}
|
||||
```
|
||||
|
||||
### Automatic updates
|
||||
|
||||
The plugin checks npm for newer versions and warns you when one is available. OpenCode caches plugins, so it continues using the cached version until that cache is removed.
|
||||
|
||||
Automatic updates are enabled by default. After detecting a newer version, the plugin removes itself from the cache when OpenCode shuts down. OpenCode installs the latest version the next time it starts. Automatic updates only apply when the plugin is unpinned or explicitly uses the `latest` tag. Exact versions, ranges, and other dist-tags are left untouched because reinstalling them may resolve to the same version again. Set `"autoupdate": false` to only receive the warning.
|
||||
25
documentation/docs/60-plugins/30-cursor-plugin.md
Normal file
25
documentation/docs/60-plugins/30-cursor-plugin.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Cursor
|
||||
---
|
||||
|
||||
Cursor has a [plugin system](https://cursor.com/docs/plugins) that can bundle rules, skills, agents, commands, MCP servers, and hooks.
|
||||
|
||||
The Svelte plugin gives you the remote Svelte MCP server, Cursor [skills](skills), an always-on rule that tells the model how to use the Svelte MCP tools correctly, and the `svelte-file-editor` subagent for working on `.svelte` files and `.svelte.ts`/`.svelte.js` modules. The source is available in the [`sveltejs/ai-tools`](https://github.com/sveltejs/ai-tools/tree/main/plugins/cursor/svelte) repo.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the plugin from the [Cursor Marketplace](https://cursor.com/marketplace/svelte) with the following command:
|
||||
|
||||
```
|
||||
/add-plugin svelte
|
||||
```
|
||||
|
||||
Plugins can be installed either for the current project or at user level.
|
||||
|
||||
Once installed, Cursor will discover the plugin components automatically:
|
||||
|
||||
- the Svelte MCP server is added from the plugin's `.mcp.json`
|
||||
- rules and skills appear in Cursor's rules UI
|
||||
- the `svelte-file-editor` agent becomes available in chat
|
||||
|
||||
> [!NOTE] The Cursor CLI does not support plugins yet. Plugin support in [Cloud Agents](https://cursor.com/docs/cloud-agent) is limited to MCP servers.
|
||||
36
documentation/docs/60-plugins/40-copilot-plugin.md
Normal file
36
documentation/docs/60-plugins/40-copilot-plugin.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: GitHub Copilot CLI
|
||||
---
|
||||
|
||||
The open source [repository](https://github.com/sveltejs/ai-tools) containing the code for the MCP server is also a GitHub Copilot CLI [plugin marketplace](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-finding-installing).
|
||||
|
||||
The marketplace allows you to install the `svelte` plugin which will give you the remote MCP server, [skills](skills) to instruct the LLM on how to properly write Svelte 5 code, and a specialized agent for editing Svelte files.
|
||||
|
||||
If possible, we recommend that you instruct the LLM to execute MCP calls with the agent (you can explicitly mention an agent in your message to delegate work to it) when creating or editing `.svelte` files or `.svelte.ts`/`.svelte.js` modules — this will help save context by handling Svelte-specific tasks more efficiently.
|
||||
|
||||
## Installation
|
||||
|
||||
In VS Code, run the 'Install plugin from source' command and use the repository URL:
|
||||
|
||||
```text
|
||||
https://github.com/sveltejs/ai-tools
|
||||
```
|
||||
|
||||
You can also add the repository as a marketplace from the Copilot CLI:
|
||||
|
||||
```bash
|
||||
copilot plugin marketplace add sveltejs/ai-tools
|
||||
```
|
||||
|
||||
Then, install the Svelte plugin:
|
||||
|
||||
```bash
|
||||
copilot plugin install svelte@ai-tools
|
||||
```
|
||||
|
||||
You can also run the same commands from an interactive Copilot CLI session:
|
||||
|
||||
```bash
|
||||
/plugin marketplace add sveltejs/ai-tools
|
||||
/plugin install svelte@ai-tools
|
||||
```
|
||||
28
documentation/docs/60-plugins/50-codex-plugin.md
Normal file
28
documentation/docs/60-plugins/50-codex-plugin.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Codex CLI
|
||||
---
|
||||
|
||||
The open source [repository](https://github.com/sveltejs/ai-tools) containing the code for the MCP server is also a Codex CLI [plugin marketplace](https://developers.openai.com/codex/plugins).
|
||||
|
||||
The marketplace allows you to install the `svelte` plugin which will give you the remote MCP server, [skills](skills) to instruct the LLM on how to properly write Svelte 5 code, and a specialized agent for editing Svelte files.
|
||||
|
||||
If possible, we recommend that you instruct the LLM to execute MCP calls with the agent (you can explicitly mention an agent in your message to delegate work to it) when creating or editing `.svelte` files or `.svelte.ts`/`.svelte.js` modules — this will help save context by handling Svelte-specific tasks more efficiently.
|
||||
|
||||
## Installation
|
||||
|
||||
Add the repository as a marketplace from the Codex CLI:
|
||||
|
||||
```bash
|
||||
codex plugin marketplace add sveltejs/ai-tools
|
||||
```
|
||||
|
||||
Then, open the plugin directory from an interactive Codex CLI session:
|
||||
|
||||
```bash
|
||||
codex
|
||||
/plugins
|
||||
```
|
||||
|
||||
Choose the Svelte marketplace, select the `svelte` plugin, and install it.
|
||||
|
||||
Codex can read the repository's legacy-compatible `.claude-plugin/marketplace.json` marketplace file, so the same marketplace source works for both Claude Code and Codex CLI.
|
||||
3
documentation/docs/60-plugins/index.md
Normal file
3
documentation/docs/60-plugins/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Plugins
|
||||
---
|
||||
3
documentation/docs/index.md
Normal file
3
documentation/docs/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: AI
|
||||
---
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineConfig } from 'drizzle-kit';
|
||||
|
||||
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
|
||||
|
||||
export default defineConfig({
|
||||
schema: './src/lib/server/db/schema.ts',
|
||||
dialect: 'sqlite',
|
||||
dbCredentials: { url: process.env.DATABASE_URL },
|
||||
verbose: true,
|
||||
strict: true,
|
||||
});
|
||||
@@ -5,15 +5,27 @@ import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
import svelteConfig from './apps/mcp-remote/svelte.config.js';
|
||||
import eslint_plugin_import from 'eslint-plugin-import';
|
||||
import { configs as pnpm } from 'eslint-plugin-pnpm';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
const gitignore_path = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default ts.config(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
export default /** @type {import("eslint").Linter.Config} */ ([
|
||||
includeIgnoreFile(gitignore_path),
|
||||
{
|
||||
ignores: [
|
||||
'.claude/**/*',
|
||||
'.changeset/*',
|
||||
'.github/**/*.yml',
|
||||
'.github/**/*.yaml',
|
||||
'**/pnpm-lock.yaml',
|
||||
],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
eslint_plugin_import.flatConfigs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
@@ -24,6 +36,38 @@ export default ts.config(
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off',
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'error',
|
||||
{
|
||||
selector: ['variableLike'],
|
||||
format: ['snake_case', 'UPPER_CASE'],
|
||||
leadingUnderscore: 'allow',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
varsIgnorePattern: '^_',
|
||||
ignoreRestSiblings: true,
|
||||
},
|
||||
],
|
||||
'func-style': ['error', 'declaration', { allowTypeAnnotation: true }],
|
||||
'import/no-unresolved': 'off', // this doesn't work well with typescript path mapping
|
||||
'import/extensions': [
|
||||
'error',
|
||||
{
|
||||
ignorePackages: true,
|
||||
pattern: {
|
||||
js: 'always',
|
||||
mjs: 'always',
|
||||
cjs: 'always',
|
||||
ts: 'always',
|
||||
svelte: 'always',
|
||||
svg: 'always',
|
||||
json: 'always',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -37,4 +81,16 @@ export default ts.config(
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
{
|
||||
name: 'pnpm/exclude-some-rules',
|
||||
files: ['**/*.json', '**/*.yaml', '**/*.yml', 'pnpm-workspace.yaml'],
|
||||
rules: {
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 'off',
|
||||
'func-style': 'off',
|
||||
},
|
||||
},
|
||||
...pnpm.json,
|
||||
...pnpm.yaml,
|
||||
]);
|
||||
|
||||
104
package.json
104
package.json
@@ -1,33 +1,39 @@
|
||||
{
|
||||
"name": "@sveltejs/mcp",
|
||||
"name": "@sveltejs/mcp-mono",
|
||||
"version": "0.0.1",
|
||||
"description": "The official Svelte MCP server implementation",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
"svelte-mcp": "./dist/lib/stdio.js"
|
||||
},
|
||||
"packageManager": "pnpm@10.33.4+sha512.1c67b3b359b2d408119ba1ed289f34b8fc3c6873412bec6fd264fbdc82489e510fcbecb9ce9d22dae7f3b76269d8441046014bdca53b9979cd7a561ad631b800",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"build:mcp": "tsc --project tsconfig.build.json",
|
||||
"prepublishOnly": "pnpm build:mcp",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"build": "pnpm -r run build",
|
||||
"dev": "pnpm --filter @sveltejs/mcp-remote run dev",
|
||||
"check": "pnpm -r run check",
|
||||
"check:publint": "pnpm -r run check:publint",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"lint:fix": "prettier --write . && eslint . --fix",
|
||||
"lint:inspect": "pnpm dlx @eslint/config-inspector",
|
||||
"node:inspect": "pnpm dlx node-modules-inspector",
|
||||
"test:unit": "vitest",
|
||||
"test": "npm run test:unit -- --run",
|
||||
"test:watch": "npm run test:unit -- --watch",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"inspect": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector"
|
||||
"inspect": "pnpm mcp-inspector",
|
||||
"generate-opencode-jsonschema": "pnpm --filter @sveltejs/opencode run generate-schema",
|
||||
"generate-summaries": "pnpm --filter @sveltejs/mcp-server run generate-summaries",
|
||||
"generate-prompt-docs": "node --import node-resolve-ts/register scripts/update-docs-prompts.ts",
|
||||
"generate-skill-docs": "node --import node-resolve-ts/register scripts/update-docs-skills.ts",
|
||||
"generate-subagent-docs": "cp ./tools/agents/svelte-file-editor.md ./documentation/docs/50-subagents/.generated/subagent.md",
|
||||
"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 && 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",
|
||||
@@ -37,46 +43,24 @@
|
||||
],
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@modelcontextprotocol/inspector": "^0.16.7",
|
||||
"@sveltejs/adapter-vercel": "^5.6.3",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@types/eslint-scope": "^8.3.2",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/node": "^24.3.1",
|
||||
"@typescript-eslint/types": "^8.43.0",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^7.0.4",
|
||||
"vite-plugin-devtools-json": "^1.0.0",
|
||||
"vitest": "^3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tmcp/adapter-valibot": "^0.1.4",
|
||||
"@tmcp/transport-http": "^0.6.0",
|
||||
"@tmcp/transport-stdio": "^0.1.3",
|
||||
"@typescript-eslint/parser": "^8.43.0",
|
||||
"svelte-eslint-parser": "^1.3.2",
|
||||
"tmcp": "^1.12.2",
|
||||
"valibot": "^1.1.0",
|
||||
"zimmerframe": "^1.1.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
"@changesets/changelog-github": "catalog:tooling",
|
||||
"@changesets/cli": "catalog:tooling",
|
||||
"@eslint/compat": "catalog:lint",
|
||||
"@eslint/js": "catalog:lint",
|
||||
"@modelcontextprotocol/inspector": "catalog:ai",
|
||||
"@sveltejs/adapter-vercel": "catalog:svelte",
|
||||
"eslint": "catalog:lint",
|
||||
"eslint-config-prettier": "catalog:lint",
|
||||
"eslint-plugin-import": "catalog:lint",
|
||||
"eslint-plugin-pnpm": "catalog:lint",
|
||||
"eslint-plugin-svelte": "catalog:lint",
|
||||
"globals": "catalog:lint",
|
||||
"node-resolve-ts": "catalog:tooling",
|
||||
"prettier": "catalog:lint",
|
||||
"prettier-plugin-svelte": "catalog:lint",
|
||||
"publint": "catalog:tooling",
|
||||
"typescript": "catalog:tooling",
|
||||
"typescript-eslint": "catalog:lint",
|
||||
"vitest": "catalog:tooling"
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/mcp-server/.env.example
Normal file
2
packages/mcp-server/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
# Anthropic API Key from: https://console.anthropic.com/
|
||||
ANTHROPIC_API_KEY=your_api_key_here
|
||||
43
packages/mcp-server/package.json
Normal file
43
packages/mcp-server/package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@sveltejs/mcp-server",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest",
|
||||
"generate-summaries": "node scripts/generate-summaries.ts --experimental-strip-types",
|
||||
"debug:generate-summaries": "DEBUG_MODE=1 node scripts/generate-summaries.ts --experimental-strip-types"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./handlers": "./src/mcp/handlers/tools/handlers.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mcp-ui/server": "catalog:ai",
|
||||
"@tmcp/adapter-valibot": "catalog:tmcp",
|
||||
"@tmcp/transport-in-memory": "catalog:tmcp",
|
||||
"@typescript-eslint/parser": "catalog:lint",
|
||||
"eslint": "catalog:lint",
|
||||
"eslint-plugin-svelte": "catalog:lint",
|
||||
"svelte": "catalog:svelte",
|
||||
"svelte-eslint-parser": "catalog:lint",
|
||||
"tmcp": "catalog:tmcp",
|
||||
"ts-blank-space": "catalog:tooling",
|
||||
"typescript-eslint": "catalog:lint",
|
||||
"valibot": "catalog:tooling",
|
||||
"vitest": "catalog:tooling",
|
||||
"zimmerframe": "catalog:tooling"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "catalog:ai",
|
||||
"@sveltejs/kit": "catalog:svelte",
|
||||
"@types/estree": "catalog:tooling",
|
||||
"@typescript-eslint/types": "catalog:lint",
|
||||
"dotenv": "catalog:tooling"
|
||||
}
|
||||
}
|
||||
244
packages/mcp-server/scripts/generate-summaries.ts
Normal file
244
packages/mcp-server/scripts/generate-summaries.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env node
|
||||
import 'dotenv/config';
|
||||
import { writeFile, mkdir } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { get_sections } from '../src/mcp/utils.ts';
|
||||
import { AnthropicProvider } from '../src/lib/anthropic.ts';
|
||||
import { type AnthropicBatchRequest, type SummaryData } from '../src/lib/schemas.ts';
|
||||
|
||||
const current_filename = fileURLToPath(import.meta.url);
|
||||
const current_dirname = path.dirname(current_filename);
|
||||
|
||||
const USE_CASES_PROMPT = `
|
||||
You are tasked with analyzing Svelte 5 and SvelteKit documentation pages to identify when they would be useful.
|
||||
|
||||
Your task:
|
||||
1. Read the documentation page content provided
|
||||
2. Identify the main use cases, scenarios, or queries where this documentation would be relevant
|
||||
3. Create a VERY SHORT, comma-separated list of use cases (maximum 200 characters total)
|
||||
4. Think about what a developer might be trying to build or accomplish when they need this documentation
|
||||
|
||||
Guidelines:
|
||||
- Focus on WHEN this documentation would be needed, not WHAT it contains
|
||||
- Consider specific project types (e.g., "e-commerce site", "blog", "dashboard", "social media app")
|
||||
- Consider specific features (e.g., "authentication", "forms", "data fetching", "animations")
|
||||
- Consider specific components (e.g., "slider", "modal", "dropdown", "card")
|
||||
- Consider development stages (e.g., "project setup", "deployment", "testing", "migration")
|
||||
- Use "always" for fundamental concepts that apply to virtually all Svelte projects
|
||||
- Be concise but specific
|
||||
- Use lowercase
|
||||
- Separate multiple use cases with commas
|
||||
|
||||
Examples of good use_cases:
|
||||
- "always, any svelte project, core reactivity"
|
||||
- "authentication, login systems, user management"
|
||||
- "e-commerce, product listings, shopping carts"
|
||||
- "forms, user input, data submission"
|
||||
- "deployment, production builds, hosting setup"
|
||||
- "animation, transitions, interactive ui"
|
||||
- "routing, navigation, multi-page apps"
|
||||
- "blog, content sites, markdown rendering"
|
||||
|
||||
Requirements:
|
||||
- Maximum 200 characters (including spaces and commas)
|
||||
- Lowercase only
|
||||
- Comma-separated list of use cases
|
||||
- Focus on WHEN/WHY someone would need this, not what it is
|
||||
- Be specific about project types, features, or components when applicable
|
||||
- Use "always" sparingly, only for truly universal concepts
|
||||
- Do not include quotes or special formatting in your response
|
||||
- Respond with ONLY the use cases text, no additional text
|
||||
|
||||
Here is the documentation page content to analyze:
|
||||
|
||||
`;
|
||||
|
||||
async function fetch_section_content(url: string) {
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(30000) });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 Starting use cases generation...');
|
||||
|
||||
// Check for API key
|
||||
const api_key = process.env.ANTHROPIC_API_KEY;
|
||||
if (!api_key) {
|
||||
console.error('❌ Error: ANTHROPIC_API_KEY environment variable is required');
|
||||
console.error('Please set it in packages/mcp-server/.env file or export it:');
|
||||
console.error('export ANTHROPIC_API_KEY=your_api_key_here');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get all sections
|
||||
console.log('📚 Fetching documentation sections...');
|
||||
let sections = await get_sections();
|
||||
console.log(`Found ${sections.length} sections`);
|
||||
|
||||
// Debug mode: limit to 2 sections
|
||||
const debug_mode = process.env.DEBUG_MODE === '1';
|
||||
if (debug_mode) {
|
||||
console.log('🐛 DEBUG_MODE enabled - processing only 2 sections');
|
||||
sections = sections.slice(0, 2);
|
||||
}
|
||||
|
||||
// Fetch content for each section
|
||||
console.log('📥 Downloading section content...');
|
||||
const sections_with_content: Array<{
|
||||
section: (typeof sections)[number];
|
||||
content: string;
|
||||
index: number;
|
||||
}> = [];
|
||||
const download_errors: Array<{ section: string; error: string }> = [];
|
||||
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
const section = sections[i]!;
|
||||
try {
|
||||
console.log(`Fetching ${i + 1}/${sections.length}: ${section.title}`);
|
||||
const content = await fetch_section_content(section.url);
|
||||
sections_with_content.push({
|
||||
section,
|
||||
content,
|
||||
index: i,
|
||||
});
|
||||
} catch (error) {
|
||||
const error_msg = error instanceof Error ? error.message : String(error);
|
||||
console.error(`⚠️ Failed to fetch ${section.title}:`, error_msg);
|
||||
download_errors.push({ section: section.title, error: error_msg });
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Successfully downloaded ${sections_with_content.length} sections`);
|
||||
|
||||
if (sections_with_content.length === 0) {
|
||||
console.error('❌ No sections were successfully downloaded');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize Anthropic client
|
||||
console.log('🤖 Initializing Anthropic API...');
|
||||
const anthropic = new AnthropicProvider('claude-sonnet-4-5-20250929', api_key);
|
||||
|
||||
// Prepare batch requests
|
||||
console.log('📦 Preparing batch requests...');
|
||||
const batch_requests: AnthropicBatchRequest[] = sections_with_content.map(
|
||||
({ content, index }) => ({
|
||||
custom_id: `section-${index}`,
|
||||
params: {
|
||||
model: anthropic.get_model_identifier(),
|
||||
max_tokens: 250,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: USE_CASES_PROMPT + content,
|
||||
},
|
||||
],
|
||||
temperature: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// Create and process batch
|
||||
console.log('🚀 Creating batch job...');
|
||||
const batch_response = await anthropic.create_batch(batch_requests);
|
||||
console.log(`✅ Batch created with ID: ${batch_response.id}`);
|
||||
|
||||
// Poll for completion
|
||||
console.log('⏳ Waiting for batch to complete...');
|
||||
let batch_status = await anthropic.get_batch_status(batch_response.id);
|
||||
|
||||
while (batch_status.processing_status === 'in_progress') {
|
||||
const { succeeded, processing, errored } = batch_status.request_counts;
|
||||
console.log(` Progress: ${succeeded} succeeded, ${processing} processing, ${errored} errored`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
batch_status = await anthropic.get_batch_status(batch_response.id);
|
||||
}
|
||||
|
||||
console.log('✅ Batch processing completed!');
|
||||
|
||||
// Get results
|
||||
if (!batch_status.results_url) {
|
||||
throw new Error('Batch completed but no results URL available');
|
||||
}
|
||||
|
||||
console.log('📥 Downloading results...');
|
||||
const results = await anthropic.get_batch_results(batch_status.results_url);
|
||||
|
||||
// Process results
|
||||
console.log('📊 Processing results...');
|
||||
const summaries: Record<string, string> = {};
|
||||
const errors: Array<{ section: string; error: string }> = [];
|
||||
|
||||
for (const result of results) {
|
||||
const index = parseInt(result.custom_id.split('-')[1] ?? '0');
|
||||
const section_data = sections_with_content.find((s) => s.index === index);
|
||||
|
||||
if (!section_data) {
|
||||
console.warn(`⚠️ Could not find section for index ${index}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { section } = section_data;
|
||||
|
||||
if (result.result.type !== 'succeeded' || !result.result.message) {
|
||||
const error_msg = result.result.error?.message || 'Failed or no message';
|
||||
console.error(` ❌ ${section.title}: ${error_msg}`);
|
||||
errors.push({ section: section.title, error: error_msg });
|
||||
continue;
|
||||
}
|
||||
|
||||
const output_content = result.result.message.content[0]?.text;
|
||||
if (output_content) {
|
||||
summaries[section.slug] = output_content.trim();
|
||||
console.log(` ✅ ${section.title}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Write output to JSON file
|
||||
console.log('💾 Writing results to file...');
|
||||
const output_path = path.join(current_dirname, '../src/use_cases.json');
|
||||
const output_dir = path.dirname(output_path);
|
||||
|
||||
await mkdir(output_dir, { recursive: true });
|
||||
|
||||
const summary_data: SummaryData = {
|
||||
generated_at: new Date().toISOString(),
|
||||
model: anthropic.get_model_identifier(),
|
||||
total_sections: sections.length,
|
||||
successful_summaries: Object.keys(summaries).length,
|
||||
failed_summaries: errors.length,
|
||||
summaries,
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
download_errors: download_errors.length > 0 ? download_errors : undefined,
|
||||
};
|
||||
|
||||
await writeFile(output_path, JSON.stringify(summary_data, null, 2), 'utf-8');
|
||||
|
||||
// Print summary
|
||||
console.log('\n📊 Summary:');
|
||||
console.log(` Total sections: ${sections.length}`);
|
||||
console.log(` Successfully downloaded: ${sections_with_content.length}`);
|
||||
console.log(` Download failures: ${download_errors.length}`);
|
||||
console.log(` Successfully analyzed: ${Object.keys(summaries).length}`);
|
||||
console.log(` Analysis failures: ${errors.length}`);
|
||||
console.log(`\n✅ Results written to: ${output_path}`);
|
||||
|
||||
if (download_errors.length > 0) {
|
||||
console.log('\n⚠️ Some sections failed to download:');
|
||||
download_errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`));
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.log('\n⚠️ Some sections failed to analyze:');
|
||||
errors.forEach((e) => console.log(` - ${e.section}: ${e.error}`));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
6
packages/mcp-server/scripts/tsconfig.json
Normal file
6
packages/mcp-server/scripts/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export const base_runes = [
|
||||
export const nested_runes = [
|
||||
'$state.raw',
|
||||
'$state.snapshot',
|
||||
'$state.eager',
|
||||
'$effect.pre',
|
||||
'$effect.tracking',
|
||||
'$effect.pending',
|
||||
1
packages/mcp-server/src/index.ts
Normal file
1
packages/mcp-server/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { server, type SvelteMcp } from './mcp/index.js';
|
||||
172
packages/mcp-server/src/lib/anthropic.ts
Normal file
172
packages/mcp-server/src/lib/anthropic.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Anthropic } from '@anthropic-ai/sdk';
|
||||
import type { Model } from '@anthropic-ai/sdk/resources/messages/messages.js';
|
||||
import * as v from 'valibot';
|
||||
import {
|
||||
anthropic_batch_response_schema,
|
||||
anthropic_batch_result_schema,
|
||||
type AnthropicBatchRequest,
|
||||
} from './schemas.js';
|
||||
|
||||
export class AnthropicProvider {
|
||||
private client: Anthropic;
|
||||
private modelId: Model;
|
||||
private baseUrl: string;
|
||||
private apiKey: string;
|
||||
name = 'Anthropic';
|
||||
|
||||
constructor(model_id: Model, api_key: string) {
|
||||
if (!api_key) {
|
||||
throw new Error('ANTHROPIC_API_KEY is required');
|
||||
}
|
||||
this.apiKey = api_key;
|
||||
this.client = new Anthropic({ apiKey: api_key, timeout: 1800000 });
|
||||
this.modelId = model_id;
|
||||
this.baseUrl = 'https://api.anthropic.com/v1';
|
||||
}
|
||||
|
||||
get_client(): Anthropic {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
get_model_identifier(): Model {
|
||||
return this.modelId;
|
||||
}
|
||||
|
||||
async create_batch(requests: AnthropicBatchRequest[]) {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/messages/batches`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-api-key': this.apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ requests }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error_text = await response.text();
|
||||
throw new Error(
|
||||
`Failed to create batch: ${response.status} ${response.statusText} - ${error_text}`,
|
||||
);
|
||||
}
|
||||
|
||||
const json_data = await response.json();
|
||||
const validated_response = v.safeParse(anthropic_batch_response_schema, json_data);
|
||||
|
||||
if (!validated_response.success) {
|
||||
throw new Error(
|
||||
`Invalid batch response from Anthropic API: ${JSON.stringify(validated_response.issues)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return validated_response.output;
|
||||
} catch (error) {
|
||||
console.error('Error creating batch with Anthropic:', error);
|
||||
throw new Error(
|
||||
`Failed to create batch: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async get_batch_status(batch_id: string, max_retries = 10, retry_delay = 30000) {
|
||||
let retry_count = 0;
|
||||
|
||||
while (retry_count <= max_retries) {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/messages/batches/${batch_id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-api-key': this.apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error_text = await response.text();
|
||||
throw new Error(
|
||||
`Failed to get batch status: ${response.status} ${response.statusText} - ${error_text}`,
|
||||
);
|
||||
}
|
||||
|
||||
const json_data = await response.json();
|
||||
const validated_response = v.safeParse(anthropic_batch_response_schema, json_data);
|
||||
|
||||
if (!validated_response.success) {
|
||||
throw new Error(
|
||||
`Invalid batch status response from Anthropic API: ${JSON.stringify(validated_response.issues)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return validated_response.output;
|
||||
} catch (error) {
|
||||
retry_count++;
|
||||
|
||||
if (retry_count > max_retries) {
|
||||
console.error(
|
||||
`Error getting batch status for ${batch_id} after ${max_retries} retries:`,
|
||||
error,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to get batch status after ${max_retries} retries: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`Error getting batch status for ${batch_id} (attempt ${retry_count}/${max_retries}):`,
|
||||
error,
|
||||
);
|
||||
console.log(`Retrying in ${retry_delay / 1000} seconds...`);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, retry_delay));
|
||||
}
|
||||
}
|
||||
|
||||
// This should never be reached due to the throw in the catch block, but TypeScript needs a return
|
||||
throw new Error(`Failed to get batch status for ${batch_id} after ${max_retries} retries`);
|
||||
}
|
||||
|
||||
async get_batch_results(results_url: string) {
|
||||
try {
|
||||
const response = await fetch(results_url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-api-key': this.apiKey,
|
||||
'anthropic-version': '2023-06-01',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error_text = await response.text();
|
||||
throw new Error(
|
||||
`Failed to get batch results: ${response.status} ${response.statusText} - ${error_text}`,
|
||||
);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
// Parse JSONL format (one JSON object per line)
|
||||
const parsed_results = text
|
||||
.split('\n')
|
||||
.filter((line) => line.trim())
|
||||
.map((line) => JSON.parse(line));
|
||||
|
||||
// Validate all results
|
||||
const validated_results = v.safeParse(v.array(anthropic_batch_result_schema), parsed_results);
|
||||
|
||||
if (!validated_results.success) {
|
||||
throw new Error(
|
||||
`Invalid batch results from Anthropic API: ${JSON.stringify(validated_results.issues)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return validated_results.output;
|
||||
} catch (error) {
|
||||
console.error(`Error getting batch results:`, error);
|
||||
throw new Error(
|
||||
`Failed to get batch results: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
packages/mcp-server/src/lib/schemas.ts
Normal file
122
packages/mcp-server/src/lib/schemas.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import * as v from 'valibot';
|
||||
|
||||
export const documentation_sections_schema = v.record(
|
||||
v.string(),
|
||||
v.object({
|
||||
metadata: v.object({
|
||||
title: v.string(),
|
||||
use_cases: v.optional(v.string()),
|
||||
}),
|
||||
slug: v.string(),
|
||||
}),
|
||||
);
|
||||
|
||||
// Valibot schemas for Batch API
|
||||
export const summary_data_schema = v.object({
|
||||
generated_at: v.string(),
|
||||
model: v.string(),
|
||||
total_sections: v.number(),
|
||||
successful_summaries: v.number(),
|
||||
failed_summaries: v.number(),
|
||||
summaries: v.record(v.string(), v.string()),
|
||||
errors: v.optional(
|
||||
v.array(
|
||||
v.object({
|
||||
section: v.string(),
|
||||
error: v.string(),
|
||||
}),
|
||||
),
|
||||
),
|
||||
download_errors: v.optional(
|
||||
v.array(
|
||||
v.object({
|
||||
section: v.string(),
|
||||
error: v.string(),
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
export const anthropic_batch_request_schema = v.object({
|
||||
custom_id: v.string(),
|
||||
params: v.object({
|
||||
model: v.string(),
|
||||
max_tokens: v.number(),
|
||||
messages: v.array(
|
||||
v.object({
|
||||
role: v.union([v.literal('user'), v.literal('assistant')]),
|
||||
content: v.union([
|
||||
v.string(),
|
||||
v.array(
|
||||
v.object({
|
||||
type: v.string(),
|
||||
text: v.string(),
|
||||
}),
|
||||
),
|
||||
]),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
export const anthropic_batch_response_schema = v.object({
|
||||
id: v.string(),
|
||||
type: v.string(),
|
||||
processing_status: v.union([v.literal('in_progress'), v.literal('ended')]),
|
||||
request_counts: v.object({
|
||||
processing: v.number(),
|
||||
succeeded: v.number(),
|
||||
errored: v.number(),
|
||||
canceled: v.number(),
|
||||
expired: v.number(),
|
||||
}),
|
||||
ended_at: v.nullable(v.string()),
|
||||
created_at: v.string(),
|
||||
expires_at: v.string(),
|
||||
cancel_initiated_at: v.nullable(v.string()),
|
||||
results_url: v.nullable(v.string()),
|
||||
});
|
||||
|
||||
export const anthropic_batch_result_schema = v.object({
|
||||
custom_id: v.string(),
|
||||
result: v.object({
|
||||
type: v.union([
|
||||
v.literal('succeeded'),
|
||||
v.literal('errored'),
|
||||
v.literal('canceled'),
|
||||
v.literal('expired'),
|
||||
]),
|
||||
message: v.optional(
|
||||
v.object({
|
||||
id: v.string(),
|
||||
type: v.string(),
|
||||
role: v.string(),
|
||||
model: v.string(),
|
||||
content: v.array(
|
||||
v.object({
|
||||
type: v.string(),
|
||||
text: v.string(),
|
||||
}),
|
||||
),
|
||||
stop_reason: v.string(),
|
||||
stop_sequence: v.nullable(v.string()),
|
||||
usage: v.object({
|
||||
input_tokens: v.number(),
|
||||
output_tokens: v.number(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
error: v.optional(
|
||||
v.object({
|
||||
type: v.string(),
|
||||
message: v.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
// Export inferred types
|
||||
export type SummaryData = v.InferOutput<typeof summary_data_schema>;
|
||||
export type AnthropicBatchRequest = v.InferOutput<typeof anthropic_batch_request_schema>;
|
||||
export type AnthropicBatchResponse = v.InferOutput<typeof anthropic_batch_response_schema>;
|
||||
export type AnthropicBatchResult = v.InferOutput<typeof anthropic_batch_result_schema>;
|
||||
@@ -0,0 +1,790 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { add_autofixers_issues } from './add-autofixers-issues.js';
|
||||
import { base_runes } from '../../constants.js';
|
||||
|
||||
const dollarless_runes = base_runes.map((r) => ({ rune: r.replace('$', '') }));
|
||||
|
||||
function run_autofixers_on_code(code: string, desired_svelte_version = 5) {
|
||||
const content = { issues: [], suggestions: [] };
|
||||
add_autofixers_issues(content, code, desired_svelte_version);
|
||||
return content;
|
||||
}
|
||||
|
||||
function with_possible_inits(title: string, fn: (args: { init: string }) => void) {
|
||||
describe.each([
|
||||
{ init: '$state' },
|
||||
{ init: '$state.raw' },
|
||||
{ init: '$derived' },
|
||||
{ init: '$derived.by' },
|
||||
])(title, fn);
|
||||
}
|
||||
|
||||
describe('add_autofixers_issues', () => {
|
||||
describe('assign_in_effect', () => {
|
||||
with_possible_inits('($init)', ({ init }) => {
|
||||
it(`should add suggestions when assigning to a stateful variable inside an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}(0);
|
||||
$effect(() => {
|
||||
count = 43;
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add a suggestion for each variable assigned within an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = $state(0);
|
||||
const count2 = $state(0);
|
||||
$effect(() => {
|
||||
count = 43;
|
||||
count2 = 44;
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(2);
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count2" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
it(`should not add a suggestion for variables that are not assigned within an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}(0);
|
||||
</script>
|
||||
|
||||
<button onclick={() => count = 43}>Increment</button>
|
||||
`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it("should not add a suggestions for variables that are assigned within an effect but aren't stateful", () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = 0;
|
||||
|
||||
$effect(() => {
|
||||
count = 43;
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add a suggestion for variables that are assigned within an effect with an update`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
let count = ${init}(0);
|
||||
|
||||
$effect(() => {
|
||||
count++;
|
||||
});
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add a suggestion for variables that are mutated within an effect`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
let count = ${init}({ value: 0 });
|
||||
|
||||
$effect(() => {
|
||||
count.value = 42;
|
||||
});
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add a suggestion for variables that are mutated within an effect.pre`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
let count = ${init}({ value: 0 });
|
||||
|
||||
$effect.pre(() => {
|
||||
count.value = 42;
|
||||
});
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.suggestions).toContain(
|
||||
'The stateful variable "count" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a suggestion when calling a function inside an effect', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { fetch_data } from './data.js';
|
||||
$effect(() => {
|
||||
fetch_data();
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are calling the function \`fetch_data\` inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should add a suggestion when calling a function inside an effect (with non identifier callee)', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { fetch_data } from './data.js';
|
||||
$effect(() => {
|
||||
fetch_data.fetch();
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are calling a function inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
with_possible_inits('($init)', ({ init }) => {
|
||||
describe.each([{ method: 'set' }, { method: 'update' }])(
|
||||
'wrong_property_access_state ($method)',
|
||||
({ method }) => {
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with a literal init`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}(0);
|
||||
function update_count() {
|
||||
count.${method}(43);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with an array init`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}([0]);
|
||||
function update_count() {
|
||||
count.${method}([1]);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}({}))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}({ value: 0 });
|
||||
function update_count() {
|
||||
count.${method}({ value: 43 });
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}(new Class()))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}(new Class());
|
||||
function update_count() {
|
||||
count.${method}(new Class());
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using .${method}() on a stateful variable with conditional if it's not sure if the method could actually be present on the variable (${init}(variable_name))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { init } = $props();
|
||||
const count = ${init}(init);
|
||||
function update_count() {
|
||||
count.${method}(43);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should not add suggestions when using .${method} on a stateful variable if it's not a method call`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}({});
|
||||
function update_count() {
|
||||
console.log(count.${method});
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are trying to update the stateful variable "count" using "${method}". stateful variables should be updated with a normal assignment/mutation, do not use methods to update them. However I can't verify if "count" is a state variable of an object or a class with a "${method}" method on it. Please verify that before updating the code to use a normal assignment`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe.each([{ property: '$' }])(
|
||||
'wrong_property_access_state property ($property)',
|
||||
async ({ property }) => {
|
||||
it(`should add suggestions when reading .${property} on a stateful variable with a literal init`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}(0);
|
||||
function read_count() {
|
||||
count.${property};
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when reading .${property} on a stateful variable with an array init`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}([1]);
|
||||
function read_count() {
|
||||
count.${property};
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them.`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}({}))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}({ value: 0 });
|
||||
function read_count() {
|
||||
count.${property};
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}(new Class()))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const count = ${init}(new Class());
|
||||
function read_count() {
|
||||
count.${property};
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when reading .${property} on a stateful variable with conditional if it's not sure if the property could actually be present on the variable (${init}(variable_name))`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { init } = $props();
|
||||
const count = ${init}(init);
|
||||
function read_count() {
|
||||
count.${property};
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are trying to read the stateful variable "count" using "${property}". stateful variables should be read just by accessing them like normal variable, do not use properties to read them. However I can't verify if "count" is a state variable of an object or a class with a "${property}" property on it. Please verify that before updating the code to use a normal access`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('imported_runes', () => {
|
||||
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.`,
|
||||
);
|
||||
});
|
||||
|
||||
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.`,
|
||||
);
|
||||
});
|
||||
|
||||
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.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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.`,
|
||||
);
|
||||
});
|
||||
|
||||
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.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each(dollarless_runes)('importing $rune from external lib', ({ rune }) => {
|
||||
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).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', () => {
|
||||
it(`should add suggestions when using a function as the first argument to $derived`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const value = $derived(() => {
|
||||
return 43;
|
||||
});
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived in classes`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
class Double {
|
||||
value = $derived(() => 43);
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived in classes constructors`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
class Double {
|
||||
value;
|
||||
|
||||
constructor(){
|
||||
this.value = $derived(function() { return 44; });
|
||||
}
|
||||
}
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived when declaring "value" but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived without the declaring part if it's not an identifier`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { destructured } = $derived(() => 43);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it(`should add suggestions when using a function as the first argument to $derived.by`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
const { destructured } = $derived.by(() => 43);
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'You are passing a function to $derived but $derived expects an expression. You can use $derived.by instead.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('use_runes_instead_of_store', () => {
|
||||
describe.each([{ import: 'derived' }, { import: 'writable' }, { import: 'readable' }])(
|
||||
'importing $import from svelte/store',
|
||||
({ import: imported }) => {
|
||||
it(`should add suggestions when importing '${imported}' from 'svelte/store'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { ${imported} } from 'svelte/store';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
`You are importing "${imported}" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it(`should not add suggestions when importing other identifiers from 'svelte/store'`, () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import { get } from 'svelte/store';
|
||||
</script>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
`You are importing "get" from "svelte/store". Unless the user specifically asked for stores or it's required because some library/component requires a store as input consider using runes like \`$state\` or \`$derived\` instead, all runes are globally available.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('suggest_attachments', () => {
|
||||
describe('bind:this', () => {
|
||||
it('should add suggestions when using bind:this on an element', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
let a = $state();
|
||||
</script>
|
||||
|
||||
<a bind:this={a} />`);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add suggestions when using bind:this on a component', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import Child from './Child.svelte';
|
||||
let a = $state();
|
||||
</script>
|
||||
|
||||
<Child bind:this={a} />`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add suggestions when using bind:this on a component nested in an element', () => {
|
||||
const content = run_autofixers_on_code(`
|
||||
<script>
|
||||
import Child from './Child.svelte';
|
||||
let a = $state();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Child bind:this={a} />
|
||||
</div>`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'The usage of `bind:this` can often be replaced with an easier to read `action` or even better an `attachment`. Consider using the latter if possible.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add suggestions but not suggest attachments when using bind:this on an element and the desired svelte version is 4', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`
|
||||
<script>
|
||||
let a;
|
||||
</script>
|
||||
|
||||
<a bind:this={a} />`,
|
||||
4,
|
||||
);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'The usage of `bind:this` can often be replaced with an easier to read `action`. Consider using the latter if possible.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('use:', () => {
|
||||
it('should add suggestions when using use: on an element and the action is declared as a function', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`<script>
|
||||
function my_action(node) {
|
||||
// do something with the node
|
||||
}
|
||||
</script>
|
||||
|
||||
<a use:my_action />`,
|
||||
);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add suggestions when using use: on an element and the action is declared as a variable', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`<script>
|
||||
const my_action = (node) => {
|
||||
// do something with the node
|
||||
}
|
||||
</script>
|
||||
|
||||
<a use:my_action />`,
|
||||
);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add suggestions when using use: on an element and the action is declared as an object', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`<script>
|
||||
const my_action = {
|
||||
action: (node) => {
|
||||
// do something with the node
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<a use:my_action.action />`,
|
||||
);
|
||||
|
||||
expect(content.suggestions.length).toBeGreaterThanOrEqual(1);
|
||||
expect(content.suggestions).toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add suggestions when using use: on an element and the desired svelte version is 4', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`<script>
|
||||
function my_action(node) {
|
||||
// do something with the node
|
||||
}
|
||||
</script>
|
||||
|
||||
<a use:my_action />`,
|
||||
4,
|
||||
);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add suggestions when using use: on an element and the action comes from an import', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`<script>
|
||||
import { my_action } from './actions.js';
|
||||
</script>
|
||||
|
||||
<a use:my_action />`,
|
||||
);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add suggestions when using use: on an element and the action comes from the props', () => {
|
||||
const content = run_autofixers_on_code(
|
||||
`<script>
|
||||
const { my_action } = $props();
|
||||
</script>
|
||||
|
||||
<a use:my_action />`,
|
||||
);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not add suggestions when using use: on an element and the action comes from a global variable', () => {
|
||||
const content = run_autofixers_on_code(`<a use:my_action />`);
|
||||
|
||||
expect(content.suggestions).not.toContain(
|
||||
'Consider using an `attachment` instead of an `action` for "my_action".',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('read_state_with_dollar', () => {
|
||||
with_possible_inits('($init)', ({ init }) => {
|
||||
it(`should add an issue when reading a stateful variable initialized with ${init} like if it was a store`, () => {
|
||||
const content = run_autofixers_on_code(`<script>
|
||||
let x = ${init}(()=> 43);
|
||||
$x;
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.issues).toContain(
|
||||
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should not add an issue when reading an imported variable like if it was a store`, () => {
|
||||
const content = run_autofixers_on_code(`<script>
|
||||
import { x } from "./my-stores.ts";
|
||||
$x;
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.issues).not.toContain(
|
||||
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should not add an issue when reading a non-stateful variable like if it was a store`, () => {
|
||||
const content = run_autofixers_on_code(`<script>
|
||||
import { writable } from "svelte/store";
|
||||
const x = writable(0);
|
||||
$x;
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.issues).not.toContain(
|
||||
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
|
||||
);
|
||||
});
|
||||
|
||||
it(`should not add an issue when reading a prop like if it was a store`, () => {
|
||||
const content = run_autofixers_on_code(`<script>
|
||||
const { x } = $props();
|
||||
$x;
|
||||
</script>
|
||||
`);
|
||||
|
||||
expect(content.issues).not.toContain(
|
||||
`You are reading the stateful variable "$x" with a "$" prefix. Stateful variables are not stores and should be read without the "$". Please read it as a normal variable "x"`,
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/sveltejs/ai-tools/issues/200
|
||||
it('should not crash when reading $-prefixed identifier for variable initialized with member expression', () => {
|
||||
expect(() =>
|
||||
run_autofixers_on_code(`<div>{$x}</div>
|
||||
|
||||
<script>
|
||||
const x = foo.bar;
|
||||
</script>`),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { parse } from '../../parse/parse.js';
|
||||
import { walk } from '../../index.js';
|
||||
import { walk } from '../../mcp/autofixers/ast/walk.js';
|
||||
import type { Node } from 'estree';
|
||||
import * as autofixers from './visitors/index.js';
|
||||
|
||||
@@ -8,6 +8,7 @@ export function add_autofixers_issues(
|
||||
code: string,
|
||||
desired_svelte_version: number,
|
||||
filename = 'Component.svelte',
|
||||
async = false,
|
||||
) {
|
||||
const parsed = parse(code, filename);
|
||||
|
||||
@@ -15,7 +16,7 @@ export function add_autofixers_issues(
|
||||
for (const autofixer of Object.values(autofixers)) {
|
||||
walk(
|
||||
parsed.ast as unknown as Node,
|
||||
{ output: content, parsed, desired_svelte_version },
|
||||
{ output: content, parsed, desired_svelte_version, async },
|
||||
autofixer,
|
||||
);
|
||||
}
|
||||
39
packages/mcp-server/src/mcp/autofixers/add-compile-issues.ts
Normal file
39
packages/mcp-server/src/mcp/autofixers/add-compile-issues.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { compile as compile_component, compileModule } from 'svelte/compiler';
|
||||
import { extname } from 'path';
|
||||
import ts from 'ts-blank-space';
|
||||
|
||||
export function add_compile_issues(
|
||||
content: { issues: string[]; suggestions: string[] },
|
||||
code: string,
|
||||
desired_svelte_version: number,
|
||||
filename = 'Component.svelte',
|
||||
async = false,
|
||||
) {
|
||||
let compile = compile_component;
|
||||
const extension = extname(filename);
|
||||
if (extension !== '.svelte') {
|
||||
compile = compileModule;
|
||||
// compile module doesn't accept .ts files so we need to transpile them first with ts-blank-space
|
||||
// a fast and lightweight typescript transpiler that can strips types replacing them with white spaces
|
||||
// so the code positions are not affected
|
||||
if (extension === '.ts') {
|
||||
code = ts(code, (node) => {
|
||||
content.issues.push(
|
||||
`The provided file is a module but it contains invalid TypeScript code: ${node.getText()} at ${node.getStart()}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
const compilation_result = compile(code, {
|
||||
filename: filename || 'Component.svelte',
|
||||
generate: false,
|
||||
runes: desired_svelte_version >= 5,
|
||||
experimental: { async },
|
||||
});
|
||||
|
||||
for (const warning of compilation_result.warnings) {
|
||||
content.issues.push(
|
||||
`${warning.message} at line ${warning.start?.line}, column ${warning.start?.column}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ESLint } from 'eslint';
|
||||
import svelte_parser from 'svelte-eslint-parser';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import type { Config } from '@sveltejs/kit';
|
||||
import ts from 'typescript-eslint';
|
||||
|
||||
let svelte_5_linter: ESLint | undefined;
|
||||
|
||||
@@ -11,7 +12,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
|
||||
return [
|
||||
...svelte.configs.recommended,
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
files: ['*.svelte', '*.svelte.ts', '*.svelte.js'],
|
||||
rules: {
|
||||
'no-self-assign': 'warn',
|
||||
'svelte/infinite-reactive-loop': 'warn',
|
||||
@@ -34,6 +35,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
|
||||
'svelte/prefer-writable-derived': 'warn',
|
||||
'svelte/require-event-dispatcher-types': 'warn',
|
||||
'svelte/require-store-reactive-access': 'warn',
|
||||
'svelte/no-inspect': 'off',
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
@@ -41,6 +43,8 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
|
||||
sourceType: 'module',
|
||||
parser: svelte_parser,
|
||||
parserOptions: {
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig: svelte_config,
|
||||
},
|
||||
},
|
||||
@@ -48,7 +52,7 @@ function base_config(svelte_config: Config): ESLint.Options['baseConfig'] {
|
||||
];
|
||||
}
|
||||
|
||||
function get_linter(version: number) {
|
||||
function get_linter(version: number, async = false) {
|
||||
if (version < 5) {
|
||||
return (svelte_4_linter ??= new ESLint({
|
||||
overrideConfigFile: true,
|
||||
@@ -64,6 +68,7 @@ function get_linter(version: number) {
|
||||
baseConfig: base_config({
|
||||
compilerOptions: {
|
||||
runes: true,
|
||||
experimental: { async },
|
||||
},
|
||||
}),
|
||||
}));
|
||||
@@ -74,11 +79,12 @@ export async function add_eslint_issues(
|
||||
code: string,
|
||||
desired_svelte_version: number,
|
||||
filename = 'Component.svelte',
|
||||
async = false,
|
||||
) {
|
||||
const eslint = get_linter(desired_svelte_version);
|
||||
const eslint = get_linter(desired_svelte_version, async);
|
||||
const results = await eslint.lintText(code, { filePath: filename || './Component.svelte' });
|
||||
|
||||
for (const message of results[0].messages) {
|
||||
for (const message of results[0]?.messages ?? []) {
|
||||
if (message.severity === 2) {
|
||||
content.issues.push(`${message.message} at line ${message.line}, column ${message.column}`);
|
||||
} else if (message.severity === 1) {
|
||||
@@ -0,0 +1,80 @@
|
||||
import type {
|
||||
AssignmentExpression,
|
||||
CallExpression,
|
||||
Identifier,
|
||||
Node,
|
||||
UpdateExpression,
|
||||
} from 'estree';
|
||||
import type { Autofixer, AutofixerState } from './index.js';
|
||||
import { left_most_id } from '../ast/utils.js';
|
||||
import type { AST } from 'svelte-eslint-parser';
|
||||
import type { Context } from 'zimmerframe';
|
||||
|
||||
function run_if_in_effect(
|
||||
path: (Node | AST.SvelteNode)[],
|
||||
state: AutofixerState,
|
||||
to_run: () => void,
|
||||
) {
|
||||
const in_effect = path.findLast(
|
||||
(node) =>
|
||||
node.type === 'CallExpression' && state.parsed.is_rune(node, ['$effect', '$effect.pre']),
|
||||
);
|
||||
|
||||
if (in_effect) {
|
||||
to_run();
|
||||
}
|
||||
}
|
||||
|
||||
function assign_or_update_visitor(
|
||||
node: UpdateExpression | AssignmentExpression,
|
||||
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
|
||||
) {
|
||||
run_if_in_effect(path, state, () => {
|
||||
function check_if_stateful_id(id: Identifier) {
|
||||
const reference = state.parsed.find_reference_by_id(id);
|
||||
const definition = reference?.resolved?.defs[0];
|
||||
if (definition && definition.type === 'Variable') {
|
||||
const init = definition.node.init;
|
||||
if (
|
||||
init?.type === 'CallExpression' &&
|
||||
state.parsed.is_rune(init, ['$state', '$state.raw', '$derived', '$derived.by'])
|
||||
) {
|
||||
state.output.suggestions.push(
|
||||
`The stateful variable "${id.name}" is assigned inside an $effect which is generally consider a malpractice. Consider using $derived if possible.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const variable = node.type === 'UpdateExpression' ? node.argument : node.left;
|
||||
|
||||
if (variable.type === 'Identifier') {
|
||||
check_if_stateful_id(variable);
|
||||
} else if (variable.type === 'MemberExpression') {
|
||||
const object = left_most_id(variable);
|
||||
if (object) {
|
||||
check_if_stateful_id(object);
|
||||
}
|
||||
}
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
function call_expression_visitor(
|
||||
node: CallExpression,
|
||||
{ state, path, next }: Context<Node | AST.SvelteNode, AutofixerState>,
|
||||
) {
|
||||
run_if_in_effect(path, state, () => {
|
||||
const function_name =
|
||||
node.callee.type === 'Identifier' ? `the function \`${node.callee.name}\`` : 'a function';
|
||||
state.output.suggestions.push(
|
||||
`You are calling ${function_name} inside an $effect. Please check if the function is reassigning a stateful variable because that's considered malpractice and check if it could use \`$derived\` instead. Ignore this suggestion if you are sure this function is not assigning any stateful variable or if you can't check if it does.`,
|
||||
);
|
||||
});
|
||||
next();
|
||||
}
|
||||
|
||||
export const assign_in_effect: Autofixer = {
|
||||
UpdateExpression: assign_or_update_visitor,
|
||||
AssignmentExpression: assign_or_update_visitor,
|
||||
CallExpression: call_expression_visitor,
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user