claude-autopm 1.26.0 → 1.27.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/agents/frameworks/e2e-test-engineer.md +1 -18
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -18
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -18
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +1 -18
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +1 -18
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +1 -18
- package/autopm/.claude/agents/languages/bash-scripting-expert.md +1 -18
- package/autopm/.claude/agents/languages/javascript-frontend-engineer.md +1 -18
- package/autopm/.claude/agents/languages/nodejs-backend-engineer.md +1 -18
- package/autopm/.claude/agents/languages/python-backend-engineer.md +1 -18
- package/autopm/.claude/agents/languages/python-backend-expert.md +1 -18
- package/autopm/.claude/commands/pm/epic-decompose.md +19 -5
- package/autopm/.claude/commands/pm/prd-new.md +14 -1
- package/autopm/.claude/includes/task-creation-excellence.md +18 -0
- package/autopm/.claude/lib/ai-task-generator.js +84 -0
- package/autopm/.claude/lib/cli-parser.js +148 -0
- package/autopm/.claude/lib/dependency-analyzer.js +157 -0
- package/autopm/.claude/lib/frontmatter.js +224 -0
- package/autopm/.claude/lib/task-utils.js +64 -0
- package/autopm/.claude/scripts/pm-epic-decompose-local.js +158 -0
- package/autopm/.claude/scripts/pm-epic-list-local.js +103 -0
- package/autopm/.claude/scripts/pm-epic-show-local.js +70 -0
- package/autopm/.claude/scripts/pm-epic-update-local.js +56 -0
- package/autopm/.claude/scripts/pm-prd-list-local.js +111 -0
- package/autopm/.claude/scripts/pm-prd-new-local.js +196 -0
- package/autopm/.claude/scripts/pm-prd-parse-local.js +360 -0
- package/autopm/.claude/scripts/pm-prd-show-local.js +101 -0
- package/autopm/.claude/scripts/pm-prd-update-local.js +153 -0
- package/autopm/.claude/scripts/pm-sync-download-local.js +424 -0
- package/autopm/.claude/scripts/pm-sync-upload-local.js +473 -0
- package/autopm/.claude/scripts/pm-task-list-local.js +86 -0
- package/autopm/.claude/scripts/pm-task-show-local.js +92 -0
- package/autopm/.claude/scripts/pm-task-update-local.js +109 -0
- package/autopm/.claude/scripts/setup-local-mode.js +127 -0
- package/package.json +5 -3
- package/scripts/create-task-issues.sh +26 -0
- package/scripts/fix-invalid-command-refs.sh +4 -3
- package/scripts/fix-invalid-refs-simple.sh +8 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show Local Epic
|
|
3
|
+
*
|
|
4
|
+
* Displays details of a specific epic including frontmatter,
|
|
5
|
+
* body content, and directory information.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const { showLocalEpic } = require('./pm-epic-show-local');
|
|
9
|
+
*
|
|
10
|
+
* const epic = await showLocalEpic('epic-001');
|
|
11
|
+
* console.log(epic.frontmatter.title);
|
|
12
|
+
* console.log(epic.body);
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs').promises;
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { parseFrontmatter } = require('../lib/frontmatter');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get epic details by ID
|
|
21
|
+
*
|
|
22
|
+
* @param {string} epicId - Epic ID to retrieve
|
|
23
|
+
* @returns {Promise<Object>} Epic object with frontmatter, body, and directory
|
|
24
|
+
* @throws {Error} If epic not found
|
|
25
|
+
*/
|
|
26
|
+
async function showLocalEpic(epicId) {
|
|
27
|
+
const basePath = process.cwd();
|
|
28
|
+
const epicsDir = path.join(basePath, '.claude', 'epics');
|
|
29
|
+
|
|
30
|
+
// Check if epics directory exists
|
|
31
|
+
try {
|
|
32
|
+
await fs.access(epicsDir);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err.code === 'ENOENT') {
|
|
35
|
+
throw new Error(`Epic not found: ${epicId} (epics directory does not exist)`);
|
|
36
|
+
}
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Find epic directory by ID
|
|
41
|
+
const dirs = await fs.readdir(epicsDir);
|
|
42
|
+
const epicDir = dirs.find(dir => dir.startsWith(`${epicId}-`));
|
|
43
|
+
|
|
44
|
+
if (!epicDir) {
|
|
45
|
+
throw new Error(`Epic not found: ${epicId}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const epicDirPath = path.join(epicsDir, epicDir);
|
|
49
|
+
const epicPath = path.join(epicDirPath, 'epic.md');
|
|
50
|
+
|
|
51
|
+
// Read and parse epic file
|
|
52
|
+
try {
|
|
53
|
+
const content = await fs.readFile(epicPath, 'utf8');
|
|
54
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
frontmatter,
|
|
58
|
+
body,
|
|
59
|
+
directory: epicDir,
|
|
60
|
+
path: epicPath
|
|
61
|
+
};
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (err.code === 'ENOENT') {
|
|
64
|
+
throw new Error(`Epic not found: ${epicId} (epic.md missing in ${epicDir})`);
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { showLocalEpic };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Local Epic
|
|
3
|
+
*
|
|
4
|
+
* Updates epic frontmatter fields while preserving body content.
|
|
5
|
+
* Supports updating single or multiple fields at once.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const { updateLocalEpic } = require('./pm-epic-update-local');
|
|
9
|
+
*
|
|
10
|
+
* // Update status
|
|
11
|
+
* await updateLocalEpic('epic-001', { status: 'in_progress' });
|
|
12
|
+
*
|
|
13
|
+
* // Update multiple fields
|
|
14
|
+
* await updateLocalEpic('epic-001', {
|
|
15
|
+
* status: 'completed',
|
|
16
|
+
* tasks_completed: 5
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs').promises;
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const { parseFrontmatter, stringifyFrontmatter } = require('../lib/frontmatter');
|
|
23
|
+
const { showLocalEpic } = require('./pm-epic-show-local');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Update epic frontmatter fields
|
|
27
|
+
*
|
|
28
|
+
* @param {string} epicId - Epic ID to update
|
|
29
|
+
* @param {Object} updates - Fields to update in frontmatter
|
|
30
|
+
* @returns {Promise<Object>} Updated epic object
|
|
31
|
+
* @throws {Error} If epic not found
|
|
32
|
+
*/
|
|
33
|
+
async function updateLocalEpic(epicId, updates) {
|
|
34
|
+
// Get current epic
|
|
35
|
+
const epic = await showLocalEpic(epicId);
|
|
36
|
+
|
|
37
|
+
// Merge updates into frontmatter
|
|
38
|
+
const updatedFrontmatter = {
|
|
39
|
+
...epic.frontmatter,
|
|
40
|
+
...updates
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Preserve body content
|
|
44
|
+
const updatedContent = stringifyFrontmatter(updatedFrontmatter, epic.body);
|
|
45
|
+
|
|
46
|
+
// Write updated content back to file
|
|
47
|
+
await fs.writeFile(epic.path, updatedContent, 'utf8');
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
epicId,
|
|
51
|
+
frontmatter: updatedFrontmatter,
|
|
52
|
+
body: epic.body
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { updateLocalEpic };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local PRD Listing Command
|
|
3
|
+
*
|
|
4
|
+
* Lists all Product Requirements Documents (PRDs) in local mode.
|
|
5
|
+
* Supports filtering and sorting.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* /pm:prd-list --local
|
|
9
|
+
* /pm:prd-list --local --status approved
|
|
10
|
+
*
|
|
11
|
+
* @module pm-prd-list-local
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs').promises;
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { parseFrontmatter } = require('../lib/frontmatter');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lists all local PRDs with optional filtering
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} options - Listing options
|
|
22
|
+
* @param {string} options.status - Filter by status (draft, approved, etc.)
|
|
23
|
+
* @returns {Promise<Array>} Array of PRD metadata objects
|
|
24
|
+
*/
|
|
25
|
+
async function listLocalPRDs(options = {}) {
|
|
26
|
+
const prdsDir = path.join(process.cwd(), '.claude', 'prds');
|
|
27
|
+
|
|
28
|
+
// Ensure directory exists
|
|
29
|
+
try {
|
|
30
|
+
await fs.access(prdsDir);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err.code === 'ENOENT') {
|
|
33
|
+
return []; // No PRDs directory = no PRDs
|
|
34
|
+
}
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Read directory
|
|
39
|
+
const files = await fs.readdir(prdsDir);
|
|
40
|
+
const mdFiles = files.filter(f => f.endsWith('.md'));
|
|
41
|
+
|
|
42
|
+
// Parallelize file reading/parsing with Promise.allSettled
|
|
43
|
+
const prdPromises = mdFiles.map(async (file) => {
|
|
44
|
+
try {
|
|
45
|
+
const filepath = path.join(prdsDir, file);
|
|
46
|
+
const content = await fs.readFile(filepath, 'utf8');
|
|
47
|
+
const { frontmatter } = parseFrontmatter(content);
|
|
48
|
+
// Only include files with valid frontmatter containing required fields
|
|
49
|
+
// A valid PRD must have at least an 'id' field
|
|
50
|
+
if (frontmatter && typeof frontmatter === 'object' && frontmatter.id) {
|
|
51
|
+
return {
|
|
52
|
+
filename: file,
|
|
53
|
+
...frontmatter
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
// Skip files that can't be parsed
|
|
58
|
+
console.warn(`Warning: Could not parse ${file}:`, err.message);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const settled = await Promise.allSettled(prdPromises);
|
|
64
|
+
const prds = settled
|
|
65
|
+
.filter(r => r.status === 'fulfilled' && r.value)
|
|
66
|
+
.map(r => r.value);
|
|
67
|
+
// Filter by status if specified
|
|
68
|
+
let filtered = prds;
|
|
69
|
+
if (options.status) {
|
|
70
|
+
filtered = filtered.filter(p => p.status === options.status);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Sort by creation timestamp (newest first)
|
|
74
|
+
filtered.sort((a, b) => {
|
|
75
|
+
// Use createdAt if available (full timestamp), fallback to created (date only)
|
|
76
|
+
const dateA = new Date(a.createdAt || a.created || 0);
|
|
77
|
+
const dateB = new Date(b.createdAt || b.created || 0);
|
|
78
|
+
return dateB - dateA;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return filtered;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Formats PRD list for display
|
|
86
|
+
*
|
|
87
|
+
* @param {Array} prds - Array of PRD objects
|
|
88
|
+
* @returns {string} Formatted string for display
|
|
89
|
+
*/
|
|
90
|
+
function formatPRDList(prds) {
|
|
91
|
+
if (prds.length === 0) {
|
|
92
|
+
return 'No PRDs found.';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const lines = ['', 'Local PRDs:', ''];
|
|
96
|
+
|
|
97
|
+
prds.forEach((prd, index) => {
|
|
98
|
+
lines.push(
|
|
99
|
+
`${index + 1}. [${prd.id}] ${prd.title}`,
|
|
100
|
+
` Status: ${prd.status} | Priority: ${prd.priority} | Created: ${prd.created}`,
|
|
101
|
+
''
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
listLocalPRDs,
|
|
110
|
+
formatPRDList
|
|
111
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local PRD Creation Command
|
|
3
|
+
*
|
|
4
|
+
* Creates a new Product Requirements Document (PRD) in local mode.
|
|
5
|
+
* PRDs are stored in .claude/prds/ directory.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* /pm:prd-new --local "Feature Name"
|
|
9
|
+
*
|
|
10
|
+
* @module pm-prd-new-local
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs').promises;
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { stringifyFrontmatter } = require('../lib/frontmatter');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new PRD in the local .claude/prds/ directory
|
|
19
|
+
*
|
|
20
|
+
* @param {string} name - PRD title/name
|
|
21
|
+
* @param {Object} options - Optional configuration
|
|
22
|
+
* @param {string} options.id - Custom PRD ID (auto-generated if not provided)
|
|
23
|
+
* @param {string} options.author - Author name (default: 'ClaudeAutoPM')
|
|
24
|
+
* @param {string} options.priority - Priority level (default: 'medium')
|
|
25
|
+
* @returns {Promise<Object>} Created PRD metadata
|
|
26
|
+
* @throws {Error} If name is invalid or PRD already exists
|
|
27
|
+
*/
|
|
28
|
+
async function createLocalPRD(name, options = {}) {
|
|
29
|
+
// Validate name
|
|
30
|
+
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
|
31
|
+
throw new Error('PRD name is required and must be a non-empty string');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const sanitizedName = name.trim();
|
|
35
|
+
|
|
36
|
+
// Generate unique ID
|
|
37
|
+
const id = options.id || generatePRDId();
|
|
38
|
+
|
|
39
|
+
// Create frontmatter
|
|
40
|
+
const now = new Date();
|
|
41
|
+
const frontmatter = {
|
|
42
|
+
id,
|
|
43
|
+
title: sanitizedName,
|
|
44
|
+
created: now.toISOString().split('T')[0],
|
|
45
|
+
createdAt: now.toISOString(), // Include full timestamp for sorting
|
|
46
|
+
author: options.author || 'ClaudeAutoPM',
|
|
47
|
+
status: 'draft',
|
|
48
|
+
priority: options.priority || 'medium',
|
|
49
|
+
version: '1.0'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Create PRD body template
|
|
53
|
+
const body = createPRDTemplate(sanitizedName);
|
|
54
|
+
|
|
55
|
+
// Generate full markdown content
|
|
56
|
+
const content = stringifyFrontmatter(frontmatter, body);
|
|
57
|
+
|
|
58
|
+
// Generate filename (sanitize name + add ID for uniqueness)
|
|
59
|
+
const filename = `${id}-${sanitizeFilename(sanitizedName)}`;
|
|
60
|
+
const filepath = path.join(process.cwd(), '.claude', 'prds', filename);
|
|
61
|
+
|
|
62
|
+
// Check if file already exists
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(filepath);
|
|
65
|
+
throw new Error(`PRD already exists: ${filename}`);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
if (err.code !== 'ENOENT') {
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ensure directory exists
|
|
73
|
+
await fs.mkdir(path.dirname(filepath), { recursive: true });
|
|
74
|
+
|
|
75
|
+
// Write to file
|
|
76
|
+
await fs.writeFile(filepath, content, 'utf8');
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
filepath,
|
|
81
|
+
frontmatter
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a PRD template with standard sections
|
|
87
|
+
*
|
|
88
|
+
* @param {string} name - PRD title
|
|
89
|
+
* @returns {string} PRD template content
|
|
90
|
+
*/
|
|
91
|
+
function createPRDTemplate(name) {
|
|
92
|
+
return `# Product Requirements Document: ${name}
|
|
93
|
+
|
|
94
|
+
## 1. Executive Summary
|
|
95
|
+
|
|
96
|
+
### Overview
|
|
97
|
+
[Describe the feature/product in 2-3 sentences]
|
|
98
|
+
|
|
99
|
+
### Business Value
|
|
100
|
+
[Why is this important?]
|
|
101
|
+
|
|
102
|
+
### Success Metrics
|
|
103
|
+
[How will we measure success?]
|
|
104
|
+
|
|
105
|
+
## 2. Background
|
|
106
|
+
|
|
107
|
+
### Problem Statement
|
|
108
|
+
[What problem are we solving?]
|
|
109
|
+
|
|
110
|
+
### Current State
|
|
111
|
+
[What exists today?]
|
|
112
|
+
|
|
113
|
+
### Goals and Objectives
|
|
114
|
+
[What are we trying to achieve?]
|
|
115
|
+
|
|
116
|
+
## 3. User Stories
|
|
117
|
+
|
|
118
|
+
[Epic-level user stories]
|
|
119
|
+
|
|
120
|
+
## 4. Functional Requirements
|
|
121
|
+
|
|
122
|
+
[Detailed requirements]
|
|
123
|
+
|
|
124
|
+
## 5. Non-Functional Requirements
|
|
125
|
+
|
|
126
|
+
[Performance, security, etc.]
|
|
127
|
+
|
|
128
|
+
## 6. Out of Scope
|
|
129
|
+
|
|
130
|
+
[What we're NOT doing]
|
|
131
|
+
|
|
132
|
+
## 7. Timeline
|
|
133
|
+
|
|
134
|
+
[Key milestones]
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Counter for ensuring unique IDs even when created at the same millisecond
|
|
139
|
+
let idCounter = 0;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generates a unique PRD ID
|
|
143
|
+
*
|
|
144
|
+
* Format: prd-XXX (3 digits from timestamp + counter)
|
|
145
|
+
*
|
|
146
|
+
* @returns {string} Generated PRD ID
|
|
147
|
+
*/
|
|
148
|
+
function generatePRDId() {
|
|
149
|
+
const timestamp = Date.now();
|
|
150
|
+
const random = Math.floor(Math.random() * 900) + 100; // 100-999
|
|
151
|
+
const counter = (idCounter++) % 10; // 0-9 cycling counter
|
|
152
|
+
|
|
153
|
+
// Use last digit of timestamp + last 2 digits of random = 3 digits total
|
|
154
|
+
const suffix = (timestamp % 10).toString() +
|
|
155
|
+
(Math.floor(random / 10) % 10).toString() +
|
|
156
|
+
counter.toString();
|
|
157
|
+
|
|
158
|
+
return `prd-${suffix}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sanitizes a PRD name for use as a filename
|
|
163
|
+
*
|
|
164
|
+
* - Converts to lowercase
|
|
165
|
+
* - Replaces spaces with hyphens
|
|
166
|
+
* - Removes special characters except hyphens
|
|
167
|
+
* - Truncates to safe length (max 100 chars before .md)
|
|
168
|
+
* - Adds .md extension
|
|
169
|
+
*
|
|
170
|
+
* @param {string} name - PRD name to sanitize
|
|
171
|
+
* @returns {string} Sanitized filename
|
|
172
|
+
*/
|
|
173
|
+
function sanitizeFilename(name) {
|
|
174
|
+
const sanitized = name
|
|
175
|
+
.toLowerCase()
|
|
176
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
177
|
+
.replace(/[^a-z0-9-]/g, '') // Remove special characters
|
|
178
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
179
|
+
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
|
|
180
|
+
|
|
181
|
+
// Truncate to safe length (100 chars + .md = 103 total)
|
|
182
|
+
// With prd-XXX- prefix (8 chars), max base name is ~92 chars
|
|
183
|
+
const maxLength = 92;
|
|
184
|
+
const truncated = sanitized.length > maxLength
|
|
185
|
+
? sanitized.substring(0, maxLength)
|
|
186
|
+
: sanitized;
|
|
187
|
+
|
|
188
|
+
return truncated + '.md';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
createLocalPRD,
|
|
193
|
+
createPRDTemplate,
|
|
194
|
+
generatePRDId,
|
|
195
|
+
sanitizeFilename
|
|
196
|
+
};
|