clavix 2.7.0 → 2.8.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.
Files changed (165) hide show
  1. package/README.md +22 -8
  2. package/bin/clavix.js +12 -5
  3. package/dist/cli/commands/archive.d.ts +5 -4
  4. package/dist/cli/commands/archive.js +135 -161
  5. package/dist/cli/commands/config.d.ts +4 -4
  6. package/dist/cli/commands/config.js +66 -105
  7. package/dist/cli/commands/deep.d.ts +3 -3
  8. package/dist/cli/commands/deep.js +97 -103
  9. package/dist/cli/commands/execute.d.ts +4 -4
  10. package/dist/cli/commands/execute.js +57 -63
  11. package/dist/cli/commands/fast.d.ts +3 -3
  12. package/dist/cli/commands/fast.js +122 -128
  13. package/dist/cli/commands/implement.d.ts +4 -4
  14. package/dist/cli/commands/implement.js +84 -148
  15. package/dist/cli/commands/init.js +87 -126
  16. package/dist/cli/commands/list.d.ts +5 -5
  17. package/dist/cli/commands/list.js +72 -111
  18. package/dist/cli/commands/plan.d.ts +7 -7
  19. package/dist/cli/commands/plan.js +92 -131
  20. package/dist/cli/commands/prd.d.ts +4 -4
  21. package/dist/cli/commands/prd.js +76 -111
  22. package/dist/cli/commands/prompts/clear.d.ts +6 -6
  23. package/dist/cli/commands/prompts/clear.js +70 -76
  24. package/dist/cli/commands/prompts/list.js +37 -43
  25. package/dist/cli/commands/show.d.ts +4 -4
  26. package/dist/cli/commands/show.js +72 -111
  27. package/dist/cli/commands/start.d.ts +3 -3
  28. package/dist/cli/commands/start.js +63 -101
  29. package/dist/cli/commands/summarize.d.ts +4 -4
  30. package/dist/cli/commands/summarize.js +81 -120
  31. package/dist/cli/commands/task-complete.d.ts +4 -4
  32. package/dist/cli/commands/task-complete.js +86 -123
  33. package/dist/cli/commands/update.d.ts +3 -3
  34. package/dist/cli/commands/update.js +97 -130
  35. package/dist/cli/commands/version.js +13 -48
  36. package/dist/core/adapters/agents-md-generator.js +17 -50
  37. package/dist/core/adapters/amp-adapter.d.ts +1 -1
  38. package/dist/core/adapters/amp-adapter.js +13 -21
  39. package/dist/core/adapters/augment-adapter.d.ts +2 -2
  40. package/dist/core/adapters/augment-adapter.js +16 -56
  41. package/dist/core/adapters/base-adapter.d.ts +1 -1
  42. package/dist/core/adapters/base-adapter.js +11 -47
  43. package/dist/core/adapters/claude-code-adapter.d.ts +2 -2
  44. package/dist/core/adapters/claude-code-adapter.js +19 -60
  45. package/dist/core/adapters/cline-adapter.d.ts +1 -1
  46. package/dist/core/adapters/cline-adapter.js +13 -21
  47. package/dist/core/adapters/codebuddy-adapter.d.ts +2 -2
  48. package/dist/core/adapters/codebuddy-adapter.js +17 -57
  49. package/dist/core/adapters/codex-adapter.d.ts +2 -2
  50. package/dist/core/adapters/codex-adapter.js +16 -56
  51. package/dist/core/adapters/copilot-instructions-generator.js +18 -51
  52. package/dist/core/adapters/crush-adapter.d.ts +2 -2
  53. package/dist/core/adapters/crush-adapter.js +13 -20
  54. package/dist/core/adapters/cursor-adapter.d.ts +1 -1
  55. package/dist/core/adapters/cursor-adapter.js +12 -20
  56. package/dist/core/adapters/droid-adapter.d.ts +2 -2
  57. package/dist/core/adapters/droid-adapter.js +14 -21
  58. package/dist/core/adapters/gemini-adapter.d.ts +2 -2
  59. package/dist/core/adapters/gemini-adapter.js +16 -52
  60. package/dist/core/adapters/kilocode-adapter.d.ts +1 -1
  61. package/dist/core/adapters/kilocode-adapter.js +12 -20
  62. package/dist/core/adapters/octo-md-generator.js +17 -50
  63. package/dist/core/adapters/opencode-adapter.d.ts +2 -2
  64. package/dist/core/adapters/opencode-adapter.js +14 -21
  65. package/dist/core/adapters/qwen-adapter.d.ts +2 -2
  66. package/dist/core/adapters/qwen-adapter.js +16 -52
  67. package/dist/core/adapters/roocode-adapter.d.ts +2 -2
  68. package/dist/core/adapters/roocode-adapter.js +12 -19
  69. package/dist/core/adapters/warp-md-generator.js +17 -50
  70. package/dist/core/adapters/windsurf-adapter.d.ts +1 -1
  71. package/dist/core/adapters/windsurf-adapter.js +12 -20
  72. package/dist/core/agent-manager.d.ts +1 -1
  73. package/dist/core/agent-manager.js +34 -38
  74. package/dist/core/archive-manager.js +10 -46
  75. package/dist/core/config-manager.d.ts +2 -2
  76. package/dist/core/config-manager.js +3 -40
  77. package/dist/core/conversation-analyzer.d.ts +1 -1
  78. package/dist/core/conversation-analyzer.js +1 -5
  79. package/dist/core/doc-injector.js +23 -60
  80. package/dist/core/git-manager.js +11 -48
  81. package/dist/core/prd-generator.js +16 -51
  82. package/dist/core/prompt-manager.js +6 -42
  83. package/dist/core/prompt-optimizer.js +1 -5
  84. package/dist/core/question-engine.js +6 -45
  85. package/dist/core/session-manager.d.ts +1 -1
  86. package/dist/core/session-manager.js +11 -49
  87. package/dist/core/task-manager.d.ts +26 -0
  88. package/dist/core/task-manager.js +243 -101
  89. package/dist/index.d.ts +2 -1
  90. package/dist/index.js +8 -12
  91. package/dist/templates/agents/agents.md +31 -2
  92. package/dist/templates/agents/copilot-instructions.md +1 -1
  93. package/dist/templates/agents/octo.md +20 -1
  94. package/dist/templates/agents/warp.md +1 -1
  95. package/dist/templates/slash-commands/_canonical/implement.md +33 -11
  96. package/dist/types/agent.js +1 -2
  97. package/dist/types/config.js +3 -8
  98. package/dist/types/errors.js +7 -13
  99. package/dist/types/session.js +1 -2
  100. package/dist/utils/agent-error-messages.js +1 -5
  101. package/dist/utils/error-utils.js +5 -12
  102. package/dist/utils/file-system.js +20 -57
  103. package/dist/utils/legacy-command-cleanup.d.ts +1 -1
  104. package/dist/utils/legacy-command-cleanup.js +9 -45
  105. package/dist/utils/template-loader.d.ts +1 -1
  106. package/dist/utils/template-loader.js +9 -41
  107. package/dist/utils/toml-templates.js +1 -4
  108. package/package.json +12 -7
  109. package/dist/core/adapters 2/agents-md-generator.d.ts +0 -26
  110. package/dist/core/adapters 2/agents-md-generator.js +0 -102
  111. package/dist/core/adapters 2/amp-adapter.d.ts +0 -27
  112. package/dist/core/adapters 2/amp-adapter.js +0 -42
  113. package/dist/core/adapters 2/augment-adapter.d.ts +0 -22
  114. package/dist/core/adapters 2/augment-adapter.js +0 -77
  115. package/dist/core/adapters 2/base-adapter.d.ts +0 -45
  116. package/dist/core/adapters 2/base-adapter.js +0 -142
  117. package/dist/core/adapters 2/claude-code-adapter.d.ts +0 -32
  118. package/dist/core/adapters 2/claude-code-adapter.js +0 -116
  119. package/dist/core/adapters 2/cline-adapter.d.ts +0 -34
  120. package/dist/core/adapters 2/cline-adapter.js +0 -52
  121. package/dist/core/adapters 2/codebuddy-adapter.d.ts +0 -24
  122. package/dist/core/adapters 2/codebuddy-adapter.js +0 -82
  123. package/dist/core/adapters 2/codex-adapter.d.ts +0 -24
  124. package/dist/core/adapters 2/codex-adapter.js +0 -79
  125. package/dist/core/adapters 2/copilot-instructions-generator.d.ts +0 -26
  126. package/dist/core/adapters 2/copilot-instructions-generator.js +0 -104
  127. package/dist/core/adapters 2/crush-adapter.d.ts +0 -35
  128. package/dist/core/adapters 2/crush-adapter.js +0 -49
  129. package/dist/core/adapters 2/cursor-adapter.d.ts +0 -25
  130. package/dist/core/adapters 2/cursor-adapter.js +0 -40
  131. package/dist/core/adapters 2/droid-adapter.d.ts +0 -33
  132. package/dist/core/adapters 2/droid-adapter.js +0 -57
  133. package/dist/core/adapters 2/gemini-adapter.d.ts +0 -27
  134. package/dist/core/adapters 2/gemini-adapter.js +0 -90
  135. package/dist/core/adapters 2/kilocode-adapter.d.ts +0 -34
  136. package/dist/core/adapters 2/kilocode-adapter.js +0 -49
  137. package/dist/core/adapters 2/octo-md-generator.d.ts +0 -26
  138. package/dist/core/adapters 2/octo-md-generator.js +0 -102
  139. package/dist/core/adapters 2/opencode-adapter.d.ts +0 -33
  140. package/dist/core/adapters 2/opencode-adapter.js +0 -56
  141. package/dist/core/adapters 2/qwen-adapter.d.ts +0 -27
  142. package/dist/core/adapters 2/qwen-adapter.js +0 -90
  143. package/dist/core/adapters 2/roocode-adapter.d.ts +0 -40
  144. package/dist/core/adapters 2/roocode-adapter.js +0 -68
  145. package/dist/core/adapters 2/warp-md-generator.d.ts +0 -17
  146. package/dist/core/adapters 2/warp-md-generator.js +0 -88
  147. package/dist/core/adapters 2/windsurf-adapter.d.ts +0 -34
  148. package/dist/core/adapters 2/windsurf-adapter.js +0 -49
  149. package/dist/core/agent-manager 2.js +0 -126
  150. package/dist/core/agent-manager.d 2.ts +0 -51
  151. package/dist/core/archive-manager 2.js +0 -338
  152. package/dist/core/archive-manager.d 2.ts +0 -100
  153. package/dist/core/conversation-analyzer.d 2.ts +0 -86
  154. package/dist/core/doc-injector 2.js +0 -236
  155. package/dist/core/doc-injector.d 2.ts +0 -51
  156. package/dist/core/git-manager 2.js +0 -214
  157. package/dist/core/git-manager.d 2.ts +0 -100
  158. package/dist/core/prompt-optimizer 2.js +0 -963
  159. package/dist/core/prompt-optimizer.d 2.ts +0 -268
  160. package/dist/core/question-engine 2.js +0 -395
  161. package/dist/core/question-engine.d 2.ts +0 -167
  162. package/dist/core/session-manager 2.js +0 -403
  163. package/dist/core/session-manager.d 2.ts +0 -139
  164. package/dist/core/task-manager 2.js +0 -689
  165. package/dist/core/task-manager.d 2.ts +0 -155
@@ -1,689 +0,0 @@
1
- "use strict";
2
- /**
3
- * TaskManager - Manages PRD-based task generation and execution
4
- *
5
- * This class handles:
6
- * - Analyzing PRD documents
7
- * - Generating CLEAR-optimized task breakdowns
8
- * - Reading/writing tasks.md with checkbox format
9
- * - Tracking task completion state
10
- * - Managing session resume capability
11
- */
12
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- var desc = Object.getOwnPropertyDescriptor(m, k);
15
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
- desc = { enumerable: true, get: function() { return m[k]; } };
17
- }
18
- Object.defineProperty(o, k2, desc);
19
- }) : (function(o, m, k, k2) {
20
- if (k2 === undefined) k2 = k;
21
- o[k2] = m[k];
22
- }));
23
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
- Object.defineProperty(o, "default", { enumerable: true, value: v });
25
- }) : function(o, v) {
26
- o["default"] = v;
27
- });
28
- var __importStar = (this && this.__importStar) || (function () {
29
- var ownKeys = function(o) {
30
- ownKeys = Object.getOwnPropertyNames || function (o) {
31
- var ar = [];
32
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
- return ar;
34
- };
35
- return ownKeys(o);
36
- };
37
- return function (mod) {
38
- if (mod && mod.__esModule) return mod;
39
- var result = {};
40
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
- __setModuleDefault(result, mod);
42
- return result;
43
- };
44
- })();
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.TaskManager = void 0;
47
- const fs = __importStar(require("fs-extra"));
48
- const path = __importStar(require("path"));
49
- const prompt_optimizer_1 = require("./prompt-optimizer");
50
- const file_system_1 = require("../utils/file-system");
51
- const SOURCE_FILE_MAP = {
52
- full: ['full-prd.md', 'PRD.md', 'prd.md', 'Full-PRD.md', 'FULL_PRD.md', 'FULL-PRD.md'],
53
- quick: ['quick-prd.md', 'QUICK_PRD.md'],
54
- mini: ['mini-prd.md'],
55
- prompt: ['optimized-prompt.md'],
56
- };
57
- const SOURCE_ORDER_AUTO = ['full', 'quick', 'mini', 'prompt'];
58
- const ALL_KNOWN_PRD_FILES = Array.from(new Set(Object.values(SOURCE_FILE_MAP).flat()));
59
- /**
60
- * TaskManager class
61
- *
62
- * Generates and manages implementation tasks from PRD documents
63
- */
64
- class TaskManager {
65
- constructor() {
66
- this.optimizer = new prompt_optimizer_1.PromptOptimizer();
67
- }
68
- /**
69
- * Generate tasks.md from PRD
70
- *
71
- * @param prdPath - Path to the PRD directory
72
- * @param options - Generation options
73
- * @returns Task generation result
74
- */
75
- async generateTasksFromPrd(prdPath, options = {}) {
76
- // Read the full PRD
77
- const { path: fullPrdPath, sourceType } = await this.resolvePrdFile(prdPath, options.source ?? 'auto');
78
- const prdContent = await fs.readFile(fullPrdPath, 'utf-8');
79
- // Analyze PRD and generate tasks
80
- const phases = await this.analyzePrdAndGenerateTasks(prdContent, options);
81
- // Write tasks.md
82
- const outputPath = path.join(prdPath, 'tasks.md');
83
- await this.writeTasksFile(outputPath, phases, prdContent);
84
- return {
85
- phases,
86
- totalTasks: phases.reduce((sum, phase) => sum + phase.tasks.length, 0),
87
- outputPath,
88
- sourcePath: fullPrdPath,
89
- sourceType,
90
- };
91
- }
92
- /**
93
- * Find the PRD file in a directory
94
- */
95
- async resolvePrdFile(prdPath, preferredSource) {
96
- const order = preferredSource === 'auto' ? SOURCE_ORDER_AUTO : [preferredSource];
97
- for (const source of order) {
98
- const filenames = SOURCE_FILE_MAP[source];
99
- for (const filename of filenames) {
100
- const filepath = path.join(prdPath, filename);
101
- if (await fs.pathExists(filepath)) {
102
- return { path: filepath, sourceType: source };
103
- }
104
- }
105
- }
106
- if (preferredSource !== 'auto') {
107
- throw new Error(`No PRD artifacts found for source "${preferredSource}" in ${prdPath}`);
108
- }
109
- throw new Error(`No PRD artifacts found in ${prdPath}`);
110
- }
111
- /**
112
- * Analyze PRD content and generate task breakdown
113
- */
114
- async analyzePrdAndGenerateTasks(prdContent, options) {
115
- const phases = [];
116
- // Parse PRD sections
117
- const sections = this.parsePrdSections(prdContent);
118
- const coreSection = this.getSectionByAliases(sections, [
119
- 'requirements',
120
- 'corefeatures',
121
- 'features',
122
- 'keyrequirements',
123
- ]);
124
- if (coreSection) {
125
- phases.push(...this.generatePhasesFromCoreFeatures(coreSection, options));
126
- }
127
- if (phases.length === 0 && sections.requirements) {
128
- phases.push(...this.generateTasksFromRequirements(sections.requirements, sections));
129
- }
130
- const technicalSection = this.getSectionByAliases(sections, [
131
- 'technicalrequirements',
132
- 'technicalconstraints',
133
- ]);
134
- if (technicalSection) {
135
- this.injectTechnicalConstraintsTask(phases, technicalSection, options);
136
- }
137
- const successSection = this.getSectionByAliases(sections, [
138
- 'successcriteria',
139
- 'acceptancecriteria',
140
- ]);
141
- if (successSection) {
142
- this.appendSuccessCriteriaPhase(phases, successSection, options);
143
- }
144
- if (phases.length === 0) {
145
- phases.push(this.generateDefaultPhases(prdContent));
146
- }
147
- // Ensure all tasks follow CLEAR principles (applied AFTER all tasks are added)
148
- phases.forEach((phase) => {
149
- phase.tasks = phase.tasks.map((task) => ({
150
- ...task,
151
- description: this.optimizeTaskDescription(task.description),
152
- }));
153
- });
154
- return phases;
155
- }
156
- getSectionByAliases(sections, aliases) {
157
- for (const alias of aliases) {
158
- if (sections[alias]) {
159
- return sections[alias];
160
- }
161
- }
162
- return null;
163
- }
164
- generatePhasesFromCoreFeatures(coreContent, options) {
165
- const bullets = this.extractListItems(coreContent);
166
- if (bullets.length === 0) {
167
- return [];
168
- }
169
- const maxTasks = options.maxTasksPerPhase ?? 20;
170
- const phases = [];
171
- bullets.forEach((rawFeature, index) => {
172
- const feature = rawFeature.trim();
173
- if (!feature) {
174
- return;
175
- }
176
- const phaseName = `Phase ${index + 1}: ${this.toTitleCase(feature)}`;
177
- const baseDescriptions = this.buildFeatureTaskDescriptions(feature);
178
- const allowed = Math.max(1, Math.min(maxTasks, baseDescriptions.length));
179
- const minTasks = maxTasks >= 2 ? 2 : 1;
180
- const taskCount = Math.max(minTasks, allowed);
181
- const limitedDescriptions = baseDescriptions.slice(0, Math.min(taskCount, baseDescriptions.length));
182
- const tasks = limitedDescriptions.map((description, taskIndex) => ({
183
- id: `${this.sanitizeId(phaseName)}-${taskIndex + 1}`,
184
- description,
185
- phase: phaseName,
186
- completed: false,
187
- prdReference: feature,
188
- }));
189
- phases.push({
190
- name: phaseName,
191
- tasks,
192
- });
193
- });
194
- return phases;
195
- }
196
- extractListItems(sectionContent) {
197
- const items = [];
198
- const regex = /^\s*(?:[-*]|\d+[.)])\s+(.+)$/gm;
199
- let match;
200
- while ((match = regex.exec(sectionContent)) !== null) {
201
- const value = match[1].trim();
202
- if (value) {
203
- items.push(value.replace(/\s+/g, ' ').replace(/\.$/, ''));
204
- }
205
- }
206
- return items;
207
- }
208
- buildFeatureTaskDescriptions(feature) {
209
- const formattedFeature = this.formatInlineText(feature);
210
- const tasks = [
211
- this.convertBehaviorToTask(feature),
212
- `Add tests covering ${formattedFeature}`,
213
- `Integrate ${formattedFeature} into the end-to-end experience`,
214
- `Document ${formattedFeature} for stakeholders`,
215
- `Validate ${formattedFeature} against requirements`,
216
- ];
217
- return tasks;
218
- }
219
- formatInlineText(text) {
220
- if (!text) {
221
- return text;
222
- }
223
- const trimmed = text.replace(/\.$/, '').trim();
224
- if (!trimmed) {
225
- return trimmed;
226
- }
227
- return trimmed.charAt(0).toLowerCase() + trimmed.slice(1);
228
- }
229
- toTitleCase(text) {
230
- const cleaned = text.replace(/\.$/, '').trim();
231
- if (!cleaned) {
232
- return 'Feature';
233
- }
234
- return cleaned
235
- .split(' ')
236
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
237
- .join(' ')
238
- .substring(0, 60);
239
- }
240
- injectTechnicalConstraintsTask(phases, technicalContent, _options) {
241
- const constraints = this.extractListItems(technicalContent);
242
- if (constraints.length === 0) {
243
- return;
244
- }
245
- const summary = constraints.slice(0, 3).join('; ');
246
- const description = `Ensure technical constraints are satisfied: ${summary}`;
247
- if (phases.length === 0) {
248
- phases.push({
249
- name: 'Phase 1: Technical Foundations',
250
- tasks: [
251
- {
252
- id: 'technical-1',
253
- description,
254
- phase: 'Phase 1: Technical Foundations',
255
- completed: false,
256
- prdReference: 'Technical Constraints',
257
- },
258
- ],
259
- });
260
- return;
261
- }
262
- const firstPhase = phases[0];
263
- firstPhase.tasks.unshift({
264
- id: `${this.sanitizeId(firstPhase.name)}-constraints`,
265
- description,
266
- phase: firstPhase.name,
267
- completed: false,
268
- prdReference: 'Technical Constraints',
269
- });
270
- }
271
- appendSuccessCriteriaPhase(phases, successContent, _options) {
272
- const criteria = this.extractListItems(successContent);
273
- if (criteria.length === 0) {
274
- return;
275
- }
276
- const selected = criteria.slice(0, 2);
277
- const phaseName = 'Phase QA: Validation & Success';
278
- const tasks = selected.map((criterion, index) => ({
279
- id: `${this.sanitizeId(phaseName)}-${index + 1}`,
280
- description: `Validate success criterion: ${this.formatInlineText(criterion)}`,
281
- phase: phaseName,
282
- completed: false,
283
- prdReference: 'Success Criteria',
284
- }));
285
- phases.push({
286
- name: phaseName,
287
- tasks,
288
- });
289
- }
290
- /**
291
- * Parse PRD into sections
292
- */
293
- parsePrdSections(prdContent) {
294
- const sections = {};
295
- const lines = prdContent.split('\n');
296
- let currentSection = '';
297
- let currentContent = [];
298
- for (const line of lines) {
299
- // Check for section headers (## or ###)
300
- if (line.match(/^##\s+(.+)/)) {
301
- // Save previous section
302
- if (currentSection) {
303
- sections[this.normalizeSectionName(currentSection)] = currentContent.join('\n').trim();
304
- }
305
- // Start new section
306
- currentSection = line.replace(/^##\s+/, '').trim();
307
- currentContent = [];
308
- }
309
- else {
310
- currentContent.push(line);
311
- }
312
- }
313
- // Save last section
314
- if (currentSection) {
315
- sections[this.normalizeSectionName(currentSection)] = currentContent.join('\n').trim();
316
- }
317
- return sections;
318
- }
319
- /**
320
- * Normalize section name for consistent lookup
321
- */
322
- normalizeSectionName(name) {
323
- return name.toLowerCase().replace(/[^a-z0-9]/g, '');
324
- }
325
- /**
326
- * Generate tasks from requirements section
327
- */
328
- generateTasksFromRequirements(requirementsContent, _allSections) {
329
- const phases = [];
330
- // Extract must-have features section (stop at next ### or ##)
331
- const mustHaveMatch = requirementsContent.match(/### Must-Have Features([\s\S]*?)(?=###|##|$)/);
332
- if (mustHaveMatch) {
333
- const featuresContent = mustHaveMatch[1];
334
- // Split by feature headers (#### Number. Feature Name)
335
- const featureHeaders = [...featuresContent.matchAll(/####\s+(\d+)\.\s+(.+)/g)];
336
- for (let i = 0; i < featureHeaders.length; i++) {
337
- const featureNumber = featureHeaders[i][1];
338
- const featureName = featureHeaders[i][2].trim();
339
- // Extract content between this header and the next one
340
- const startIndex = featureHeaders[i].index;
341
- const endIndex = i < featureHeaders.length - 1
342
- ? featureHeaders[i + 1].index
343
- : featuresContent.length;
344
- const featureContent = featuresContent.substring(startIndex, endIndex);
345
- // Generate phase for this feature
346
- const phase = this.generatePhaseFromFeature(featureName, featureContent, `Phase ${featureNumber}`);
347
- if (phase && phase.tasks.length > 0) {
348
- phases.push(phase);
349
- }
350
- }
351
- }
352
- // If no structured features found, create a general implementation phase
353
- if (phases.length === 0) {
354
- phases.push(this.generateDefaultPhases(requirementsContent));
355
- }
356
- return phases;
357
- }
358
- /**
359
- * Generate a phase from a feature description
360
- */
361
- generatePhaseFromFeature(featureName, featureContent, phasePrefix) {
362
- const tasks = [];
363
- // Extract behavior points
364
- const behaviorMatch = featureContent.match(/\*\*Behavior\*\*:([\s\S]*?)(?=\*\*|####|$)/);
365
- if (behaviorMatch) {
366
- const behaviors = behaviorMatch[1];
367
- const bulletPoints = behaviors.match(/^[-*]\s+(.+)$/gm);
368
- if (bulletPoints) {
369
- bulletPoints.forEach((bullet, index) => {
370
- const description = bullet.replace(/^[-*]\s+/, '').trim();
371
- // Skip overly long bullets (likely multi-line descriptions)
372
- if (description.length < 200) {
373
- tasks.push({
374
- id: `${this.sanitizeId(featureName)}-${index + 1}`,
375
- description: this.convertBehaviorToTask(description),
376
- phase: phasePrefix,
377
- completed: false,
378
- prdReference: featureName,
379
- });
380
- }
381
- });
382
- }
383
- }
384
- // If no behavior points, try to extract from feature description
385
- if (tasks.length === 0) {
386
- tasks.push({
387
- id: this.sanitizeId(featureName),
388
- description: `Implement ${featureName.toLowerCase()}`,
389
- phase: phasePrefix,
390
- completed: false,
391
- prdReference: featureName,
392
- });
393
- }
394
- return {
395
- name: `${phasePrefix}: ${featureName}`,
396
- tasks,
397
- };
398
- }
399
- /**
400
- * Convert behavior description to task description
401
- */
402
- convertBehaviorToTask(behavior) {
403
- // Remove ** bold markers
404
- let task = behavior.replace(/\*\*/g, '');
405
- // If starts with action verb, keep as is
406
- // Otherwise, prepend "Implement"
407
- const actionVerbs = /^(Create|Add|Implement|Build|Generate|Read|Write|Parse|Analyze|Display|Update|Handle|Process|Execute|Mark|Track|Ensure|Validate|Configure)/i;
408
- if (!actionVerbs.test(task)) {
409
- task = `Implement ${task.charAt(0).toLowerCase() + task.slice(1)}`;
410
- }
411
- return task;
412
- }
413
- /**
414
- * Generate default phases when no structure found
415
- */
416
- generateDefaultPhases(_requirementsContent) {
417
- return {
418
- name: 'Phase 1: Implementation',
419
- tasks: [
420
- {
421
- id: 'setup-1',
422
- description: 'Set up project structure and dependencies',
423
- phase: 'Phase 1',
424
- completed: false,
425
- },
426
- {
427
- id: 'implement-1',
428
- description: 'Implement core functionality as described in requirements',
429
- phase: 'Phase 1',
430
- completed: false,
431
- prdReference: 'Requirements',
432
- },
433
- {
434
- id: 'test-1',
435
- description: 'Add tests and validation',
436
- phase: 'Phase 1',
437
- completed: false,
438
- },
439
- ],
440
- };
441
- }
442
- /**
443
- * Sanitize ID for use in task IDs
444
- */
445
- sanitizeId(text) {
446
- return text
447
- .toLowerCase()
448
- .replace(/[^a-z0-9]/g, '-')
449
- .replace(/-+/g, '-')
450
- .replace(/^-|-$/g, '')
451
- .substring(0, 30);
452
- }
453
- /**
454
- * Optimize task description using CLEAR principles
455
- */
456
- optimizeTaskDescription(description) {
457
- // Ensure starts with action verb first
458
- 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;
459
- if (!actionVerbs.test(description)) {
460
- description = `Implement ${description}`;
461
- }
462
- // Ensure conciseness: limit to reasonable length (after adding action verb)
463
- if (description.length > 150) {
464
- description = description.substring(0, 147) + '...';
465
- }
466
- return description;
467
- }
468
- /**
469
- * Write tasks to tasks.md file
470
- */
471
- async writeTasksFile(outputPath, phases, prdContent) {
472
- let content = '# Implementation Tasks\n\n';
473
- // Extract project name from PRD
474
- const projectMatch = prdContent.match(/^#\s+(.+?)$/m);
475
- if (projectMatch) {
476
- content += `**Project**: ${projectMatch[1]}\n\n`;
477
- }
478
- content += `**Generated**: ${new Date().toLocaleString()}\n\n`;
479
- content += '---\n\n';
480
- // Add phases and tasks
481
- for (const phase of phases) {
482
- content += `## ${phase.name}\n\n`;
483
- for (const task of phase.tasks) {
484
- const checkbox = task.completed ? '[x]' : '[ ]';
485
- const reference = task.prdReference ? ` (ref: ${task.prdReference})` : '';
486
- content += `- ${checkbox} ${task.description}${reference}\n`;
487
- }
488
- content += '\n';
489
- }
490
- // Add footer
491
- content += '---\n\n';
492
- content += '*Generated by Clavix /clavix:plan*\n';
493
- await file_system_1.FileSystem.writeFileAtomic(outputPath, content);
494
- }
495
- /**
496
- * Read tasks from tasks.md file
497
- */
498
- async readTasksFile(tasksPath) {
499
- if (!(await fs.pathExists(tasksPath))) {
500
- throw new Error(`Tasks file not found: ${tasksPath}`);
501
- }
502
- const content = await fs.readFile(tasksPath, 'utf-8');
503
- return this.parseTasksFile(content);
504
- }
505
- /**
506
- * Parse tasks.md content into TaskPhase objects
507
- */
508
- parseTasksFile(content) {
509
- const phases = [];
510
- const lines = content.split('\n');
511
- let currentPhase = null;
512
- let taskCounter = 0;
513
- for (const line of lines) {
514
- // Check for phase header (## Phase Name)
515
- const phaseMatch = line.match(/^##\s+(.+)$/);
516
- if (phaseMatch) {
517
- if (currentPhase) {
518
- phases.push(currentPhase);
519
- }
520
- currentPhase = {
521
- name: phaseMatch[1].trim(),
522
- tasks: [],
523
- };
524
- taskCounter = 0;
525
- continue;
526
- }
527
- // Check for task (- [ ] or - [x] Task description)
528
- const taskMatch = line.match(/^-\s+\[([ x])\]\s+(.+?)(?:\s+\(ref:\s+(.+?)\))?$/);
529
- if (taskMatch && currentPhase) {
530
- const completed = taskMatch[1] === 'x';
531
- const description = taskMatch[2].trim();
532
- const reference = taskMatch[3]?.trim();
533
- taskCounter++;
534
- currentPhase.tasks.push({
535
- id: `${this.sanitizeId(currentPhase.name)}-${taskCounter}`,
536
- description,
537
- phase: currentPhase.name,
538
- completed,
539
- prdReference: reference,
540
- });
541
- }
542
- }
543
- // Add last phase
544
- if (currentPhase) {
545
- phases.push(currentPhase);
546
- }
547
- return phases;
548
- }
549
- /**
550
- * Find the first incomplete task
551
- */
552
- findFirstIncompleteTask(phases) {
553
- for (const phase of phases) {
554
- for (const task of phase.tasks) {
555
- if (!task.completed) {
556
- return task;
557
- }
558
- }
559
- }
560
- return null;
561
- }
562
- /**
563
- * Mark a task as completed in the tasks.md file
564
- */
565
- async markTaskCompleted(tasksPath, taskId) {
566
- const phases = await this.readTasksFile(tasksPath);
567
- // Find and mark the task
568
- let found = false;
569
- for (const phase of phases) {
570
- for (const task of phase.tasks) {
571
- if (task.id === taskId) {
572
- task.completed = true;
573
- found = true;
574
- break;
575
- }
576
- }
577
- if (found)
578
- break;
579
- }
580
- if (!found) {
581
- throw new Error(`Task not found: ${taskId}`);
582
- }
583
- // Read original content to preserve formatting
584
- const content = await fs.readFile(tasksPath, 'utf-8');
585
- // Find the task line and replace [ ] with [x]
586
- // We need to find the exact task by description
587
- const targetTask = phases
588
- .flatMap((p) => p.tasks)
589
- .find((t) => t.id === taskId);
590
- if (!targetTask) {
591
- throw new Error(`Task not found: ${taskId}`);
592
- }
593
- // Replace - [ ] with - [x] for this specific task description
594
- const taskDescPattern = this.escapeRegex(targetTask.description);
595
- const refPattern = targetTask.prdReference
596
- ? `\\s+\\(ref:\\s+${this.escapeRegex(targetTask.prdReference)}\\)`
597
- : '';
598
- const regex = new RegExp(`^(-\\s+\\[)( )(\\]\\s+${taskDescPattern}${refPattern})$`, 'm');
599
- const updatedContent = content.replace(regex, '$1x$3');
600
- await file_system_1.FileSystem.writeFileAtomic(tasksPath, updatedContent);
601
- }
602
- /**
603
- * Escape special regex characters
604
- */
605
- escapeRegex(str) {
606
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
607
- }
608
- /**
609
- * Get task completion statistics
610
- */
611
- getTaskStats(phases) {
612
- const allTasks = phases.flatMap((p) => p.tasks);
613
- const total = allTasks.length;
614
- const completed = allTasks.filter((t) => t.completed).length;
615
- const remaining = total - completed;
616
- const percentage = total > 0 ? (completed / total) * 100 : 0;
617
- return { total, completed, remaining, percentage };
618
- }
619
- /**
620
- * Find PRD directory from current working directory
621
- */
622
- async findPrdDirectory(projectName) {
623
- const baseDir = '.clavix/outputs';
624
- if (!await fs.pathExists(baseDir)) {
625
- throw new Error('No .clavix/outputs directory found. Have you generated a PRD yet?');
626
- }
627
- // If project name specified, look for it
628
- if (projectName) {
629
- const projectPath = path.join(baseDir, projectName);
630
- if (await fs.pathExists(projectPath)) {
631
- return projectPath;
632
- }
633
- throw new Error(`PRD project not found: ${projectName}`);
634
- }
635
- // Otherwise, find most recent PRD directory
636
- const dirs = await fs.readdir(baseDir);
637
- const prdDirs = [];
638
- for (const dir of dirs) {
639
- const fullPath = path.join(baseDir, dir);
640
- const stat = await fs.stat(fullPath);
641
- if (stat.isDirectory()) {
642
- // Check if it has a PRD file
643
- const hasPrd = await this.hasPrdFile(fullPath);
644
- if (hasPrd) {
645
- prdDirs.push({
646
- path: fullPath,
647
- mtime: stat.mtime,
648
- });
649
- }
650
- }
651
- }
652
- if (prdDirs.length === 0) {
653
- throw new Error('No PRD directories found in .clavix/outputs');
654
- }
655
- // Return most recent
656
- prdDirs.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
657
- return prdDirs[0].path;
658
- }
659
- /**
660
- * Check if directory has a PRD file
661
- */
662
- async hasPrdFile(dirPath) {
663
- for (const filename of ALL_KNOWN_PRD_FILES) {
664
- if (await fs.pathExists(path.join(dirPath, filename))) {
665
- return true;
666
- }
667
- }
668
- // Prompt-only projects
669
- if (await fs.pathExists(path.join(dirPath, 'optimized-prompt.md'))) {
670
- return true;
671
- }
672
- return false;
673
- }
674
- async detectAvailableSources(dirPath) {
675
- const available = [];
676
- for (const source of SOURCE_ORDER_AUTO) {
677
- const filenames = SOURCE_FILE_MAP[source];
678
- for (const filename of filenames) {
679
- if (await fs.pathExists(path.join(dirPath, filename))) {
680
- available.push(source);
681
- break;
682
- }
683
- }
684
- }
685
- return available;
686
- }
687
- }
688
- exports.TaskManager = TaskManager;
689
- //# sourceMappingURL=task-manager.js.map