task-o-matic 0.0.7 → 0.0.9

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 (78) hide show
  1. package/README.md +286 -23
  2. package/dist/commands/benchmark.d.ts +3 -0
  3. package/dist/commands/benchmark.d.ts.map +1 -0
  4. package/dist/commands/benchmark.js +569 -0
  5. package/dist/commands/prd.d.ts.map +1 -1
  6. package/dist/commands/prd.js +203 -9
  7. package/dist/commands/tasks/execute-loop.d.ts +3 -0
  8. package/dist/commands/tasks/execute-loop.d.ts.map +1 -0
  9. package/dist/commands/tasks/execute-loop.js +118 -0
  10. package/dist/commands/tasks/index.d.ts +1 -0
  11. package/dist/commands/tasks/index.d.ts.map +1 -1
  12. package/dist/commands/tasks/index.js +1 -0
  13. package/dist/commands/tasks.d.ts.map +1 -1
  14. package/dist/commands/tasks.js +1 -0
  15. package/dist/commands/workflow.d.ts.map +1 -1
  16. package/dist/commands/workflow.js +491 -331
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -0
  19. package/dist/lib/ai-service/ai-operations.d.ts +5 -0
  20. package/dist/lib/ai-service/ai-operations.d.ts.map +1 -1
  21. package/dist/lib/ai-service/ai-operations.js +167 -0
  22. package/dist/lib/benchmark/registry.d.ts +11 -0
  23. package/dist/lib/benchmark/registry.d.ts.map +1 -0
  24. package/dist/lib/benchmark/registry.js +89 -0
  25. package/dist/lib/benchmark/runner.d.ts +6 -0
  26. package/dist/lib/benchmark/runner.d.ts.map +1 -0
  27. package/dist/lib/benchmark/runner.js +150 -0
  28. package/dist/lib/benchmark/storage.d.ts +13 -0
  29. package/dist/lib/benchmark/storage.d.ts.map +1 -0
  30. package/dist/lib/benchmark/storage.js +99 -0
  31. package/dist/lib/benchmark/types.d.ts +104 -0
  32. package/dist/lib/benchmark/types.d.ts.map +1 -0
  33. package/dist/lib/benchmark/types.js +2 -0
  34. package/dist/lib/index.d.ts +9 -0
  35. package/dist/lib/index.d.ts.map +1 -1
  36. package/dist/lib/index.js +7 -1
  37. package/dist/lib/prompt-registry.d.ts.map +1 -1
  38. package/dist/lib/prompt-registry.js +23 -0
  39. package/dist/lib/task-loop-execution.d.ts +25 -0
  40. package/dist/lib/task-loop-execution.d.ts.map +1 -0
  41. package/dist/lib/task-loop-execution.js +473 -0
  42. package/dist/prompts/index.d.ts +7 -6
  43. package/dist/prompts/index.d.ts.map +1 -1
  44. package/dist/prompts/index.js +1 -0
  45. package/dist/prompts/prd-question.d.ts +3 -0
  46. package/dist/prompts/prd-question.d.ts.map +1 -0
  47. package/dist/prompts/prd-question.js +40 -0
  48. package/dist/services/benchmark.d.ts +12 -0
  49. package/dist/services/benchmark.d.ts.map +1 -0
  50. package/dist/services/benchmark.js +18 -0
  51. package/dist/services/prd.d.ts +25 -0
  52. package/dist/services/prd.d.ts.map +1 -1
  53. package/dist/services/prd.js +224 -29
  54. package/dist/services/tasks.d.ts.map +1 -1
  55. package/dist/services/tasks.js +90 -3
  56. package/dist/services/workflow-benchmark.d.ts +34 -0
  57. package/dist/services/workflow-benchmark.d.ts.map +1 -0
  58. package/dist/services/workflow-benchmark.js +317 -0
  59. package/dist/services/workflow.d.ts +85 -0
  60. package/dist/services/workflow.d.ts.map +1 -0
  61. package/dist/services/workflow.js +476 -0
  62. package/dist/test/task-loop-git.test.d.ts +2 -0
  63. package/dist/test/task-loop-git.test.d.ts.map +1 -0
  64. package/dist/test/task-loop-git.test.js +62 -0
  65. package/dist/types/index.d.ts +53 -0
  66. package/dist/types/index.d.ts.map +1 -1
  67. package/dist/types/options.d.ts +2 -1
  68. package/dist/types/options.d.ts.map +1 -1
  69. package/dist/types/options.js +16 -0
  70. package/dist/types/results.d.ts +29 -1
  71. package/dist/types/results.d.ts.map +1 -1
  72. package/dist/types/workflow-options.d.ts +45 -0
  73. package/dist/types/workflow-options.d.ts.map +1 -0
  74. package/dist/types/workflow-options.js +2 -0
  75. package/dist/types/workflow-results.d.ts +82 -0
  76. package/dist/types/workflow-results.d.ts.map +1 -0
  77. package/dist/types/workflow-results.js +2 -0
  78. package/package.json +1 -1
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
4
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
38
  };
@@ -9,27 +42,84 @@ const commander_1 = require("commander");
9
42
  const chalk_1 = __importDefault(require("chalk"));
10
43
  const fs_1 = require("fs");
11
44
  const path_1 = require("path");
12
- const config_1 = require("../lib/config");
13
- const better_t_stack_cli_1 = require("../lib/better-t-stack-cli");
45
+ const workflow_1 = require("../services/workflow");
14
46
  const prd_1 = require("../services/prd");
15
- const tasks_1 = require("../services/tasks");
16
- const workflow_ai_assistant_1 = require("../services/workflow-ai-assistant");
47
+ const inquirer_1 = __importDefault(require("inquirer"));
17
48
  const workflow_prompts_1 = require("../utils/workflow-prompts");
18
49
  const streaming_options_1 = require("../utils/streaming-options");
19
50
  const progress_1 = require("../cli/display/progress");
20
51
  exports.workflowCommand = new commander_1.Command("workflow")
21
52
  .description("Interactive workflow for complete project setup and task management")
53
+ // Existing AI options
22
54
  .option("--stream", "Show streaming AI output")
23
55
  .option("--ai-provider <provider>", "AI provider override")
24
56
  .option("--ai-model <model>", "AI model override")
25
57
  .option("--ai-key <key>", "AI API key override")
26
- .action(async (options) => {
58
+ .option("--ai-provider-url <url>", "AI provider URL override")
59
+ // Global workflow control
60
+ .option("--skip-all", "Skip all optional steps (use defaults)")
61
+ .option("--auto-accept", "Auto-accept all AI suggestions")
62
+ .option("--config-file <path>", "Load workflow options from JSON file")
63
+ // Step 1: Initialize
64
+ .option("--skip-init", "Skip initialization step")
65
+ .option("--project-name <name>", "Project name")
66
+ .option("--init-method <method>", "Initialization method: quick, custom, ai")
67
+ .option("--project-description <desc>", "Project description for AI-assisted init")
68
+ .option("--use-existing-config", "Use existing configuration if found")
69
+ .option("--frontend <framework>", "Frontend framework")
70
+ .option("--backend <framework>", "Backend framework")
71
+ .option("--database <db>", "Database choice")
72
+ .option("--auth", "Include authentication")
73
+ .option("--no-auth", "Exclude authentication")
74
+ .option("--bootstrap", "Bootstrap with Better-T-Stack")
75
+ .option("--no-bootstrap", "Skip bootstrapping")
76
+ // Step 2: Define PRD
77
+ .option("--skip-prd", "Skip PRD definition")
78
+ .option("--prd-method <method>", "PRD method: upload, manual, ai, skip")
79
+ .option("--prd-file <path>", "Path to existing PRD file")
80
+ .option("--prd-description <desc>", "Product description for AI-assisted PRD")
81
+ .option("--prd-content <content>", "Direct PRD content")
82
+ // Step 2.5: PRD Question/Refine (NEW)
83
+ .option("--skip-prd-question-refine", "Skip PRD question/refine step")
84
+ .option("--prd-question-refine", "Use question-based PRD refinement")
85
+ .option("--prd-answer-mode <mode>", "Who answers questions: user, ai")
86
+ .option("--prd-answer-ai-provider <provider>", "AI provider for answering (optional override)")
87
+ .option("--prd-answer-ai-model <model>", "AI model for answering (optional override)")
88
+ .option("--prd-answer-ai-reasoning", "Enable reasoning for AI answering model (if supported)")
89
+ // Step 3: Refine PRD
90
+ .option("--skip-refine", "Skip PRD refinement")
91
+ .option("--refine-method <method>", "Refinement method: manual, ai, skip")
92
+ .option("--refine-feedback <feedback>", "Feedback for AI refinement")
93
+ // Step 4: Generate Tasks
94
+ .option("--skip-generate", "Skip task generation")
95
+ .option("--generate-method <method>", "Generation method: standard, ai")
96
+ .option("--generate-instructions <instructions>", "Custom task generation instructions")
97
+ // Step 5: Split Tasks
98
+ .option("--skip-split", "Skip task splitting")
99
+ .option("--split-tasks <ids>", "Comma-separated task IDs to split")
100
+ .option("--split-all", "Split all tasks")
101
+ .option("--split-method <method>", "Split method: interactive, standard, custom")
102
+ .option("--split-instructions <instructions>", "Custom split instructions")
103
+ .action(async (cliOptions) => {
27
104
  try {
105
+ // Load and merge options from config file if specified
106
+ const options = await loadWorkflowOptions(cliOptions);
28
107
  console.log(chalk_1.default.blue.bold("\nšŸš€ Task-O-Matic Interactive Workflow\n"));
108
+ // Show automation status
109
+ if (options.configFile) {
110
+ console.log(chalk_1.default.cyan(`šŸ“‹ Using config: ${options.configFile}`));
111
+ }
112
+ if (options.skipAll) {
113
+ console.log(chalk_1.default.yellow("⚔ Fast mode: Skipping all optional steps"));
114
+ }
115
+ if (options.autoAccept) {
116
+ console.log(chalk_1.default.yellow("āœ“ Auto-accepting all AI suggestions"));
117
+ }
29
118
  console.log(chalk_1.default.gray("This wizard will guide you through:"));
30
119
  console.log(chalk_1.default.gray(" 1. Project initialization & bootstrap"));
31
120
  console.log(chalk_1.default.gray(" 2. PRD definition"));
32
- console.log(chalk_1.default.gray(" 3. PRD refinement"));
121
+ console.log(chalk_1.default.gray(" 2.5. PRD question & refinement (optional)"));
122
+ console.log(chalk_1.default.gray(" 3. PRD manual refinement (optional)"));
33
123
  console.log(chalk_1.default.gray(" 4. Task generation"));
34
124
  console.log(chalk_1.default.gray(" 5. Task splitting\n"));
35
125
  const state = {
@@ -37,23 +127,19 @@ exports.workflowCommand = new commander_1.Command("workflow")
37
127
  currentStep: "initialize",
38
128
  projectDir: process.cwd(),
39
129
  };
40
- // Store AI options for later use
41
- const aiOptions = {
42
- aiProvider: options.aiProvider,
43
- aiModel: options.aiModel,
44
- aiKey: options.aiKey,
45
- };
46
130
  const streamingOptions = (0, streaming_options_1.createStreamingOptions)(options.stream, "Workflow");
47
131
  // Step 1: Initialize/Bootstrap
48
- await stepInitialize(state, aiOptions, streamingOptions);
132
+ await stepInitialize(state, options, streamingOptions);
49
133
  // Step 2: Define PRD
50
- await stepDefinePRD(state, aiOptions, streamingOptions);
134
+ await stepDefinePRD(state, options, streamingOptions);
135
+ // Step 2.5: PRD Question/Refine (NEW)
136
+ await stepPRDQuestionRefine(state, options, streamingOptions);
51
137
  // Step 3: Refine PRD
52
- await stepRefinePRD(state, aiOptions, streamingOptions);
138
+ await stepRefinePRD(state, options, streamingOptions);
53
139
  // Step 4: Generate Tasks
54
- await stepGenerateTasks(state, aiOptions, streamingOptions);
140
+ await stepGenerateTasks(state, options, streamingOptions);
55
141
  // Step 5: Split Tasks
56
- await stepSplitTasks(state, aiOptions, streamingOptions);
142
+ await stepSplitTasks(state, options, streamingOptions);
57
143
  // Complete
58
144
  state.currentStep = "complete";
59
145
  console.log(chalk_1.default.green.bold("\nāœ… Workflow Complete!\n"));
@@ -68,412 +154,443 @@ exports.workflowCommand = new commander_1.Command("workflow")
68
154
  process.exit(1);
69
155
  }
70
156
  });
157
+ /**
158
+ * Load and merge workflow options from config file if specified
159
+ */
160
+ async function loadWorkflowOptions(cliOptions) {
161
+ let options = { ...cliOptions };
162
+ if (cliOptions.configFile) {
163
+ try {
164
+ const configPath = (0, path_1.resolve)(cliOptions.configFile);
165
+ const { existsSync, readFileSync } = await Promise.resolve().then(() => __importStar(require("fs")));
166
+ if (existsSync(configPath)) {
167
+ const configContent = readFileSync(configPath, "utf-8");
168
+ const fileOptions = JSON.parse(configContent);
169
+ // CLI options override file options
170
+ options = { ...fileOptions, ...cliOptions };
171
+ console.log(chalk_1.default.green(`āœ“ Loaded workflow config from ${configPath}`));
172
+ }
173
+ else {
174
+ console.log(chalk_1.default.yellow(`⚠ Config file not found: ${configPath}`));
175
+ }
176
+ }
177
+ catch (error) {
178
+ console.log(chalk_1.default.red(`āœ— Failed to load config file: ${error}`));
179
+ }
180
+ }
181
+ return options;
182
+ }
183
+ /**
184
+ * Helper to get pre-answered value or prompt user
185
+ */
186
+ async function getOrPrompt(preAnswered, promptFn, skipCondition = false) {
187
+ if (skipCondition) {
188
+ throw new Error("Step skipped");
189
+ }
190
+ if (preAnswered !== undefined) {
191
+ return preAnswered;
192
+ }
193
+ return promptFn();
194
+ }
71
195
  /**
72
196
  * Step 1: Initialize/Bootstrap
197
+ * Uses workflowService.initializeProject()
73
198
  */
74
- async function stepInitialize(state, aiOptions, streamingOptions) {
199
+ async function stepInitialize(state, options, streamingOptions) {
75
200
  console.log(chalk_1.default.blue.bold("\nšŸ“¦ Step 1: Project Initialization\n"));
76
- // Check if already initialized in current directory
77
- const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
78
- const alreadyInitialized = (0, fs_1.existsSync)(taskOMaticDir);
79
- if (alreadyInitialized) {
80
- console.log(chalk_1.default.yellow("āœ“ Project already initialized"));
81
- const useExisting = await (0, workflow_prompts_1.confirmPrompt)("Use existing configuration?", true);
82
- if (useExisting) {
83
- state.initialized = true;
84
- state.currentStep = "define-prd";
85
- return;
86
- }
201
+ if (options.skipInit) {
202
+ console.log(chalk_1.default.yellow("⚠ Skipping initialization (--skip-init)"));
203
+ state.initialized = false;
204
+ state.currentStep = "define-prd";
205
+ return;
87
206
  }
88
- const shouldInitialize = await (0, workflow_prompts_1.confirmPrompt)("Initialize a new task-o-matic project?", true);
207
+ const shouldInitialize = await getOrPrompt(options.skipInit === false ? true : undefined, () => (0, workflow_prompts_1.confirmPrompt)("Initialize a new task-o-matic project?", true));
89
208
  if (!shouldInitialize) {
90
209
  console.log(chalk_1.default.yellow("⚠ Skipping initialization"));
91
210
  state.initialized = false;
92
211
  state.currentStep = "define-prd";
93
212
  return;
94
213
  }
95
- const projectName = await (0, workflow_prompts_1.textInputPrompt)("What is the name of your project?", "my-app");
96
- // IMMEDIATE DIRECTORY CREATION AND SWITCH
97
- const projectDir = (0, path_1.resolve)(process.cwd(), projectName);
98
- if (!(0, fs_1.existsSync)(projectDir)) {
99
- (0, fs_1.mkdirSync)(projectDir, { recursive: true });
100
- console.log(chalk_1.default.green(`\nāœ“ Created directory: ${projectName}`));
101
- }
102
- else {
103
- console.log(chalk_1.default.yellow(`\n⚠ Directory ${projectName} already exists`));
104
- }
105
- console.log(chalk_1.default.cyan(` šŸ“‚ Switching to project directory: ${projectDir}\n`));
106
- process.chdir(projectDir);
107
- config_1.configManager.setWorkingDirectory(projectDir);
108
- state.projectDir = projectDir;
109
- // Initialize task-o-matic in the NEW directory
110
- console.log(chalk_1.default.cyan(" Initializing task-o-matic...\n"));
111
- const newTaskOMaticDir = (0, path_1.join)(projectDir, ".task-o-matic");
112
- if (!(0, fs_1.existsSync)(newTaskOMaticDir)) {
113
- (0, fs_1.mkdirSync)(newTaskOMaticDir, { recursive: true });
114
- ["tasks", "prd", "logs"].forEach((dir) => {
115
- (0, fs_1.mkdirSync)((0, path_1.join)(newTaskOMaticDir, dir), { recursive: true });
116
- });
117
- }
118
- // AI Configuration Step - ALWAYS ask for this first
119
- console.log(chalk_1.default.blue.bold("\nšŸ¤– Step 1.1: AI Configuration\n"));
120
- const aiProvider = await (0, workflow_prompts_1.selectPrompt)("Select AI Provider:", [
121
- { name: "OpenRouter", value: "openrouter" },
122
- { name: "Anthropic", value: "anthropic" },
123
- { name: "OpenAI", value: "openai" },
124
- { name: "Custom (e.g. local LLM)", value: "custom" },
125
- ]);
126
- let aiProviderUrl;
127
- if (aiProvider === "custom") {
128
- aiProviderUrl = await (0, workflow_prompts_1.textInputPrompt)("Enter Custom Provider URL:", "http://localhost:11434/v1");
129
- }
130
- const defaultModel = aiProvider === "openrouter"
131
- ? "anthropic/claude-3.5-sonnet"
132
- : aiProvider === "anthropic"
133
- ? "claude-3-5-sonnet-20240620"
134
- : aiProvider === "openai"
135
- ? "gpt-4o"
136
- : "llama3";
137
- const aiModel = await (0, workflow_prompts_1.textInputPrompt)("Enter AI Model:", defaultModel);
138
- // Check/Ask for API Key
139
- const providerKeyName = aiProvider === "openai"
140
- ? "OPENAI_API_KEY"
141
- : aiProvider === "anthropic"
142
- ? "ANTHROPIC_API_KEY"
143
- : aiProvider === "openrouter"
144
- ? "OPENROUTER_API_KEY"
145
- : "AI_API_KEY";
146
- // Check if key exists in current env
147
- let apiKey = process.env[providerKeyName];
148
- if (!apiKey) {
149
- console.log(chalk_1.default.yellow(`\nāš ļø No API key found for ${aiProvider}`));
150
- apiKey = await (0, workflow_prompts_1.textInputPrompt)(`Enter your ${aiProvider} API Key:`);
151
- }
152
- // Save AI Config to .env immediately in the new project dir
153
- const envPath = (0, path_1.join)(projectDir, ".env");
154
- let envContent = "";
155
- if ((0, fs_1.existsSync)(envPath)) {
156
- envContent = (0, fs_1.readFileSync)(envPath, "utf-8");
157
- }
158
- if (!envContent.includes("AI_PROVIDER=")) {
159
- envContent += `AI_PROVIDER=${aiProvider}\n`;
160
- }
161
- if (!envContent.includes("AI_MODEL=")) {
162
- envContent += `AI_MODEL=${aiModel}\n`;
163
- }
164
- if (aiProviderUrl && !envContent.includes("AI_PROVIDER_URL=")) {
165
- envContent += `AI_PROVIDER_URL=${aiProviderUrl}\n`;
166
- }
167
- if (!envContent.includes(`${providerKeyName}=`)) {
168
- envContent += `${providerKeyName}=${apiKey}\n`;
169
- }
170
- (0, fs_1.writeFileSync)(envPath, envContent);
171
- // Update process.env for immediate use
172
- process.env.AI_PROVIDER = aiProvider;
173
- process.env.AI_MODEL = aiModel;
174
- process.env[providerKeyName] = apiKey;
175
- if (aiProviderUrl) {
176
- process.env.AI_PROVIDER_URL = aiProviderUrl;
177
- }
178
- // Update ConfigManager
179
- config_1.configManager.setAIConfig({
180
- provider: aiProvider,
181
- model: aiModel,
182
- apiKey: apiKey,
183
- baseURL: aiProviderUrl,
184
- });
185
- console.log(chalk_1.default.green("āœ“ AI Configuration saved"));
186
- // Stack Configuration Step
187
- console.log(chalk_1.default.blue.bold("\nšŸ“¦ Step 1.2: Stack Configuration\n"));
188
- // Choose initialization method
189
- let initMethod = await (0, workflow_prompts_1.selectPrompt)("How would you like to configure your project stack?", [
214
+ const projectName = await getOrPrompt(options.projectName, () => (0, workflow_prompts_1.textInputPrompt)("What is the name of your project?", "my-app"));
215
+ // Determine initialization method
216
+ const initMethod = await getOrPrompt(options.initMethod, () => (0, workflow_prompts_1.selectPrompt)("How would you like to configure your project stack?", [
190
217
  { name: "Quick start (recommended defaults)", value: "quick" },
191
218
  { name: "Custom configuration", value: "custom" },
192
219
  { name: "AI-assisted (describe your project)", value: "ai" },
193
- ]);
194
- let config;
220
+ ]));
221
+ let projectDescription;
195
222
  if (initMethod === "ai") {
196
- console.log(chalk_1.default.cyan("\nšŸ¤– AI-Assisted Stack Configuration\n"));
197
- const description = await (0, workflow_prompts_1.textInputPrompt)("Describe your project (e.g., 'A SaaS app for team collaboration with real-time features'):");
198
- console.log(chalk_1.default.gray("\n Analyzing your requirements...\n"));
199
- config = await workflow_ai_assistant_1.workflowAIAssistant.assistInitConfig({
200
- userDescription: description,
201
- aiOptions: {
202
- aiProvider,
203
- aiModel,
204
- aiKey: apiKey,
205
- aiProviderUrl,
206
- },
207
- streamingOptions,
208
- });
209
- // Override AI's project name with user's choice
210
- config.projectName = projectName;
211
- // Override AI config with what we just set
212
- config.aiProvider = aiProvider;
213
- config.aiModel = aiModel;
214
- console.log(chalk_1.default.green("\nāœ“ AI Recommendations:"));
215
- console.log(chalk_1.default.gray(` Project: ${config.projectName}`));
216
- console.log(chalk_1.default.gray(` Frontend: ${config.frontend || "none"}`));
217
- console.log(chalk_1.default.gray(` Backend: ${config.backend || "none"}`));
218
- console.log(chalk_1.default.gray(` Database: ${config.database || "none"}`));
219
- console.log(chalk_1.default.gray(` Auth: ${config.auth ? "yes" : "no"}`));
220
- if (config.reasoning) {
221
- console.log(chalk_1.default.gray(`\n ${config.reasoning}\n`));
222
- }
223
- const acceptRecommendation = await (0, workflow_prompts_1.confirmPrompt)("Accept these recommendations?", true);
224
- if (!acceptRecommendation) {
225
- console.log(chalk_1.default.yellow("⚠ Falling back to custom configuration"));
226
- initMethod = "custom";
227
- }
223
+ projectDescription = await getOrPrompt(options.projectDescription, () => (0, workflow_prompts_1.textInputPrompt)("Describe your project (e.g., 'A SaaS app for team collaboration with real-time features'):"));
228
224
  }
229
- if (initMethod === "quick") {
230
- config = {
231
- projectName: projectName,
232
- aiProvider: aiProvider,
233
- aiModel: aiModel,
234
- frontend: "next",
235
- backend: "hono",
236
- database: "sqlite",
237
- auth: true,
238
- reasoning: "Modern, well-supported stack",
239
- };
240
- }
241
- else if (initMethod === "custom") {
242
- config = {
243
- projectName: projectName,
244
- aiProvider: aiProvider,
245
- aiModel: aiModel,
246
- };
247
- const shouldBootstrap = await (0, workflow_prompts_1.confirmPrompt)("Bootstrap with Better-T-Stack?", true);
225
+ // Collect stack config for custom mode
226
+ let stackConfig = {};
227
+ if (initMethod === "custom") {
228
+ const shouldBootstrap = await getOrPrompt(options.bootstrap, () => (0, workflow_prompts_1.confirmPrompt)("Bootstrap with Better-T-Stack?", true));
248
229
  if (shouldBootstrap) {
249
- config.frontend = await (0, workflow_prompts_1.selectPrompt)("Frontend framework:", [
230
+ stackConfig.frontend = await getOrPrompt(options.frontend, () => (0, workflow_prompts_1.selectPrompt)("Frontend framework:", [
250
231
  "next",
251
232
  "tanstack-router",
252
233
  "react-router",
253
234
  "vite-react",
254
235
  "remix",
255
- ]);
256
- config.backend = await (0, workflow_prompts_1.selectPrompt)("Backend framework:", [
236
+ ]));
237
+ stackConfig.backend = await getOrPrompt(options.backend, () => (0, workflow_prompts_1.selectPrompt)("Backend framework:", [
257
238
  "hono",
258
239
  "express",
259
240
  "elysia",
260
241
  "fastify",
261
- ]);
262
- config.database = await (0, workflow_prompts_1.selectPrompt)("Database:", [
242
+ ]));
243
+ stackConfig.database = await getOrPrompt(options.database, () => (0, workflow_prompts_1.selectPrompt)("Database:", [
263
244
  "sqlite",
264
245
  "postgres",
265
246
  "mysql",
266
247
  "mongodb",
267
248
  "turso",
268
249
  "neon",
269
- ]);
270
- config.auth = await (0, workflow_prompts_1.confirmPrompt)("Include authentication?", true);
250
+ ]));
251
+ stackConfig.auth = await getOrPrompt(options.auth, () => (0, workflow_prompts_1.confirmPrompt)("Include authentication?", true));
271
252
  }
272
253
  }
273
- // Bootstrap Logic
274
- if (config.frontend || config.backend) {
275
- const shouldBootstrap = await (0, workflow_prompts_1.confirmPrompt)("Bootstrap project now?", true);
276
- if (shouldBootstrap) {
277
- console.log(chalk_1.default.cyan("\n Bootstrapping with Better-T-Stack...\n"));
278
- try {
279
- // We are already in the project directory.
280
- // We pass "." as the project name so Better-T-Stack scaffolds in the current directory.
281
- const result = await (0, better_t_stack_cli_1.runBetterTStackCLI)({
282
- projectName: ".", // Force scaffolding in current dir
283
- frontend: config.frontend || "next",
284
- backend: config.backend || "hono",
285
- database: config.database || "sqlite",
286
- noAuth: !config.auth,
287
- // Default values for required fields that might be missing from simple config
288
- orm: "drizzle",
289
- packageManager: "npm",
290
- runtime: "node",
291
- noInstall: false,
292
- noGit: false,
293
- }, process.cwd());
294
- if (result.success) {
295
- console.log(chalk_1.default.green(`\nāœ“ ${result.message}\n`));
296
- // Fix up the configuration files
297
- // Because we passed ".", the config file is named ".-bts-config.json" and contains projectName: "."
298
- const dotConfigPath = (0, path_1.join)(newTaskOMaticDir, ".-bts-config.json");
299
- const realConfigPath = (0, path_1.join)(newTaskOMaticDir, `${projectName}-bts-config.json`);
300
- const stackConfigPath = (0, path_1.join)(newTaskOMaticDir, "stack.json");
301
- if ((0, fs_1.existsSync)(dotConfigPath)) {
302
- const configContent = JSON.parse((0, fs_1.readFileSync)(dotConfigPath, "utf-8"));
303
- configContent.projectName = projectName; // Fix the project name
304
- const newContent = JSON.stringify(configContent, null, 2);
305
- (0, fs_1.writeFileSync)(realConfigPath, newContent);
306
- (0, fs_1.writeFileSync)(stackConfigPath, newContent);
307
- // Remove the temporary dot config
308
- const { unlinkSync } = require("fs");
309
- unlinkSync(dotConfigPath);
310
- }
311
- }
312
- else {
313
- console.log(chalk_1.default.red(`\nāœ— Bootstrap failed: ${result.message}\n`));
314
- console.log(chalk_1.default.yellow("You can try running 'task-o-matic init bootstrap' manually later.\n"));
315
- }
316
- }
317
- catch (error) {
318
- console.log(chalk_1.default.red(`\nāœ— Bootstrap failed: ${error}\n`));
319
- }
320
- }
321
- }
322
- // Save configuration
323
- config_1.configManager.save();
254
+ // Determine if we should bootstrap
255
+ const bootstrap = initMethod === "quick" ||
256
+ (initMethod === "ai" && options.bootstrap !== false) ||
257
+ (initMethod === "custom" && Object.keys(stackConfig).length > 0);
258
+ // Call service
259
+ const result = await workflow_1.workflowService.initializeProject({
260
+ projectName,
261
+ initMethod: initMethod,
262
+ projectDescription,
263
+ aiOptions: options,
264
+ stackConfig,
265
+ bootstrap,
266
+ streamingOptions,
267
+ callbacks: {
268
+ onProgress: progress_1.displayProgress,
269
+ onError: progress_1.displayError,
270
+ },
271
+ });
324
272
  console.log(chalk_1.default.green("āœ“ Project initialized"));
325
273
  state.initialized = true;
326
- state.projectName = config.projectName;
327
- state.aiConfig = {
328
- provider: aiProvider,
329
- model: aiModel,
330
- key: apiKey,
331
- };
274
+ state.projectName = result.projectName;
275
+ state.projectDir = result.projectDir;
276
+ state.aiConfig = result.aiConfig;
332
277
  state.currentStep = "define-prd";
333
278
  }
334
279
  /**
335
280
  * Step 2: Define PRD
281
+ * Uses workflowService.definePRD()
336
282
  */
337
- async function stepDefinePRD(state, aiOptions, streamingOptions) {
283
+ async function stepDefinePRD(state, options, streamingOptions) {
338
284
  console.log(chalk_1.default.blue.bold("\nšŸ“ Step 2: Define PRD\n"));
339
- const prdMethod = await (0, workflow_prompts_1.selectPrompt)("How would you like to define your PRD?", [
285
+ if (options.skipPrd) {
286
+ console.log(chalk_1.default.yellow("⚠ Skipping PRD definition (--skip-prd)"));
287
+ state.currentStep = "refine-prd";
288
+ return;
289
+ }
290
+ const prdMethod = await getOrPrompt(options.prdMethod, () => (0, workflow_prompts_1.selectPrompt)("How would you like to define your PRD?", [
340
291
  { name: "Upload existing file", value: "upload" },
341
292
  { name: "Write manually (open editor)", value: "manual" },
342
293
  { name: "AI-assisted creation", value: "ai" },
343
294
  { name: "Skip (use existing PRD)", value: "skip" },
344
- ]);
345
- const taskOMaticDir = config_1.configManager.getTaskOMaticDir();
346
- const prdDir = (0, path_1.join)(taskOMaticDir, "prd");
295
+ ]));
347
296
  if (prdMethod === "skip") {
348
297
  console.log(chalk_1.default.yellow("⚠ Skipping PRD definition"));
349
298
  state.currentStep = "refine-prd";
350
299
  return;
351
300
  }
352
- let prdContent = "";
353
- let prdFilename = "prd.md";
301
+ let prdFile;
302
+ let prdDescription;
303
+ let prdContent;
354
304
  if (prdMethod === "upload") {
355
- const filePath = await (0, workflow_prompts_1.textInputPrompt)("Path to PRD file:");
356
- if (!(0, fs_1.existsSync)(filePath)) {
357
- console.log(chalk_1.default.red(`āœ— File not found: ${filePath}`));
358
- return stepDefinePRD(state, aiOptions, streamingOptions);
359
- }
360
- prdContent = (0, fs_1.readFileSync)(filePath, "utf-8");
361
- prdFilename = filePath.split("/").pop() || "prd.md";
305
+ prdFile = await getOrPrompt(options.prdFile, () => (0, workflow_prompts_1.textInputPrompt)("Path to PRD file:"));
362
306
  }
363
307
  else if (prdMethod === "manual") {
364
308
  console.log(chalk_1.default.gray("\n Opening editor...\n"));
365
309
  prdContent = await (0, workflow_prompts_1.editorPrompt)("Write your PRD (save and close editor when done):", "# Product Requirements Document\n\n## Overview\n\n## Objectives\n\n## Features\n\n");
366
310
  }
367
311
  else if (prdMethod === "ai") {
368
- console.log(chalk_1.default.cyan("\nšŸ¤– AI-Assisted PRD Creation\n"));
369
- const description = await (0, workflow_prompts_1.textInputPrompt)("Describe your product in detail:");
370
- console.log(chalk_1.default.gray("\n Generating PRD...\n"));
371
- prdContent = await workflow_ai_assistant_1.workflowAIAssistant.assistPRDCreation({
372
- userDescription: description,
373
- aiOptions,
374
- streamingOptions,
375
- });
312
+ prdDescription = await getOrPrompt(options.prdDescription, () => (0, workflow_prompts_1.textInputPrompt)("Describe your product in detail:"));
313
+ }
314
+ // Call service
315
+ const result = await workflow_1.workflowService.definePRD({
316
+ method: prdMethod,
317
+ prdFile,
318
+ prdDescription,
319
+ prdContent,
320
+ projectDir: state.projectDir,
321
+ aiOptions: options,
322
+ streamingOptions,
323
+ callbacks: {
324
+ onProgress: progress_1.displayProgress,
325
+ onError: progress_1.displayError,
326
+ },
327
+ });
328
+ if (prdMethod === "ai") {
376
329
  console.log(chalk_1.default.green("\nāœ“ PRD generated"));
377
- console.log(chalk_1.default.gray("\n" + prdContent.substring(0, 500) + "...\n"));
378
- const acceptPRD = await (0, workflow_prompts_1.confirmPrompt)("Accept this PRD?", true);
330
+ console.log(chalk_1.default.gray("\n" + result.prdContent.substring(0, 500) + "...\n"));
331
+ // Display metrics if available
332
+ if (result.stats) {
333
+ console.log(chalk_1.default.cyan(` Duration: ${result.stats.duration}ms`));
334
+ if (result.stats.tokenUsage) {
335
+ console.log(chalk_1.default.cyan(` Tokens: ${result.stats.tokenUsage.total} (Prompt: ${result.stats.tokenUsage.prompt}, Completion: ${result.stats.tokenUsage.completion})`));
336
+ }
337
+ if (result.stats.timeToFirstToken) {
338
+ console.log(chalk_1.default.cyan(` Time to First Token: ${result.stats.timeToFirstToken}ms`));
339
+ }
340
+ if (result.stats.cost) {
341
+ console.log(chalk_1.default.cyan(` Estimated Cost: $${result.stats.cost.toFixed(6)}`));
342
+ }
343
+ }
344
+ const acceptPRD = await getOrPrompt(options.autoAccept ? true : undefined, () => (0, workflow_prompts_1.confirmPrompt)("Accept this PRD?", true));
379
345
  if (!acceptPRD) {
380
346
  console.log(chalk_1.default.yellow("⚠ Regenerating..."));
381
- return stepDefinePRD(state, aiOptions, streamingOptions);
347
+ return stepDefinePRD(state, options, streamingOptions);
348
+ }
349
+ }
350
+ state.prdFile = result.prdFile;
351
+ state.prdContent = result.prdContent;
352
+ state.currentStep = "question-refine-prd";
353
+ }
354
+ /**
355
+ * Step 2.5: PRD Question/Refine (NEW)
356
+ * Uses prdService.refinePRDWithQuestions()
357
+ */
358
+ async function stepPRDQuestionRefine(state, options, streamingOptions) {
359
+ console.log(chalk_1.default.blue.bold("\nā“ Step 2.5: PRD Question & Refine\n"));
360
+ if (!state.prdFile) {
361
+ console.log(chalk_1.default.yellow("⚠ No PRD file found, skipping"));
362
+ state.currentStep = "refine-prd";
363
+ return;
364
+ }
365
+ if (options.skipPrdQuestionRefine || options.skipAll) {
366
+ console.log(chalk_1.default.gray(" Skipping question-based refinement"));
367
+ state.currentStep = "refine-prd";
368
+ return;
369
+ }
370
+ // Ask if user wants question-based refinement
371
+ const useQuestions = await getOrPrompt(options.prdQuestionRefine, () => (0, workflow_prompts_1.confirmPrompt)("Refine PRD with clarifying questions?", false));
372
+ if (!useQuestions) {
373
+ console.log(chalk_1.default.gray(" Skipping question-based refinement"));
374
+ state.currentStep = "refine-prd";
375
+ return;
376
+ }
377
+ // Ask who should answer: user or AI
378
+ const answerMode = await getOrPrompt(options.prdAnswerMode, () => (0, workflow_prompts_1.selectPrompt)("Who should answer the questions?", [
379
+ { name: "I will answer", value: "user" },
380
+ { name: "AI assistant (uses PRD + stack context)", value: "ai" },
381
+ ]));
382
+ let questionAIOptions = undefined;
383
+ if (answerMode === "ai") {
384
+ // Ask if they want to use a different AI model for answering
385
+ const useCustomAI = await getOrPrompt(options.prdAnswerAiProvider !== undefined, () => (0, workflow_prompts_1.confirmPrompt)("Use a different AI model for answering? (e.g., a smarter model)", false));
386
+ if (useCustomAI) {
387
+ const provider = await getOrPrompt(options.prdAnswerAiProvider, () => (0, workflow_prompts_1.selectPrompt)("AI Provider for answering:", [
388
+ { name: "OpenRouter", value: "openrouter" },
389
+ { name: "Anthropic", value: "anthropic" },
390
+ { name: "OpenAI", value: "openai" },
391
+ ]));
392
+ const model = await getOrPrompt(options.prdAnswerAiModel, () => (0, workflow_prompts_1.textInputPrompt)("AI Model for answering:", provider === "openrouter" ? "anthropic/claude-3.5-sonnet" : ""));
393
+ questionAIOptions = {
394
+ aiProvider: provider,
395
+ aiModel: model,
396
+ };
397
+ }
398
+ // Check if reasoning should be enabled
399
+ if (options.prdAnswerAiReasoning) {
400
+ if (!questionAIOptions) {
401
+ // No custom AI specified, use main AI with reasoning
402
+ questionAIOptions = {
403
+ aiProvider: options.aiProvider,
404
+ aiModel: options.aiModel,
405
+ aiReasoning: "enabled",
406
+ };
407
+ }
408
+ else {
409
+ // Custom AI specified, add reasoning to it
410
+ questionAIOptions.aiReasoning = "enabled";
411
+ }
412
+ }
413
+ }
414
+ // For user mode, we need to collect answers interactively
415
+ let answers;
416
+ if (answerMode === "user") {
417
+ // First, generate questions
418
+ console.log(chalk_1.default.cyan("\n Generating questions...\n"));
419
+ const questions = await prd_1.prdService.generateQuestions({
420
+ file: state.prdFile,
421
+ workingDirectory: state.projectDir,
422
+ aiOptions: options,
423
+ streamingOptions,
424
+ callbacks: {
425
+ onProgress: progress_1.displayProgress,
426
+ onError: progress_1.displayError,
427
+ },
428
+ });
429
+ if (questions.length === 0) {
430
+ console.log(chalk_1.default.yellow("No questions generated - PRD appears complete"));
431
+ state.currentStep = "refine-prd";
432
+ return;
433
+ }
434
+ console.log(chalk_1.default.blue(`\nPlease answer the following ${questions.length} questions to refine the PRD:\n`));
435
+ answers = {};
436
+ for (let i = 0; i < questions.length; i++) {
437
+ const q = questions[i];
438
+ const answer = await inquirer_1.default.prompt([
439
+ {
440
+ type: "input",
441
+ name: "response",
442
+ message: `${i + 1}/${questions.length}: ${q}`,
443
+ validate: (input) => input.trim().length > 0 || "Please provide an answer",
444
+ },
445
+ ]);
446
+ answers[q] = answer.response;
382
447
  }
383
448
  }
384
- // Save PRD
385
- const prdPath = (0, path_1.join)(prdDir, prdFilename);
386
- (0, fs_1.writeFileSync)(prdPath, prdContent);
387
- console.log(chalk_1.default.green(`āœ“ PRD saved to ${prdPath}`));
388
- state.prdFile = prdPath;
389
- state.prdContent = prdContent;
449
+ // Call service - it will automatically refine after answering
450
+ console.log(chalk_1.default.cyan("\n Generating questions and refining PRD...\n"));
451
+ const result = await prd_1.prdService.refinePRDWithQuestions({
452
+ file: state.prdFile,
453
+ questionMode: answerMode,
454
+ answers, // Only provided for user mode
455
+ questionAIOptions,
456
+ workingDirectory: state.projectDir,
457
+ aiOptions: options,
458
+ streamingOptions,
459
+ callbacks: {
460
+ onProgress: progress_1.displayProgress,
461
+ onError: progress_1.displayError,
462
+ },
463
+ });
464
+ console.log(chalk_1.default.green(`\nāœ“ PRD refined with ${result.questions.length} questions answered`));
465
+ console.log(chalk_1.default.gray("\n Questions & Answers:"));
466
+ result.questions.forEach((q, i) => {
467
+ console.log(chalk_1.default.cyan(` Q${i + 1}: ${q}`));
468
+ console.log(chalk_1.default.gray(` A${i + 1}: ${result.answers[q]?.substring(0, 100)}...\n`));
469
+ });
470
+ // Update state with refined PRD
471
+ state.prdFile = result.refinedPRDPath;
472
+ state.prdContent = (0, fs_1.readFileSync)(result.refinedPRDPath, "utf-8");
390
473
  state.currentStep = "refine-prd";
391
474
  }
392
475
  /**
393
476
  * Step 3: Refine PRD
477
+ * Uses workflowService.refinePRD()
394
478
  */
395
- async function stepRefinePRD(state, aiOptions, streamingOptions) {
479
+ async function stepRefinePRD(state, options, streamingOptions) {
396
480
  console.log(chalk_1.default.blue.bold("\n✨ Step 3: Refine PRD\n"));
397
481
  if (!state.prdFile) {
398
482
  console.log(chalk_1.default.yellow("⚠ No PRD file found, skipping refinement"));
399
483
  state.currentStep = "generate-tasks";
400
484
  return;
401
485
  }
402
- const shouldRefine = await (0, workflow_prompts_1.confirmPrompt)("Refine your PRD?", false);
486
+ if (options.skipRefine || options.skipAll) {
487
+ console.log(chalk_1.default.gray(" Skipping refinement"));
488
+ state.currentStep = "generate-tasks";
489
+ return;
490
+ }
491
+ const shouldRefine = await getOrPrompt(options.skipRefine === false ? true : undefined, () => (0, workflow_prompts_1.confirmPrompt)("Refine your PRD further?", false));
403
492
  if (!shouldRefine) {
404
493
  console.log(chalk_1.default.gray(" Skipping refinement"));
405
494
  state.currentStep = "generate-tasks";
406
495
  return;
407
496
  }
408
- const refineMethod = await (0, workflow_prompts_1.selectPrompt)("How would you like to refine?", [
497
+ const refineMethod = await getOrPrompt(options.refineMethod, () => (0, workflow_prompts_1.selectPrompt)("How would you like to refine?", [
409
498
  { name: "Manual editing (open editor)", value: "manual" },
410
499
  { name: "AI-assisted refinement", value: "ai" },
411
500
  { name: "Skip", value: "skip" },
412
- ]);
501
+ ]));
413
502
  if (refineMethod === "skip") {
414
503
  state.currentStep = "generate-tasks";
415
504
  return;
416
505
  }
417
506
  let refinedContent = state.prdContent || (0, fs_1.readFileSync)(state.prdFile, "utf-8");
507
+ let feedback;
418
508
  if (refineMethod === "manual") {
419
509
  console.log(chalk_1.default.gray("\n Opening editor...\n"));
420
510
  refinedContent = await (0, workflow_prompts_1.editorPrompt)("Edit your PRD (save and close when done):", refinedContent);
421
511
  }
422
512
  else if (refineMethod === "ai") {
423
- console.log(chalk_1.default.cyan("\nšŸ¤– AI-Assisted Refinement\n"));
424
- const feedback = await (0, workflow_prompts_1.textInputPrompt)("What would you like to improve? (e.g., 'Add more technical details', 'Focus on MVP features'):");
425
- console.log(chalk_1.default.gray("\n Refining PRD...\n"));
426
- refinedContent = await workflow_ai_assistant_1.workflowAIAssistant.assistPRDRefinement({
427
- currentPRD: refinedContent,
428
- userFeedback: feedback,
429
- aiOptions,
430
- streamingOptions,
431
- });
513
+ feedback = await getOrPrompt(options.refineFeedback, () => (0, workflow_prompts_1.textInputPrompt)("What would you like to improve? (e.g., 'Add more technical details', 'Focus on MVP features'):"));
514
+ }
515
+ // Call service
516
+ const result = await workflow_1.workflowService.refinePRD({
517
+ method: refineMethod,
518
+ prdFile: state.prdFile,
519
+ prdContent: refineMethod === "manual" ? refinedContent : undefined,
520
+ feedback,
521
+ projectDir: state.projectDir,
522
+ aiOptions: options,
523
+ streamingOptions,
524
+ callbacks: {
525
+ onProgress: progress_1.displayProgress,
526
+ onError: progress_1.displayError,
527
+ },
528
+ });
529
+ if (refineMethod === "ai") {
432
530
  console.log(chalk_1.default.green("\nāœ“ PRD refined"));
433
- console.log(chalk_1.default.gray("\n" + refinedContent.substring(0, 500) + "...\n"));
434
- const acceptRefinement = await (0, workflow_prompts_1.confirmPrompt)("Accept refinements?", true);
531
+ console.log(chalk_1.default.gray("\n" + result.prdContent.substring(0, 500) + "...\n"));
532
+ // Display metrics if available
533
+ if (result.stats) {
534
+ console.log(chalk_1.default.cyan(` Duration: ${result.stats.duration}ms`));
535
+ if (result.stats.tokenUsage) {
536
+ console.log(chalk_1.default.cyan(` Tokens: ${result.stats.tokenUsage.total} (Prompt: ${result.stats.tokenUsage.prompt}, Completion: ${result.stats.tokenUsage.completion})`));
537
+ }
538
+ if (result.stats.timeToFirstToken) {
539
+ console.log(chalk_1.default.cyan(` Time to First Token: ${result.stats.timeToFirstToken}ms`));
540
+ }
541
+ if (result.stats.cost) {
542
+ console.log(chalk_1.default.cyan(` Estimated Cost: $${result.stats.cost.toFixed(6)}`));
543
+ }
544
+ }
545
+ const acceptRefinement = await getOrPrompt(options.autoAccept ? true : undefined, () => (0, workflow_prompts_1.confirmPrompt)("Accept refinements?", true));
435
546
  if (!acceptRefinement) {
436
547
  console.log(chalk_1.default.yellow("⚠ Keeping original PRD"));
437
548
  state.currentStep = "generate-tasks";
438
549
  return;
439
550
  }
440
551
  }
441
- // Save refined PRD
442
- (0, fs_1.writeFileSync)(state.prdFile, refinedContent);
443
- state.prdContent = refinedContent;
552
+ state.prdContent = result.prdContent;
444
553
  console.log(chalk_1.default.green(`āœ“ PRD updated`));
445
554
  state.currentStep = "generate-tasks";
446
555
  }
447
556
  /**
448
557
  * Step 4: Generate Tasks
558
+ * Uses workflowService.generateTasks()
449
559
  */
450
- async function stepGenerateTasks(state, aiOptions, streamingOptions) {
560
+ async function stepGenerateTasks(state, options, streamingOptions) {
451
561
  console.log(chalk_1.default.blue.bold("\nšŸŽÆ Step 4: Generate Tasks\n"));
452
562
  if (!state.prdFile) {
453
563
  console.log(chalk_1.default.yellow("⚠ No PRD file found, skipping task generation"));
454
564
  state.currentStep = "split-tasks";
455
565
  return;
456
566
  }
457
- const shouldGenerate = await (0, workflow_prompts_1.confirmPrompt)("Generate tasks from PRD?", true);
567
+ if (options.skipGenerate || options.skipAll) {
568
+ console.log(chalk_1.default.gray(" Skipping task generation"));
569
+ state.currentStep = "split-tasks";
570
+ return;
571
+ }
572
+ const shouldGenerate = await getOrPrompt(options.skipGenerate === false ? true : undefined, () => (0, workflow_prompts_1.confirmPrompt)("Generate tasks from PRD?", true));
458
573
  if (!shouldGenerate) {
459
574
  console.log(chalk_1.default.gray(" Skipping task generation"));
460
575
  state.currentStep = "split-tasks";
461
576
  return;
462
577
  }
463
- const generationMethod = await (0, workflow_prompts_1.selectPrompt)("Choose generation method:", [
578
+ const generationMethod = await getOrPrompt(options.generateMethod, () => (0, workflow_prompts_1.selectPrompt)("Choose generation method:", [
464
579
  { name: "Standard parsing", value: "standard" },
465
580
  { name: "AI-assisted with custom instructions", value: "ai" },
466
- ]);
581
+ ]));
467
582
  let customInstructions;
468
583
  if (generationMethod === "ai") {
469
- customInstructions = await (0, workflow_prompts_1.textInputPrompt)("Custom instructions (e.g., 'Focus on MVP features', 'Break into small tasks'):", "");
584
+ customInstructions = await getOrPrompt(options.generateInstructions, () => (0, workflow_prompts_1.textInputPrompt)("Custom instructions (e.g., 'Focus on MVP features', 'Break into small tasks'):", ""));
470
585
  }
471
586
  console.log(chalk_1.default.cyan("\n Parsing PRD and generating tasks...\n"));
472
- const result = await prd_1.prdService.parsePRD({
473
- file: state.prdFile,
474
- workingDirectory: state.projectDir,
475
- aiOptions,
476
- messageOverride: customInstructions,
587
+ // Call service
588
+ const result = await workflow_1.workflowService.generateTasks({
589
+ prdFile: state.prdFile,
590
+ method: generationMethod,
591
+ customInstructions,
592
+ projectDir: state.projectDir,
593
+ aiOptions: options,
477
594
  streamingOptions,
478
595
  callbacks: {
479
596
  onProgress: progress_1.displayProgress,
@@ -481,6 +598,19 @@ async function stepGenerateTasks(state, aiOptions, streamingOptions) {
481
598
  },
482
599
  });
483
600
  console.log(chalk_1.default.green(`\nāœ“ Generated ${result.tasks.length} tasks`));
601
+ // Display metrics if available
602
+ if (result.stats) {
603
+ console.log(chalk_1.default.cyan(` Duration: ${result.stats.duration}ms`));
604
+ if (result.stats.tokenUsage) {
605
+ console.log(chalk_1.default.cyan(` Tokens: ${result.stats.tokenUsage.total} (Prompt: ${result.stats.tokenUsage.prompt}, Completion: ${result.stats.tokenUsage.completion})`));
606
+ }
607
+ if (result.stats.timeToFirstToken) {
608
+ console.log(chalk_1.default.cyan(` Time to First Token: ${result.stats.timeToFirstToken}ms`));
609
+ }
610
+ if (result.stats.cost) {
611
+ console.log(chalk_1.default.cyan(` Estimated Cost: $${result.stats.cost.toFixed(6)}`));
612
+ }
613
+ }
484
614
  // Display tasks
485
615
  console.log(chalk_1.default.blue("\n Created Tasks:\n"));
486
616
  result.tasks.forEach((task, index) => {
@@ -495,23 +625,39 @@ async function stepGenerateTasks(state, aiOptions, streamingOptions) {
495
625
  }
496
626
  /**
497
627
  * Step 5: Split Complex Tasks
628
+ * Uses workflowService.splitTasks()
498
629
  */
499
- async function stepSplitTasks(state, aiOptions, streamingOptions) {
630
+ async function stepSplitTasks(state, options, streamingOptions) {
500
631
  console.log(chalk_1.default.blue.bold("\nšŸ”€ Step 5: Split Complex Tasks\n"));
501
632
  if (!state.tasks || state.tasks.length === 0) {
502
633
  console.log(chalk_1.default.yellow("⚠ No tasks found, skipping splitting"));
503
634
  return;
504
635
  }
505
- const shouldSplit = await (0, workflow_prompts_1.confirmPrompt)("Split any complex tasks into subtasks?", false);
506
- if (!shouldSplit) {
636
+ if (options.skipSplit || options.skipAll) {
507
637
  console.log(chalk_1.default.gray(" Skipping task splitting"));
508
638
  return;
509
639
  }
510
- // Show tasks with effort estimates
511
- const tasksToSplit = await (0, workflow_prompts_1.multiSelectPrompt)("Select tasks to split:", state.tasks.map((t) => ({
512
- name: `${t.title}${t.description ? ` - ${t.description.substring(0, 50)}...` : ""}`,
513
- value: t.id,
514
- })));
640
+ // Handle --split-tasks and --split-all options
641
+ let tasksToSplit;
642
+ if (options.splitAll) {
643
+ tasksToSplit = state.tasks?.map((t) => t.id) || [];
644
+ console.log(chalk_1.default.cyan(` Splitting all ${tasksToSplit.length} tasks`));
645
+ }
646
+ else if (options.splitTasks) {
647
+ tasksToSplit = options.splitTasks.split(",").map((id) => id.trim());
648
+ console.log(chalk_1.default.cyan(` Splitting ${tasksToSplit.length} specified tasks`));
649
+ }
650
+ else {
651
+ const shouldSplit = await (0, workflow_prompts_1.confirmPrompt)("Split any complex tasks into subtasks?", false);
652
+ if (!shouldSplit) {
653
+ console.log(chalk_1.default.gray(" Skipping task splitting"));
654
+ return;
655
+ }
656
+ tasksToSplit = await (0, workflow_prompts_1.multiSelectPrompt)("Select tasks to split:", state.tasks.map((t) => ({
657
+ name: `${t.title}${t.description ? ` - ${t.description.substring(0, 50)}...` : ""}`,
658
+ value: t.id,
659
+ })));
660
+ }
515
661
  if (tasksToSplit.length === 0) {
516
662
  console.log(chalk_1.default.gray(" No tasks selected"));
517
663
  return;
@@ -519,42 +665,56 @@ async function stepSplitTasks(state, aiOptions, streamingOptions) {
519
665
  let globalSplitMethod = "interactive";
520
666
  let globalCustomInstructions;
521
667
  if (tasksToSplit.length > 1) {
522
- globalSplitMethod = await (0, workflow_prompts_1.selectPrompt)("How would you like to split these tasks?", [
668
+ globalSplitMethod = await getOrPrompt(options.splitMethod, () => (0, workflow_prompts_1.selectPrompt)("How would you like to split these tasks?", [
523
669
  { name: "Interactive (ask for each task)", value: "interactive" },
524
670
  { name: "Standard AI split for ALL", value: "standard" },
525
671
  { name: "Same custom instructions for ALL", value: "custom" },
526
- ]);
672
+ ]));
527
673
  if (globalSplitMethod === "custom") {
528
- globalCustomInstructions = await (0, workflow_prompts_1.textInputPrompt)("Custom instructions for ALL tasks (e.g., 'Break into 2-4 hour chunks'):", "");
674
+ globalCustomInstructions = await getOrPrompt(options.splitInstructions, () => (0, workflow_prompts_1.textInputPrompt)("Custom instructions for ALL tasks (e.g., 'Break into 2-4 hour chunks'):", ""));
529
675
  }
530
676
  }
531
- for (const taskId of tasksToSplit) {
532
- const task = state.tasks.find((t) => t.id === taskId);
533
- if (!task)
534
- continue;
535
- console.log(chalk_1.default.cyan(`\n Splitting: ${task.title}\n`));
536
- let splitMethod = globalSplitMethod;
537
- let customInstructions = globalCustomInstructions;
538
- if (globalSplitMethod === "interactive") {
539
- splitMethod = await (0, workflow_prompts_1.selectPrompt)("Split method:", [
677
+ // Collect per-task instructions for interactive mode
678
+ const taskInstructions = {};
679
+ if (globalSplitMethod === "interactive") {
680
+ for (const taskId of tasksToSplit) {
681
+ const task = state.tasks.find((t) => t.id === taskId);
682
+ if (!task)
683
+ continue;
684
+ console.log(chalk_1.default.cyan(`\n Task: ${task.title}\n`));
685
+ const splitMethod = await (0, workflow_prompts_1.selectPrompt)("Split method:", [
540
686
  { name: "Standard AI split", value: "standard" },
541
687
  { name: "Custom instructions", value: "custom" },
542
688
  ]);
543
689
  if (splitMethod === "custom") {
544
- customInstructions = await (0, workflow_prompts_1.textInputPrompt)("Custom instructions (e.g., 'Break into 2-4 hour chunks'):", "");
690
+ taskInstructions[taskId] = await (0, workflow_prompts_1.textInputPrompt)("Custom instructions (e.g., 'Break into 2-4 hour chunks'):", "");
545
691
  }
546
692
  }
547
- try {
548
- const result = await tasks_1.taskService.splitTask(taskId, aiOptions, undefined, // promptOverride
549
- customInstructions, streamingOptions);
550
- console.log(chalk_1.default.green(` āœ“ Created ${result.subtasks.length} subtasks`));
551
- result.subtasks.forEach((subtask, index) => {
693
+ }
694
+ // Call service
695
+ const result = await workflow_1.workflowService.splitTasks({
696
+ taskIds: tasksToSplit,
697
+ splitMethod: globalSplitMethod,
698
+ customInstructions: globalCustomInstructions,
699
+ aiOptions: options,
700
+ streamingOptions,
701
+ callbacks: {
702
+ onProgress: progress_1.displayProgress,
703
+ onError: progress_1.displayError,
704
+ },
705
+ });
706
+ // Display results
707
+ result.results.forEach((taskResult) => {
708
+ const task = state.tasks?.find((t) => t.id === taskResult.taskId);
709
+ if (taskResult.error) {
710
+ console.log(chalk_1.default.red(` āœ— Failed to split ${task?.title}: ${taskResult.error}`));
711
+ }
712
+ else {
713
+ console.log(chalk_1.default.green(` āœ“ Split ${task?.title} into ${taskResult.subtasks.length} subtasks`));
714
+ taskResult.subtasks.forEach((subtask, index) => {
552
715
  console.log(chalk_1.default.gray(` ${index + 1}. ${subtask.title}`));
553
716
  });
554
717
  }
555
- catch (error) {
556
- console.log(chalk_1.default.red(` āœ— Failed to split task: ${error}`));
557
- }
558
- }
718
+ });
559
719
  console.log(chalk_1.default.green("\nāœ“ Task splitting complete"));
560
720
  }