clavix 4.11.2 → 5.0.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 +72 -60
- package/dist/cli/commands/update.js +9 -10
- package/dist/templates/agents/agents.md +14 -8
- package/dist/templates/agents/copilot-instructions.md +1 -1
- package/dist/templates/instructions/core/verification.md +2 -2
- package/dist/templates/slash-commands/_canonical/archive.md +83 -121
- package/dist/templates/slash-commands/_canonical/execute.md +32 -42
- package/dist/templates/slash-commands/_canonical/implement.md +32 -44
- package/dist/templates/slash-commands/_canonical/improve.md +14 -53
- package/dist/templates/slash-commands/_canonical/start.md +1 -1
- package/dist/templates/slash-commands/_canonical/summarize.md +8 -8
- package/dist/templates/slash-commands/_components/agent-protocols/cli-reference.md +84 -180
- package/dist/templates/slash-commands/_components/agent-protocols/error-handling.md +2 -2
- package/dist/templates/slash-commands/_components/agent-protocols/file-formats.md +41 -59
- package/dist/templates/slash-commands/_components/agent-protocols/state-assertion.md +1 -1
- package/dist/templates/slash-commands/_components/references/intent-types.md +1 -1
- package/dist/templates/slash-commands/_components/sections/file-saving-protocol.md +20 -27
- package/dist/templates/slash-commands/_components/sections/pattern-visibility.md +29 -46
- package/dist/templates/slash-commands/_components/troubleshooting/file-not-saved.md +4 -5
- package/dist/types/config.d.ts +57 -0
- package/dist/utils/legacy-command-cleanup.js +31 -4
- package/package.json +5 -4
- package/dist/cli/commands/analyze.d.ts +0 -17
- package/dist/cli/commands/analyze.js +0 -133
- package/dist/cli/commands/archive.d.ts +0 -36
- package/dist/cli/commands/archive.js +0 -266
- package/dist/cli/commands/deep.d.ts +0 -17
- package/dist/cli/commands/deep.js +0 -170
- package/dist/cli/commands/execute.d.ts +0 -15
- package/dist/cli/commands/execute.js +0 -168
- package/dist/cli/commands/fast.d.ts +0 -18
- package/dist/cli/commands/fast.js +0 -219
- package/dist/cli/commands/implement.d.ts +0 -24
- package/dist/cli/commands/implement.js +0 -289
- package/dist/cli/commands/improve.d.ts +0 -32
- package/dist/cli/commands/improve.js +0 -250
- package/dist/cli/commands/list.d.ts +0 -17
- package/dist/cli/commands/list.js +0 -217
- package/dist/cli/commands/plan.d.ts +0 -21
- package/dist/cli/commands/plan.js +0 -297
- package/dist/cli/commands/prd.d.ts +0 -24
- package/dist/cli/commands/prd.js +0 -321
- package/dist/cli/commands/prompts/clear.d.ts +0 -16
- package/dist/cli/commands/prompts/clear.js +0 -222
- package/dist/cli/commands/prompts/list.d.ts +0 -8
- package/dist/cli/commands/prompts/list.js +0 -88
- package/dist/cli/commands/show.d.ts +0 -21
- package/dist/cli/commands/show.js +0 -191
- package/dist/cli/commands/start.d.ts +0 -40
- package/dist/cli/commands/start.js +0 -210
- package/dist/cli/commands/summarize.d.ts +0 -17
- package/dist/cli/commands/summarize.js +0 -196
- package/dist/cli/commands/task-complete.d.ts +0 -27
- package/dist/cli/commands/task-complete.js +0 -269
- package/dist/cli/commands/verify.d.ts +0 -28
- package/dist/cli/commands/verify.js +0 -349
- package/dist/core/archive-manager.d.ts +0 -100
- package/dist/core/archive-manager.js +0 -302
- package/dist/core/basic-checklist-generator.d.ts +0 -35
- package/dist/core/basic-checklist-generator.js +0 -344
- package/dist/core/checklist-parser.d.ts +0 -48
- package/dist/core/checklist-parser.js +0 -238
- package/dist/core/config-manager.d.ts +0 -149
- package/dist/core/config-manager.js +0 -230
- package/dist/core/conversation-analyzer.d.ts +0 -86
- package/dist/core/conversation-analyzer.js +0 -387
- package/dist/core/conversation-quality-tracker.d.ts +0 -81
- package/dist/core/conversation-quality-tracker.js +0 -195
- package/dist/core/git-manager.d.ts +0 -126
- package/dist/core/git-manager.js +0 -282
- package/dist/core/intelligence/confidence-calculator.d.ts +0 -93
- package/dist/core/intelligence/confidence-calculator.js +0 -124
- package/dist/core/intelligence/index.d.ts +0 -11
- package/dist/core/intelligence/index.js +0 -15
- package/dist/core/intelligence/intent-detector.d.ts +0 -54
- package/dist/core/intelligence/intent-detector.js +0 -723
- package/dist/core/intelligence/pattern-library.d.ts +0 -104
- package/dist/core/intelligence/pattern-library.js +0 -339
- package/dist/core/intelligence/patterns/actionability-enhancer.d.ts +0 -27
- package/dist/core/intelligence/patterns/actionability-enhancer.js +0 -192
- package/dist/core/intelligence/patterns/alternative-phrasing-generator.d.ts +0 -29
- package/dist/core/intelligence/patterns/alternative-phrasing-generator.js +0 -239
- package/dist/core/intelligence/patterns/ambiguity-detector.d.ts +0 -22
- package/dist/core/intelligence/patterns/ambiguity-detector.js +0 -196
- package/dist/core/intelligence/patterns/assumption-explicitizer.d.ts +0 -30
- package/dist/core/intelligence/patterns/assumption-explicitizer.js +0 -296
- package/dist/core/intelligence/patterns/base-pattern.d.ts +0 -192
- package/dist/core/intelligence/patterns/base-pattern.js +0 -103
- package/dist/core/intelligence/patterns/completeness-validator.d.ts +0 -27
- package/dist/core/intelligence/patterns/completeness-validator.js +0 -221
- package/dist/core/intelligence/patterns/conciseness-filter.d.ts +0 -20
- package/dist/core/intelligence/patterns/conciseness-filter.js +0 -92
- package/dist/core/intelligence/patterns/context-precision.d.ts +0 -32
- package/dist/core/intelligence/patterns/context-precision.js +0 -389
- package/dist/core/intelligence/patterns/conversation-summarizer.d.ts +0 -30
- package/dist/core/intelligence/patterns/conversation-summarizer.js +0 -277
- package/dist/core/intelligence/patterns/dependency-identifier.d.ts +0 -23
- package/dist/core/intelligence/patterns/dependency-identifier.js +0 -166
- package/dist/core/intelligence/patterns/domain-context-enricher.d.ts +0 -21
- package/dist/core/intelligence/patterns/domain-context-enricher.js +0 -198
- package/dist/core/intelligence/patterns/edge-case-identifier.d.ts +0 -30
- package/dist/core/intelligence/patterns/edge-case-identifier.js +0 -269
- package/dist/core/intelligence/patterns/error-tolerance-enhancer.d.ts +0 -22
- package/dist/core/intelligence/patterns/error-tolerance-enhancer.js +0 -179
- package/dist/core/intelligence/patterns/implicit-requirement-extractor.d.ts +0 -24
- package/dist/core/intelligence/patterns/implicit-requirement-extractor.js +0 -259
- package/dist/core/intelligence/patterns/objective-clarifier.d.ts +0 -22
- package/dist/core/intelligence/patterns/objective-clarifier.js +0 -126
- package/dist/core/intelligence/patterns/output-format-enforcer.d.ts +0 -22
- package/dist/core/intelligence/patterns/output-format-enforcer.js +0 -151
- package/dist/core/intelligence/patterns/prd-structure-enforcer.d.ts +0 -23
- package/dist/core/intelligence/patterns/prd-structure-enforcer.js +0 -183
- package/dist/core/intelligence/patterns/prerequisite-identifier.d.ts +0 -23
- package/dist/core/intelligence/patterns/prerequisite-identifier.js +0 -221
- package/dist/core/intelligence/patterns/requirement-prioritizer.d.ts +0 -24
- package/dist/core/intelligence/patterns/requirement-prioritizer.js +0 -134
- package/dist/core/intelligence/patterns/scope-definer.d.ts +0 -26
- package/dist/core/intelligence/patterns/scope-definer.js +0 -236
- package/dist/core/intelligence/patterns/step-decomposer.d.ts +0 -31
- package/dist/core/intelligence/patterns/step-decomposer.js +0 -242
- package/dist/core/intelligence/patterns/structure-organizer.d.ts +0 -31
- package/dist/core/intelligence/patterns/structure-organizer.js +0 -218
- package/dist/core/intelligence/patterns/success-criteria-enforcer.d.ts +0 -22
- package/dist/core/intelligence/patterns/success-criteria-enforcer.js +0 -165
- package/dist/core/intelligence/patterns/success-metrics-enforcer.d.ts +0 -24
- package/dist/core/intelligence/patterns/success-metrics-enforcer.js +0 -165
- package/dist/core/intelligence/patterns/technical-context-enricher.d.ts +0 -25
- package/dist/core/intelligence/patterns/technical-context-enricher.js +0 -165
- package/dist/core/intelligence/patterns/topic-coherence-analyzer.d.ts +0 -26
- package/dist/core/intelligence/patterns/topic-coherence-analyzer.js +0 -300
- package/dist/core/intelligence/patterns/user-persona-enricher.d.ts +0 -24
- package/dist/core/intelligence/patterns/user-persona-enricher.js +0 -141
- package/dist/core/intelligence/patterns/validation-checklist-creator.d.ts +0 -31
- package/dist/core/intelligence/patterns/validation-checklist-creator.js +0 -242
- package/dist/core/intelligence/quality-assessor.d.ts +0 -51
- package/dist/core/intelligence/quality-assessor.js +0 -505
- package/dist/core/intelligence/types.d.ts +0 -111
- package/dist/core/intelligence/types.js +0 -3
- package/dist/core/intelligence/universal-optimizer.d.ts +0 -84
- package/dist/core/intelligence/universal-optimizer.js +0 -371
- package/dist/core/prd-generator.d.ts +0 -76
- package/dist/core/prd-generator.js +0 -173
- package/dist/core/prompt-manager.d.ts +0 -110
- package/dist/core/prompt-manager.js +0 -274
- package/dist/core/prompt-optimizer.d.ts +0 -268
- package/dist/core/prompt-optimizer.js +0 -959
- package/dist/core/question-engine.d.ts +0 -167
- package/dist/core/question-engine.js +0 -356
- package/dist/core/session-manager.d.ts +0 -139
- package/dist/core/session-manager.js +0 -365
- package/dist/core/task-manager.d.ts +0 -211
- package/dist/core/task-manager.js +0 -981
- package/dist/core/verification-hooks.d.ts +0 -67
- package/dist/core/verification-hooks.js +0 -309
- package/dist/core/verification-manager.d.ts +0 -107
- package/dist/core/verification-manager.js +0 -415
- package/dist/index 2.js +0 -13
- package/dist/index.d 2.ts +0 -4
- package/dist/types/session.d.ts +0 -78
- package/dist/types/session.js +0 -8
- package/dist/types/verification.d.ts +0 -205
- package/dist/types/verification.js +0 -9
|
@@ -1,981 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TaskManager - Manages PRD-based task generation and execution
|
|
3
|
-
*
|
|
4
|
-
* This class handles:
|
|
5
|
-
* - Analyzing PRD documents
|
|
6
|
-
* - Generating optimized task breakdowns
|
|
7
|
-
* - Reading/writing tasks.md with checkbox format
|
|
8
|
-
* - Tracking task completion state
|
|
9
|
-
* - Managing session resume capability
|
|
10
|
-
*/
|
|
11
|
-
import fs from 'fs-extra';
|
|
12
|
-
import * as path from 'path';
|
|
13
|
-
import { FileSystem } from '../utils/file-system.js';
|
|
14
|
-
const SOURCE_FILE_MAP = {
|
|
15
|
-
full: ['full-prd.md', 'PRD.md', 'prd.md', 'Full-PRD.md', 'FULL_PRD.md', 'FULL-PRD.md'],
|
|
16
|
-
quick: ['quick-prd.md', 'QUICK_PRD.md'],
|
|
17
|
-
mini: ['mini-prd.md'],
|
|
18
|
-
prompt: ['optimized-prompt.md'],
|
|
19
|
-
};
|
|
20
|
-
const SOURCE_ORDER_AUTO = [
|
|
21
|
-
'full',
|
|
22
|
-
'quick',
|
|
23
|
-
'mini',
|
|
24
|
-
'prompt',
|
|
25
|
-
];
|
|
26
|
-
const ALL_KNOWN_PRD_FILES = Array.from(new Set(Object.values(SOURCE_FILE_MAP).flat()));
|
|
27
|
-
/**
|
|
28
|
-
* TaskManager class
|
|
29
|
-
*
|
|
30
|
-
* Generates and manages implementation tasks from PRD documents
|
|
31
|
-
*/
|
|
32
|
-
export class TaskManager {
|
|
33
|
-
constructor() {
|
|
34
|
-
// TaskManager uses Clavix Intelligence for optimization
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Generate tasks.md from PRD
|
|
38
|
-
*
|
|
39
|
-
* @param prdPath - Path to the PRD directory
|
|
40
|
-
* @param options - Generation options
|
|
41
|
-
* @returns Task generation result
|
|
42
|
-
*/
|
|
43
|
-
async generateTasksFromPrd(prdPath, options = {}) {
|
|
44
|
-
// Read the full PRD
|
|
45
|
-
const { path: fullPrdPath, sourceType } = await this.resolvePrdFile(prdPath, options.source ?? 'auto');
|
|
46
|
-
const prdContent = await fs.readFile(fullPrdPath, 'utf-8');
|
|
47
|
-
// Analyze PRD and generate tasks
|
|
48
|
-
const phases = await this.analyzePrdAndGenerateTasks(prdContent, options);
|
|
49
|
-
// Write tasks.md
|
|
50
|
-
const outputPath = path.join(prdPath, 'tasks.md');
|
|
51
|
-
await this.writeTasksFile(outputPath, phases, prdContent);
|
|
52
|
-
return {
|
|
53
|
-
phases,
|
|
54
|
-
totalTasks: phases.reduce((sum, phase) => sum + phase.tasks.length, 0),
|
|
55
|
-
outputPath,
|
|
56
|
-
sourcePath: fullPrdPath,
|
|
57
|
-
sourceType,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Find the PRD file in a directory
|
|
62
|
-
*/
|
|
63
|
-
async resolvePrdFile(prdPath, preferredSource) {
|
|
64
|
-
const order = preferredSource === 'auto' ? SOURCE_ORDER_AUTO : [preferredSource];
|
|
65
|
-
for (const source of order) {
|
|
66
|
-
const filenames = SOURCE_FILE_MAP[source];
|
|
67
|
-
for (const filename of filenames) {
|
|
68
|
-
const filepath = path.join(prdPath, filename);
|
|
69
|
-
if (await fs.pathExists(filepath)) {
|
|
70
|
-
return { path: filepath, sourceType: source };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (preferredSource !== 'auto') {
|
|
75
|
-
throw new Error(`No PRD artifacts found for source "${preferredSource}" in ${prdPath}`);
|
|
76
|
-
}
|
|
77
|
-
throw new Error(`No PRD artifacts found in ${prdPath}`);
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Analyze PRD content and generate task breakdown
|
|
81
|
-
*/
|
|
82
|
-
async analyzePrdAndGenerateTasks(prdContent, options) {
|
|
83
|
-
const phases = [];
|
|
84
|
-
// Parse PRD sections
|
|
85
|
-
const sections = this.parsePrdSections(prdContent);
|
|
86
|
-
const coreSection = this.getSectionByAliases(sections, [
|
|
87
|
-
'requirements',
|
|
88
|
-
'corefeatures',
|
|
89
|
-
'features',
|
|
90
|
-
'keyrequirements',
|
|
91
|
-
]);
|
|
92
|
-
if (coreSection) {
|
|
93
|
-
phases.push(...this.generatePhasesFromCoreFeatures(coreSection, options));
|
|
94
|
-
}
|
|
95
|
-
if (phases.length === 0 && sections.requirements) {
|
|
96
|
-
phases.push(...this.generateTasksFromRequirements(sections.requirements, sections));
|
|
97
|
-
}
|
|
98
|
-
const technicalSection = this.getSectionByAliases(sections, [
|
|
99
|
-
'technicalrequirements',
|
|
100
|
-
'technicalconstraints',
|
|
101
|
-
]);
|
|
102
|
-
if (technicalSection) {
|
|
103
|
-
this.injectTechnicalConstraintsTask(phases, technicalSection, options);
|
|
104
|
-
}
|
|
105
|
-
const successSection = this.getSectionByAliases(sections, [
|
|
106
|
-
'successcriteria',
|
|
107
|
-
'acceptancecriteria',
|
|
108
|
-
]);
|
|
109
|
-
if (successSection) {
|
|
110
|
-
this.appendSuccessCriteriaPhase(phases, successSection, options);
|
|
111
|
-
}
|
|
112
|
-
if (phases.length === 0) {
|
|
113
|
-
phases.push(this.generateDefaultPhases(prdContent));
|
|
114
|
-
}
|
|
115
|
-
// Ensure all tasks follow CLEAR principles (applied AFTER all tasks are added)
|
|
116
|
-
phases.forEach((phase) => {
|
|
117
|
-
phase.tasks = phase.tasks.map((task) => ({
|
|
118
|
-
...task,
|
|
119
|
-
description: this.optimizeTaskDescription(task.description),
|
|
120
|
-
}));
|
|
121
|
-
});
|
|
122
|
-
return phases;
|
|
123
|
-
}
|
|
124
|
-
getSectionByAliases(sections, aliases) {
|
|
125
|
-
for (const alias of aliases) {
|
|
126
|
-
if (sections[alias]) {
|
|
127
|
-
return sections[alias];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Generate phases from core features with intelligent grouping
|
|
134
|
-
* CRITICAL FIX: Group related features instead of 1 phase per bullet
|
|
135
|
-
*/
|
|
136
|
-
generatePhasesFromCoreFeatures(coreContent, _options) {
|
|
137
|
-
const bullets = this.extractListItems(coreContent);
|
|
138
|
-
if (bullets.length === 0) {
|
|
139
|
-
return [];
|
|
140
|
-
}
|
|
141
|
-
// GRANULARITY CONTROL: Warn if too many bullets
|
|
142
|
-
if (bullets.length > 50) {
|
|
143
|
-
console.warn(`Warning: PRD contains ${bullets.length} top-level features. Consider grouping related items.`);
|
|
144
|
-
}
|
|
145
|
-
// Group features by type/category instead of creating separate phases
|
|
146
|
-
const groupedFeatures = this.groupFeaturesByCategory(bullets);
|
|
147
|
-
const phases = [];
|
|
148
|
-
let phaseNumber = 1;
|
|
149
|
-
for (const [category, features] of Object.entries(groupedFeatures)) {
|
|
150
|
-
const phaseName = `Phase ${phaseNumber}: ${category}`;
|
|
151
|
-
const tasks = [];
|
|
152
|
-
// Generate tasks for each feature in this group
|
|
153
|
-
features.forEach((feature) => {
|
|
154
|
-
const taskDescriptions = this.buildFeatureTaskDescriptions(feature);
|
|
155
|
-
taskDescriptions.forEach((description) => {
|
|
156
|
-
tasks.push({
|
|
157
|
-
id: `${this.sanitizeId(phaseName)}-${tasks.length + 1}`,
|
|
158
|
-
description,
|
|
159
|
-
phase: phaseName,
|
|
160
|
-
completed: false,
|
|
161
|
-
prdReference: feature,
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
// GRANULARITY CONTROL: Skip empty phases
|
|
166
|
-
if (tasks.length > 0) {
|
|
167
|
-
phases.push({
|
|
168
|
-
name: phaseName,
|
|
169
|
-
tasks,
|
|
170
|
-
});
|
|
171
|
-
phaseNumber++;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// GRANULARITY CONTROL: Cap total tasks (merge if exceeding)
|
|
175
|
-
const totalTasks = phases.reduce((sum, p) => sum + p.tasks.length, 0);
|
|
176
|
-
if (totalTasks > 50) {
|
|
177
|
-
console.warn(`Warning: Generated ${totalTasks} tasks. Consider merging related tasks or simplifying PRD.`);
|
|
178
|
-
}
|
|
179
|
-
return phases;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Group features by category for logical phase organization
|
|
183
|
-
* Replaces "1 bullet = 1 phase" with intelligent grouping
|
|
184
|
-
*/
|
|
185
|
-
groupFeaturesByCategory(features) {
|
|
186
|
-
const groups = {
|
|
187
|
-
'Configuration & Setup': [],
|
|
188
|
-
'Core Implementation': [],
|
|
189
|
-
'Testing & Validation': [],
|
|
190
|
-
Documentation: [],
|
|
191
|
-
'Integration & Release': [],
|
|
192
|
-
};
|
|
193
|
-
features.forEach((feature) => {
|
|
194
|
-
// Config/setup phase
|
|
195
|
-
if (/\b(config|configuration|setup|install|package|tsconfig|dependencies|environment)\b/i.test(feature)) {
|
|
196
|
-
groups['Configuration & Setup'].push(feature);
|
|
197
|
-
}
|
|
198
|
-
// Testing phase
|
|
199
|
-
else if (/\b(test|testing|coverage|validation|verify|qa)\b/i.test(feature)) {
|
|
200
|
-
groups['Testing & Validation'].push(feature);
|
|
201
|
-
}
|
|
202
|
-
// Documentation phase
|
|
203
|
-
else if (/\b(document|documentation|readme|changelog|guide|comment)\b/i.test(feature)) {
|
|
204
|
-
groups['Documentation'].push(feature);
|
|
205
|
-
}
|
|
206
|
-
// Integration/release phase
|
|
207
|
-
else if (/\b(integrate|integration|release|deploy|publish|build|distribution)\b/i.test(feature)) {
|
|
208
|
-
groups['Integration & Release'].push(feature);
|
|
209
|
-
}
|
|
210
|
-
// Default to core implementation
|
|
211
|
-
else {
|
|
212
|
-
groups['Core Implementation'].push(feature);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
// Remove empty groups
|
|
216
|
-
return Object.fromEntries(Object.entries(groups).filter(([_, features]) => features.length > 0));
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Extract top-level list items only (ignore nested bullets)
|
|
220
|
-
* This prevents sub-bullets from being treated as separate tasks
|
|
221
|
-
*/
|
|
222
|
-
extractListItems(sectionContent) {
|
|
223
|
-
const items = [];
|
|
224
|
-
const lines = sectionContent.split('\n');
|
|
225
|
-
let inCodeBlock = false;
|
|
226
|
-
for (const line of lines) {
|
|
227
|
-
// Track code blocks to ignore bullets inside them
|
|
228
|
-
if (line.trim().startsWith('```')) {
|
|
229
|
-
inCodeBlock = !inCodeBlock;
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (inCodeBlock) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
// Match ONLY top-level bullets (no indentation at all)
|
|
236
|
-
// Nested bullets (2+ spaces) are implementation details, not separate tasks
|
|
237
|
-
const topLevelMatch = line.match(/^(?:[-*]|\d+[.)])\s+(.+)$/);
|
|
238
|
-
if (topLevelMatch) {
|
|
239
|
-
const value = topLevelMatch[1].trim();
|
|
240
|
-
// Skip items that look like code examples, file paths, or implementation details
|
|
241
|
-
if (value &&
|
|
242
|
-
!this.looksLikeCodeOrPath(value) &&
|
|
243
|
-
!this.looksLikeImplementationDetail(value)) {
|
|
244
|
-
items.push(value.replace(/\s+/g, ' ').replace(/\.$/, ''));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return items;
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Detect if a line looks like code or a file path (not a task)
|
|
252
|
-
*/
|
|
253
|
-
looksLikeCodeOrPath(text) {
|
|
254
|
-
// File paths with extensions
|
|
255
|
-
if (/\.(ts|js|json|md|tsx|jsx|mjs|cjs)/.test(text)) {
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
// Code-like patterns
|
|
259
|
-
if (text.includes('import ') || text.includes('export ') || text.includes('require(')) {
|
|
260
|
-
return true;
|
|
261
|
-
}
|
|
262
|
-
// JSON-like
|
|
263
|
-
if (text.trim().startsWith('{') || text.trim().startsWith('[')) {
|
|
264
|
-
return true;
|
|
265
|
-
}
|
|
266
|
-
// Very short technical commands
|
|
267
|
-
if (text.length < 15 && /^[a-z-]+:[a-z-]+$/i.test(text)) {
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Detect if text is an implementation detail rather than a feature
|
|
274
|
-
* Implementation details are constraints, requirements, or sub-steps
|
|
275
|
-
*/
|
|
276
|
-
looksLikeImplementationDetail(text) {
|
|
277
|
-
const lower = text.toLowerCase();
|
|
278
|
-
// Constraints and requirements (not tasks)
|
|
279
|
-
if (lower.includes(' must ') || lower.includes(' should ') || lower.includes(' required')) {
|
|
280
|
-
return true;
|
|
281
|
-
}
|
|
282
|
-
// Very short items (< 25 chars) are likely details, not features
|
|
283
|
-
if (text.length < 25 && !lower.startsWith('implement') && !lower.startsWith('create')) {
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
// Specific technical constraints
|
|
287
|
-
if (/^(password|email|session|token|rate|https)/i.test(text)) {
|
|
288
|
-
return true;
|
|
289
|
-
}
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Build context-aware task descriptions based on feature type and complexity
|
|
294
|
-
* Replaces the old 5-task boilerplate with intelligent task generation
|
|
295
|
-
*/
|
|
296
|
-
buildFeatureTaskDescriptions(feature) {
|
|
297
|
-
const formattedFeature = this.formatInlineText(feature);
|
|
298
|
-
// Detect task type
|
|
299
|
-
const isConfig = /\b(config|configuration|setup|install|update.*json|tsconfig|package\.json)\b/i.test(feature);
|
|
300
|
-
const isTest = /\b(test|testing|coverage|validation|verify)\b/i.test(feature);
|
|
301
|
-
const isDocumentation = /\b(document|documentation|readme|changelog|guide)\b/i.test(feature);
|
|
302
|
-
const isConversion = /\b(convert|migrate|refactor|replace|update.*code)\b/i.test(feature);
|
|
303
|
-
// Simple tasks (config, documentation) - Single task only
|
|
304
|
-
if (isConfig) {
|
|
305
|
-
return [this.convertBehaviorToTask(feature)];
|
|
306
|
-
}
|
|
307
|
-
if (isDocumentation) {
|
|
308
|
-
return [this.convertBehaviorToTask(feature)];
|
|
309
|
-
}
|
|
310
|
-
// Testing tasks - Implementation + validation
|
|
311
|
-
if (isTest) {
|
|
312
|
-
return [
|
|
313
|
-
this.convertBehaviorToTask(feature),
|
|
314
|
-
`Verify ${formattedFeature} passes successfully`,
|
|
315
|
-
];
|
|
316
|
-
}
|
|
317
|
-
// Conversion/migration tasks - Convert + test
|
|
318
|
-
if (isConversion) {
|
|
319
|
-
return [this.convertBehaviorToTask(feature), `Test ${formattedFeature} works correctly`];
|
|
320
|
-
}
|
|
321
|
-
// Default for complex features - Implementation + testing only
|
|
322
|
-
// (No more "integrate into end-to-end" boilerplate)
|
|
323
|
-
return [this.convertBehaviorToTask(feature), `Add tests covering ${formattedFeature}`];
|
|
324
|
-
}
|
|
325
|
-
formatInlineText(text) {
|
|
326
|
-
if (!text) {
|
|
327
|
-
return text;
|
|
328
|
-
}
|
|
329
|
-
const trimmed = text.replace(/\.$/, '').trim();
|
|
330
|
-
if (!trimmed) {
|
|
331
|
-
return trimmed;
|
|
332
|
-
}
|
|
333
|
-
return trimmed.charAt(0).toLowerCase() + trimmed.slice(1);
|
|
334
|
-
}
|
|
335
|
-
toTitleCase(text) {
|
|
336
|
-
const cleaned = text.replace(/\.$/, '').trim();
|
|
337
|
-
if (!cleaned) {
|
|
338
|
-
return 'Feature';
|
|
339
|
-
}
|
|
340
|
-
return cleaned
|
|
341
|
-
.split(' ')
|
|
342
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
343
|
-
.join(' ')
|
|
344
|
-
.substring(0, 60);
|
|
345
|
-
}
|
|
346
|
-
injectTechnicalConstraintsTask(phases, technicalContent, _options) {
|
|
347
|
-
const constraints = this.extractListItems(technicalContent);
|
|
348
|
-
if (constraints.length === 0) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const summary = constraints.slice(0, 3).join('; ');
|
|
352
|
-
const description = `Ensure technical constraints are satisfied: ${summary}`;
|
|
353
|
-
if (phases.length === 0) {
|
|
354
|
-
phases.push({
|
|
355
|
-
name: 'Phase 1: Technical Foundations',
|
|
356
|
-
tasks: [
|
|
357
|
-
{
|
|
358
|
-
id: 'technical-1',
|
|
359
|
-
description,
|
|
360
|
-
phase: 'Phase 1: Technical Foundations',
|
|
361
|
-
completed: false,
|
|
362
|
-
prdReference: 'Technical Constraints',
|
|
363
|
-
},
|
|
364
|
-
],
|
|
365
|
-
});
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
const firstPhase = phases[0];
|
|
369
|
-
firstPhase.tasks.unshift({
|
|
370
|
-
id: `${this.sanitizeId(firstPhase.name)}-constraints`,
|
|
371
|
-
description,
|
|
372
|
-
phase: firstPhase.name,
|
|
373
|
-
completed: false,
|
|
374
|
-
prdReference: 'Technical Constraints',
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
appendSuccessCriteriaPhase(phases, successContent, _options) {
|
|
378
|
-
const criteria = this.extractListItems(successContent);
|
|
379
|
-
if (criteria.length === 0) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
const selected = criteria.slice(0, 2);
|
|
383
|
-
const phaseName = 'Phase QA: Validation & Success';
|
|
384
|
-
const tasks = selected.map((criterion, index) => ({
|
|
385
|
-
id: `${this.sanitizeId(phaseName)}-${index + 1}`,
|
|
386
|
-
description: `Validate success criterion: ${this.formatInlineText(criterion)}`,
|
|
387
|
-
phase: phaseName,
|
|
388
|
-
completed: false,
|
|
389
|
-
prdReference: 'Success Criteria',
|
|
390
|
-
}));
|
|
391
|
-
phases.push({
|
|
392
|
-
name: phaseName,
|
|
393
|
-
tasks,
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Parse PRD into sections
|
|
398
|
-
*/
|
|
399
|
-
parsePrdSections(prdContent) {
|
|
400
|
-
const sections = {};
|
|
401
|
-
const lines = prdContent.split('\n');
|
|
402
|
-
let currentSection = '';
|
|
403
|
-
let currentContent = [];
|
|
404
|
-
for (const line of lines) {
|
|
405
|
-
// Check for section headers (## or ###)
|
|
406
|
-
if (line.match(/^##\s+(.+)/)) {
|
|
407
|
-
// Save previous section
|
|
408
|
-
if (currentSection) {
|
|
409
|
-
sections[this.normalizeSectionName(currentSection)] = currentContent.join('\n').trim();
|
|
410
|
-
}
|
|
411
|
-
// Start new section
|
|
412
|
-
currentSection = line.replace(/^##\s+/, '').trim();
|
|
413
|
-
currentContent = [];
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
currentContent.push(line);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
// Save last section
|
|
420
|
-
if (currentSection) {
|
|
421
|
-
sections[this.normalizeSectionName(currentSection)] = currentContent.join('\n').trim();
|
|
422
|
-
}
|
|
423
|
-
return sections;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Normalize section name for consistent lookup
|
|
427
|
-
*/
|
|
428
|
-
normalizeSectionName(name) {
|
|
429
|
-
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Generate tasks from requirements section
|
|
433
|
-
*/
|
|
434
|
-
generateTasksFromRequirements(requirementsContent, _allSections) {
|
|
435
|
-
const phases = [];
|
|
436
|
-
// Extract must-have features section (stop at next ### or ##)
|
|
437
|
-
const mustHaveMatch = requirementsContent.match(/### Must-Have Features([\s\S]*?)(?=###|##|$)/);
|
|
438
|
-
if (mustHaveMatch) {
|
|
439
|
-
const featuresContent = mustHaveMatch[1];
|
|
440
|
-
// Split by feature headers (#### Number. Feature Name)
|
|
441
|
-
const featureHeaders = [...featuresContent.matchAll(/####\s+(\d+)\.\s+(.+)/g)];
|
|
442
|
-
// Collect all top-level behaviors from all features
|
|
443
|
-
const allBehaviors = [];
|
|
444
|
-
for (let i = 0; i < featureHeaders.length; i++) {
|
|
445
|
-
const featureName = featureHeaders[i][2].trim();
|
|
446
|
-
// Extract content between this header and the next one
|
|
447
|
-
const startIndex = featureHeaders[i].index;
|
|
448
|
-
const endIndex = i < featureHeaders.length - 1 ? featureHeaders[i + 1].index : featuresContent.length;
|
|
449
|
-
const featureContent = featuresContent.substring(startIndex, endIndex);
|
|
450
|
-
// Extract behavior points using hierarchical parsing
|
|
451
|
-
const behaviorMatch = featureContent.match(/\*\*Behavior\*\*:([\s\S]*?)(?=\*\*|####|$)/);
|
|
452
|
-
if (behaviorMatch) {
|
|
453
|
-
const behaviors = behaviorMatch[1];
|
|
454
|
-
const bulletPoints = this.extractListItems(behaviors);
|
|
455
|
-
// Add all top-level behaviors to the list
|
|
456
|
-
allBehaviors.push(...bulletPoints);
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
// If no Behavior section, add the feature name itself
|
|
460
|
-
allBehaviors.push(featureName);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
// Now group all behaviors by category (instead of 1 phase per feature)
|
|
464
|
-
if (allBehaviors.length > 0) {
|
|
465
|
-
const groupedFeatures = this.groupFeaturesByCategory(allBehaviors);
|
|
466
|
-
let phaseNumber = 1;
|
|
467
|
-
for (const [category, features] of Object.entries(groupedFeatures)) {
|
|
468
|
-
const phaseName = `Phase ${phaseNumber}: ${category}`;
|
|
469
|
-
const tasks = [];
|
|
470
|
-
features.forEach((feature) => {
|
|
471
|
-
const taskDescriptions = this.buildFeatureTaskDescriptions(feature);
|
|
472
|
-
taskDescriptions.forEach((description) => {
|
|
473
|
-
tasks.push({
|
|
474
|
-
id: `${this.sanitizeId(phaseName)}-${tasks.length + 1}`,
|
|
475
|
-
description,
|
|
476
|
-
phase: phaseName,
|
|
477
|
-
completed: false,
|
|
478
|
-
prdReference: feature,
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
if (tasks.length > 0) {
|
|
483
|
-
phases.push({
|
|
484
|
-
name: phaseName,
|
|
485
|
-
tasks,
|
|
486
|
-
});
|
|
487
|
-
phaseNumber++;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
// If no structured features found, create a general implementation phase
|
|
493
|
-
if (phases.length === 0) {
|
|
494
|
-
phases.push(this.generateDefaultPhases(requirementsContent));
|
|
495
|
-
}
|
|
496
|
-
return phases;
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Generate a phase from a feature description
|
|
500
|
-
*/
|
|
501
|
-
generatePhaseFromFeature(featureName, featureContent, phasePrefix) {
|
|
502
|
-
const tasks = [];
|
|
503
|
-
// Extract behavior points using hierarchical parsing
|
|
504
|
-
const behaviorMatch = featureContent.match(/\*\*Behavior\*\*:([\s\S]*?)(?=\*\*|####|$)/);
|
|
505
|
-
if (behaviorMatch) {
|
|
506
|
-
const behaviors = behaviorMatch[1];
|
|
507
|
-
// Use the same hierarchical parsing as extractListItems()
|
|
508
|
-
const bulletPoints = this.extractListItems(behaviors);
|
|
509
|
-
bulletPoints.forEach((description, index) => {
|
|
510
|
-
// Skip overly long bullets (likely multi-line descriptions)
|
|
511
|
-
if (description.length < 200) {
|
|
512
|
-
tasks.push({
|
|
513
|
-
id: `${this.sanitizeId(featureName)}-${index + 1}`,
|
|
514
|
-
description: this.convertBehaviorToTask(description),
|
|
515
|
-
phase: phasePrefix,
|
|
516
|
-
completed: false,
|
|
517
|
-
prdReference: featureName,
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
// If no behavior points, try to extract from feature description
|
|
523
|
-
if (tasks.length === 0) {
|
|
524
|
-
tasks.push({
|
|
525
|
-
id: this.sanitizeId(featureName),
|
|
526
|
-
description: `Implement ${featureName.toLowerCase()}`,
|
|
527
|
-
phase: phasePrefix,
|
|
528
|
-
completed: false,
|
|
529
|
-
prdReference: featureName,
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
return {
|
|
533
|
-
name: `${phasePrefix}: ${featureName}`,
|
|
534
|
-
tasks,
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* Convert behavior description to task description
|
|
539
|
-
*/
|
|
540
|
-
convertBehaviorToTask(behavior) {
|
|
541
|
-
// Remove ** bold markers
|
|
542
|
-
let task = behavior.replace(/\*\*/g, '');
|
|
543
|
-
// If starts with action verb, keep as is
|
|
544
|
-
// Otherwise, prepend "Implement"
|
|
545
|
-
const actionVerbs = /^(Create|Add|Implement|Build|Generate|Read|Write|Parse|Analyze|Display|Update|Handle|Process|Execute|Mark|Track|Ensure|Validate|Configure)/i;
|
|
546
|
-
if (!actionVerbs.test(task)) {
|
|
547
|
-
task = `Implement ${task.charAt(0).toLowerCase() + task.slice(1)}`;
|
|
548
|
-
}
|
|
549
|
-
return task;
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Generate default phases when no structure found
|
|
553
|
-
*/
|
|
554
|
-
generateDefaultPhases(_requirementsContent) {
|
|
555
|
-
return {
|
|
556
|
-
name: 'Phase 1: Implementation',
|
|
557
|
-
tasks: [
|
|
558
|
-
{
|
|
559
|
-
id: 'setup-1',
|
|
560
|
-
description: 'Set up project structure and dependencies',
|
|
561
|
-
phase: 'Phase 1',
|
|
562
|
-
completed: false,
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
id: 'implement-1',
|
|
566
|
-
description: 'Implement core functionality as described in requirements',
|
|
567
|
-
phase: 'Phase 1',
|
|
568
|
-
completed: false,
|
|
569
|
-
prdReference: 'Requirements',
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
id: 'test-1',
|
|
573
|
-
description: 'Add tests and validation',
|
|
574
|
-
phase: 'Phase 1',
|
|
575
|
-
completed: false,
|
|
576
|
-
},
|
|
577
|
-
],
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Sanitize ID for use in task IDs
|
|
582
|
-
*/
|
|
583
|
-
sanitizeId(text) {
|
|
584
|
-
return text
|
|
585
|
-
.toLowerCase()
|
|
586
|
-
.replace(/[^a-z0-9]/g, '-')
|
|
587
|
-
.replace(/-+/g, '-')
|
|
588
|
-
.replace(/^-|-$/g, '')
|
|
589
|
-
.substring(0, 30);
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Optimize task description using CLEAR principles
|
|
593
|
-
*/
|
|
594
|
-
optimizeTaskDescription(description) {
|
|
595
|
-
// Ensure starts with action verb first
|
|
596
|
-
const actionVerbs = /^(Create|Add|Implement|Build|Generate|Read|Write|Parse|Analyze|Display|Update|Handle|Process|Execute|Mark|Track|Ensure|Validate|Configure|Set up|Fix|Refactor|Test)/i;
|
|
597
|
-
if (!actionVerbs.test(description)) {
|
|
598
|
-
description = `Implement ${description}`;
|
|
599
|
-
}
|
|
600
|
-
// Ensure conciseness: limit to reasonable length (after adding action verb)
|
|
601
|
-
if (description.length > 150) {
|
|
602
|
-
description = description.substring(0, 147) + '...';
|
|
603
|
-
}
|
|
604
|
-
return description;
|
|
605
|
-
}
|
|
606
|
-
/**
|
|
607
|
-
* Write tasks to tasks.md file
|
|
608
|
-
*/
|
|
609
|
-
async writeTasksFile(outputPath, phases, prdContent) {
|
|
610
|
-
let content = '# Implementation Tasks\n\n';
|
|
611
|
-
// Extract project name from PRD
|
|
612
|
-
const projectMatch = prdContent.match(/^#\s+(.+?)$/m);
|
|
613
|
-
if (projectMatch) {
|
|
614
|
-
content += `**Project**: ${projectMatch[1]}\n\n`;
|
|
615
|
-
}
|
|
616
|
-
content += `**Generated**: ${new Date().toLocaleString()}\n\n`;
|
|
617
|
-
content += '---\n\n';
|
|
618
|
-
// Add phases and tasks
|
|
619
|
-
for (const phase of phases) {
|
|
620
|
-
content += `## ${phase.name}\n\n`;
|
|
621
|
-
for (const task of phase.tasks) {
|
|
622
|
-
const checkbox = task.completed ? '[x]' : '[ ]';
|
|
623
|
-
const reference = task.prdReference ? ` (ref: ${task.prdReference})` : '';
|
|
624
|
-
content += `- ${checkbox} ${task.description}${reference}\n`;
|
|
625
|
-
content += ` Task ID: ${task.id}\n`;
|
|
626
|
-
}
|
|
627
|
-
content += '\n';
|
|
628
|
-
}
|
|
629
|
-
// Add footer
|
|
630
|
-
content += '---\n\n';
|
|
631
|
-
content += '*Generated by Clavix /clavix:plan*\n';
|
|
632
|
-
await FileSystem.writeFileAtomic(outputPath, content);
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Read tasks from tasks.md file
|
|
636
|
-
*/
|
|
637
|
-
async readTasksFile(tasksPath) {
|
|
638
|
-
if (!(await fs.pathExists(tasksPath))) {
|
|
639
|
-
throw new Error(`Tasks file not found: ${tasksPath}`);
|
|
640
|
-
}
|
|
641
|
-
const content = await fs.readFile(tasksPath, 'utf-8');
|
|
642
|
-
return this.parseTasksFile(content);
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Parse tasks.md content into TaskPhase objects
|
|
646
|
-
*/
|
|
647
|
-
parseTasksFile(content) {
|
|
648
|
-
const phases = [];
|
|
649
|
-
const lines = content.split('\n');
|
|
650
|
-
let currentPhase = null;
|
|
651
|
-
let taskCounter = 0;
|
|
652
|
-
for (let i = 0; i < lines.length; i++) {
|
|
653
|
-
const line = lines[i];
|
|
654
|
-
// Check for phase header (## Phase Name)
|
|
655
|
-
const phaseMatch = line.match(/^##\s+(.+)$/);
|
|
656
|
-
if (phaseMatch) {
|
|
657
|
-
if (currentPhase) {
|
|
658
|
-
phases.push(currentPhase);
|
|
659
|
-
}
|
|
660
|
-
currentPhase = {
|
|
661
|
-
name: phaseMatch[1].trim(),
|
|
662
|
-
tasks: [],
|
|
663
|
-
};
|
|
664
|
-
taskCounter = 0;
|
|
665
|
-
continue;
|
|
666
|
-
}
|
|
667
|
-
// Check for task (- [ ] or - [x] Task description)
|
|
668
|
-
const taskMatch = line.match(/^-\s+\[([ x])\]\s+(.+?)(?:\s+\(ref:\s+(.+?)\))?$/);
|
|
669
|
-
if (taskMatch && currentPhase) {
|
|
670
|
-
const completed = taskMatch[1] === 'x';
|
|
671
|
-
const description = taskMatch[2].trim();
|
|
672
|
-
const reference = taskMatch[3]?.trim();
|
|
673
|
-
taskCounter++;
|
|
674
|
-
// Check next line for Task ID
|
|
675
|
-
let taskId;
|
|
676
|
-
const nextLine = i + 1 < lines.length ? lines[i + 1] : null;
|
|
677
|
-
const taskIdMatch = nextLine?.trim().match(/^Task ID:\s+(.+)$/);
|
|
678
|
-
if (taskIdMatch) {
|
|
679
|
-
// Use Task ID from file (preferred)
|
|
680
|
-
taskId = taskIdMatch[1].trim();
|
|
681
|
-
i++; // Skip the Task ID line in next iteration
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
// Fallback: regenerate ID from phase name (backward compatibility)
|
|
685
|
-
taskId = `${this.sanitizeId(currentPhase.name)}-${taskCounter}`;
|
|
686
|
-
}
|
|
687
|
-
currentPhase.tasks.push({
|
|
688
|
-
id: taskId,
|
|
689
|
-
description,
|
|
690
|
-
phase: currentPhase.name,
|
|
691
|
-
completed,
|
|
692
|
-
prdReference: reference,
|
|
693
|
-
});
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
// Add last phase
|
|
697
|
-
if (currentPhase) {
|
|
698
|
-
phases.push(currentPhase);
|
|
699
|
-
}
|
|
700
|
-
return phases;
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Find the first incomplete task
|
|
704
|
-
*/
|
|
705
|
-
findFirstIncompleteTask(phases) {
|
|
706
|
-
for (const phase of phases) {
|
|
707
|
-
for (const task of phase.tasks) {
|
|
708
|
-
if (!task.completed) {
|
|
709
|
-
return task;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Mark a task as completed in the tasks.md file
|
|
717
|
-
*/
|
|
718
|
-
async markTaskCompleted(tasksPath, taskId) {
|
|
719
|
-
const phases = await this.readTasksFile(tasksPath);
|
|
720
|
-
// Find and mark the task
|
|
721
|
-
let found = false;
|
|
722
|
-
for (const phase of phases) {
|
|
723
|
-
for (const task of phase.tasks) {
|
|
724
|
-
if (task.id === taskId) {
|
|
725
|
-
task.completed = true;
|
|
726
|
-
found = true;
|
|
727
|
-
break;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
if (found)
|
|
731
|
-
break;
|
|
732
|
-
}
|
|
733
|
-
if (!found) {
|
|
734
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
735
|
-
}
|
|
736
|
-
// Read original content to preserve formatting
|
|
737
|
-
const content = await fs.readFile(tasksPath, 'utf-8');
|
|
738
|
-
// Find the task line and replace [ ] with [x]
|
|
739
|
-
// We need to find the exact task by description
|
|
740
|
-
const targetTask = phases.flatMap((p) => p.tasks).find((t) => t.id === taskId);
|
|
741
|
-
if (!targetTask) {
|
|
742
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
743
|
-
}
|
|
744
|
-
// Replace - [ ] with - [x] for this specific task description
|
|
745
|
-
const taskDescPattern = this.escapeRegex(targetTask.description);
|
|
746
|
-
const refPattern = targetTask.prdReference
|
|
747
|
-
? `\\s+\\(ref:\\s+${this.escapeRegex(targetTask.prdReference)}\\)`
|
|
748
|
-
: '';
|
|
749
|
-
const regex = new RegExp(`^(-\\s+\\[)( )(\\]\\s+${taskDescPattern}${refPattern})$`, 'm');
|
|
750
|
-
const updatedContent = content.replace(regex, '$1x$3');
|
|
751
|
-
await FileSystem.writeFileAtomic(tasksPath, updatedContent);
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Escape special regex characters
|
|
755
|
-
*/
|
|
756
|
-
escapeRegex(str) {
|
|
757
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Validate that a task exists in the phases
|
|
761
|
-
* @param phases - Array of task phases
|
|
762
|
-
* @param taskId - Task ID to validate
|
|
763
|
-
* @returns The task if found, null otherwise
|
|
764
|
-
*/
|
|
765
|
-
validateTaskExists(phases, taskId) {
|
|
766
|
-
for (const phase of phases) {
|
|
767
|
-
const task = phase.tasks.find((t) => t.id === taskId);
|
|
768
|
-
if (task) {
|
|
769
|
-
return task;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
return null;
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Verify that a task was successfully marked as completed in the file
|
|
776
|
-
* @param tasksPath - Path to tasks.md file
|
|
777
|
-
* @param taskId - Task ID to verify
|
|
778
|
-
* @returns true if task is marked completed, false otherwise
|
|
779
|
-
*/
|
|
780
|
-
async verifyTaskMarked(tasksPath, taskId) {
|
|
781
|
-
try {
|
|
782
|
-
const phases = await this.readTasksFile(tasksPath);
|
|
783
|
-
const task = this.validateTaskExists(phases, taskId);
|
|
784
|
-
return task ? task.completed : false;
|
|
785
|
-
}
|
|
786
|
-
catch {
|
|
787
|
-
// If we can't read the file, verification failed
|
|
788
|
-
return false;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Mark a task as completed with validation and error recovery
|
|
793
|
-
* Enhanced version with pre/post validation
|
|
794
|
-
* @param tasksPath - Path to tasks.md file
|
|
795
|
-
* @param taskId - Task ID to mark as completed
|
|
796
|
-
* @param options - Optional configuration for error recovery
|
|
797
|
-
* @returns Object with success status and any warnings/errors
|
|
798
|
-
*/
|
|
799
|
-
async markTaskCompletedWithValidation(tasksPath, taskId, options = {}) {
|
|
800
|
-
const { retryOnFailure = true, createBackup = true } = options;
|
|
801
|
-
const warnings = [];
|
|
802
|
-
// Pre-validation: Check if file exists
|
|
803
|
-
if (!(await fs.pathExists(tasksPath))) {
|
|
804
|
-
return {
|
|
805
|
-
success: false,
|
|
806
|
-
error: `Tasks file not found: ${tasksPath}`,
|
|
807
|
-
};
|
|
808
|
-
}
|
|
809
|
-
// Create backup if requested
|
|
810
|
-
let backupPath = null;
|
|
811
|
-
if (createBackup) {
|
|
812
|
-
backupPath = `${tasksPath}.backup`;
|
|
813
|
-
try {
|
|
814
|
-
await fs.copyFile(tasksPath, backupPath);
|
|
815
|
-
}
|
|
816
|
-
catch {
|
|
817
|
-
warnings.push('Failed to create backup file');
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
try {
|
|
821
|
-
// Read and validate task exists
|
|
822
|
-
const phases = await this.readTasksFile(tasksPath);
|
|
823
|
-
const task = this.validateTaskExists(phases, taskId);
|
|
824
|
-
if (!task) {
|
|
825
|
-
// Task not found - provide helpful error
|
|
826
|
-
const allTaskIds = phases.flatMap((p) => p.tasks.map((t) => t.id));
|
|
827
|
-
return {
|
|
828
|
-
success: false,
|
|
829
|
-
error: `Task ID "${taskId}" not found. Available task IDs:\n${allTaskIds.join('\n')}`,
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
// Check if already completed
|
|
833
|
-
if (task.completed) {
|
|
834
|
-
return {
|
|
835
|
-
success: true,
|
|
836
|
-
alreadyCompleted: true,
|
|
837
|
-
warnings: [`Task "${taskId}" was already marked as completed`],
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
// Attempt to mark task completed
|
|
841
|
-
await this.markTaskCompleted(tasksPath, taskId);
|
|
842
|
-
// Post-validation: Verify the checkbox was actually changed
|
|
843
|
-
const verified = await this.verifyTaskMarked(tasksPath, taskId);
|
|
844
|
-
if (!verified) {
|
|
845
|
-
// Verification failed - attempt recovery if enabled
|
|
846
|
-
if (retryOnFailure && backupPath) {
|
|
847
|
-
warnings.push('First attempt failed verification, retrying...');
|
|
848
|
-
// Restore from backup and try again
|
|
849
|
-
await fs.copyFile(backupPath, tasksPath);
|
|
850
|
-
await this.markTaskCompleted(tasksPath, taskId);
|
|
851
|
-
// Verify again
|
|
852
|
-
const secondVerification = await this.verifyTaskMarked(tasksPath, taskId);
|
|
853
|
-
if (!secondVerification) {
|
|
854
|
-
// Still failed - restore backup and return error
|
|
855
|
-
await fs.copyFile(backupPath, tasksPath);
|
|
856
|
-
return {
|
|
857
|
-
success: false,
|
|
858
|
-
error: 'Failed to mark task as completed even after retry. File has been restored from backup.',
|
|
859
|
-
warnings,
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
warnings.push('Task marked successfully on retry');
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
// No retry - just fail
|
|
866
|
-
return {
|
|
867
|
-
success: false,
|
|
868
|
-
error: 'Task completion verification failed',
|
|
869
|
-
warnings,
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
// Clean up backup on success
|
|
874
|
-
if (backupPath && (await fs.pathExists(backupPath))) {
|
|
875
|
-
await fs.remove(backupPath);
|
|
876
|
-
}
|
|
877
|
-
return {
|
|
878
|
-
success: true,
|
|
879
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
catch (error) {
|
|
883
|
-
// Restore from backup if available
|
|
884
|
-
if (backupPath && (await fs.pathExists(backupPath))) {
|
|
885
|
-
try {
|
|
886
|
-
await fs.copyFile(backupPath, tasksPath);
|
|
887
|
-
warnings.push('Restored tasks.md from backup due to error');
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
warnings.push('Failed to restore from backup');
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
894
|
-
return {
|
|
895
|
-
success: false,
|
|
896
|
-
error: `Error marking task as completed: ${errorMessage}`,
|
|
897
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
/**
|
|
902
|
-
* Get task completion statistics
|
|
903
|
-
*/
|
|
904
|
-
getTaskStats(phases) {
|
|
905
|
-
const allTasks = phases.flatMap((p) => p.tasks);
|
|
906
|
-
const total = allTasks.length;
|
|
907
|
-
const completed = allTasks.filter((t) => t.completed).length;
|
|
908
|
-
const remaining = total - completed;
|
|
909
|
-
const percentage = total > 0 ? (completed / total) * 100 : 0;
|
|
910
|
-
return { total, completed, remaining, percentage };
|
|
911
|
-
}
|
|
912
|
-
/**
|
|
913
|
-
* Find PRD directory from current working directory
|
|
914
|
-
*/
|
|
915
|
-
async findPrdDirectory(projectName) {
|
|
916
|
-
const baseDir = '.clavix/outputs';
|
|
917
|
-
if (!(await fs.pathExists(baseDir))) {
|
|
918
|
-
throw new Error('No .clavix/outputs directory found. Have you generated a PRD yet?');
|
|
919
|
-
}
|
|
920
|
-
// If project name specified, look for it
|
|
921
|
-
if (projectName) {
|
|
922
|
-
const projectPath = path.join(baseDir, projectName);
|
|
923
|
-
if (await fs.pathExists(projectPath)) {
|
|
924
|
-
return projectPath;
|
|
925
|
-
}
|
|
926
|
-
throw new Error(`PRD project not found: ${projectName}`);
|
|
927
|
-
}
|
|
928
|
-
// Otherwise, find most recent PRD directory
|
|
929
|
-
const dirs = await fs.readdir(baseDir);
|
|
930
|
-
const prdDirs = [];
|
|
931
|
-
for (const dir of dirs) {
|
|
932
|
-
const fullPath = path.join(baseDir, dir);
|
|
933
|
-
const stat = await fs.stat(fullPath);
|
|
934
|
-
if (stat.isDirectory()) {
|
|
935
|
-
// Check if it has a PRD file
|
|
936
|
-
const hasPrd = await this.hasPrdFile(fullPath);
|
|
937
|
-
if (hasPrd) {
|
|
938
|
-
prdDirs.push({
|
|
939
|
-
path: fullPath,
|
|
940
|
-
mtime: stat.mtime,
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
if (prdDirs.length === 0) {
|
|
946
|
-
throw new Error('No PRD directories found in .clavix/outputs');
|
|
947
|
-
}
|
|
948
|
-
// Return most recent
|
|
949
|
-
prdDirs.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
950
|
-
return prdDirs[0].path;
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Check if directory has a PRD file
|
|
954
|
-
*/
|
|
955
|
-
async hasPrdFile(dirPath) {
|
|
956
|
-
for (const filename of ALL_KNOWN_PRD_FILES) {
|
|
957
|
-
if (await fs.pathExists(path.join(dirPath, filename))) {
|
|
958
|
-
return true;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
// Prompt-only projects
|
|
962
|
-
if (await fs.pathExists(path.join(dirPath, 'optimized-prompt.md'))) {
|
|
963
|
-
return true;
|
|
964
|
-
}
|
|
965
|
-
return false;
|
|
966
|
-
}
|
|
967
|
-
async detectAvailableSources(dirPath) {
|
|
968
|
-
const available = [];
|
|
969
|
-
for (const source of SOURCE_ORDER_AUTO) {
|
|
970
|
-
const filenames = SOURCE_FILE_MAP[source];
|
|
971
|
-
for (const filename of filenames) {
|
|
972
|
-
if (await fs.pathExists(path.join(dirPath, filename))) {
|
|
973
|
-
available.push(source);
|
|
974
|
-
break;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
return available;
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
//# sourceMappingURL=task-manager.js.map
|