claude-autopm 3.21.0 → 3.22.1
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/dashboard-serve.js +177 -12
- 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);
|
|
@@ -132,8 +132,7 @@ function generateEpicFlowDiagram() {
|
|
|
132
132
|
}
|
|
133
133
|
} catch {}
|
|
134
134
|
if (epics.length === 0 && prds.length === 0) {
|
|
135
|
-
|
|
136
|
-
return lines.join('\n');
|
|
135
|
+
return { mermaid: '', hasData: false };
|
|
137
136
|
}
|
|
138
137
|
let nodeId = 0;
|
|
139
138
|
for (const prd of prds) {
|
|
@@ -159,7 +158,7 @@ function generateEpicFlowDiagram() {
|
|
|
159
158
|
lines.push(` ${eid} --> ${tid}["${t.name} ${t.icon}"]`);
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
|
-
return lines.join('\n');
|
|
161
|
+
return { mermaid: lines.join('\n'), hasData: true };
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
function generatePluginGraph() {
|
|
@@ -263,8 +262,10 @@ function getProjectDiagrams() {
|
|
|
263
262
|
}
|
|
264
263
|
|
|
265
264
|
function getDiagramsData() {
|
|
265
|
+
const epic = generateEpicFlowDiagram();
|
|
266
266
|
return {
|
|
267
|
-
epicFlow:
|
|
267
|
+
epicFlow: epic.mermaid,
|
|
268
|
+
epicFlowHasData: epic.hasData,
|
|
268
269
|
projectDiagrams: getProjectDiagrams(),
|
|
269
270
|
pluginGraph: generatePluginGraph(),
|
|
270
271
|
agentTree: generateAgentTree()
|
|
@@ -364,6 +365,42 @@ function renderHTML() {
|
|
|
364
365
|
.toast.ok { background: #238636; }
|
|
365
366
|
.toast.err { background: #da3633; }
|
|
366
367
|
footer { margin-top: 24px; text-align: center; color: #484f58; font-size: 12px; }
|
|
368
|
+
/* Diagram cards */
|
|
369
|
+
.diagram-card { position: relative; }
|
|
370
|
+
.diagram-card .diagram-toolbar { display: flex; gap: 6px; position: absolute; top: 12px; right: 12px; z-index: 2; }
|
|
371
|
+
.diagram-card .diagram-toolbar button {
|
|
372
|
+
background: #21262d; color: #8b949e; border: 1px solid #30363d;
|
|
373
|
+
padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
|
|
374
|
+
}
|
|
375
|
+
.diagram-card .diagram-toolbar button:hover { background: #30363d; color: #c9d1d9; }
|
|
376
|
+
.diagram-card .mermaid-wrap { overflow: auto; max-height: 400px; cursor: pointer; }
|
|
377
|
+
.diagram-card .mermaid-wrap:hover { outline: 1px solid #30363d; border-radius: 4px; }
|
|
378
|
+
/* Fullscreen modal */
|
|
379
|
+
.diagram-modal {
|
|
380
|
+
display: none; position: fixed; inset: 0; z-index: 1000;
|
|
381
|
+
background: rgba(0,0,0,0.85); justify-content: center; align-items: center;
|
|
382
|
+
}
|
|
383
|
+
.diagram-modal.open { display: flex; }
|
|
384
|
+
.diagram-modal .modal-inner {
|
|
385
|
+
background: #161b22; border: 1px solid #30363d; border-radius: 12px;
|
|
386
|
+
width: 95vw; height: 92vh; display: flex; flex-direction: column; position: relative;
|
|
387
|
+
}
|
|
388
|
+
.diagram-modal .modal-header {
|
|
389
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
390
|
+
padding: 12px 20px; border-bottom: 1px solid #30363d;
|
|
391
|
+
}
|
|
392
|
+
.diagram-modal .modal-header h3 { color: #c9d1d9; font-size: 14px; }
|
|
393
|
+
.diagram-modal .modal-header .modal-actions { display: flex; gap: 8px; }
|
|
394
|
+
.diagram-modal .modal-header button {
|
|
395
|
+
background: #21262d; color: #8b949e; border: 1px solid #30363d;
|
|
396
|
+
padding: 6px 14px; border-radius: 4px; cursor: pointer; font-size: 12px;
|
|
397
|
+
}
|
|
398
|
+
.diagram-modal .modal-header button:hover { background: #30363d; color: #c9d1d9; }
|
|
399
|
+
.diagram-modal .modal-body {
|
|
400
|
+
flex: 1; overflow: auto; padding: 20px; display: flex;
|
|
401
|
+
justify-content: center; align-items: flex-start;
|
|
402
|
+
}
|
|
403
|
+
.diagram-modal .modal-body svg { max-width: 100%; height: auto; }
|
|
367
404
|
</style>
|
|
368
405
|
</head>
|
|
369
406
|
<body>
|
|
@@ -494,15 +531,33 @@ function renderHTML() {
|
|
|
494
531
|
|
|
495
532
|
<!-- Diagrams Tab -->
|
|
496
533
|
<div id="tab-diagrams" class="tab-content">
|
|
497
|
-
<div class="
|
|
498
|
-
<
|
|
499
|
-
|
|
534
|
+
<div id="diagram-epic-card" class="card diagram-card" style="display:none;">
|
|
535
|
+
<h3>Epic Flow <span style="color:#8b949e;font-size:11px;font-weight:normal;margin-left:8px;">PRD → Epic → Tasks workflow</span></h3>
|
|
536
|
+
<div class="diagram-toolbar">
|
|
537
|
+
<button onclick="downloadMermaid('diagram-epic')">↓ .mmd</button>
|
|
538
|
+
<button onclick="openDiagramModal('diagram-epic','Epic Flow')">⤢ Fullscreen</button>
|
|
539
|
+
</div>
|
|
540
|
+
<div class="mermaid-wrap" onclick="openDiagramModal('diagram-epic','Epic Flow')">
|
|
500
541
|
<pre class="mermaid" id="diagram-epic"></pre>
|
|
501
542
|
</div>
|
|
502
543
|
</div>
|
|
503
544
|
<div id="project-diagrams"></div>
|
|
504
545
|
</div>
|
|
505
546
|
|
|
547
|
+
<!-- Diagram fullscreen modal -->
|
|
548
|
+
<div class="diagram-modal" id="diagram-modal" role="dialog" aria-modal="true" aria-labelledby="modal-diagram-title" onclick="if(event.target===this)closeDiagramModal()">
|
|
549
|
+
<div class="modal-inner">
|
|
550
|
+
<div class="modal-header">
|
|
551
|
+
<h3 id="modal-diagram-title"></h3>
|
|
552
|
+
<div class="modal-actions">
|
|
553
|
+
<button onclick="downloadMermaid(currentModalDiagram)">↓ Download .mmd</button>
|
|
554
|
+
<button onclick="closeDiagramModal()">✕ Close</button>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
<div class="modal-body" id="modal-diagram-body"></div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
506
561
|
<!-- Tests Tab -->
|
|
507
562
|
<div id="tab-tests" class="tab-content">
|
|
508
563
|
<div class="card">
|
|
@@ -542,26 +597,68 @@ function showTab(name, btn) {
|
|
|
542
597
|
}
|
|
543
598
|
|
|
544
599
|
let diagramsRendered = false;
|
|
600
|
+
const diagramSources = {};
|
|
601
|
+
const diagramNames = {};
|
|
602
|
+
let currentModalDiagram = null;
|
|
603
|
+
|
|
545
604
|
function renderMermaid() {
|
|
546
605
|
if (diagramsRendered) return;
|
|
547
606
|
fetch('/api/diagrams', { headers: { 'Authorization': 'Bearer ' + TOKEN } })
|
|
548
607
|
.then(r => r.json())
|
|
549
608
|
.then(data => {
|
|
550
|
-
|
|
609
|
+
// Epic Flow — only show if there are actual epics/PRDs
|
|
610
|
+
const epicCard = document.getElementById('diagram-epic-card');
|
|
611
|
+
const epicEl = document.getElementById('diagram-epic');
|
|
612
|
+
if (data.epicFlowHasData) {
|
|
613
|
+
epicCard.style.display = '';
|
|
614
|
+
epicEl.className = 'mermaid';
|
|
615
|
+
epicEl.textContent = data.epicFlow;
|
|
616
|
+
diagramSources['diagram-epic'] = data.epicFlow;
|
|
617
|
+
diagramNames['diagram-epic'] = 'epic-flow';
|
|
618
|
+
} else {
|
|
619
|
+
epicCard.style.display = 'none';
|
|
620
|
+
epicEl.className = '';
|
|
621
|
+
epicEl.textContent = '';
|
|
622
|
+
}
|
|
551
623
|
document.getElementById('diagram-plugins').textContent = data.pluginGraph;
|
|
552
624
|
document.getElementById('diagram-agents').textContent = data.agentTree;
|
|
625
|
+
diagramSources['diagram-plugins'] = data.pluginGraph;
|
|
626
|
+
diagramSources['diagram-agents'] = data.agentTree;
|
|
627
|
+
diagramNames['diagram-plugins'] = 'plugins';
|
|
628
|
+
diagramNames['diagram-agents'] = 'agents';
|
|
553
629
|
// Render project diagrams dynamically
|
|
554
630
|
const container = document.getElementById('project-diagrams');
|
|
555
631
|
container.innerHTML = '';
|
|
556
632
|
if (data.projectDiagrams && data.projectDiagrams.length > 0) {
|
|
557
|
-
data.projectDiagrams.forEach(d => {
|
|
633
|
+
data.projectDiagrams.forEach((d, i) => {
|
|
634
|
+
const id = 'proj-diagram-' + i;
|
|
635
|
+
diagramSources[id] = d.content;
|
|
636
|
+
diagramNames[id] = d.name;
|
|
558
637
|
const card = document.createElement('div');
|
|
559
|
-
card.className = 'card';
|
|
560
|
-
|
|
638
|
+
card.className = 'card diagram-card';
|
|
639
|
+
const h3 = document.createElement('h3');
|
|
640
|
+
h3.textContent = d.name;
|
|
641
|
+
card.appendChild(h3);
|
|
642
|
+
const toolbar = document.createElement('div');
|
|
643
|
+
toolbar.className = 'diagram-toolbar';
|
|
644
|
+
const dlBtn = document.createElement('button');
|
|
645
|
+
dlBtn.textContent = '↓ .mmd';
|
|
646
|
+
dlBtn.onclick = function(e) { e.stopPropagation(); downloadMermaid(id); };
|
|
647
|
+
const fsBtn = document.createElement('button');
|
|
648
|
+
fsBtn.textContent = '⤢ Fullscreen';
|
|
649
|
+
fsBtn.onclick = function(e) { e.stopPropagation(); openDiagramModal(id, d.name); };
|
|
650
|
+
toolbar.appendChild(dlBtn);
|
|
651
|
+
toolbar.appendChild(fsBtn);
|
|
652
|
+
card.appendChild(toolbar);
|
|
653
|
+
const wrap = document.createElement('div');
|
|
654
|
+
wrap.className = 'mermaid-wrap';
|
|
655
|
+
wrap.onclick = function() { openDiagramModal(id, d.name); };
|
|
561
656
|
const pre = document.createElement('pre');
|
|
562
657
|
pre.className = 'mermaid';
|
|
658
|
+
pre.id = id;
|
|
563
659
|
pre.textContent = d.content;
|
|
564
|
-
|
|
660
|
+
wrap.appendChild(pre);
|
|
661
|
+
card.appendChild(wrap);
|
|
565
662
|
container.appendChild(card);
|
|
566
663
|
});
|
|
567
664
|
} else {
|
|
@@ -586,6 +683,59 @@ function renderMermaid() {
|
|
|
586
683
|
});
|
|
587
684
|
}
|
|
588
685
|
|
|
686
|
+
function openDiagramModal(diagramId, title) {
|
|
687
|
+
currentModalDiagram = diagramId;
|
|
688
|
+
document.getElementById('modal-diagram-title').textContent = title || diagramId;
|
|
689
|
+
const body = document.getElementById('modal-diagram-body');
|
|
690
|
+
body.textContent = '';
|
|
691
|
+
const source = diagramSources[diagramId];
|
|
692
|
+
if (source) {
|
|
693
|
+
const pre = document.createElement('pre');
|
|
694
|
+
pre.className = 'mermaid';
|
|
695
|
+
pre.textContent = source;
|
|
696
|
+
body.appendChild(pre);
|
|
697
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
698
|
+
mermaid.run({ nodes: body.querySelectorAll('.mermaid') });
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
body.textContent = 'Diagram not available.';
|
|
702
|
+
}
|
|
703
|
+
const modal = document.getElementById('diagram-modal');
|
|
704
|
+
modal.classList.add('open');
|
|
705
|
+
modalPrevFocus = document.activeElement;
|
|
706
|
+
const closeBtn = modal.querySelector('.modal-actions button:last-child');
|
|
707
|
+
if (closeBtn) closeBtn.focus();
|
|
708
|
+
document.addEventListener('keydown', modalEscHandler);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let modalPrevFocus = null;
|
|
712
|
+
function closeDiagramModal() {
|
|
713
|
+
document.getElementById('diagram-modal').classList.remove('open');
|
|
714
|
+
document.removeEventListener('keydown', modalEscHandler);
|
|
715
|
+
currentModalDiagram = null;
|
|
716
|
+
if (modalPrevFocus) { modalPrevFocus.focus(); modalPrevFocus = null; }
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function modalEscHandler(e) { if (e.key === 'Escape') closeDiagramModal(); }
|
|
720
|
+
|
|
721
|
+
function downloadMermaid(diagramId) {
|
|
722
|
+
const src = diagramSources[diagramId];
|
|
723
|
+
if (!src) return;
|
|
724
|
+
const label = diagramNames[diagramId] || diagramId.replace('proj-diagram-', 'diagram-').replace('diagram-', '');
|
|
725
|
+
const safeName = label.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
|
|
726
|
+
const blob = new Blob([src], { type: 'text/plain' });
|
|
727
|
+
const a = document.createElement('a');
|
|
728
|
+
const url = URL.createObjectURL(blob);
|
|
729
|
+
a.href = url;
|
|
730
|
+
a.download = safeName;
|
|
731
|
+
a.style.display = 'none';
|
|
732
|
+
document.body.appendChild(a);
|
|
733
|
+
try { a.click(); } finally {
|
|
734
|
+
document.body.removeChild(a);
|
|
735
|
+
setTimeout(function() { URL.revokeObjectURL(url); }, 100);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
589
739
|
function renderTestPlan(md) {
|
|
590
740
|
if (!md) { document.getElementById('test-plan-content').textContent = 'No test plan found. Run /pm:test-plan to generate.'; return; }
|
|
591
741
|
const lines = md.split('\\n');
|
|
@@ -963,7 +1113,22 @@ function addEnvSuggestion(key, val, hint) {
|
|
|
963
1113
|
addEnvRow(key, val);
|
|
964
1114
|
}
|
|
965
1115
|
|
|
1116
|
+
// Pause auto-refresh while user is editing MCP or env forms
|
|
1117
|
+
let editingLock = false;
|
|
1118
|
+
document.addEventListener('focusin', function(e) {
|
|
1119
|
+
if (e.target.closest('#mcp-list, #env-list, .mcp-entry, .env-row')) editingLock = true;
|
|
1120
|
+
});
|
|
1121
|
+
document.addEventListener('focusout', function(e) {
|
|
1122
|
+
if (e.target.closest('#mcp-list, #env-list, .mcp-entry, .env-row')) {
|
|
1123
|
+
setTimeout(function() {
|
|
1124
|
+
const active = document.activeElement;
|
|
1125
|
+
if (!active || !active.closest('#mcp-list, #env-list, .mcp-entry, .env-row')) editingLock = false;
|
|
1126
|
+
}, 200);
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
966
1130
|
async function refresh() {
|
|
1131
|
+
if (editingLock) return;
|
|
967
1132
|
try {
|
|
968
1133
|
const data = await api('GET', '/api/status');
|
|
969
1134
|
loadStatus(data);
|
|
@@ -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.1",
|
|
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",
|