wogiflow 2.1.3 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/wogi-audit.md +189 -3
- package/.claude/commands/wogi-review.md +86 -13
- package/.claude/commands/wogi-start.md +66 -21
- package/.claude/docs/claude-code-compatibility.md +28 -0
- package/.claude/rules/_internal/README.md +64 -0
- package/.claude/rules/_internal/document-structure.md +77 -0
- package/.claude/rules/_internal/dual-repo-management.md +174 -0
- package/.claude/rules/_internal/feature-refactoring-cleanup.md +87 -0
- package/.claude/rules/_internal/github-releases.md +71 -0
- package/.claude/rules/_internal/model-management.md +35 -0
- package/.claude/rules/_internal/self-maintenance.md +87 -0
- package/.claude/rules/architecture/component-reuse.md +38 -0
- package/.claude/rules/code-style/naming-conventions.md +52 -0
- package/.claude/rules/operations/git-workflows.md +92 -0
- package/.claude/rules/operations/scratch-directory.md +54 -0
- package/.claude/rules/security/security-patterns.md +176 -0
- package/.claude/skills/figma-analyzer/knowledge/learnings.md +11 -0
- package/.workflow/models/registry.json +1 -1
- package/.workflow/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +32 -2
- package/package.json +1 -1
- package/scripts/flow-audit.js +158 -1
- package/scripts/flow-progress-tracker.js +289 -0
- package/scripts/flow-prompt-capture.js +263 -170
- package/scripts/flow-standards-learner.js +167 -3
- package/scripts/flow-task-checkpoint.js +2 -0
- package/scripts/flow-version-check.js +1 -0
- package/scripts/hooks/core/commit-log-gate.js +146 -0
- package/scripts/hooks/core/post-compact.js +81 -8
- package/scripts/hooks/core/task-completed.js +19 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +60 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +27 -0
|
@@ -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 -
|
|
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
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
117
|
+
// v1 → v2 Migration
|
|
105
118
|
// ============================================================================
|
|
106
119
|
|
|
107
120
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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
|
-
|
|
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 -
|
|
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
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @returns {
|
|
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
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
270
|
+
const history = loadPromptHistory();
|
|
271
|
+
history.prompts.push(entry);
|
|
163
272
|
|
|
164
|
-
//
|
|
165
|
-
if (
|
|
166
|
-
history
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
339
|
+
* @returns {{ taskId: string, prompts: Object[], refinementCount: number }|null}
|
|
186
340
|
*/
|
|
187
341
|
function getTaskPromptHistory(taskId) {
|
|
188
342
|
const history = loadPromptHistory();
|
|
189
|
-
|
|
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
|
-
*
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
233
|
-
|
|
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
|
-
|
|
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(`[
|
|
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
|
|
447
|
-
|
|
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
|
-
|
|
493
|
-
|
|
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 <
|
|
503
|
-
history [taskId]
|
|
504
|
-
|
|
505
|
-
|
|
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,
|