Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "stellar-dev",
"source": "./",
"description": "Skills for Stellar and Soroban development"
"description": "Skills for modern Stellar development"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "stellar-dev",
"description": "End-to-end Stellar development: Soroban smart contracts (Rust), stellar-sdk (JS/Python/Go), RPC/Horizon APIs, Stellar Assets, wallet integration, testing, security, and ecosystem",
"description": "End-to-end Stellar development: smart contracts (Rust, soroban-sdk), stellar-sdk (JS/Python/Go), RPC/Horizon APIs, Stellar Assets, wallet integration, testing, security, and ecosystem",
"version": "1.1.1",
"author": {
"name": "Stellar Development Foundation"
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ Inspired by [solana-foundation/solana-dev-skill](https://github.com/solana-found

This skill provides AI assistants with deep knowledge of the current Stellar development ecosystem:

- **Smart Contracts**: Soroban (Rust SDK, WebAssembly)
- **Smart Contracts**: Stellar smart contracts (Rust soroban-sdk, WebAssembly)
- **Client SDKs**: stellar-sdk (JavaScript), Python, Go, Rust
- **APIs**: Stellar RPC (preferred), Horizon (legacy)
- **Assets**: Stellar Assets, Stellar Asset Contract (SAC)
- **Wallets**: Freighter, Stellar Wallets Kit, Smart Accounts (passkeys)
- **Testing**: Local Quickstart, Testnet, Unit tests
- **Security**: Soroban-specific patterns, audit checklists
- **Security**: Smart contract security patterns, audit checklists
- **Ecosystem**: DeFi protocols, developer tools, community projects

## Installing
Expand Down Expand Up @@ -65,24 +65,24 @@ Copy the `skills/` directory contents to your assistant's skills location.

```
skills/
├── soroban/SKILL.md # Soroban contracts + testing + security + patterns + pitfalls
├── smart-contracts/ # Stellar smart contracts — SKILL.md entry + development/testing/security files
├── dapp/SKILL.md # Frontend, wallets (Freighter, Wallets Kit), signing, smart accounts
├── assets/SKILL.md # Stellar Assets, trustlines, SAC bridge
├── data/SKILL.md # Stellar RPC (preferred) + Horizon (legacy), indexing
├── agentic-payments/SKILL.md # x402 + MPP (Charge + Channel) for AI/machine payments
├── zk-proofs/SKILL.md # ZK verification, BLS12-381, BN254/Poseidon (status-sensitive)
├── zk-proofs/SKILL.md # ZK verification (BLS12-381 Groth16), Circom/Noir/RISC Zero walkthroughs
└── standards/SKILL.md # SEPs, CAPs, ecosystem projects, curated reference links
```

Each sub-skill is a self-contained Agent Skill with its own frontmatter. Cross-references link related skills (e.g., the `agentic-payments` skill points to `soroban` for the Soroban SACs the protocols call, and to `assets` for USDC). The AI reads only the sub-skills relevant to the task at hand.
Each sub-skill is a self-contained Agent Skill with its own frontmatter. Cross-references link related skills (e.g., the `agentic-payments` skill points to `smart-contracts` for the SACs the protocols call, and to `assets` for USDC). The AI reads only the sub-skills relevant to the task at hand.

## Example Prompts

```
"Help me write a Soroban smart contract for a token"
"Help me write a Stellar smart contract for a token"
"Set up a Next.js app with Freighter wallet connection"
"How do I deploy a contract to Stellar Testnet?"
"Create unit tests for my Soroban contract"
"Create unit tests for my smart contract"
"Review this contract for security issues"
```

Expand Down
2 changes: 1 addition & 1 deletion site/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ then append to `SKILL_CARD_SOURCES`:
```ts
{
source: "skills/<your-skill>/SKILL.md",
category: "Soroban", // any FilterType value
category: "Smart Contracts", // any FilterType value
// Optional overrides — both default to the upstream SKILL.md's
// first H1 (title) and frontmatter `description`.
title: "Your Skill Title",
Expand Down
2 changes: 1 addition & 1 deletion site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pnpm generate:llms-txt # regenerate public/llms.txt

The site lives in `site/` alongside `skills/` at the repo root. Each
entry in `src/data/skills.ts` has a `source` field pointing at an
upstream path (e.g. `skills/soroban/SKILL.md`).
upstream path (e.g. `skills/smart-contracts/SKILL.md`).
`scripts/copy-skills.mjs` mirrors that path verbatim into `public/`
on `predev` / `prebuild`. There's no network fetch and no upstream ref
to pin: the site always reflects whatever's on disk in `../skills/`,
Expand Down
47 changes: 36 additions & 11 deletions site/scripts/copy-skills.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*
* The site lives at `<repo-root>/site/` alongside `<repo-root>/skills/`.
* Each card in src/data/skills.ts has a `source` field — an
* upstream-relative path like "skills/soroban/SKILL.md" — that we copy
* upstream-relative path like "skills/smart-contracts/SKILL.md" — that we copy
* verbatim into public/, so the upstream layout drives the site URL.
*
* Flags:
Expand All @@ -18,6 +18,7 @@ import {
cpSync,
existsSync,
mkdirSync,
readdirSync,
readFileSync,
rmSync,
statSync,
Expand Down Expand Up @@ -55,20 +56,40 @@ if (sources.length === 0) {
process.exit(1);
}

// `--cached` skips the copy when every dest exists AND every dest is at
// least as fresh as its upstream source. Without the mtime check, editing
// a SKILL.md during `pnpm dev` wouldn't show up until the next manual
// `pnpm sync:skills`.
// Returns the newest mtime (ms) among all files under `dir`, or 0 if the
// directory does not exist / is empty.
const newestMtimeInDir = (dir) => {
if (!existsSync(dir)) return 0;
let newest = 0;
const scan = (d) => {
for (const entry of readdirSync(d, { withFileTypes: true })) {
const fullPath = join(d, entry.name);
if (entry.isDirectory()) {
scan(fullPath);
} else {
const mtime = statSync(fullPath).mtimeMs;
if (mtime > newest) newest = mtime;
}
}
};
scan(dir);
return newest;
};

// `--cached` skips the copy when every dest skill directory exists AND its
// newest file is at least as fresh as the newest file in the upstream source
// directory. Comparing directories (not just SKILL.md) ensures that edits to
// companion files like testing.md also trigger a refresh during `pnpm dev`.
const isFresh = (source) => {
const dest = join(PUBLIC_DIR, source);
if (!existsSync(dest)) return false;
const src = join(REPO_ROOT, source);
if (!existsSync(src)) {
const srcDir = dirname(join(REPO_ROOT, source));
const destDir = dirname(join(PUBLIC_DIR, source));
if (!existsSync(destDir)) return false;
if (!existsSync(srcDir)) {
// Upstream missing; --lenient will warn later. Treat as fresh so we
// don't trigger a full re-copy just to discover the same missing file.
return true;
}
return statSync(dest).mtimeMs >= statSync(src).mtimeMs;
return newestMtimeInDir(destDir) >= newestMtimeInDir(srcDir);
};

if (cached && sources.every(isFresh)) {
Expand All @@ -89,8 +110,12 @@ for (const source of sources) {
missing.push(source);
continue;
}
// Copy the whole skill directory, not just the SKILL.md: skills may
// split deep-dive content into companion files (e.g.
// skills/smart-contracts/development.md) referenced by relative links, and
// those must be served at the same paths.
mkdirSync(dirname(dest), { recursive: true });
cpSync(src, dest, { dereference: false });
cpSync(dirname(src), dirname(dest), { recursive: true, dereference: false });
}
Comment on lines +113 to 119

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit. isFresh now uses a newestMtimeInDir helper that recursively scans the skill directory and compares the newest mtime across all files (src vs dest), so edits to companion files like testing.md will correctly invalidate the cached check and trigger a re-copy during pnpm dev.


// Apache-2.0 attribution alongside the content.
Expand Down
19 changes: 16 additions & 3 deletions site/scripts/generate-llms-txt.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* this file). The page (src/app/page.tsx) and this script therefore
* share one source of truth (no regex parsing, no second copy).
*/
import { writeFileSync } from "node:fs";
import { existsSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

Expand Down Expand Up @@ -82,10 +82,10 @@ lines.push("");
lines.push("Use Stellar Skills to help when your human asks things like:");
lines.push("");
for (const prompt of [
"Help me write a Soroban smart contract for a token",
"Help me write a Stellar smart contract for a token",
"Set up a Next.js app with Freighter wallet connection",
"How do I deploy a contract to Stellar Testnet?",
"Create unit tests for my Soroban contract",
"Create unit tests for my smart contract",
"Review this contract for security issues",
]) {
lines.push(`- "${prompt}"`);
Expand Down Expand Up @@ -125,6 +125,19 @@ for (const filter of filters) {
for (const c of cards) {
if (!c.path) continue;
lines.push(`- [${c.title}](${ORIGIN}${c.path}): ${c.description}`);
// Companion markdown files in the same skill directory (e.g.
// skills/smart-contracts/development.md) are part of the skill — index them
// as nested entries so agents can fetch them directly.
const dir = dirname(c.source);
const absDir = join(ROOT, "public", dir);
if (!existsSync(absDir)) continue;
const companions = readdirSync(absDir)
.filter((f) => f.endsWith(".md") && f !== "SKILL.md")
.sort();
for (const f of companions) {
const meta = readSkillMeta(`${dir}/${f}`);
lines.push(` - [${meta.title ?? f}](${ORIGIN}/${dir}/${f})`);
}
}
lines.push("");
}
Expand Down
2 changes: 1 addition & 1 deletion site/src/app/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@
}

@each $filter
in (Soroban, "Agentic Payments", Frontend, Assets, APIs, ZK, Ecosystem)
in ("Smart Contracts", "Agentic Payments", Frontend, Assets, APIs, ZK, Ecosystem)
{
.SkillsLanding__filterPanel[data-active-filter="#{$filter}"]
.SkillsLanding__filterItem[data-category="#{$filter}"] {
Expand Down
18 changes: 9 additions & 9 deletions site/src/data/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
type FilterType =
| "All"
| "Agentic Payments"
| "Soroban"
| "Smart Contracts"
| "Frontend"
| "Assets"
| "APIs"
Expand All @@ -30,7 +30,7 @@ type FilterType =
*/
export const FILTERS: readonly FilterType[] = [
"All",
"Soroban",
"Smart Contracts",
"Agentic Payments",
"Frontend",
"Assets",
Expand All @@ -46,7 +46,7 @@ export const FILTERS: readonly FilterType[] = [
* to upstream metadata when they're omitted.
*/
export type SkillCardSource = {
/** Upstream path, e.g. "skills/soroban/SKILL.md". Forms both the
/** Upstream path, e.g. "skills/smart-contracts/SKILL.md". Forms both the
* fetch source and the site URL ("/" + source). */
source: string;
category: FilterType;
Expand Down Expand Up @@ -80,9 +80,9 @@ export type EcosystemCardSource = {
*/
export const SKILL_CARD_SOURCES: readonly SkillCardSource[] = [
{
source: "skills/soroban/SKILL.md",
category: "Soroban",
title: "Soroban Smart Contracts",
source: "skills/smart-contracts/SKILL.md",
category: "Smart Contracts",
title: "Stellar Smart Contracts",
description:
"Write, test, secure, and ship Rust smart contracts on Stellar. Covers patterns, pitfalls, and architecture.",
},
Expand All @@ -98,7 +98,7 @@ export const SKILL_CARD_SOURCES: readonly SkillCardSource[] = [
category: "Assets",
title: "Stellar Assets & SAC",
description:
"Issue and manage classic Stellar assets and trustlines, with the SAC bridge for Soroban interop.",
"Issue and manage classic Stellar assets and trustlines, with the SAC bridge for smart contract interop.",
},
{
source: "skills/data/SKILL.md",
Expand All @@ -119,7 +119,7 @@ export const SKILL_CARD_SOURCES: readonly SkillCardSource[] = [
category: "ZK",
title: "ZK Proofs",
description:
"Verify Groth16 zero-knowledge proofs on Stellar using BLS12-381, BN254, and Poseidon primitives.",
"Verify Groth16 proofs on-chain via BLS12-381, with Circom, Noir, and RISC Zero toolchain walkthroughs.",
},
{
source: "skills/standards/SKILL.md",
Expand All @@ -138,7 +138,7 @@ export const ECOSYSTEM_CARDS: readonly EcosystemCardSource[] = [
{
title: "OpenZeppelin Contracts",
description:
"Scaffold a Soroban project with OpenZeppelin's audited Stellar contract libraries. Walks through Rust toolchain setup, Stellar CLI install, workspace dependencies, and applying the pausable and ownable macros to your contract.",
"Scaffold a Stellar smart contract project with OpenZeppelin's audited Stellar contract libraries. Walks through Rust toolchain setup, Stellar CLI install, workspace dependencies, and applying the pausable and ownable macros to your contract.",
pathLabel: "OpenZeppelin/openzeppelin-skills",
copyValue:
"https://github.com/OpenZeppelin/openzeppelin-skills/blob/main/skills/setup-stellar-contracts/SKILL.md",
Expand Down
2 changes: 1 addition & 1 deletion site/src/lib/skill-meta.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const firstH1 = (body) => {
};

/**
* @param {string} source - upstream path like "skills/soroban/SKILL.md"
* @param {string} source - upstream path like "skills/smart-contracts/SKILL.md"
* @returns {SkillMeta}
*/
export const readSkillMeta = (source) => {
Expand Down
Loading
Loading