clavix 2.4.2 → 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/bin/clavix.js +20 -1
- package/dist/cli/commands/implement.js +4 -8
- package/dist/cli/commands/plan.js +14 -10
- package/dist/cli/commands/prd.js +1 -1
- package/dist/cli/commands/start.js +1 -1
- package/dist/cli/commands/summarize.js +9 -12
- 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/index.js +4 -0
- package/dist/templates/slash-commands/_canonical/implement.md +86 -33
- package/dist/utils/template-loader.d.ts +1 -1
- package/dist/utils/template-loader.js +1 -1
- 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
package/bin/clavix.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Disable debug mode (stack traces) unless explicitly requested via DEBUG env var
|
|
4
|
+
if (!process.env.DEBUG) {
|
|
5
|
+
require('@oclif/core').settings.debug = false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Custom error handler to suppress stack traces
|
|
9
|
+
async function handleError(error) {
|
|
10
|
+
// For CLIError, show only the formatted message
|
|
11
|
+
if (error.oclif && error.oclif.exit !== undefined) {
|
|
12
|
+
// Format error message (hints are now included in error.message)
|
|
13
|
+
console.error(' › Error: ' + error.message);
|
|
14
|
+
process.exit(error.oclif.exit);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// For other errors, use default handler
|
|
18
|
+
const { handle } = require('@oclif/core');
|
|
19
|
+
return handle(error);
|
|
20
|
+
}
|
|
21
|
+
|
|
3
22
|
require('../dist/index.js')
|
|
4
23
|
.run()
|
|
5
|
-
.catch(
|
|
24
|
+
.catch(handleError);
|
|
@@ -67,9 +67,7 @@ class Implement extends core_1.Command {
|
|
|
67
67
|
const prdPath = await manager.findPrdDirectory(flags.project);
|
|
68
68
|
tasksPath = path.join(prdPath, 'tasks.md');
|
|
69
69
|
if (!(await fs.pathExists(tasksPath))) {
|
|
70
|
-
this.error(
|
|
71
|
-
'\n\n' +
|
|
72
|
-
chalk_1.default.yellow('Hint: Run ') + chalk_1.default.cyan('clavix plan') + chalk_1.default.yellow(' first to generate task breakdown'));
|
|
70
|
+
this.error('No tasks.md found\n\nHint: Run "clavix plan" first to generate task breakdown');
|
|
73
71
|
}
|
|
74
72
|
console.log(chalk_1.default.dim(`Found: ${tasksPath}\n`));
|
|
75
73
|
}
|
|
@@ -187,7 +185,7 @@ class Implement extends core_1.Command {
|
|
|
187
185
|
}
|
|
188
186
|
catch (error) {
|
|
189
187
|
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
|
190
|
-
this.error(
|
|
188
|
+
this.error(errorMessage);
|
|
191
189
|
}
|
|
192
190
|
}
|
|
193
191
|
/**
|
|
@@ -234,9 +232,7 @@ class Implement extends core_1.Command {
|
|
|
234
232
|
}
|
|
235
233
|
// No PRD projects found
|
|
236
234
|
if (prdProjects.length === 0) {
|
|
237
|
-
this.error(
|
|
238
|
-
'\n\n' +
|
|
239
|
-
chalk_1.default.gray('Hint: Create a PRD first using ') + chalk_1.default.cyan('clavix prd'));
|
|
235
|
+
this.error('No PRD projects found in .clavix/outputs/\n\nHint: Create a PRD first using: clavix prd');
|
|
240
236
|
}
|
|
241
237
|
// Only one PRD - auto-select
|
|
242
238
|
if (prdProjects.length === 1) {
|
|
@@ -319,7 +315,7 @@ class Implement extends core_1.Command {
|
|
|
319
315
|
}
|
|
320
316
|
catch (error) {
|
|
321
317
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
322
|
-
this.error(
|
|
318
|
+
this.error(`Running clavix plan failed: ${errorMessage}`);
|
|
323
319
|
}
|
|
324
320
|
}
|
|
325
321
|
else {
|
|
@@ -69,9 +69,11 @@ class Plan extends core_1.Command {
|
|
|
69
69
|
if (!prdPath) {
|
|
70
70
|
selectedProject = await this.resolveProjectDirectory(manager, flags.project);
|
|
71
71
|
if (!selectedProject) {
|
|
72
|
-
this.error(
|
|
73
|
-
'
|
|
74
|
-
|
|
72
|
+
this.error('No PRD projects found in .clavix/outputs/\n\n' +
|
|
73
|
+
'Hints:\n' +
|
|
74
|
+
' • Run "clavix prd" to generate a PRD\n' +
|
|
75
|
+
' • Run "clavix summarize" to create a mini-PRD\n' +
|
|
76
|
+
' • Use "clavix plan --session <id>" to plan from a session');
|
|
75
77
|
}
|
|
76
78
|
prdPath = selectedProject.path;
|
|
77
79
|
projectName = selectedProject.name;
|
|
@@ -91,14 +93,16 @@ class Plan extends core_1.Command {
|
|
|
91
93
|
console.log(chalk_1.default.dim('Looking for PRD artifacts...'));
|
|
92
94
|
const availableSources = await manager.detectAvailableSources(prdPath);
|
|
93
95
|
if (availableSources.length === 0) {
|
|
94
|
-
this.error(
|
|
95
|
-
'
|
|
96
|
-
|
|
96
|
+
this.error('No PRD artifacts found in this directory\n\n' +
|
|
97
|
+
'Hints:\n' +
|
|
98
|
+
' • Generate a PRD with: clavix prd\n' +
|
|
99
|
+
' • Create a mini-PRD with: clavix summarize\n' +
|
|
100
|
+
' • Plan from a session with: clavix plan --session <id>');
|
|
97
101
|
}
|
|
98
102
|
if (sourcePreference !== 'auto' && !availableSources.includes(sourcePreference)) {
|
|
99
|
-
this.error(
|
|
100
|
-
'\n
|
|
101
|
-
|
|
103
|
+
this.error(`Preferred source "${sourcePreference}" not found in ${prdPath}\n\n` +
|
|
104
|
+
`Available sources: ${availableSources.join(', ') || 'none'}\n` +
|
|
105
|
+
'Hint: Override with --source flag');
|
|
102
106
|
}
|
|
103
107
|
if (availableSources.length > 1 && sourcePreference === 'auto') {
|
|
104
108
|
console.log(chalk_1.default.dim(`Found multiple sources (${availableSources.join(', ')}). Selecting best match...`));
|
|
@@ -159,7 +163,7 @@ class Plan extends core_1.Command {
|
|
|
159
163
|
}
|
|
160
164
|
catch (error) {
|
|
161
165
|
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
|
162
|
-
this.error(
|
|
166
|
+
this.error(errorMessage);
|
|
163
167
|
}
|
|
164
168
|
}
|
|
165
169
|
validateSessionFlags(session, activeSession) {
|
package/dist/cli/commands/prd.js
CHANGED
|
@@ -173,7 +173,7 @@ class Prd extends core_1.Command {
|
|
|
173
173
|
}
|
|
174
174
|
catch (error) {
|
|
175
175
|
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
|
176
|
-
this.error(
|
|
176
|
+
this.error(errorMessage);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
/**
|
|
@@ -56,28 +56,25 @@ class Summarize extends core_1.Command {
|
|
|
56
56
|
console.log(chalk_1.default.dim(`Loading session ${args.sessionId}...\n`));
|
|
57
57
|
session = await manager.getSession(args.sessionId);
|
|
58
58
|
if (!session) {
|
|
59
|
-
this.error(
|
|
59
|
+
this.error(`Session not found: ${args.sessionId}`);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
else if (flags.active) {
|
|
63
63
|
console.log(chalk_1.default.dim('Loading most recent active session...\n'));
|
|
64
64
|
session = await manager.getActiveSession();
|
|
65
65
|
if (!session) {
|
|
66
|
-
this.error(
|
|
67
|
-
'\n\n' +
|
|
68
|
-
chalk_1.default.gray('Hint: Use ') + chalk_1.default.cyan('clavix list') + chalk_1.default.gray(' to see all sessions'));
|
|
66
|
+
this.error('No active session found\n\nHint: Use "clavix list" to see all sessions');
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
else {
|
|
72
70
|
// Try to get active session by default
|
|
73
71
|
session = await manager.getActiveSession();
|
|
74
72
|
if (!session) {
|
|
75
|
-
this.error(
|
|
76
|
-
'
|
|
77
|
-
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'\n' + chalk_1.default.gray(' • ') + chalk_1.default.cyan('clavix list') + chalk_1.default.gray(' - View all sessions'));
|
|
73
|
+
this.error('No active session found\n\n' +
|
|
74
|
+
'Usage:\n' +
|
|
75
|
+
' • clavix summarize <session-id> - Summarize specific session\n' +
|
|
76
|
+
' • clavix summarize --active - Summarize most recent active session\n' +
|
|
77
|
+
' • clavix list - View all sessions');
|
|
81
78
|
}
|
|
82
79
|
}
|
|
83
80
|
// Display session info
|
|
@@ -89,7 +86,7 @@ class Summarize extends core_1.Command {
|
|
|
89
86
|
console.log();
|
|
90
87
|
// Check if session has messages
|
|
91
88
|
if (session.messages.length === 0) {
|
|
92
|
-
this.error(
|
|
89
|
+
this.error('Session has no messages to analyze');
|
|
93
90
|
}
|
|
94
91
|
// Analyze conversation
|
|
95
92
|
console.log(chalk_1.default.dim('Analyzing conversation...\n'));
|
|
@@ -138,7 +135,7 @@ class Summarize extends core_1.Command {
|
|
|
138
135
|
}
|
|
139
136
|
catch (error) {
|
|
140
137
|
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
|
141
|
-
this.error(
|
|
138
|
+
this.error(errorMessage);
|
|
142
139
|
}
|
|
143
140
|
}
|
|
144
141
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class TaskComplete extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
taskId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
'no-git': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
config: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Find config file (auto-discover or use provided path)
|
|
16
|
+
*/
|
|
17
|
+
private findConfigFile;
|
|
18
|
+
/**
|
|
19
|
+
* Handle git commit based on strategy
|
|
20
|
+
*/
|
|
21
|
+
private handleGitCommit;
|
|
22
|
+
/**
|
|
23
|
+
* Show next incomplete task
|
|
24
|
+
*/
|
|
25
|
+
private showNextTask;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=task-complete.d.ts.map
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const core_1 = require("@oclif/core");
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const task_manager_1 = require("../../core/task-manager");
|
|
42
|
+
const config_manager_1 = require("../../core/config-manager");
|
|
43
|
+
const git_manager_1 = require("../../core/git-manager");
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const fs = __importStar(require("fs-extra"));
|
|
46
|
+
class TaskComplete extends core_1.Command {
|
|
47
|
+
async run() {
|
|
48
|
+
const { args, flags } = await this.parse(TaskComplete);
|
|
49
|
+
const taskId = args.taskId;
|
|
50
|
+
console.log(chalk_1.default.bold.cyan(`\nTask Completion: ${taskId}\n`));
|
|
51
|
+
try {
|
|
52
|
+
const taskManager = new task_manager_1.TaskManager();
|
|
53
|
+
const configManager = new config_manager_1.ConfigManager();
|
|
54
|
+
const gitManager = new git_manager_1.GitManager();
|
|
55
|
+
// Find config file
|
|
56
|
+
const configPath = await this.findConfigFile(flags.config);
|
|
57
|
+
console.log(chalk_1.default.dim(`Config: ${configPath}\n`));
|
|
58
|
+
// Read config
|
|
59
|
+
const config = await configManager.read(configPath);
|
|
60
|
+
const tasksPath = config.tasksPath;
|
|
61
|
+
// Read tasks
|
|
62
|
+
const phases = await taskManager.readTasksFile(tasksPath);
|
|
63
|
+
// Validate task exists
|
|
64
|
+
const task = taskManager.validateTaskExists(phases, taskId);
|
|
65
|
+
if (!task) {
|
|
66
|
+
// Smart recovery: List available tasks
|
|
67
|
+
console.log(chalk_1.default.red(`✗ Task ID "${taskId}" not found\n`));
|
|
68
|
+
console.log(chalk_1.default.yellow('Available task IDs:\n'));
|
|
69
|
+
phases.forEach(phase => {
|
|
70
|
+
console.log(chalk_1.default.bold(` ${phase.name}:`));
|
|
71
|
+
phase.tasks.forEach(t => {
|
|
72
|
+
const status = t.completed ? chalk_1.default.green('[x]') : chalk_1.default.gray('[ ]');
|
|
73
|
+
console.log(` ${status} ${chalk_1.default.cyan(t.id)} - ${t.description}`);
|
|
74
|
+
});
|
|
75
|
+
console.log();
|
|
76
|
+
});
|
|
77
|
+
this.error('Task not found. Please use one of the task IDs listed above.');
|
|
78
|
+
}
|
|
79
|
+
// Check if already completed
|
|
80
|
+
if (task.completed && !flags.force) {
|
|
81
|
+
console.log(chalk_1.default.yellow(`⚠ Task "${taskId}" is already marked as completed\n`));
|
|
82
|
+
// Check if it's in config too
|
|
83
|
+
const isInConfig = await configManager.isTaskCompleted(configPath, taskId);
|
|
84
|
+
if (isInConfig) {
|
|
85
|
+
console.log(chalk_1.default.dim('Task is tracked in config as completed.\n'));
|
|
86
|
+
}
|
|
87
|
+
console.log(chalk_1.default.gray('Use --force to re-mark this task as completed.\n'));
|
|
88
|
+
// Show next task
|
|
89
|
+
await this.showNextTask(taskManager, phases);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Mark task as completed with validation
|
|
93
|
+
console.log(chalk_1.default.dim('Marking task as completed...'));
|
|
94
|
+
const result = await taskManager.markTaskCompletedWithValidation(tasksPath, taskId);
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
// Smart recovery: Show error and suggestions
|
|
97
|
+
console.log(chalk_1.default.red(`\n✗ Failed to mark task as completed\n`));
|
|
98
|
+
console.log(chalk_1.default.yellow(`Error: ${result.error}\n`));
|
|
99
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
100
|
+
console.log(chalk_1.default.yellow('Warnings:'));
|
|
101
|
+
result.warnings.forEach(warning => console.log(chalk_1.default.yellow(` • ${warning}`)));
|
|
102
|
+
console.log();
|
|
103
|
+
}
|
|
104
|
+
// Provide recovery suggestions
|
|
105
|
+
console.log(chalk_1.default.bold('Recovery Options:\n'));
|
|
106
|
+
console.log(chalk_1.default.gray(' 1. Check if tasks.md file is readable and writable'));
|
|
107
|
+
console.log(chalk_1.default.gray(' 2. Verify task ID matches exactly (run "clavix implement" to see current task)'));
|
|
108
|
+
console.log(chalk_1.default.gray(' 3. Try running with --force flag if task is already completed'));
|
|
109
|
+
console.log(chalk_1.default.gray(' 4. Check tasks.md.backup file if one was created\n'));
|
|
110
|
+
this.error('Task completion failed');
|
|
111
|
+
}
|
|
112
|
+
// Display warnings if any
|
|
113
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
114
|
+
console.log(chalk_1.default.yellow('\nWarnings:'));
|
|
115
|
+
result.warnings.forEach(warning => console.log(chalk_1.default.yellow(` • ${warning}`)));
|
|
116
|
+
console.log();
|
|
117
|
+
}
|
|
118
|
+
// Success!
|
|
119
|
+
if (result.alreadyCompleted) {
|
|
120
|
+
console.log(chalk_1.default.green(`✓ Task was already completed (tracking updated)\n`));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(chalk_1.default.green(`✓ Task marked as completed\n`));
|
|
124
|
+
}
|
|
125
|
+
// Track completion in config
|
|
126
|
+
console.log(chalk_1.default.dim('Updating configuration...'));
|
|
127
|
+
await configManager.trackCompletion(configPath, taskId);
|
|
128
|
+
// Update stats
|
|
129
|
+
const updatedStats = taskManager.getTaskStats(phases);
|
|
130
|
+
await configManager.update(configPath, { stats: updatedStats });
|
|
131
|
+
console.log(chalk_1.default.green('✓ Configuration updated\n'));
|
|
132
|
+
// Show progress
|
|
133
|
+
console.log(chalk_1.default.bold('Progress:'));
|
|
134
|
+
console.log(chalk_1.default.cyan(` Completed: ${updatedStats.completed}/${updatedStats.total} tasks (${updatedStats.percentage.toFixed(0)}%)`));
|
|
135
|
+
console.log(chalk_1.default.cyan(` Remaining: ${updatedStats.remaining} tasks\n`));
|
|
136
|
+
// Create git commit if enabled
|
|
137
|
+
if (!flags['no-git'] && config.commitStrategy !== 'none') {
|
|
138
|
+
await this.handleGitCommit(gitManager, configManager, configPath, config.commitStrategy, task, phases);
|
|
139
|
+
}
|
|
140
|
+
// Show next task
|
|
141
|
+
await this.showNextTask(taskManager, phases);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
|
145
|
+
console.log(chalk_1.default.red(`\n✗ Error: ${errorMessage}\n`));
|
|
146
|
+
this.error(errorMessage);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Find config file (auto-discover or use provided path)
|
|
151
|
+
*/
|
|
152
|
+
async findConfigFile(providedPath) {
|
|
153
|
+
if (providedPath) {
|
|
154
|
+
if (await fs.pathExists(providedPath)) {
|
|
155
|
+
return providedPath;
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Config file not found: ${providedPath}`);
|
|
158
|
+
}
|
|
159
|
+
// Auto-discover: Look for .clavix/outputs/*/.clavix-implement-config.json
|
|
160
|
+
const outputsDir = path.join(process.cwd(), '.clavix', 'outputs');
|
|
161
|
+
if (!(await fs.pathExists(outputsDir))) {
|
|
162
|
+
throw new Error('No .clavix/outputs directory found.\n\nHint: Run "clavix implement" first to initialize');
|
|
163
|
+
}
|
|
164
|
+
// Search for config files
|
|
165
|
+
const entries = await fs.readdir(outputsDir, { withFileTypes: true });
|
|
166
|
+
const configFiles = [];
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (!entry.isDirectory() || entry.name === 'archive') {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const configPath = path.join(outputsDir, entry.name, '.clavix-implement-config.json');
|
|
172
|
+
if (await fs.pathExists(configPath)) {
|
|
173
|
+
configFiles.push(configPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (configFiles.length === 0) {
|
|
177
|
+
throw new Error('No config files found.\n\nHint: Run "clavix implement" first to initialize');
|
|
178
|
+
}
|
|
179
|
+
// Use most recent config
|
|
180
|
+
const configsWithStats = await Promise.all(configFiles.map(async (filePath) => {
|
|
181
|
+
const stat = await fs.stat(filePath);
|
|
182
|
+
return { path: filePath, mtime: stat.mtime };
|
|
183
|
+
}));
|
|
184
|
+
configsWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
185
|
+
return configsWithStats[0].path;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Handle git commit based on strategy
|
|
189
|
+
*/
|
|
190
|
+
async handleGitCommit(gitManager, configManager, configPath, strategy, completedTask, phases) {
|
|
191
|
+
console.log(chalk_1.default.dim(`Checking git commit strategy (${strategy})...`));
|
|
192
|
+
const config = await configManager.read(configPath);
|
|
193
|
+
const completedCount = config.completedTaskIds?.length ?? 0;
|
|
194
|
+
let shouldCommit = false;
|
|
195
|
+
let commitMessage = '';
|
|
196
|
+
switch (strategy) {
|
|
197
|
+
case 'per-task':
|
|
198
|
+
shouldCommit = true;
|
|
199
|
+
commitMessage = `clavix: ${completedTask.description}`;
|
|
200
|
+
break;
|
|
201
|
+
case 'per-5-tasks':
|
|
202
|
+
shouldCommit = completedCount % 5 === 0;
|
|
203
|
+
if (shouldCommit) {
|
|
204
|
+
const last5 = config.completedTaskIds?.slice(-5) ?? [];
|
|
205
|
+
const taskDescriptions = phases
|
|
206
|
+
.flatMap(p => p.tasks)
|
|
207
|
+
.filter((t) => last5.includes(t.id))
|
|
208
|
+
.map((t) => t.description);
|
|
209
|
+
commitMessage = `clavix: Completed ${last5.length} tasks\n\nCompleted tasks:\n${taskDescriptions.map(d => `- ${d}`).join('\n')}`;
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'per-phase':
|
|
213
|
+
// Check if current phase is complete
|
|
214
|
+
const currentPhase = phases.find(p => p.name === completedTask.phase);
|
|
215
|
+
if (currentPhase) {
|
|
216
|
+
const phaseComplete = currentPhase.tasks.every((t) => t.completed);
|
|
217
|
+
shouldCommit = phaseComplete;
|
|
218
|
+
if (shouldCommit) {
|
|
219
|
+
commitMessage = `clavix: Completed ${currentPhase.name}\n\nCompleted tasks:\n${currentPhase.tasks.map((t) => `- ${t.description}`).join('\n')}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
case 'none':
|
|
224
|
+
default:
|
|
225
|
+
shouldCommit = false;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
if (!shouldCommit) {
|
|
229
|
+
console.log(chalk_1.default.dim('No commit needed (strategy criteria not met)\n'));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Check git status
|
|
233
|
+
const gitStatus = await gitManager.validateGitSetup();
|
|
234
|
+
if (!gitStatus.isRepo) {
|
|
235
|
+
console.log(chalk_1.default.yellow('⚠ Not a git repository - skipping commit\n'));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!gitStatus.hasChanges) {
|
|
239
|
+
console.log(chalk_1.default.dim('No git changes to commit\n'));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Create commit
|
|
243
|
+
console.log(chalk_1.default.dim('Creating git commit...'));
|
|
244
|
+
const fullMessage = `${commitMessage}\n\nGenerated by Clavix task-complete`;
|
|
245
|
+
const success = await gitManager.createCommit({
|
|
246
|
+
message: fullMessage,
|
|
247
|
+
description: completedTask.description,
|
|
248
|
+
projectName: path.basename(path.dirname(configPath)),
|
|
249
|
+
});
|
|
250
|
+
if (success) {
|
|
251
|
+
console.log(chalk_1.default.green('✓ Git commit created\n'));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
console.log(chalk_1.default.yellow('⚠ Git commit failed (non-critical)\n'));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Show next incomplete task
|
|
259
|
+
*/
|
|
260
|
+
async showNextTask(taskManager, phases) {
|
|
261
|
+
const nextTask = taskManager.findFirstIncompleteTask(phases);
|
|
262
|
+
if (!nextTask) {
|
|
263
|
+
console.log(chalk_1.default.bold.green('🎉 All tasks completed!\n'));
|
|
264
|
+
console.log(chalk_1.default.gray('Great work! All implementation tasks are done.\n'));
|
|
265
|
+
console.log(chalk_1.default.dim('Hint: Run "clavix archive" to archive this project\n'));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
console.log(chalk_1.default.bold('Next Task:'));
|
|
269
|
+
console.log(chalk_1.default.bold.white(` ID: ${chalk_1.default.cyan(nextTask.id)}`));
|
|
270
|
+
console.log(chalk_1.default.bold.white(` ${nextTask.description}`));
|
|
271
|
+
if (nextTask.prdReference) {
|
|
272
|
+
console.log(chalk_1.default.dim(` Reference: ${nextTask.prdReference}`));
|
|
273
|
+
}
|
|
274
|
+
console.log(chalk_1.default.dim(` Phase: ${nextTask.phase}\n`));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
TaskComplete.description = 'Mark a task as completed with validation and optional git commit';
|
|
278
|
+
TaskComplete.examples = [
|
|
279
|
+
'<%= config.bin %> <%= command.id %> phase-1-auth-1',
|
|
280
|
+
'<%= config.bin %> <%= command.id %> phase-2-api-3 --no-git',
|
|
281
|
+
'<%= config.bin %> <%= command.id %> setup-1 --force',
|
|
282
|
+
];
|
|
283
|
+
TaskComplete.args = {
|
|
284
|
+
taskId: core_1.Args.string({
|
|
285
|
+
description: 'Task ID to mark as completed',
|
|
286
|
+
required: true,
|
|
287
|
+
}),
|
|
288
|
+
};
|
|
289
|
+
TaskComplete.flags = {
|
|
290
|
+
'no-git': core_1.Flags.boolean({
|
|
291
|
+
description: 'Skip git commit even if strategy is enabled',
|
|
292
|
+
default: false,
|
|
293
|
+
}),
|
|
294
|
+
force: core_1.Flags.boolean({
|
|
295
|
+
char: 'f',
|
|
296
|
+
description: 'Force completion even if already marked complete',
|
|
297
|
+
default: false,
|
|
298
|
+
}),
|
|
299
|
+
config: core_1.Flags.string({
|
|
300
|
+
char: 'c',
|
|
301
|
+
description: 'Path to config file (defaults to auto-discover)',
|
|
302
|
+
}),
|
|
303
|
+
};
|
|
304
|
+
exports.default = TaskComplete;
|
|
305
|
+
//# sourceMappingURL=task-complete.js.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConfigManager - Manages .clavix-implement-config.json
|
|
3
|
+
*
|
|
4
|
+
* This class handles:
|
|
5
|
+
* - Reading/writing implementation configuration
|
|
6
|
+
* - Tracking task completion state
|
|
7
|
+
* - Managing resume checkpoints
|
|
8
|
+
* - Storing git commit strategy preferences
|
|
9
|
+
*/
|
|
10
|
+
import { Task } from './task-manager';
|
|
11
|
+
import { CommitStrategy } from './git-manager';
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for task implementation
|
|
14
|
+
*/
|
|
15
|
+
export interface ImplementConfig {
|
|
16
|
+
/** Git commit strategy */
|
|
17
|
+
commitStrategy: CommitStrategy;
|
|
18
|
+
/** Path to tasks.md file */
|
|
19
|
+
tasksPath: string;
|
|
20
|
+
/** Current task being worked on */
|
|
21
|
+
currentTask: Task;
|
|
22
|
+
/** Overall task statistics */
|
|
23
|
+
stats: {
|
|
24
|
+
total: number;
|
|
25
|
+
completed: number;
|
|
26
|
+
remaining: number;
|
|
27
|
+
percentage: number;
|
|
28
|
+
};
|
|
29
|
+
/** Timestamp when config was last updated */
|
|
30
|
+
timestamp: string;
|
|
31
|
+
/** ID of the last completed task */
|
|
32
|
+
lastCompletedTaskId?: string;
|
|
33
|
+
/** Array of all completed task IDs (for validation and resume) */
|
|
34
|
+
completedTaskIds?: string[];
|
|
35
|
+
/** Timestamps for each task completion (for tracking progress) */
|
|
36
|
+
completionTimestamps?: Record<string, string>;
|
|
37
|
+
/** Array of blocked task IDs with reasons */
|
|
38
|
+
blockedTasks?: Array<{
|
|
39
|
+
taskId: string;
|
|
40
|
+
reason: string;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Resume checkpoint for interrupted sessions */
|
|
44
|
+
resumeCheckpoint?: {
|
|
45
|
+
lastTaskId: string;
|
|
46
|
+
phaseProgress: Record<string, number>;
|
|
47
|
+
sessionStartTime: string;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Options for updating config
|
|
52
|
+
*/
|
|
53
|
+
export interface ConfigUpdateOptions {
|
|
54
|
+
/** Merge with existing config (true) or overwrite (false) */
|
|
55
|
+
merge?: boolean;
|
|
56
|
+
/** Validate config structure before writing */
|
|
57
|
+
validate?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* ConfigManager class
|
|
61
|
+
*
|
|
62
|
+
* Manages implementation configuration and task tracking state
|
|
63
|
+
*/
|
|
64
|
+
export declare class ConfigManager {
|
|
65
|
+
/**
|
|
66
|
+
* Read implementation config from file
|
|
67
|
+
* @param configPath - Path to config file (.clavix-implement-config.json)
|
|
68
|
+
* @returns Implementation configuration
|
|
69
|
+
*/
|
|
70
|
+
read(configPath: string): Promise<ImplementConfig>;
|
|
71
|
+
/**
|
|
72
|
+
* Write implementation config to file
|
|
73
|
+
* @param configPath - Path to config file
|
|
74
|
+
* @param config - Configuration to write
|
|
75
|
+
* @param options - Update options
|
|
76
|
+
*/
|
|
77
|
+
write(configPath: string, config: ImplementConfig, options?: ConfigUpdateOptions): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Update specific fields in config
|
|
80
|
+
* @param configPath - Path to config file
|
|
81
|
+
* @param updates - Partial config updates
|
|
82
|
+
*/
|
|
83
|
+
update(configPath: string, updates: Partial<ImplementConfig>): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Track a task completion
|
|
86
|
+
* @param configPath - Path to config file
|
|
87
|
+
* @param taskId - ID of completed task
|
|
88
|
+
*/
|
|
89
|
+
trackCompletion(configPath: string, taskId: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Add a blocked task
|
|
92
|
+
* @param configPath - Path to config file
|
|
93
|
+
* @param taskId - ID of blocked task
|
|
94
|
+
* @param reason - Reason for blocking
|
|
95
|
+
*/
|
|
96
|
+
addBlockedTask(configPath: string, taskId: string, reason: string): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Remove a blocked task (unblock)
|
|
99
|
+
* @param configPath - Path to config file
|
|
100
|
+
* @param taskId - ID of task to unblock
|
|
101
|
+
*/
|
|
102
|
+
removeBlockedTask(configPath: string, taskId: string): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Get current implementation state
|
|
105
|
+
* @param configPath - Path to config file
|
|
106
|
+
* @returns State summary
|
|
107
|
+
*/
|
|
108
|
+
getState(configPath: string): Promise<{
|
|
109
|
+
currentTaskId: string;
|
|
110
|
+
completedCount: number;
|
|
111
|
+
remainingCount: number;
|
|
112
|
+
blockedCount: number;
|
|
113
|
+
lastCompletedTaskId?: string;
|
|
114
|
+
lastCompletionTime?: string;
|
|
115
|
+
}>;
|
|
116
|
+
/**
|
|
117
|
+
* Check if a task has been completed
|
|
118
|
+
* @param configPath - Path to config file
|
|
119
|
+
* @param taskId - Task ID to check
|
|
120
|
+
* @returns true if task is in completed list
|
|
121
|
+
*/
|
|
122
|
+
isTaskCompleted(configPath: string, taskId: string): Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Validate config structure
|
|
125
|
+
* @param config - Configuration to validate
|
|
126
|
+
* @throws Error if config is invalid
|
|
127
|
+
*/
|
|
128
|
+
private validateConfig;
|
|
129
|
+
/**
|
|
130
|
+
* Migrate old config format to new format
|
|
131
|
+
* @param config - Old config format
|
|
132
|
+
* @returns Migrated config
|
|
133
|
+
*/
|
|
134
|
+
private migrateConfig;
|
|
135
|
+
/**
|
|
136
|
+
* Create a resume checkpoint
|
|
137
|
+
* @param configPath - Path to config file
|
|
138
|
+
* @param currentTaskId - Current task being worked on
|
|
139
|
+
* @param phaseProgress - Progress by phase (phase name -> completed count)
|
|
140
|
+
*/
|
|
141
|
+
createResumeCheckpoint(configPath: string, currentTaskId: string, phaseProgress: Record<string, number>): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Get the config file path for a PRD directory
|
|
144
|
+
* @param prdPath - Path to PRD directory
|
|
145
|
+
* @returns Path to config file
|
|
146
|
+
*/
|
|
147
|
+
static getConfigPath(prdPath: string): string;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=config-manager.d.ts.map
|