claude-autopm 1.18.0 → 1.20.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/README.md +159 -0
- package/autopm/.claude/agents/README.md +1 -1
- package/autopm/.claude/agents/core/mcp-manager.md +1 -1
- package/autopm/.claude/agents/decision-matrices/python-backend-selection.md +25 -25
- package/autopm/.claude/agents/decision-matrices/ui-framework-selection.md +43 -43
- package/autopm/.claude/agents/devops/github-operations-specialist.md +1 -1
- package/autopm/.claude/agents/frameworks/README.md +5 -5
- package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -1
- package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -1
- package/autopm/.claude/agents/frameworks/react-ui-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +3 -3
- package/autopm/.claude/agents/frameworks/ux-design-expert.md +3 -3
- package/autopm/.claude/commands/infrastructure/traefik-setup.md +1 -1
- package/autopm/.claude/commands/playwright/test-scaffold.md +1 -1
- package/autopm/.claude/commands/pm/context.md +11 -0
- package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
- package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
- package/autopm/.claude/commands/pm/epic-start.md +19 -0
- package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
- package/autopm/.claude/commands/pm/epic-sync.md +14 -14
- package/autopm/.claude/commands/pm/issue-start.md +50 -5
- package/autopm/.claude/commands/pm/issue-sync.md +15 -15
- package/autopm/.claude/commands/pm/what-next.md +11 -0
- package/autopm/.claude/commands/ui/bootstrap-scaffold.md +6 -5
- package/autopm/.claude/commands/ui/tailwind-system.md +1 -1
- package/autopm/.claude/examples/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/examples/mcp-servers.example.json +2 -2
- package/autopm/.claude/hooks/docker-first-enforcement.sh +1 -1
- package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
- package/autopm/.claude/mcp/playwright-mcp.md +2 -2
- package/autopm/.claude/rules/agent-coordination.md +26 -24
- package/autopm/.claude/rules/docker-first-development.md +1 -1
- package/autopm/.claude/rules/infrastructure-pipeline.md +1 -1
- package/autopm/.claude/rules/ui-development-standards.md +1 -1
- package/autopm/.claude/rules/visual-testing.md +3 -3
- package/autopm/.claude/scripts/azure/active-work.js +2 -2
- package/autopm/.claude/scripts/azure/blocked.js +13 -13
- package/autopm/.claude/scripts/azure/daily.js +1 -1
- package/autopm/.claude/scripts/azure/dashboard.js +1 -1
- package/autopm/.claude/scripts/azure/feature-list.js +2 -2
- package/autopm/.claude/scripts/azure/feature-status.js +1 -1
- package/autopm/.claude/scripts/azure/next-task.js +1 -1
- package/autopm/.claude/scripts/azure/search.js +1 -1
- package/autopm/.claude/scripts/azure/setup.js +15 -15
- package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
- package/autopm/.claude/scripts/azure/sync.js +1 -1
- package/autopm/.claude/scripts/azure/us-list.js +1 -1
- package/autopm/.claude/scripts/azure/us-status.js +1 -1
- package/autopm/.claude/scripts/azure/validate.js +13 -13
- package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
- package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
- package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
- package/autopm/.claude/scripts/pm/context.js +338 -0
- package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
- package/autopm/.claude/scripts/pm/lib/README.md +85 -0
- package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
- package/autopm/.claude/scripts/pm/next.js +25 -1
- package/autopm/.claude/scripts/pm/what-next.js +660 -0
- package/autopm/.claude/teams.json +3 -5
- package/autopm/.claude/templates/claude-templates/addons/devops-agents.md +2 -2
- package/autopm/.claude/templates/claude-templates/addons/docker-agents.md +4 -4
- package/autopm/.claude/templates/claude-templates/addons/minimal-agents.md +1 -1
- package/autopm/.claude/templates/issue-decomposition/api.yaml +2 -2
- package/autopm/.claude/templates/issue-decomposition/auth.yaml +4 -4
- package/autopm/.claude/templates/issue-decomposition/crud.yaml +3 -3
- package/autopm/.claude/templates/issue-decomposition/default.yaml +1 -1
- package/autopm/.claude/templates/issue-decomposition/ui-feature.yaml +2 -2
- package/bin/autopm.js +25 -0
- package/package.json +1 -2
- package/lib/agentExecutor.js.deprecated +0 -101
- package/lib/azure/cache.js +0 -80
- package/lib/azure/client.js +0 -77
- package/lib/azure/formatter.js +0 -177
- package/lib/commandHelpers.js +0 -177
- package/lib/context/manager.js +0 -290
- package/lib/documentation/manager.js +0 -528
- package/lib/github/workflow-manager.js +0 -546
- package/lib/helpers/azure-batch-api.js +0 -133
- package/lib/helpers/azure-cache-manager.js +0 -287
- package/lib/helpers/azure-parallel-processor.js +0 -158
- package/lib/helpers/azure-work-item-create.js +0 -278
- package/lib/helpers/gh-issue-create.js +0 -250
- package/lib/helpers/interactive-prompt.js +0 -336
- package/lib/helpers/output-manager.js +0 -335
- package/lib/helpers/progress-indicator.js +0 -258
- package/lib/performance/benchmarker.js +0 -429
- package/lib/pm/epic-decomposer.js +0 -273
- package/lib/pm/epic-syncer.js +0 -221
- package/lib/prdMetadata.js +0 -270
- package/lib/providers/azure/index.js +0 -234
- package/lib/providers/factory.js +0 -87
- package/lib/providers/github/index.js +0 -204
- package/lib/providers/interface.js +0 -73
- package/lib/python/scaffold-manager.js +0 -576
- package/lib/react/scaffold-manager.js +0 -745
- package/lib/regression/analyzer.js +0 -578
- package/lib/release/manager.js +0 -324
- package/lib/tailwind/manager.js +0 -486
- package/lib/traefik/manager.js +0 -484
- package/lib/utils/colors.js +0 -126
- package/lib/utils/config.js +0 -317
- package/lib/utils/filesystem.js +0 -316
- package/lib/utils/logger.js +0 -135
- package/lib/utils/prompts.js +0 -294
- package/lib/utils/shell.js +0 -237
- package/lib/validators/email-validator.js +0 -337
- package/lib/workflow/manager.js +0 -449
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Azure DevOps Provider Implementation
|
|
5
|
-
* Maps ClaudeAutoPM concepts to Azure DevOps Work Items
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const ProviderInterface = require('../interface');
|
|
9
|
-
|
|
10
|
-
class AzureProvider extends ProviderInterface {
|
|
11
|
-
constructor(client) {
|
|
12
|
-
super(client);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create Epic Work Item
|
|
17
|
-
*/
|
|
18
|
-
async createEpic(prd) {
|
|
19
|
-
const workItem = await this.client.createWorkItem({
|
|
20
|
-
type: 'Epic',
|
|
21
|
-
fields: {
|
|
22
|
-
'System.Title': prd.title,
|
|
23
|
-
'System.Description': prd.description || '',
|
|
24
|
-
'System.Tags': 'ClaudeAutoPM;Epic'
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
id: workItem.id,
|
|
30
|
-
title: workItem.fields['System.Title'],
|
|
31
|
-
url: workItem.url || this._getWorkItemUrl(workItem.id),
|
|
32
|
-
provider: 'azure',
|
|
33
|
-
workItemType: 'Epic'
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Create User Story Work Item linked to Epic
|
|
39
|
-
*/
|
|
40
|
-
async createUserStory(epic, story) {
|
|
41
|
-
const fields = {
|
|
42
|
-
'System.Title': story.title,
|
|
43
|
-
'System.Description': story.description || '',
|
|
44
|
-
'System.Parent': epic.id,
|
|
45
|
-
'System.Tags': 'ClaudeAutoPM;UserStory'
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Add acceptance criteria if present
|
|
49
|
-
if (story.acceptanceCriteria && story.acceptanceCriteria.length > 0) {
|
|
50
|
-
fields['Microsoft.VSTS.Common.AcceptanceCriteria'] =
|
|
51
|
-
'<ul>' + story.acceptanceCriteria.map(c => `<li>${c}</li>`).join('') + '</ul>';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const workItem = await this.client.createWorkItem({
|
|
55
|
-
type: 'User Story',
|
|
56
|
-
fields: fields
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
id: workItem.id,
|
|
61
|
-
title: workItem.fields['System.Title'],
|
|
62
|
-
url: workItem.url || this._getWorkItemUrl(workItem.id),
|
|
63
|
-
parentId: epic.id,
|
|
64
|
-
workItemType: 'User Story'
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Create Task Work Item linked to User Story
|
|
70
|
-
*/
|
|
71
|
-
async createTask(parent, task) {
|
|
72
|
-
const fields = {
|
|
73
|
-
'System.Title': task.title,
|
|
74
|
-
'System.Description': task.description || '',
|
|
75
|
-
'System.Parent': parent.id,
|
|
76
|
-
'System.Tags': 'ClaudeAutoPM;Task'
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Add remaining work if specified
|
|
80
|
-
if (task.remainingWork) {
|
|
81
|
-
fields['Microsoft.VSTS.Scheduling.RemainingWork'] = task.remainingWork;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Add assigned to if specified
|
|
85
|
-
if (task.assignedTo) {
|
|
86
|
-
fields['System.AssignedTo'] = task.assignedTo;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const workItem = await this.client.createWorkItem({
|
|
90
|
-
type: 'Task',
|
|
91
|
-
fields: fields
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
id: workItem.id,
|
|
96
|
-
title: workItem.fields['System.Title'],
|
|
97
|
-
url: workItem.url || this._getWorkItemUrl(workItem.id),
|
|
98
|
-
parentId: parent.id,
|
|
99
|
-
workItemType: 'Task'
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Link work items in parent-child hierarchy
|
|
105
|
-
*/
|
|
106
|
-
async linkHierarchy(parent, child) {
|
|
107
|
-
// Azure DevOps uses the System.Parent field for hierarchy
|
|
108
|
-
await this.client.updateWorkItem({
|
|
109
|
-
id: child.id,
|
|
110
|
-
fields: {
|
|
111
|
-
'System.Parent': parent.id
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Sync complete Epic structure to Azure DevOps
|
|
118
|
-
*/
|
|
119
|
-
async syncEpic(epicData) {
|
|
120
|
-
const results = {
|
|
121
|
-
epic: null,
|
|
122
|
-
userStories: [],
|
|
123
|
-
hierarchy: {}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
// Create Epic
|
|
128
|
-
results.epic = await this.createEpic({
|
|
129
|
-
title: epicData.title,
|
|
130
|
-
description: epicData.description
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Create User Stories with Tasks
|
|
134
|
-
if (epicData.userStories && epicData.userStories.length > 0) {
|
|
135
|
-
for (const story of epicData.userStories) {
|
|
136
|
-
const createdStory = await this.createUserStory(results.epic, story);
|
|
137
|
-
|
|
138
|
-
// Track tasks for this story
|
|
139
|
-
const storyTasks = [];
|
|
140
|
-
|
|
141
|
-
// Create tasks for this user story
|
|
142
|
-
if (story.tasks && story.tasks.length > 0) {
|
|
143
|
-
for (const task of story.tasks) {
|
|
144
|
-
const createdTask = await this.createTask(createdStory, task);
|
|
145
|
-
storyTasks.push(createdTask);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
results.userStories.push({
|
|
150
|
-
...createdStory,
|
|
151
|
-
tasks: storyTasks
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Build hierarchy response
|
|
157
|
-
results.hierarchy = {
|
|
158
|
-
epic: {
|
|
159
|
-
id: results.epic.id,
|
|
160
|
-
title: results.epic.title,
|
|
161
|
-
url: results.epic.url
|
|
162
|
-
},
|
|
163
|
-
userStories: results.userStories.map(story => ({
|
|
164
|
-
id: story.id,
|
|
165
|
-
title: story.title,
|
|
166
|
-
url: story.url,
|
|
167
|
-
tasks: story.tasks.map(task => ({
|
|
168
|
-
id: task.id,
|
|
169
|
-
title: task.title,
|
|
170
|
-
url: task.url
|
|
171
|
-
}))
|
|
172
|
-
}))
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
return results;
|
|
176
|
-
|
|
177
|
-
} catch (error) {
|
|
178
|
-
throw new Error(`Failed to sync epic: ${error.message}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get work item by ID
|
|
184
|
-
*/
|
|
185
|
-
async getWorkItem(id) {
|
|
186
|
-
const workItem = await this.client.getWorkItem({ id });
|
|
187
|
-
return {
|
|
188
|
-
id: workItem.id,
|
|
189
|
-
title: workItem.fields['System.Title'],
|
|
190
|
-
description: workItem.fields['System.Description'],
|
|
191
|
-
state: workItem.fields['System.State'],
|
|
192
|
-
workItemType: workItem.fields['System.WorkItemType'],
|
|
193
|
-
parent: workItem.fields['System.Parent']
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Update work item
|
|
199
|
-
*/
|
|
200
|
-
async updateWorkItem(id, updates) {
|
|
201
|
-
const fields = {};
|
|
202
|
-
|
|
203
|
-
// Map common updates to Azure fields
|
|
204
|
-
if (updates.title) {
|
|
205
|
-
fields['System.Title'] = updates.title;
|
|
206
|
-
}
|
|
207
|
-
if (updates.description) {
|
|
208
|
-
fields['System.Description'] = updates.description;
|
|
209
|
-
}
|
|
210
|
-
if (updates.state) {
|
|
211
|
-
fields['System.State'] = updates.state;
|
|
212
|
-
}
|
|
213
|
-
if (updates.assignedTo) {
|
|
214
|
-
fields['System.AssignedTo'] = updates.assignedTo;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return await this.client.updateWorkItem({
|
|
218
|
-
id: id,
|
|
219
|
-
fields: fields
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Get work item URL
|
|
225
|
-
*/
|
|
226
|
-
_getWorkItemUrl(id) {
|
|
227
|
-
// This would need to be configured with actual org/project
|
|
228
|
-
const org = process.env.AZURE_DEVOPS_ORG || 'organization';
|
|
229
|
-
const project = process.env.AZURE_DEVOPS_PROJECT || 'project';
|
|
230
|
-
return `https://dev.azure.com/${org}/${project}/_workitems/edit/${id}`;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
module.exports = AzureProvider;
|
package/lib/providers/factory.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Provider Factory
|
|
5
|
-
* Creates appropriate provider based on configuration
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs-extra');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const GitHubProvider = require('./github');
|
|
11
|
-
const AzureProvider = require('./azure');
|
|
12
|
-
|
|
13
|
-
class ProviderFactory {
|
|
14
|
-
/**
|
|
15
|
-
* Create provider instance
|
|
16
|
-
*/
|
|
17
|
-
static create(providerName, client) {
|
|
18
|
-
switch (providerName) {
|
|
19
|
-
case 'github':
|
|
20
|
-
return new GitHubProvider(client);
|
|
21
|
-
|
|
22
|
-
case 'azure':
|
|
23
|
-
return new AzureProvider(client);
|
|
24
|
-
|
|
25
|
-
default:
|
|
26
|
-
throw new Error(`Unknown provider: ${providerName}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Auto-detect provider from configuration
|
|
32
|
-
*/
|
|
33
|
-
static autoDetect() {
|
|
34
|
-
const configPath = path.join(process.cwd(), '.claude', 'config.json');
|
|
35
|
-
|
|
36
|
-
if (!fs.existsSync(configPath)) {
|
|
37
|
-
// Default to GitHub if no config
|
|
38
|
-
return new GitHubProvider(this._getDefaultClient('github'));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const config = fs.readJsonSync(configPath);
|
|
43
|
-
const provider = config.provider || 'github';
|
|
44
|
-
const client = this._getDefaultClient(provider, config);
|
|
45
|
-
|
|
46
|
-
return this.create(provider, client);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(`Error loading config: ${error.message}`);
|
|
49
|
-
return new GitHubProvider(this._getDefaultClient('github'));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get default client for provider
|
|
55
|
-
*/
|
|
56
|
-
static _getDefaultClient(provider, config = {}) {
|
|
57
|
-
if (provider === 'azure') {
|
|
58
|
-
// Return Azure DevOps client
|
|
59
|
-
const azureConfig = config.providers?.azure || {};
|
|
60
|
-
return require('../../lib/azure/client').createClient({
|
|
61
|
-
organization: azureConfig.organization || process.env.AZURE_DEVOPS_ORG,
|
|
62
|
-
project: azureConfig.project || process.env.AZURE_DEVOPS_PROJECT,
|
|
63
|
-
token: process.env.AZURE_DEVOPS_PAT
|
|
64
|
-
});
|
|
65
|
-
} else {
|
|
66
|
-
// Return GitHub client
|
|
67
|
-
const githubConfig = config.providers?.github || {};
|
|
68
|
-
return {
|
|
69
|
-
createIssue: async (data) => {
|
|
70
|
-
// This would use the actual GitHub API
|
|
71
|
-
console.log('Creating GitHub issue:', data);
|
|
72
|
-
return { number: Math.floor(Math.random() * 1000), ...data };
|
|
73
|
-
},
|
|
74
|
-
updateIssue: async (number, data) => {
|
|
75
|
-
console.log(`Updating GitHub issue #${number}:`, data);
|
|
76
|
-
return { number, ...data };
|
|
77
|
-
},
|
|
78
|
-
getIssue: async (number) => {
|
|
79
|
-
console.log(`Getting GitHub issue #${number}`);
|
|
80
|
-
return { number, body: 'Issue body', state: 'open' };
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
module.exports = ProviderFactory;
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GitHub Provider Implementation
|
|
5
|
-
* Maps ClaudeAutoPM concepts to GitHub Issues
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const ProviderInterface = require('../interface');
|
|
9
|
-
|
|
10
|
-
class GitHubProvider extends ProviderInterface {
|
|
11
|
-
constructor(client) {
|
|
12
|
-
super(client);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Create Epic as a GitHub Issue with 'epic' label
|
|
17
|
-
*/
|
|
18
|
-
async createEpic(prd) {
|
|
19
|
-
const issue = await this.client.createIssue({
|
|
20
|
-
title: `Epic: ${prd.title}`,
|
|
21
|
-
body: this._formatEpicBody(prd),
|
|
22
|
-
labels: ['epic']
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
number: issue.number,
|
|
27
|
-
title: issue.title,
|
|
28
|
-
url: issue.html_url,
|
|
29
|
-
provider: 'github'
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create Task as GitHub Issue linked to Epic
|
|
35
|
-
*/
|
|
36
|
-
async createTask(epic, task) {
|
|
37
|
-
const body = `${task.description || task.title}\n\nPart of #${epic.number}`;
|
|
38
|
-
|
|
39
|
-
const issue = await this.client.createIssue({
|
|
40
|
-
title: task.title,
|
|
41
|
-
body: body,
|
|
42
|
-
labels: ['task']
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
number: issue.number,
|
|
47
|
-
title: issue.title,
|
|
48
|
-
url: issue.html_url,
|
|
49
|
-
epicNumber: epic.number
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* GitHub doesn't have User Stories, so tasks link directly to Epic
|
|
55
|
-
*/
|
|
56
|
-
async createUserStory(epic, story) {
|
|
57
|
-
// In GitHub, we treat user stories as labeled issues
|
|
58
|
-
const body = this._formatStoryBody(story, epic);
|
|
59
|
-
|
|
60
|
-
const issue = await this.client.createIssue({
|
|
61
|
-
title: story.title,
|
|
62
|
-
body: body,
|
|
63
|
-
labels: ['user-story', 'epic']
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
number: issue.number,
|
|
68
|
-
title: issue.title,
|
|
69
|
-
url: issue.html_url,
|
|
70
|
-
epicNumber: epic.number
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Link hierarchy via issue references in GitHub
|
|
76
|
-
*/
|
|
77
|
-
async linkHierarchy(parent, child) {
|
|
78
|
-
// GitHub uses issue references in body text
|
|
79
|
-
const currentBody = await this.client.getIssue(child.number);
|
|
80
|
-
const updatedBody = `${currentBody.body}\n\nParent: #${parent.number}`;
|
|
81
|
-
|
|
82
|
-
await this.client.updateIssue(child.number, {
|
|
83
|
-
body: updatedBody
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Sync complete Epic structure to GitHub
|
|
89
|
-
*/
|
|
90
|
-
async syncEpic(epicData) {
|
|
91
|
-
const results = {
|
|
92
|
-
epic: null,
|
|
93
|
-
userStories: [],
|
|
94
|
-
tasks: [],
|
|
95
|
-
hierarchy: {}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Create Epic
|
|
99
|
-
results.epic = await this.createEpic({
|
|
100
|
-
title: epicData.title,
|
|
101
|
-
description: epicData.description
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Create User Stories and Tasks
|
|
105
|
-
if (epicData.userStories && epicData.userStories.length > 0) {
|
|
106
|
-
for (const story of epicData.userStories) {
|
|
107
|
-
const createdStory = await this.createUserStory(results.epic, story);
|
|
108
|
-
results.userStories.push(createdStory);
|
|
109
|
-
|
|
110
|
-
// Create tasks for this story
|
|
111
|
-
if (story.tasks && story.tasks.length > 0) {
|
|
112
|
-
const storyTasks = [];
|
|
113
|
-
for (const task of story.tasks) {
|
|
114
|
-
const createdTask = await this.createTask(createdStory, task);
|
|
115
|
-
storyTasks.push(createdTask);
|
|
116
|
-
}
|
|
117
|
-
createdStory.tasks = storyTasks;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
} else if (epicData.issues) {
|
|
121
|
-
// Direct issues without user stories
|
|
122
|
-
for (const issue of epicData.issues) {
|
|
123
|
-
const createdTask = await this.createTask(results.epic, issue);
|
|
124
|
-
results.tasks.push(createdTask);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Build hierarchy for response
|
|
129
|
-
results.hierarchy = {
|
|
130
|
-
epic: results.epic,
|
|
131
|
-
userStories: results.userStories.map(story => ({
|
|
132
|
-
id: story.number,
|
|
133
|
-
tasks: story.tasks || []
|
|
134
|
-
}))
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
return results;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Get work item (issue) by ID
|
|
142
|
-
*/
|
|
143
|
-
async getWorkItem(id) {
|
|
144
|
-
const issue = await this.client.getIssue(id);
|
|
145
|
-
return {
|
|
146
|
-
id: issue.number,
|
|
147
|
-
title: issue.title,
|
|
148
|
-
body: issue.body,
|
|
149
|
-
state: issue.state,
|
|
150
|
-
labels: issue.labels
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Update work item (issue)
|
|
156
|
-
*/
|
|
157
|
-
async updateWorkItem(id, updates) {
|
|
158
|
-
return await this.client.updateIssue(id, updates);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Format Epic body for GitHub
|
|
163
|
-
*/
|
|
164
|
-
_formatEpicBody(prd) {
|
|
165
|
-
let body = `# ${prd.title}\n\n`;
|
|
166
|
-
body += `${prd.description || ''}\n\n`;
|
|
167
|
-
|
|
168
|
-
if (prd.userStories && prd.userStories.length > 0) {
|
|
169
|
-
body += '## User Stories\n\n';
|
|
170
|
-
prd.userStories.forEach(story => {
|
|
171
|
-
body += `- [ ] ${story}\n`;
|
|
172
|
-
});
|
|
173
|
-
body += '\n';
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (prd.acceptanceCriteria && prd.acceptanceCriteria.length > 0) {
|
|
177
|
-
body += '## Acceptance Criteria\n\n';
|
|
178
|
-
prd.acceptanceCriteria.forEach(criteria => {
|
|
179
|
-
body += `- ${criteria}\n`;
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return body;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Format User Story body
|
|
188
|
-
*/
|
|
189
|
-
_formatStoryBody(story, epic) {
|
|
190
|
-
let body = story.description || story.title;
|
|
191
|
-
body += `\n\nPart of Epic #${epic.number}\n\n`;
|
|
192
|
-
|
|
193
|
-
if (story.acceptanceCriteria && story.acceptanceCriteria.length > 0) {
|
|
194
|
-
body += '## Acceptance Criteria\n\n';
|
|
195
|
-
story.acceptanceCriteria.forEach(criteria => {
|
|
196
|
-
body += `- ${criteria}\n`;
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return body;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
module.exports = GitHubProvider;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Provider Interface - Abstract base class
|
|
5
|
-
* All providers must extend this interface
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
class ProviderInterface {
|
|
9
|
-
constructor(client) {
|
|
10
|
-
if (new.target === ProviderInterface) {
|
|
11
|
-
throw new TypeError('Cannot instantiate abstract class');
|
|
12
|
-
}
|
|
13
|
-
this.client = client;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create an Epic from PRD
|
|
18
|
-
*/
|
|
19
|
-
async createEpic(prd) {
|
|
20
|
-
throw new Error('Not implemented');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create a User Story (or equivalent)
|
|
25
|
-
*/
|
|
26
|
-
async createUserStory(parent, story) {
|
|
27
|
-
throw new Error('Not implemented');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Create a Task
|
|
32
|
-
*/
|
|
33
|
-
async createTask(parent, task) {
|
|
34
|
-
throw new Error('Not implemented');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Link work items in hierarchy
|
|
39
|
-
*/
|
|
40
|
-
async linkHierarchy(parent, child) {
|
|
41
|
-
throw new Error('Not implemented');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Sync complete epic structure
|
|
46
|
-
*/
|
|
47
|
-
async sync(epicData) {
|
|
48
|
-
throw new Error('Not implemented');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get a work item by ID
|
|
53
|
-
*/
|
|
54
|
-
async getWorkItem(id) {
|
|
55
|
-
throw new Error('Not implemented');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Update a work item
|
|
60
|
-
*/
|
|
61
|
-
async updateWorkItem(id, updates) {
|
|
62
|
-
throw new Error('Not implemented');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Sync an entire epic hierarchy
|
|
67
|
-
*/
|
|
68
|
-
async syncEpic(epicData) {
|
|
69
|
-
throw new Error('Not implemented');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
module.exports = ProviderInterface;
|