clavix 2.4.3 → 2.5.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/task-complete.d.ts +27 -0
- package/dist/cli/commands/task-complete.js +305 -0
- package/dist/core/config-manager.d.ts +149 -0
- package/dist/core/config-manager.js +267 -0
- package/dist/core/git-manager.d.ts +27 -1
- package/dist/core/git-manager.js +114 -10
- package/dist/core/task-manager.d.ts +31 -0
- package/dist/core/task-manager.js +142 -0
- package/dist/templates/slash-commands/_canonical/implement.md +86 -33
- package/package.json +1 -1
- /package/dist/{core 2/adapters → core/adapters 2}/agents-md-generator.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/agents-md-generator.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/amp-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/amp-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/augment-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/augment-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/base-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/base-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/claude-code-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/claude-code-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/cline-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/cline-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/codebuddy-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/codebuddy-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/codex-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/codex-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/copilot-instructions-generator.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/copilot-instructions-generator.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/crush-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/crush-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/cursor-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/cursor-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/droid-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/droid-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/gemini-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/gemini-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/kilocode-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/kilocode-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/octo-md-generator.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/octo-md-generator.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/opencode-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/opencode-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/qwen-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/qwen-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/roocode-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/roocode-adapter.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/warp-md-generator.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/warp-md-generator.js +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/windsurf-adapter.d.ts +0 -0
- /package/dist/{core 2/adapters → core/adapters 2}/windsurf-adapter.js +0 -0
- /package/dist/{core 2/agent-manager.js → core/agent-manager 2.js} +0 -0
- /package/dist/{core 2/agent-manager.d.ts → core/agent-manager.d 2.ts} +0 -0
- /package/dist/{core 2/archive-manager.js → core/archive-manager 2.js} +0 -0
- /package/dist/{core 2/archive-manager.d.ts → core/archive-manager.d 2.ts} +0 -0
- /package/dist/{core 2/conversation-analyzer.d.ts → core/conversation-analyzer.d 2.ts} +0 -0
- /package/dist/{core 2/doc-injector.js → core/doc-injector 2.js} +0 -0
- /package/dist/{core 2/doc-injector.d.ts → core/doc-injector.d 2.ts} +0 -0
- /package/dist/{core 2/git-manager.js → core/git-manager 2.js} +0 -0
- /package/dist/{core 2/git-manager.d.ts → core/git-manager.d 2.ts} +0 -0
- /package/dist/{core 2/prompt-optimizer.js → core/prompt-optimizer 2.js} +0 -0
- /package/dist/{core 2/prompt-optimizer.d.ts → core/prompt-optimizer.d 2.ts} +0 -0
- /package/dist/{core 2/question-engine.js → core/question-engine 2.js} +0 -0
- /package/dist/{core 2/question-engine.d.ts → core/question-engine.d 2.ts} +0 -0
- /package/dist/{core 2/session-manager.js → core/session-manager 2.js} +0 -0
- /package/dist/{core 2/session-manager.d.ts → core/session-manager.d 2.ts} +0 -0
- /package/dist/{core 2/task-manager.js → core/task-manager 2.js} +0 -0
- /package/dist/{core 2/task-manager.d.ts → core/task-manager.d 2.ts} +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ConfigManager - Manages .clavix-implement-config.json
|
|
4
|
+
*
|
|
5
|
+
* This class handles:
|
|
6
|
+
* - Reading/writing implementation configuration
|
|
7
|
+
* - Tracking task completion state
|
|
8
|
+
* - Managing resume checkpoints
|
|
9
|
+
* - Storing git commit strategy preferences
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.ConfigManager = void 0;
|
|
46
|
+
const fs = __importStar(require("fs-extra"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
/**
|
|
49
|
+
* ConfigManager class
|
|
50
|
+
*
|
|
51
|
+
* Manages implementation configuration and task tracking state
|
|
52
|
+
*/
|
|
53
|
+
class ConfigManager {
|
|
54
|
+
/**
|
|
55
|
+
* Read implementation config from file
|
|
56
|
+
* @param configPath - Path to config file (.clavix-implement-config.json)
|
|
57
|
+
* @returns Implementation configuration
|
|
58
|
+
*/
|
|
59
|
+
async read(configPath) {
|
|
60
|
+
if (!(await fs.pathExists(configPath))) {
|
|
61
|
+
throw new Error(`Config file not found: ${configPath}\n\nHint: Run "clavix implement" first to initialize configuration`);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const config = await fs.readJson(configPath);
|
|
65
|
+
// Migrate old config format if needed
|
|
66
|
+
return this.migrateConfig(config);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
70
|
+
throw new Error(`Failed to read config file: ${errorMessage}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Write implementation config to file
|
|
75
|
+
* @param configPath - Path to config file
|
|
76
|
+
* @param config - Configuration to write
|
|
77
|
+
* @param options - Update options
|
|
78
|
+
*/
|
|
79
|
+
async write(configPath, config, options = {}) {
|
|
80
|
+
const { validate = true } = options;
|
|
81
|
+
if (validate) {
|
|
82
|
+
this.validateConfig(config);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
89
|
+
throw new Error(`Failed to write config file: ${errorMessage}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Update specific fields in config
|
|
94
|
+
* @param configPath - Path to config file
|
|
95
|
+
* @param updates - Partial config updates
|
|
96
|
+
*/
|
|
97
|
+
async update(configPath, updates) {
|
|
98
|
+
const existing = await this.read(configPath);
|
|
99
|
+
const updated = {
|
|
100
|
+
...existing,
|
|
101
|
+
...updates,
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
};
|
|
104
|
+
await this.write(configPath, updated);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Track a task completion
|
|
108
|
+
* @param configPath - Path to config file
|
|
109
|
+
* @param taskId - ID of completed task
|
|
110
|
+
*/
|
|
111
|
+
async trackCompletion(configPath, taskId) {
|
|
112
|
+
const config = await this.read(configPath);
|
|
113
|
+
// Initialize tracking arrays if not present
|
|
114
|
+
if (!config.completedTaskIds) {
|
|
115
|
+
config.completedTaskIds = [];
|
|
116
|
+
}
|
|
117
|
+
if (!config.completionTimestamps) {
|
|
118
|
+
config.completionTimestamps = {};
|
|
119
|
+
}
|
|
120
|
+
// Add to completed tasks (avoid duplicates)
|
|
121
|
+
if (!config.completedTaskIds.includes(taskId)) {
|
|
122
|
+
config.completedTaskIds.push(taskId);
|
|
123
|
+
}
|
|
124
|
+
// Record completion timestamp
|
|
125
|
+
config.completionTimestamps[taskId] = new Date().toISOString();
|
|
126
|
+
// Update last completed task
|
|
127
|
+
config.lastCompletedTaskId = taskId;
|
|
128
|
+
// Update timestamp
|
|
129
|
+
config.timestamp = new Date().toISOString();
|
|
130
|
+
await this.write(configPath, config);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Add a blocked task
|
|
134
|
+
* @param configPath - Path to config file
|
|
135
|
+
* @param taskId - ID of blocked task
|
|
136
|
+
* @param reason - Reason for blocking
|
|
137
|
+
*/
|
|
138
|
+
async addBlockedTask(configPath, taskId, reason) {
|
|
139
|
+
const config = await this.read(configPath);
|
|
140
|
+
if (!config.blockedTasks) {
|
|
141
|
+
config.blockedTasks = [];
|
|
142
|
+
}
|
|
143
|
+
// Remove existing entry for this task if present
|
|
144
|
+
config.blockedTasks = config.blockedTasks.filter(b => b.taskId !== taskId);
|
|
145
|
+
// Add new blocked task
|
|
146
|
+
config.blockedTasks.push({
|
|
147
|
+
taskId,
|
|
148
|
+
reason,
|
|
149
|
+
timestamp: new Date().toISOString(),
|
|
150
|
+
});
|
|
151
|
+
config.timestamp = new Date().toISOString();
|
|
152
|
+
await this.write(configPath, config);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Remove a blocked task (unblock)
|
|
156
|
+
* @param configPath - Path to config file
|
|
157
|
+
* @param taskId - ID of task to unblock
|
|
158
|
+
*/
|
|
159
|
+
async removeBlockedTask(configPath, taskId) {
|
|
160
|
+
const config = await this.read(configPath);
|
|
161
|
+
if (config.blockedTasks) {
|
|
162
|
+
config.blockedTasks = config.blockedTasks.filter(b => b.taskId !== taskId);
|
|
163
|
+
config.timestamp = new Date().toISOString();
|
|
164
|
+
await this.write(configPath, config);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get current implementation state
|
|
169
|
+
* @param configPath - Path to config file
|
|
170
|
+
* @returns State summary
|
|
171
|
+
*/
|
|
172
|
+
async getState(configPath) {
|
|
173
|
+
const config = await this.read(configPath);
|
|
174
|
+
return {
|
|
175
|
+
currentTaskId: config.currentTask.id,
|
|
176
|
+
completedCount: config.completedTaskIds?.length ?? 0,
|
|
177
|
+
remainingCount: config.stats.remaining,
|
|
178
|
+
blockedCount: config.blockedTasks?.length ?? 0,
|
|
179
|
+
lastCompletedTaskId: config.lastCompletedTaskId,
|
|
180
|
+
lastCompletionTime: config.lastCompletedTaskId
|
|
181
|
+
? config.completionTimestamps?.[config.lastCompletedTaskId]
|
|
182
|
+
: undefined,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if a task has been completed
|
|
187
|
+
* @param configPath - Path to config file
|
|
188
|
+
* @param taskId - Task ID to check
|
|
189
|
+
* @returns true if task is in completed list
|
|
190
|
+
*/
|
|
191
|
+
async isTaskCompleted(configPath, taskId) {
|
|
192
|
+
const config = await this.read(configPath);
|
|
193
|
+
return config.completedTaskIds?.includes(taskId) ?? false;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Validate config structure
|
|
197
|
+
* @param config - Configuration to validate
|
|
198
|
+
* @throws Error if config is invalid
|
|
199
|
+
*/
|
|
200
|
+
validateConfig(config) {
|
|
201
|
+
if (!config.commitStrategy) {
|
|
202
|
+
throw new Error('Config validation failed: commitStrategy is required');
|
|
203
|
+
}
|
|
204
|
+
if (!config.tasksPath) {
|
|
205
|
+
throw new Error('Config validation failed: tasksPath is required');
|
|
206
|
+
}
|
|
207
|
+
if (!config.currentTask) {
|
|
208
|
+
throw new Error('Config validation failed: currentTask is required');
|
|
209
|
+
}
|
|
210
|
+
if (!config.stats) {
|
|
211
|
+
throw new Error('Config validation failed: stats is required');
|
|
212
|
+
}
|
|
213
|
+
const validStrategies = ['per-task', 'per-5-tasks', 'per-phase', 'none'];
|
|
214
|
+
if (!validStrategies.includes(config.commitStrategy)) {
|
|
215
|
+
throw new Error(`Config validation failed: invalid commitStrategy "${config.commitStrategy}"`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Migrate old config format to new format
|
|
220
|
+
* @param config - Old config format
|
|
221
|
+
* @returns Migrated config
|
|
222
|
+
*/
|
|
223
|
+
migrateConfig(config) {
|
|
224
|
+
// If already has new fields, return as-is
|
|
225
|
+
if (config.completedTaskIds !== undefined) {
|
|
226
|
+
return config;
|
|
227
|
+
}
|
|
228
|
+
// Migrate from old format
|
|
229
|
+
const migrated = {
|
|
230
|
+
commitStrategy: config.commitStrategy,
|
|
231
|
+
tasksPath: config.tasksPath,
|
|
232
|
+
currentTask: config.currentTask,
|
|
233
|
+
stats: config.stats,
|
|
234
|
+
timestamp: config.timestamp ?? new Date().toISOString(),
|
|
235
|
+
completedTaskIds: [],
|
|
236
|
+
completionTimestamps: {},
|
|
237
|
+
blockedTasks: [],
|
|
238
|
+
};
|
|
239
|
+
return migrated;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Create a resume checkpoint
|
|
243
|
+
* @param configPath - Path to config file
|
|
244
|
+
* @param currentTaskId - Current task being worked on
|
|
245
|
+
* @param phaseProgress - Progress by phase (phase name -> completed count)
|
|
246
|
+
*/
|
|
247
|
+
async createResumeCheckpoint(configPath, currentTaskId, phaseProgress) {
|
|
248
|
+
const config = await this.read(configPath);
|
|
249
|
+
config.resumeCheckpoint = {
|
|
250
|
+
lastTaskId: currentTaskId,
|
|
251
|
+
phaseProgress,
|
|
252
|
+
sessionStartTime: new Date().toISOString(),
|
|
253
|
+
};
|
|
254
|
+
config.timestamp = new Date().toISOString();
|
|
255
|
+
await this.write(configPath, config);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get the config file path for a PRD directory
|
|
259
|
+
* @param prdPath - Path to PRD directory
|
|
260
|
+
* @returns Path to config file
|
|
261
|
+
*/
|
|
262
|
+
static getConfigPath(prdPath) {
|
|
263
|
+
return path.join(prdPath, '.clavix-implement-config.json');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
exports.ConfigManager = ConfigManager;
|
|
267
|
+
//# sourceMappingURL=config-manager.js.map
|
|
@@ -15,7 +15,9 @@ export type CommitStrategy = 'per-phase' | 'per-5-tasks' | 'per-task' | 'none';
|
|
|
15
15
|
* Options for creating a commit
|
|
16
16
|
*/
|
|
17
17
|
export interface CommitOptions {
|
|
18
|
-
|
|
18
|
+
message?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
tasks?: string[];
|
|
19
21
|
phase?: string;
|
|
20
22
|
projectName?: string;
|
|
21
23
|
}
|
|
@@ -41,6 +43,30 @@ export declare class GitManager {
|
|
|
41
43
|
* Generate commit message from tasks
|
|
42
44
|
*/
|
|
43
45
|
private generateCommitMessage;
|
|
46
|
+
/**
|
|
47
|
+
* Validate that tasks are marked before committing
|
|
48
|
+
* This ensures the tasks.md file matches what we expect to commit
|
|
49
|
+
* @param tasksPath - Path to tasks.md file
|
|
50
|
+
* @param completedTaskIds - Array of task IDs that should be completed
|
|
51
|
+
* @returns Validation result with any errors
|
|
52
|
+
*/
|
|
53
|
+
validateBeforeCommit(tasksPath: string, completedTaskIds: string[]): Promise<{
|
|
54
|
+
valid: boolean;
|
|
55
|
+
errors?: string[];
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Create commit with validation
|
|
59
|
+
* Validates that tasks are marked before committing
|
|
60
|
+
* @param options - Commit options
|
|
61
|
+
* @param tasksPath - Path to tasks.md file
|
|
62
|
+
* @param completedTaskIds - Array of task IDs that should be completed
|
|
63
|
+
* @returns Commit result with validation status
|
|
64
|
+
*/
|
|
65
|
+
createCommitWithValidation(options: CommitOptions, tasksPath?: string, completedTaskIds?: string[]): Promise<{
|
|
66
|
+
success: boolean;
|
|
67
|
+
validated: boolean;
|
|
68
|
+
errors?: string[];
|
|
69
|
+
}>;
|
|
44
70
|
/**
|
|
45
71
|
* Escape commit message for shell
|
|
46
72
|
*/
|
package/dist/core/git-manager.js
CHANGED
|
@@ -8,6 +8,39 @@
|
|
|
8
8
|
* - Generating commit messages
|
|
9
9
|
* - Handling commit strategies (per task, per phase, per N tasks)
|
|
10
10
|
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
11
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
45
|
exports.CommitScheduler = exports.GitManager = void 0;
|
|
13
46
|
const child_process_1 = require("child_process");
|
|
@@ -54,8 +87,8 @@ class GitManager {
|
|
|
54
87
|
}
|
|
55
88
|
// Stage all changes
|
|
56
89
|
await execAsync('git add .');
|
|
57
|
-
//
|
|
58
|
-
const message = this.generateCommitMessage(options);
|
|
90
|
+
// Use provided message or generate one
|
|
91
|
+
const message = options.message ?? this.generateCommitMessage(options);
|
|
59
92
|
// Create commit
|
|
60
93
|
await execAsync(`git commit -m "${this.escapeCommitMessage(message)}"`);
|
|
61
94
|
return true;
|
|
@@ -70,31 +103,102 @@ class GitManager {
|
|
|
70
103
|
* Generate commit message from tasks
|
|
71
104
|
*/
|
|
72
105
|
generateCommitMessage(options) {
|
|
73
|
-
const { tasks, phase, projectName } = options;
|
|
106
|
+
const { description, tasks = [], phase, projectName } = options;
|
|
74
107
|
let message = '';
|
|
75
108
|
// Header
|
|
76
109
|
if (phase) {
|
|
77
110
|
message += `clavix: ${phase}\n\n`;
|
|
78
111
|
}
|
|
112
|
+
else if (description) {
|
|
113
|
+
message += `clavix: ${description}\n\n`;
|
|
114
|
+
}
|
|
79
115
|
else if (tasks.length === 1) {
|
|
80
116
|
message += `clavix: ${tasks[0]}\n\n`;
|
|
81
117
|
}
|
|
82
|
-
else {
|
|
118
|
+
else if (tasks.length > 1) {
|
|
83
119
|
message += `clavix: implement ${tasks.length} tasks\n\n`;
|
|
84
120
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
121
|
+
else {
|
|
122
|
+
message += `clavix: task completed\n\n`;
|
|
123
|
+
}
|
|
124
|
+
// Task list (if provided)
|
|
125
|
+
if (tasks.length > 0) {
|
|
126
|
+
message += 'Completed tasks:\n';
|
|
127
|
+
tasks.forEach((task) => {
|
|
128
|
+
message += `- ${task}\n`;
|
|
129
|
+
});
|
|
130
|
+
message += '\n';
|
|
131
|
+
}
|
|
90
132
|
// Footer
|
|
91
|
-
message += '\n';
|
|
92
133
|
if (projectName) {
|
|
93
134
|
message += `Project: ${projectName}\n`;
|
|
94
135
|
}
|
|
95
136
|
message += 'Generated by Clavix /clavix:implement';
|
|
96
137
|
return message;
|
|
97
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Validate that tasks are marked before committing
|
|
141
|
+
* This ensures the tasks.md file matches what we expect to commit
|
|
142
|
+
* @param tasksPath - Path to tasks.md file
|
|
143
|
+
* @param completedTaskIds - Array of task IDs that should be completed
|
|
144
|
+
* @returns Validation result with any errors
|
|
145
|
+
*/
|
|
146
|
+
async validateBeforeCommit(tasksPath, completedTaskIds) {
|
|
147
|
+
const errors = [];
|
|
148
|
+
try {
|
|
149
|
+
// Read tasks.md file
|
|
150
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs-extra')));
|
|
151
|
+
if (!(await fs.pathExists(tasksPath))) {
|
|
152
|
+
errors.push(`Tasks file not found: ${tasksPath}`);
|
|
153
|
+
return { valid: false, errors };
|
|
154
|
+
}
|
|
155
|
+
const content = await fs.readFile(tasksPath, 'utf-8');
|
|
156
|
+
// Check each task ID is marked as complete in the file
|
|
157
|
+
for (const taskId of completedTaskIds) {
|
|
158
|
+
// Look for the task in the file (should have [x] checkbox)
|
|
159
|
+
const hasCompleteCheckbox = content.includes(`[x]`) && content.includes(taskId);
|
|
160
|
+
if (!hasCompleteCheckbox) {
|
|
161
|
+
errors.push(`Task ${taskId} not marked as completed in tasks.md`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
valid: errors.length === 0,
|
|
166
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
171
|
+
errors.push(`Validation failed: ${errorMessage}`);
|
|
172
|
+
return { valid: false, errors };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Create commit with validation
|
|
177
|
+
* Validates that tasks are marked before committing
|
|
178
|
+
* @param options - Commit options
|
|
179
|
+
* @param tasksPath - Path to tasks.md file
|
|
180
|
+
* @param completedTaskIds - Array of task IDs that should be completed
|
|
181
|
+
* @returns Commit result with validation status
|
|
182
|
+
*/
|
|
183
|
+
async createCommitWithValidation(options, tasksPath, completedTaskIds) {
|
|
184
|
+
// If validation info provided, validate first
|
|
185
|
+
if (tasksPath && completedTaskIds) {
|
|
186
|
+
const validation = await this.validateBeforeCommit(tasksPath, completedTaskIds);
|
|
187
|
+
if (!validation.valid) {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
validated: false,
|
|
191
|
+
errors: validation.errors,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Create commit
|
|
196
|
+
const success = await this.createCommit(options);
|
|
197
|
+
return {
|
|
198
|
+
success,
|
|
199
|
+
validated: tasksPath !== undefined && completedTaskIds !== undefined,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
98
202
|
/**
|
|
99
203
|
* Escape commit message for shell
|
|
100
204
|
*/
|
|
@@ -133,6 +133,37 @@ export declare class TaskManager {
|
|
|
133
133
|
* Escape special regex characters
|
|
134
134
|
*/
|
|
135
135
|
private escapeRegex;
|
|
136
|
+
/**
|
|
137
|
+
* Validate that a task exists in the phases
|
|
138
|
+
* @param phases - Array of task phases
|
|
139
|
+
* @param taskId - Task ID to validate
|
|
140
|
+
* @returns The task if found, null otherwise
|
|
141
|
+
*/
|
|
142
|
+
validateTaskExists(phases: TaskPhase[], taskId: string): Task | null;
|
|
143
|
+
/**
|
|
144
|
+
* Verify that a task was successfully marked as completed in the file
|
|
145
|
+
* @param tasksPath - Path to tasks.md file
|
|
146
|
+
* @param taskId - Task ID to verify
|
|
147
|
+
* @returns true if task is marked completed, false otherwise
|
|
148
|
+
*/
|
|
149
|
+
verifyTaskMarked(tasksPath: string, taskId: string): Promise<boolean>;
|
|
150
|
+
/**
|
|
151
|
+
* Mark a task as completed with validation and error recovery
|
|
152
|
+
* Enhanced version with pre/post validation
|
|
153
|
+
* @param tasksPath - Path to tasks.md file
|
|
154
|
+
* @param taskId - Task ID to mark as completed
|
|
155
|
+
* @param options - Optional configuration for error recovery
|
|
156
|
+
* @returns Object with success status and any warnings/errors
|
|
157
|
+
*/
|
|
158
|
+
markTaskCompletedWithValidation(tasksPath: string, taskId: string, options?: {
|
|
159
|
+
retryOnFailure?: boolean;
|
|
160
|
+
createBackup?: boolean;
|
|
161
|
+
}): Promise<{
|
|
162
|
+
success: boolean;
|
|
163
|
+
alreadyCompleted?: boolean;
|
|
164
|
+
error?: string;
|
|
165
|
+
warnings?: string[];
|
|
166
|
+
}>;
|
|
136
167
|
/**
|
|
137
168
|
* Get task completion statistics
|
|
138
169
|
*/
|
|
@@ -605,6 +605,148 @@ class TaskManager {
|
|
|
605
605
|
escapeRegex(str) {
|
|
606
606
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
607
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Validate that a task exists in the phases
|
|
610
|
+
* @param phases - Array of task phases
|
|
611
|
+
* @param taskId - Task ID to validate
|
|
612
|
+
* @returns The task if found, null otherwise
|
|
613
|
+
*/
|
|
614
|
+
validateTaskExists(phases, taskId) {
|
|
615
|
+
for (const phase of phases) {
|
|
616
|
+
const task = phase.tasks.find(t => t.id === taskId);
|
|
617
|
+
if (task) {
|
|
618
|
+
return task;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Verify that a task was successfully marked as completed in the file
|
|
625
|
+
* @param tasksPath - Path to tasks.md file
|
|
626
|
+
* @param taskId - Task ID to verify
|
|
627
|
+
* @returns true if task is marked completed, false otherwise
|
|
628
|
+
*/
|
|
629
|
+
async verifyTaskMarked(tasksPath, taskId) {
|
|
630
|
+
try {
|
|
631
|
+
const phases = await this.readTasksFile(tasksPath);
|
|
632
|
+
const task = this.validateTaskExists(phases, taskId);
|
|
633
|
+
return task ? task.completed : false;
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
// If we can't read the file, verification failed
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Mark a task as completed with validation and error recovery
|
|
642
|
+
* Enhanced version with pre/post validation
|
|
643
|
+
* @param tasksPath - Path to tasks.md file
|
|
644
|
+
* @param taskId - Task ID to mark as completed
|
|
645
|
+
* @param options - Optional configuration for error recovery
|
|
646
|
+
* @returns Object with success status and any warnings/errors
|
|
647
|
+
*/
|
|
648
|
+
async markTaskCompletedWithValidation(tasksPath, taskId, options = {}) {
|
|
649
|
+
const { retryOnFailure = true, createBackup = true } = options;
|
|
650
|
+
const warnings = [];
|
|
651
|
+
// Pre-validation: Check if file exists
|
|
652
|
+
if (!(await fs.pathExists(tasksPath))) {
|
|
653
|
+
return {
|
|
654
|
+
success: false,
|
|
655
|
+
error: `Tasks file not found: ${tasksPath}`,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
// Create backup if requested
|
|
659
|
+
let backupPath = null;
|
|
660
|
+
if (createBackup) {
|
|
661
|
+
backupPath = `${tasksPath}.backup`;
|
|
662
|
+
try {
|
|
663
|
+
await fs.copyFile(tasksPath, backupPath);
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
warnings.push('Failed to create backup file');
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
// Read and validate task exists
|
|
671
|
+
const phases = await this.readTasksFile(tasksPath);
|
|
672
|
+
const task = this.validateTaskExists(phases, taskId);
|
|
673
|
+
if (!task) {
|
|
674
|
+
// Task not found - provide helpful error
|
|
675
|
+
const allTaskIds = phases.flatMap(p => p.tasks.map(t => t.id));
|
|
676
|
+
return {
|
|
677
|
+
success: false,
|
|
678
|
+
error: `Task ID "${taskId}" not found. Available task IDs:\n${allTaskIds.join('\n')}`,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
// Check if already completed
|
|
682
|
+
if (task.completed) {
|
|
683
|
+
return {
|
|
684
|
+
success: true,
|
|
685
|
+
alreadyCompleted: true,
|
|
686
|
+
warnings: [`Task "${taskId}" was already marked as completed`],
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
// Attempt to mark task completed
|
|
690
|
+
await this.markTaskCompleted(tasksPath, taskId);
|
|
691
|
+
// Post-validation: Verify the checkbox was actually changed
|
|
692
|
+
const verified = await this.verifyTaskMarked(tasksPath, taskId);
|
|
693
|
+
if (!verified) {
|
|
694
|
+
// Verification failed - attempt recovery if enabled
|
|
695
|
+
if (retryOnFailure && backupPath) {
|
|
696
|
+
warnings.push('First attempt failed verification, retrying...');
|
|
697
|
+
// Restore from backup and try again
|
|
698
|
+
await fs.copyFile(backupPath, tasksPath);
|
|
699
|
+
await this.markTaskCompleted(tasksPath, taskId);
|
|
700
|
+
// Verify again
|
|
701
|
+
const secondVerification = await this.verifyTaskMarked(tasksPath, taskId);
|
|
702
|
+
if (!secondVerification) {
|
|
703
|
+
// Still failed - restore backup and return error
|
|
704
|
+
await fs.copyFile(backupPath, tasksPath);
|
|
705
|
+
return {
|
|
706
|
+
success: false,
|
|
707
|
+
error: 'Failed to mark task as completed even after retry. File has been restored from backup.',
|
|
708
|
+
warnings,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
warnings.push('Task marked successfully on retry');
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
// No retry - just fail
|
|
715
|
+
return {
|
|
716
|
+
success: false,
|
|
717
|
+
error: 'Task completion verification failed',
|
|
718
|
+
warnings,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
// Clean up backup on success
|
|
723
|
+
if (backupPath && await fs.pathExists(backupPath)) {
|
|
724
|
+
await fs.remove(backupPath);
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
success: true,
|
|
728
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
catch (error) {
|
|
732
|
+
// Restore from backup if available
|
|
733
|
+
if (backupPath && await fs.pathExists(backupPath)) {
|
|
734
|
+
try {
|
|
735
|
+
await fs.copyFile(backupPath, tasksPath);
|
|
736
|
+
warnings.push('Restored tasks.md from backup due to error');
|
|
737
|
+
}
|
|
738
|
+
catch (restoreError) {
|
|
739
|
+
warnings.push('Failed to restore from backup');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
743
|
+
return {
|
|
744
|
+
success: false,
|
|
745
|
+
error: `Error marking task as completed: ${errorMessage}`,
|
|
746
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
}
|
|
608
750
|
/**
|
|
609
751
|
* Get task completion statistics
|
|
610
752
|
*/
|