wogiflow 1.0.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/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Session State Manager
|
|
5
|
+
*
|
|
6
|
+
* Persists and restores session context across Claude sessions.
|
|
7
|
+
* Enables automatic continuity when resuming work.
|
|
8
|
+
*
|
|
9
|
+
* Key features:
|
|
10
|
+
* - Tracks current task across sessions
|
|
11
|
+
* - Remembers recently modified files
|
|
12
|
+
* - Stores session decisions
|
|
13
|
+
* - Provides resume context for fast pickup
|
|
14
|
+
*
|
|
15
|
+
* Part of v1.7.0 Context Memory Management
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const {
|
|
21
|
+
getConfig,
|
|
22
|
+
PATHS,
|
|
23
|
+
STATE_DIR,
|
|
24
|
+
colors,
|
|
25
|
+
color,
|
|
26
|
+
warn,
|
|
27
|
+
success,
|
|
28
|
+
error,
|
|
29
|
+
readJson,
|
|
30
|
+
writeJson,
|
|
31
|
+
fileExists,
|
|
32
|
+
printHeader,
|
|
33
|
+
withLock
|
|
34
|
+
} = require('./flow-utils');
|
|
35
|
+
|
|
36
|
+
// ============================================================
|
|
37
|
+
// Constants
|
|
38
|
+
// ============================================================
|
|
39
|
+
|
|
40
|
+
const SESSION_PATH = path.join(STATE_DIR, 'session-state.json');
|
|
41
|
+
|
|
42
|
+
// Default configuration
|
|
43
|
+
const DEFAULTS = {
|
|
44
|
+
enabled: true,
|
|
45
|
+
autoRestore: true,
|
|
46
|
+
maxGapHours: 24, // Consider session "resumed" if within this gap
|
|
47
|
+
trackFiles: true,
|
|
48
|
+
trackDecisions: true,
|
|
49
|
+
maxRecentFiles: 20,
|
|
50
|
+
maxRecentDecisions: 10
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ============================================================
|
|
54
|
+
// Configuration
|
|
55
|
+
// ============================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get session state configuration
|
|
59
|
+
*/
|
|
60
|
+
function getSessionStateConfig() {
|
|
61
|
+
const config = getConfig();
|
|
62
|
+
return {
|
|
63
|
+
...DEFAULTS,
|
|
64
|
+
...(config.sessionState || {})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================
|
|
69
|
+
// Default State
|
|
70
|
+
// ============================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Default session state structure
|
|
74
|
+
*/
|
|
75
|
+
function getDefaultState() {
|
|
76
|
+
return {
|
|
77
|
+
lastActive: null,
|
|
78
|
+
currentTask: null,
|
|
79
|
+
recentFiles: [],
|
|
80
|
+
recentDecisions: [],
|
|
81
|
+
contextSnapshot: {
|
|
82
|
+
keyFacts: [],
|
|
83
|
+
inProgress: null,
|
|
84
|
+
blockers: []
|
|
85
|
+
},
|
|
86
|
+
metrics: {
|
|
87
|
+
tasksCompleted: 0,
|
|
88
|
+
filesModified: 0,
|
|
89
|
+
errorsEncountered: 0,
|
|
90
|
+
sessionCount: 0
|
|
91
|
+
},
|
|
92
|
+
lastSessionSummary: null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================================
|
|
97
|
+
// Core Operations
|
|
98
|
+
// ============================================================
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load session state from file
|
|
102
|
+
* Returns default state if file doesn't exist or is invalid
|
|
103
|
+
*/
|
|
104
|
+
function loadSessionState() {
|
|
105
|
+
if (!fileExists(SESSION_PATH)) {
|
|
106
|
+
return getDefaultState();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const state = readJson(SESSION_PATH, null);
|
|
111
|
+
if (!state) return getDefaultState();
|
|
112
|
+
|
|
113
|
+
// Merge with defaults to handle schema evolution
|
|
114
|
+
return {
|
|
115
|
+
...getDefaultState(),
|
|
116
|
+
...state
|
|
117
|
+
};
|
|
118
|
+
} catch (parseError) {
|
|
119
|
+
// Log in debug mode to help diagnose issues
|
|
120
|
+
if (process.env.DEBUG) {
|
|
121
|
+
console.warn(`[DEBUG] Could not parse session state: ${parseError.message}`);
|
|
122
|
+
}
|
|
123
|
+
return getDefaultState();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Save session state to file (with locking for concurrent access safety)
|
|
129
|
+
*/
|
|
130
|
+
async function saveSessionStateAsync(updates = {}) {
|
|
131
|
+
return withLock(SESSION_PATH, async () => {
|
|
132
|
+
const current = loadSessionState();
|
|
133
|
+
const newState = {
|
|
134
|
+
...current,
|
|
135
|
+
...updates,
|
|
136
|
+
lastActive: new Date().toISOString()
|
|
137
|
+
};
|
|
138
|
+
writeJson(SESSION_PATH, newState);
|
|
139
|
+
return newState;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Save session state to file (sync version for backward compatibility)
|
|
145
|
+
* Note: Use saveSessionStateAsync when possible for concurrent safety
|
|
146
|
+
*/
|
|
147
|
+
function saveSessionState(updates = {}) {
|
|
148
|
+
const current = loadSessionState();
|
|
149
|
+
const newState = {
|
|
150
|
+
...current,
|
|
151
|
+
...updates,
|
|
152
|
+
lastActive: new Date().toISOString()
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
writeJson(SESSION_PATH, newState);
|
|
156
|
+
return newState;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Clear session state (for fresh start)
|
|
161
|
+
*/
|
|
162
|
+
function clearSession() {
|
|
163
|
+
if (fileExists(SESSION_PATH)) {
|
|
164
|
+
fs.unlinkSync(SESSION_PATH);
|
|
165
|
+
}
|
|
166
|
+
return getDefaultState();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================================================
|
|
170
|
+
// Session Detection
|
|
171
|
+
// ============================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if this is a resumed session (within maxGapHours)
|
|
175
|
+
*/
|
|
176
|
+
function isResumingSession() {
|
|
177
|
+
const config = getSessionStateConfig();
|
|
178
|
+
if (!config.enabled) return false;
|
|
179
|
+
|
|
180
|
+
const state = loadSessionState();
|
|
181
|
+
if (!state.lastActive) return false;
|
|
182
|
+
|
|
183
|
+
const lastActive = new Date(state.lastActive);
|
|
184
|
+
const hoursSince = (Date.now() - lastActive.getTime()) / (1000 * 60 * 60);
|
|
185
|
+
|
|
186
|
+
return hoursSince < config.maxGapHours;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get time since last activity
|
|
191
|
+
*/
|
|
192
|
+
function getTimeSinceLastActive() {
|
|
193
|
+
const state = loadSessionState();
|
|
194
|
+
if (!state.lastActive) return null;
|
|
195
|
+
|
|
196
|
+
const lastActive = new Date(state.lastActive);
|
|
197
|
+
const ms = Date.now() - lastActive.getTime();
|
|
198
|
+
|
|
199
|
+
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
200
|
+
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
|
201
|
+
|
|
202
|
+
if (hours > 0) {
|
|
203
|
+
return `${hours}h ${minutes}m ago`;
|
|
204
|
+
}
|
|
205
|
+
return `${minutes}m ago`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============================================================
|
|
209
|
+
// Task Tracking
|
|
210
|
+
// ============================================================
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Track task start
|
|
214
|
+
*/
|
|
215
|
+
function trackTaskStart(taskId, taskTitle, metadata = {}) {
|
|
216
|
+
const state = loadSessionState();
|
|
217
|
+
return saveSessionState({
|
|
218
|
+
currentTask: {
|
|
219
|
+
id: taskId,
|
|
220
|
+
title: taskTitle,
|
|
221
|
+
startedAt: new Date().toISOString(),
|
|
222
|
+
...metadata
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Track task completion
|
|
229
|
+
*/
|
|
230
|
+
function trackTaskComplete(taskId) {
|
|
231
|
+
const state = loadSessionState();
|
|
232
|
+
const newMetrics = {
|
|
233
|
+
...state.metrics,
|
|
234
|
+
tasksCompleted: (state.metrics?.tasksCompleted || 0) + 1
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
return saveSessionState({
|
|
238
|
+
currentTask: null,
|
|
239
|
+
metrics: newMetrics
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get current task
|
|
245
|
+
*/
|
|
246
|
+
function getCurrentTask() {
|
|
247
|
+
const state = loadSessionState();
|
|
248
|
+
return state.currentTask;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ============================================================
|
|
252
|
+
// File Tracking
|
|
253
|
+
// ============================================================
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Track file modification
|
|
257
|
+
*/
|
|
258
|
+
function trackFileModified(filePath) {
|
|
259
|
+
const config = getSessionStateConfig();
|
|
260
|
+
if (!config.trackFiles) return null;
|
|
261
|
+
|
|
262
|
+
const state = loadSessionState();
|
|
263
|
+
const relPath = path.relative(process.cwd(), filePath);
|
|
264
|
+
|
|
265
|
+
// Remove if already in list, add to front
|
|
266
|
+
const recentFiles = [
|
|
267
|
+
relPath,
|
|
268
|
+
...state.recentFiles.filter(f => f !== relPath)
|
|
269
|
+
].slice(0, config.maxRecentFiles);
|
|
270
|
+
|
|
271
|
+
const newMetrics = {
|
|
272
|
+
...state.metrics,
|
|
273
|
+
filesModified: (state.metrics?.filesModified || 0) + 1
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return saveSessionState({
|
|
277
|
+
recentFiles,
|
|
278
|
+
metrics: newMetrics
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get recent files
|
|
284
|
+
*/
|
|
285
|
+
function getRecentFiles(limit = 10) {
|
|
286
|
+
const state = loadSessionState();
|
|
287
|
+
return state.recentFiles.slice(0, limit);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================
|
|
291
|
+
// Decision Tracking
|
|
292
|
+
// ============================================================
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Track a decision made during session
|
|
296
|
+
*/
|
|
297
|
+
function trackDecision(decision, context = null) {
|
|
298
|
+
const config = getSessionStateConfig();
|
|
299
|
+
if (!config.trackDecisions) return null;
|
|
300
|
+
|
|
301
|
+
const state = loadSessionState();
|
|
302
|
+
|
|
303
|
+
const recentDecisions = [
|
|
304
|
+
{
|
|
305
|
+
decision,
|
|
306
|
+
context,
|
|
307
|
+
timestamp: new Date().toISOString()
|
|
308
|
+
},
|
|
309
|
+
...state.recentDecisions
|
|
310
|
+
].slice(0, config.maxRecentDecisions);
|
|
311
|
+
|
|
312
|
+
return saveSessionState({ recentDecisions });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get recent decisions
|
|
317
|
+
*/
|
|
318
|
+
function getRecentDecisions(limit = 5) {
|
|
319
|
+
const state = loadSessionState();
|
|
320
|
+
return state.recentDecisions.slice(0, limit);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ============================================================
|
|
324
|
+
// Context Snapshot
|
|
325
|
+
// ============================================================
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Update context snapshot (key facts, blockers, etc.)
|
|
329
|
+
*/
|
|
330
|
+
function updateContextSnapshot(updates) {
|
|
331
|
+
const state = loadSessionState();
|
|
332
|
+
const contextSnapshot = {
|
|
333
|
+
...state.contextSnapshot,
|
|
334
|
+
...updates
|
|
335
|
+
};
|
|
336
|
+
return saveSessionState({ contextSnapshot });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Add a key fact
|
|
341
|
+
*/
|
|
342
|
+
function addKeyFact(fact) {
|
|
343
|
+
const state = loadSessionState();
|
|
344
|
+
const keyFacts = [...(state.contextSnapshot?.keyFacts || [])];
|
|
345
|
+
|
|
346
|
+
if (!keyFacts.includes(fact)) {
|
|
347
|
+
keyFacts.push(fact);
|
|
348
|
+
// Keep last 10
|
|
349
|
+
while (keyFacts.length > 10) {
|
|
350
|
+
keyFacts.shift();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return updateContextSnapshot({ keyFacts });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Set blockers
|
|
359
|
+
*/
|
|
360
|
+
function setBlockers(blockers) {
|
|
361
|
+
return updateContextSnapshot({ blockers });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================
|
|
365
|
+
// Session Summary
|
|
366
|
+
// ============================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Save session summary (at session end)
|
|
370
|
+
*/
|
|
371
|
+
function saveSessionSummary(summary) {
|
|
372
|
+
const state = loadSessionState();
|
|
373
|
+
const newMetrics = {
|
|
374
|
+
...state.metrics,
|
|
375
|
+
sessionCount: (state.metrics?.sessionCount || 0) + 1
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return saveSessionState({
|
|
379
|
+
lastSessionSummary: {
|
|
380
|
+
...summary,
|
|
381
|
+
timestamp: new Date().toISOString()
|
|
382
|
+
},
|
|
383
|
+
metrics: newMetrics
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ============================================================
|
|
388
|
+
// Resume Context
|
|
389
|
+
// ============================================================
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get resume context for Claude
|
|
393
|
+
* Returns formatted string with key context for picking up work
|
|
394
|
+
*/
|
|
395
|
+
function getResumeContext() {
|
|
396
|
+
const config = getSessionStateConfig();
|
|
397
|
+
if (!config.enabled) return null;
|
|
398
|
+
|
|
399
|
+
const state = loadSessionState();
|
|
400
|
+
if (!state.lastActive) return null;
|
|
401
|
+
|
|
402
|
+
const lines = [];
|
|
403
|
+
const timeSince = getTimeSinceLastActive();
|
|
404
|
+
|
|
405
|
+
lines.push(`**Session resume** (last active: ${timeSince})`);
|
|
406
|
+
lines.push('');
|
|
407
|
+
|
|
408
|
+
// Current task
|
|
409
|
+
if (state.currentTask) {
|
|
410
|
+
lines.push(`**Resuming task**: ${state.currentTask.id} - ${state.currentTask.title}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Recent files
|
|
414
|
+
if (state.recentFiles.length > 0) {
|
|
415
|
+
const recent = state.recentFiles.slice(0, 5);
|
|
416
|
+
lines.push(`**Recently modified**: ${recent.join(', ')}`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Key facts
|
|
420
|
+
if (state.contextSnapshot?.keyFacts?.length > 0) {
|
|
421
|
+
lines.push(`**Key context**:`);
|
|
422
|
+
for (const fact of state.contextSnapshot.keyFacts.slice(0, 5)) {
|
|
423
|
+
lines.push(` - ${fact}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Blockers
|
|
428
|
+
if (state.contextSnapshot?.blockers?.length > 0) {
|
|
429
|
+
lines.push(`**Blockers**: ${state.contextSnapshot.blockers.join(', ')}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Last session summary
|
|
433
|
+
if (state.lastSessionSummary?.summary) {
|
|
434
|
+
lines.push('');
|
|
435
|
+
lines.push(`**Last session**: ${state.lastSessionSummary.summary}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Metrics
|
|
439
|
+
if (state.metrics?.tasksCompleted > 0) {
|
|
440
|
+
lines.push('');
|
|
441
|
+
lines.push(color('dim', `Metrics: ${state.metrics.tasksCompleted} tasks completed, ${state.metrics.filesModified} files modified`));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return lines.length > 2 ? lines.join('\n') : null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Check for resume and display context if resuming
|
|
449
|
+
* Returns true if resume context was displayed
|
|
450
|
+
*/
|
|
451
|
+
function checkAndDisplayResumeContext() {
|
|
452
|
+
if (!isResumingSession()) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const context = getResumeContext();
|
|
457
|
+
if (!context) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
printHeader('Session Resume');
|
|
462
|
+
console.log(context);
|
|
463
|
+
console.log('');
|
|
464
|
+
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ============================================================
|
|
469
|
+
// Error Tracking
|
|
470
|
+
// ============================================================
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Track an error encountered
|
|
474
|
+
*/
|
|
475
|
+
function trackError(errorType = 'unknown') {
|
|
476
|
+
const state = loadSessionState();
|
|
477
|
+
const newMetrics = {
|
|
478
|
+
...state.metrics,
|
|
479
|
+
errorsEncountered: (state.metrics?.errorsEncountered || 0) + 1
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
return saveSessionState({ metrics: newMetrics });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ============================================================
|
|
486
|
+
// CLI Interface
|
|
487
|
+
// ============================================================
|
|
488
|
+
|
|
489
|
+
function printUsage() {
|
|
490
|
+
console.log(`
|
|
491
|
+
Usage: flow-session-state.js [command] [args]
|
|
492
|
+
|
|
493
|
+
Commands:
|
|
494
|
+
show Show current session state
|
|
495
|
+
resume Show resume context (if resuming)
|
|
496
|
+
clear Clear session state
|
|
497
|
+
task <id> <title> Set current task
|
|
498
|
+
task done [id] Mark task complete
|
|
499
|
+
file <path> Track file modification
|
|
500
|
+
decision <text> Track a decision
|
|
501
|
+
fact <text> Add a key fact
|
|
502
|
+
--help Show this help
|
|
503
|
+
|
|
504
|
+
Examples:
|
|
505
|
+
node scripts/flow-session-state.js show
|
|
506
|
+
node scripts/flow-session-state.js task TASK-042 "Add login"
|
|
507
|
+
node scripts/flow-session-state.js fact "Using JWT for auth"
|
|
508
|
+
`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Main CLI handler
|
|
512
|
+
if (require.main === module) {
|
|
513
|
+
const args = process.argv.slice(2);
|
|
514
|
+
const command = args[0];
|
|
515
|
+
|
|
516
|
+
switch (command) {
|
|
517
|
+
case 'show': {
|
|
518
|
+
const state = loadSessionState();
|
|
519
|
+
printHeader('Session State');
|
|
520
|
+
console.log(JSON.stringify(state, null, 2));
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
case 'resume': {
|
|
525
|
+
const displayed = checkAndDisplayResumeContext();
|
|
526
|
+
if (!displayed) {
|
|
527
|
+
console.log('No resume context available (new session)');
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
case 'clear': {
|
|
533
|
+
clearSession();
|
|
534
|
+
success('Session state cleared');
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
case 'task': {
|
|
539
|
+
if (args[1] === 'done') {
|
|
540
|
+
const taskId = args[2] || getCurrentTask()?.id;
|
|
541
|
+
if (taskId) {
|
|
542
|
+
trackTaskComplete(taskId);
|
|
543
|
+
success(`Task ${taskId} marked complete`);
|
|
544
|
+
} else {
|
|
545
|
+
error('No task to complete');
|
|
546
|
+
}
|
|
547
|
+
} else if (args[1] && args[2]) {
|
|
548
|
+
trackTaskStart(args[1], args.slice(2).join(' '));
|
|
549
|
+
success(`Started task: ${args[1]}`);
|
|
550
|
+
} else {
|
|
551
|
+
const task = getCurrentTask();
|
|
552
|
+
if (task) {
|
|
553
|
+
console.log(`Current task: ${task.id} - ${task.title}`);
|
|
554
|
+
} else {
|
|
555
|
+
console.log('No current task');
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
case 'file': {
|
|
562
|
+
if (args[1]) {
|
|
563
|
+
trackFileModified(args[1]);
|
|
564
|
+
success(`Tracked file: ${args[1]}`);
|
|
565
|
+
} else {
|
|
566
|
+
const files = getRecentFiles();
|
|
567
|
+
console.log('Recent files:', files.join(', ') || 'none');
|
|
568
|
+
}
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
case 'decision': {
|
|
573
|
+
if (args[1]) {
|
|
574
|
+
trackDecision(args.slice(1).join(' '));
|
|
575
|
+
success('Decision tracked');
|
|
576
|
+
} else {
|
|
577
|
+
const decisions = getRecentDecisions();
|
|
578
|
+
if (decisions.length > 0) {
|
|
579
|
+
console.log('Recent decisions:');
|
|
580
|
+
for (const d of decisions) {
|
|
581
|
+
console.log(` - ${d.decision}`);
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
console.log('No recent decisions');
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
case 'fact': {
|
|
591
|
+
if (args[1]) {
|
|
592
|
+
addKeyFact(args.slice(1).join(' '));
|
|
593
|
+
success('Key fact added');
|
|
594
|
+
} else {
|
|
595
|
+
const state = loadSessionState();
|
|
596
|
+
const facts = state.contextSnapshot?.keyFacts || [];
|
|
597
|
+
if (facts.length > 0) {
|
|
598
|
+
console.log('Key facts:');
|
|
599
|
+
facts.forEach((f, i) => console.log(` ${i}. ${f}`));
|
|
600
|
+
} else {
|
|
601
|
+
console.log('No key facts');
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
case '--help':
|
|
608
|
+
case '-h':
|
|
609
|
+
printUsage();
|
|
610
|
+
break;
|
|
611
|
+
|
|
612
|
+
default:
|
|
613
|
+
if (command) {
|
|
614
|
+
error(`Unknown command: ${command}`);
|
|
615
|
+
}
|
|
616
|
+
printUsage();
|
|
617
|
+
process.exit(command ? 1 : 0);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ============================================================
|
|
622
|
+
// Exports
|
|
623
|
+
// ============================================================
|
|
624
|
+
|
|
625
|
+
module.exports = {
|
|
626
|
+
// Configuration
|
|
627
|
+
getSessionStateConfig,
|
|
628
|
+
DEFAULTS,
|
|
629
|
+
|
|
630
|
+
// Core operations
|
|
631
|
+
loadSessionState,
|
|
632
|
+
saveSessionState,
|
|
633
|
+
saveSessionStateAsync, // Concurrent-safe version
|
|
634
|
+
clearSession,
|
|
635
|
+
getDefaultState,
|
|
636
|
+
|
|
637
|
+
// Session detection
|
|
638
|
+
isResumingSession,
|
|
639
|
+
getTimeSinceLastActive,
|
|
640
|
+
|
|
641
|
+
// Task tracking
|
|
642
|
+
trackTaskStart,
|
|
643
|
+
trackTaskComplete,
|
|
644
|
+
getCurrentTask,
|
|
645
|
+
|
|
646
|
+
// File tracking
|
|
647
|
+
trackFileModified,
|
|
648
|
+
getRecentFiles,
|
|
649
|
+
|
|
650
|
+
// Decision tracking
|
|
651
|
+
trackDecision,
|
|
652
|
+
getRecentDecisions,
|
|
653
|
+
|
|
654
|
+
// Context snapshot
|
|
655
|
+
updateContextSnapshot,
|
|
656
|
+
addKeyFact,
|
|
657
|
+
setBlockers,
|
|
658
|
+
|
|
659
|
+
// Session summary
|
|
660
|
+
saveSessionSummary,
|
|
661
|
+
|
|
662
|
+
// Resume context
|
|
663
|
+
getResumeContext,
|
|
664
|
+
checkAndDisplayResumeContext,
|
|
665
|
+
|
|
666
|
+
// Error tracking
|
|
667
|
+
trackError,
|
|
668
|
+
|
|
669
|
+
// Path
|
|
670
|
+
SESSION_PATH
|
|
671
|
+
};
|