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,559 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Request Log Manager
|
|
5
|
+
*
|
|
6
|
+
* Implements summary buffer pattern for request-log.md:
|
|
7
|
+
* - Keeps recent entries in main log
|
|
8
|
+
* - Archives older entries to monthly files
|
|
9
|
+
* - Maintains summary of archived content
|
|
10
|
+
*
|
|
11
|
+
* Inspired by LangChain's SummaryBufferMemory pattern.
|
|
12
|
+
*
|
|
13
|
+
* Part of v1.7.0 Context Memory Management
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const {
|
|
19
|
+
getConfig,
|
|
20
|
+
PATHS,
|
|
21
|
+
WORKFLOW_DIR,
|
|
22
|
+
STATE_DIR,
|
|
23
|
+
colors,
|
|
24
|
+
color,
|
|
25
|
+
warn,
|
|
26
|
+
success,
|
|
27
|
+
error,
|
|
28
|
+
readFile,
|
|
29
|
+
writeFile,
|
|
30
|
+
fileExists,
|
|
31
|
+
dirExists,
|
|
32
|
+
countRequestLogEntries,
|
|
33
|
+
printHeader
|
|
34
|
+
} = require('./flow-utils');
|
|
35
|
+
|
|
36
|
+
// ============================================================
|
|
37
|
+
// Constants
|
|
38
|
+
// ============================================================
|
|
39
|
+
|
|
40
|
+
const LOG_PATH = PATHS.requestLog;
|
|
41
|
+
const ARCHIVE_DIR = path.join(WORKFLOW_DIR, 'archive');
|
|
42
|
+
const SUMMARY_PATH = path.join(STATE_DIR, 'request-log-summary.md');
|
|
43
|
+
|
|
44
|
+
// Default configuration
|
|
45
|
+
const DEFAULTS = {
|
|
46
|
+
enabled: true,
|
|
47
|
+
autoArchive: true,
|
|
48
|
+
maxRecentEntries: 50,
|
|
49
|
+
keepRecent: 30,
|
|
50
|
+
createSummary: true
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ============================================================
|
|
54
|
+
// Configuration
|
|
55
|
+
// ============================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get log manager configuration
|
|
59
|
+
*/
|
|
60
|
+
function getLogManagerConfig() {
|
|
61
|
+
const config = getConfig();
|
|
62
|
+
return {
|
|
63
|
+
...DEFAULTS,
|
|
64
|
+
...(config.requestLog || {})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ============================================================
|
|
69
|
+
// Entry Parsing
|
|
70
|
+
// ============================================================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse entries from request-log content
|
|
74
|
+
* Returns array of { id, raw, type, tags, request, result, date }
|
|
75
|
+
*/
|
|
76
|
+
function parseEntries(content) {
|
|
77
|
+
const entries = [];
|
|
78
|
+
|
|
79
|
+
// Match entry blocks starting with ### R-NNN
|
|
80
|
+
const entryRegex = /### (R-\d+)\s*\|\s*([^\n]+)\n([\s\S]*?)(?=### R-|\Z|$)/g;
|
|
81
|
+
let match;
|
|
82
|
+
|
|
83
|
+
while ((match = entryRegex.exec(content)) !== null) {
|
|
84
|
+
const id = match[1];
|
|
85
|
+
const dateStr = match[2].trim();
|
|
86
|
+
const body = match[3];
|
|
87
|
+
|
|
88
|
+
entries.push({
|
|
89
|
+
id,
|
|
90
|
+
date: dateStr,
|
|
91
|
+
raw: match[0].trim(),
|
|
92
|
+
type: extractField(body, 'Type'),
|
|
93
|
+
tags: extractField(body, 'Tags'),
|
|
94
|
+
request: extractField(body, 'Request'),
|
|
95
|
+
result: extractField(body, 'Result'),
|
|
96
|
+
files: extractField(body, 'Files')
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return entries;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Extract a field value from entry body
|
|
105
|
+
*/
|
|
106
|
+
function extractField(text, field) {
|
|
107
|
+
const match = text.match(new RegExp(`\\*\\*${field}\\*\\*:\\s*(.+)`, 'i'));
|
|
108
|
+
return match ? match[1].trim() : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the header portion of the log (before entries)
|
|
113
|
+
*/
|
|
114
|
+
function getLogHeader(content) {
|
|
115
|
+
// Find where entries start (first ### R-)
|
|
116
|
+
const entryStart = content.indexOf('### R-');
|
|
117
|
+
if (entryStart === -1) {
|
|
118
|
+
return content;
|
|
119
|
+
}
|
|
120
|
+
return content.slice(0, entryStart);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================
|
|
124
|
+
// Archive Operations
|
|
125
|
+
// ============================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if archival is needed based on entry count
|
|
129
|
+
*/
|
|
130
|
+
function shouldArchive() {
|
|
131
|
+
const config = getLogManagerConfig();
|
|
132
|
+
if (!config.autoArchive) return false;
|
|
133
|
+
|
|
134
|
+
const currentCount = countRequestLogEntries();
|
|
135
|
+
return currentCount > config.maxRecentEntries;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Archive old entries and optionally create summary
|
|
140
|
+
* Returns { archived, remaining, archivePath }
|
|
141
|
+
*/
|
|
142
|
+
function archiveOldEntries() {
|
|
143
|
+
const config = getLogManagerConfig();
|
|
144
|
+
|
|
145
|
+
if (!fileExists(LOG_PATH)) {
|
|
146
|
+
return { archived: 0, remaining: 0, archivePath: null };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const content = readFile(LOG_PATH, '');
|
|
150
|
+
const entries = parseEntries(content);
|
|
151
|
+
|
|
152
|
+
if (entries.length <= config.keepRecent) {
|
|
153
|
+
return { archived: 0, remaining: entries.length, archivePath: null };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Split into archive and keep
|
|
157
|
+
const toArchive = entries.slice(0, entries.length - config.keepRecent);
|
|
158
|
+
const toKeep = entries.slice(-config.keepRecent);
|
|
159
|
+
|
|
160
|
+
// Ensure archive directory exists
|
|
161
|
+
if (!dirExists(ARCHIVE_DIR)) {
|
|
162
|
+
fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Create archive file (monthly grouping)
|
|
166
|
+
const archiveDate = new Date().toISOString().slice(0, 7); // YYYY-MM
|
|
167
|
+
const archivePath = path.join(ARCHIVE_DIR, `request-log-${archiveDate}.md`);
|
|
168
|
+
|
|
169
|
+
// Prepare archive content
|
|
170
|
+
const archiveContent = toArchive.map(e => e.raw).join('\n\n');
|
|
171
|
+
|
|
172
|
+
// Write to archive first - only update main log if archive succeeds
|
|
173
|
+
try {
|
|
174
|
+
if (fileExists(archivePath)) {
|
|
175
|
+
// Append to existing archive
|
|
176
|
+
const existing = readFile(archivePath, '');
|
|
177
|
+
writeFile(archivePath, existing + '\n\n' + archiveContent);
|
|
178
|
+
} else {
|
|
179
|
+
// Create new archive with header
|
|
180
|
+
const archiveHeader = `# Request Log Archive - ${archiveDate}
|
|
181
|
+
|
|
182
|
+
Archived entries from request-log.md.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
`;
|
|
187
|
+
writeFile(archivePath, archiveHeader + archiveContent);
|
|
188
|
+
}
|
|
189
|
+
} catch (err) {
|
|
190
|
+
// Archive write failed - don't update main log to prevent data loss
|
|
191
|
+
console.error(`Failed to write archive: ${err.message}`);
|
|
192
|
+
return {
|
|
193
|
+
archived: 0,
|
|
194
|
+
remaining: entries.length,
|
|
195
|
+
error: `Archive write failed: ${err.message}`
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Update summary if enabled
|
|
200
|
+
if (config.createSummary) {
|
|
201
|
+
updateSummary(toArchive, archiveDate);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Rewrite main log with only recent entries (only after archive succeeded)
|
|
205
|
+
const header = getLogHeader(content);
|
|
206
|
+
const recentContent = header + toKeep.map(e => e.raw).join('\n\n') + '\n';
|
|
207
|
+
writeFile(LOG_PATH, recentContent);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
archived: toArchive.length,
|
|
211
|
+
remaining: toKeep.length,
|
|
212
|
+
archivePath
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update running summary of archived entries
|
|
218
|
+
*/
|
|
219
|
+
function updateSummary(archivedEntries, archiveDate) {
|
|
220
|
+
let summary;
|
|
221
|
+
|
|
222
|
+
if (fileExists(SUMMARY_PATH)) {
|
|
223
|
+
summary = readFile(SUMMARY_PATH, '');
|
|
224
|
+
} else {
|
|
225
|
+
summary = `# Request Log Summary
|
|
226
|
+
|
|
227
|
+
Compressed history of archived entries.
|
|
228
|
+
Search archives in \`.workflow/archive/\` for full details.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Archive Summary
|
|
233
|
+
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Group archived entries by type
|
|
238
|
+
const byType = {};
|
|
239
|
+
for (const entry of archivedEntries) {
|
|
240
|
+
const type = entry.type || 'other';
|
|
241
|
+
if (!byType[type]) byType[type] = [];
|
|
242
|
+
|
|
243
|
+
// Extract brief description from request
|
|
244
|
+
let brief = entry.request || entry.id;
|
|
245
|
+
brief = brief.replace(/^["']|["']$/g, ''); // Remove quotes
|
|
246
|
+
if (brief.length > 50) {
|
|
247
|
+
brief = brief.slice(0, 47) + '...';
|
|
248
|
+
}
|
|
249
|
+
byType[type].push(brief);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Create summary section for this archive batch
|
|
253
|
+
const date = new Date().toISOString().split('T')[0];
|
|
254
|
+
let newSummary = `\n### Archived ${date} (${archivedEntries.length} entries)\n`;
|
|
255
|
+
|
|
256
|
+
for (const [type, requests] of Object.entries(byType)) {
|
|
257
|
+
const displayItems = requests.slice(0, 3);
|
|
258
|
+
const remaining = requests.length - displayItems.length;
|
|
259
|
+
const suffix = remaining > 0 ? ` (+${remaining} more)` : '';
|
|
260
|
+
newSummary += `- **${type}**: ${displayItems.join('; ')}${suffix}\n`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
writeFile(SUMMARY_PATH, summary + newSummary);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ============================================================
|
|
267
|
+
// Auto-Archive Hook
|
|
268
|
+
// ============================================================
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Automatically archive if needed
|
|
272
|
+
* Safe to call frequently - only archives when threshold exceeded
|
|
273
|
+
* Returns result object or null if no action taken
|
|
274
|
+
*/
|
|
275
|
+
function autoArchiveIfNeeded() {
|
|
276
|
+
const config = getLogManagerConfig();
|
|
277
|
+
|
|
278
|
+
if (!config.autoArchive) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!shouldArchive()) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return archiveOldEntries();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ============================================================
|
|
290
|
+
// Search Operations
|
|
291
|
+
// ============================================================
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Search entries in both current log and archives
|
|
295
|
+
* @param {string} query - Search term (tag, type, or text)
|
|
296
|
+
* @param {object} options - { searchArchives, maxResults }
|
|
297
|
+
*/
|
|
298
|
+
function searchEntries(query, options = {}) {
|
|
299
|
+
const {
|
|
300
|
+
searchArchives = true,
|
|
301
|
+
maxResults = 20
|
|
302
|
+
} = options;
|
|
303
|
+
|
|
304
|
+
const results = [];
|
|
305
|
+
const queryLower = query.toLowerCase();
|
|
306
|
+
|
|
307
|
+
// Search current log
|
|
308
|
+
if (fileExists(LOG_PATH)) {
|
|
309
|
+
const content = readFile(LOG_PATH, '');
|
|
310
|
+
const entries = parseEntries(content);
|
|
311
|
+
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (matchesQuery(entry, queryLower)) {
|
|
314
|
+
results.push({ ...entry, source: 'current' });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Search archives
|
|
320
|
+
if (searchArchives && dirExists(ARCHIVE_DIR)) {
|
|
321
|
+
try {
|
|
322
|
+
const archiveFiles = fs.readdirSync(ARCHIVE_DIR)
|
|
323
|
+
.filter(f => f.startsWith('request-log-') && f.endsWith('.md'))
|
|
324
|
+
.sort()
|
|
325
|
+
.reverse(); // Most recent first
|
|
326
|
+
|
|
327
|
+
for (const file of archiveFiles) {
|
|
328
|
+
if (results.length >= maxResults) break;
|
|
329
|
+
|
|
330
|
+
const archivePath = path.join(ARCHIVE_DIR, file);
|
|
331
|
+
const content = readFile(archivePath, '');
|
|
332
|
+
const entries = parseEntries(content);
|
|
333
|
+
|
|
334
|
+
for (const entry of entries) {
|
|
335
|
+
if (results.length >= maxResults) break;
|
|
336
|
+
if (matchesQuery(entry, queryLower)) {
|
|
337
|
+
results.push({ ...entry, source: file });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
// Ignore errors reading archives
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return results.slice(0, maxResults);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Check if entry matches search query
|
|
351
|
+
*/
|
|
352
|
+
function matchesQuery(entry, queryLower) {
|
|
353
|
+
const searchable = [
|
|
354
|
+
entry.tags,
|
|
355
|
+
entry.type,
|
|
356
|
+
entry.request,
|
|
357
|
+
entry.result,
|
|
358
|
+
entry.raw
|
|
359
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
360
|
+
|
|
361
|
+
return searchable.includes(queryLower);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================
|
|
365
|
+
// Statistics
|
|
366
|
+
// ============================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get log statistics
|
|
370
|
+
*/
|
|
371
|
+
function getLogStats() {
|
|
372
|
+
const stats = {
|
|
373
|
+
currentEntries: countRequestLogEntries(),
|
|
374
|
+
archiveFiles: 0,
|
|
375
|
+
archivedEntries: 0,
|
|
376
|
+
hasSummary: fileExists(SUMMARY_PATH)
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// Count archive entries
|
|
380
|
+
if (dirExists(ARCHIVE_DIR)) {
|
|
381
|
+
try {
|
|
382
|
+
const archiveFiles = fs.readdirSync(ARCHIVE_DIR)
|
|
383
|
+
.filter(f => f.startsWith('request-log-') && f.endsWith('.md'));
|
|
384
|
+
|
|
385
|
+
stats.archiveFiles = archiveFiles.length;
|
|
386
|
+
|
|
387
|
+
for (const file of archiveFiles) {
|
|
388
|
+
const content = readFile(path.join(ARCHIVE_DIR, file), '');
|
|
389
|
+
const entries = parseEntries(content);
|
|
390
|
+
stats.archivedEntries += entries.length;
|
|
391
|
+
}
|
|
392
|
+
} catch {
|
|
393
|
+
// Ignore errors
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
stats.totalEntries = stats.currentEntries + stats.archivedEntries;
|
|
398
|
+
return stats;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ============================================================
|
|
402
|
+
// CLI Interface
|
|
403
|
+
// ============================================================
|
|
404
|
+
|
|
405
|
+
function printUsage() {
|
|
406
|
+
console.log(`
|
|
407
|
+
Usage: flow-log-manager.js [command] [args]
|
|
408
|
+
|
|
409
|
+
Commands:
|
|
410
|
+
status Show log statistics
|
|
411
|
+
check Check if archiving is needed
|
|
412
|
+
archive Force archive old entries
|
|
413
|
+
search <query> Search entries (current + archives)
|
|
414
|
+
list-archives List archive files
|
|
415
|
+
--help Show this help
|
|
416
|
+
|
|
417
|
+
Examples:
|
|
418
|
+
node scripts/flow-log-manager.js status
|
|
419
|
+
node scripts/flow-log-manager.js search "#component:Button"
|
|
420
|
+
node scripts/flow-log-manager.js archive
|
|
421
|
+
`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Main CLI handler
|
|
425
|
+
if (require.main === module) {
|
|
426
|
+
const args = process.argv.slice(2);
|
|
427
|
+
const command = args[0];
|
|
428
|
+
|
|
429
|
+
switch (command) {
|
|
430
|
+
case 'status': {
|
|
431
|
+
const stats = getLogStats();
|
|
432
|
+
const config = getLogManagerConfig();
|
|
433
|
+
|
|
434
|
+
printHeader('Request Log Status');
|
|
435
|
+
console.log(`Current entries: ${stats.currentEntries}`);
|
|
436
|
+
console.log(`Archive files: ${stats.archiveFiles}`);
|
|
437
|
+
console.log(`Archived entries: ${stats.archivedEntries}`);
|
|
438
|
+
console.log(`Total entries: ${stats.totalEntries}`);
|
|
439
|
+
console.log(`Summary exists: ${stats.hasSummary ? 'yes' : 'no'}`);
|
|
440
|
+
console.log('');
|
|
441
|
+
console.log(color('dim', `Config: autoArchive=${config.autoArchive}, maxRecent=${config.maxRecentEntries}, keep=${config.keepRecent}`));
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
case 'check': {
|
|
446
|
+
if (shouldArchive()) {
|
|
447
|
+
const stats = getLogStats();
|
|
448
|
+
const config = getLogManagerConfig();
|
|
449
|
+
warn(`Archive recommended: ${stats.currentEntries} entries (threshold: ${config.maxRecentEntries})`);
|
|
450
|
+
} else {
|
|
451
|
+
success('No archiving needed');
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
case 'archive': {
|
|
457
|
+
const result = archiveOldEntries();
|
|
458
|
+
if (result.archived > 0) {
|
|
459
|
+
success(`Archived ${result.archived} entries to ${result.archivePath}`);
|
|
460
|
+
console.log(`Remaining in log: ${result.remaining} entries`);
|
|
461
|
+
} else {
|
|
462
|
+
console.log('No entries needed archiving');
|
|
463
|
+
}
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
case 'search': {
|
|
468
|
+
const query = args.slice(1).join(' ');
|
|
469
|
+
if (!query) {
|
|
470
|
+
error('Please provide a search query');
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const results = searchEntries(query);
|
|
475
|
+
if (results.length === 0) {
|
|
476
|
+
console.log(`No entries found for: ${query}`);
|
|
477
|
+
} else {
|
|
478
|
+
printHeader(`Search Results: "${query}"`);
|
|
479
|
+
for (const entry of results) {
|
|
480
|
+
console.log(`\n${color('cyan', entry.id)} (${entry.source})`);
|
|
481
|
+
if (entry.type) console.log(` Type: ${entry.type}`);
|
|
482
|
+
if (entry.request) console.log(` Request: ${entry.request}`);
|
|
483
|
+
if (entry.tags) console.log(` Tags: ${entry.tags}`);
|
|
484
|
+
}
|
|
485
|
+
console.log(`\n${results.length} result(s) found`);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
case 'list-archives': {
|
|
491
|
+
if (!dirExists(ARCHIVE_DIR)) {
|
|
492
|
+
console.log('No archive directory');
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const files = fs.readdirSync(ARCHIVE_DIR)
|
|
497
|
+
.filter(f => f.startsWith('request-log-') && f.endsWith('.md'))
|
|
498
|
+
.sort()
|
|
499
|
+
.reverse();
|
|
500
|
+
|
|
501
|
+
if (files.length === 0) {
|
|
502
|
+
console.log('No archive files');
|
|
503
|
+
} else {
|
|
504
|
+
printHeader('Archive Files');
|
|
505
|
+
for (const file of files) {
|
|
506
|
+
const stat = fs.statSync(path.join(ARCHIVE_DIR, file));
|
|
507
|
+
const size = Math.round(stat.size / 1024);
|
|
508
|
+
console.log(` ${file} (${size}KB)`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
case '--help':
|
|
515
|
+
case '-h':
|
|
516
|
+
printUsage();
|
|
517
|
+
break;
|
|
518
|
+
|
|
519
|
+
default:
|
|
520
|
+
if (command) {
|
|
521
|
+
error(`Unknown command: ${command}`);
|
|
522
|
+
}
|
|
523
|
+
printUsage();
|
|
524
|
+
process.exit(command ? 1 : 0);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ============================================================
|
|
529
|
+
// Exports
|
|
530
|
+
// ============================================================
|
|
531
|
+
|
|
532
|
+
module.exports = {
|
|
533
|
+
// Configuration
|
|
534
|
+
getLogManagerConfig,
|
|
535
|
+
DEFAULTS,
|
|
536
|
+
|
|
537
|
+
// Parsing
|
|
538
|
+
parseEntries,
|
|
539
|
+
extractField,
|
|
540
|
+
getLogHeader,
|
|
541
|
+
|
|
542
|
+
// Archive operations
|
|
543
|
+
shouldArchive,
|
|
544
|
+
archiveOldEntries,
|
|
545
|
+
autoArchiveIfNeeded,
|
|
546
|
+
updateSummary,
|
|
547
|
+
|
|
548
|
+
// Search
|
|
549
|
+
searchEntries,
|
|
550
|
+
matchesQuery,
|
|
551
|
+
|
|
552
|
+
// Statistics
|
|
553
|
+
getLogStats,
|
|
554
|
+
|
|
555
|
+
// Paths
|
|
556
|
+
LOG_PATH,
|
|
557
|
+
ARCHIVE_DIR,
|
|
558
|
+
SUMMARY_PATH
|
|
559
|
+
};
|