claude-autopm 1.17.0 → 1.20.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/README.md +159 -0
- package/autopm/.claude/agents/core/mcp-manager.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/mcp/MCP-REGISTRY.md +1 -1
- 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/bin/autopm.js +25 -0
- package/bin/commands/team.js +86 -0
- package/package.json +1 -1
- 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
package/lib/pm/epic-syncer.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Epic Syncer
|
|
5
|
-
* Syncs Epic structure to provider (GitHub/Azure DevOps)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs-extra');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const ProviderFactory = require('../providers/factory');
|
|
11
|
-
|
|
12
|
-
class EpicSyncer {
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
this.provider = options.provider || this.detectProvider();
|
|
15
|
-
this.client = options.client || this.getDefaultClient();
|
|
16
|
-
this.providerInstance = this.getProviderInstance();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Detect provider from config
|
|
21
|
-
*/
|
|
22
|
-
detectProvider() {
|
|
23
|
-
try {
|
|
24
|
-
const configPath = path.join(process.cwd(), '.claude', 'config.json');
|
|
25
|
-
if (fs.existsSync(configPath)) {
|
|
26
|
-
const config = fs.readJsonSync(configPath);
|
|
27
|
-
return config.provider || 'github';
|
|
28
|
-
}
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.error(`Error detecting provider: ${error.message}`);
|
|
31
|
-
}
|
|
32
|
-
return 'github';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get default client for provider
|
|
37
|
-
*/
|
|
38
|
-
getDefaultClient() {
|
|
39
|
-
if (this.provider === 'azure') {
|
|
40
|
-
return require('../../lib/azure/client').getClient();
|
|
41
|
-
} else {
|
|
42
|
-
// Mock GitHub client for now
|
|
43
|
-
return {
|
|
44
|
-
createIssue: async (data) => ({ number: Date.now(), ...data }),
|
|
45
|
-
updateIssue: async (number, data) => ({ number, ...data }),
|
|
46
|
-
getIssue: async (number) => ({ number, body: 'Issue body' })
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Get provider instance
|
|
53
|
-
*/
|
|
54
|
-
getProviderInstance() {
|
|
55
|
-
if (this.client) {
|
|
56
|
-
return ProviderFactory.create(this.provider, this.client);
|
|
57
|
-
}
|
|
58
|
-
return ProviderFactory.autoDetect();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Sync epic to provider
|
|
63
|
-
*/
|
|
64
|
-
async sync(epicName) {
|
|
65
|
-
const epicJsonPath = path.join(process.cwd(), '.claude', 'epics', `${epicName}.json`);
|
|
66
|
-
|
|
67
|
-
if (!await fs.pathExists(epicJsonPath)) {
|
|
68
|
-
throw new Error(`Epic not found: ${epicName}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const epicData = await fs.readJson(epicJsonPath);
|
|
72
|
-
|
|
73
|
-
let result;
|
|
74
|
-
if (this.provider === 'azure') {
|
|
75
|
-
result = await this.syncToAzure(epicData);
|
|
76
|
-
} else {
|
|
77
|
-
result = await this.syncToGitHub(epicData);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Update epic file with provider IDs
|
|
81
|
-
await this.updateEpicFile(epicName, epicData, result);
|
|
82
|
-
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Sync to Azure DevOps
|
|
88
|
-
*/
|
|
89
|
-
async syncToAzure(epicData) {
|
|
90
|
-
try {
|
|
91
|
-
const result = await this.providerInstance.syncEpic(epicData);
|
|
92
|
-
|
|
93
|
-
// Add Azure-specific URLs and IDs
|
|
94
|
-
if (result.hierarchy) {
|
|
95
|
-
result.epic = result.hierarchy.epic;
|
|
96
|
-
result.userStories = result.hierarchy.userStories;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return result;
|
|
100
|
-
} catch (error) {
|
|
101
|
-
throw new Error(`Failed to sync epic: ${error.message}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Sync to GitHub
|
|
107
|
-
*/
|
|
108
|
-
async syncToGitHub(epicData) {
|
|
109
|
-
try {
|
|
110
|
-
const result = await this.providerInstance.syncEpic(epicData);
|
|
111
|
-
|
|
112
|
-
// GitHub returns a simpler structure
|
|
113
|
-
return result;
|
|
114
|
-
} catch (error) {
|
|
115
|
-
throw new Error(`Failed to sync epic: ${error.message}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Update epic file with provider IDs
|
|
121
|
-
*/
|
|
122
|
-
async updateEpicFile(epicName, epicData, syncResult) {
|
|
123
|
-
const epicJsonPath = path.join(process.cwd(), '.claude', 'epics', `${epicName}.json`);
|
|
124
|
-
|
|
125
|
-
// Add provider sync information
|
|
126
|
-
if (this.provider === 'azure') {
|
|
127
|
-
epicData.azureDevOps = {
|
|
128
|
-
epicId: syncResult.epic.id,
|
|
129
|
-
url: syncResult.epic.url,
|
|
130
|
-
syncedAt: new Date().toISOString()
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// Add IDs to user stories
|
|
134
|
-
if (epicData.userStories && syncResult.userStories) {
|
|
135
|
-
epicData.userStories.forEach((story, index) => {
|
|
136
|
-
const syncedStory = syncResult.userStories[index];
|
|
137
|
-
if (syncedStory) {
|
|
138
|
-
story.azureDevOps = {
|
|
139
|
-
storyId: syncedStory.id,
|
|
140
|
-
url: syncedStory.url
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Add IDs to tasks
|
|
144
|
-
if (story.tasks && syncedStory.tasks) {
|
|
145
|
-
story.tasks.forEach((task, taskIndex) => {
|
|
146
|
-
const syncedTask = syncedStory.tasks[taskIndex];
|
|
147
|
-
if (syncedTask) {
|
|
148
|
-
task.azureDevOps = {
|
|
149
|
-
taskId: syncedTask.id,
|
|
150
|
-
url: syncedTask.url
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
} else if (this.provider === 'github') {
|
|
159
|
-
epicData.github = {
|
|
160
|
-
epicNumber: syncResult.epic.number,
|
|
161
|
-
url: syncResult.epic.url,
|
|
162
|
-
syncedAt: new Date().toISOString()
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
// Add issue numbers
|
|
166
|
-
if (epicData.issues && syncResult.tasks) {
|
|
167
|
-
epicData.issues.forEach((issue, index) => {
|
|
168
|
-
const syncedIssue = syncResult.tasks[index];
|
|
169
|
-
if (syncedIssue) {
|
|
170
|
-
issue.github = {
|
|
171
|
-
issueNumber: syncedIssue.number,
|
|
172
|
-
url: syncedIssue.url
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Save updated epic
|
|
180
|
-
await fs.writeJson(epicJsonPath, epicData, { spaces: 2 });
|
|
181
|
-
|
|
182
|
-
// Also update markdown file
|
|
183
|
-
await this.updateEpicMarkdown(epicName, epicData);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Update epic markdown file
|
|
188
|
-
*/
|
|
189
|
-
async updateEpicMarkdown(epicName, epicData) {
|
|
190
|
-
const epicMdPath = path.join(process.cwd(), '.claude', 'epics', `${epicName}.md`);
|
|
191
|
-
|
|
192
|
-
if (!await fs.pathExists(epicMdPath)) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let markdown = await fs.readFile(epicMdPath, 'utf8');
|
|
197
|
-
|
|
198
|
-
// Add sync status to frontmatter
|
|
199
|
-
const frontmatterEnd = markdown.indexOf('---', 3);
|
|
200
|
-
if (frontmatterEnd > 0) {
|
|
201
|
-
const frontmatter = markdown.substring(0, frontmatterEnd);
|
|
202
|
-
const content = markdown.substring(frontmatterEnd);
|
|
203
|
-
|
|
204
|
-
let syncInfo = '';
|
|
205
|
-
if (this.provider === 'azure' && epicData.azureDevOps) {
|
|
206
|
-
syncInfo = `\nazure_epic_id: ${epicData.azureDevOps.epicId}`;
|
|
207
|
-
syncInfo += `\nazure_url: ${epicData.azureDevOps.url}`;
|
|
208
|
-
} else if (this.provider === 'github' && epicData.github) {
|
|
209
|
-
syncInfo = `\ngithub_epic_number: ${epicData.github.epicNumber}`;
|
|
210
|
-
syncInfo += `\ngithub_url: ${epicData.github.url}`;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (syncInfo) {
|
|
214
|
-
markdown = frontmatter + syncInfo + '\nsynced: true' + content;
|
|
215
|
-
await fs.writeFile(epicMdPath, markdown);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
module.exports = EpicSyncer;
|
package/lib/prdMetadata.js
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PRD Metadata Management System
|
|
3
|
-
* Handles progress tracking, validation, and section dependencies
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
|
|
9
|
-
class PRDMetadata {
|
|
10
|
-
constructor(featureName) {
|
|
11
|
-
this.featureName = featureName;
|
|
12
|
-
this.prdsDir = '.claude/prds';
|
|
13
|
-
this.draftsDir = path.join(this.prdsDir, 'drafts');
|
|
14
|
-
this.metaDir = path.join(this.prdsDir, 'meta');
|
|
15
|
-
this.prdPath = path.join(this.draftsDir, `${featureName}.md`);
|
|
16
|
-
this.metaPath = path.join(this.metaDir, `${featureName}.json`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Load metadata from file
|
|
21
|
-
*/
|
|
22
|
-
load() {
|
|
23
|
-
if (!fs.existsSync(this.metaPath)) {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
return JSON.parse(fs.readFileSync(this.metaPath, 'utf-8'));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Save metadata to file
|
|
31
|
-
*/
|
|
32
|
-
save(metadata) {
|
|
33
|
-
// Ensure directories exist
|
|
34
|
-
if (!fs.existsSync(this.metaDir)) {
|
|
35
|
-
fs.mkdirSync(this.metaDir, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
fs.writeFileSync(this.metaPath, JSON.stringify(metadata, null, 2));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Update section content and metadata
|
|
43
|
-
*/
|
|
44
|
-
updateSection(sectionName, content) {
|
|
45
|
-
const metadata = this.load();
|
|
46
|
-
if (!metadata) {
|
|
47
|
-
throw new Error(`PRD metadata not found for ${this.featureName}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const sectionKey = sectionName.toLowerCase().replace(/\s+/g, '-');
|
|
51
|
-
const timestamp = new Date().toISOString();
|
|
52
|
-
const wordCount = content.trim().split(/\s+/).length;
|
|
53
|
-
|
|
54
|
-
// Update section metadata
|
|
55
|
-
if (!metadata.sections[sectionKey]) {
|
|
56
|
-
metadata.sections[sectionKey] = {};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
metadata.sections[sectionKey] = {
|
|
60
|
-
...metadata.sections[sectionKey],
|
|
61
|
-
status: content.trim() ? 'completed' : 'empty',
|
|
62
|
-
word_count: wordCount,
|
|
63
|
-
last_edited: timestamp,
|
|
64
|
-
dependencies_met: this.checkDependencies(sectionKey, metadata)
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// Update overall progress
|
|
68
|
-
metadata.last_activity = timestamp;
|
|
69
|
-
this.updateProgress(metadata);
|
|
70
|
-
|
|
71
|
-
// Update PRD file
|
|
72
|
-
this.updatePRDContent(sectionName, content);
|
|
73
|
-
|
|
74
|
-
// Save metadata
|
|
75
|
-
this.save(metadata);
|
|
76
|
-
|
|
77
|
-
return metadata;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Update PRD file content for specific section
|
|
82
|
-
*/
|
|
83
|
-
updatePRDContent(sectionName, content) {
|
|
84
|
-
if (!fs.existsSync(this.prdPath)) {
|
|
85
|
-
throw new Error(`PRD file not found: ${this.prdPath}`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let prdContent = fs.readFileSync(this.prdPath, 'utf-8');
|
|
89
|
-
|
|
90
|
-
// Find section boundaries
|
|
91
|
-
const sectionHeaderRegex = new RegExp(`^## ${sectionName}$`, 'm');
|
|
92
|
-
const nextSectionRegex = /^## /gm;
|
|
93
|
-
|
|
94
|
-
const headerMatch = prdContent.match(sectionHeaderRegex);
|
|
95
|
-
if (!headerMatch) {
|
|
96
|
-
throw new Error(`Section '${sectionName}' not found in PRD`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const headerIndex = headerMatch.index;
|
|
100
|
-
const headerEnd = headerIndex + headerMatch[0].length;
|
|
101
|
-
|
|
102
|
-
// Find next section
|
|
103
|
-
nextSectionRegex.lastIndex = headerEnd;
|
|
104
|
-
const nextMatch = nextSectionRegex.exec(prdContent);
|
|
105
|
-
|
|
106
|
-
const sectionEnd = nextMatch ? nextMatch.index : prdContent.length;
|
|
107
|
-
|
|
108
|
-
// Replace section content
|
|
109
|
-
const beforeSection = prdContent.slice(0, headerEnd);
|
|
110
|
-
const afterSection = prdContent.slice(sectionEnd);
|
|
111
|
-
const newContent = content.trim() ? `\n\n${content.trim()}\n\n` : '\n\n<!-- This section will be filled by AI during conversation -->\n\n';
|
|
112
|
-
|
|
113
|
-
prdContent = beforeSection + newContent + afterSection;
|
|
114
|
-
|
|
115
|
-
fs.writeFileSync(this.prdPath, prdContent);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Check if section dependencies are met
|
|
120
|
-
*/
|
|
121
|
-
checkDependencies(sectionKey, metadata) {
|
|
122
|
-
const sectionTemplatesDir = path.join(__dirname, '../autopm/.claude/prds/templates/sections');
|
|
123
|
-
const templatePath = path.join(sectionTemplatesDir, `${sectionKey}.md`);
|
|
124
|
-
|
|
125
|
-
if (!fs.existsSync(templatePath)) {
|
|
126
|
-
return true; // No template means no dependencies
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
130
|
-
const yamlMatch = templateContent.match(/^---\n([\s\S]*?)\n---/);
|
|
131
|
-
|
|
132
|
-
if (!yamlMatch) {
|
|
133
|
-
return true; // No YAML frontmatter means no dependencies
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const yaml = require('js-yaml');
|
|
137
|
-
const config = yaml.load(yamlMatch[1]);
|
|
138
|
-
|
|
139
|
-
if (!config.dependencies || config.dependencies.length === 0) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return config.dependencies.every(dep => {
|
|
144
|
-
const depKey = dep.toLowerCase().replace(/\s+/g, '-');
|
|
145
|
-
return metadata.sections[depKey] &&
|
|
146
|
-
metadata.sections[depKey].status === 'completed';
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Update overall progress statistics
|
|
152
|
-
*/
|
|
153
|
-
updateProgress(metadata) {
|
|
154
|
-
const sections = Object.values(metadata.sections);
|
|
155
|
-
const totalSections = sections.length;
|
|
156
|
-
const completedSections = sections.filter(s => s.status === 'completed').length;
|
|
157
|
-
const inProgressSections = sections.filter(s => s.status === 'in_progress').length;
|
|
158
|
-
const emptySections = sections.filter(s => s.status === 'empty').length;
|
|
159
|
-
|
|
160
|
-
metadata.progress = {
|
|
161
|
-
total_sections: totalSections,
|
|
162
|
-
completed_sections: completedSections,
|
|
163
|
-
in_progress_sections: inProgressSections,
|
|
164
|
-
empty_sections: emptySections,
|
|
165
|
-
completion_percentage: Math.round((completedSections / totalSections) * 100)
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// Update validation status
|
|
169
|
-
this.updateValidationStatus(metadata);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Update validation status
|
|
174
|
-
*/
|
|
175
|
-
updateValidationStatus(metadata) {
|
|
176
|
-
const timestamp = new Date().toISOString();
|
|
177
|
-
const issues = [];
|
|
178
|
-
|
|
179
|
-
// Check for empty required sections
|
|
180
|
-
Object.entries(metadata.sections).forEach(([key, section]) => {
|
|
181
|
-
if (section.status === 'empty') {
|
|
182
|
-
issues.push(`Section '${key}' is empty`);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Check word counts
|
|
187
|
-
const lowWordCountSections = Object.entries(metadata.sections)
|
|
188
|
-
.filter(([key, section]) => section.status === 'completed' && section.word_count < 50)
|
|
189
|
-
.map(([key]) => key);
|
|
190
|
-
|
|
191
|
-
if (lowWordCountSections.length > 0) {
|
|
192
|
-
issues.push(`Low word count in: ${lowWordCountSections.join(', ')}`);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Calculate overall score
|
|
196
|
-
const completionScore = metadata.progress.completion_percentage;
|
|
197
|
-
const qualityScore = Math.max(0, 100 - (lowWordCountSections.length * 20));
|
|
198
|
-
const overallScore = Math.round((completionScore + qualityScore) / 2);
|
|
199
|
-
|
|
200
|
-
metadata.validation = {
|
|
201
|
-
last_check: timestamp,
|
|
202
|
-
overall_score: overallScore,
|
|
203
|
-
issues: issues,
|
|
204
|
-
ready_for_review: issues.length === 0 && completionScore >= 80,
|
|
205
|
-
ready_for_publish: issues.length === 0 && completionScore === 100 && overallScore >= 90
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get next recommended section to work on
|
|
211
|
-
*/
|
|
212
|
-
getNextSection(metadata) {
|
|
213
|
-
// Define logical section order (recommended workflow)
|
|
214
|
-
const sectionPriority = [
|
|
215
|
-
'problem-statement',
|
|
216
|
-
'success-criteria',
|
|
217
|
-
'user-stories',
|
|
218
|
-
'acceptance-criteria',
|
|
219
|
-
'out-of-scope',
|
|
220
|
-
'executive-summary'
|
|
221
|
-
];
|
|
222
|
-
|
|
223
|
-
// Find empty sections with met dependencies
|
|
224
|
-
const emptySections = Object.entries(metadata.sections)
|
|
225
|
-
.filter(([key, section]) => section.status === 'empty')
|
|
226
|
-
.map(([key, section]) => ({
|
|
227
|
-
key,
|
|
228
|
-
section,
|
|
229
|
-
dependencies_met: this.checkDependencies(key, metadata),
|
|
230
|
-
priority: sectionPriority.indexOf(key)
|
|
231
|
-
}))
|
|
232
|
-
.sort((a, b) => a.priority - b.priority); // Sort by priority
|
|
233
|
-
|
|
234
|
-
// Prioritize sections with met dependencies
|
|
235
|
-
const readySections = emptySections.filter(s => s.dependencies_met);
|
|
236
|
-
if (readySections.length > 0) {
|
|
237
|
-
return readySections[0].key;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Return first empty section if none are ready
|
|
241
|
-
if (emptySections.length > 0) {
|
|
242
|
-
return emptySections[0].key;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Convert section key to display name
|
|
250
|
-
*/
|
|
251
|
-
static keyToDisplayName(key) {
|
|
252
|
-
return key.split('-')
|
|
253
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
254
|
-
.join(' ');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get section status emoji
|
|
259
|
-
*/
|
|
260
|
-
static getStatusEmoji(status) {
|
|
261
|
-
switch (status) {
|
|
262
|
-
case 'completed': return '✅';
|
|
263
|
-
case 'in_progress': return '🔄';
|
|
264
|
-
case 'empty': return '⭕';
|
|
265
|
-
default: return '❓';
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
module.exports = PRDMetadata;
|