wogiflow 2.1.3 → 2.3.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.
@@ -1,17 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Wogi Flow - Prompt Capture System
4
+ * Wogi Flow - Prompt Capture System (v2 — Flat Array Architecture)
5
5
  *
6
6
  * Two-file system for capturing and learning from user prompts:
7
- * 1. prompt-history.json - Full history of all prompts during task execution
7
+ * 1. prompt-history.json - Chronological flat array of ALL prompts (v2)
8
8
  * 2. clarifications.md - Learning entries from refinement patterns
9
9
  *
10
- * Features:
11
- * - Captures all user prompts during task execution
12
- * - Detects refinement/clarification patterns
13
- * - Tracks refinement count for learning system
14
- * - Generates learning entries on task completion
10
+ * v2 Architecture (Approach B):
11
+ * - All prompts are ALWAYS saved, regardless of whether a taskId exists
12
+ * - taskId is optional metadata on each prompt, not a primary key
13
+ * - First prompt is never lost (saved with taskId: null, tagged retrospectively)
14
+ * - Stale taskIds are detected via ready.json cross-check
15
+ * - v1 → v2 migration happens automatically on first load
16
+ *
17
+ * Fixes bugs: wf-ddd498de
18
+ * - Bug 1: First prompt lost (no taskId yet) → now saved with taskId: null
19
+ * - Bug 2: Prompts persist under stale taskId → cross-check with ready.json
20
+ * - Bug 3: Top-level prompts[] never used → now the primary storage
15
21
  */
16
22
 
17
23
  const fs = require('node:fs');
@@ -23,9 +29,19 @@ const {
23
29
  ensureDir,
24
30
  fileExists,
25
31
  readFile,
26
- writeFile
32
+ writeFile,
33
+ getReadyData
27
34
  } = require('./flow-utils');
28
- const { loadDurableSession } = require('./flow-durable-session');
35
+ const { getTodayDate } = require('./flow-output');
36
+
37
+ // Lazy-load to avoid circular dependency (durable-session imports flow-utils too)
38
+ let _loadDurableSession;
39
+ function loadDurableSession() {
40
+ if (!_loadDurableSession) {
41
+ _loadDurableSession = require('./flow-durable-session').loadDurableSession;
42
+ }
43
+ return _loadDurableSession();
44
+ }
29
45
 
30
46
  // ============================================================================
31
47
  // Constants
@@ -33,7 +49,8 @@ const { loadDurableSession } = require('./flow-durable-session');
33
49
 
34
50
  const PROMPT_HISTORY_FILE = 'prompt-history.json';
35
51
  const CLARIFICATIONS_FILE = 'clarifications.md';
36
- const MAX_TASK_HISTORY = 50; // Max tasks to keep in history before cleanup
52
+ const MAX_PROMPTS = 500; // Max prompts to keep before cleanup (flat array)
53
+ const SCHEMA_VERSION = 2;
37
54
 
38
55
  // Patterns that indicate a refinement/clarification
39
56
  const REFINEMENT_PATTERNS = [
@@ -80,9 +97,7 @@ function detectRefinement(prompt) {
80
97
  if (!prompt || typeof prompt !== 'string') {
81
98
  return false;
82
99
  }
83
-
84
- const trimmed = prompt.trim();
85
- return REFINEMENT_PATTERNS.some(pattern => pattern.test(trimmed));
100
+ return REFINEMENT_PATTERNS.some(pattern => pattern.test(prompt.trim()));
86
101
  }
87
102
 
88
103
  /**
@@ -91,31 +106,97 @@ function detectRefinement(prompt) {
91
106
  * @returns {Object} Analysis result
92
107
  */
93
108
  function analyzePrompt(prompt) {
94
- const isRefinement = detectRefinement(prompt);
95
-
96
109
  return {
97
- isRefinement,
110
+ isRefinement: detectRefinement(prompt),
98
111
  length: prompt?.length || 0,
99
112
  timestamp: new Date().toISOString()
100
113
  };
101
114
  }
102
115
 
103
116
  // ============================================================================
104
- // Prompt History Management
117
+ // v1 v2 Migration
105
118
  // ============================================================================
106
119
 
107
120
  /**
108
- * Load prompt history from file
109
- * @returns {Object} Prompt history keyed by task ID
121
+ * Migrate v1 (task-keyed object) to v2 (flat array) format.
122
+ * v1: { "wf-xxx": { prompts: [...], ... }, "wf-yyy": { ... } }
123
+ * v2: { version: 2, prompts: [{ taskId, content, timestamp, ... }] }
124
+ *
125
+ * @param {Object} v1Data - v1 format data
126
+ * @returns {Object} v2 format data
127
+ */
128
+ function migrateV1ToV2(v1Data) {
129
+ const prompts = [];
130
+
131
+ for (const [taskId, taskEntry] of Object.entries(v1Data)) {
132
+ // Skip metadata keys
133
+ if (taskId === 'version' || taskId === 'prompts') continue;
134
+ if (!taskEntry || !Array.isArray(taskEntry.prompts)) continue;
135
+
136
+ for (const prompt of taskEntry.prompts) {
137
+ prompts.push({
138
+ timestamp: prompt.timestamp || taskEntry.startedAt || new Date().toISOString(),
139
+ content: prompt.content || '',
140
+ taskId: taskId,
141
+ taskTitle: taskEntry.title || null,
142
+ isRefinement: prompt.isRefinement || false,
143
+ isInitial: prompt.isInitial || false,
144
+ sessionId: null,
145
+ source: 'migrated-v1'
146
+ });
147
+ }
148
+ }
149
+
150
+ // Sort chronologically
151
+ prompts.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
152
+
153
+ return { version: SCHEMA_VERSION, prompts };
154
+ }
155
+
156
+ // ============================================================================
157
+ // Prompt History Management (v2 — Flat Array)
158
+ // ============================================================================
159
+
160
+ /**
161
+ * Load prompt history, auto-migrating v1 → v2 if needed.
162
+ * @returns {{ version: number, prompts: Object[] }}
110
163
  */
111
164
  function loadPromptHistory() {
112
165
  const historyPath = getPromptHistoryPath();
113
- return safeJsonParse(historyPath, {});
166
+ const raw = safeJsonParse(historyPath, null);
167
+
168
+ // No file yet
169
+ if (!raw) {
170
+ return { version: SCHEMA_VERSION, prompts: [] };
171
+ }
172
+
173
+ // Already v2
174
+ if (raw.version === SCHEMA_VERSION && Array.isArray(raw.prompts)) {
175
+ return raw;
176
+ }
177
+
178
+ // v1 format detected — migrate
179
+ if (!raw.version && typeof raw === 'object') {
180
+ const migrated = migrateV1ToV2(raw);
181
+ // Save migrated data
182
+ try {
183
+ ensureDir(path.dirname(historyPath));
184
+ writeJson(historyPath, migrated);
185
+ } catch (err) {
186
+ if (process.env.DEBUG) {
187
+ console.error(`[prompt-capture] Migration write failed: ${err.message}`);
188
+ }
189
+ }
190
+ return migrated;
191
+ }
192
+
193
+ // Unknown format — start fresh
194
+ return { version: SCHEMA_VERSION, prompts: [] };
114
195
  }
115
196
 
116
197
  /**
117
198
  * Save prompt history to file
118
- * @param {Object} history - History object to save
199
+ * @param {Object} history - v2 history object
119
200
  */
120
201
  function savePromptHistory(history) {
121
202
  const historyPath = getPromptHistoryPath();
@@ -124,69 +205,151 @@ function savePromptHistory(history) {
124
205
  }
125
206
 
126
207
  /**
127
- * Capture a user prompt for the current task
128
- * @param {string} taskId - Current task ID
129
- * @param {string} prompt - User prompt text
130
- * @param {Object} options - Additional options
131
- * @param {string} options.taskTitle - Task title
132
- * @returns {Object} Captured prompt entry
208
+ * Get current valid task ID cross-checked against ready.json.
209
+ * Returns null if:
210
+ * - No durable session exists
211
+ * - durable-session taskId is not in ready.json inProgress (stale)
212
+ *
213
+ * @returns {string|null} Valid task ID or null
133
214
  */
134
- function capturePrompt(taskId, prompt, options = {}) {
135
- if (!taskId || !prompt) {
215
+ function getCurrentTaskId() {
216
+ try {
217
+ const session = loadDurableSession();
218
+ const taskId = session?.taskId;
219
+ if (!taskId) return null;
220
+
221
+ // Cross-check: is this task actually in progress?
222
+ const readyData = getReadyData();
223
+ const inProgress = Array.isArray(readyData.inProgress) ? readyData.inProgress : [];
224
+ const isActive = inProgress.some(t =>
225
+ (typeof t === 'string' ? t : t.id) === taskId
226
+ );
227
+
228
+ if (!isActive) {
229
+ // Task is no longer in progress — stale durable session
230
+ if (process.env.DEBUG) {
231
+ console.error(`[prompt-capture] Stale taskId ${taskId} — not in ready.json inProgress`);
232
+ }
233
+ return null;
234
+ }
235
+
236
+ return taskId;
237
+ } catch (err) {
136
238
  return null;
137
239
  }
240
+ }
138
241
 
139
- const history = loadPromptHistory();
140
-
141
- // Initialize task entry if not exists
142
- if (!history[taskId]) {
143
- history[taskId] = {
144
- taskId,
145
- title: options.taskTitle || null,
146
- startedAt: new Date().toISOString(),
147
- prompts: [],
148
- refinementCount: 0
149
- };
242
+ /**
243
+ * Capture a user prompt. ALWAYS saves — taskId is optional metadata.
244
+ *
245
+ * @param {string} prompt - User prompt text
246
+ * @param {Object} [options] - Additional options
247
+ * @param {string} [options.taskId] - Override task ID (otherwise auto-detected)
248
+ * @param {string} [options.taskTitle] - Task title
249
+ * @param {string} [options.sessionId] - Session ID
250
+ * @returns {Object} Captured prompt entry
251
+ */
252
+ function capturePrompt(prompt, options = {}) {
253
+ if (!prompt || typeof prompt !== 'string') {
254
+ return null;
150
255
  }
151
256
 
257
+ const taskId = options.taskId !== undefined ? options.taskId : getCurrentTaskId();
152
258
  const analysis = analyzePrompt(prompt);
153
- const isInitial = history[taskId].prompts.length === 0;
154
259
 
155
260
  const entry = {
156
261
  timestamp: analysis.timestamp,
157
262
  content: prompt,
158
- isInitial,
159
- isRefinement: analysis.isRefinement
263
+ taskId: taskId || null,
264
+ taskTitle: options.taskTitle || null,
265
+ isRefinement: analysis.isRefinement,
266
+ sessionId: options.sessionId || process.env.CLAUDE_CODE_SESSION_ID || null,
267
+ source: 'hook'
160
268
  };
161
269
 
162
- history[taskId].prompts.push(entry);
270
+ const history = loadPromptHistory();
271
+ history.prompts.push(entry);
163
272
 
164
- // Track refinement count
165
- if (analysis.isRefinement) {
166
- history[taskId].refinementCount = (history[taskId].refinementCount || 0) + 1;
273
+ // Cleanup if over limit
274
+ if (history.prompts.length > MAX_PROMPTS) {
275
+ history.prompts = history.prompts.slice(-MAX_PROMPTS);
167
276
  }
168
277
 
169
- // Update title if provided
170
- if (options.taskTitle && !history[taskId].title) {
171
- history[taskId].title = options.taskTitle;
172
- }
278
+ savePromptHistory(history);
279
+ return entry;
280
+ }
281
+
282
+ /**
283
+ * Capture prompt for current task (backward-compatible wrapper).
284
+ * In v2, this ALWAYS saves — even without a taskId.
285
+ *
286
+ * @param {string} prompt - User prompt text
287
+ * @returns {Object|null} Captured entry (null only if prompt is empty)
288
+ */
289
+ function captureCurrentPrompt(prompt) {
290
+ return capturePrompt(prompt);
291
+ }
173
292
 
174
- // Cleanup old tasks if we have too many
175
- cleanupOldTasks(history);
293
+ /**
294
+ * Tag recent untagged prompts with a taskId (retrospective tagging).
295
+ * Called after task creation to tag the initial prompt that triggered it.
296
+ *
297
+ * @param {string} taskId - Task ID to tag with
298
+ * @param {string} [taskTitle] - Task title
299
+ * @param {number} [lookbackMs=60000] - How far back to look (default 1 min)
300
+ * @returns {{ tagged: number }}
301
+ */
302
+ function tagRecentPrompts(taskId, taskTitle, lookbackMs = 60000) {
303
+ if (!taskId) return { tagged: 0 };
176
304
 
177
- savePromptHistory(history);
305
+ const history = loadPromptHistory();
306
+ const cutoff = Date.now() - lookbackMs;
307
+ let tagged = 0;
308
+
309
+ // Walk backwards through recent prompts
310
+ for (let i = history.prompts.length - 1; i >= 0; i--) {
311
+ const prompt = history.prompts[i];
312
+ const promptTime = new Date(prompt.timestamp).getTime();
313
+
314
+ // Stop if we've gone past the lookback window
315
+ if (promptTime < cutoff) break;
316
+
317
+ // Tag untagged prompts
318
+ if (!prompt.taskId) {
319
+ prompt.taskId = taskId;
320
+ if (taskTitle) prompt.taskTitle = taskTitle;
321
+ tagged++;
322
+ }
323
+ }
178
324
 
179
- return entry;
325
+ if (tagged > 0) {
326
+ savePromptHistory(history);
327
+ }
328
+
329
+ return { tagged };
180
330
  }
181
331
 
332
+ // ============================================================================
333
+ // Query Functions (backward-compatible)
334
+ // ============================================================================
335
+
182
336
  /**
183
- * Get prompt history for a specific task
337
+ * Get prompt history for a specific task (filters flat array by taskId).
184
338
  * @param {string} taskId - Task ID
185
- * @returns {Object|null} Task prompt history or null
339
+ * @returns {{ taskId: string, prompts: Object[], refinementCount: number }|null}
186
340
  */
187
341
  function getTaskPromptHistory(taskId) {
188
342
  const history = loadPromptHistory();
189
- return history[taskId] || null;
343
+ const taskPrompts = history.prompts.filter(p => p.taskId === taskId);
344
+
345
+ if (taskPrompts.length === 0) return null;
346
+
347
+ return {
348
+ taskId,
349
+ title: taskPrompts[0]?.taskTitle || null,
350
+ prompts: taskPrompts,
351
+ refinementCount: taskPrompts.filter(p => p.isRefinement).length
352
+ };
190
353
  }
191
354
 
192
355
  /**
@@ -213,42 +376,13 @@ function getLastRefinement(taskId) {
213
376
  }
214
377
 
215
378
  /**
216
- * Mark a task as completed in prompt history
217
- * @param {string} taskId - Task ID
218
- */
219
- function markTaskCompleted(taskId) {
220
- const history = loadPromptHistory();
221
-
222
- if (history[taskId]) {
223
- history[taskId].completedAt = new Date().toISOString();
224
- savePromptHistory(history);
225
- }
226
- }
227
-
228
- /**
229
- * Cleanup old task entries to prevent unbounded growth
230
- * @param {Object} history - History object (modified in place)
379
+ * Mark a task as completed in prompt history.
380
+ * In v2, this is a no-op for the flat array (completedAt is tracked in ready.json).
381
+ * Kept for backward compatibility with flow-done.js.
382
+ * @param {string} _taskId - Task ID (unused in v2)
231
383
  */
232
- function cleanupOldTasks(history) {
233
- const taskIds = Object.keys(history);
234
-
235
- if (taskIds.length <= MAX_TASK_HISTORY) {
236
- return;
237
- }
238
-
239
- // Sort by startedAt and remove oldest
240
- // Tasks without startedAt get epoch 0 (oldest) so they're cleaned first
241
- const sorted = taskIds.sort((a, b) => {
242
- const dateA = history[a].startedAt ? new Date(history[a].startedAt).getTime() : 0;
243
- const dateB = history[b].startedAt ? new Date(history[b].startedAt).getTime() : 0;
244
- return dateA - dateB;
245
- });
246
-
247
- const toRemove = sorted.slice(0, sorted.length - MAX_TASK_HISTORY);
248
-
249
- for (const taskId of toRemove) {
250
- delete history[taskId];
251
- }
384
+ function markTaskCompleted(_taskId) {
385
+ // No-op in v2 — task completion is tracked in ready.json, not prompt-history
252
386
  }
253
387
 
254
388
  // ============================================================================
@@ -271,18 +405,12 @@ function generateClarificationId() {
271
405
  */
272
406
  function generateClarificationEntry(taskId, taskTitle) {
273
407
  const taskHistory = getTaskPromptHistory(taskId);
274
-
275
- if (!taskHistory) {
276
- return null;
277
- }
408
+ if (!taskHistory) return null;
278
409
 
279
410
  const refinements = taskHistory.prompts.filter(p => p.isRefinement);
411
+ if (refinements.length === 0) return null;
280
412
 
281
- if (refinements.length === 0) {
282
- return null; // No clarifications needed
283
- }
284
-
285
- const initial = taskHistory.prompts.find(p => p.isInitial);
413
+ const initial = taskHistory.prompts[0];
286
414
  const final = refinements[refinements.length - 1];
287
415
 
288
416
  return {
@@ -308,8 +436,6 @@ function appendClarificationLearning(entry) {
308
436
  ensureDir(path.dirname(clPath));
309
437
 
310
438
  const today = getTodayDate();
311
-
312
- // Build markdown entry
313
439
  const markdown = `
314
440
  ### ${entry.id} | ${entry.taskId} | ${entry.taskTitle}
315
441
  **Initial Request:** "${truncateString(entry.initial, 200)}"
@@ -322,11 +448,9 @@ function appendClarificationLearning(entry) {
322
448
 
323
449
  try {
324
450
  let content = '';
325
-
326
451
  if (fileExists(clPath)) {
327
452
  content = readFile(clPath, '');
328
453
  } else {
329
- // Create file with header
330
454
  content = `# Clarification Learnings
331
455
 
332
456
  This file contains learnings from user clarifications during task execution.
@@ -335,18 +459,16 @@ High-value patterns can be promoted to decisions.md for permanent rules.
335
459
  `;
336
460
  }
337
461
 
338
- // Add date header if new day
339
462
  if (!content.includes(`## ${today}`)) {
340
463
  content += `\n## ${today}\n`;
341
464
  }
342
465
 
343
466
  content += markdown;
344
-
345
467
  writeFile(clPath, content);
346
468
  return true;
347
469
  } catch (err) {
348
470
  if (process.env.DEBUG) {
349
- console.error(`[DEBUG] appendClarificationLearning: ${err.message}`);
471
+ console.error(`[prompt-capture] appendClarificationLearning: ${err.message}`);
350
472
  }
351
473
  return false;
352
474
  }
@@ -359,20 +481,15 @@ High-value patterns can be promoted to decisions.md for permanent rules.
359
481
  * @returns {Object} Result with entry details
360
482
  */
361
483
  function processTaskCompletion(taskId, taskTitle) {
362
- // Generate entry if refinements exist
363
484
  const entry = generateClarificationEntry(taskId, taskTitle);
364
485
 
365
486
  if (!entry) {
366
487
  return { generated: false, reason: 'no-refinements' };
367
488
  }
368
489
 
369
- // Append to clarifications.md
370
490
  const success = appendClarificationLearning(entry);
371
491
 
372
492
  if (success) {
373
- // Mark task as completed in history
374
- markTaskCompleted(taskId);
375
-
376
493
  return {
377
494
  generated: true,
378
495
  entry,
@@ -383,53 +500,15 @@ function processTaskCompletion(taskId, taskTitle) {
383
500
  return { generated: false, reason: 'write-failed' };
384
501
  }
385
502
 
386
- // ============================================================================
387
- // Auto-Detection from Session
388
- // ============================================================================
389
-
390
- /**
391
- * Get current task ID from durable session
392
- * @returns {string|null} Task ID or null
393
- */
394
- function getCurrentTaskId() {
395
- try {
396
- const session = loadDurableSession();
397
- return session?.taskId || null;
398
- } catch (err) {
399
- return null;
400
- }
401
- }
402
-
403
- /**
404
- * Capture prompt for current task (auto-detects task ID)
405
- * @param {string} prompt - User prompt text
406
- * @returns {Object|null} Captured entry or null
407
- */
408
- function captureCurrentPrompt(prompt) {
409
- const taskId = getCurrentTaskId();
410
-
411
- if (!taskId) {
412
- // No active task - don't capture
413
- return null;
414
- }
415
-
416
- return capturePrompt(taskId, prompt);
417
- }
418
-
419
503
  // ============================================================================
420
504
  // Utility Functions
421
505
  // ============================================================================
422
506
 
423
507
  /**
424
508
  * Truncate string with ellipsis
425
- * @param {string} str - String to truncate
426
- * @param {number} maxLength - Max length
427
- * @returns {string} Truncated string
428
509
  */
429
510
  function truncateString(str, maxLength) {
430
- if (!str || str.length <= maxLength) {
431
- return str || '';
432
- }
511
+ if (!str || str.length <= maxLength) return str || '';
433
512
  return str.slice(0, maxLength - 3) + '...';
434
513
  }
435
514
 
@@ -443,15 +522,12 @@ if (require.main === module) {
443
522
 
444
523
  switch (command) {
445
524
  case 'capture': {
446
- const taskId = args[1];
447
- const prompt = args.slice(2).join(' ');
448
-
449
- if (!taskId || !prompt) {
450
- console.log('Usage: node flow-prompt-capture.js capture <taskId> <prompt>');
525
+ const prompt = args.slice(1).join(' ');
526
+ if (!prompt) {
527
+ console.log('Usage: node flow-prompt-capture.js capture <prompt>');
451
528
  process.exit(1);
452
529
  }
453
-
454
- const result = capturePrompt(taskId, prompt);
530
+ const result = capturePrompt(prompt);
455
531
  console.log(JSON.stringify(result, null, 2));
456
532
  break;
457
533
  }
@@ -463,20 +539,30 @@ if (require.main === module) {
463
539
  console.log(JSON.stringify(history, null, 2));
464
540
  } else {
465
541
  const allHistory = loadPromptHistory();
466
- console.log(JSON.stringify(allHistory, null, 2));
542
+ console.log(JSON.stringify({ version: allHistory.version, count: allHistory.prompts.length, prompts: allHistory.prompts.slice(-20) }, null, 2));
543
+ }
544
+ break;
545
+ }
546
+
547
+ case 'tag': {
548
+ const taskId = args[1];
549
+ if (!taskId) {
550
+ console.log('Usage: node flow-prompt-capture.js tag <taskId> [title]');
551
+ process.exit(1);
467
552
  }
553
+ const title = args.slice(2).join(' ') || null;
554
+ const result = tagRecentPrompts(taskId, title);
555
+ console.log(JSON.stringify(result, null, 2));
468
556
  break;
469
557
  }
470
558
 
471
559
  case 'complete': {
472
560
  const taskId = args[1];
473
561
  const title = args.slice(2).join(' ') || taskId;
474
-
475
562
  if (!taskId) {
476
563
  console.log('Usage: node flow-prompt-capture.js complete <taskId> [title]');
477
564
  process.exit(1);
478
565
  }
479
-
480
566
  const result = processTaskCompletion(taskId, title);
481
567
  console.log(JSON.stringify(result, null, 2));
482
568
  break;
@@ -488,9 +574,13 @@ if (require.main === module) {
488
574
  console.log('Usage: node flow-prompt-capture.js analyze <prompt>');
489
575
  process.exit(1);
490
576
  }
577
+ console.log(JSON.stringify({ prompt, isRefinement: detectRefinement(prompt) }, null, 2));
578
+ break;
579
+ }
491
580
 
492
- const isRefinement = detectRefinement(prompt);
493
- console.log(JSON.stringify({ prompt, isRefinement }, null, 2));
581
+ case 'migrate': {
582
+ const history = loadPromptHistory(); // auto-migrates on load
583
+ console.log(JSON.stringify({ version: history.version, promptCount: history.prompts.length, migrated: true }, null, 2));
494
584
  break;
495
585
  }
496
586
 
@@ -499,10 +589,12 @@ if (require.main === module) {
499
589
  Usage: node flow-prompt-capture.js <command> [args]
500
590
 
501
591
  Commands:
502
- capture <taskId> <prompt> - Capture a prompt for a task
503
- history [taskId] - Show prompt history (all or for specific task)
504
- complete <taskId> [title] - Process task completion (generate learning entry)
505
- analyze <prompt> - Analyze if prompt is a refinement
592
+ capture <prompt> - Capture a prompt (taskId auto-detected)
593
+ history [taskId] - Show prompt history (all or filtered by task)
594
+ tag <taskId> [title] - Tag recent untagged prompts with a taskId
595
+ complete <taskId> [title] - Process task completion (generate learning entry)
596
+ analyze <prompt> - Analyze if prompt is a refinement
597
+ migrate - Force v1 → v2 migration
506
598
  `);
507
599
  }
508
600
  }
@@ -517,10 +609,11 @@ module.exports = {
517
609
  analyzePrompt,
518
610
  REFINEMENT_PATTERNS,
519
611
 
520
- // Prompt history
612
+ // Prompt history (v2)
521
613
  loadPromptHistory,
522
614
  capturePrompt,
523
615
  captureCurrentPrompt,
616
+ tagRecentPrompts,
524
617
  getTaskPromptHistory,
525
618
  getRefinementCount,
526
619
  getLastRefinement,