clavix 2.3.1 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +0 -116
  2. package/bin/clavix.js +7 -0
  3. package/dist/cli/commands/implement.js +25 -33
  4. package/dist/cli/commands/plan.js +22 -27
  5. package/dist/cli/commands/prd.js +7 -12
  6. package/dist/cli/commands/start.js +4 -9
  7. package/dist/cli/commands/summarize.js +15 -22
  8. package/dist/core 2/adapters/agents-md-generator.d.ts +26 -0
  9. package/dist/core 2/adapters/agents-md-generator.js +102 -0
  10. package/dist/core 2/adapters/amp-adapter.d.ts +27 -0
  11. package/dist/core 2/adapters/amp-adapter.js +42 -0
  12. package/dist/core 2/adapters/augment-adapter.d.ts +22 -0
  13. package/dist/core 2/adapters/augment-adapter.js +77 -0
  14. package/dist/core 2/adapters/base-adapter.d.ts +45 -0
  15. package/dist/core 2/adapters/base-adapter.js +142 -0
  16. package/dist/core 2/adapters/claude-code-adapter.d.ts +32 -0
  17. package/dist/core 2/adapters/claude-code-adapter.js +116 -0
  18. package/dist/core 2/adapters/cline-adapter.d.ts +34 -0
  19. package/dist/core 2/adapters/cline-adapter.js +52 -0
  20. package/dist/core 2/adapters/codebuddy-adapter.d.ts +24 -0
  21. package/dist/core 2/adapters/codebuddy-adapter.js +82 -0
  22. package/dist/core 2/adapters/codex-adapter.d.ts +24 -0
  23. package/dist/core 2/adapters/codex-adapter.js +79 -0
  24. package/dist/core 2/adapters/copilot-instructions-generator.d.ts +26 -0
  25. package/dist/core 2/adapters/copilot-instructions-generator.js +104 -0
  26. package/dist/core 2/adapters/crush-adapter.d.ts +35 -0
  27. package/dist/core 2/adapters/crush-adapter.js +49 -0
  28. package/dist/core 2/adapters/cursor-adapter.d.ts +25 -0
  29. package/dist/core 2/adapters/cursor-adapter.js +40 -0
  30. package/dist/core 2/adapters/droid-adapter.d.ts +33 -0
  31. package/dist/core 2/adapters/droid-adapter.js +57 -0
  32. package/dist/core 2/adapters/gemini-adapter.d.ts +27 -0
  33. package/dist/core 2/adapters/gemini-adapter.js +90 -0
  34. package/dist/core 2/adapters/kilocode-adapter.d.ts +34 -0
  35. package/dist/core 2/adapters/kilocode-adapter.js +49 -0
  36. package/dist/core 2/adapters/octo-md-generator.d.ts +26 -0
  37. package/dist/core 2/adapters/octo-md-generator.js +102 -0
  38. package/dist/core 2/adapters/opencode-adapter.d.ts +33 -0
  39. package/dist/core 2/adapters/opencode-adapter.js +56 -0
  40. package/dist/core 2/adapters/qwen-adapter.d.ts +27 -0
  41. package/dist/core 2/adapters/qwen-adapter.js +90 -0
  42. package/dist/core 2/adapters/roocode-adapter.d.ts +40 -0
  43. package/dist/core 2/adapters/roocode-adapter.js +68 -0
  44. package/dist/core 2/adapters/warp-md-generator.d.ts +17 -0
  45. package/dist/core 2/adapters/warp-md-generator.js +88 -0
  46. package/dist/core 2/adapters/windsurf-adapter.d.ts +34 -0
  47. package/dist/core 2/adapters/windsurf-adapter.js +49 -0
  48. package/dist/core 2/agent-manager.d.ts +51 -0
  49. package/dist/core 2/agent-manager.js +126 -0
  50. package/dist/core 2/archive-manager.d.ts +100 -0
  51. package/dist/core 2/archive-manager.js +338 -0
  52. package/dist/core 2/conversation-analyzer.d.ts +86 -0
  53. package/dist/core 2/doc-injector.d.ts +51 -0
  54. package/dist/core 2/doc-injector.js +236 -0
  55. package/dist/core 2/git-manager.d.ts +100 -0
  56. package/dist/core 2/git-manager.js +214 -0
  57. package/dist/core 2/prompt-optimizer.d.ts +268 -0
  58. package/dist/core 2/prompt-optimizer.js +963 -0
  59. package/dist/core 2/question-engine.d.ts +167 -0
  60. package/dist/core 2/question-engine.js +395 -0
  61. package/dist/core 2/session-manager.d.ts +139 -0
  62. package/dist/core 2/session-manager.js +403 -0
  63. package/dist/core 2/task-manager.d.ts +155 -0
  64. package/dist/core 2/task-manager.js +689 -0
  65. package/dist/index.js +6 -0
  66. package/dist/utils/template-loader.js +24 -22
  67. package/package.json +1 -1
  68. package/dist/templates/slash-commands/augment/archive.md +0 -291
  69. package/dist/templates/slash-commands/augment/deep.md +0 -207
  70. package/dist/templates/slash-commands/augment/fast.md +0 -183
  71. package/dist/templates/slash-commands/augment/implement.md +0 -267
  72. package/dist/templates/slash-commands/augment/plan.md +0 -173
  73. package/dist/templates/slash-commands/augment/prd.md +0 -178
  74. package/dist/templates/slash-commands/augment/start.md +0 -142
  75. package/dist/templates/slash-commands/augment/summarize.md +0 -179
  76. package/dist/templates/slash-commands/claude-code/archive.md +0 -291
  77. package/dist/templates/slash-commands/claude-code/deep.md +0 -207
  78. package/dist/templates/slash-commands/claude-code/fast.md +0 -183
  79. package/dist/templates/slash-commands/claude-code/implement.md +0 -267
  80. package/dist/templates/slash-commands/claude-code/plan.md +0 -173
  81. package/dist/templates/slash-commands/claude-code/prd.md +0 -178
  82. package/dist/templates/slash-commands/claude-code/start.md +0 -142
  83. package/dist/templates/slash-commands/claude-code/summarize.md +0 -179
  84. package/dist/templates/slash-commands/cline/archive.md +0 -291
  85. package/dist/templates/slash-commands/cline/deep.md +0 -207
  86. package/dist/templates/slash-commands/cline/fast.md +0 -183
  87. package/dist/templates/slash-commands/cline/implement.md +0 -267
  88. package/dist/templates/slash-commands/cline/plan.md +0 -173
  89. package/dist/templates/slash-commands/cline/prd.md +0 -178
  90. package/dist/templates/slash-commands/cline/start.md +0 -142
  91. package/dist/templates/slash-commands/cline/summarize.md +0 -179
  92. package/dist/templates/slash-commands/codebuddy/archive.md +0 -291
  93. package/dist/templates/slash-commands/codebuddy/deep.md +0 -207
  94. package/dist/templates/slash-commands/codebuddy/fast.md +0 -183
  95. package/dist/templates/slash-commands/codebuddy/implement.md +0 -267
  96. package/dist/templates/slash-commands/codebuddy/plan.md +0 -173
  97. package/dist/templates/slash-commands/codebuddy/prd.md +0 -178
  98. package/dist/templates/slash-commands/codebuddy/start.md +0 -142
  99. package/dist/templates/slash-commands/codebuddy/summarize.md +0 -179
  100. package/dist/templates/slash-commands/codex/archive.md +0 -291
  101. package/dist/templates/slash-commands/codex/deep.md +0 -207
  102. package/dist/templates/slash-commands/codex/fast.md +0 -183
  103. package/dist/templates/slash-commands/codex/implement.md +0 -267
  104. package/dist/templates/slash-commands/codex/plan.md +0 -173
  105. package/dist/templates/slash-commands/codex/prd.md +0 -178
  106. package/dist/templates/slash-commands/codex/start.md +0 -142
  107. package/dist/templates/slash-commands/codex/summarize.md +0 -179
  108. package/dist/templates/slash-commands/crush/archive.md +0 -291
  109. package/dist/templates/slash-commands/crush/deep.md +0 -207
  110. package/dist/templates/slash-commands/crush/fast.md +0 -183
  111. package/dist/templates/slash-commands/crush/implement.md +0 -267
  112. package/dist/templates/slash-commands/crush/plan.md +0 -173
  113. package/dist/templates/slash-commands/crush/prd.md +0 -178
  114. package/dist/templates/slash-commands/crush/start.md +0 -142
  115. package/dist/templates/slash-commands/crush/summarize.md +0 -179
  116. package/dist/templates/slash-commands/cursor/archive.md +0 -291
  117. package/dist/templates/slash-commands/cursor/deep.md +0 -207
  118. package/dist/templates/slash-commands/cursor/fast.md +0 -183
  119. package/dist/templates/slash-commands/cursor/implement.md +0 -267
  120. package/dist/templates/slash-commands/cursor/plan.md +0 -173
  121. package/dist/templates/slash-commands/cursor/prd.md +0 -178
  122. package/dist/templates/slash-commands/cursor/start.md +0 -142
  123. package/dist/templates/slash-commands/cursor/summarize.md +0 -179
  124. package/dist/templates/slash-commands/droid/archive.md +0 -291
  125. package/dist/templates/slash-commands/droid/deep.md +0 -207
  126. package/dist/templates/slash-commands/droid/fast.md +0 -183
  127. package/dist/templates/slash-commands/droid/implement.md +0 -267
  128. package/dist/templates/slash-commands/droid/plan.md +0 -173
  129. package/dist/templates/slash-commands/droid/prd.md +0 -178
  130. package/dist/templates/slash-commands/droid/start.md +0 -142
  131. package/dist/templates/slash-commands/droid/summarize.md +0 -179
  132. package/dist/templates/slash-commands/gemini/archive.toml +0 -290
  133. package/dist/templates/slash-commands/gemini/deep.toml +0 -206
  134. package/dist/templates/slash-commands/gemini/fast.toml +0 -182
  135. package/dist/templates/slash-commands/gemini/implement.toml +0 -266
  136. package/dist/templates/slash-commands/gemini/plan.toml +0 -170
  137. package/dist/templates/slash-commands/gemini/prd.toml +0 -177
  138. package/dist/templates/slash-commands/gemini/start.toml +0 -141
  139. package/dist/templates/slash-commands/gemini/summarize.toml +0 -178
  140. package/dist/templates/slash-commands/kilocode/archive.md +0 -291
  141. package/dist/templates/slash-commands/kilocode/deep.md +0 -207
  142. package/dist/templates/slash-commands/kilocode/fast.md +0 -183
  143. package/dist/templates/slash-commands/kilocode/implement.md +0 -267
  144. package/dist/templates/slash-commands/kilocode/plan.md +0 -173
  145. package/dist/templates/slash-commands/kilocode/prd.md +0 -178
  146. package/dist/templates/slash-commands/kilocode/start.md +0 -142
  147. package/dist/templates/slash-commands/kilocode/summarize.md +0 -179
  148. package/dist/templates/slash-commands/opencode/archive.md +0 -291
  149. package/dist/templates/slash-commands/opencode/deep.md +0 -207
  150. package/dist/templates/slash-commands/opencode/fast.md +0 -183
  151. package/dist/templates/slash-commands/opencode/implement.md +0 -267
  152. package/dist/templates/slash-commands/opencode/plan.md +0 -173
  153. package/dist/templates/slash-commands/opencode/prd.md +0 -178
  154. package/dist/templates/slash-commands/opencode/start.md +0 -142
  155. package/dist/templates/slash-commands/opencode/summarize.md +0 -179
  156. package/dist/templates/slash-commands/qwen/archive.toml +0 -290
  157. package/dist/templates/slash-commands/qwen/deep.toml +0 -206
  158. package/dist/templates/slash-commands/qwen/fast.toml +0 -182
  159. package/dist/templates/slash-commands/qwen/implement.toml +0 -266
  160. package/dist/templates/slash-commands/qwen/plan.toml +0 -170
  161. package/dist/templates/slash-commands/qwen/prd.toml +0 -177
  162. package/dist/templates/slash-commands/qwen/start.toml +0 -141
  163. package/dist/templates/slash-commands/qwen/summarize.toml +0 -178
  164. package/dist/templates/slash-commands/roocode/archive.md +0 -291
  165. package/dist/templates/slash-commands/roocode/deep.md +0 -207
  166. package/dist/templates/slash-commands/roocode/fast.md +0 -183
  167. package/dist/templates/slash-commands/roocode/implement.md +0 -267
  168. package/dist/templates/slash-commands/roocode/plan.md +0 -173
  169. package/dist/templates/slash-commands/roocode/prd.md +0 -178
  170. package/dist/templates/slash-commands/roocode/start.md +0 -142
  171. package/dist/templates/slash-commands/roocode/summarize.md +0 -179
  172. package/dist/templates/slash-commands/windsurf/archive.md +0 -291
  173. package/dist/templates/slash-commands/windsurf/deep.md +0 -207
  174. package/dist/templates/slash-commands/windsurf/fast.md +0 -183
  175. package/dist/templates/slash-commands/windsurf/implement.md +0 -267
  176. package/dist/templates/slash-commands/windsurf/plan.md +0 -173
  177. package/dist/templates/slash-commands/windsurf/prd.md +0 -178
  178. package/dist/templates/slash-commands/windsurf/start.md +0 -142
  179. package/dist/templates/slash-commands/windsurf/summarize.md +0 -179
  180. /package/dist/templates/slash-commands/{amp → _canonical}/archive.md +0 -0
  181. /package/dist/templates/slash-commands/{amp → _canonical}/deep.md +0 -0
  182. /package/dist/templates/slash-commands/{amp → _canonical}/fast.md +0 -0
  183. /package/dist/templates/slash-commands/{amp → _canonical}/implement.md +0 -0
  184. /package/dist/templates/slash-commands/{amp → _canonical}/plan.md +0 -0
  185. /package/dist/templates/slash-commands/{amp → _canonical}/prd.md +0 -0
  186. /package/dist/templates/slash-commands/{amp → _canonical}/start.md +0 -0
  187. /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
package/dist/index.js CHANGED
@@ -9,6 +9,12 @@ async function run(argv) {
9
9
  // Run if called directly
10
10
  if (require.main === module) {
11
11
  run().catch((error) => {
12
+ // ExitError from OCLIF is thrown when this.error() is called
13
+ // It already displays the error message, so we just exit with the code
14
+ if (error.oclif && error.oclif.exit !== undefined) {
15
+ process.exit(error.oclif.exit);
16
+ }
17
+ // For other errors, display them
12
18
  console.error(error);
13
19
  process.exit(1);
14
20
  });