agileflow 2.90.6 → 2.91.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/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/codebase-indexer.js +810 -0
- package/lib/validate-names.js +3 -3
- package/package.json +4 -1
- package/scripts/obtain-context.js +238 -0
- package/scripts/precompact-context.sh +13 -1
- package/scripts/query-codebase.js +430 -0
- package/scripts/tui/blessed/data/watcher.js +175 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +95 -0
- package/scripts/tui/blessed/panels/sessions.js +143 -0
- package/scripts/tui/blessed/panels/trace.js +91 -0
- package/scripts/tui/blessed/ui/help.js +77 -0
- package/scripts/tui/blessed/ui/screen.js +52 -0
- package/scripts/tui/blessed/ui/statusbar.js +51 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -32
- package/scripts/tui/simple-tui.js +8 -5
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +212 -0
- package/scripts/validators/json-schema-validator.js +179 -0
- package/scripts/validators/markdown-validator.js +153 -0
- package/scripts/validators/migration-validator.js +117 -0
- package/scripts/validators/security-validator.js +276 -0
- package/scripts/validators/story-format-validator.js +176 -0
- package/scripts/validators/test-result-validator.js +99 -0
- package/scripts/validators/workflow-validator.js +240 -0
- package/src/core/agents/accessibility.md +6 -0
- package/src/core/agents/adr-writer.md +6 -0
- package/src/core/agents/analytics.md +6 -0
- package/src/core/agents/api.md +6 -0
- package/src/core/agents/ci.md +6 -0
- package/src/core/agents/codebase-query.md +237 -0
- package/src/core/agents/compliance.md +6 -0
- package/src/core/agents/configuration-damage-control.md +6 -0
- package/src/core/agents/configuration-visual-e2e.md +6 -0
- package/src/core/agents/database.md +10 -0
- package/src/core/agents/datamigration.md +6 -0
- package/src/core/agents/design.md +6 -0
- package/src/core/agents/devops.md +6 -0
- package/src/core/agents/documentation.md +6 -0
- package/src/core/agents/epic-planner.md +6 -0
- package/src/core/agents/integrations.md +6 -0
- package/src/core/agents/mentor.md +6 -0
- package/src/core/agents/mobile.md +6 -0
- package/src/core/agents/monitoring.md +6 -0
- package/src/core/agents/multi-expert.md +6 -0
- package/src/core/agents/performance.md +6 -0
- package/src/core/agents/product.md +6 -0
- package/src/core/agents/qa.md +6 -0
- package/src/core/agents/readme-updater.md +6 -0
- package/src/core/agents/refactor.md +6 -0
- package/src/core/agents/research.md +6 -0
- package/src/core/agents/security.md +6 -0
- package/src/core/agents/testing.md +10 -0
- package/src/core/agents/ui.md +6 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/epic.md +92 -1
- package/src/core/commands/help.md +1 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/research/analyze.md +1 -0
- package/src/core/commands/research/ask.md +2 -0
- package/src/core/commands/research/import.md +1 -0
- package/src/core/commands/research/list.md +2 -0
- package/src/core/commands/research/synthesize.md +584 -0
- package/src/core/commands/research/view.md +2 -0
- package/src/core/commands/status.md +126 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/experts/codebase-query/expertise.yaml +190 -0
- package/src/core/experts/codebase-query/question.md +73 -0
- package/src/core/experts/codebase-query/self-improve.md +105 -0
- package/tools/cli/commands/tui.js +40 -271
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* query-codebase.js
|
|
4
|
+
*
|
|
5
|
+
* Query engine for programmatic codebase searches.
|
|
6
|
+
* Part of the RLM-inspired Codebase Query Interface (EP-0021).
|
|
7
|
+
*
|
|
8
|
+
* Uses indexing and programmatic search instead of loading full context,
|
|
9
|
+
* following RLM principles: virtualize documents and query programmatically.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node query-codebase.js --build-index # Build/rebuild index
|
|
13
|
+
* node query-codebase.js --query="auth files" # Search by pattern/keyword
|
|
14
|
+
* node query-codebase.js --deps="src/api.js" # Show dependencies
|
|
15
|
+
* node query-codebase.js --content="validate" # Search file content
|
|
16
|
+
* node query-codebase.js --tag="api" # Search by tag
|
|
17
|
+
* node query-codebase.js --export="login" # Find export locations
|
|
18
|
+
*
|
|
19
|
+
* Options:
|
|
20
|
+
* --project=<path> Project root (default: cwd)
|
|
21
|
+
* --budget=<chars> Token budget for output (default: 15000)
|
|
22
|
+
* --json Output as JSON
|
|
23
|
+
* --verbose Show debug info
|
|
24
|
+
*
|
|
25
|
+
* Exit codes:
|
|
26
|
+
* 0 = Success
|
|
27
|
+
* 1 = Error
|
|
28
|
+
* 2 = No results
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
const {
|
|
34
|
+
buildIndex,
|
|
35
|
+
updateIndex,
|
|
36
|
+
getIndex,
|
|
37
|
+
queryFiles,
|
|
38
|
+
queryByTag,
|
|
39
|
+
queryByExport,
|
|
40
|
+
getDependencies,
|
|
41
|
+
} = require('../lib/codebase-indexer');
|
|
42
|
+
|
|
43
|
+
// Default configuration
|
|
44
|
+
const DEFAULT_BUDGET = 15000;
|
|
45
|
+
|
|
46
|
+
// Parse command line arguments
|
|
47
|
+
function parseArgs(argv) {
|
|
48
|
+
const args = {
|
|
49
|
+
buildIndex: false,
|
|
50
|
+
query: null,
|
|
51
|
+
deps: null,
|
|
52
|
+
content: null,
|
|
53
|
+
tag: null,
|
|
54
|
+
export: null,
|
|
55
|
+
project: process.cwd(),
|
|
56
|
+
budget: DEFAULT_BUDGET,
|
|
57
|
+
json: false,
|
|
58
|
+
verbose: false,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (const arg of argv.slice(2)) {
|
|
62
|
+
if (arg === '--build-index') {
|
|
63
|
+
args.buildIndex = true;
|
|
64
|
+
} else if (arg === '--json') {
|
|
65
|
+
args.json = true;
|
|
66
|
+
} else if (arg === '--verbose') {
|
|
67
|
+
args.verbose = true;
|
|
68
|
+
} else if (arg.startsWith('--query=')) {
|
|
69
|
+
args.query = arg.slice(8);
|
|
70
|
+
} else if (arg.startsWith('--deps=')) {
|
|
71
|
+
args.deps = arg.slice(7);
|
|
72
|
+
} else if (arg.startsWith('--content=')) {
|
|
73
|
+
args.content = arg.slice(10);
|
|
74
|
+
} else if (arg.startsWith('--tag=')) {
|
|
75
|
+
args.tag = arg.slice(6);
|
|
76
|
+
} else if (arg.startsWith('--export=')) {
|
|
77
|
+
args.export = arg.slice(9);
|
|
78
|
+
} else if (arg.startsWith('--project=')) {
|
|
79
|
+
args.project = arg.slice(10);
|
|
80
|
+
} else if (arg.startsWith('--budget=')) {
|
|
81
|
+
args.budget = parseInt(arg.slice(9), 10) || DEFAULT_BUDGET;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return args;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Search file content using grep-style regex
|
|
89
|
+
function queryContent(projectRoot, pattern, budget) {
|
|
90
|
+
const results = [];
|
|
91
|
+
let totalChars = 0;
|
|
92
|
+
|
|
93
|
+
// Get list of files to search
|
|
94
|
+
const indexResult = getIndex(projectRoot);
|
|
95
|
+
if (!indexResult.ok) {
|
|
96
|
+
return { ok: false, error: indexResult.error };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const regex = new RegExp(pattern, 'gi');
|
|
100
|
+
const files = Object.keys(indexResult.data.files);
|
|
101
|
+
|
|
102
|
+
for (const relativePath of files) {
|
|
103
|
+
const fullPath = path.join(projectRoot, relativePath);
|
|
104
|
+
const fileType = indexResult.data.files[relativePath].type;
|
|
105
|
+
|
|
106
|
+
// Only search code files
|
|
107
|
+
if (!['javascript', 'typescript', 'javascript-react', 'typescript-react'].includes(fileType)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
113
|
+
const lines = content.split('\n');
|
|
114
|
+
const matches = [];
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < lines.length; i++) {
|
|
117
|
+
if (regex.test(lines[i])) {
|
|
118
|
+
// Include context (2 lines before/after)
|
|
119
|
+
const start = Math.max(0, i - 2);
|
|
120
|
+
const end = Math.min(lines.length - 1, i + 2);
|
|
121
|
+
const context = lines.slice(start, end + 1).map((line, idx) => ({
|
|
122
|
+
lineNumber: start + idx + 1,
|
|
123
|
+
content: line,
|
|
124
|
+
isMatch: start + idx === i,
|
|
125
|
+
}));
|
|
126
|
+
matches.push({ line: i + 1, context });
|
|
127
|
+
}
|
|
128
|
+
regex.lastIndex = 0; // Reset regex state
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (matches.length > 0) {
|
|
132
|
+
const result = { file: relativePath, matches };
|
|
133
|
+
const resultChars = JSON.stringify(result).length;
|
|
134
|
+
|
|
135
|
+
if (totalChars + resultChars > budget) {
|
|
136
|
+
results.push({
|
|
137
|
+
file: '...',
|
|
138
|
+
matches: [{ line: 0, context: [{ lineNumber: 0, content: `[Truncated: budget exceeded]`, isMatch: false }] }],
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
results.push(result);
|
|
144
|
+
totalChars += resultChars;
|
|
145
|
+
}
|
|
146
|
+
} catch (err) {
|
|
147
|
+
// Skip unreadable files
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { ok: true, data: results };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Format output for human readability
|
|
155
|
+
function formatResults(results, type) {
|
|
156
|
+
const lines = [];
|
|
157
|
+
|
|
158
|
+
switch (type) {
|
|
159
|
+
case 'files':
|
|
160
|
+
lines.push(`Found ${results.length} file(s):`);
|
|
161
|
+
for (const file of results) {
|
|
162
|
+
lines.push(` ${file}`);
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case 'content':
|
|
167
|
+
lines.push(`Found matches in ${results.length} file(s):`);
|
|
168
|
+
for (const result of results) {
|
|
169
|
+
lines.push(`\n${result.file}:`);
|
|
170
|
+
for (const match of result.matches) {
|
|
171
|
+
for (const ctx of match.context) {
|
|
172
|
+
const marker = ctx.isMatch ? '>' : ' ';
|
|
173
|
+
lines.push(`${marker} ${ctx.lineNumber}: ${ctx.content}`);
|
|
174
|
+
}
|
|
175
|
+
lines.push(' ---');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case 'deps':
|
|
181
|
+
lines.push('Dependencies:');
|
|
182
|
+
if (results.imports.length > 0) {
|
|
183
|
+
lines.push('\nImports:');
|
|
184
|
+
for (const imp of results.imports) {
|
|
185
|
+
lines.push(` ${imp}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (results.importedBy.length > 0) {
|
|
189
|
+
lines.push('\nImported by:');
|
|
190
|
+
for (const dep of results.importedBy) {
|
|
191
|
+
lines.push(` ${dep}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case 'index':
|
|
197
|
+
lines.push('Index Statistics:');
|
|
198
|
+
lines.push(` Total files: ${results.stats.total_files}`);
|
|
199
|
+
lines.push(` Indexed files: ${results.stats.indexed_files}`);
|
|
200
|
+
lines.push(` Build time: ${results.stats.build_time_ms}ms`);
|
|
201
|
+
lines.push(` Tags: ${Object.keys(results.tags).length}`);
|
|
202
|
+
lines.push(` Exports tracked: ${Object.keys(results.symbols.exports).length}`);
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
default:
|
|
206
|
+
lines.push(JSON.stringify(results, null, 2));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return lines.join('\n');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Truncate output to budget
|
|
213
|
+
function truncateOutput(output, budget) {
|
|
214
|
+
if (output.length <= budget) {
|
|
215
|
+
return output;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const truncated = output.slice(0, budget - 50);
|
|
219
|
+
const lastNewline = truncated.lastIndexOf('\n');
|
|
220
|
+
return truncated.slice(0, lastNewline) + '\n\n... [Truncated: output exceeded budget]';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Main execution
|
|
224
|
+
async function main() {
|
|
225
|
+
const args = parseArgs(process.argv);
|
|
226
|
+
|
|
227
|
+
if (args.verbose) {
|
|
228
|
+
console.error('Args:', JSON.stringify(args, null, 2));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check project exists
|
|
232
|
+
if (!fs.existsSync(args.project)) {
|
|
233
|
+
console.error(`Error: Project not found: ${args.project}`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let result;
|
|
238
|
+
let outputType;
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
// Handle --build-index
|
|
242
|
+
if (args.buildIndex) {
|
|
243
|
+
if (args.verbose) console.error('Building index...');
|
|
244
|
+
result = buildIndex(args.project);
|
|
245
|
+
outputType = 'index';
|
|
246
|
+
|
|
247
|
+
if (!result.ok) {
|
|
248
|
+
console.error(`Error: ${result.error}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const output = args.json
|
|
253
|
+
? JSON.stringify(result.data, null, 2)
|
|
254
|
+
: formatResults(result.data, outputType);
|
|
255
|
+
console.log(truncateOutput(output, args.budget));
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Handle --query (glob pattern search)
|
|
260
|
+
if (args.query) {
|
|
261
|
+
if (args.verbose) console.error(`Querying: ${args.query}`);
|
|
262
|
+
|
|
263
|
+
// First try to get/update index
|
|
264
|
+
const indexResult = updateIndex(args.project);
|
|
265
|
+
if (!indexResult.ok) {
|
|
266
|
+
console.error(`Error: ${indexResult.error}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Interpret query
|
|
271
|
+
const query = args.query.toLowerCase();
|
|
272
|
+
let files = [];
|
|
273
|
+
|
|
274
|
+
// If query looks like a glob, use it directly
|
|
275
|
+
if (query.includes('*') || query.includes('/')) {
|
|
276
|
+
files = queryFiles(indexResult.data, args.query);
|
|
277
|
+
} else {
|
|
278
|
+
// Otherwise, search multiple ways:
|
|
279
|
+
// 1. Files containing query in name
|
|
280
|
+
files = queryFiles(indexResult.data, `**/*${args.query}*`);
|
|
281
|
+
|
|
282
|
+
// 2. Files with matching tag
|
|
283
|
+
const tagFiles = queryByTag(indexResult.data, query);
|
|
284
|
+
files = [...new Set([...files, ...tagFiles])];
|
|
285
|
+
|
|
286
|
+
// 3. Files exporting symbol
|
|
287
|
+
const exportFiles = queryByExport(indexResult.data, args.query);
|
|
288
|
+
files = [...new Set([...files, ...exportFiles])];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (files.length === 0) {
|
|
292
|
+
console.error(`No files found matching: ${args.query}`);
|
|
293
|
+
process.exit(2);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const output = args.json
|
|
297
|
+
? JSON.stringify(files, null, 2)
|
|
298
|
+
: formatResults(files, 'files');
|
|
299
|
+
console.log(truncateOutput(output, args.budget));
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle --content (grep-style search)
|
|
304
|
+
if (args.content) {
|
|
305
|
+
if (args.verbose) console.error(`Searching content: ${args.content}`);
|
|
306
|
+
|
|
307
|
+
result = queryContent(args.project, args.content, args.budget);
|
|
308
|
+
|
|
309
|
+
if (!result.ok) {
|
|
310
|
+
console.error(`Error: ${result.error}`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (result.data.length === 0) {
|
|
315
|
+
console.error(`No content matches for: ${args.content}`);
|
|
316
|
+
process.exit(2);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const output = args.json
|
|
320
|
+
? JSON.stringify(result.data, null, 2)
|
|
321
|
+
: formatResults(result.data, 'content');
|
|
322
|
+
console.log(truncateOutput(output, args.budget));
|
|
323
|
+
process.exit(0);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Handle --tag
|
|
327
|
+
if (args.tag) {
|
|
328
|
+
if (args.verbose) console.error(`Searching tag: ${args.tag}`);
|
|
329
|
+
|
|
330
|
+
const indexResult = getIndex(args.project);
|
|
331
|
+
if (!indexResult.ok) {
|
|
332
|
+
console.error(`Error: ${indexResult.error}`);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const files = queryByTag(indexResult.data, args.tag);
|
|
337
|
+
|
|
338
|
+
if (files.length === 0) {
|
|
339
|
+
console.error(`No files with tag: ${args.tag}`);
|
|
340
|
+
process.exit(2);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const output = args.json
|
|
344
|
+
? JSON.stringify(files, null, 2)
|
|
345
|
+
: formatResults(files, 'files');
|
|
346
|
+
console.log(truncateOutput(output, args.budget));
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Handle --export
|
|
351
|
+
if (args.export) {
|
|
352
|
+
if (args.verbose) console.error(`Searching export: ${args.export}`);
|
|
353
|
+
|
|
354
|
+
const indexResult = getIndex(args.project);
|
|
355
|
+
if (!indexResult.ok) {
|
|
356
|
+
console.error(`Error: ${indexResult.error}`);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const files = queryByExport(indexResult.data, args.export);
|
|
361
|
+
|
|
362
|
+
if (files.length === 0) {
|
|
363
|
+
console.error(`No files export: ${args.export}`);
|
|
364
|
+
process.exit(2);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const output = args.json
|
|
368
|
+
? JSON.stringify(files, null, 2)
|
|
369
|
+
: formatResults(files, 'files');
|
|
370
|
+
console.log(truncateOutput(output, args.budget));
|
|
371
|
+
process.exit(0);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Handle --deps
|
|
375
|
+
if (args.deps) {
|
|
376
|
+
if (args.verbose) console.error(`Getting dependencies: ${args.deps}`);
|
|
377
|
+
|
|
378
|
+
const indexResult = getIndex(args.project);
|
|
379
|
+
if (!indexResult.ok) {
|
|
380
|
+
console.error(`Error: ${indexResult.error}`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const deps = getDependencies(indexResult.data, args.deps);
|
|
385
|
+
|
|
386
|
+
if (deps.imports.length === 0 && deps.importedBy.length === 0) {
|
|
387
|
+
console.error(`No dependencies found for: ${args.deps}`);
|
|
388
|
+
process.exit(2);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const output = args.json
|
|
392
|
+
? JSON.stringify(deps, null, 2)
|
|
393
|
+
: formatResults(deps, 'deps');
|
|
394
|
+
console.log(truncateOutput(output, args.budget));
|
|
395
|
+
process.exit(0);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// No action specified - show help
|
|
399
|
+
console.log(`Usage: node query-codebase.js <command>
|
|
400
|
+
|
|
401
|
+
Commands:
|
|
402
|
+
--build-index Build/rebuild codebase index
|
|
403
|
+
--query="<pattern>" Search files by pattern or keyword
|
|
404
|
+
--content="<regex>" Search file content (grep-style)
|
|
405
|
+
--tag="<tag>" Find files by tag (api, ui, auth, etc.)
|
|
406
|
+
--export="<name>" Find files exporting a symbol
|
|
407
|
+
--deps="<file>" Show file dependencies
|
|
408
|
+
|
|
409
|
+
Options:
|
|
410
|
+
--project=<path> Project root (default: cwd)
|
|
411
|
+
--budget=<chars> Output budget in characters (default: 15000)
|
|
412
|
+
--json Output as JSON
|
|
413
|
+
--verbose Show debug info
|
|
414
|
+
|
|
415
|
+
Exit codes:
|
|
416
|
+
0 = Success
|
|
417
|
+
1 = Error
|
|
418
|
+
2 = No results
|
|
419
|
+
`);
|
|
420
|
+
process.exit(0);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
console.error(`Error: ${err.message}`);
|
|
423
|
+
if (args.verbose) {
|
|
424
|
+
console.error(err.stack);
|
|
425
|
+
}
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
main();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* DataWatcher - watches status.json and log files for changes
|
|
9
|
+
* Triggers refresh callback when data updates
|
|
10
|
+
*/
|
|
11
|
+
class DataWatcher {
|
|
12
|
+
constructor(state, onUpdate) {
|
|
13
|
+
this.state = state;
|
|
14
|
+
this.onUpdate = onUpdate;
|
|
15
|
+
this.watcher = null;
|
|
16
|
+
this.cwd = process.cwd();
|
|
17
|
+
|
|
18
|
+
// Paths to watch
|
|
19
|
+
this.statusPath = path.join(this.cwd, 'docs/09-agents/status.json');
|
|
20
|
+
this.logPath = path.join(this.cwd, 'docs/09-agents/bus/log.jsonl');
|
|
21
|
+
this.sessionStatePath = path.join(this.cwd, '.agileflow/session-state.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
start() {
|
|
25
|
+
// Initial load
|
|
26
|
+
this.loadStatus();
|
|
27
|
+
this.loadLogs();
|
|
28
|
+
this.loadSessionState();
|
|
29
|
+
|
|
30
|
+
// Set up file watcher
|
|
31
|
+
const watchPaths = [this.statusPath, this.logPath, this.sessionStatePath].filter(p => {
|
|
32
|
+
const dir = path.dirname(p);
|
|
33
|
+
return fs.existsSync(dir);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (watchPaths.length === 0) {
|
|
37
|
+
// No paths to watch yet, check periodically
|
|
38
|
+
this.pollInterval = setInterval(() => {
|
|
39
|
+
this.loadStatus();
|
|
40
|
+
this.loadLogs();
|
|
41
|
+
this.loadSessionState();
|
|
42
|
+
this.onUpdate();
|
|
43
|
+
}, 2000);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.watcher = chokidar.watch(watchPaths, {
|
|
48
|
+
persistent: true,
|
|
49
|
+
ignoreInitial: true,
|
|
50
|
+
usePolling: false,
|
|
51
|
+
awaitWriteFinish: {
|
|
52
|
+
stabilityThreshold: 100,
|
|
53
|
+
pollInterval: 50
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.watcher.on('change', filePath => {
|
|
58
|
+
if (filePath.endsWith('status.json')) {
|
|
59
|
+
this.loadStatus();
|
|
60
|
+
} else if (filePath.endsWith('log.jsonl')) {
|
|
61
|
+
this.loadLogs();
|
|
62
|
+
} else if (filePath.endsWith('session-state.json')) {
|
|
63
|
+
this.loadSessionState();
|
|
64
|
+
}
|
|
65
|
+
this.onUpdate();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.watcher.on('error', err => {
|
|
69
|
+
// Silently handle watcher errors
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
loadStatus() {
|
|
74
|
+
try {
|
|
75
|
+
if (fs.existsSync(this.statusPath)) {
|
|
76
|
+
const content = fs.readFileSync(this.statusPath, 'utf8');
|
|
77
|
+
const data = JSON.parse(content);
|
|
78
|
+
|
|
79
|
+
// Extract stories with in_progress status as "sessions"
|
|
80
|
+
const stories = data.stories || [];
|
|
81
|
+
this.state.sessions = stories
|
|
82
|
+
.filter(s => s.status === 'in_progress')
|
|
83
|
+
.map((s, i) => ({
|
|
84
|
+
id: `S-${i + 1}`,
|
|
85
|
+
story: s.id || s.title || 'Unknown',
|
|
86
|
+
status: 'active',
|
|
87
|
+
duration: this.formatDuration(s.started_at),
|
|
88
|
+
progress: s.progress || '--'
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// If no active stories, show all stories summary
|
|
92
|
+
if (this.state.sessions.length === 0) {
|
|
93
|
+
const ready = stories.filter(s => s.status === 'ready').length;
|
|
94
|
+
const completed = stories.filter(s => s.status === 'completed').length;
|
|
95
|
+
this.state.sessions = [{
|
|
96
|
+
id: '--',
|
|
97
|
+
story: `${ready} ready, ${completed} completed`,
|
|
98
|
+
status: 'idle',
|
|
99
|
+
duration: '--',
|
|
100
|
+
progress: '--'
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
// Ignore parse errors
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
loadLogs() {
|
|
110
|
+
try {
|
|
111
|
+
if (fs.existsSync(this.logPath)) {
|
|
112
|
+
const content = fs.readFileSync(this.logPath, 'utf8');
|
|
113
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
114
|
+
|
|
115
|
+
// Take last 100 lines
|
|
116
|
+
this.state.logs = lines.slice(-100).map(line => {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(line);
|
|
119
|
+
} catch {
|
|
120
|
+
return { message: line, level: 'info' };
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
// Ignore read errors
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
loadSessionState() {
|
|
130
|
+
try {
|
|
131
|
+
if (fs.existsSync(this.sessionStatePath)) {
|
|
132
|
+
const content = fs.readFileSync(this.sessionStatePath, 'utf8');
|
|
133
|
+
const data = JSON.parse(content);
|
|
134
|
+
|
|
135
|
+
// Extract traces from active commands
|
|
136
|
+
if (data.active_commands) {
|
|
137
|
+
this.state.traces = data.active_commands.map((cmd, i) => ({
|
|
138
|
+
action: cmd.command || cmd,
|
|
139
|
+
status: 'running',
|
|
140
|
+
duration: '--',
|
|
141
|
+
details: cmd.args || ''
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// Ignore errors
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
formatDuration(startedAt) {
|
|
151
|
+
if (!startedAt) return '--';
|
|
152
|
+
try {
|
|
153
|
+
const start = new Date(startedAt);
|
|
154
|
+
const now = new Date();
|
|
155
|
+
const diff = Math.floor((now - start) / 1000);
|
|
156
|
+
|
|
157
|
+
if (diff < 60) return `${diff}s`;
|
|
158
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
|
|
159
|
+
return `${Math.floor(diff / 3600)}h ${Math.floor((diff % 3600) / 60)}m`;
|
|
160
|
+
} catch {
|
|
161
|
+
return '--';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
stop() {
|
|
166
|
+
if (this.watcher) {
|
|
167
|
+
this.watcher.close();
|
|
168
|
+
}
|
|
169
|
+
if (this.pollInterval) {
|
|
170
|
+
clearInterval(this.pollInterval);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = DataWatcher;
|