clavix 2.3.0 → 2.4.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 (180) hide show
  1. package/dist/cli/commands/init.js +1 -1
  2. package/dist/core 2/adapters/agents-md-generator.d.ts +26 -0
  3. package/dist/core 2/adapters/agents-md-generator.js +102 -0
  4. package/dist/core 2/adapters/amp-adapter.d.ts +27 -0
  5. package/dist/core 2/adapters/amp-adapter.js +42 -0
  6. package/dist/core 2/adapters/augment-adapter.d.ts +22 -0
  7. package/dist/core 2/adapters/augment-adapter.js +77 -0
  8. package/dist/core 2/adapters/base-adapter.d.ts +45 -0
  9. package/dist/core 2/adapters/base-adapter.js +142 -0
  10. package/dist/core 2/adapters/claude-code-adapter.d.ts +32 -0
  11. package/dist/core 2/adapters/claude-code-adapter.js +116 -0
  12. package/dist/core 2/adapters/cline-adapter.d.ts +34 -0
  13. package/dist/core 2/adapters/cline-adapter.js +52 -0
  14. package/dist/core 2/adapters/codebuddy-adapter.d.ts +24 -0
  15. package/dist/core 2/adapters/codebuddy-adapter.js +82 -0
  16. package/dist/core 2/adapters/codex-adapter.d.ts +24 -0
  17. package/dist/core 2/adapters/codex-adapter.js +79 -0
  18. package/dist/core 2/adapters/copilot-instructions-generator.d.ts +26 -0
  19. package/dist/core 2/adapters/copilot-instructions-generator.js +104 -0
  20. package/dist/core 2/adapters/crush-adapter.d.ts +35 -0
  21. package/dist/core 2/adapters/crush-adapter.js +49 -0
  22. package/dist/core 2/adapters/cursor-adapter.d.ts +25 -0
  23. package/dist/core 2/adapters/cursor-adapter.js +40 -0
  24. package/dist/core 2/adapters/droid-adapter.d.ts +33 -0
  25. package/dist/core 2/adapters/droid-adapter.js +57 -0
  26. package/dist/core 2/adapters/gemini-adapter.d.ts +27 -0
  27. package/dist/core 2/adapters/gemini-adapter.js +90 -0
  28. package/dist/core 2/adapters/kilocode-adapter.d.ts +34 -0
  29. package/dist/core 2/adapters/kilocode-adapter.js +49 -0
  30. package/dist/core 2/adapters/octo-md-generator.d.ts +26 -0
  31. package/dist/core 2/adapters/octo-md-generator.js +102 -0
  32. package/dist/core 2/adapters/opencode-adapter.d.ts +33 -0
  33. package/dist/core 2/adapters/opencode-adapter.js +56 -0
  34. package/dist/core 2/adapters/qwen-adapter.d.ts +27 -0
  35. package/dist/core 2/adapters/qwen-adapter.js +90 -0
  36. package/dist/core 2/adapters/roocode-adapter.d.ts +40 -0
  37. package/dist/core 2/adapters/roocode-adapter.js +68 -0
  38. package/dist/core 2/adapters/warp-md-generator.d.ts +17 -0
  39. package/dist/core 2/adapters/warp-md-generator.js +88 -0
  40. package/dist/core 2/adapters/windsurf-adapter.d.ts +34 -0
  41. package/dist/core 2/adapters/windsurf-adapter.js +49 -0
  42. package/dist/core 2/agent-manager.d.ts +51 -0
  43. package/dist/core 2/agent-manager.js +126 -0
  44. package/dist/core 2/archive-manager.d.ts +100 -0
  45. package/dist/core 2/archive-manager.js +338 -0
  46. package/dist/core 2/conversation-analyzer.d.ts +86 -0
  47. package/dist/core 2/doc-injector.d.ts +51 -0
  48. package/dist/core 2/doc-injector.js +236 -0
  49. package/dist/core 2/git-manager.d.ts +100 -0
  50. package/dist/core 2/git-manager.js +214 -0
  51. package/dist/core 2/prompt-optimizer.d.ts +268 -0
  52. package/dist/core 2/prompt-optimizer.js +963 -0
  53. package/dist/core 2/question-engine.d.ts +167 -0
  54. package/dist/core 2/question-engine.js +395 -0
  55. package/dist/core 2/session-manager.d.ts +139 -0
  56. package/dist/core 2/session-manager.js +403 -0
  57. package/dist/core 2/task-manager.d.ts +155 -0
  58. package/dist/core 2/task-manager.js +689 -0
  59. package/dist/utils/template-loader.js +24 -22
  60. package/package.json +1 -1
  61. package/dist/templates/slash-commands/augment/archive.md +0 -291
  62. package/dist/templates/slash-commands/augment/deep.md +0 -207
  63. package/dist/templates/slash-commands/augment/fast.md +0 -183
  64. package/dist/templates/slash-commands/augment/implement.md +0 -267
  65. package/dist/templates/slash-commands/augment/plan.md +0 -173
  66. package/dist/templates/slash-commands/augment/prd.md +0 -178
  67. package/dist/templates/slash-commands/augment/start.md +0 -142
  68. package/dist/templates/slash-commands/augment/summarize.md +0 -179
  69. package/dist/templates/slash-commands/claude-code/archive.md +0 -291
  70. package/dist/templates/slash-commands/claude-code/deep.md +0 -207
  71. package/dist/templates/slash-commands/claude-code/fast.md +0 -183
  72. package/dist/templates/slash-commands/claude-code/implement.md +0 -267
  73. package/dist/templates/slash-commands/claude-code/plan.md +0 -173
  74. package/dist/templates/slash-commands/claude-code/prd.md +0 -178
  75. package/dist/templates/slash-commands/claude-code/start.md +0 -142
  76. package/dist/templates/slash-commands/claude-code/summarize.md +0 -179
  77. package/dist/templates/slash-commands/cline/archive.md +0 -291
  78. package/dist/templates/slash-commands/cline/deep.md +0 -207
  79. package/dist/templates/slash-commands/cline/fast.md +0 -183
  80. package/dist/templates/slash-commands/cline/implement.md +0 -267
  81. package/dist/templates/slash-commands/cline/plan.md +0 -173
  82. package/dist/templates/slash-commands/cline/prd.md +0 -178
  83. package/dist/templates/slash-commands/cline/start.md +0 -142
  84. package/dist/templates/slash-commands/cline/summarize.md +0 -179
  85. package/dist/templates/slash-commands/codebuddy/archive.md +0 -291
  86. package/dist/templates/slash-commands/codebuddy/deep.md +0 -207
  87. package/dist/templates/slash-commands/codebuddy/fast.md +0 -183
  88. package/dist/templates/slash-commands/codebuddy/implement.md +0 -267
  89. package/dist/templates/slash-commands/codebuddy/plan.md +0 -173
  90. package/dist/templates/slash-commands/codebuddy/prd.md +0 -178
  91. package/dist/templates/slash-commands/codebuddy/start.md +0 -142
  92. package/dist/templates/slash-commands/codebuddy/summarize.md +0 -179
  93. package/dist/templates/slash-commands/codex/archive.md +0 -291
  94. package/dist/templates/slash-commands/codex/deep.md +0 -207
  95. package/dist/templates/slash-commands/codex/fast.md +0 -183
  96. package/dist/templates/slash-commands/codex/implement.md +0 -267
  97. package/dist/templates/slash-commands/codex/plan.md +0 -173
  98. package/dist/templates/slash-commands/codex/prd.md +0 -178
  99. package/dist/templates/slash-commands/codex/start.md +0 -142
  100. package/dist/templates/slash-commands/codex/summarize.md +0 -179
  101. package/dist/templates/slash-commands/crush/archive.md +0 -291
  102. package/dist/templates/slash-commands/crush/deep.md +0 -207
  103. package/dist/templates/slash-commands/crush/fast.md +0 -183
  104. package/dist/templates/slash-commands/crush/implement.md +0 -267
  105. package/dist/templates/slash-commands/crush/plan.md +0 -173
  106. package/dist/templates/slash-commands/crush/prd.md +0 -178
  107. package/dist/templates/slash-commands/crush/start.md +0 -142
  108. package/dist/templates/slash-commands/crush/summarize.md +0 -179
  109. package/dist/templates/slash-commands/cursor/archive.md +0 -291
  110. package/dist/templates/slash-commands/cursor/deep.md +0 -207
  111. package/dist/templates/slash-commands/cursor/fast.md +0 -183
  112. package/dist/templates/slash-commands/cursor/implement.md +0 -267
  113. package/dist/templates/slash-commands/cursor/plan.md +0 -173
  114. package/dist/templates/slash-commands/cursor/prd.md +0 -178
  115. package/dist/templates/slash-commands/cursor/start.md +0 -142
  116. package/dist/templates/slash-commands/cursor/summarize.md +0 -179
  117. package/dist/templates/slash-commands/droid/archive.md +0 -291
  118. package/dist/templates/slash-commands/droid/deep.md +0 -207
  119. package/dist/templates/slash-commands/droid/fast.md +0 -183
  120. package/dist/templates/slash-commands/droid/implement.md +0 -267
  121. package/dist/templates/slash-commands/droid/plan.md +0 -173
  122. package/dist/templates/slash-commands/droid/prd.md +0 -178
  123. package/dist/templates/slash-commands/droid/start.md +0 -142
  124. package/dist/templates/slash-commands/droid/summarize.md +0 -179
  125. package/dist/templates/slash-commands/gemini/archive.toml +0 -290
  126. package/dist/templates/slash-commands/gemini/deep.toml +0 -206
  127. package/dist/templates/slash-commands/gemini/fast.toml +0 -182
  128. package/dist/templates/slash-commands/gemini/implement.toml +0 -266
  129. package/dist/templates/slash-commands/gemini/plan.toml +0 -170
  130. package/dist/templates/slash-commands/gemini/prd.toml +0 -177
  131. package/dist/templates/slash-commands/gemini/start.toml +0 -141
  132. package/dist/templates/slash-commands/gemini/summarize.toml +0 -178
  133. package/dist/templates/slash-commands/kilocode/archive.md +0 -291
  134. package/dist/templates/slash-commands/kilocode/deep.md +0 -207
  135. package/dist/templates/slash-commands/kilocode/fast.md +0 -183
  136. package/dist/templates/slash-commands/kilocode/implement.md +0 -267
  137. package/dist/templates/slash-commands/kilocode/plan.md +0 -173
  138. package/dist/templates/slash-commands/kilocode/prd.md +0 -178
  139. package/dist/templates/slash-commands/kilocode/start.md +0 -142
  140. package/dist/templates/slash-commands/kilocode/summarize.md +0 -179
  141. package/dist/templates/slash-commands/opencode/archive.md +0 -291
  142. package/dist/templates/slash-commands/opencode/deep.md +0 -207
  143. package/dist/templates/slash-commands/opencode/fast.md +0 -183
  144. package/dist/templates/slash-commands/opencode/implement.md +0 -267
  145. package/dist/templates/slash-commands/opencode/plan.md +0 -173
  146. package/dist/templates/slash-commands/opencode/prd.md +0 -178
  147. package/dist/templates/slash-commands/opencode/start.md +0 -142
  148. package/dist/templates/slash-commands/opencode/summarize.md +0 -179
  149. package/dist/templates/slash-commands/qwen/archive.toml +0 -290
  150. package/dist/templates/slash-commands/qwen/deep.toml +0 -206
  151. package/dist/templates/slash-commands/qwen/fast.toml +0 -182
  152. package/dist/templates/slash-commands/qwen/implement.toml +0 -266
  153. package/dist/templates/slash-commands/qwen/plan.toml +0 -170
  154. package/dist/templates/slash-commands/qwen/prd.toml +0 -177
  155. package/dist/templates/slash-commands/qwen/start.toml +0 -141
  156. package/dist/templates/slash-commands/qwen/summarize.toml +0 -178
  157. package/dist/templates/slash-commands/roocode/archive.md +0 -291
  158. package/dist/templates/slash-commands/roocode/deep.md +0 -207
  159. package/dist/templates/slash-commands/roocode/fast.md +0 -183
  160. package/dist/templates/slash-commands/roocode/implement.md +0 -267
  161. package/dist/templates/slash-commands/roocode/plan.md +0 -173
  162. package/dist/templates/slash-commands/roocode/prd.md +0 -178
  163. package/dist/templates/slash-commands/roocode/start.md +0 -142
  164. package/dist/templates/slash-commands/roocode/summarize.md +0 -179
  165. package/dist/templates/slash-commands/windsurf/archive.md +0 -291
  166. package/dist/templates/slash-commands/windsurf/deep.md +0 -207
  167. package/dist/templates/slash-commands/windsurf/fast.md +0 -183
  168. package/dist/templates/slash-commands/windsurf/implement.md +0 -267
  169. package/dist/templates/slash-commands/windsurf/plan.md +0 -173
  170. package/dist/templates/slash-commands/windsurf/prd.md +0 -178
  171. package/dist/templates/slash-commands/windsurf/start.md +0 -142
  172. package/dist/templates/slash-commands/windsurf/summarize.md +0 -179
  173. /package/dist/templates/slash-commands/{amp → _canonical}/archive.md +0 -0
  174. /package/dist/templates/slash-commands/{amp → _canonical}/deep.md +0 -0
  175. /package/dist/templates/slash-commands/{amp → _canonical}/fast.md +0 -0
  176. /package/dist/templates/slash-commands/{amp → _canonical}/implement.md +0 -0
  177. /package/dist/templates/slash-commands/{amp → _canonical}/plan.md +0 -0
  178. /package/dist/templates/slash-commands/{amp → _canonical}/prd.md +0 -0
  179. /package/dist/templates/slash-commands/{amp → _canonical}/start.md +0 -0
  180. /package/dist/templates/slash-commands/{amp → _canonical}/summarize.md +0 -0
@@ -0,0 +1,689 @@
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