developer-stack-skills
Advanced tools
| { | ||
| "mcpServers": { | ||
| "developer-stack-skills": { | ||
| "command": "npx", | ||
| "args": ["developer-stack-skills", "serve"], | ||
| "type": "stdio" | ||
| } | ||
| } | ||
| } |
| { | ||
| "mcpServers": { | ||
| "developer-stack-skills": { | ||
| "command": "npx", | ||
| "args": ["developer-stack-skills", "serve"], | ||
| "type": "stdio" | ||
| } | ||
| } | ||
| } |
| --- | ||
| description: Java & Spring Boot — Spring Boot 3, JPA, REST APIs, JUnit 5, Maven/Gradle | ||
| description: Java 25 & Spring Boot 4 / Spring 7 — JPA, REST APIs, JUnit 5, Maven/Gradle | ||
| globs: ["**/*.java","**/*.kt","**/pom.xml","**/build.gradle","**/build.gradle.kts"] | ||
@@ -4,0 +4,0 @@ alwaysApply: false |
+44
-0
| # Changelog | ||
| ## 3.0.0 - 2026-05-20 | ||
| Breaking: | ||
| - Node.js engine requirement raised to `>=24.0.0` — drops Node 18, 20, 22 | ||
| - MCP tool error responses changed from plain text to structured JSON `{error_type, message, retryable}` — `INVALID_SKILL`, `SKILL_NOT_FOUND`, `UNKNOWN_TOOL` | ||
| - `configureCline(dir, paths, dryRun)` → `configureCline(dir, paths, useMcp, dryRun)` — third param changed | ||
| - `configureRoocode(dir, paths, dryRun)` → `configureRoocode(dir, paths, useMcp, dryRun)` — third param changed | ||
| - `configureClaude` now generates MCP-first CLAUDE.md when `configureMcpServer=true` — installed projects get `detect_stack`/`get_skill` instructions instead of file paths | ||
| Added: | ||
| - `configureCursorMcp(dir, packageInstallType, dryRun)` — writes `.cursor/mcp.json` | ||
| - `configureSharedMcp(dir, packageInstallType, dryRun)` — writes `.mcp.json` at project root for Cline/Roocode | ||
| - `writeMcpJsonFile(filePath, packageInstallType, dryRun)` — shared MCP JSON write/merge helper | ||
| - `removeMcpJsonEntry(filePath, dryRun)` — removes package entry, preserves others, deletes file if empty | ||
| - `unconfigureCursorMcp(dir, dryRun)`, `unconfigureSharedMcp(dir, dryRun)` — cleanup counterparts | ||
| - `handleTool` exported from `lib/mcp-server.js` for direct testing | ||
| - Cursor now receives `.cursor/mcp.json` on install when MCP opted in | ||
| - Cline now receives `.mcp.json` + MCP-first `.clinerules` on install when MCP opted in | ||
| - Roocode now receives `.mcp.json` + MCP-first `.roo/rules/developer-stack-skills.md` on install when MCP opted in | ||
| - Source repo `.claude/mcp.json` and `.mcp.json` added for Cline/Roocode users of this repo | ||
| - 33 new tests: MCP routing in `configureAgents`, structured error shape, `writeMcpJsonFile`/`removeMcpJsonEntry` helpers, idempotency for all MCP config functions, unconfigure cleanup | ||
| - Smoke test fixtures (claude, cline, roocode) updated to MCP-first instructions; cline/roocode fixtures include `.mcp.json` | ||
| Changed: | ||
| - `configureCline` — generates MCP-first body when `useMcp=true`; path-based body when false | ||
| - `configureRoocode` — same | ||
| - `configureAgents` — passes `configureMcpServer` to all agent configurers; calls `configureCursorMcp`/`configureSharedMcp` when opted in; calls `unconfigureCursorMcp`/`unconfigureSharedMcp` on uninstall | ||
| - `configureMcp`/`unconfigureMcp` refactored to delegate to `writeMcpJsonFile`/`removeMcpJsonEntry` | ||
| - Commands `implement-feature`, `write-tests`, `add-endpoint` — replaced manual root-file stack detection with `detect_stack` + `get_skill` MCP tool calls; `allowed-tools` updated to include MCP tool names | ||
| - `MCP_INSTRUCTION_LINES` constant extracted in `lib/installer.js` — shared between Cline and Roocode instruction generation | ||
| - Java examples: `@MockBean` → `@MockitoBean` (removed Spring Boot 4) | ||
| - Angular testing: Jasmine+Karma → Jest; `HttpClientTestingModule` → `provideHttpClient()` + `provideHttpClientTesting()` | ||
| - All SKILL.md `last-reviewed` dates updated to `2026-05-20` | ||
| Version updates: | ||
| - Java 17+ → Java 25; Spring Boot 3.x → Spring Boot 4 / Spring Framework 7 | ||
| - PostgreSQL container image: `postgres:16-alpine` → `postgres:18-alpine` | ||
| - Python `>=3.12` → `>=3.14`; ruff `>=0.4` → `>=1.0`; mypy `>=1.10` → `>=1.15`; pytest `>=8.0` → `>=8.3`; pytest-asyncio `>=0.23` → `>=0.24`; httpx `>=0.27` → `>=0.28`; uvicorn `>=0.30` → `>=0.34`; asyncpg `>=0.29` → `>=0.30` | ||
| - React 18+ → React 19+; Angular 17+ → Angular 21+; Node engine `>=18` → `>=24` | ||
| ## 2.0.0 - 2026-05-16 | ||
@@ -4,0 +48,0 @@ |
| --- | ||
| description: Add REST API endpoint following stack and REST conventions | ||
| argument-hint: [METHOD /path description] | ||
| allowed-tools: Read, Write, Edit, Bash, Grep, Glob | ||
| allowed-tools: Read, Write, Edit, Bash, Grep, Glob, mcp__developer-stack-skills__detect_stack, mcp__developer-stack-skills__get_skill | ||
| --- | ||
@@ -9,18 +9,13 @@ | ||
| 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. | ||
| 1. Identify the controller or router file where this endpoint will live. Call `detect_stack` with that file path, then call `get_skill` with the result to load full conventions before writing anything. If no existing file exists yet, call `detect_stack` with a representative source file (e.g. an existing controller). | ||
| 2. Read existing endpoints in the project to understand current patterns, naming, and error handling before writing anything. | ||
| 2. Read existing endpoints in the project to understand current patterns, naming, and error handling. | ||
| 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 | ||
| - Request/response DTOs validated at the 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) | ||
| - Service: business logic, throws domain-specific exceptions | ||
| - Repository: data access only (create if DB interaction needed) | ||
@@ -27,0 +22,0 @@ |
| --- | ||
| description: Implement feature following project stack conventions | ||
| argument-hint: [feature-description] | ||
| allowed-tools: Read, Write, Edit, Bash, Grep, Glob | ||
| allowed-tools: Read, Write, Edit, Bash, Grep, Glob, mcp__developer-stack-skills__detect_stack, mcp__developer-stack-skills__get_skill, mcp__developer-stack-skills__get_conventions | ||
| --- | ||
@@ -9,15 +9,9 @@ | ||
| 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) | ||
| 1. Identify the primary file to create or modify for this feature. Call `detect_stack` with that file path to determine the stack, then call `get_skill` with the result to load full conventions before writing anything. | ||
| 2. Read existing code to understand current architecture, naming conventions, and patterns before writing anything. | ||
| 2. Read existing code to understand current architecture, naming conventions, and patterns. | ||
| 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 | ||
| 4. Implement following the loaded skill conventions. | ||
@@ -24,0 +18,0 @@ 5. Write tests alongside implementation: |
| --- | ||
| description: Write tests for a file or class following testing conventions | ||
| argument-hint: [file-or-class] | ||
| allowed-tools: Read, Write, Edit, Bash, Grep, Glob | ||
| allowed-tools: Read, Write, Edit, Bash, Grep, Glob, mcp__developer-stack-skills__detect_stack, mcp__developer-stack-skills__get_skill | ||
| --- | ||
@@ -9,9 +9,5 @@ | ||
| 1. Read the target file thoroughly to understand all public methods, edge cases, and error paths. | ||
| 1. Call `detect_stack` with the target file path, then call `get_skill` with the result to load the full testing conventions for this stack before writing anything. | ||
| 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 | ||
| 2. Read the target file thoroughly to understand all public methods, edge cases, and error paths. | ||
@@ -27,4 +23,4 @@ 3. For each public method or exported function, write: | ||
| 6. Use descriptive test names: `method_WhenCondition_ExpectedResult` (Java) or `test_does_x_when_y` (Python) or `should do X when Y` (TypeScript). | ||
| 6. Use descriptive test names per the loaded skill conventions. | ||
| 7. Place test file in the correct location per project structure. Run existing tests to confirm nothing broke. |
@@ -1,7 +0,9 @@ | ||
| Load these skill files before starting work: | ||
| Use the developer-stack-skills MCP server before editing any file: | ||
| - node_modules/developer-stack-skills/frontend/SKILL.md | ||
| - node_modules/developer-stack-skills/testing/SKILL.md | ||
| - node_modules/developer-stack-skills/project-conventions/SKILL.md | ||
| 1. Call `detect_stack` with the file path to identify the relevant stack. | ||
| 2. Call `get_skill` with the detected stack to load its conventions. | ||
| 3. Do not preload all skill files — load only the relevant skill on demand. | ||
| After loading, create a concise implementation plan, state assumptions, then implement requested changes. | ||
| For cross-cutting decisions, call `get_conventions` to load project-wide standards. | ||
| After loading the relevant skill, create a concise implementation plan, state assumptions, then implement requested changes. |
| # Developer Stack Skills | ||
| Load and follow these skill files before starting work: | ||
| Use the developer-stack-skills MCP server before editing any file: | ||
| - 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 | ||
| 1. Call `detect_stack` with the file path to identify the relevant stack. | ||
| 2. Call `get_skill` with the detected stack to load its conventions. | ||
| 3. Do not preload all skill files — load only the relevant skill on demand. | ||
| For cross-cutting decisions, call `get_conventions` to load project-wide standards. | ||
| After loading the relevant skill, create a concise implementation plan, state assumptions, then implement. |
@@ -11,4 +11,4 @@ --- | ||
| version: 1.0.0 | ||
| last-reviewed: 2026-05-15 | ||
| applies-to: React, Angular, JavaScript, TypeScript, frontend architecture, UI refactors | ||
| last-reviewed: 2026-05-20 | ||
| applies-to: React 19+, Angular 21+, JavaScript, TypeScript 5+, frontend architecture, UI refactors | ||
| --- | ||
@@ -191,3 +191,3 @@ | ||
| ### Component Rules | ||
| - Use **standalone components** (Angular 17+) by default | ||
| - Use **standalone components** — default since Angular 17, mandatory pattern from Angular 21+ | ||
| - Use `OnPush` change detection for all components | ||
@@ -220,3 +220,3 @@ - Use **signals** (`signal`, `computed`, `effect`) for reactive state | ||
| ### Service Rules | ||
| - Use `inject()` function (not constructor injection) for Angular 14+ | ||
| - Use `inject()` function (not constructor injection) — standard from Angular 14+, required style from Angular 21+ | ||
| - Provide at root: `providedIn: 'root'` | ||
@@ -223,0 +223,0 @@ - Return `Observable` from service methods; never subscribe inside services |
@@ -11,3 +11,3 @@ --- | ||
| version: 1.0.0 | ||
| last-reviewed: 2026-05-15 | ||
| last-reviewed: 2026-05-20 | ||
| applies-to: Java, Spring Boot, REST APIs, JPA, security, testing, build configuration | ||
@@ -111,3 +111,3 @@ --- | ||
| ### DTO Convention | ||
| - Use Java `record` for DTOs (Java 16+) | ||
| - Use Java `record` for DTOs (Java 17+ feature — Spring Boot 4 / Java 25 baseline) | ||
| - Suffix: `*Request`, `*Response`, `*DTO` | ||
@@ -288,3 +288,3 @@ | ||
| @Autowired MockMvc mockMvc; | ||
| @MockBean UserService userService; | ||
| @MockitoBean UserService userService; // @MockBean removed in Spring Boot 4 | ||
@@ -291,0 +291,0 @@ @Test |
+104
-45
@@ -32,3 +32,3 @@ const fsp = require("fs/promises"); | ||
| skillName: "java-spring", | ||
| description: "Java & Spring Boot — Spring Boot 3, JPA, REST APIs, JUnit 5, Maven/Gradle", | ||
| description: "Java 25 & Spring Boot 4 / Spring 7 — JPA, REST APIs, JUnit 5, Maven/Gradle", | ||
| globs: ["**/*.java", "**/*.kt", "**/pom.xml", "**/build.gradle", "**/build.gradle.kts"], | ||
@@ -466,3 +466,3 @@ }, | ||
| async function configureClaude(projectDir, conventionsPath, context, dryRun = false) { | ||
| async function configureClaude(projectDir, conventionsPath, context, useMcp = false, dryRun = false) { | ||
| const filePath = path.join(projectDir, "CLAUDE.md"); | ||
@@ -481,11 +481,25 @@ const current = await readIfExists(filePath); | ||
| lines.push( | ||
| "Load this skill file before starting work:", | ||
| "", | ||
| `- ${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.", | ||
| ); | ||
| if (useMcp) { | ||
| lines.push( | ||
| "Use the developer-stack-skills MCP server before editing any file:", | ||
| "", | ||
| "1. Call `detect_stack` with the file path to identify the relevant stack.", | ||
| "2. Call `get_skill` with the detected stack to load its conventions.", | ||
| "3. Do not preload all skill files — load only the relevant skill on demand.", | ||
| "", | ||
| "For cross-cutting decisions, call `get_conventions` to load project-wide standards.", | ||
| "", | ||
| "After loading the relevant skill, create a concise implementation plan, state assumptions, then implement.", | ||
| ); | ||
| } else { | ||
| lines.push( | ||
| "Load this skill file before starting work:", | ||
| "", | ||
| `- ${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.", | ||
| ); | ||
| } | ||
@@ -573,11 +587,7 @@ const body = lines.join("\n"); | ||
| async function configureMcp(projectDir, packageInstallType, dryRun = false) { | ||
| const filePath = path.join(projectDir, ".claude", "mcp.json"); | ||
| async function writeMcpJsonFile(filePath, packageInstallType, dryRun = false) { | ||
| const current = await readIfExists(filePath); | ||
| let config = {}; | ||
| if (current.trim()) { | ||
| try { | ||
| config = JSON.parse(current); | ||
| } catch { | ||
| try { config = JSON.parse(current); } catch { | ||
| process.stderr.write(`[${PACKAGE_NAME}] Warning: ${filePath} has invalid JSON — skipping MCP update to avoid data loss\n`); | ||
@@ -587,7 +597,5 @@ 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); | ||
@@ -597,11 +605,7 @@ return filePath; | ||
| async function unconfigureMcp(projectDir, dryRun = false) { | ||
| const filePath = path.join(projectDir, ".claude", "mcp.json"); | ||
| async function removeMcpJsonEntry(filePath, dryRun = false) { | ||
| const current = await readIfExists(filePath); | ||
| if (!current.trim()) return filePath; | ||
| let config; | ||
| try { config = JSON.parse(current); } catch { return filePath; } | ||
| if (config.mcpServers) { | ||
@@ -611,3 +615,2 @@ delete config.mcpServers[PACKAGE_NAME]; | ||
| } | ||
| if (Object.keys(config).length === 0) { | ||
@@ -618,6 +621,29 @@ await removePath(filePath, dryRun); | ||
| } | ||
| return filePath; | ||
| } | ||
| async function configureMcp(projectDir, packageInstallType, dryRun = false) { | ||
| return writeMcpJsonFile(path.join(projectDir, ".claude", "mcp.json"), packageInstallType, dryRun); | ||
| } | ||
| async function configureCursorMcp(projectDir, packageInstallType, dryRun = false) { | ||
| return writeMcpJsonFile(path.join(projectDir, ".cursor", "mcp.json"), packageInstallType, dryRun); | ||
| } | ||
| async function configureSharedMcp(projectDir, packageInstallType, dryRun = false) { | ||
| return writeMcpJsonFile(path.join(projectDir, ".mcp.json"), packageInstallType, dryRun); | ||
| } | ||
| async function unconfigureMcp(projectDir, dryRun = false) { | ||
| return removeMcpJsonEntry(path.join(projectDir, ".claude", "mcp.json"), dryRun); | ||
| } | ||
| async function unconfigureCursorMcp(projectDir, dryRun = false) { | ||
| return removeMcpJsonEntry(path.join(projectDir, ".cursor", "mcp.json"), dryRun); | ||
| } | ||
| async function unconfigureSharedMcp(projectDir, dryRun = false) { | ||
| return removeMcpJsonEntry(path.join(projectDir, ".mcp.json"), dryRun); | ||
| } | ||
| async function unconfigureClaudeRules(projectDir, dryRun = false) { | ||
@@ -736,10 +762,24 @@ const rulesDir = path.join(projectDir, ".claude", "rules"); | ||
| async function configureCline(projectDir, skillPaths, dryRun = false) { | ||
| const MCP_INSTRUCTION_LINES = [ | ||
| "Use the developer-stack-skills MCP server before editing any file:", | ||
| "", | ||
| "1. Call `detect_stack` with the file path to identify the relevant stack.", | ||
| "2. Call `get_skill` with the detected stack to load its conventions.", | ||
| "3. Do not preload all skill files — load only the relevant skill on demand.", | ||
| "", | ||
| "For cross-cutting decisions, call `get_conventions` to load project-wide standards.", | ||
| "", | ||
| "After loading the relevant skill, create a concise implementation plan, state assumptions, then implement.", | ||
| ]; | ||
| async function configureCline(projectDir, skillPaths, useMcp = false, dryRun = false) { | ||
| const filePath = path.join(projectDir, ".clinerules"); | ||
| const current = await readIfExists(filePath); | ||
| const body = [ | ||
| "Read and follow these skill files before starting work:", | ||
| "", | ||
| ...skillPaths.map((skillPath) => `- ${skillPath}`), | ||
| ].join("\n"); | ||
| const body = useMcp | ||
| ? MCP_INSTRUCTION_LINES.join("\n") | ||
| : [ | ||
| "Read and follow these skill files before starting work:", | ||
| "", | ||
| ...skillPaths.map((skillPath) => `- ${skillPath}`), | ||
| ].join("\n"); | ||
| const next = replaceManagedBlock(current, body, "html"); | ||
@@ -750,13 +790,14 @@ await writeFileWithDirs(filePath, next, dryRun); | ||
| async function configureRoocode(projectDir, skillPaths, dryRun = false) { | ||
| async function configureRoocode(projectDir, skillPaths, useMcp = false, dryRun = false) { | ||
| 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"); | ||
| const body = useMcp | ||
| ? ["# Developer Stack Skills", "", ...MCP_INSTRUCTION_LINES, ""].join("\n") | ||
| : [ | ||
| "# Developer Stack Skills", | ||
| "", | ||
| "Load and follow these skill files before starting work:", | ||
| "", | ||
| ...skillPaths.map((skillPath) => `- ${skillPath}`), | ||
| "", | ||
| ].join("\n"); | ||
| await writeFileWithDirs(filePath, body, dryRun); | ||
@@ -794,3 +835,3 @@ return filePath; | ||
| const conventionsPath = path.join(installRoot, "project-conventions", "SKILL.md"); | ||
| configured.push({ agent: target, filePath: await configureClaude(projectDir, conventionsPath, context, dryRun) }); | ||
| configured.push({ agent: target, filePath: await configureClaude(projectDir, conventionsPath, context, configureMcpServer, dryRun) }); | ||
| const ruleFiles = await configureClaudeRules(projectDir, installRoot, dryRun); | ||
@@ -818,2 +859,5 @@ for (const ruleFilePath of ruleFiles) { | ||
| } | ||
| if (configureMcpServer) { | ||
| configured.push({ agent: "cursor-mcp", filePath: await configureCursorMcp(projectDir, packageInstallType, dryRun) }); | ||
| } | ||
| continue; | ||
@@ -823,3 +867,6 @@ } | ||
| if (target === "cline") { | ||
| configured.push({ agent: target, filePath: await configureCline(projectDir, skillPaths, dryRun) }); | ||
| configured.push({ agent: target, filePath: await configureCline(projectDir, skillPaths, configureMcpServer, dryRun) }); | ||
| if (configureMcpServer) { | ||
| configured.push({ agent: "cline-mcp", filePath: await configureSharedMcp(projectDir, packageInstallType, dryRun) }); | ||
| } | ||
| continue; | ||
@@ -829,3 +876,6 @@ } | ||
| if (target === "roocode") { | ||
| configured.push({ agent: target, filePath: await configureRoocode(projectDir, skillPaths, dryRun) }); | ||
| configured.push({ agent: target, filePath: await configureRoocode(projectDir, skillPaths, configureMcpServer, dryRun) }); | ||
| if (configureMcpServer) { | ||
| configured.push({ agent: "roocode-mcp", filePath: await configureSharedMcp(projectDir, packageInstallType, dryRun) }); | ||
| } | ||
| continue; | ||
@@ -932,2 +982,3 @@ } | ||
| } | ||
| configured.push({ agent: "cursor-mcp", filePath: await unconfigureCursorMcp(projectDir, dryRun) }); | ||
| continue; | ||
@@ -938,2 +989,3 @@ } | ||
| configured.push({ agent: target, filePath: await unconfigureCline(projectDir, dryRun) }); | ||
| configured.push({ agent: "cline-mcp", filePath: await unconfigureSharedMcp(projectDir, dryRun) }); | ||
| continue; | ||
@@ -944,2 +996,3 @@ } | ||
| configured.push({ agent: target, filePath: await unconfigureRoocode(projectDir, dryRun) }); | ||
| configured.push({ agent: "roocode-mcp", filePath: await unconfigureSharedMcp(projectDir, dryRun) }); | ||
| continue; | ||
@@ -1228,4 +1281,6 @@ } | ||
| configureCursor, | ||
| configureCursorMcp, | ||
| configureMcp, | ||
| configureRoocode, | ||
| configureSharedMcp, | ||
| detectPackageInstallType, | ||
@@ -1243,2 +1298,3 @@ detectPlatform, | ||
| removeManagedBlock, | ||
| removeMcpJsonEntry, | ||
| removeOurHookEntries, | ||
@@ -1250,4 +1306,7 @@ removeSkillsSectionItems, | ||
| runUninstall, | ||
| unconfigureCursorMcp, | ||
| unconfigureSharedMcp, | ||
| upsertSkillsSection, | ||
| validateArgs, | ||
| writeMcpJsonFile, | ||
| }; |
+24
-20
@@ -8,11 +8,11 @@ const path = require("path"); | ||
| "java-spring": { | ||
| description: "Java & Spring Boot 3 — JPA, REST APIs, JUnit 5, Mockito, Maven/Gradle", | ||
| description: "Java 25 & Spring Boot 4 / Spring 7 — 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", | ||
| description: "Python 3.14 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", | ||
| description: "Frontend — React 19+, Angular 21+, TypeScript, TanStack Query, Vitest, Playwright", | ||
| globs: ["**/*.tsx", "**/*.jsx", "**/*.ts", "**/*.js", "**/*.vue", "**/*.svelte", "**/package.json"], | ||
@@ -113,2 +113,9 @@ }, | ||
| function errorResponse(error_type, message, retryable = false) { | ||
| return { | ||
| isError: true, | ||
| content: [{ type: "text", text: JSON.stringify({ error_type, message, retryable }) }], | ||
| }; | ||
| } | ||
| async function handleTool(name, args) { | ||
@@ -127,13 +134,13 @@ if (name === "list_available_skills") { | ||
| if (!SKILL_META[stack_name]) { | ||
| return { | ||
| content: [{ type: "text", text: `Unknown skill: ${stack_name}. Available: ${SKILL_NAMES.join(", ")}` }], | ||
| isError: true, | ||
| }; | ||
| return errorResponse( | ||
| "INVALID_SKILL", | ||
| `Unknown skill: '${stack_name}'. Available: ${SKILL_NAMES.join(", ")}`, | ||
| ); | ||
| } | ||
| const content = await readSkillFile(stack_name); | ||
| if (!content) { | ||
| return { | ||
| content: [{ type: "text", text: `Skill file not found: ${stack_name}` }], | ||
| isError: true, | ||
| }; | ||
| return errorResponse( | ||
| "SKILL_NOT_FOUND", | ||
| `Skill '${stack_name}' file missing. Run: developer-stack-skills install`, | ||
| ); | ||
| } | ||
@@ -146,6 +153,6 @@ return { content: [{ type: "text", text: content }] }; | ||
| if (!content) { | ||
| return { | ||
| content: [{ type: "text", text: "project-conventions skill file not found." }], | ||
| isError: true, | ||
| }; | ||
| return errorResponse( | ||
| "SKILL_NOT_FOUND", | ||
| "Skill 'project-conventions' file missing. Run: developer-stack-skills install", | ||
| ); | ||
| } | ||
@@ -172,6 +179,3 @@ return { content: [{ type: "text", text: content }] }; | ||
| return { | ||
| content: [{ type: "text", text: `Unknown tool: ${name}` }], | ||
| isError: true, | ||
| }; | ||
| return errorResponse("UNKNOWN_TOOL", `Unknown tool: '${name}'`); | ||
| } | ||
@@ -205,2 +209,2 @@ | ||
| module.exports = { runMcpServer, detectStack, SKILL_META, SKILL_NAMES }; | ||
| module.exports = { runMcpServer, detectStack, handleTool, SKILL_META, SKILL_NAMES }; |
+2
-2
| { | ||
| "name": "developer-stack-skills", | ||
| "version": "2.0.0", | ||
| "version": "3.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.", | ||
@@ -33,3 +33,3 @@ "keywords": [ | ||
| "engines": { | ||
| "node": ">=18.0.0" | ||
| "node": ">=24.0.0" | ||
| }, | ||
@@ -36,0 +36,0 @@ "scripts": { |
@@ -13,3 +13,3 @@ --- | ||
| version: 1.0.0 | ||
| last-reviewed: 2026-05-15 | ||
| last-reviewed: 2026-05-20 | ||
| applies-to: Branching, commits, pull requests, ADRs, naming, environment config, documentation | ||
@@ -16,0 +16,0 @@ --- |
@@ -11,3 +11,3 @@ --- | ||
| version: 1.0.0 | ||
| last-reviewed: 2026-05-15 | ||
| last-reviewed: 2026-05-20 | ||
| applies-to: Python, FastAPI, Django, SQLAlchemy, background workers, CLI tools, testing | ||
@@ -366,9 +366,9 @@ --- | ||
| version = "0.1.0" | ||
| requires-python = ">=3.12" | ||
| requires-python = ">=3.14" | ||
| dependencies = [ | ||
| "fastapi>=0.115", | ||
| "uvicorn[standard]>=0.30", | ||
| "uvicorn[standard]>=0.34", | ||
| "sqlalchemy>=2.0", | ||
| "pydantic-settings>=2.0", | ||
| "asyncpg>=0.29", | ||
| "asyncpg>=0.30", | ||
| ] | ||
@@ -378,7 +378,7 @@ | ||
| dev = [ | ||
| "pytest>=8.0", | ||
| "pytest-asyncio>=0.23", | ||
| "httpx>=0.27", | ||
| "ruff>=0.4", | ||
| "mypy>=1.10", | ||
| "pytest>=8.3", | ||
| "pytest-asyncio>=0.24", | ||
| "httpx>=0.28", | ||
| "ruff>=1.0", | ||
| "mypy>=1.15", | ||
| ] | ||
@@ -385,0 +385,0 @@ ``` |
+5
-5
@@ -21,3 +21,3 @@ # developer-stack-skills | ||
| Version in this README: `2.0.0` | ||
| Version in this README: `3.0.0` | ||
@@ -128,3 +128,3 @@ 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`. | ||
| ```text | ||
| [developer-stack-skills] installing version 2.0.0 | ||
| [developer-stack-skills] installing version 3.0.0 | ||
| [developer-stack-skills] package install type: global | ||
@@ -328,5 +328,5 @@ [developer-stack-skills] skill install scope: global | ||
| - **Java**: Java 17+, Spring Boot 3.x, JPA/Hibernate, JUnit 5, Mockito | ||
| - **Python**: Python 3.12+, FastAPI, Pydantic v2, SQLAlchemy 2.x, pytest | ||
| - **Frontend**: React 18+, Angular 17+, TypeScript 5+, TanStack Query, Vitest | ||
| - **Java**: Java 25, Spring Boot 4 / Spring Framework 7, JPA/Hibernate, JUnit 5, Mockito | ||
| - **Python**: Python 3.14+, FastAPI, Pydantic v2, SQLAlchemy 2.x, pytest | ||
| - **Frontend**: React 19+, Angular 21+, TypeScript 5+, TanStack Query, Vitest | ||
| - **Testing**: JUnit 5, pytest, Vitest, Testing Library, Playwright, Testcontainers | ||
@@ -333,0 +333,0 @@ - **Conventions**: Conventional Commits, ADRs, Git Flow or Trunk-Based Development |
+72
-0
| # Release Notes | ||
| ## 3.0.0 - 2026-05-20 | ||
| This release makes MCP the primary delivery mechanism for all supported agents, introduces structured MCP error responses, updates all stack versions to current LTS, and expands multi-agent MCP configuration. | ||
| ### Breaking Changes | ||
| - **Node.js >=24 required.** Drops support for Node 18, 20, and 22. | ||
| - **MCP error responses are now structured JSON.** Tools that return errors now return `{"error_type": "...", "message": "...", "retryable": false}` instead of plain text. Consumers that parsed the error text string must be updated. | ||
| - **`configureCline` signature changed.** Third parameter is now `useMcp` (boolean), not `dryRun`. Update direct library calls: `configureCline(dir, paths, false, dryRun)`. | ||
| - **`configureRoocode` signature changed.** Same pattern: `configureRoocode(dir, paths, false, dryRun)`. | ||
| ### MCP-First Agent Configuration | ||
| All supported agents now receive MCP configuration and MCP-first instructions on install (when MCP is opted in): | ||
| - **Claude** — `CLAUDE.md` now instructs `detect_stack` + `get_skill` instead of preloading all SKILL.md files | ||
| - **Cursor** — installer now writes `.cursor/mcp.json` in addition to per-stack rule files | ||
| - **Cline** — installer now writes `.mcp.json` (project root) and updates `.clinerules` to MCP-first instructions | ||
| - **Roocode** — same: `.mcp.json` + MCP-first `.roo/rules/developer-stack-skills.md` | ||
| - **GitHub Copilot** — unchanged; no MCP support; path-based instructions retained | ||
| New installer API: | ||
| | Function | Description | | ||
| |---|---| | ||
| | `configureCursorMcp(dir, type, dryRun)` | Writes `.cursor/mcp.json` | | ||
| | `configureSharedMcp(dir, type, dryRun)` | Writes `.mcp.json` (shared Cline/Roocode) | | ||
| | `writeMcpJsonFile(filePath, type, dryRun)` | Shared low-level helper for any MCP JSON file | | ||
| | `removeMcpJsonEntry(filePath, dryRun)` | Removes our entry, preserves others, deletes if empty | | ||
| | `unconfigureCursorMcp(dir, dryRun)` | Cleanup for `.cursor/mcp.json` | | ||
| | `unconfigureSharedMcp(dir, dryRun)` | Cleanup for `.mcp.json` | | ||
| ### MCP Structured Errors | ||
| MCP tools now return structured error JSON on failure: | ||
| ```json | ||
| { "error_type": "INVALID_SKILL", "message": "Unknown skill: 'cobol'. Available: ...", "retryable": false } | ||
| ``` | ||
| Error types: `INVALID_SKILL`, `SKILL_NOT_FOUND`, `UNKNOWN_TOOL`. | ||
| ### Commands Updated to Use MCP Tools | ||
| `implement-feature`, `write-tests`, and `add-endpoint` slash commands now call `detect_stack` and `get_skill` MCP tools instead of manually inspecting root files. Stack detection logic lives in one place — the MCP server — not duplicated in markdown. | ||
| ### Language and Framework Version Updates | ||
| | Stack | Previous | Now | | ||
| |---|---|---| | ||
| | Java | Java 17+ | **Java 25** | | ||
| | Spring Boot | Spring Boot 3.x | **Spring Boot 4 / Spring Framework 7** | | ||
| | PostgreSQL (Testcontainers) | postgres:16 | **postgres:18** | | ||
| | Python | Python 3.12+ | **Python 3.14+** | | ||
| | React | React 18+ | **React 19+** | | ||
| | Angular | Angular 17+ | **Angular 21+** | | ||
| | Node.js (engine) | >=18.0.0 | **>=24.0.0** | | ||
| | ruff | >=0.4 | **>=1.0** | | ||
| | mypy | >=1.10 | **>=1.15** | | ||
| | pytest | >=8.0 | **>=8.3** | | ||
| | pytest-asyncio | >=0.23 | **>=0.24** | | ||
| | httpx | >=0.27 | **>=0.28** | | ||
| | uvicorn | >=0.30 | **>=0.34** | | ||
| ### Testing Updates | ||
| - `@MockBean` replaced by `@MockitoBean` in all Java examples (removed in Spring Boot 4) | ||
| - Angular testing migrated from Jasmine+Karma (removed Angular 18) to **Jest**; `HttpClientTestingModule` replaced by `provideHttpClient()` + `provideHttpClientTesting()` | ||
| - 33 new tests added covering MCP routing, structured errors, idempotency, and unconfigure cleanup | ||
| --- | ||
| ## 2.0.0 - 2026-05-16 | ||
@@ -4,0 +76,0 @@ |
+15
-9
@@ -11,3 +11,3 @@ --- | ||
| version: 1.0.0 | ||
| last-reviewed: 2026-05-15 | ||
| last-reviewed: 2026-05-20 | ||
| applies-to: Unit tests, integration tests, E2E tests, test reviews, test debugging | ||
@@ -119,3 +119,3 @@ --- | ||
| @Autowired private MockMvc mockMvc; | ||
| @MockBean private UserService userService; | ||
| @MockitoBean private UserService userService; // replaces @MockBean (removed Spring Boot 4) | ||
| @Autowired private ObjectMapper objectMapper; | ||
@@ -151,3 +151,3 @@ | ||
| @Container | ||
| static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine"); | ||
| static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:18-alpine"); | ||
@@ -303,4 +303,6 @@ @DynamicPropertySource | ||
| ## Angular — Jasmine + Karma | ||
| ## Angular — Jest (Angular 18+) | ||
| > **Karma removed in Angular 18.** New projects use Jest (recommended) or Web Test Runner. `ng generate` no longer scaffolds Karma config. Migrate existing projects: `ng generate jest-config` or use the `@angular-builders/jest` builder. | ||
| ### Component Test | ||
@@ -310,7 +312,7 @@ ```typescript | ||
| let fixture: ComponentFixture<UserCardComponent>; | ||
| let userServiceSpy: jasmine.SpyObj<UserService>; | ||
| let userServiceSpy: jest.Mocked<UserService>; | ||
| beforeEach(async () => { | ||
| userServiceSpy = jasmine.createSpyObj("UserService", ["getUser"]); | ||
| userServiceSpy.getUser.and.returnValue( | ||
| userServiceSpy = { getUser: jest.fn() } as jest.Mocked<UserService>; | ||
| userServiceSpy.getUser.mockReturnValue( | ||
| of({ id: 1, name: "Alice", email: "alice@example.com" }) | ||
@@ -338,2 +340,5 @@ ); | ||
| ```typescript | ||
| import { provideHttpClient } from "@angular/common/http"; | ||
| import { provideHttpClientTesting, HttpTestingController } from "@angular/common/http/testing"; | ||
| describe("UserService", () => { | ||
@@ -345,4 +350,3 @@ let service: UserService; | ||
| TestBed.configureTestingModule({ | ||
| imports: [HttpClientTestingModule], | ||
| providers: [UserService], | ||
| providers: [UserService, provideHttpClient(), provideHttpClientTesting()], | ||
| }); | ||
@@ -367,2 +371,4 @@ service = TestBed.inject(UserService); | ||
| > `HttpClientTestingModule` is deprecated Angular 18+. Use `provideHttpClient()` + `provideHttpClientTesting()` instead. | ||
| --- | ||
@@ -369,0 +375,0 @@ |
Sorry, the diff of this file is not supported yet
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
155014
8.3%36
5.88%1414
6.16%8
-11.11%