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.
Files changed (76) hide show
  1. package/bin/clavix.js +20 -1
  2. package/dist/cli/commands/implement.js +4 -8
  3. package/dist/cli/commands/plan.js +14 -10
  4. package/dist/cli/commands/prd.js +1 -1
  5. package/dist/cli/commands/start.js +1 -1
  6. package/dist/cli/commands/summarize.js +9 -12
  7. package/dist/cli/commands/task-complete.d.ts +27 -0
  8. package/dist/cli/commands/task-complete.js +305 -0
  9. package/dist/core/config-manager.d.ts +149 -0
  10. package/dist/core/config-manager.js +267 -0
  11. package/dist/core/git-manager.d.ts +27 -1
  12. package/dist/core/git-manager.js +114 -10
  13. package/dist/core/task-manager.d.ts +31 -0
  14. package/dist/core/task-manager.js +142 -0
  15. package/dist/index.js +4 -0
  16. package/dist/templates/slash-commands/_canonical/implement.md +86 -33
  17. package/dist/utils/template-loader.d.ts +1 -1
  18. package/dist/utils/template-loader.js +1 -1
  19. package/package.json +1 -1
  20. /package/dist/{core 2/adapters → core/adapters 2}/agents-md-generator.d.ts +0 -0
  21. /package/dist/{core 2/adapters → core/adapters 2}/agents-md-generator.js +0 -0
  22. /package/dist/{core 2/adapters → core/adapters 2}/amp-adapter.d.ts +0 -0
  23. /package/dist/{core 2/adapters → core/adapters 2}/amp-adapter.js +0 -0
  24. /package/dist/{core 2/adapters → core/adapters 2}/augment-adapter.d.ts +0 -0
  25. /package/dist/{core 2/adapters → core/adapters 2}/augment-adapter.js +0 -0
  26. /package/dist/{core 2/adapters → core/adapters 2}/base-adapter.d.ts +0 -0
  27. /package/dist/{core 2/adapters → core/adapters 2}/base-adapter.js +0 -0
  28. /package/dist/{core 2/adapters → core/adapters 2}/claude-code-adapter.d.ts +0 -0
  29. /package/dist/{core 2/adapters → core/adapters 2}/claude-code-adapter.js +0 -0
  30. /package/dist/{core 2/adapters → core/adapters 2}/cline-adapter.d.ts +0 -0
  31. /package/dist/{core 2/adapters → core/adapters 2}/cline-adapter.js +0 -0
  32. /package/dist/{core 2/adapters → core/adapters 2}/codebuddy-adapter.d.ts +0 -0
  33. /package/dist/{core 2/adapters → core/adapters 2}/codebuddy-adapter.js +0 -0
  34. /package/dist/{core 2/adapters → core/adapters 2}/codex-adapter.d.ts +0 -0
  35. /package/dist/{core 2/adapters → core/adapters 2}/codex-adapter.js +0 -0
  36. /package/dist/{core 2/adapters → core/adapters 2}/copilot-instructions-generator.d.ts +0 -0
  37. /package/dist/{core 2/adapters → core/adapters 2}/copilot-instructions-generator.js +0 -0
  38. /package/dist/{core 2/adapters → core/adapters 2}/crush-adapter.d.ts +0 -0
  39. /package/dist/{core 2/adapters → core/adapters 2}/crush-adapter.js +0 -0
  40. /package/dist/{core 2/adapters → core/adapters 2}/cursor-adapter.d.ts +0 -0
  41. /package/dist/{core 2/adapters → core/adapters 2}/cursor-adapter.js +0 -0
  42. /package/dist/{core 2/adapters → core/adapters 2}/droid-adapter.d.ts +0 -0
  43. /package/dist/{core 2/adapters → core/adapters 2}/droid-adapter.js +0 -0
  44. /package/dist/{core 2/adapters → core/adapters 2}/gemini-adapter.d.ts +0 -0
  45. /package/dist/{core 2/adapters → core/adapters 2}/gemini-adapter.js +0 -0
  46. /package/dist/{core 2/adapters → core/adapters 2}/kilocode-adapter.d.ts +0 -0
  47. /package/dist/{core 2/adapters → core/adapters 2}/kilocode-adapter.js +0 -0
  48. /package/dist/{core 2/adapters → core/adapters 2}/octo-md-generator.d.ts +0 -0
  49. /package/dist/{core 2/adapters → core/adapters 2}/octo-md-generator.js +0 -0
  50. /package/dist/{core 2/adapters → core/adapters 2}/opencode-adapter.d.ts +0 -0
  51. /package/dist/{core 2/adapters → core/adapters 2}/opencode-adapter.js +0 -0
  52. /package/dist/{core 2/adapters → core/adapters 2}/qwen-adapter.d.ts +0 -0
  53. /package/dist/{core 2/adapters → core/adapters 2}/qwen-adapter.js +0 -0
  54. /package/dist/{core 2/adapters → core/adapters 2}/roocode-adapter.d.ts +0 -0
  55. /package/dist/{core 2/adapters → core/adapters 2}/roocode-adapter.js +0 -0
  56. /package/dist/{core 2/adapters → core/adapters 2}/warp-md-generator.d.ts +0 -0
  57. /package/dist/{core 2/adapters → core/adapters 2}/warp-md-generator.js +0 -0
  58. /package/dist/{core 2/adapters → core/adapters 2}/windsurf-adapter.d.ts +0 -0
  59. /package/dist/{core 2/adapters → core/adapters 2}/windsurf-adapter.js +0 -0
  60. /package/dist/{core 2/agent-manager.js → core/agent-manager 2.js} +0 -0
  61. /package/dist/{core 2/agent-manager.d.ts → core/agent-manager.d 2.ts} +0 -0
  62. /package/dist/{core 2/archive-manager.js → core/archive-manager 2.js} +0 -0
  63. /package/dist/{core 2/archive-manager.d.ts → core/archive-manager.d 2.ts} +0 -0
  64. /package/dist/{core 2/conversation-analyzer.d.ts → core/conversation-analyzer.d 2.ts} +0 -0
  65. /package/dist/{core 2/doc-injector.js → core/doc-injector 2.js} +0 -0
  66. /package/dist/{core 2/doc-injector.d.ts → core/doc-injector.d 2.ts} +0 -0
  67. /package/dist/{core 2/git-manager.js → core/git-manager 2.js} +0 -0
  68. /package/dist/{core 2/git-manager.d.ts → core/git-manager.d 2.ts} +0 -0
  69. /package/dist/{core 2/prompt-optimizer.js → core/prompt-optimizer 2.js} +0 -0
  70. /package/dist/{core 2/prompt-optimizer.d.ts → core/prompt-optimizer.d 2.ts} +0 -0
  71. /package/dist/{core 2/question-engine.js → core/question-engine 2.js} +0 -0
  72. /package/dist/{core 2/question-engine.d.ts → core/question-engine.d 2.ts} +0 -0
  73. /package/dist/{core 2/session-manager.js → core/session-manager 2.js} +0 -0
  74. /package/dist/{core 2/session-manager.d.ts → core/session-manager.d 2.ts} +0 -0
  75. /package/dist/{core 2/task-manager.js → core/task-manager 2.js} +0 -0
  76. /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(require('@oclif/core/handle'));
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(chalk_1.default.red('No tasks.md found!') +
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(chalk_1.default.red(errorMessage));
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(chalk_1.default.yellow('Warning: No PRD projects found in .clavix/outputs/') +
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(chalk_1.default.red(`Running clavix plan failed: ${errorMessage}`));
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(chalk_1.default.red('No PRD projects found in .clavix/outputs/') +
73
- '\n\n' +
74
- chalk_1.default.gray("Hint: Run 'clavix prd' to generate a PRD, 'clavix summarize' to create a mini-PRD, or use 'clavix plan --session <id>'"));
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(chalk_1.default.red('No PRD artifacts found in this directory') +
95
- '\n\n' +
96
- chalk_1.default.gray('Hint: Generate a PRD with clavix prd, run clavix summarize, or supply a session via --session'));
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(chalk_1.default.red(`Preferred source "${sourcePreference}" not found in ${prdPath}`) +
100
- '\n\n' +
101
- chalk_1.default.gray(`Hint: Available sources: ${availableSources.join(', ') || 'none'}. Override with --source`));
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(chalk_1.default.red(errorMessage));
166
+ this.error(errorMessage);
163
167
  }
164
168
  }
165
169
  validateSessionFlags(session, activeSession) {
@@ -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(chalk_1.default.red(errorMessage));
176
+ this.error(errorMessage);
177
177
  }
178
178
  }
179
179
  /**
@@ -72,7 +72,7 @@ class Start extends core_1.Command {
72
72
  }
73
73
  catch (error) {
74
74
  const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
75
- this.error(chalk_1.default.red(errorMessage));
75
+ this.error(errorMessage);
76
76
  }
77
77
  }
78
78
  /**
@@ -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(chalk_1.default.red(`Session not found: ${args.sessionId}`));
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(chalk_1.default.red('No active session found') +
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(chalk_1.default.yellow('Warning: No active session found') +
76
- '\n\n' +
77
- chalk_1.default.gray('Usage:') +
78
- '\n' + chalk_1.default.gray('') + chalk_1.default.cyan('clavix summarize <session-id>') + chalk_1.default.gray(' - Summarize specific session') +
79
- '\n' + chalk_1.default.gray('') + chalk_1.default.cyan('clavix summarize --active') + chalk_1.default.gray(' - Summarize most recent active session') +
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(chalk_1.default.yellow('Warning: Session has no messages to analyze'));
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(chalk_1.default.red(errorMessage));
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