superkit-mcp-server 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +52 -3
- package/SUPERKIT.md +32 -2
- package/build/index.js +275 -22
- package/build/tools/ProjectAssets.js +177 -0
- package/package.json +4 -1
- package/build/__tests__/test_apply_prompt_args.js +0 -104
- package/build/tools/__tests__/archTools.test.js +0 -42
- package/build/tools/__tests__/compoundTools.test.js +0 -60
- package/build/tools/__tests__/docsTools.test.js +0 -44
- package/build/tools/__tests__/gitTools.test.js +0 -45
- package/build/tools/__tests__/loggerTools.test.js +0 -74
- package/build/tools/__tests__/todoTools.test.js +0 -73
- package/build/tools/validators/__tests__/apiSchema.test.js +0 -77
- package/build/tools/validators/__tests__/convertRules.test.js +0 -38
- package/build/tools/validators/__tests__/frontendDesign.test.js +0 -55
- package/build/tools/validators/__tests__/geoChecker.test.js +0 -45
- package/build/tools/validators/__tests__/i18nChecker.test.js +0 -32
- package/build/tools/validators/__tests__/lintRunner.test.js +0 -65
- package/build/tools/validators/__tests__/mobileAudit.test.js +0 -40
- package/build/tools/validators/__tests__/playwrightRunner.test.js +0 -55
- package/build/tools/validators/__tests__/reactPerformanceChecker.test.js +0 -49
- package/build/tools/validators/__tests__/securityScan.test.js +0 -42
- package/build/tools/validators/__tests__/seoChecker.test.js +0 -44
- package/build/tools/validators/__tests__/testRunner.test.js +0 -49
- package/build/tools/validators/__tests__/typeCoverage.test.js +0 -62
package/ARCHITECTURE.md
CHANGED
|
@@ -17,17 +17,25 @@ Super-Kit is a model-agnostic and agent-agnostic toolkit designed to provide a h
|
|
|
17
17
|
## 🏗️ Directory Structure
|
|
18
18
|
|
|
19
19
|
```plaintext
|
|
20
|
-
super-kit/
|
|
20
|
+
super-kit/ # Global Super-Kit package (npm: superkit-mcp-server)
|
|
21
21
|
├── ARCHITECTURE.md # This file
|
|
22
22
|
├── SUPERKIT.md # Global rules and activation protocol
|
|
23
23
|
├── .core/ # Core engine-independent logic
|
|
24
24
|
│ ├── rules/ # Universal mandates (e.g., clean-code, security-first)
|
|
25
|
-
├── agents/ # The T-Shaped AI Team Personas
|
|
26
|
-
├── skills/ # The Knowledge Modules
|
|
25
|
+
├── agents/ # The T-Shaped AI Team Personas [source: "global"]
|
|
26
|
+
├── skills/ # The Knowledge Modules [source: "global"]
|
|
27
27
|
│ ├── meta/ # Session-resume, compound-docs, file-todos
|
|
28
28
|
│ ├── tech/ # Node.js, React, Python, Prisma
|
|
29
29
|
│ └── workflows/ # TDD, CI/CD, Code Review checklists
|
|
30
30
|
└── workflows/ # Slash commands and lifecycle loops
|
|
31
|
+
|
|
32
|
+
{your-project}/ # Any project using Super-Kit
|
|
33
|
+
└── .agents/ # Project-local assets [source: "project"]
|
|
34
|
+
├── agents/ # Custom agent .md files (e.g., my-domain-expert.md)
|
|
35
|
+
├── skills/
|
|
36
|
+
│ ├── tech/ # Tech skill dirs, each with a SKILL.md
|
|
37
|
+
│ └── meta/ # Meta skill dirs, each with a SKILL.md
|
|
38
|
+
└── workflows/ # Custom workflow .md files
|
|
31
39
|
```
|
|
32
40
|
|
|
33
41
|
---
|
|
@@ -100,3 +108,44 @@ The entry point is reading `SUPERKIT.md` to establish global rules.
|
|
|
100
108
|
- **Aider**: Run aider with the message `aider --message "Read SUPERKIT.md for your system instructions before doing anything else."`
|
|
101
109
|
|
|
102
110
|
Once the agent has loaded `SUPERKIT.md`, it will follow the instructions to activate the appropriate `@agent` which dynamically reads `SKILL.md` from the relevant directories.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 🗂️ Project-Based Assets
|
|
115
|
+
|
|
116
|
+
Super-Kit supports a two-scope asset system that allows any project to ship its own agents, skills, and workflows alongside the global Super-Kit package assets.
|
|
117
|
+
|
|
118
|
+
### Scope Definitions
|
|
119
|
+
|
|
120
|
+
| Scope | Source | Location | `source` Label |
|
|
121
|
+
|-------|--------|----------|----------------|
|
|
122
|
+
| **Global** | `superkit-mcp-server` npm package | `super-kit/agents/`, `super-kit/skills/` | `"global"` |
|
|
123
|
+
| **Project** | User's project `.agents/` folder | `{project-root}/.agents/` | `"project"` |
|
|
124
|
+
|
|
125
|
+
### Resolution Rules
|
|
126
|
+
|
|
127
|
+
- Project assets **complement** global assets — they never override or shadow them.
|
|
128
|
+
- If `.agents/` does not exist in a project, all project-scoped tools return empty results gracefully (no errors thrown).
|
|
129
|
+
- Asset names are validated to prevent path traversal — absolute paths and `..` components are rejected.
|
|
130
|
+
|
|
131
|
+
### MCP Tool Mapping
|
|
132
|
+
|
|
133
|
+
| Tool | Scope | Description |
|
|
134
|
+
|------|-------|-------------|
|
|
135
|
+
| `list_superkit_assets` | Global (default) | Lists global assets. Accepts `scope: "all"` to merge both scopes with source labels. |
|
|
136
|
+
| `load_superkit_agent` | Global | Loads an agent from the Super-Kit package. |
|
|
137
|
+
| `load_superkit_skill` | Global | Loads a skill from the Super-Kit package. |
|
|
138
|
+
| `load_superkit_workflow` | Global | Loads a workflow from the Super-Kit package. |
|
|
139
|
+
| `list_project_assets` | Project | Lists project-local assets from `.agents/`. Falls back to `process.cwd()` if no `projectPath` given. |
|
|
140
|
+
| `load_project_agent` | Project | Loads an agent from `{projectPath}/.agents/agents/`. |
|
|
141
|
+
| `load_project_skill` | Project | Loads a skill from `{projectPath}/.agents/skills/{category}/{skillName}/SKILL.md`. |
|
|
142
|
+
| `load_project_workflow` | Project | Loads a workflow from `{projectPath}/.agents/workflows/`. |
|
|
143
|
+
|
|
144
|
+
### Recommended Discovery Order
|
|
145
|
+
|
|
146
|
+
When an agent starts work on a project, it should:
|
|
147
|
+
|
|
148
|
+
1. Call `list_project_assets` to discover what the project provides.
|
|
149
|
+
2. Load project-specific agents/skills/workflows first (`load_project_*`).
|
|
150
|
+
3. Fall back to global Super-Kit assets (`load_superkit_*`) for anything not covered.
|
|
151
|
+
4. Use `list_superkit_assets({ scope: "all" })` for a unified merged view with source labels.
|
package/SUPERKIT.md
CHANGED
|
@@ -85,12 +85,18 @@ When user says: "Always use TypeScript strict mode"
|
|
|
85
85
|
|
|
86
86
|
## Available Tools
|
|
87
87
|
|
|
88
|
-
**Super-Kit MCP Tools:**
|
|
89
|
-
- `list_superkit_assets` - Lists all available agents, skills, and workflows.
|
|
88
|
+
**Super-Kit MCP Tools (Global Scope):**
|
|
89
|
+
- `list_superkit_assets` - Lists all available agents, skills, and workflows. Accepts optional `scope` (`"global"` | `"project"` | `"all"`) and `projectPath` params.
|
|
90
90
|
- `load_superkit_agent` - Loads Markdown instructions for an agent (e.g., `data-engineer`).
|
|
91
91
|
- `load_superkit_skill` - Loads Markdown instructions for a skill (e.g., `tech`, `api-patterns`).
|
|
92
92
|
- `load_superkit_workflow` - Loads a workflow guide (e.g., `work`, `explore`).
|
|
93
93
|
|
|
94
|
+
**Project-Scoped MCP Tools:**
|
|
95
|
+
- `list_project_assets` - Lists project-scoped agents, skills, and workflows from the `.agents/` folder.
|
|
96
|
+
- `load_project_agent` - Loads a project-scoped agent from `{projectPath}/.agents/agents/`.
|
|
97
|
+
- `load_project_skill` - Loads a project-scoped skill's `SKILL.md` from `{projectPath}/.agents/skills/`.
|
|
98
|
+
- `load_project_workflow` - Loads a project-scoped workflow from `{projectPath}/.agents/workflows/`.
|
|
99
|
+
|
|
94
100
|
**Core Development Tools:**
|
|
95
101
|
- `kit_create_checkpoint` - Create checkpoint before changes
|
|
96
102
|
- `kit_restore_checkpoint` - Restore checkpoint if needed
|
|
@@ -103,6 +109,30 @@ When user says: "Always use TypeScript strict mode"
|
|
|
103
109
|
- `kit_save_learning` - **Save lesson from user feedback**
|
|
104
110
|
- `kit_get_learnings` - Read saved learnings
|
|
105
111
|
|
|
112
|
+
## 🗂️ Project-Based Assets
|
|
113
|
+
|
|
114
|
+
Any project can define its own agents, skills, and workflows by creating a `.agents/` folder at the project root:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
{project-root}/
|
|
118
|
+
└── .agents/
|
|
119
|
+
├── agents/ # Custom agent .md files (e.g., my-domain-expert.md)
|
|
120
|
+
├── skills/
|
|
121
|
+
│ ├── tech/ # Tech skill dirs, each containing a SKILL.md
|
|
122
|
+
│ └── meta/ # Meta skill dirs, each containing a SKILL.md
|
|
123
|
+
└── workflows/ # Custom workflow .md files (e.g., deploy-staging.md)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Resolution rules:**
|
|
127
|
+
- Project assets have `"source": "project"` and **complement** (do not replace) global Super-Kit assets.
|
|
128
|
+
- Global Super-Kit assets always have `"source": "global"`.
|
|
129
|
+
- If `.agents/` does not exist, all project-scoped tools return empty results gracefully — no errors.
|
|
130
|
+
|
|
131
|
+
**When starting work on ANY project, ALWAYS:**
|
|
132
|
+
1. Call `list_project_assets` (or `list_superkit_assets` with `scope: "all"`) to discover project-specific agents, skills, and workflows.
|
|
133
|
+
2. Load project assets with `load_project_agent`, `load_project_skill`, or `load_project_workflow` before falling back to global equivalents.
|
|
134
|
+
3. Use global assets (`load_superkit_agent`, etc.) for anything not covered by the project's `.agents/` folder.
|
|
135
|
+
|
|
106
136
|
## Documentation Management
|
|
107
137
|
|
|
108
138
|
- Docs location: `./docs/`
|
package/build/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { compoundSearch, updateSolutionRef, validateCompound, auditStateDrift, s
|
|
|
16
16
|
import { bootstrapFolderDocs, checkDocsFreshness, discoverUndocumentedFolders, validateFolderDocs, } from "./tools/docsTools.js";
|
|
17
17
|
import { generateChangelog, validateChangelog, archiveCompleted, prePushHousekeeping, } from "./tools/gitTools.js";
|
|
18
18
|
import { validateSpecConsistency, completePlan, validateArchitecture, syncSpec, updateSpecPhase, } from "./tools/archTools.js";
|
|
19
|
+
import { list_project_agents, list_project_skills, list_project_workflows, load_project_agent_file, load_project_skill_file, load_project_workflow_file, } from "./tools/ProjectAssets.js";
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = path.dirname(__filename);
|
|
21
22
|
const superKitRoot = path.resolve(__dirname, "../");
|
|
@@ -119,7 +120,23 @@ const TOOLS = [
|
|
|
119
120
|
},
|
|
120
121
|
{
|
|
121
122
|
name: "call_tool_todo_manager",
|
|
122
|
-
description:
|
|
123
|
+
description: `Manages todos. Per-action usage:
|
|
124
|
+
|
|
125
|
+
• nextId — Returns the next available todo ID. No extra params needed.
|
|
126
|
+
|
|
127
|
+
• create — Creates a new todo file. Required: title (string), description (1-2 sentence problem statement), priority ("p0"|"p1"|"p2"|"p3"), criteria (string array of acceptance criteria). Optional: projectPath.
|
|
128
|
+
Example: { action: "create", title: "Add auth", description: "Implement JWT login.", priority: "p2", criteria: ["User can log in", "Token is stored"], projectPath: "." }
|
|
129
|
+
|
|
130
|
+
• start — Marks a todo as in-progress. Required: todoId = RELATIVE PATH to the todo file, e.g. "todos/001-pending-p2-my-task.md". The file must exist at that path relative to projectPath.
|
|
131
|
+
Example: { action: "start", todoId: "todos/001-pending-p2-my-task.md", projectPath: "." }
|
|
132
|
+
|
|
133
|
+
• done — Marks a todo as done. Required: todoId = relative path (same format as start). All acceptance criteria checkboxes must be checked, or pass force: true to bypass.
|
|
134
|
+
Example: { action: "done", todoId: "todos/001-in-progress-p2-my-task.md", force: true, projectPath: "." }
|
|
135
|
+
|
|
136
|
+
• complete — Marks a todo as complete (final state). Required: todoId = relative path (same format as start/done).
|
|
137
|
+
Example: { action: "complete", todoId: "todos/001-done-p2-my-task.md", force: true, projectPath: "." }
|
|
138
|
+
|
|
139
|
+
⚠️ IMPORTANT: todoId must be the FULL RELATIVE FILE PATH (e.g. "todos/001-in-progress-p2-my-task.md"), NOT just the numeric ID ("001"). The filename changes with each status transition, so always use the current filename on disk.`,
|
|
123
140
|
inputSchema: {
|
|
124
141
|
type: "object",
|
|
125
142
|
properties: {
|
|
@@ -127,12 +144,33 @@ const TOOLS = [
|
|
|
127
144
|
type: "string",
|
|
128
145
|
enum: ["nextId", "create", "start", "done", "complete"],
|
|
129
146
|
},
|
|
130
|
-
title: {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
title: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "Todo title. Used by: create.",
|
|
150
|
+
},
|
|
151
|
+
description: {
|
|
152
|
+
type: "string",
|
|
153
|
+
description: "1-2 sentence problem statement. Used by: create.",
|
|
154
|
+
},
|
|
155
|
+
priority: {
|
|
156
|
+
type: "string",
|
|
157
|
+
enum: ["p0", "p1", "p2", "p3"],
|
|
158
|
+
description: "Priority level. p0=critical, p1=urgent, p2=normal, p3=low. Used by: create.",
|
|
159
|
+
},
|
|
160
|
+
criteria: {
|
|
161
|
+
type: "array",
|
|
162
|
+
items: { type: "string" },
|
|
163
|
+
description: "Acceptance criteria checklist items. Used by: create.",
|
|
164
|
+
},
|
|
165
|
+
todoId: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "RELATIVE PATH to the todo file from projectPath, e.g. 'todos/001-pending-p2-my-task.md'. NOT just the numeric ID. Used by: start, done, complete.",
|
|
168
|
+
},
|
|
169
|
+
force: {
|
|
170
|
+
type: "boolean",
|
|
171
|
+
default: false,
|
|
172
|
+
description: "Bypass terminal-state or unchecked-criteria guards. Used by: start, done, complete.",
|
|
173
|
+
},
|
|
136
174
|
projectPath: { type: "string", default: "." },
|
|
137
175
|
},
|
|
138
176
|
required: ["action", "projectPath"],
|
|
@@ -233,10 +271,93 @@ const TOOLS = [
|
|
|
233
271
|
description: "Lists all available agents, skills, and workflows in the Super-Kit repository.",
|
|
234
272
|
inputSchema: {
|
|
235
273
|
type: "object",
|
|
236
|
-
properties: {
|
|
274
|
+
properties: {
|
|
275
|
+
scope: {
|
|
276
|
+
type: "string",
|
|
277
|
+
enum: ["global", "project", "all"],
|
|
278
|
+
default: "global",
|
|
279
|
+
description: "Which scope to list: 'global' (superkit package assets), 'project' (.agents/ folder assets), or 'all' (merged with source labels on every entry).",
|
|
280
|
+
},
|
|
281
|
+
projectPath: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "Project root path used when scope includes 'project'. Defaults to process.cwd().",
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
required: [],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "list_project_assets",
|
|
291
|
+
description: "Lists project-scoped agents, skills, and workflows from the .agents/ folder in the given project directory. Falls back to process.cwd() if no projectPath is given.",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
projectPath: {
|
|
296
|
+
type: "string",
|
|
297
|
+
description: "Absolute path to the project root. Defaults to process.cwd().",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
237
300
|
required: [],
|
|
238
301
|
},
|
|
239
302
|
},
|
|
303
|
+
{
|
|
304
|
+
name: "load_project_agent",
|
|
305
|
+
description: "Loads a project-scoped agent markdown file from {projectPath}/.agents/agents/{agentName}.md",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
agentName: {
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "Agent name without .md extension.",
|
|
312
|
+
},
|
|
313
|
+
projectPath: {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Absolute path to the project root. Defaults to process.cwd().",
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
required: ["agentName"],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "load_project_skill",
|
|
323
|
+
description: "Loads a project-scoped skill's SKILL.md from {projectPath}/.agents/skills/{category}/{skillName}/SKILL.md",
|
|
324
|
+
inputSchema: {
|
|
325
|
+
type: "object",
|
|
326
|
+
properties: {
|
|
327
|
+
category: {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "Skill category: 'tech' or 'meta'.",
|
|
330
|
+
},
|
|
331
|
+
skillName: {
|
|
332
|
+
type: "string",
|
|
333
|
+
description: "Skill directory name.",
|
|
334
|
+
},
|
|
335
|
+
projectPath: {
|
|
336
|
+
type: "string",
|
|
337
|
+
description: "Absolute path to the project root. Defaults to process.cwd().",
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
required: ["category", "skillName"],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: "load_project_workflow",
|
|
345
|
+
description: "Loads a project-scoped workflow markdown file from {projectPath}/.agents/workflows/{workflowName}.md",
|
|
346
|
+
inputSchema: {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {
|
|
349
|
+
workflowName: {
|
|
350
|
+
type: "string",
|
|
351
|
+
description: "Workflow name without .md extension.",
|
|
352
|
+
},
|
|
353
|
+
projectPath: {
|
|
354
|
+
type: "string",
|
|
355
|
+
description: "Absolute path to the project root. Defaults to process.cwd().",
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
required: ["workflowName"],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
240
361
|
{
|
|
241
362
|
name: "load_superkit_agent",
|
|
242
363
|
description: "Loads the instruction markdown for a specific specialist agent.",
|
|
@@ -493,29 +614,106 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
493
614
|
return { content: [{ type: "text", text: res }] };
|
|
494
615
|
}
|
|
495
616
|
if (request.params.name === "list_superkit_assets") {
|
|
617
|
+
const args = request.params.arguments;
|
|
618
|
+
const scope = args?.scope ?? "global";
|
|
619
|
+
const projectPath = args?.projectPath;
|
|
496
620
|
const agentsPath = path.join(superKitRoot, "agents");
|
|
497
621
|
const skillsTechPath = path.join(superKitRoot, "skills", "tech");
|
|
498
622
|
const skillsMetaPath = path.join(superKitRoot, "skills", "meta");
|
|
499
623
|
const workflowsPath = path.join(superKitRoot, "skills", "workflows");
|
|
500
624
|
const commandsPath = path.join(superKitRoot, "commands");
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
625
|
+
// Build global asset lists (used for scope "global" or "all")
|
|
626
|
+
let globalData = null;
|
|
627
|
+
if (scope !== "project") {
|
|
628
|
+
const agents = await listDirectorySafe(agentsPath);
|
|
629
|
+
const techSkills = await listDirectorySafe(skillsTechPath);
|
|
630
|
+
const metaSkills = await listDirectorySafe(skillsMetaPath);
|
|
631
|
+
const workflows = await listDirectorySafe(workflowsPath);
|
|
632
|
+
const commands = await listDirectorySafe(commandsPath);
|
|
633
|
+
if (scope === "global") {
|
|
634
|
+
// Original backward-compatible format — no source labels
|
|
635
|
+
globalData = {
|
|
636
|
+
agents: agents.map((a) => a.replace(".md", "")),
|
|
637
|
+
skills: {
|
|
638
|
+
tech: techSkills.map((s) => s.replace("/", "")),
|
|
639
|
+
meta: metaSkills.map((s) => s.replace("/", "")),
|
|
640
|
+
},
|
|
641
|
+
workflows: workflows.map((w) => w.replace(".md", "")),
|
|
642
|
+
commands: commands.map((c) => c.replace(".toml", "")),
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
// "all" scope — include source labels on every entry
|
|
647
|
+
globalData = {
|
|
648
|
+
agents: agents.map((a) => ({
|
|
649
|
+
name: a.replace(".md", ""),
|
|
650
|
+
source: "global",
|
|
651
|
+
})),
|
|
652
|
+
skills: {
|
|
653
|
+
tech: techSkills.map((s) => ({
|
|
654
|
+
name: s.replace("/", ""),
|
|
655
|
+
source: "global",
|
|
656
|
+
})),
|
|
657
|
+
meta: metaSkills.map((s) => ({
|
|
658
|
+
name: s.replace("/", ""),
|
|
659
|
+
source: "global",
|
|
660
|
+
})),
|
|
661
|
+
},
|
|
662
|
+
workflows: workflows.map((w) => ({
|
|
663
|
+
name: w.replace(".md", ""),
|
|
664
|
+
source: "global",
|
|
665
|
+
})),
|
|
666
|
+
commands: commands.map((c) => ({
|
|
667
|
+
name: c.replace(".toml", ""),
|
|
668
|
+
source: "global",
|
|
669
|
+
})),
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Build project asset lists (used for scope "project" or "all")
|
|
674
|
+
let projectData = null;
|
|
675
|
+
if (scope !== "global") {
|
|
676
|
+
const [projAgents, projSkills, projWorkflows] = await Promise.all([
|
|
677
|
+
list_project_agents(projectPath),
|
|
678
|
+
list_project_skills(projectPath),
|
|
679
|
+
list_project_workflows(projectPath),
|
|
680
|
+
]);
|
|
681
|
+
projectData = {
|
|
682
|
+
agents: projAgents.map((a) => ({ name: a, source: "project" })),
|
|
683
|
+
skills: {
|
|
684
|
+
tech: projSkills.tech.map((s) => ({ name: s, source: "project" })),
|
|
685
|
+
meta: projSkills.meta.map((s) => ({ name: s, source: "project" })),
|
|
686
|
+
},
|
|
687
|
+
workflows: projWorkflows.map((w) => ({ name: w, source: "project" })),
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
// Compose the final response payload
|
|
691
|
+
let payload;
|
|
692
|
+
if (scope === "global") {
|
|
693
|
+
payload = globalData;
|
|
694
|
+
}
|
|
695
|
+
else if (scope === "project") {
|
|
696
|
+
payload = projectData;
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
// "all" — deep-merge both lists
|
|
700
|
+
const g = globalData;
|
|
701
|
+
const p = projectData;
|
|
702
|
+
payload = {
|
|
703
|
+
agents: [...g.agents, ...p.agents],
|
|
704
|
+
skills: {
|
|
705
|
+
tech: [...g.skills.tech, ...p.skills.tech],
|
|
706
|
+
meta: [...g.skills.meta, ...p.skills.meta],
|
|
707
|
+
},
|
|
708
|
+
workflows: [...g.workflows, ...p.workflows],
|
|
709
|
+
commands: g.commands,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
506
712
|
return {
|
|
507
713
|
content: [
|
|
508
714
|
{
|
|
509
715
|
type: "text",
|
|
510
|
-
text: JSON.stringify(
|
|
511
|
-
agents: agents.map((a) => a.replace(".md", "")),
|
|
512
|
-
skills: {
|
|
513
|
-
tech: techSkills.map((s) => s.replace("/", "")),
|
|
514
|
-
meta: metaSkills.map((s) => s.replace("/", "")),
|
|
515
|
-
},
|
|
516
|
-
workflows: workflows.map((w) => w.replace(".md", "")),
|
|
517
|
-
commands: commands.map((c) => c.replace(".toml", "")),
|
|
518
|
-
}, null, 2),
|
|
716
|
+
text: JSON.stringify(payload, null, 2),
|
|
519
717
|
},
|
|
520
718
|
],
|
|
521
719
|
};
|
|
@@ -573,6 +771,61 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
573
771
|
const content = await fs.readFile(safePath, "utf-8");
|
|
574
772
|
return { content: [{ type: "text", text: content }] };
|
|
575
773
|
}
|
|
774
|
+
if (request.params.name === "list_project_assets") {
|
|
775
|
+
const args = request.params.arguments;
|
|
776
|
+
const projectPath = args?.projectPath;
|
|
777
|
+
const [agents, skills, workflows] = await Promise.all([
|
|
778
|
+
list_project_agents(projectPath),
|
|
779
|
+
list_project_skills(projectPath),
|
|
780
|
+
list_project_workflows(projectPath),
|
|
781
|
+
]);
|
|
782
|
+
return {
|
|
783
|
+
content: [
|
|
784
|
+
{
|
|
785
|
+
type: "text",
|
|
786
|
+
text: JSON.stringify({
|
|
787
|
+
source: "project",
|
|
788
|
+
agents: agents.map((a) => ({ name: a, source: "project" })),
|
|
789
|
+
skills: {
|
|
790
|
+
tech: skills.tech.map((s) => ({
|
|
791
|
+
name: s,
|
|
792
|
+
source: "project",
|
|
793
|
+
})),
|
|
794
|
+
meta: skills.meta.map((s) => ({
|
|
795
|
+
name: s,
|
|
796
|
+
source: "project",
|
|
797
|
+
})),
|
|
798
|
+
},
|
|
799
|
+
workflows: workflows.map((w) => ({
|
|
800
|
+
name: w,
|
|
801
|
+
source: "project",
|
|
802
|
+
})),
|
|
803
|
+
}, null, 2),
|
|
804
|
+
},
|
|
805
|
+
],
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (request.params.name === "load_project_agent") {
|
|
809
|
+
const args = request.params.arguments;
|
|
810
|
+
if (!args.agentName)
|
|
811
|
+
throw new Error("Missing agentName");
|
|
812
|
+
const content = await load_project_agent_file(args.agentName, args.projectPath);
|
|
813
|
+
return { content: [{ type: "text", text: content }] };
|
|
814
|
+
}
|
|
815
|
+
if (request.params.name === "load_project_skill") {
|
|
816
|
+
const args = request.params.arguments;
|
|
817
|
+
if (!args.category || !args.skillName)
|
|
818
|
+
throw new Error("Missing category or skillName");
|
|
819
|
+
const content = await load_project_skill_file(args.category, args.skillName, args.projectPath);
|
|
820
|
+
return { content: [{ type: "text", text: content }] };
|
|
821
|
+
}
|
|
822
|
+
if (request.params.name === "load_project_workflow") {
|
|
823
|
+
const args = request.params.arguments;
|
|
824
|
+
if (!args.workflowName)
|
|
825
|
+
throw new Error("Missing workflowName");
|
|
826
|
+
const content = await load_project_workflow_file(args.workflowName, args.projectPath);
|
|
827
|
+
return { content: [{ type: "text", text: content }] };
|
|
828
|
+
}
|
|
576
829
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
577
830
|
}
|
|
578
831
|
catch (error) {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
// The conventional folder name for project-local Super-Kit assets
|
|
4
|
+
export const PROJECT_ASSETS_DIR = '.agents';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the effective project root path.
|
|
7
|
+
* - If an explicit projectPath is provided, it is resolved to an absolute path.
|
|
8
|
+
* - Otherwise, falls back to process.cwd().
|
|
9
|
+
*/
|
|
10
|
+
export function resolve_project_path(projectPath) {
|
|
11
|
+
if (projectPath) {
|
|
12
|
+
return path.resolve(projectPath);
|
|
13
|
+
}
|
|
14
|
+
return process.cwd();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns the absolute path to the .agents/ root for the given project.
|
|
18
|
+
*/
|
|
19
|
+
export function get_project_agents_root(projectPath) {
|
|
20
|
+
return path.join(resolve_project_path(projectPath), PROJECT_ASSETS_DIR);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Internal guard: ensures the resolved path stays strictly within the .agents/ root.
|
|
24
|
+
* Returns the resolved absolute path, or null if a traversal attempt is detected.
|
|
25
|
+
*/
|
|
26
|
+
function safe_project_path(agentsRoot, relative) {
|
|
27
|
+
// Normalize the root so the startsWith check is reliable on all platforms
|
|
28
|
+
const normalizedRoot = path.resolve(agentsRoot);
|
|
29
|
+
const resolved = path.resolve(agentsRoot, relative);
|
|
30
|
+
// On Windows path.resolve produces lower-cased drive letters consistently,
|
|
31
|
+
// so this comparison is safe cross-platform.
|
|
32
|
+
if (!resolved.startsWith(normalizedRoot + path.sep) && resolved !== normalizedRoot) {
|
|
33
|
+
return null; // traversal detected
|
|
34
|
+
}
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validates a user-supplied asset name (agentName, skillName, workflowName).
|
|
39
|
+
* Rejects absolute paths and anything containing path-separator characters.
|
|
40
|
+
*/
|
|
41
|
+
function validate_asset_name(name) {
|
|
42
|
+
if (!name || typeof name !== 'string') {
|
|
43
|
+
throw new Error('Asset name must be a non-empty string.');
|
|
44
|
+
}
|
|
45
|
+
if (path.isAbsolute(name)) {
|
|
46
|
+
throw new Error(`Asset name must not be an absolute path: "${name}"`);
|
|
47
|
+
}
|
|
48
|
+
// Reject any component that contains a path separator or navigates upward
|
|
49
|
+
const normalized = path.normalize(name);
|
|
50
|
+
if (normalized.includes('..') || normalized.startsWith('/') || normalized.startsWith('\\')) {
|
|
51
|
+
throw new Error(`Asset name contains invalid path components: "${name}"`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Listing helpers
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
/**
|
|
58
|
+
* Lists project-scoped agent names (without .md extension) from .agents/agents/.
|
|
59
|
+
* Returns an empty array if the directory does not exist.
|
|
60
|
+
*/
|
|
61
|
+
export async function list_project_agents(projectPath) {
|
|
62
|
+
const agentsRoot = get_project_agents_root(projectPath);
|
|
63
|
+
const agentsDir = path.join(agentsRoot, 'agents');
|
|
64
|
+
try {
|
|
65
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
66
|
+
return entries
|
|
67
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
68
|
+
.map((e) => e.name.replace(/\.md$/, ''));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Directory does not exist — graceful degradation
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Lists project-scoped skill directory names from .agents/skills/tech/ and .agents/skills/meta/.
|
|
77
|
+
* Returns empty arrays for each category if the directories do not exist.
|
|
78
|
+
*/
|
|
79
|
+
export async function list_project_skills(projectPath) {
|
|
80
|
+
const agentsRoot = get_project_agents_root(projectPath);
|
|
81
|
+
const list_category_skills = async (category) => {
|
|
82
|
+
const categoryDir = path.join(agentsRoot, 'skills', category);
|
|
83
|
+
try {
|
|
84
|
+
const entries = await fs.readdir(categoryDir, { withFileTypes: true });
|
|
85
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const [tech, meta] = await Promise.all([
|
|
92
|
+
list_category_skills('tech'),
|
|
93
|
+
list_category_skills('meta'),
|
|
94
|
+
]);
|
|
95
|
+
return { tech, meta };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Lists project-scoped workflow names (without .md extension) from .agents/workflows/.
|
|
99
|
+
* Returns an empty array if the directory does not exist.
|
|
100
|
+
*/
|
|
101
|
+
export async function list_project_workflows(projectPath) {
|
|
102
|
+
const agentsRoot = get_project_agents_root(projectPath);
|
|
103
|
+
const workflowsDir = path.join(agentsRoot, 'workflows');
|
|
104
|
+
try {
|
|
105
|
+
const entries = await fs.readdir(workflowsDir, { withFileTypes: true });
|
|
106
|
+
return entries
|
|
107
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
108
|
+
.map((e) => e.name.replace(/\.md$/, ''));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Loading helpers
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Loads a project-scoped agent's markdown content from:
|
|
119
|
+
* {projectPath}/.agents/agents/{agentName}.md
|
|
120
|
+
*/
|
|
121
|
+
export async function load_project_agent_file(agentName, projectPath) {
|
|
122
|
+
validate_asset_name(agentName);
|
|
123
|
+
const agentsRoot = get_project_agents_root(projectPath);
|
|
124
|
+
const relative = path.join('agents', `${agentName}.md`);
|
|
125
|
+
const safePath = safe_project_path(agentsRoot, relative);
|
|
126
|
+
if (!safePath) {
|
|
127
|
+
throw new Error(`Path traversal detected for agent name: "${agentName}"`);
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
return await fs.readFile(safePath, 'utf-8');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw new Error(`Project agent not found: "${agentName}". ` +
|
|
134
|
+
`Expected file at: ${safePath}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Loads a project-scoped skill's SKILL.md content from:
|
|
139
|
+
* {projectPath}/.agents/skills/{category}/{skillName}/SKILL.md
|
|
140
|
+
*/
|
|
141
|
+
export async function load_project_skill_file(category, skillName, projectPath) {
|
|
142
|
+
validate_asset_name(category);
|
|
143
|
+
validate_asset_name(skillName);
|
|
144
|
+
const agentsRoot = get_project_agents_root(projectPath);
|
|
145
|
+
const relative = path.join('skills', category, skillName, 'SKILL.md');
|
|
146
|
+
const safePath = safe_project_path(agentsRoot, relative);
|
|
147
|
+
if (!safePath) {
|
|
148
|
+
throw new Error(`Path traversal detected for skill: category="${category}", skillName="${skillName}"`);
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
return await fs.readFile(safePath, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
throw new Error(`Project skill not found: category="${category}", skillName="${skillName}". ` +
|
|
155
|
+
`Expected SKILL.md at: ${safePath}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Loads a project-scoped workflow's markdown content from:
|
|
160
|
+
* {projectPath}/.agents/workflows/{workflowName}.md
|
|
161
|
+
*/
|
|
162
|
+
export async function load_project_workflow_file(workflowName, projectPath) {
|
|
163
|
+
validate_asset_name(workflowName);
|
|
164
|
+
const agentsRoot = get_project_agents_root(projectPath);
|
|
165
|
+
const relative = path.join('workflows', `${workflowName}.md`);
|
|
166
|
+
const safePath = safe_project_path(agentsRoot, relative);
|
|
167
|
+
if (!safePath) {
|
|
168
|
+
throw new Error(`Path traversal detected for workflow name: "${workflowName}"`);
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
return await fs.readFile(safePath, 'utf-8');
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
throw new Error(`Project workflow not found: "${workflowName}". ` +
|
|
175
|
+
`Expected file at: ${safePath}`);
|
|
176
|
+
}
|
|
177
|
+
}
|