claude-autopm 3.21.0 → 3.22.0
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/autopm/.claude/commands/pm:diagram-list.md +12 -0
- package/autopm/.claude/commands/pm:diagram-new.md +85 -0
- package/autopm/.claude/commands/pm:diagram-show.md +14 -0
- package/autopm/.claude/commands/pm:diagram-update.md +24 -0
- package/autopm/.claude/mcp/autopm.md +44 -0
- package/autopm/.claude/scripts/mcp/autopm-server.js +325 -0
- package/autopm/.claude/scripts/pm/diagram-list.js +94 -0
- package/package.json +2 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
allowed-tools: Bash, Read, Write, Glob, Grep
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Diagram New
|
|
6
|
+
|
|
7
|
+
Create a project architecture diagram by analyzing the codebase.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
/pm:diagram-new <name> [--type architecture|modules|data-flow|dependencies]
|
|
11
|
+
|
|
12
|
+
## Instructions
|
|
13
|
+
|
|
14
|
+
1. Determine diagram type from --type flag or infer from project:
|
|
15
|
+
- **architecture**: High-level system components and how they connect
|
|
16
|
+
- **modules**: Import/require dependency graph between source files
|
|
17
|
+
- **data-flow**: How data moves through the system (API → service → DB)
|
|
18
|
+
- **dependencies**: package.json dependency tree
|
|
19
|
+
|
|
20
|
+
2. Analyze the project:
|
|
21
|
+
- Read package.json for dependencies and project type
|
|
22
|
+
- Scan src/ or main source directory for imports/requires
|
|
23
|
+
- Check for docker-compose.yml, Dockerfile
|
|
24
|
+
- Check for API routes (Express, FastAPI, etc.)
|
|
25
|
+
- Check for database models/schemas
|
|
26
|
+
- Check for .env for external service connections
|
|
27
|
+
**NEVER include actual .env values (tokens, passwords, secrets) in diagram or metadata.**
|
|
28
|
+
Only infer service names/types from variable names, not values.
|
|
29
|
+
|
|
30
|
+
3. Generate Mermaid diagram based on analysis:
|
|
31
|
+
- Use `graph TD` for hierarchical, `graph LR` for flow
|
|
32
|
+
- Group related modules in `subgraph` blocks
|
|
33
|
+
- Use icons: databases [(DB)], services [Service], external{{External}}
|
|
34
|
+
- Color-code using Mermaid classDef:
|
|
35
|
+
```
|
|
36
|
+
classDef active fill:#d4f8db,stroke:#2f855a
|
|
37
|
+
classDef config fill:#e2e8f0,stroke:#4a5568
|
|
38
|
+
classDef api fill:#ebf4ff,stroke:#2b6cb0
|
|
39
|
+
class FastAPI api
|
|
40
|
+
class PostgreSQL active
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
4. Save diagram:
|
|
44
|
+
```bash
|
|
45
|
+
mkdir -p .claude/pm/diagrams
|
|
46
|
+
```
|
|
47
|
+
Write Mermaid syntax to `.claude/pm/diagrams/<name>.mmd`
|
|
48
|
+
Write metadata to `.claude/pm/diagrams/<name>.meta.json`:
|
|
49
|
+
```json
|
|
50
|
+
{"name":"<name>","type":"<type>","created":"<ISO>","updated":"<ISO>","scope":"src/"}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
5. Display the diagram in terminal (show the Mermaid source)
|
|
54
|
+
|
|
55
|
+
## Example Output
|
|
56
|
+
|
|
57
|
+
For a FastAPI + React project:
|
|
58
|
+
```mermaid
|
|
59
|
+
graph TD
|
|
60
|
+
subgraph Frontend
|
|
61
|
+
React[React App]
|
|
62
|
+
Redux[Redux Store]
|
|
63
|
+
API_Client[API Client]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
subgraph Backend
|
|
67
|
+
FastAPI[FastAPI Server]
|
|
68
|
+
Auth[Auth Module]
|
|
69
|
+
Users[Users API]
|
|
70
|
+
ORM[SQLAlchemy ORM]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
subgraph Data
|
|
74
|
+
PostgreSQL[(PostgreSQL)]
|
|
75
|
+
Redis[(Redis Cache)]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
React --> API_Client
|
|
79
|
+
API_Client --> FastAPI
|
|
80
|
+
FastAPI --> Auth
|
|
81
|
+
FastAPI --> Users
|
|
82
|
+
Users --> ORM
|
|
83
|
+
ORM --> PostgreSQL
|
|
84
|
+
Auth --> Redis
|
|
85
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
allowed-tools: Bash, Read
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Diagram Show
|
|
6
|
+
|
|
7
|
+
Display a project diagram.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
```bash
|
|
11
|
+
node .claude/scripts/pm/diagram-list.js --show <name>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Shows the Mermaid source of the diagram. View rendered version in `/pm:dashboard` Diagrams tab.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
allowed-tools: Bash, Read, Write, Glob, Grep
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Diagram Update
|
|
6
|
+
|
|
7
|
+
Update an existing project diagram after code changes.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
/pm:diagram-update <name>
|
|
11
|
+
|
|
12
|
+
## Instructions
|
|
13
|
+
|
|
14
|
+
1. Read existing diagram from `.claude/pm/diagrams/<name>.mmd`
|
|
15
|
+
2. Read metadata from `.claude/pm/diagrams/<name>.meta.json` for type and scope
|
|
16
|
+
3. If either file is missing: `❌ Diagram not found: Run /pm:diagram-new <name>`
|
|
17
|
+
4. Re-analyze the project (same analysis as diagram-new, scoped to metadata scope)
|
|
18
|
+
5. Compare with existing diagram — identify:
|
|
19
|
+
- New modules/components added
|
|
20
|
+
- Removed modules
|
|
21
|
+
- Changed connections
|
|
22
|
+
6. Update the .mmd file with changes
|
|
23
|
+
7. Update metadata: set `updated` to current timestamp (`date -u +"%Y-%m-%dT%H:%M:%SZ"`)
|
|
24
|
+
8. Show diff summary: "Added: X, Removed: Y, Updated: Z connections"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: autopm
|
|
3
|
+
command: node
|
|
4
|
+
args: [".claude/scripts/mcp/autopm-server.js"]
|
|
5
|
+
description: AutoPM project management — issues, epics, PRDs, learnings, checkpoints
|
|
6
|
+
category: project-management
|
|
7
|
+
status: inactive
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# AutoPM MCP Server
|
|
11
|
+
|
|
12
|
+
Local MCP server exposing AutoPM project management data. Reuses local providers — same data as slash commands.
|
|
13
|
+
|
|
14
|
+
## Tools (13)
|
|
15
|
+
- `autopm_list_issues` — list issues (filter by status)
|
|
16
|
+
- `autopm_show_issue` — issue details
|
|
17
|
+
- `autopm_create_issue` — create new issue
|
|
18
|
+
- `autopm_start_issue` — start working (set in_progress)
|
|
19
|
+
- `autopm_close_issue` — close issue
|
|
20
|
+
- `autopm_list_epics` — list epics with progress
|
|
21
|
+
- `autopm_show_epic` — epic with tasks
|
|
22
|
+
- `autopm_list_prds` — list PRDs
|
|
23
|
+
- `autopm_show_prd` — PRD details
|
|
24
|
+
- `autopm_status` — project overview
|
|
25
|
+
- `autopm_learn` — save learning
|
|
26
|
+
- `autopm_recall` — get learnings
|
|
27
|
+
- `autopm_checkpoint` — create checkpoint
|
|
28
|
+
|
|
29
|
+
## Resources (5)
|
|
30
|
+
- `autopm://config` — config.json
|
|
31
|
+
- `autopm://agents` — agent-registry.xml
|
|
32
|
+
- `autopm://events` — recent events
|
|
33
|
+
- `autopm://learnings` — all learnings
|
|
34
|
+
- `autopm://test-plan` — test plan
|
|
35
|
+
|
|
36
|
+
## Prompts (3)
|
|
37
|
+
- `autopm_issue_template` — issue XML template
|
|
38
|
+
- `autopm_prd_template` — PRD XML template
|
|
39
|
+
- `autopm_epic_template` — epic XML template
|
|
40
|
+
|
|
41
|
+
## Enable
|
|
42
|
+
```bash
|
|
43
|
+
autopm mcp enable autopm
|
|
44
|
+
```
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AutoPM MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes PM data (issues, epics, PRDs, learnings, config) via Model Context Protocol.
|
|
6
|
+
* Reuses local providers as backend — no data duplication.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node .claude/scripts/mcp/autopm-server.js
|
|
10
|
+
*
|
|
11
|
+
* In .claude/mcp-servers.json:
|
|
12
|
+
* { "autopm": { "command": "node", "args": [".claude/scripts/mcp/autopm-server.js"] } }
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
16
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
17
|
+
const {
|
|
18
|
+
CallToolRequestSchema,
|
|
19
|
+
ListToolsRequestSchema,
|
|
20
|
+
ListResourcesRequestSchema,
|
|
21
|
+
ReadResourceRequestSchema,
|
|
22
|
+
ListPromptsRequestSchema,
|
|
23
|
+
GetPromptRequestSchema
|
|
24
|
+
} = require('@modelcontextprotocol/sdk/types.js');
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const basePath = process.cwd();
|
|
30
|
+
const settings = { basePath };
|
|
31
|
+
|
|
32
|
+
// Lazy-load providers to avoid errors when files don't exist
|
|
33
|
+
function loadProvider(name) {
|
|
34
|
+
const providerPath = path.join(basePath, '.claude', 'providers', 'local', name + '.js');
|
|
35
|
+
if (fs.existsSync(providerPath)) return require(providerPath);
|
|
36
|
+
// Fallback to autopm path
|
|
37
|
+
const autopmPath = path.join(__dirname, '..', '..', 'providers', 'local', name + '.js');
|
|
38
|
+
if (fs.existsSync(autopmPath)) return require(autopmPath);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readFile(relativePath) {
|
|
43
|
+
const fullPath = path.join(basePath, relativePath);
|
|
44
|
+
if (fs.existsSync(fullPath)) return fs.readFileSync(fullPath, 'utf8');
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readJSON(relativePath) {
|
|
49
|
+
const content = readFile(relativePath);
|
|
50
|
+
if (!content) return null;
|
|
51
|
+
try { return JSON.parse(content); } catch { return null; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Server Setup ──
|
|
55
|
+
|
|
56
|
+
const server = new Server(
|
|
57
|
+
{ name: 'autopm', version: '1.0.0' },
|
|
58
|
+
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// ── Tools ──
|
|
62
|
+
|
|
63
|
+
const TOOLS = [
|
|
64
|
+
{ name: 'autopm_list_issues', description: 'List local issues with optional status filter', inputSchema: { type: 'object', properties: { status: { type: 'string', description: 'Filter: open, in_progress, closed' } } } },
|
|
65
|
+
{ name: 'autopm_show_issue', description: 'Show issue details by ID', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Issue number' } }, required: ['id'] } },
|
|
66
|
+
{ name: 'autopm_create_issue', description: 'Create a new local issue', inputSchema: { type: 'object', properties: { title: { type: 'string' }, labels: { type: 'array', items: { type: 'string' } }, body: { type: 'string' } }, required: ['title'] } },
|
|
67
|
+
{ name: 'autopm_start_issue', description: 'Start working on an issue (set in_progress)', inputSchema: { type: 'object', properties: { id: { type: 'number' }, no_branch: { type: 'boolean' } }, required: ['id'] } },
|
|
68
|
+
{ name: 'autopm_close_issue', description: 'Close an issue', inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] } },
|
|
69
|
+
{ name: 'autopm_list_epics', description: 'List epics with progress', inputSchema: { type: 'object', properties: { status: { type: 'string' } } } },
|
|
70
|
+
{ name: 'autopm_show_epic', description: 'Show epic with tasks', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
71
|
+
{ name: 'autopm_list_prds', description: 'List PRDs', inputSchema: { type: 'object', properties: { status: { type: 'string' } } } },
|
|
72
|
+
{ name: 'autopm_show_prd', description: 'Show PRD details', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
73
|
+
{ name: 'autopm_status', description: 'Project overview — counts, recent activity', inputSchema: { type: 'object', properties: {} } },
|
|
74
|
+
{ name: 'autopm_learn', description: 'Save a project learning', inputSchema: { type: 'object', properties: { learning: { type: 'string' }, tags: { type: 'array', items: { type: 'string' } } }, required: ['learning'] } },
|
|
75
|
+
{ name: 'autopm_recall', description: 'Get project learnings', inputSchema: { type: 'object', properties: { tag: { type: 'string' }, limit: { type: 'number' } } } },
|
|
76
|
+
{ name: 'autopm_checkpoint', description: 'Create a project checkpoint', inputSchema: { type: 'object', properties: { description: { type: 'string' } }, required: ['description'] } }
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
80
|
+
|
|
81
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
82
|
+
const { name, arguments: args } = request.params;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
switch (name) {
|
|
86
|
+
case 'autopm_list_issues': {
|
|
87
|
+
const provider = loadProvider('issue-list');
|
|
88
|
+
if (!provider) return text('Issue list provider not available');
|
|
89
|
+
const result = await provider.execute(args || {}, settings);
|
|
90
|
+
return text(JSON.stringify(result, null, 2));
|
|
91
|
+
}
|
|
92
|
+
case 'autopm_show_issue': {
|
|
93
|
+
const provider = loadProvider('issue-show');
|
|
94
|
+
if (!provider) return text('Issue show provider not available');
|
|
95
|
+
const result = await provider.execute(args, settings);
|
|
96
|
+
return text(JSON.stringify(result, null, 2));
|
|
97
|
+
}
|
|
98
|
+
case 'autopm_create_issue': {
|
|
99
|
+
const provider = loadProvider('issue-create');
|
|
100
|
+
if (!provider) return text('Issue create provider not available');
|
|
101
|
+
const result = await provider.execute(args, settings);
|
|
102
|
+
return text(JSON.stringify(result, null, 2));
|
|
103
|
+
}
|
|
104
|
+
case 'autopm_start_issue': {
|
|
105
|
+
const provider = loadProvider('issue-start');
|
|
106
|
+
if (!provider) return text('Issue start provider not available');
|
|
107
|
+
const result = await provider.execute(args, settings);
|
|
108
|
+
return text(JSON.stringify(result, null, 2));
|
|
109
|
+
}
|
|
110
|
+
case 'autopm_close_issue': {
|
|
111
|
+
const provider = loadProvider('issue-close');
|
|
112
|
+
if (!provider) return text('Issue close provider not available');
|
|
113
|
+
const result = await provider.execute(args, settings);
|
|
114
|
+
return text(JSON.stringify(result, null, 2));
|
|
115
|
+
}
|
|
116
|
+
case 'autopm_list_epics': {
|
|
117
|
+
const provider = loadProvider('epic-list');
|
|
118
|
+
if (!provider) return text('Epic list provider not available');
|
|
119
|
+
const result = await provider.execute(args || {}, settings);
|
|
120
|
+
return text(JSON.stringify(result, null, 2));
|
|
121
|
+
}
|
|
122
|
+
case 'autopm_show_epic': {
|
|
123
|
+
const provider = loadProvider('epic-show');
|
|
124
|
+
if (!provider) return text('Epic show provider not available');
|
|
125
|
+
const result = await provider.execute(args, settings);
|
|
126
|
+
return text(JSON.stringify(result, null, 2));
|
|
127
|
+
}
|
|
128
|
+
case 'autopm_list_prds': {
|
|
129
|
+
const provider = loadProvider('prd-list');
|
|
130
|
+
if (!provider) return text('PRD list provider not available');
|
|
131
|
+
const result = await provider.execute(args || {}, settings);
|
|
132
|
+
return text(JSON.stringify(result, null, 2));
|
|
133
|
+
}
|
|
134
|
+
case 'autopm_show_prd': {
|
|
135
|
+
const provider = loadProvider('prd-show');
|
|
136
|
+
if (!provider) return text('PRD show provider not available');
|
|
137
|
+
const result = await provider.execute(args, settings);
|
|
138
|
+
return text(JSON.stringify(result, null, 2));
|
|
139
|
+
}
|
|
140
|
+
case 'autopm_status': {
|
|
141
|
+
const issues = loadProvider('issue-list');
|
|
142
|
+
const epics = loadProvider('epic-list');
|
|
143
|
+
const prds = loadProvider('prd-list');
|
|
144
|
+
const status = {
|
|
145
|
+
issues: issues ? await issues.execute({}, settings) : { count: 0 },
|
|
146
|
+
epics: epics ? await epics.execute({}, settings) : { count: 0 },
|
|
147
|
+
prds: prds ? await prds.execute({}, settings) : { count: 0 }
|
|
148
|
+
};
|
|
149
|
+
// Add recent events
|
|
150
|
+
try {
|
|
151
|
+
const loggerPath = path.join(basePath, '.claude', 'lib', 'event-logger');
|
|
152
|
+
const { readEvents } = require(loggerPath);
|
|
153
|
+
status.recentEvents = readEvents(10, null, basePath);
|
|
154
|
+
} catch { status.recentEvents = []; }
|
|
155
|
+
return text(JSON.stringify(status, null, 2));
|
|
156
|
+
}
|
|
157
|
+
case 'autopm_learn': {
|
|
158
|
+
try {
|
|
159
|
+
const learningsPath = path.join(basePath, '.claude', 'pm', 'learnings.jsonl');
|
|
160
|
+
const dir = path.dirname(learningsPath);
|
|
161
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
162
|
+
const entry = { timestamp: new Date().toISOString(), type: 'learning', learning: args.learning, tags: args.tags || [] };
|
|
163
|
+
fs.appendFileSync(learningsPath, JSON.stringify(entry) + '\n');
|
|
164
|
+
try {
|
|
165
|
+
const loggerPath = path.join(basePath, '.claude', 'lib', 'event-logger');
|
|
166
|
+
const { logEvent } = require(loggerPath);
|
|
167
|
+
logEvent('learning.saved', { learning: args.learning, tags: args.tags || [] }, basePath);
|
|
168
|
+
} catch { /* best effort */ }
|
|
169
|
+
return text(JSON.stringify({ success: true, learning: args.learning }));
|
|
170
|
+
} catch (e) { return text(JSON.stringify({ error: e.message })); }
|
|
171
|
+
}
|
|
172
|
+
case 'autopm_recall': {
|
|
173
|
+
const learningsPath = path.join(basePath, '.claude', 'pm', 'learnings.jsonl');
|
|
174
|
+
if (!fs.existsSync(learningsPath)) return text(JSON.stringify({ learnings: [] }));
|
|
175
|
+
let entries = fs.readFileSync(learningsPath, 'utf8').trim().split('\n')
|
|
176
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
177
|
+
if (args && args.tag) entries = entries.filter(e => (e.tags || []).includes(args.tag));
|
|
178
|
+
const limit = (args && args.limit) || 20;
|
|
179
|
+
return text(JSON.stringify({ learnings: entries.slice(-limit).reverse() }));
|
|
180
|
+
}
|
|
181
|
+
case 'autopm_checkpoint': {
|
|
182
|
+
try {
|
|
183
|
+
const { execSync } = require('child_process');
|
|
184
|
+
const cpDir = path.join(basePath, '.claude', 'pm', 'checkpoints');
|
|
185
|
+
if (!fs.existsSync(cpDir)) fs.mkdirSync(cpDir, { recursive: true });
|
|
186
|
+
const now = new Date().toISOString();
|
|
187
|
+
let gitInfo = {};
|
|
188
|
+
try {
|
|
189
|
+
gitInfo.branch = execSync('git branch --show-current', { encoding: 'utf8', cwd: basePath }).trim();
|
|
190
|
+
gitInfo.hash = execSync('git rev-parse --short HEAD', { encoding: 'utf8', cwd: basePath }).trim();
|
|
191
|
+
gitInfo.clean = !execSync('git status --porcelain', { encoding: 'utf8', cwd: basePath }).trim();
|
|
192
|
+
} catch { /* not a git repo */ }
|
|
193
|
+
// Count PM artifacts to match CLI checkpoint format
|
|
194
|
+
const counts = {};
|
|
195
|
+
for (const [key, dir] of [['issues', 'issues'], ['epics', 'epics'], ['prds', 'prds']]) {
|
|
196
|
+
const d = path.join(basePath, '.claude', dir);
|
|
197
|
+
try { counts[key] = fs.readdirSync(d).filter(f => f.endsWith('.md')).length; } catch { counts[key] = 0; }
|
|
198
|
+
}
|
|
199
|
+
// Load recent learnings
|
|
200
|
+
let learnings = [];
|
|
201
|
+
try {
|
|
202
|
+
const lPath = path.join(basePath, '.claude', 'pm', 'learnings.jsonl');
|
|
203
|
+
if (fs.existsSync(lPath)) {
|
|
204
|
+
learnings = fs.readFileSync(lPath, 'utf8').trim().split('\n')
|
|
205
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean).slice(-5);
|
|
206
|
+
}
|
|
207
|
+
} catch { /* ignore */ }
|
|
208
|
+
const checkpoint = { timestamp: now, description: args.description, git: gitInfo, counts, learnings, config_snapshot: null };
|
|
209
|
+
fs.writeFileSync(path.join(cpDir, now.replace(/[:.]/g, '-') + '.json'), JSON.stringify(checkpoint, null, 2));
|
|
210
|
+
return text(JSON.stringify({ success: true, checkpoint }));
|
|
211
|
+
} catch (e) { return text(JSON.stringify({ error: e.message })); }
|
|
212
|
+
}
|
|
213
|
+
default:
|
|
214
|
+
return text(`Unknown tool: ${name}`);
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
return text(JSON.stringify({ error: e.message }));
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ── Resources ──
|
|
222
|
+
|
|
223
|
+
const RESOURCES = [
|
|
224
|
+
{ uri: 'autopm://config', name: 'Project Config', description: 'Current .claude/config.json', mimeType: 'application/json' },
|
|
225
|
+
{ uri: 'autopm://agents', name: 'Agent Registry', description: 'Loaded agents from agent-registry.xml', mimeType: 'text/xml' },
|
|
226
|
+
{ uri: 'autopm://events', name: 'Recent Events', description: 'Last 50 events from events.jsonl', mimeType: 'application/json' },
|
|
227
|
+
{ uri: 'autopm://learnings', name: 'Project Learnings', description: 'All learnings from learnings.jsonl', mimeType: 'application/json' },
|
|
228
|
+
{ uri: 'autopm://test-plan', name: 'Test Plan', description: 'Current test plan', mimeType: 'text/markdown' }
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: RESOURCES }));
|
|
232
|
+
|
|
233
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
234
|
+
const { uri } = request.params;
|
|
235
|
+
|
|
236
|
+
switch (uri) {
|
|
237
|
+
case 'autopm://config':
|
|
238
|
+
return resource(uri, readFile('.claude/config.json') || '{}', 'application/json');
|
|
239
|
+
case 'autopm://agents':
|
|
240
|
+
return resource(uri, readFile('.claude/agents/agent-registry.xml') || '<agent-registry/>', 'text/xml');
|
|
241
|
+
case 'autopm://events': {
|
|
242
|
+
try {
|
|
243
|
+
const loggerPath = path.join(basePath, '.claude', 'lib', 'event-logger');
|
|
244
|
+
const { readEvents } = require(loggerPath);
|
|
245
|
+
return resource(uri, JSON.stringify(readEvents(50, null, basePath), null, 2), 'application/json');
|
|
246
|
+
} catch { return resource(uri, '[]', 'application/json'); }
|
|
247
|
+
}
|
|
248
|
+
case 'autopm://learnings': {
|
|
249
|
+
const content = readFile('.claude/pm/learnings.jsonl');
|
|
250
|
+
if (!content) return resource(uri, '[]', 'application/json');
|
|
251
|
+
const entries = content.trim().split('\n').map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
252
|
+
return resource(uri, JSON.stringify(entries, null, 2), 'application/json');
|
|
253
|
+
}
|
|
254
|
+
case 'autopm://test-plan':
|
|
255
|
+
return resource(uri, readFile('.claude/pm/test-plan.md') || 'No test plan. Run /pm:test-plan to generate.', 'text/markdown');
|
|
256
|
+
default:
|
|
257
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ── Prompts ──
|
|
262
|
+
|
|
263
|
+
const PROMPTS = [
|
|
264
|
+
{ name: 'autopm_issue_template', description: 'Template for creating a new issue' },
|
|
265
|
+
{ name: 'autopm_prd_template', description: 'Template for creating a new PRD' },
|
|
266
|
+
{ name: 'autopm_epic_template', description: 'Template for creating a new epic' }
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
270
|
+
|
|
271
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
272
|
+
const { name } = request.params;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const templateReaderPath = path.join(basePath, '.claude', 'lib', 'template-reader');
|
|
276
|
+
const { readTemplate, generateMarkdown, resolveTemplatePath } = require(templateReaderPath);
|
|
277
|
+
|
|
278
|
+
const templateMap = {
|
|
279
|
+
autopm_issue_template: 'issue.xml',
|
|
280
|
+
autopm_prd_template: 'prd.xml',
|
|
281
|
+
autopm_epic_template: 'epic.xml'
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const templateFile = templateMap[name];
|
|
285
|
+
if (!templateFile) throw new Error(`Unknown prompt: ${name}`);
|
|
286
|
+
|
|
287
|
+
const templatePath = resolveTemplatePath(templateFile, basePath);
|
|
288
|
+
const template = readTemplate(templatePath);
|
|
289
|
+
const markdown = generateMarkdown(template, {});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
messages: [{
|
|
293
|
+
role: 'user',
|
|
294
|
+
content: [{ type: 'text', text: `Use this template to create a new ${templateFile.replace('.xml', '')}:\n\n${markdown}` }]
|
|
295
|
+
}]
|
|
296
|
+
};
|
|
297
|
+
} catch (e) {
|
|
298
|
+
return {
|
|
299
|
+
messages: [{
|
|
300
|
+
role: 'user',
|
|
301
|
+
content: [{ type: 'text', text: `Template not available: ${e.message}. Run autopm install first.` }]
|
|
302
|
+
}]
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// ── Helpers ──
|
|
308
|
+
|
|
309
|
+
function text(content) {
|
|
310
|
+
return { content: [{ type: 'text', text: content }] };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function resource(uri, content, mimeType) {
|
|
314
|
+
return { contents: [{ uri, text: content, mimeType }] };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── Start ──
|
|
318
|
+
|
|
319
|
+
async function main() {
|
|
320
|
+
const transport = new StdioServerTransport();
|
|
321
|
+
await server.connect(transport);
|
|
322
|
+
console.error('AutoPM MCP Server running on stdio');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PM Diagram List/Show Script
|
|
5
|
+
*
|
|
6
|
+
* Lists diagrams from .claude/pm/diagrams/*.mmd with metadata.
|
|
7
|
+
* With --show <name>: displays diagram content.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const DIAGRAMS_DIR = path.join('.claude', 'pm', 'diagrams');
|
|
14
|
+
|
|
15
|
+
function findDiagrams() {
|
|
16
|
+
if (!fs.existsSync(DIAGRAMS_DIR)) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return fs.readdirSync(DIAGRAMS_DIR)
|
|
20
|
+
.filter(f => f.endsWith('.mmd'))
|
|
21
|
+
.map(f => f.replace('.mmd', ''));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadMeta(name) {
|
|
25
|
+
const metaPath = path.join(DIAGRAMS_DIR, `${name}.meta.json`);
|
|
26
|
+
if (!fs.existsSync(metaPath)) {
|
|
27
|
+
return { name, type: 'unknown', created: '-', updated: '-' };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const raw = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
|
|
31
|
+
return {
|
|
32
|
+
name: raw.name || name,
|
|
33
|
+
type: raw.type || 'unknown',
|
|
34
|
+
created: (raw.created || '-').slice(0, 10),
|
|
35
|
+
updated: (raw.updated || '-').slice(0, 10)
|
|
36
|
+
};
|
|
37
|
+
} catch {
|
|
38
|
+
return { name, type: 'unknown', created: '-', updated: '-' };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function listDiagrams() {
|
|
43
|
+
const names = findDiagrams();
|
|
44
|
+
if (names.length === 0) {
|
|
45
|
+
console.log('No diagrams found.');
|
|
46
|
+
console.log('Create one with: /pm:diagram-new <name>');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const rows = names.map(loadMeta);
|
|
51
|
+
|
|
52
|
+
console.log('## Project Diagrams\n');
|
|
53
|
+
console.log('| Name | Type | Created | Updated |');
|
|
54
|
+
console.log('|------|------|---------|---------|');
|
|
55
|
+
for (const r of rows) {
|
|
56
|
+
const escapedName = r.name.replace(/\|/g, '\\|');
|
|
57
|
+
const escapedType = (r.type || '').replace(/\|/g, '\\|');
|
|
58
|
+
console.log(`| ${escapedName} | ${escapedType} | ${r.created} | ${r.updated} |`);
|
|
59
|
+
}
|
|
60
|
+
console.log(`\nTotal: ${rows.length} diagram${rows.length === 1 ? '' : 's'}`);
|
|
61
|
+
console.log('View in dashboard: /pm:dashboard → Diagrams tab');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function showDiagram(name) {
|
|
65
|
+
const mmdPath = path.join(DIAGRAMS_DIR, `${name}.mmd`);
|
|
66
|
+
if (!fs.existsSync(mmdPath)) {
|
|
67
|
+
console.error(`❌ Diagram "${name}" not found. Run: /pm:diagram-new ${name}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const content = fs.readFileSync(mmdPath, 'utf8');
|
|
72
|
+
const meta = loadMeta(name);
|
|
73
|
+
|
|
74
|
+
console.log(`## Diagram: ${meta.name} (${meta.type})\n`);
|
|
75
|
+
console.log('```mermaid');
|
|
76
|
+
console.log(content.trim());
|
|
77
|
+
console.log('```');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Parse args
|
|
81
|
+
const args = process.argv.slice(2);
|
|
82
|
+
const showIdx = args.indexOf('--show');
|
|
83
|
+
|
|
84
|
+
if (showIdx !== -1 && args[showIdx + 1]) {
|
|
85
|
+
const name = args[showIdx + 1];
|
|
86
|
+
const safeName = path.basename(name || '');
|
|
87
|
+
if (!safeName || !/^[a-z0-9_-]+$/i.test(safeName)) {
|
|
88
|
+
console.log('Invalid diagram name. Use alphanumeric, hyphens, underscores only.');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
showDiagram(safeName);
|
|
92
|
+
} else {
|
|
93
|
+
listDiagrams();
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-autopm",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.22.0",
|
|
4
4
|
"description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
|
|
5
5
|
"main": "bin/autopm.js",
|
|
6
6
|
"workspaces": [
|
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
],
|
|
127
127
|
"dependencies": {
|
|
128
128
|
"@anthropic-ai/sdk": "^0.67.0",
|
|
129
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
129
130
|
"@octokit/rest": "^22.0.0",
|
|
130
131
|
"azure-devops-node-api": "^15.1.1",
|
|
131
132
|
"chalk": "4.1.2",
|