@kosuke-ai/cli 0.0.25 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +32 -28
- package/dist/index.js.map +1 -1
- package/dist/kosuke/commands/requirements.d.ts +7 -2
- package/dist/kosuke/commands/requirements.d.ts.map +1 -1
- package/dist/kosuke/commands/requirements.js +335 -216
- package/dist/kosuke/commands/requirements.js.map +1 -1
- package/dist/kosuke/commands/tickets.d.ts +10 -3
- package/dist/kosuke/commands/tickets.d.ts.map +1 -1
- package/dist/kosuke/commands/tickets.js +81 -42
- package/dist/kosuke/commands/tickets.js.map +1 -1
- package/dist/kosuke/types.d.ts +12 -1
- package/dist/kosuke/types.d.ts.map +1 -1
- package/dist/kosuke/utils/claude-agent.d.ts +11 -0
- package/dist/kosuke/utils/claude-agent.d.ts.map +1 -1
- package/dist/kosuke/utils/claude-agent.js +87 -6
- package/dist/kosuke/utils/claude-agent.js.map +1 -1
- package/dist/kosuke/utils/logger.d.ts +20 -0
- package/dist/kosuke/utils/logger.d.ts.map +1 -1
- package/dist/kosuke/utils/logger.js +2 -0
- package/dist/kosuke/utils/logger.js.map +1 -1
- package/dist/package.json +2 -1
- package/package.json +2 -1
|
@@ -7,11 +7,50 @@
|
|
|
7
7
|
* - Claude asks clarification questions
|
|
8
8
|
* - User answers questions (iterative until clear)
|
|
9
9
|
* - Generate docs.md with complete requirements
|
|
10
|
+
*
|
|
11
|
+
* Implementation: Uses Anthropic SDK directly with two custom tools:
|
|
12
|
+
* - write_docs: Create new docs.md file
|
|
13
|
+
* - edit_docs: Update existing docs.md file
|
|
10
14
|
*/
|
|
11
|
-
import
|
|
15
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
12
17
|
import { join } from 'path';
|
|
13
18
|
import * as readline from 'readline';
|
|
14
19
|
import { calculateCost } from '../utils/claude-agent.js';
|
|
20
|
+
import { logger, setupCancellationHandler } from '../utils/logger.js';
|
|
21
|
+
/**
|
|
22
|
+
* Tool definitions for docs.md management
|
|
23
|
+
*/
|
|
24
|
+
const REQUIREMENTS_TOOLS = [
|
|
25
|
+
{
|
|
26
|
+
name: 'write_docs',
|
|
27
|
+
description: 'Create a new docs.md file with comprehensive product requirements. Use this when creating the initial requirements document.',
|
|
28
|
+
input_schema: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
content: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Full markdown content for docs.md including all sections: Product Description (high-level overview and purpose), Core Functionalities (detailed feature descriptions), and Interface & Design (ASCII wireframes for all major pages/screens with component descriptions and user interactions). Do NOT include technical implementation details like database schemas, API endpoints, or code architecture.',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ['content'],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'edit_docs',
|
|
41
|
+
description: 'Update an existing docs.md file with revised requirements. Use this when making changes to an existing requirements document.',
|
|
42
|
+
input_schema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
content: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Full updated markdown content for docs.md with all revisions incorporated.',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ['content'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
15
54
|
/**
|
|
16
55
|
* Custom system prompt for requirements gathering
|
|
17
56
|
*/
|
|
@@ -84,18 +123,12 @@ For each clarification needed, provide BOTH a question AND a recommended approac
|
|
|
84
123
|
- Always prioritize simplicity and MVP scope
|
|
85
124
|
- Continue the conversation until EVERYTHING is crystal clear
|
|
86
125
|
|
|
87
|
-
3. **Final Deliverable - docs.md**: Once ALL questions are answered and requirements are 100% clear, create the \`docs.md\` file. This is the FINAL DELIVERABLE that the user will review before implementation begins.
|
|
126
|
+
3. **Final Deliverable - docs.md**: Once ALL questions are answered and requirements are 100% clear, use the \`write_docs\` tool to create the \`docs.md\` file. This is the FINAL DELIVERABLE that the user will review before implementation begins.
|
|
88
127
|
|
|
89
128
|
**docs.md MUST contain:**
|
|
90
|
-
- **Product
|
|
91
|
-
- **Core Functionalities** - Detailed feature descriptions
|
|
92
|
-
- **Interface & Design** - ASCII wireframes for ALL major pages/screens
|
|
93
|
-
- **Technical Architecture** - Tech stack, folder structure, key libraries
|
|
94
|
-
- **User Flows** - Step-by-step user journeys for key features
|
|
95
|
-
- **Database Schema** - Tables, fields, relationships, data types
|
|
96
|
-
- **API Endpoints** - Routes, methods, request/response formats (if applicable)
|
|
97
|
-
- **Business Logic** - Key algorithms, calculations, rules
|
|
98
|
-
- **Implementation Notes** - Important technical considerations
|
|
129
|
+
- **Product Description** - High-level description of what will be built, the core concept and purpose
|
|
130
|
+
- **Core Functionalities** - Detailed feature descriptions (what the product should do)
|
|
131
|
+
- **Interface & Design** - ASCII wireframes for ALL major pages/screens with component descriptions and user interactions
|
|
99
132
|
|
|
100
133
|
**Critical Rules:**
|
|
101
134
|
- NEVER start implementation - you only gather requirements
|
|
@@ -107,16 +140,40 @@ For each clarification needed, provide BOTH a question AND a recommended approac
|
|
|
107
140
|
- Focus on WHAT the product should do, not HOW to code it
|
|
108
141
|
- Be conversational and help the user think through edge cases
|
|
109
142
|
- Bias towards simplicity - this is an MVP, not a full-featured product
|
|
143
|
+
- Use the \`write_docs\` tool when creating the initial docs.md file
|
|
144
|
+
- Use the \`edit_docs\` tool if you need to update docs.md after user feedback
|
|
110
145
|
- The docs.md file is your SUCCESS CRITERIA - make it comprehensive and clear
|
|
146
|
+
- NEVER include technical implementation details (database schemas, API endpoints, code architecture, tech stack) in docs.md
|
|
147
|
+
- Keep docs.md focused on user-facing features, functionality, and interface design only
|
|
111
148
|
|
|
112
149
|
**Success = User reviews docs.md and says "Yes, this is exactly what I want to build"**`;
|
|
113
150
|
/**
|
|
114
|
-
*
|
|
115
|
-
* Just returns the user message - all instructions are in the system prompt
|
|
151
|
+
* Execute a tool call (write or edit docs.md)
|
|
116
152
|
*/
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
function executeToolCall(toolName, toolInput, workspaceRoot) {
|
|
154
|
+
const docsPath = join(workspaceRoot, 'docs.md');
|
|
155
|
+
try {
|
|
156
|
+
if (toolName === 'write_docs') {
|
|
157
|
+
const content = toolInput.content;
|
|
158
|
+
writeFileSync(docsPath, content, 'utf-8');
|
|
159
|
+
console.log('\n✍️ Created docs.md with comprehensive requirements');
|
|
160
|
+
return 'Successfully created docs.md file';
|
|
161
|
+
}
|
|
162
|
+
else if (toolName === 'edit_docs') {
|
|
163
|
+
const content = toolInput.content;
|
|
164
|
+
writeFileSync(docsPath, content, 'utf-8');
|
|
165
|
+
console.log('\n✏️ Updated docs.md with revised requirements');
|
|
166
|
+
return 'Successfully updated docs.md file';
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
return `Unknown tool: ${toolName}`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error writing docs.md';
|
|
174
|
+
console.error(`\n❌ Failed to write docs.md: ${errorMessage}`);
|
|
175
|
+
return `Error: ${errorMessage}`;
|
|
176
|
+
}
|
|
120
177
|
}
|
|
121
178
|
/**
|
|
122
179
|
* Format token usage for display
|
|
@@ -136,212 +193,209 @@ function formatTokenUsage(inputTokens, outputTokens, cacheCreationTokens, cacheR
|
|
|
136
193
|
/**
|
|
137
194
|
* Process a single Claude interaction with streaming support
|
|
138
195
|
*/
|
|
139
|
-
async function processClaudeInteraction(userInput,
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
196
|
+
async function processClaudeInteraction(userInput, previousMessages, workspaceRoot, onStream) {
|
|
197
|
+
const anthropic = new Anthropic({
|
|
198
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
199
|
+
});
|
|
200
|
+
// Build message history: previous messages + new user message
|
|
201
|
+
const messages = [
|
|
202
|
+
...previousMessages,
|
|
203
|
+
{
|
|
204
|
+
role: 'user',
|
|
205
|
+
content: userInput,
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
// Stream the response
|
|
209
|
+
const stream = await anthropic.messages.stream({
|
|
210
|
+
model: 'claude-sonnet-4-20250514',
|
|
211
|
+
max_tokens: 8096,
|
|
212
|
+
system: REQUIREMENTS_SYSTEM_PROMPT,
|
|
213
|
+
tools: REQUIREMENTS_TOOLS,
|
|
214
|
+
messages,
|
|
215
|
+
});
|
|
153
216
|
let responseText = '';
|
|
154
|
-
|
|
155
|
-
let inputTokens = 0;
|
|
156
|
-
let outputTokens = 0;
|
|
157
|
-
let cacheCreationTokens = 0;
|
|
158
|
-
let cacheReadTokens = 0;
|
|
217
|
+
const toolUses = [];
|
|
159
218
|
let isFirstOutput = true;
|
|
160
|
-
//
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
219
|
+
// Process stream events
|
|
220
|
+
for await (const event of stream) {
|
|
221
|
+
if (event.type === 'content_block_start') {
|
|
222
|
+
if (event.content_block.type === 'text') {
|
|
223
|
+
if (isFirstOutput) {
|
|
224
|
+
if (!onStream) {
|
|
225
|
+
process.stdout.write('\n> Claude:\n');
|
|
226
|
+
}
|
|
227
|
+
isFirstOutput = false;
|
|
228
|
+
}
|
|
169
229
|
}
|
|
170
230
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const content = message.message.content;
|
|
179
|
-
for (let i = 0; i < content.length; i++) {
|
|
180
|
-
const block = content[i];
|
|
181
|
-
if (block.type === 'text') {
|
|
182
|
-
const currentText = block.text || '';
|
|
183
|
-
const previousText = blockTexts.get(i) || '';
|
|
184
|
-
// Calculate the delta (new text added since last message)
|
|
185
|
-
const delta = currentText.substring(previousText.length);
|
|
186
|
-
// Stream only the delta to console in real-time
|
|
187
|
-
if (delta) {
|
|
188
|
-
if (isFirstOutput) {
|
|
189
|
-
process.stdout.write('\n> Claude:\n');
|
|
190
|
-
isFirstOutput = false;
|
|
191
|
-
}
|
|
192
|
-
process.stdout.write(delta);
|
|
193
|
-
}
|
|
194
|
-
// Update tracked text and full response
|
|
195
|
-
blockTexts.set(i, currentText);
|
|
196
|
-
responseText = currentText;
|
|
231
|
+
else if (event.type === 'content_block_delta') {
|
|
232
|
+
if (event.delta.type === 'text_delta') {
|
|
233
|
+
const delta = event.delta.text;
|
|
234
|
+
responseText += delta;
|
|
235
|
+
// Stream to console or callback
|
|
236
|
+
if (onStream) {
|
|
237
|
+
onStream(delta);
|
|
197
238
|
}
|
|
198
|
-
else
|
|
199
|
-
|
|
200
|
-
if (block.name === 'Write' || block.name === 'Edit') {
|
|
201
|
-
const input = block.input;
|
|
202
|
-
if (input.path === 'docs.md' || input.path?.includes('docs.md')) {
|
|
203
|
-
console.log('\n\n✍️ Generating docs.md...');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
239
|
+
else {
|
|
240
|
+
process.stdout.write(delta);
|
|
206
241
|
}
|
|
207
242
|
}
|
|
208
243
|
}
|
|
209
|
-
else if (
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
244
|
+
else if (event.type === 'content_block_stop') {
|
|
245
|
+
// Content block finished
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Get final message from stream
|
|
249
|
+
const finalMessage = await stream.finalMessage();
|
|
250
|
+
// Extract tool uses from the response
|
|
251
|
+
for (const block of finalMessage.content) {
|
|
252
|
+
if (block.type === 'tool_use') {
|
|
253
|
+
toolUses.push({
|
|
254
|
+
id: block.id,
|
|
255
|
+
name: block.name,
|
|
256
|
+
input: block.input,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Execute tools and build tool result messages
|
|
261
|
+
const hasToolCalls = toolUses.length > 0;
|
|
262
|
+
let updatedMessages = messages;
|
|
263
|
+
if (hasToolCalls) {
|
|
264
|
+
// Add assistant message with tool uses
|
|
265
|
+
updatedMessages = [
|
|
266
|
+
...messages,
|
|
267
|
+
{
|
|
268
|
+
role: 'assistant',
|
|
269
|
+
content: finalMessage.content,
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
// Execute each tool and collect results
|
|
273
|
+
const toolResults = toolUses.map((tool) => {
|
|
274
|
+
const result = executeToolCall(tool.name, tool.input, workspaceRoot);
|
|
275
|
+
return {
|
|
276
|
+
type: 'tool_result',
|
|
277
|
+
tool_use_id: tool.id,
|
|
278
|
+
content: result,
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
// Add tool results as user message
|
|
282
|
+
updatedMessages = [
|
|
283
|
+
...updatedMessages,
|
|
284
|
+
{
|
|
285
|
+
role: 'user',
|
|
286
|
+
content: toolResults,
|
|
287
|
+
},
|
|
288
|
+
];
|
|
289
|
+
// Continue conversation after tool execution to get final response
|
|
290
|
+
const followupStream = await anthropic.messages.stream({
|
|
291
|
+
model: 'claude-sonnet-4-20250514',
|
|
292
|
+
max_tokens: 8096,
|
|
293
|
+
system: REQUIREMENTS_SYSTEM_PROMPT,
|
|
294
|
+
tools: REQUIREMENTS_TOOLS,
|
|
295
|
+
messages: updatedMessages,
|
|
296
|
+
});
|
|
297
|
+
let followupText = '';
|
|
298
|
+
for await (const event of followupStream) {
|
|
299
|
+
if (event.type === 'content_block_delta') {
|
|
300
|
+
if (event.delta.type === 'text_delta') {
|
|
301
|
+
const delta = event.delta.text;
|
|
302
|
+
followupText += delta;
|
|
303
|
+
// Stream followup response
|
|
304
|
+
if (onStream) {
|
|
305
|
+
onStream(delta);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
process.stdout.write(delta);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
216
311
|
}
|
|
217
312
|
}
|
|
313
|
+
const followupMessage = await followupStream.finalMessage();
|
|
314
|
+
// Update response and messages
|
|
315
|
+
responseText += '\n' + followupText;
|
|
316
|
+
updatedMessages = [
|
|
317
|
+
...updatedMessages,
|
|
318
|
+
{
|
|
319
|
+
role: 'assistant',
|
|
320
|
+
content: followupMessage.content,
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
// Combine token usage from both calls
|
|
324
|
+
const finalUsage = finalMessage.usage;
|
|
325
|
+
const followupUsage = followupMessage.usage;
|
|
326
|
+
const combinedUsage = {
|
|
327
|
+
input_tokens: finalUsage.input_tokens + followupUsage.input_tokens,
|
|
328
|
+
output_tokens: finalUsage.output_tokens + followupUsage.output_tokens,
|
|
329
|
+
cache_creation_input_tokens: (finalUsage.cache_creation_input_tokens || 0) +
|
|
330
|
+
(followupUsage.cache_creation_input_tokens || 0),
|
|
331
|
+
cache_read_input_tokens: (finalUsage.cache_read_input_tokens || 0) + (followupUsage.cache_read_input_tokens || 0),
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
response: responseText,
|
|
335
|
+
messages: updatedMessages,
|
|
336
|
+
inputTokens: combinedUsage.input_tokens,
|
|
337
|
+
outputTokens: combinedUsage.output_tokens,
|
|
338
|
+
cacheCreationTokens: combinedUsage.cache_creation_input_tokens,
|
|
339
|
+
cacheReadTokens: combinedUsage.cache_read_input_tokens,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// No tool calls - just add assistant response to messages
|
|
344
|
+
updatedMessages = [
|
|
345
|
+
...messages,
|
|
346
|
+
{
|
|
347
|
+
role: 'assistant',
|
|
348
|
+
content: finalMessage.content,
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
const usage = finalMessage.usage;
|
|
352
|
+
return {
|
|
353
|
+
response: responseText,
|
|
354
|
+
messages: updatedMessages,
|
|
355
|
+
inputTokens: usage.input_tokens,
|
|
356
|
+
outputTokens: usage.output_tokens,
|
|
357
|
+
cacheCreationTokens: usage.cache_creation_input_tokens || 0,
|
|
358
|
+
cacheReadTokens: usage.cache_read_input_tokens || 0,
|
|
359
|
+
};
|
|
218
360
|
}
|
|
219
|
-
return {
|
|
220
|
-
response: responseText,
|
|
221
|
-
sessionId: newSessionId,
|
|
222
|
-
inputTokens,
|
|
223
|
-
outputTokens,
|
|
224
|
-
cacheCreationTokens,
|
|
225
|
-
cacheReadTokens,
|
|
226
|
-
};
|
|
227
361
|
}
|
|
228
362
|
/**
|
|
229
363
|
* Core requirements gathering function for programmatic use
|
|
230
364
|
* This is the non-interactive API that can be used by kosuke-core
|
|
231
365
|
*/
|
|
232
366
|
export async function requirementsCore(options) {
|
|
233
|
-
const { workspaceRoot, userMessage,
|
|
367
|
+
const { workspaceRoot, userMessage, previousMessages = [], isFirstRequest = false, onStream, } = options;
|
|
234
368
|
try {
|
|
235
369
|
// Validate API key
|
|
236
370
|
if (!process.env.ANTHROPIC_API_KEY) {
|
|
237
371
|
throw new Error('ANTHROPIC_API_KEY environment variable is required');
|
|
238
372
|
}
|
|
239
|
-
|
|
240
|
-
const effectivePrompt = buildRequirementsPrompt(userMessage, isFirstRequest);
|
|
241
|
-
// Query options with custom requirements gathering system prompt
|
|
242
|
-
const queryOptions = {
|
|
243
|
-
model: 'claude-sonnet-4-5',
|
|
244
|
-
maxTurns: 20,
|
|
245
|
-
cwd: workspaceRoot,
|
|
246
|
-
permissionMode: 'acceptEdits',
|
|
247
|
-
resume: sessionId || undefined, // Resume previous session if sessionId provided
|
|
248
|
-
allowedTools: ['Read', 'Write', 'Edit', 'LS', 'Grep', 'Glob', 'WebSearch'],
|
|
249
|
-
systemPrompt: REQUIREMENTS_SYSTEM_PROMPT, // SDK accepts string despite TypeScript types
|
|
250
|
-
};
|
|
251
|
-
console.log(`📋 [RequirementsCore] Starting query with session: ${sessionId || 'NEW'}`);
|
|
373
|
+
console.log(`📋 [RequirementsCore] Starting query`);
|
|
252
374
|
console.log(`📋 [RequirementsCore] Is first request: ${isFirstRequest}`);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
let newSessionId = sessionId || '';
|
|
257
|
-
let inputTokens = 0;
|
|
258
|
-
let outputTokens = 0;
|
|
259
|
-
let cacheCreationTokens = 0;
|
|
260
|
-
let cacheReadTokens = 0;
|
|
261
|
-
// Process the async generator with streaming
|
|
262
|
-
// Track accumulated text per block index for delta calculation
|
|
263
|
-
const blockTexts = new Map();
|
|
264
|
-
try {
|
|
265
|
-
for await (const message of responseStream) {
|
|
266
|
-
// Debug: Log ALL message details to understand stream structure
|
|
267
|
-
const messageWithId = message;
|
|
268
|
-
console.log(`📨 [RequirementsCore] Message received:`, JSON.stringify({
|
|
269
|
-
type: message.type,
|
|
270
|
-
subtype: messageWithId.subtype,
|
|
271
|
-
hasSessionId: !!messageWithId.session_id,
|
|
272
|
-
sessionIdValue: messageWithId.session_id || 'N/A',
|
|
273
|
-
keys: Object.keys(message),
|
|
274
|
-
}, null, 2));
|
|
275
|
-
// Capture session ID from system init message (first message in stream)
|
|
276
|
-
if (message.type === 'system' && message.subtype === 'init') {
|
|
277
|
-
if (!newSessionId) {
|
|
278
|
-
newSessionId = message.session_id;
|
|
279
|
-
console.log(`🆔 [RequirementsCore] Captured new session ID from system init: ${newSessionId}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
// Also try capturing from ANY message that has a session_id
|
|
283
|
-
if (!newSessionId && messageWithId.session_id) {
|
|
284
|
-
newSessionId = messageWithId.session_id;
|
|
285
|
-
console.log(`🆔 [RequirementsCore] Captured session ID from ${message.type} message: ${newSessionId}`);
|
|
286
|
-
}
|
|
287
|
-
if (message.type === 'assistant') {
|
|
288
|
-
const content = message.message.content;
|
|
289
|
-
for (let i = 0; i < content.length; i++) {
|
|
290
|
-
const block = content[i];
|
|
291
|
-
if (block.type === 'text') {
|
|
292
|
-
const currentText = block.text || '';
|
|
293
|
-
const previousText = blockTexts.get(i) || '';
|
|
294
|
-
// Calculate the delta (new text added since last message)
|
|
295
|
-
const delta = currentText.substring(previousText.length);
|
|
296
|
-
// Stream the delta if callback provided and there's new text
|
|
297
|
-
if (delta && onStream) {
|
|
298
|
-
onStream(delta);
|
|
299
|
-
}
|
|
300
|
-
// Update the tracked text for this block
|
|
301
|
-
blockTexts.set(i, currentText);
|
|
302
|
-
// Accumulate full response
|
|
303
|
-
responseText = currentText;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
// Track token usage
|
|
307
|
-
if (message.message.usage) {
|
|
308
|
-
inputTokens += message.message.usage.input_tokens || 0;
|
|
309
|
-
outputTokens += message.message.usage.output_tokens || 0;
|
|
310
|
-
cacheCreationTokens += message.message.usage.cache_creation_input_tokens || 0;
|
|
311
|
-
cacheReadTokens += message.message.usage.cache_read_input_tokens || 0;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
catch (streamError) {
|
|
317
|
-
throw streamError;
|
|
318
|
-
}
|
|
375
|
+
console.log(`📋 [RequirementsCore] Previous messages: ${previousMessages.length}`);
|
|
376
|
+
// Process interaction
|
|
377
|
+
const result = await processClaudeInteraction(userMessage, previousMessages, workspaceRoot, onStream);
|
|
319
378
|
// Check if docs.md was created
|
|
320
379
|
let docsCreated = false;
|
|
321
380
|
let docsContent;
|
|
322
381
|
const docsPath = join(workspaceRoot, 'docs.md');
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
docsContent = await fs.readFile(docsPath, 'utf-8');
|
|
382
|
+
if (existsSync(docsPath)) {
|
|
383
|
+
docsContent = readFileSync(docsPath, 'utf-8');
|
|
326
384
|
docsCreated = true;
|
|
327
385
|
}
|
|
328
|
-
|
|
329
|
-
// docs.md not yet created
|
|
330
|
-
docsCreated = false;
|
|
331
|
-
}
|
|
332
|
-
console.log(`✅ [RequirementsCore] Returning session ID: ${newSessionId}`);
|
|
386
|
+
console.log(`✅ [RequirementsCore] Interaction complete`);
|
|
333
387
|
console.log(`✅ [RequirementsCore] Docs created: ${docsCreated}`);
|
|
334
388
|
return {
|
|
335
389
|
success: true,
|
|
336
|
-
response:
|
|
337
|
-
|
|
390
|
+
response: result.response,
|
|
391
|
+
messages: result.messages,
|
|
338
392
|
docsCreated,
|
|
339
393
|
docsContent,
|
|
340
394
|
tokenUsage: {
|
|
341
|
-
input: inputTokens,
|
|
342
|
-
output: outputTokens,
|
|
343
|
-
cacheCreation: cacheCreationTokens,
|
|
344
|
-
cacheRead: cacheReadTokens,
|
|
395
|
+
input: result.inputTokens,
|
|
396
|
+
output: result.outputTokens,
|
|
397
|
+
cacheCreation: result.cacheCreationTokens,
|
|
398
|
+
cacheRead: result.cacheReadTokens,
|
|
345
399
|
},
|
|
346
400
|
};
|
|
347
401
|
}
|
|
@@ -349,7 +403,7 @@ export async function requirementsCore(options) {
|
|
|
349
403
|
return {
|
|
350
404
|
success: false,
|
|
351
405
|
response: '',
|
|
352
|
-
|
|
406
|
+
messages: previousMessages,
|
|
353
407
|
docsCreated: false,
|
|
354
408
|
tokenUsage: {
|
|
355
409
|
input: 0,
|
|
@@ -364,7 +418,7 @@ export async function requirementsCore(options) {
|
|
|
364
418
|
/**
|
|
365
419
|
* Interactive requirements gathering loop
|
|
366
420
|
*/
|
|
367
|
-
async function interactiveSession() {
|
|
421
|
+
async function interactiveSession(logContext) {
|
|
368
422
|
console.log(`
|
|
369
423
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
370
424
|
║ Kosuke Requirements - Interactive Requirements Tool ║
|
|
@@ -375,8 +429,11 @@ async function interactiveSession() {
|
|
|
375
429
|
console.log(' and generate a detailed docs.md file.\n');
|
|
376
430
|
console.log('✨ Tip: Press Enter to submit, Shift+Enter for new lines.\n');
|
|
377
431
|
// Set up global Ctrl+C handler for the entire interactive session
|
|
378
|
-
const handleSigInt = () => {
|
|
432
|
+
const handleSigInt = async () => {
|
|
379
433
|
console.log('\n\n👋 Exiting requirements gathering...\n');
|
|
434
|
+
if (logContext) {
|
|
435
|
+
await logger.complete(logContext, 'cancelled');
|
|
436
|
+
}
|
|
380
437
|
process.exit(0);
|
|
381
438
|
};
|
|
382
439
|
process.on('SIGINT', handleSigInt);
|
|
@@ -386,10 +443,9 @@ async function interactiveSession() {
|
|
|
386
443
|
});
|
|
387
444
|
const session = {
|
|
388
445
|
productDescription: '',
|
|
389
|
-
|
|
446
|
+
messages: [],
|
|
390
447
|
isFirstRequest: true,
|
|
391
448
|
};
|
|
392
|
-
let sessionId = null;
|
|
393
449
|
let totalInputTokens = 0;
|
|
394
450
|
let totalOutputTokens = 0;
|
|
395
451
|
let totalCacheCreationTokens = 0;
|
|
@@ -500,19 +556,25 @@ async function interactiveSession() {
|
|
|
500
556
|
if (!productDescription) {
|
|
501
557
|
console.log('\n❌ No product description provided. Exiting.');
|
|
502
558
|
rl.close();
|
|
503
|
-
return
|
|
559
|
+
return {
|
|
560
|
+
messages: [],
|
|
561
|
+
tokensUsed: { input: 0, output: 0, cacheCreation: 0, cacheRead: 0 },
|
|
562
|
+
cost: 0,
|
|
563
|
+
};
|
|
504
564
|
}
|
|
505
565
|
session.productDescription = productDescription;
|
|
506
|
-
session.conversationHistory.push(`User: ${productDescription}`);
|
|
507
566
|
// Main conversation loop
|
|
508
567
|
let continueConversation = true;
|
|
509
568
|
while (continueConversation) {
|
|
510
569
|
console.log('\n🤔 Claude is thinking...\n');
|
|
511
|
-
// Get
|
|
512
|
-
const
|
|
570
|
+
// Get current user message
|
|
571
|
+
const userMessage = session.messages.length === 0
|
|
513
572
|
? productDescription
|
|
514
|
-
: session.
|
|
515
|
-
|
|
573
|
+
: session.messages[session.messages.length - 1].content;
|
|
574
|
+
// Get Claude's response with streaming
|
|
575
|
+
const result = await processClaudeInteraction(userMessage, session.messages, process.cwd());
|
|
576
|
+
// Update session messages
|
|
577
|
+
session.messages = result.messages;
|
|
516
578
|
session.isFirstRequest = false;
|
|
517
579
|
// Track costs
|
|
518
580
|
totalInputTokens += result.inputTokens;
|
|
@@ -525,24 +587,17 @@ async function interactiveSession() {
|
|
|
525
587
|
console.log('\n' + '─'.repeat(90));
|
|
526
588
|
console.log(formatTokenUsage(result.inputTokens, result.outputTokens, result.cacheCreationTokens, result.cacheReadTokens, batchCost));
|
|
527
589
|
console.log('─'.repeat(90) + '\n');
|
|
528
|
-
session.conversationHistory.push(`Claude: ${result.response}`);
|
|
529
590
|
// Check if docs.md was created
|
|
530
591
|
const docsPath = join(process.cwd(), 'docs.md');
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
continueConversation = false;
|
|
541
|
-
break;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
catch {
|
|
545
|
-
// docs.md not yet created, continue conversation
|
|
592
|
+
if (existsSync(docsPath)) {
|
|
593
|
+
console.log('\n✅ Requirements document created: docs.md');
|
|
594
|
+
console.log('\n' + '═'.repeat(90));
|
|
595
|
+
console.log('📊 Total Session Cost:');
|
|
596
|
+
console.log(formatTokenUsage(totalInputTokens, totalOutputTokens, totalCacheCreationTokens, totalCacheReadTokens, totalCost));
|
|
597
|
+
console.log('═'.repeat(90));
|
|
598
|
+
console.log('\n🎉 Requirements gathering complete!\n');
|
|
599
|
+
continueConversation = false;
|
|
600
|
+
break;
|
|
546
601
|
}
|
|
547
602
|
// Ask for user response
|
|
548
603
|
console.log('💬 Your response (type "exit" to quit):\n');
|
|
@@ -560,7 +615,14 @@ async function interactiveSession() {
|
|
|
560
615
|
continueConversation = false;
|
|
561
616
|
break;
|
|
562
617
|
}
|
|
563
|
-
|
|
618
|
+
// Add user response to messages
|
|
619
|
+
session.messages = [
|
|
620
|
+
...session.messages,
|
|
621
|
+
{
|
|
622
|
+
role: 'user',
|
|
623
|
+
content: userResponse,
|
|
624
|
+
},
|
|
625
|
+
];
|
|
564
626
|
}
|
|
565
627
|
}
|
|
566
628
|
catch (error) {
|
|
@@ -572,21 +634,78 @@ async function interactiveSession() {
|
|
|
572
634
|
process.removeListener('SIGINT', handleSigInt);
|
|
573
635
|
rl.close();
|
|
574
636
|
}
|
|
637
|
+
// Return session data for logging
|
|
638
|
+
return {
|
|
639
|
+
messages: session.messages,
|
|
640
|
+
tokensUsed: {
|
|
641
|
+
input: totalInputTokens,
|
|
642
|
+
output: totalOutputTokens,
|
|
643
|
+
cacheCreation: totalCacheCreationTokens,
|
|
644
|
+
cacheRead: totalCacheReadTokens,
|
|
645
|
+
},
|
|
646
|
+
cost: totalCost,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Convert Anthropic messages to our conversation format
|
|
651
|
+
*/
|
|
652
|
+
function convertAnthropicMessagesToConversation(messages) {
|
|
653
|
+
return messages.map((msg) => {
|
|
654
|
+
const content = typeof msg.content === 'string'
|
|
655
|
+
? msg.content
|
|
656
|
+
: msg.content.map((block) => (block.type === 'text' ? block.text : '')).join('');
|
|
657
|
+
// Extract tool calls if present (for assistant messages)
|
|
658
|
+
let toolCalls;
|
|
659
|
+
if (typeof msg.content !== 'string') {
|
|
660
|
+
const toolUses = msg.content.filter((block) => block.type === 'tool_use');
|
|
661
|
+
if (toolUses.length > 0) {
|
|
662
|
+
toolCalls = toolUses.map((tool) => {
|
|
663
|
+
// Type guard for tool_use blocks
|
|
664
|
+
if ('name' in tool && 'input' in tool) {
|
|
665
|
+
return {
|
|
666
|
+
name: tool.name,
|
|
667
|
+
input: tool.input,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
return { name: 'unknown', input: {} };
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
role: msg.role === 'user' ? 'user' : 'assistant',
|
|
676
|
+
content,
|
|
677
|
+
timestamp: new Date().toISOString(),
|
|
678
|
+
toolCalls,
|
|
679
|
+
};
|
|
680
|
+
});
|
|
575
681
|
}
|
|
576
682
|
/**
|
|
577
683
|
* Main requirements command
|
|
578
684
|
*/
|
|
579
685
|
export async function requirementsCommand() {
|
|
686
|
+
// Initialize logging context
|
|
687
|
+
const logContext = logger.createContext('tickets', { noLogs: false });
|
|
688
|
+
const cleanupHandler = setupCancellationHandler(logContext);
|
|
580
689
|
try {
|
|
581
690
|
// Validate environment
|
|
582
691
|
if (!process.env.ANTHROPIC_API_KEY) {
|
|
583
692
|
throw new Error('ANTHROPIC_API_KEY environment variable is required');
|
|
584
693
|
}
|
|
585
694
|
// Start interactive session
|
|
586
|
-
await interactiveSession();
|
|
695
|
+
const sessionData = await interactiveSession(logContext);
|
|
696
|
+
// Track metrics
|
|
697
|
+
logger.trackTokens(logContext, sessionData.tokensUsed);
|
|
698
|
+
// Convert Anthropic messages to our conversation format
|
|
699
|
+
logContext.conversationMessages = convertAnthropicMessagesToConversation(sessionData.messages);
|
|
700
|
+
// Log successful execution
|
|
701
|
+
await logger.complete(logContext, 'success');
|
|
702
|
+
cleanupHandler();
|
|
587
703
|
}
|
|
588
704
|
catch (error) {
|
|
589
705
|
console.error('\n❌ Requirements command failed:', error);
|
|
706
|
+
// Log failed execution
|
|
707
|
+
await logger.complete(logContext, 'error', error);
|
|
708
|
+
cleanupHandler();
|
|
590
709
|
throw error;
|
|
591
710
|
}
|
|
592
711
|
}
|