Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

developer-stack-skills

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

developer-stack-skills - npm Package Compare versions

Comparing version
1.2.1
to
2.0.0
+9
.claude/rules/developer-stack-skills-frontend.md
---
description: Frontend — React, Angular, TypeScript, TanStack Query, Vitest, Playwright
globs: ["**/*.tsx","**/*.jsx","**/*.ts","**/*.js","**/*.vue","**/*.svelte"]
alwaysApply: false
---
Load and follow this skill file:
- ./frontend/SKILL.md
---
description: Java & Spring Boot — Spring Boot 3, JPA, REST APIs, JUnit 5, Maven/Gradle
globs: ["**/*.java","**/*.kt","**/pom.xml","**/build.gradle","**/build.gradle.kts"]
alwaysApply: false
---
Load and follow this skill file:
- ./java-spring/SKILL.md
---
description: Project conventions — Git, commits, PRs, ADRs, naming, environment config
alwaysApply: true
---
Load and follow this skill file:
- ./project-conventions/SKILL.md
---
description: Python backend — FastAPI, Django, SQLAlchemy, Pydantic, pytest
globs: ["**/*.py","**/requirements*.txt","**/pyproject.toml","**/setup.py","**/Pipfile"]
alwaysApply: false
---
Load and follow this skill file:
- ./python-backend/SKILL.md
---
description: Testing — JUnit 5, pytest, Vitest, Testing Library, Playwright, Testcontainers
globs: ["**/*.test.ts","**/*.test.tsx","**/*.test.js","**/*.test.jsx","**/*.spec.ts","**/*.spec.js","**/*.spec.jsx","**/test/**","**/tests/**","**/__tests__/**"]
alwaysApply: false
---
Load and follow this skill file:
- ./testing/SKILL.md
---
description: Add REST API endpoint following stack and REST conventions
argument-hint: [METHOD /path description]
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
---
Add endpoint: $ARGUMENTS
1. Detect stack:
- `pom.xml` or `build.gradle` → Java/Spring Boot (@RestController pattern)
- `pyproject.toml` or `requirements.txt` → Python/FastAPI (APIRouter pattern)
- `package.json` without pom.xml/build.gradle → TypeScript/Node.js (Express/NestJS/Fastify — follow existing router pattern in project)
- If no stack detected, state so and ask user to specify framework before proceeding.
2. Read existing endpoints in the project to understand current patterns, naming, and error handling before writing anything.
3. Design the endpoint following REST conventions:
- Correct HTTP method: GET (read), POST (create → 201), PUT/PATCH (update → 200), DELETE (204)
- Request/response DTOs: Java records with `@Valid`, Pydantic models with field validators
- Input validated at controller/router layer — never in service layer
4. Implement in layers — do not skip:
- Controller/Router: thin, delegates all logic to service
- Service: business logic, throws domain-specific exceptions (never generic ones)
- Repository: data access only (create if DB interaction needed)
5. Write integration test covering:
- Happy path (correct status and response body)
- Validation failure (400 with meaningful error message)
- Not-found case (404) if endpoint accepts an ID
6. Check if OpenAPI/Swagger docs are auto-generated or manual. Update if manual.
7. Run existing tests to confirm no regressions.
---
description: Audit dependencies for outdated versions and known vulnerabilities
allowed-tools: Bash, Read, Glob
---
Audit all project dependencies for outdated versions and security vulnerabilities.
Detect package managers from project root files:
- `package.json` → npm / yarn / pnpm / bun
- `pyproject.toml` or `requirements*.txt` → pip / uv / poetry
- `pom.xml` → Maven
- `build.gradle` or `build.gradle.kts` → Gradle
For each detected package manager, run the appropriate audit command and check for:
1. **Security vulnerabilities** — packages with known CVEs (highest priority)
2. **Major version updates** — breaking changes available
3. **Minor/patch updates** — non-breaking updates available
4. **Deprecated packages** — no longer maintained, replacement needed
Audit commands by ecosystem:
- npm: `npm audit --json`
- yarn: `yarn audit --json`
- pnpm: `pnpm audit --json`
- pip/uv: `pip-audit` or `safety check` if available, otherwise `pip list --outdated`
- poetry: `poetry show --outdated`
- Maven: `mvn versions:display-dependency-updates`
- Gradle: `gradle dependencyUpdates` (if plugin present) or check `build.gradle` for version declarations
Report findings as a prioritized table:
| Package | Current | Latest | Severity | Action |
|---------|---------|--------|----------|--------|
Suggest the exact update command(s) for each package manager found. Flag any package where upgrading requires migration steps. Do not update anything without explicit confirmation.
---
description: Implement feature following project stack conventions
argument-hint: [feature-description]
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
---
Implement: $ARGUMENTS
1. Detect project stack by checking root files:
- `pom.xml` or `build.gradle` → Java/Spring Boot
- `pyproject.toml`, `requirements.txt`, or `setup.py` → Python/FastAPI
- `package.json` → TypeScript/JavaScript (React or Angular)
2. Read existing code to understand current architecture, naming conventions, and patterns before writing anything.
3. State assumptions and create a concise implementation plan. List files to create or modify.
4. Implement following the skill conventions already loaded for this stack:
- Java: thin controllers, business logic in service layer, DTOs via records, constructor injection only
- Python: Pydantic models for I/O, async handlers, pydantic-settings for config
- TypeScript: functional components, TanStack Query for server state, no fetch inside components
5. Write tests alongside implementation:
- Unit test for service/business logic
- Integration test for any new API endpoint
- Arrange-Act-Assert structure, one concept per test
6. After implementing, list all files changed and confirm the implementation compiles or type-checks.
---
description: Review current branch changes against project conventions
allowed-tools: Bash(git:*), Read, Grep
---
Review changes in the current branch.
First, detect the repository's default branch by running: !`git remote show origin`
Then get the changed files and diff summary against that branch. Use `git diff <default-branch>...HEAD --name-only` and `git diff <default-branch>...HEAD --stat`. If the upstream tracking branch is set, prefer `git diff @{upstream}...HEAD`.
For each changed file review against:
**Code quality:**
- Follows stack conventions (layering, naming, injection patterns)
- No business logic leaked into wrong layer (controller, component)
- Error handling present and specific — no bare `catch (Exception e)` or bare `except:`
- No `any` in TypeScript, no mutable default args in Python
**Testing:**
- Tests added or updated for every behavior change
- At least one happy path and one failure path per change
- Tests assert behaviour, not implementation details
**project-conventions checklist:**
- No hardcoded secrets or credentials
- No `TODO` without a ticket ID
- No commented-out code
- PR is under 400 lines (warn if larger)
- Commit messages follow Conventional Commits format
Report findings grouped by: **Critical** (must fix) / **Warning** (should fix) / **Suggestion** (nice to have). Include file and line references.
---
description: Write tests for a file or class following testing conventions
argument-hint: [file-or-class]
allowed-tools: Read, Write, Edit, Bash, Grep, Glob
---
Write tests for: $ARGUMENTS
1. Read the target file thoroughly to understand all public methods, edge cases, and error paths.
2. Detect test framework from project:
- Java → JUnit 5 + Mockito (check pom.xml or build.gradle)
- Python → pytest (check pyproject.toml)
- TypeScript/React → Vitest + Testing Library
- Angular → Jasmine + Karma
3. For each public method or exported function, write:
- At least one happy path test
- At least one failure or edge case test
- Test behaviour not implementation — assert on outputs and side effects, not internal calls
4. Follow Arrange-Act-Assert structure. Separate each section with a blank line.
5. Mock only external dependencies (DB sessions, HTTP clients, filesystem). Never mock your own code.
6. Use descriptive test names: `method_WhenCondition_ExpectedResult` (Java) or `test_does_x_when_y` (Python) or `should do X when Y` (TypeScript).
7. Place test file in the correct location per project structure. Run existing tests to confirm nothing broke.
# Developer Stack Skills
Load and follow these skill files before starting work:
- node_modules/developer-stack-skills/java-spring/SKILL.md
- node_modules/developer-stack-skills/testing/SKILL.md
- node_modules/developer-stack-skills/project-conventions/SKILL.md
#!/usr/bin/env node
// Matches package install commands across supported ecosystems
const INSTALL_PATTERN = /\b(?:pip3?\s+install|uv\s+(?:pip\s+install|add)|poetry\s+add|npm\s+(?:install|i)\b|yarn\s+add|pnpm\s+add|bun\s+add|npx\s+\S+@(?:latest|\d))/i;
const chunks = [];
process.stdin.on("data", (chunk) => chunks.push(chunk));
process.stdin.on("end", () => {
let input = {};
try { input = JSON.parse(Buffer.concat(chunks).toString()); } catch {}
const command = input.tool_input?.command || "";
if (INSTALL_PATTERN.test(command)) {
process.stdout.write(JSON.stringify({
continue: true,
systemMessage: "[developer-stack-skills] freshdeps: Verify this is the latest stable version before installing. Check for known vulnerabilities or deprecated releases.",
}));
}
process.exit(0);
});
#!/usr/bin/env node
const path = require("path");
const REMINDERS = {
java: "java-spring: Constructor injection only. DTOs via Java records. FetchType.LAZY for all JPA associations. @ControllerAdvice for exceptions. No business logic in controllers.",
kotlin: "java-spring (Kotlin): Constructor injection only. Data classes for DTOs. @ControllerAdvice for exceptions. No business logic in controllers.",
python: "python-backend: Pydantic models for all I/O. Async handlers in FastAPI. Never hardcode secrets — use pydantic-settings. pytest with fixtures, not unittest.",
angular: "frontend (Angular): Standalone components + OnPush. Signals for reactive state. Return Observable from services — never subscribe inside services.",
frontend: "frontend: Functional components. Never use `any` in TypeScript. TanStack Query for server state. No useEffect for data fetching. Never fetch directly in components.",
test: "testing: Arrange-Act-Assert. One concept per test. Test behaviour not implementation. Mock only external deps (DB, HTTP) — not your own code.",
config: "project-conventions: Never commit secrets. .env.example must list all required keys (values redacted). Use pydantic-settings or Spring @Value for env vars.",
sql: "project-conventions: SQL migrations → timestamp prefix: YYYYMMDD_description.sql. Never modify existing migrations — add a new one instead.",
};
function getReminder(filePath) {
const name = path.basename(filePath);
// Test files — checked first so test files don't get source reminder
const isJavaTest = /Tests?\.java$/.test(name) || /IT\.java$/.test(name) || /ITest\.java$/.test(name);
const isPythonTest = /^test_/.test(name) || /_test\.py$/.test(name);
const isGenericTest = /\.(test|spec)\.(ts|tsx|js|jsx|py|java)$/.test(name);
if (isJavaTest || isPythonTest || isGenericTest) return REMINDERS.test;
// Source files by extension
if (/\.java$/.test(name)) return REMINDERS.java;
if (/\.kt$/.test(name)) return REMINDERS.kotlin;
if (/\.py$/.test(name)) return REMINDERS.python;
// Angular-specific TypeScript files before generic TS catch-all
if (/\.(component|service|module|guard|pipe|interceptor|directive|resolver)\.ts$/.test(name)) return REMINDERS.angular;
if (/\.(ts|tsx|js|jsx)$/.test(name)) return REMINDERS.frontend;
// Config / secrets — skip .env.example (safe template)
if (/^\.env(\.|$)/.test(name) && !name.endsWith(".example")) return REMINDERS.config;
// SQL migrations
if (/\.sql$/.test(name)) return REMINDERS.sql;
return null;
}
const chunks = [];
process.stdin.on("data", (chunk) => chunks.push(chunk));
process.stdin.on("end", () => {
let input = {};
try { input = JSON.parse(Buffer.concat(chunks).toString()); } catch {}
const filePath = input.tool_input?.file_path || "";
const reminder = getReminder(filePath);
if (reminder) {
process.stdout.write(JSON.stringify({
continue: true,
systemMessage: `[developer-stack-skills] ${reminder}`,
}));
}
process.exit(0);
});
const path = require("path");
const fsp = require("fs/promises");
const PACKAGE_NAME = "developer-stack-skills";
const SKILL_META = {
"java-spring": {
description: "Java & Spring Boot 3 — JPA, REST APIs, JUnit 5, Mockito, Maven/Gradle",
globs: ["**/*.java", "**/*.kt", "**/pom.xml", "**/build.gradle", "**/build.gradle.kts"],
},
"python-backend": {
description: "Python backend — FastAPI, Django, SQLAlchemy 2.x, Pydantic v2, pytest",
globs: ["**/*.py", "**/requirements*.txt", "**/pyproject.toml", "**/setup.py", "**/Pipfile"],
},
frontend: {
description: "Frontend — React 18+, Angular 17+, TypeScript, TanStack Query, Vitest, Playwright",
globs: ["**/*.tsx", "**/*.jsx", "**/*.ts", "**/*.js", "**/*.vue", "**/*.svelte", "**/package.json"],
},
testing: {
description: "Testing — JUnit 5, pytest, Vitest, Testing Library, Playwright, Testcontainers",
globs: ["**/*.test.*", "**/*.spec.*", "**/test/**", "**/tests/**", "**/__tests__/**"],
},
"project-conventions": {
description: "Project conventions — Git flow, Conventional Commits, PR process, ADRs, naming, env config",
globs: [],
},
};
const SKILL_NAMES = Object.keys(SKILL_META);
const TOOLS = [
{
name: "list_available_skills",
description: "List all developer stack skills with descriptions and applicable file patterns.",
inputSchema: { type: "object", properties: {}, required: [] },
annotations: { readOnlyHint: true },
},
{
name: "get_skill",
description: "Get full SKILL.md content for a technology stack. Load this before writing code in that stack.",
inputSchema: {
type: "object",
properties: {
stack_name: {
type: "string",
enum: SKILL_NAMES,
description: "Stack to retrieve: java-spring, python-backend, frontend, testing, or project-conventions.",
},
},
required: ["stack_name"],
},
annotations: { readOnlyHint: true },
},
{
name: "get_conventions",
description: "Get project-wide conventions: Git branching, Conventional Commits, PR process, naming rules, ADRs, env config.",
inputSchema: { type: "object", properties: {}, required: [] },
annotations: { readOnlyHint: true },
},
{
name: "detect_stack",
description: "Detect the recommended skill to load from a file path. Returns the skill name and a ready-to-use get_skill call.",
inputSchema: {
type: "object",
properties: {
file_path: {
type: "string",
description: "File path to analyze, e.g. src/UserService.java or app/routes/users.py.",
},
},
required: ["file_path"],
},
annotations: { readOnlyHint: true },
},
];
function getPackageRoot() {
return path.resolve(__dirname, "..");
}
function getVersion() {
return require(path.join(getPackageRoot(), "package.json")).version;
}
async function readSkillFile(skillName) {
const skillPath = path.join(getPackageRoot(), skillName, "SKILL.md");
try {
return await fsp.readFile(skillPath, "utf8");
} catch {
return null;
}
}
function detectStack(filePath) {
const name = path.basename(filePath);
const isTestFile =
/\.(test|spec)\.(java|py|ts|tsx|js|jsx)$/.test(name) ||
/Tests?\.java$/.test(name) ||
/IT\.java$/.test(name) ||
/^test_/.test(name) ||
/_test\.py$/.test(name);
if (isTestFile) return "testing";
if (/\.(java|kt)$/.test(name) || /^(pom\.xml|build\.gradle(\.kts)?)$/.test(name)) return "java-spring";
if (/\.py$/.test(name) || /^(pyproject\.toml|requirements.*\.txt|setup\.py|Pipfile)$/.test(name)) return "python-backend";
if (/\.(component|service|module|guard|pipe|directive|interceptor|resolver)\.ts$/.test(name)) return "frontend";
if (/\.(tsx|jsx|ts|js|vue|svelte)$/.test(name) || /^package\.json$/.test(name)) return "frontend";
return "project-conventions";
}
async function handleTool(name, args) {
if (name === "list_available_skills") {
const skills = SKILL_NAMES.map((skillName) => ({
name: skillName,
description: SKILL_META[skillName].description,
applies_to: SKILL_META[skillName].globs.slice(0, 3).join(", ") || "always",
}));
return { content: [{ type: "text", text: JSON.stringify(skills, null, 2) }] };
}
if (name === "get_skill") {
const { stack_name } = args;
if (!SKILL_META[stack_name]) {
return {
content: [{ type: "text", text: `Unknown skill: ${stack_name}. Available: ${SKILL_NAMES.join(", ")}` }],
isError: true,
};
}
const content = await readSkillFile(stack_name);
if (!content) {
return {
content: [{ type: "text", text: `Skill file not found: ${stack_name}` }],
isError: true,
};
}
return { content: [{ type: "text", text: content }] };
}
if (name === "get_conventions") {
const content = await readSkillFile("project-conventions");
if (!content) {
return {
content: [{ type: "text", text: "project-conventions skill file not found." }],
isError: true,
};
}
return { content: [{ type: "text", text: content }] };
}
if (name === "detect_stack") {
const { file_path } = args;
const stack = detectStack(file_path);
const meta = SKILL_META[stack];
return {
content: [{
type: "text",
text: JSON.stringify({
file_path,
recommended_skill: stack,
description: meta.description,
next_step: `Call get_skill with stack_name: "${stack}"`,
}, null, 2),
}],
};
}
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
}
async function runMcpServer() {
// Lazy-load SDK so pure functions (detectStack, etc.) work without it installed
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
const { ListToolsRequestSchema, CallToolRequestSchema } = require("@modelcontextprotocol/sdk/types.js");
const server = new Server(
{ name: PACKAGE_NAME, version: getVersion() },
{
capabilities: { tools: {} },
instructions: "Use list_available_skills first to discover what stacks are available. Use detect_stack to identify which skill applies to a specific file. Use get_skill to load full conventions before writing code.",
},
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args = {} } = request.params;
return handleTool(name, args);
});
const transport = new StdioServerTransport();
await server.connect(transport);
process.stderr.write(`[${PACKAGE_NAME}] MCP server started (stdio)\n`);
}
module.exports = { runMcpServer, detectStack, SKILL_META, SKILL_NAMES };
+6
-0

@@ -24,2 +24,8 @@ #!/usr/bin/env node

if (args.command === "serve") {
const { runMcpServer } = require("../lib/mcp-server");
await runMcpServer();
return;
}
if (["version", "--version", "-v"].includes(args.command)) {

@@ -26,0 +32,0 @@ printVersion();

# Changelog
## 2.0.0 - 2026-05-16
Added:
- MCP server (`developer-stack-skills serve`) with four tools: `list_available_skills`, `get_skill`, `get_conventions`, `detect_stack`
- `serve` command in CLI to start MCP server over stdio
- Claude Code slash commands (`commands/`): `implement-feature`, `write-tests`, `review-pr`, `check-deps`, `add-endpoint`
- Claude Code hooks (`hooks/`): `pre-write.js` injects per-stack reminders on file writes; `pre-bash.js` warns on package install commands
- `.claude/rules/` per-stack rule files for automatic skill loading in Claude Code
- Cursor rules split into per-stack `.mdc` files: `developer-stack-skills-frontend.mdc`, `developer-stack-skills-java-spring.mdc`, `developer-stack-skills-python-backend.mdc`, `developer-stack-skills-project-conventions.mdc`, `developer-stack-skills-testing.mdc`
- Roocode migrated from `.roo/config.yml` to `.roo/rules/developer-stack-skills.md`
- `.gitignore` added to package
Changed:
- `@modelcontextprotocol/sdk` promoted to runtime dependency (was implicit dev dep)
- `files` in `package.json` now includes `commands/` and `hooks/`
Removed:
- `.roo/config.yml` replaced by `.roo/rules/developer-stack-skills.md`
- `.cursor/rules/developer-stack-skills.mdc` (single file) replaced by per-stack `.mdc` files
Notes:
- MCP server exposes all five skills as tools — agents can call `detect_stack` with a file path and get back which skill to load
- Hooks require Claude Code; Cursor and Copilot integration remains config-file based
- All MCP tools are read-only (`readOnlyHint: true`)
## 1.2.1 - 2026-05-15

@@ -15,2 +44,3 @@

- README now documents explicit post-install configuration flow, `--foreground-scripts` requirement for visible npm lifecycle prompts, and updated command examples
- README now clarifies why global install still asks for project directory and how global vs local install differ

@@ -17,0 +47,0 @@ Notes:

+2
-0

@@ -0,1 +1,2 @@

<!-- developer-stack-skills:start -->
Follow these skill files before producing code or process guidance:

@@ -6,1 +7,2 @@

- node_modules/developer-stack-skills/project-conventions/SKILL.md
<!-- developer-stack-skills:end -->

@@ -12,2 +12,3 @@ const fsp = require("fs/promises");

const MODES = ["copy", "symlink"];
const HOOKS_DIR = "hooks";

@@ -22,2 +23,36 @@ const SKILLS = [

const CONVENTIONS_RULE_CONFIG = {
skillName: "project-conventions",
description: "Project conventions — Git, commits, PRs, ADRs, naming, environment config",
globs: [],
alwaysApply: true,
};
const RULE_CONFIGS = [
{
skillName: "java-spring",
description: "Java & Spring Boot — Spring Boot 3, JPA, REST APIs, JUnit 5, Maven/Gradle",
globs: ["**/*.java", "**/*.kt", "**/pom.xml", "**/build.gradle", "**/build.gradle.kts"],
},
{
skillName: "python-backend",
description: "Python backend — FastAPI, Django, SQLAlchemy, Pydantic, pytest",
globs: ["**/*.py", "**/requirements*.txt", "**/pyproject.toml", "**/setup.py", "**/Pipfile"],
},
{
skillName: "frontend",
description: "Frontend — React, Angular, TypeScript, TanStack Query, Vitest, Playwright",
globs: ["**/*.tsx", "**/*.jsx", "**/*.ts", "**/*.js", "**/*.vue", "**/*.svelte"],
},
{
skillName: "testing",
description: "Testing — JUnit 5, pytest, Vitest, Testing Library, Playwright, Testcontainers",
globs: [
"**/*.test.ts", "**/*.test.tsx", "**/*.test.js", "**/*.test.jsx",
"**/*.spec.ts", "**/*.spec.js", "**/*.spec.jsx",
"**/test/**", "**/tests/**", "**/__tests__/**",
],
},
];
function detectPlatform() {

@@ -130,2 +165,3 @@ switch (process.platform) {

console.log(" uninstall remove installed skills and agent config entries");
console.log(" serve start MCP server on stdio");
console.log(" version print package version");

@@ -274,2 +310,20 @@ console.log(" help print this help");

function getHooksDestPath(installRoot) {
return path.join(installRoot, HOOKS_DIR);
}
function buildHookCommand(hooksDir, scriptName) {
return `node ${JSON.stringify(path.join(hooksDir, scriptName))}`;
}
function isOurHookEntry(entry) {
return (entry.hooks || []).some(
(h) => typeof h.command === "string" && h.command.includes(PACKAGE_NAME),
);
}
function removeOurHookEntries(hookArray) {
return (hookArray || []).filter((entry) => !isOurHookEntry(entry));
}
function quoteYamlString(value) {

@@ -415,13 +469,27 @@ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;

async function configureClaude(projectDir, skillPaths, dryRun = false) {
async function configureClaude(projectDir, conventionsPath, context, dryRun = false) {
const filePath = path.join(projectDir, "CLAUDE.md");
const current = await readIfExists(filePath);
const body = [
"Load these skill files before starting work:",
const lines = [];
if (context && (context.description || context.testCmd || context.buildCmd)) {
lines.push("## Project context", "");
if (context.description) lines.push(context.description, "");
if (context.testCmd) lines.push(`- Test: \`${context.testCmd}\``);
if (context.buildCmd) lines.push(`- Build: \`${context.buildCmd}\``);
lines.push("");
}
lines.push(
"Load this skill file before starting work:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
`- ${conventionsPath}`,
"",
"Stack skills (java-spring, python-backend, frontend, testing) load contextually via `.claude/rules/`.",
"",
"After loading, create concise implementation plan, state assumptions, then implement requested changes.",
].join("\n");
);
const body = lines.join("\n");
const next = replaceManagedBlock(current, body, "html");

@@ -432,25 +500,244 @@ await writeFileWithDirs(filePath, next, dryRun);

async function configureCursor(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".cursor", "rules", "developer-stack-skills.mdc");
const body = [
"---",
"description: Load installed developer-stack-skills files before coding",
"globs: []",
"alwaysApply: false",
"---",
"",
"Read and follow these skill files before starting work:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
].join("\n");
function buildRuleFileContent(skillPath, config) {
const alwaysApply = config.alwaysApply === true;
const lines = ["---", `description: ${config.description}`];
if (!alwaysApply && config.globs && config.globs.length > 0) {
lines.push(`globs: ${JSON.stringify(config.globs)}`);
}
lines.push(`alwaysApply: ${alwaysApply}`, "---", "", "Load and follow this skill file:", "", `- ${skillPath}`, "");
return lines.join("\n");
}
await writeFileWithDirs(filePath, body, dryRun);
async function configureClaudeRules(projectDir, installRoot, dryRun = false) {
const rulesDir = path.join(projectDir, ".claude", "rules");
const configured = [];
for (const config of RULE_CONFIGS) {
const skillPath = path.join(installRoot, config.skillName, "SKILL.md");
const filePath = path.join(rulesDir, `developer-stack-skills-${config.skillName}.md`);
await writeFileWithDirs(filePath, buildRuleFileContent(skillPath, config), dryRun);
configured.push(filePath);
}
return configured;
}
async function configureClaudeCommands(projectDir, packageRoot, dryRun = false) {
const sourceDir = path.join(packageRoot, "commands");
const destDir = path.join(projectDir, ".claude", "commands");
const configured = [];
let files;
try {
files = await fsp.readdir(sourceDir);
} catch {
return configured;
}
await ensureDir(destDir, dryRun);
for (const file of files) {
if (!file.endsWith(".md")) continue;
const sourcePath = path.join(sourceDir, file);
const destPath = path.join(destDir, `developer-stack-skills-${file}`);
if (!dryRun) await fsp.copyFile(sourcePath, destPath);
configured.push(destPath);
}
return configured;
}
async function unconfigureClaudeCommands(projectDir, dryRun = false) {
const commandsDir = path.join(projectDir, ".claude", "commands");
let files;
try {
files = await fsp.readdir(commandsDir);
} catch {
return [];
}
const removed = [];
for (const file of files) {
if (file.startsWith("developer-stack-skills-") && file.endsWith(".md")) {
const filePath = path.join(commandsDir, file);
await removePath(filePath, dryRun);
removed.push(filePath);
}
}
return removed;
}
function buildMcpCommand(packageInstallType) {
return packageInstallType === "global"
? { command: "developer-stack-skills", args: ["serve"] }
: { command: "npx", args: ["developer-stack-skills", "serve"] };
}
async function configureMcp(projectDir, packageInstallType, dryRun = false) {
const filePath = path.join(projectDir, ".claude", "mcp.json");
const current = await readIfExists(filePath);
let config = {};
if (current.trim()) {
try {
config = JSON.parse(current);
} catch {
process.stderr.write(`[${PACKAGE_NAME}] Warning: ${filePath} has invalid JSON — skipping MCP update to avoid data loss\n`);
return filePath;
}
}
if (!config.mcpServers) config.mcpServers = {};
const { command, args } = buildMcpCommand(packageInstallType);
config.mcpServers[PACKAGE_NAME] = { command, args, type: "stdio" };
await writeFileWithDirs(filePath, JSON.stringify(config, null, 2) + "\n", dryRun);
return filePath;
}
async function unconfigureMcp(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".claude", "mcp.json");
const current = await readIfExists(filePath);
if (!current.trim()) return filePath;
let config;
try { config = JSON.parse(current); } catch { return filePath; }
if (config.mcpServers) {
delete config.mcpServers[PACKAGE_NAME];
if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
}
if (Object.keys(config).length === 0) {
await removePath(filePath, dryRun);
} else {
await writeFileWithDirs(filePath, JSON.stringify(config, null, 2) + "\n", dryRun);
}
return filePath;
}
async function unconfigureClaudeRules(projectDir, dryRun = false) {
const rulesDir = path.join(projectDir, ".claude", "rules");
let files;
try {
files = await fsp.readdir(rulesDir);
} catch {
return [];
}
const removed = [];
for (const file of files) {
if (file.startsWith("developer-stack-skills-") && file.endsWith(".md")) {
const filePath = path.join(rulesDir, file);
await removePath(filePath, dryRun);
removed.push(filePath);
}
}
return removed;
}
async function installHooks({ packageRoot, installRoot, mode, platform, dryRun = false }) {
const sourcePath = path.join(packageRoot, HOOKS_DIR);
const destPath = getHooksDestPath(installRoot);
await removePath(destPath, dryRun);
if (mode === "copy") {
if (!dryRun) await fsp.cp(sourcePath, destPath, { recursive: true });
} else {
const symlinkType = platform === "windows" ? "junction" : "dir";
if (!dryRun) await fsp.symlink(sourcePath, destPath, symlinkType);
}
return { sourcePath, destPath };
}
async function configureClaudeHooks(projectDir, hooksDir, dryRun = false) {
const filePath = path.join(projectDir, ".claude", "settings.json");
const current = await readIfExists(filePath);
let settings = {};
if (current.trim()) {
try {
settings = JSON.parse(current);
} catch {
process.stderr.write(`[${PACKAGE_NAME}] Warning: ${filePath} has invalid JSON — skipping hooks update to avoid data loss\n`);
return filePath;
}
}
if (settings.PreToolUse) {
settings.PreToolUse = removeOurHookEntries(settings.PreToolUse);
if (settings.PreToolUse.length === 0) delete settings.PreToolUse;
}
if (!settings.PreToolUse) settings.PreToolUse = [];
settings.PreToolUse.push(
{
matcher: "Write|Edit",
hooks: [{ type: "command", command: buildHookCommand(hooksDir, "pre-write.js"), timeout: 10 }],
},
{
matcher: "Bash",
hooks: [{ type: "command", command: buildHookCommand(hooksDir, "pre-bash.js"), timeout: 10 }],
},
);
await writeFileWithDirs(filePath, JSON.stringify(settings, null, 2) + "\n", dryRun);
return filePath;
}
async function unconfigureClaudeHooks(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".claude", "settings.json");
const current = await readIfExists(filePath);
if (!current.trim()) return filePath;
let settings;
try { settings = JSON.parse(current); } catch { return filePath; }
if (settings.PreToolUse) {
settings.PreToolUse = removeOurHookEntries(settings.PreToolUse);
if (settings.PreToolUse.length === 0) delete settings.PreToolUse;
}
if (Object.keys(settings).length === 0) {
await removePath(filePath, dryRun);
} else {
await writeFileWithDirs(filePath, JSON.stringify(settings, null, 2) + "\n", dryRun);
}
return filePath;
}
async function configureCursor(projectDir, installRoot, dryRun = false) {
const rulesDir = path.join(projectDir, ".cursor", "rules");
const configured = [];
for (const config of RULE_CONFIGS) {
const skillPath = path.join(installRoot, config.skillName, "SKILL.md");
const filePath = path.join(rulesDir, `developer-stack-skills-${config.skillName}.mdc`);
await writeFileWithDirs(filePath, buildRuleFileContent(skillPath, config), dryRun);
configured.push(filePath);
}
const conventionsPath = path.join(installRoot, CONVENTIONS_RULE_CONFIG.skillName, "SKILL.md");
const conventionsFilePath = path.join(rulesDir, `developer-stack-skills-${CONVENTIONS_RULE_CONFIG.skillName}.mdc`);
await writeFileWithDirs(conventionsFilePath, buildRuleFileContent(conventionsPath, CONVENTIONS_RULE_CONFIG), dryRun);
configured.push(conventionsFilePath);
return configured;
}
async function configureCline(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".clinerules");
const current = await readIfExists(filePath);
const next = upsertSkillsSection(current, skillPaths, (skillPath) => ` - ${quoteYamlString(skillPath)}`);
const body = [
"Read and follow these skill files before starting work:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
].join("\n");
const next = replaceManagedBlock(current, body, "html");
await writeFileWithDirs(filePath, next, dryRun);

@@ -461,7 +748,13 @@ return filePath;

async function configureRoocode(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".roo", "config.yml");
const current = await readIfExists(filePath);
const next = upsertSkillsSection(current, skillPaths, (skillPath) => ` - path: ${quoteYamlString(skillPath)}`);
const filePath = path.join(projectDir, ".roo", "rules", "developer-stack-skills.md");
const body = [
"# Developer Stack Skills",
"",
"Load and follow these skill files before starting work:",
"",
...skillPaths.map((skillPath) => `- ${skillPath}`),
"",
].join("\n");
await writeFileWithDirs(filePath, next, dryRun);
await writeFileWithDirs(filePath, body, dryRun);
return filePath;

@@ -488,3 +781,3 @@ }

async function configureAgents(agent, projectDir, installRoot, dryRun = false) {
async function configureAgents({ agent, projectDir, installRoot, context, generateCommands, configureMcpServer, packageInstallType, dryRun = false }) {
const skillPaths = buildSkillPaths(installRoot);

@@ -494,5 +787,22 @@ const targets = getAgentTargets(agent);

const hooksDir = getHooksDestPath(installRoot);
for (const target of targets) {
if (target === "claude") {
configured.push({ agent: target, filePath: await configureClaude(projectDir, skillPaths, dryRun) });
const conventionsPath = path.join(installRoot, "project-conventions", "SKILL.md");
configured.push({ agent: target, filePath: await configureClaude(projectDir, conventionsPath, context, dryRun) });
const ruleFiles = await configureClaudeRules(projectDir, installRoot, dryRun);
for (const ruleFilePath of ruleFiles) {
configured.push({ agent: "claude-rules", filePath: ruleFilePath });
}
configured.push({ agent: "claude-hooks", filePath: await configureClaudeHooks(projectDir, hooksDir, dryRun) });
if (generateCommands) {
const commandFiles = await configureClaudeCommands(projectDir, getPackageRoot(), dryRun);
for (const filePath of commandFiles) {
configured.push({ agent: "claude-commands", filePath });
}
}
if (configureMcpServer) {
configured.push({ agent: "claude-mcp", filePath: await configureMcp(projectDir, packageInstallType, dryRun) });
}
continue;

@@ -502,3 +812,6 @@ }

if (target === "cursor") {
configured.push({ agent: target, filePath: await configureCursor(projectDir, skillPaths, dryRun) });
const cursorFiles = await configureCursor(projectDir, installRoot, dryRun);
for (const filePath of cursorFiles) {
configured.push({ agent: target, filePath });
}
continue;

@@ -539,11 +852,25 @@ }

async function unconfigureCursor(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".cursor", "rules", "developer-stack-skills.mdc");
await removePath(filePath, dryRun);
return filePath;
const rulesDir = path.join(projectDir, ".cursor", "rules");
let files;
try {
files = await fsp.readdir(rulesDir);
} catch {
return [];
}
const removed = [];
for (const file of files) {
if (file.startsWith("developer-stack-skills-") && file.endsWith(".mdc")) {
const filePath = path.join(rulesDir, file);
await removePath(filePath, dryRun);
removed.push(filePath);
}
}
return removed;
}
async function unconfigureCline(projectDir, skillPaths, dryRun = false) {
async function unconfigureCline(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".clinerules");
const current = await readIfExists(filePath);
const next = removeSkillsSectionItems(current, skillPaths, (skillPath) => ` - ${quoteYamlString(skillPath)}`);
const next = removeManagedBlock(current, "html");

@@ -558,12 +885,5 @@ if (next.trim()) {

async function unconfigureRoocode(projectDir, skillPaths, dryRun = false) {
const filePath = path.join(projectDir, ".roo", "config.yml");
const current = await readIfExists(filePath);
const next = removeSkillsSectionItems(current, skillPaths, (skillPath) => ` - path: ${quoteYamlString(skillPath)}`);
if (next.trim()) {
await writeFileWithDirs(filePath, next, dryRun);
} else {
await removePath(filePath, dryRun);
}
async function unconfigureRoocode(projectDir, dryRun = false) {
const filePath = path.join(projectDir, ".roo", "rules", "developer-stack-skills.md");
await removePath(filePath, dryRun);
return filePath;

@@ -586,3 +906,2 @@ }

async function unconfigureAgents(agent, projectDir, installRoot, dryRun = false) {
const skillPaths = buildSkillPaths(installRoot);
const targets = getAgentTargets(agent);

@@ -594,2 +913,12 @@ const configured = [];

configured.push({ agent: target, filePath: await unconfigureClaude(projectDir, dryRun) });
const removedRules = await unconfigureClaudeRules(projectDir, dryRun);
for (const ruleFilePath of removedRules) {
configured.push({ agent: "claude-rules", filePath: ruleFilePath });
}
configured.push({ agent: "claude-hooks", filePath: await unconfigureClaudeHooks(projectDir, dryRun) });
const removedCommands = await unconfigureClaudeCommands(projectDir, dryRun);
for (const filePath of removedCommands) {
configured.push({ agent: "claude-commands", filePath });
}
configured.push({ agent: "claude-mcp", filePath: await unconfigureMcp(projectDir, dryRun) });
continue;

@@ -599,3 +928,6 @@ }

if (target === "cursor") {
configured.push({ agent: target, filePath: await unconfigureCursor(projectDir, dryRun) });
const removedCursor = await unconfigureCursor(projectDir, dryRun);
for (const filePath of removedCursor) {
configured.push({ agent: target, filePath });
}
continue;

@@ -605,3 +937,3 @@ }

if (target === "cline") {
configured.push({ agent: target, filePath: await unconfigureCline(projectDir, skillPaths, dryRun) });
configured.push({ agent: target, filePath: await unconfigureCline(projectDir, dryRun) });
continue;

@@ -611,3 +943,3 @@ }

if (target === "roocode") {
configured.push({ agent: target, filePath: await unconfigureRoocode(projectDir, skillPaths, dryRun) });
configured.push({ agent: target, filePath: await unconfigureRoocode(projectDir, dryRun) });
continue;

@@ -624,2 +956,16 @@ }

async function collectProjectContext(prompt) {
console.log("\n[developer-stack-skills] Optional: add project context to CLAUDE.md (Enter to skip each)");
const description = await prompt.ask("Project description (1 line): ");
const testCmd = await prompt.ask("Test command (e.g. mvn test, pytest, npm test): ");
const buildCmd = await prompt.ask("Build/start command (e.g. mvn spring-boot:run, uvicorn main:app): ");
const context = {};
if (description.trim()) context.description = description.trim();
if (testCmd.trim()) context.testCmd = testCmd.trim();
if (buildCmd.trim()) context.buildCmd = buildCmd.trim();
return Object.keys(context).length ? context : null;
}
async function collectAnswers(args, defaults = {}) {

@@ -657,2 +1003,15 @@ const prompt = createPrompt();

const normalizedAgent = normalizeAgent(agent) || "all";
const isClaudeTarget = normalizedAgent === "all" || normalizedAgent === "claude";
const projectContext = isClaudeTarget ? await collectProjectContext(prompt) : null;
let generateCommands = false;
let configureMcpServer = false;
if (isClaudeTarget) {
const commandsInput = await prompt.ask("\nGenerate Claude Code slash commands? [yes/no] (default: yes): ");
generateCommands = commandsInput === "" || /^y(es)?$/i.test(commandsInput.trim());
const mcpInput = await prompt.ask("Configure MCP server (skills on-demand via tools)? [yes/no] (default: yes): ");
configureMcpServer = mcpInput === "" || /^y(es)?$/i.test(mcpInput.trim());
}
return {

@@ -662,2 +1021,5 @@ agent,

projectDir,
projectContext,
generateCommands,
configureMcpServer,
};

@@ -685,2 +1047,5 @@ } finally {

projectDir: path.resolve(args.projectDir || defaults.projectDir || process.cwd()),
projectContext: null,
generateCommands: true,
configureMcpServer: true,
};

@@ -749,3 +1114,15 @@ }

const configured = await configureAgents(selected.agent, selected.projectDir, installRoot, rawArgs.dryRun);
const hooksResult = await installHooks({ packageRoot, installRoot, mode: selected.mode, platform, dryRun: rawArgs.dryRun });
console.log(`[${PACKAGE_NAME}] hooks ${rawArgs.dryRun ? "would install" : "installed"}: ${hooksResult.destPath}`);
const configured = await configureAgents({
agent: selected.agent,
projectDir: selected.projectDir,
installRoot,
context: selected.projectContext,
generateCommands: selected.generateCommands,
configureMcpServer: selected.configureMcpServer,
packageInstallType,
dryRun: rawArgs.dryRun,
});
for (const item of configured) {

@@ -795,2 +1172,6 @@ console.log(`[${PACKAGE_NAME}] ${item.agent} config ${rawArgs.dryRun ? "would update" : "updated"}: ${item.filePath}`);

const hooksPath = getHooksDestPath(installRoot);
await removePath(hooksPath, rawArgs.dryRun);
console.log(`[${PACKAGE_NAME}] hooks ${rawArgs.dryRun ? "would remove" : "removed"}: ${hooksPath}`);
console.log(`[${PACKAGE_NAME}] ${rawArgs.dryRun ? "uninstall dry run complete" : "uninstall complete"}`);

@@ -838,6 +1219,19 @@

AGENTS,
CONVENTIONS_RULE_CONFIG,
HOOKS_DIR,
MODES,
RULE_CONFIGS,
SKILLS,
buildMcpCommand,
buildRuleFileContent,
buildSkillPaths,
configureClaude,
configureAgents,
configureClaudeCommands,
configureClaudeHooks,
configureClaudeRules,
configureCline,
configureCursor,
configureMcp,
configureRoocode,
detectPackageInstallType,

@@ -847,4 +1241,6 @@ detectPlatform,

getDefaultProjectDir,
getHooksDestPath,
getInstallRoot,
isInteractiveInstall,
isOurHookEntry,
parseArgs,

@@ -854,2 +1250,3 @@ printHelp,

removeManagedBlock,
removeOurHookEntries,
removeSkillsSectionItems,

@@ -856,0 +1253,0 @@ replaceManagedBlock,

{
"name": "developer-stack-skills",
"version": "1.2.1",
"version": "2.0.0",
"description": "AI agent SKILL.md files plus installer CLI for Java/Spring, Python/FastAPI, React/Angular, Testing, and Project Conventions. Compatible with Claude, Cline, Roocode, Copilot, and Cursor.",

@@ -32,2 +32,5 @@ "keywords": [

},
"engines": {
"node": ">=18.0.0"
},
"scripts": {

@@ -37,5 +40,11 @@ "postinstall": "node bin/postinstall.js",

},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.0"
},
"files": [
"bin/**/*",
"commands/**/*",
"hooks/**/*",
"lib/**/*",
".claude/rules/**/*",
"java-spring/SKILL.md",

@@ -42,0 +51,0 @@ "python-backend/SKILL.md",

+143
-2

@@ -21,3 +21,3 @@ # developer-stack-skills

Version in this README: `1.2.1`
Version in this README: `2.0.0`

@@ -34,2 +34,11 @@ Interactive `npm install` can auto-run post-install configuration, but recent npm versions hide lifecycle script output by default. Treat configuration as explicit step after installation unless you install with `--foreground-scripts`.

Why global install still asks for project directory:
- Global install has two separate outputs:
1. Skill files go into shared user-level folder: `~/.ai-skills/developer-stack-skills`
2. Agent config files still get written into one specific project
- Installer asks for `Project directory` so it knows where to update `CLAUDE.md`, `.clinerules`, `.roo/config.yml`, `.cursor/rules/developer-stack-skills.mdc`, and `.github/copilot-instructions.md`
- Global package install does not mean "enable skills for every repo automatically"
- It means "store one shared copy of skills globally, then link chosen project to those skills"
Post-install configure command:

@@ -73,2 +82,8 @@

Start MCP server (stdio):
```bash
developer-stack-skills serve
```
Or run from local package without global install:

@@ -78,2 +93,3 @@

npx developer-stack-skills configure
npx developer-stack-skills serve
```

@@ -115,3 +131,3 @@

```text
[developer-stack-skills] installing version 1.2.1
[developer-stack-skills] installing version 2.0.0
[developer-stack-skills] package install type: global

@@ -141,2 +157,3 @@ [developer-stack-skills] skill install scope: global

- `uninstall`
- `serve`
- `version`

@@ -147,2 +164,76 @@ - `help`

## MCP Server
Skills are exposed as MCP tools via stdio transport.
```bash
developer-stack-skills serve
```
Add to MCP client config:
```json
{
"mcpServers": {
"developer-stack-skills": {
"command": "developer-stack-skills",
"args": ["serve"]
}
}
}
```
Or without global install:
```json
{
"mcpServers": {
"developer-stack-skills": {
"command": "npx",
"args": ["developer-stack-skills", "serve"]
}
}
}
```
Available tools:
| Tool | Description |
|---|---|
| `list_available_skills` | List all skills with descriptions and file patterns |
| `get_skill` | Load full SKILL.md for a stack (`java-spring`, `python-backend`, `frontend`, `testing`, `project-conventions`) |
| `get_conventions` | Load project-wide conventions (shortcut for `get_skill` with `project-conventions`) |
| `detect_stack` | Given a file path, return which skill applies and a ready-to-use `get_skill` call |
---
## Claude Code Commands
Five slash commands are installed into your project:
| Command | Description |
|---|---|
| `/implement-feature [description]` | Detect stack, plan, implement with tests |
| `/write-tests [target]` | Write tests following stack conventions |
| `/review-pr` | Review branch changes against conventions |
| `/check-deps` | Audit dependencies for outdated versions and vulnerabilities |
| `/add-endpoint [description]` | Add REST endpoint following stack and REST conventions |
---
## Hooks
Two Claude Code hooks fire automatically:
| Hook | File | Fires When |
|---|---|---|
| `pre-write` | `hooks/pre-write.js` | Before any file write — injects stack reminder based on file extension |
| `pre-bash` | `hooks/pre-bash.js` | Before bash commands — warns to verify latest stable version on package installs |
The `pre-write` hook covers: Java, Kotlin, Python, Angular TypeScript, generic TypeScript/JSX, `.env` files, and `.sql` migrations.
The `pre-bash` hook detects: `pip install`, `uv add`, `npm install`, `yarn add`, `pnpm add`, `bun add`, `poetry add`, and `npx pkg@latest`.
---
## Installed Files

@@ -178,2 +269,52 @@

## FAQ
### Why does `npm install -g developer-stack-skills` still ask for `Project directory`?
Because installer does two different things:
1. It installs skill folders
2. It updates agent config files for a specific project
With global package install, skill folders go to:
```text
~/.ai-skills/developer-stack-skills/
```
But agent configs still must be written into a real project, for example:
```text
D:\Projects\my-app\CLAUDE.md
D:\Projects\my-app\.clinerules
D:\Projects\my-app\.roo\config.yml
```
So `Project directory` prompt is still required. Global install means shared skill storage, not machine-wide auto-enable for every repository.
### What is difference between `npm install -g developer-stack-skills` and `npm install developer-stack-skills`?
`npm install -g developer-stack-skills`
- Package installs into global npm directory
- CLI command `developer-stack-skills` works anywhere
- Skills install into `~/.ai-skills/developer-stack-skills/`
- Default install mode is `symlink`
- Best when you want one shared install reused across many projects
`npm install developer-stack-skills`
- Package installs into current project's `node_modules`
- CLI usually runs through `npx developer-stack-skills` or npm scripts
- Skills install into `<project>/.ai-skills/developer-stack-skills/`
- Default install mode is `copy`
- Best when each project should keep its own isolated skill copy
Same in both cases:
- Installer still updates agent config files per project
- Skills are not auto-enabled for every repository on machine
---
## Skills Included

@@ -180,0 +321,0 @@

# Release Notes
## 2.0.0 - 2026-05-16
This release adds MCP server support, Claude Code slash commands, and hooks — making skills available to AI agents at runtime without manual SKILL.md loading.
### MCP Server
Skills are now exposed as MCP tools. Start the server with:
```bash
developer-stack-skills serve
```
Add to your MCP client config (stdio transport):
```json
{
"mcpServers": {
"developer-stack-skills": {
"command": "developer-stack-skills",
"args": ["serve"]
}
}
}
```
Or with npx (no global install required):
```json
{
"mcpServers": {
"developer-stack-skills": {
"command": "npx",
"args": ["developer-stack-skills", "serve"]
}
}
}
```
Available tools:
| Tool | Description |
|---|---|
| `list_available_skills` | List all skills with descriptions and file patterns |
| `get_skill` | Load full SKILL.md for a stack before writing code |
| `get_conventions` | Load project-wide conventions |
| `detect_stack` | Detect which skill applies to a file path |
Typical agent workflow:
1. Agent opens a file → calls `detect_stack` with the file path
2. Server returns the recommended skill name
3. Agent calls `get_skill` to load full conventions
4. Agent writes code following those conventions
### Claude Code Commands
Five slash commands are now included and installed into your project:
- `/implement-feature [description]` — detect stack, plan, implement with tests
- `/write-tests [target]` — write tests following stack conventions
- `/review-pr` — review branch changes against conventions
- `/check-deps` — audit dependencies for outdated versions and vulnerabilities
- `/add-endpoint [description]` — add REST endpoint following stack and REST conventions
### Hooks
Two Claude Code hooks inject reminders automatically:
- `pre-write.js` — fires before any file write; injects a one-line stack reminder based on file extension (Java, Kotlin, Python, Angular, TypeScript, env files, SQL migrations)
- `pre-bash.js` — fires before bash commands; warns to verify latest stable version before any package install (`pip install`, `npm install`, `uv add`, etc.)
Hooks require Claude Code. They run automatically — no manual configuration beyond the installer wiring them up.
### Agent Config Updates
- **Claude Code**: `.claude/rules/` now contains per-stack rule files that auto-load the right skill
- **Cursor**: Single `.cursor/rules/developer-stack-skills.mdc` replaced by five per-stack `.mdc` files for finer-grained activation
- **Roocode**: Migrated from `.roo/config.yml` to `.roo/rules/developer-stack-skills.md`
---
## 1.2.1 - 2026-05-15

@@ -4,0 +85,0 @@

-4
skills:
- path: node_modules/io.github.jabhijeet/java-spring/SKILL.md
- path: node_modules/io.github.jabhijeet/testing/SKILL.md
- path: node_modules/io.github.jabhijeet/project-conventions/SKILL.md

Sorry, the diff of this file is not supported yet