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.
- package/dist/cli/commands/init.js +1 -1
- package/dist/core 2/adapters/agents-md-generator.d.ts +26 -0
- package/dist/core 2/adapters/agents-md-generator.js +102 -0
- package/dist/core 2/adapters/amp-adapter.d.ts +27 -0
- package/dist/core 2/adapters/amp-adapter.js +42 -0
- package/dist/core 2/adapters/augment-adapter.d.ts +22 -0
- package/dist/core 2/adapters/augment-adapter.js +77 -0
- package/dist/core 2/adapters/base-adapter.d.ts +45 -0
- package/dist/core 2/adapters/base-adapter.js +142 -0
- package/dist/core 2/adapters/claude-code-adapter.d.ts +32 -0
- package/dist/core 2/adapters/claude-code-adapter.js +116 -0
- package/dist/core 2/adapters/cline-adapter.d.ts +34 -0
- package/dist/core 2/adapters/cline-adapter.js +52 -0
- package/dist/core 2/adapters/codebuddy-adapter.d.ts +24 -0
- package/dist/core 2/adapters/codebuddy-adapter.js +82 -0
- package/dist/core 2/adapters/codex-adapter.d.ts +24 -0
- package/dist/core 2/adapters/codex-adapter.js +79 -0
- package/dist/core 2/adapters/copilot-instructions-generator.d.ts +26 -0
- package/dist/core 2/adapters/copilot-instructions-generator.js +104 -0
- package/dist/core 2/adapters/crush-adapter.d.ts +35 -0
- package/dist/core 2/adapters/crush-adapter.js +49 -0
- package/dist/core 2/adapters/cursor-adapter.d.ts +25 -0
- package/dist/core 2/adapters/cursor-adapter.js +40 -0
- package/dist/core 2/adapters/droid-adapter.d.ts +33 -0
- package/dist/core 2/adapters/droid-adapter.js +57 -0
- package/dist/core 2/adapters/gemini-adapter.d.ts +27 -0
- package/dist/core 2/adapters/gemini-adapter.js +90 -0
- package/dist/core 2/adapters/kilocode-adapter.d.ts +34 -0
- package/dist/core 2/adapters/kilocode-adapter.js +49 -0
- package/dist/core 2/adapters/octo-md-generator.d.ts +26 -0
- package/dist/core 2/adapters/octo-md-generator.js +102 -0
- package/dist/core 2/adapters/opencode-adapter.d.ts +33 -0
- package/dist/core 2/adapters/opencode-adapter.js +56 -0
- package/dist/core 2/adapters/qwen-adapter.d.ts +27 -0
- package/dist/core 2/adapters/qwen-adapter.js +90 -0
- package/dist/core 2/adapters/roocode-adapter.d.ts +40 -0
- package/dist/core 2/adapters/roocode-adapter.js +68 -0
- package/dist/core 2/adapters/warp-md-generator.d.ts +17 -0
- package/dist/core 2/adapters/warp-md-generator.js +88 -0
- package/dist/core 2/adapters/windsurf-adapter.d.ts +34 -0
- package/dist/core 2/adapters/windsurf-adapter.js +49 -0
- package/dist/core 2/agent-manager.d.ts +51 -0
- package/dist/core 2/agent-manager.js +126 -0
- package/dist/core 2/archive-manager.d.ts +100 -0
- package/dist/core 2/archive-manager.js +338 -0
- package/dist/core 2/conversation-analyzer.d.ts +86 -0
- package/dist/core 2/doc-injector.d.ts +51 -0
- package/dist/core 2/doc-injector.js +236 -0
- package/dist/core 2/git-manager.d.ts +100 -0
- package/dist/core 2/git-manager.js +214 -0
- package/dist/core 2/prompt-optimizer.d.ts +268 -0
- package/dist/core 2/prompt-optimizer.js +963 -0
- package/dist/core 2/question-engine.d.ts +167 -0
- package/dist/core 2/question-engine.js +395 -0
- package/dist/core 2/session-manager.d.ts +139 -0
- package/dist/core 2/session-manager.js +403 -0
- package/dist/core 2/task-manager.d.ts +155 -0
- package/dist/core 2/task-manager.js +689 -0
- package/dist/utils/template-loader.js +24 -22
- package/package.json +1 -1
- package/dist/templates/slash-commands/augment/archive.md +0 -291
- package/dist/templates/slash-commands/augment/deep.md +0 -207
- package/dist/templates/slash-commands/augment/fast.md +0 -183
- package/dist/templates/slash-commands/augment/implement.md +0 -267
- package/dist/templates/slash-commands/augment/plan.md +0 -173
- package/dist/templates/slash-commands/augment/prd.md +0 -178
- package/dist/templates/slash-commands/augment/start.md +0 -142
- package/dist/templates/slash-commands/augment/summarize.md +0 -179
- package/dist/templates/slash-commands/claude-code/archive.md +0 -291
- package/dist/templates/slash-commands/claude-code/deep.md +0 -207
- package/dist/templates/slash-commands/claude-code/fast.md +0 -183
- package/dist/templates/slash-commands/claude-code/implement.md +0 -267
- package/dist/templates/slash-commands/claude-code/plan.md +0 -173
- package/dist/templates/slash-commands/claude-code/prd.md +0 -178
- package/dist/templates/slash-commands/claude-code/start.md +0 -142
- package/dist/templates/slash-commands/claude-code/summarize.md +0 -179
- package/dist/templates/slash-commands/cline/archive.md +0 -291
- package/dist/templates/slash-commands/cline/deep.md +0 -207
- package/dist/templates/slash-commands/cline/fast.md +0 -183
- package/dist/templates/slash-commands/cline/implement.md +0 -267
- package/dist/templates/slash-commands/cline/plan.md +0 -173
- package/dist/templates/slash-commands/cline/prd.md +0 -178
- package/dist/templates/slash-commands/cline/start.md +0 -142
- package/dist/templates/slash-commands/cline/summarize.md +0 -179
- package/dist/templates/slash-commands/codebuddy/archive.md +0 -291
- package/dist/templates/slash-commands/codebuddy/deep.md +0 -207
- package/dist/templates/slash-commands/codebuddy/fast.md +0 -183
- package/dist/templates/slash-commands/codebuddy/implement.md +0 -267
- package/dist/templates/slash-commands/codebuddy/plan.md +0 -173
- package/dist/templates/slash-commands/codebuddy/prd.md +0 -178
- package/dist/templates/slash-commands/codebuddy/start.md +0 -142
- package/dist/templates/slash-commands/codebuddy/summarize.md +0 -179
- package/dist/templates/slash-commands/codex/archive.md +0 -291
- package/dist/templates/slash-commands/codex/deep.md +0 -207
- package/dist/templates/slash-commands/codex/fast.md +0 -183
- package/dist/templates/slash-commands/codex/implement.md +0 -267
- package/dist/templates/slash-commands/codex/plan.md +0 -173
- package/dist/templates/slash-commands/codex/prd.md +0 -178
- package/dist/templates/slash-commands/codex/start.md +0 -142
- package/dist/templates/slash-commands/codex/summarize.md +0 -179
- package/dist/templates/slash-commands/crush/archive.md +0 -291
- package/dist/templates/slash-commands/crush/deep.md +0 -207
- package/dist/templates/slash-commands/crush/fast.md +0 -183
- package/dist/templates/slash-commands/crush/implement.md +0 -267
- package/dist/templates/slash-commands/crush/plan.md +0 -173
- package/dist/templates/slash-commands/crush/prd.md +0 -178
- package/dist/templates/slash-commands/crush/start.md +0 -142
- package/dist/templates/slash-commands/crush/summarize.md +0 -179
- package/dist/templates/slash-commands/cursor/archive.md +0 -291
- package/dist/templates/slash-commands/cursor/deep.md +0 -207
- package/dist/templates/slash-commands/cursor/fast.md +0 -183
- package/dist/templates/slash-commands/cursor/implement.md +0 -267
- package/dist/templates/slash-commands/cursor/plan.md +0 -173
- package/dist/templates/slash-commands/cursor/prd.md +0 -178
- package/dist/templates/slash-commands/cursor/start.md +0 -142
- package/dist/templates/slash-commands/cursor/summarize.md +0 -179
- package/dist/templates/slash-commands/droid/archive.md +0 -291
- package/dist/templates/slash-commands/droid/deep.md +0 -207
- package/dist/templates/slash-commands/droid/fast.md +0 -183
- package/dist/templates/slash-commands/droid/implement.md +0 -267
- package/dist/templates/slash-commands/droid/plan.md +0 -173
- package/dist/templates/slash-commands/droid/prd.md +0 -178
- package/dist/templates/slash-commands/droid/start.md +0 -142
- package/dist/templates/slash-commands/droid/summarize.md +0 -179
- package/dist/templates/slash-commands/gemini/archive.toml +0 -290
- package/dist/templates/slash-commands/gemini/deep.toml +0 -206
- package/dist/templates/slash-commands/gemini/fast.toml +0 -182
- package/dist/templates/slash-commands/gemini/implement.toml +0 -266
- package/dist/templates/slash-commands/gemini/plan.toml +0 -170
- package/dist/templates/slash-commands/gemini/prd.toml +0 -177
- package/dist/templates/slash-commands/gemini/start.toml +0 -141
- package/dist/templates/slash-commands/gemini/summarize.toml +0 -178
- package/dist/templates/slash-commands/kilocode/archive.md +0 -291
- package/dist/templates/slash-commands/kilocode/deep.md +0 -207
- package/dist/templates/slash-commands/kilocode/fast.md +0 -183
- package/dist/templates/slash-commands/kilocode/implement.md +0 -267
- package/dist/templates/slash-commands/kilocode/plan.md +0 -173
- package/dist/templates/slash-commands/kilocode/prd.md +0 -178
- package/dist/templates/slash-commands/kilocode/start.md +0 -142
- package/dist/templates/slash-commands/kilocode/summarize.md +0 -179
- package/dist/templates/slash-commands/opencode/archive.md +0 -291
- package/dist/templates/slash-commands/opencode/deep.md +0 -207
- package/dist/templates/slash-commands/opencode/fast.md +0 -183
- package/dist/templates/slash-commands/opencode/implement.md +0 -267
- package/dist/templates/slash-commands/opencode/plan.md +0 -173
- package/dist/templates/slash-commands/opencode/prd.md +0 -178
- package/dist/templates/slash-commands/opencode/start.md +0 -142
- package/dist/templates/slash-commands/opencode/summarize.md +0 -179
- package/dist/templates/slash-commands/qwen/archive.toml +0 -290
- package/dist/templates/slash-commands/qwen/deep.toml +0 -206
- package/dist/templates/slash-commands/qwen/fast.toml +0 -182
- package/dist/templates/slash-commands/qwen/implement.toml +0 -266
- package/dist/templates/slash-commands/qwen/plan.toml +0 -170
- package/dist/templates/slash-commands/qwen/prd.toml +0 -177
- package/dist/templates/slash-commands/qwen/start.toml +0 -141
- package/dist/templates/slash-commands/qwen/summarize.toml +0 -178
- package/dist/templates/slash-commands/roocode/archive.md +0 -291
- package/dist/templates/slash-commands/roocode/deep.md +0 -207
- package/dist/templates/slash-commands/roocode/fast.md +0 -183
- package/dist/templates/slash-commands/roocode/implement.md +0 -267
- package/dist/templates/slash-commands/roocode/plan.md +0 -173
- package/dist/templates/slash-commands/roocode/prd.md +0 -178
- package/dist/templates/slash-commands/roocode/start.md +0 -142
- package/dist/templates/slash-commands/roocode/summarize.md +0 -179
- package/dist/templates/slash-commands/windsurf/archive.md +0 -291
- package/dist/templates/slash-commands/windsurf/deep.md +0 -207
- package/dist/templates/slash-commands/windsurf/fast.md +0 -183
- package/dist/templates/slash-commands/windsurf/implement.md +0 -267
- package/dist/templates/slash-commands/windsurf/plan.md +0 -173
- package/dist/templates/slash-commands/windsurf/prd.md +0 -178
- package/dist/templates/slash-commands/windsurf/start.md +0 -142
- package/dist/templates/slash-commands/windsurf/summarize.md +0 -179
- /package/dist/templates/slash-commands/{amp → _canonical}/archive.md +0 -0
- /package/dist/templates/slash-commands/{amp → _canonical}/deep.md +0 -0
- /package/dist/templates/slash-commands/{amp → _canonical}/fast.md +0 -0
- /package/dist/templates/slash-commands/{amp → _canonical}/implement.md +0 -0
- /package/dist/templates/slash-commands/{amp → _canonical}/plan.md +0 -0
- /package/dist/templates/slash-commands/{amp → _canonical}/prd.md +0 -0
- /package/dist/templates/slash-commands/{amp → _canonical}/start.md +0 -0
- /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
|