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.
Files changed (75) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/codebase-indexer.js +810 -0
  4. package/lib/validate-names.js +3 -3
  5. package/package.json +4 -1
  6. package/scripts/obtain-context.js +238 -0
  7. package/scripts/precompact-context.sh +13 -1
  8. package/scripts/query-codebase.js +430 -0
  9. package/scripts/tui/blessed/data/watcher.js +175 -0
  10. package/scripts/tui/blessed/index.js +244 -0
  11. package/scripts/tui/blessed/panels/output.js +95 -0
  12. package/scripts/tui/blessed/panels/sessions.js +143 -0
  13. package/scripts/tui/blessed/panels/trace.js +91 -0
  14. package/scripts/tui/blessed/ui/help.js +77 -0
  15. package/scripts/tui/blessed/ui/screen.js +52 -0
  16. package/scripts/tui/blessed/ui/statusbar.js +51 -0
  17. package/scripts/tui/blessed/ui/tabbar.js +99 -0
  18. package/scripts/tui/index.js +38 -32
  19. package/scripts/tui/simple-tui.js +8 -5
  20. package/scripts/validators/README.md +143 -0
  21. package/scripts/validators/component-validator.js +212 -0
  22. package/scripts/validators/json-schema-validator.js +179 -0
  23. package/scripts/validators/markdown-validator.js +153 -0
  24. package/scripts/validators/migration-validator.js +117 -0
  25. package/scripts/validators/security-validator.js +276 -0
  26. package/scripts/validators/story-format-validator.js +176 -0
  27. package/scripts/validators/test-result-validator.js +99 -0
  28. package/scripts/validators/workflow-validator.js +240 -0
  29. package/src/core/agents/accessibility.md +6 -0
  30. package/src/core/agents/adr-writer.md +6 -0
  31. package/src/core/agents/analytics.md +6 -0
  32. package/src/core/agents/api.md +6 -0
  33. package/src/core/agents/ci.md +6 -0
  34. package/src/core/agents/codebase-query.md +237 -0
  35. package/src/core/agents/compliance.md +6 -0
  36. package/src/core/agents/configuration-damage-control.md +6 -0
  37. package/src/core/agents/configuration-visual-e2e.md +6 -0
  38. package/src/core/agents/database.md +10 -0
  39. package/src/core/agents/datamigration.md +6 -0
  40. package/src/core/agents/design.md +6 -0
  41. package/src/core/agents/devops.md +6 -0
  42. package/src/core/agents/documentation.md +6 -0
  43. package/src/core/agents/epic-planner.md +6 -0
  44. package/src/core/agents/integrations.md +6 -0
  45. package/src/core/agents/mentor.md +6 -0
  46. package/src/core/agents/mobile.md +6 -0
  47. package/src/core/agents/monitoring.md +6 -0
  48. package/src/core/agents/multi-expert.md +6 -0
  49. package/src/core/agents/performance.md +6 -0
  50. package/src/core/agents/product.md +6 -0
  51. package/src/core/agents/qa.md +6 -0
  52. package/src/core/agents/readme-updater.md +6 -0
  53. package/src/core/agents/refactor.md +6 -0
  54. package/src/core/agents/research.md +6 -0
  55. package/src/core/agents/security.md +6 -0
  56. package/src/core/agents/testing.md +10 -0
  57. package/src/core/agents/ui.md +6 -0
  58. package/src/core/commands/audit.md +401 -0
  59. package/src/core/commands/board.md +1 -0
  60. package/src/core/commands/epic.md +92 -1
  61. package/src/core/commands/help.md +1 -0
  62. package/src/core/commands/metrics.md +1 -0
  63. package/src/core/commands/research/analyze.md +1 -0
  64. package/src/core/commands/research/ask.md +2 -0
  65. package/src/core/commands/research/import.md +1 -0
  66. package/src/core/commands/research/list.md +2 -0
  67. package/src/core/commands/research/synthesize.md +584 -0
  68. package/src/core/commands/research/view.md +2 -0
  69. package/src/core/commands/status.md +126 -1
  70. package/src/core/commands/story/list.md +9 -9
  71. package/src/core/commands/story/view.md +1 -0
  72. package/src/core/experts/codebase-query/expertise.yaml +190 -0
  73. package/src/core/experts/codebase-query/question.md +73 -0
  74. package/src/core/experts/codebase-query/self-improve.md +105 -0
  75. package/tools/cli/commands/tui.js +40 -271
@@ -29,9 +29,9 @@ const PATTERNS = {
29
29
  // Examples: default, my-profile, dev_config
30
30
  profileName: /^[a-zA-Z][a-zA-Z0-9_-]*$/,
31
31
 
32
- // Command name: alphanumeric with hyphens/colons, starts with letter
33
- // Examples: babysit, story:list, agileflow:configure
34
- commandName: /^[a-zA-Z][a-zA-Z0-9:-]*$/,
32
+ // Command name: alphanumeric with hyphens/colons/slashes, starts with letter
33
+ // Examples: babysit, story:list, agileflow:configure, research/ask
34
+ commandName: /^[a-zA-Z][a-zA-Z0-9:/-]*$/,
35
35
 
36
36
  // Session nickname: alphanumeric with hyphens/underscores
37
37
  // Examples: auth-work, feature_1, main
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.90.6",
3
+ "version": "2.91.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -53,7 +53,10 @@
53
53
  "test:coverage": "jest --coverage"
54
54
  },
55
55
  "dependencies": {
56
+ "blessed": "^0.1.81",
57
+ "blessed-contrib": "^4.11.0",
56
58
  "chalk": "^4.1.2",
59
+ "chokidar": "^4.0.3",
57
60
  "commander": "^12.1.0",
58
61
  "fs-extra": "^11.2.0",
59
62
  "ink": "^3.2.0",
@@ -21,14 +21,21 @@
21
21
  * - File overlaps: Only load if parallel sessions are active
22
22
  * - Configurable via features.lazyContext in agileflow-metadata.json
23
23
  *
24
+ * QUERY MODE (US-0127):
25
+ * - When QUERY=<pattern> provided, uses codebase index for targeted search
26
+ * - Falls back to full context if query returns empty
27
+ * - Based on RLM pattern: programmatic search instead of loading everything
28
+ *
24
29
  * Usage:
25
30
  * node scripts/obtain-context.js # Just gather context
26
31
  * node scripts/obtain-context.js babysit # Gather + register 'babysit'
32
+ * node scripts/obtain-context.js babysit QUERY="auth files" # Query mode
27
33
  */
28
34
 
29
35
  const fs = require('fs');
30
36
  const fsPromises = require('fs').promises;
31
37
  const path = require('path');
38
+ const os = require('os');
32
39
  const { execSync } = require('child_process');
33
40
  const { c: C, box } = require('../lib/colors');
34
41
  const { isValidCommandName } = require('../lib/validate');
@@ -77,6 +84,11 @@ function parseCommandArgs(args) {
77
84
  activeSections.push('visual-e2e');
78
85
  }
79
86
 
87
+ // Query mode: QUERY=<pattern> triggers targeted codebase search (US-0127)
88
+ if (params.QUERY) {
89
+ activeSections.push('query-mode');
90
+ }
91
+
80
92
  // Check for multi-session environment
81
93
  const registryPath = '.agileflow/sessions/registry.json';
82
94
  if (fs.existsSync(registryPath)) {
@@ -99,6 +111,40 @@ const commandName = process.argv[2];
99
111
  const commandArgs = process.argv.slice(3);
100
112
  const { activeSections, params: commandParams } = parseCommandArgs(commandArgs);
101
113
 
114
+ // Helper to extract command type from frontmatter
115
+ function getCommandType(cmdName) {
116
+ // Handle nested command paths like "research/ask" -> "research/ask.md"
117
+ // The command name may contain "/" for nested commands
118
+ const cmdPath = cmdName.includes('/')
119
+ ? `${cmdName.substring(0, cmdName.lastIndexOf('/'))}/${cmdName.substring(cmdName.lastIndexOf('/') + 1)}.md`
120
+ : `${cmdName}.md`;
121
+
122
+ // Try to find the command file and read its frontmatter type
123
+ const possiblePaths = [
124
+ `packages/cli/src/core/commands/${cmdPath}`,
125
+ `.agileflow/commands/${cmdPath}`,
126
+ `.claude/commands/agileflow/${cmdPath}`,
127
+ // Also try flat path for legacy commands
128
+ `packages/cli/src/core/commands/${cmdName.replace(/\//g, '-')}.md`,
129
+ ];
130
+
131
+ for (const searchPath of possiblePaths) {
132
+ if (fs.existsSync(searchPath)) {
133
+ try {
134
+ const content = fs.readFileSync(searchPath, 'utf8');
135
+ // Extract type from YAML frontmatter
136
+ const match = content.match(/^---\n[\s\S]*?type:\s*(\S+)/m);
137
+ if (match) {
138
+ return match[1].replace(/['"]/g, ''); // Remove quotes if any
139
+ }
140
+ } catch {
141
+ // Continue to next path
142
+ }
143
+ }
144
+ }
145
+ return 'interactive'; // Default to interactive
146
+ }
147
+
102
148
  // Register command for PreCompact context preservation
103
149
  if (commandName && isValidCommandName(commandName)) {
104
150
  const sessionStatePath = 'docs/09-agents/session-state.json';
@@ -114,9 +160,13 @@ if (commandName && isValidCommandName(commandName)) {
114
160
  // Remove any existing entry for this command (avoid duplicates)
115
161
  state.active_commands = state.active_commands.filter(c => c.name !== commandName);
116
162
 
163
+ // Get command type from frontmatter (output-only vs interactive)
164
+ const commandType = getCommandType(commandName);
165
+
117
166
  // Add the new command with active sections for progressive disclosure
118
167
  state.active_commands.push({
119
168
  name: commandName,
169
+ type: commandType, // Used by PreCompact to skip output-only commands
120
170
  activated_at: new Date().toISOString(),
121
171
  state: {},
122
172
  active_sections: activeSections,
@@ -165,6 +215,121 @@ function safeExec(cmd) {
165
215
  }
166
216
  }
167
217
 
218
+ // =============================================================================
219
+ // Context Budget Tracking (GSD Integration)
220
+ // =============================================================================
221
+
222
+ /**
223
+ * Get current context usage percentage from Claude's session files.
224
+ * Reads token counts from the active session JSONL file.
225
+ *
226
+ * @returns {{ percent: number, tokens: number, max: number } | null}
227
+ */
228
+ function getContextPercentage() {
229
+ try {
230
+ const homeDir = os.homedir();
231
+ const cwd = process.cwd();
232
+
233
+ // Convert current dir to Claude's session file path format
234
+ // e.g., /home/coder/AgileFlow -> home-coder-AgileFlow
235
+ const projectDir = cwd.replace(homeDir, '~').replace('~', homeDir).replace(/\//g, '-').replace(/^-/, '');
236
+ const sessionDir = path.join(homeDir, '.claude', 'projects', `-${projectDir}`);
237
+
238
+ if (!fs.existsSync(sessionDir)) {
239
+ return null;
240
+ }
241
+
242
+ // Find most recent .jsonl session file
243
+ const files = fs.readdirSync(sessionDir)
244
+ .filter(f => f.endsWith('.jsonl'))
245
+ .map(f => ({
246
+ name: f,
247
+ mtime: fs.statSync(path.join(sessionDir, f)).mtime.getTime(),
248
+ }))
249
+ .sort((a, b) => b.mtime - a.mtime);
250
+
251
+ if (files.length === 0) {
252
+ return null;
253
+ }
254
+
255
+ const sessionFile = path.join(sessionDir, files[0].name);
256
+ const content = fs.readFileSync(sessionFile, 'utf8');
257
+ const lines = content.trim().split('\n').slice(-20); // Last 20 lines
258
+
259
+ // Find latest usage entry
260
+ let latestTokens = 0;
261
+ for (const line of lines.reverse()) {
262
+ try {
263
+ const entry = JSON.parse(line);
264
+ if (entry?.message?.usage) {
265
+ const usage = entry.message.usage;
266
+ latestTokens = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0);
267
+ if (latestTokens > 0) break;
268
+ }
269
+ } catch {
270
+ // Skip malformed lines
271
+ }
272
+ }
273
+
274
+ if (latestTokens === 0) {
275
+ return null;
276
+ }
277
+
278
+ // Default to 200K context for modern Claude models
279
+ const maxContext = 200000;
280
+ const percent = Math.min(100, Math.round((latestTokens * 100) / maxContext));
281
+
282
+ return { percent, tokens: latestTokens, max: maxContext };
283
+ } catch {
284
+ return null;
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Generate context budget warning box if usage exceeds threshold.
290
+ * Based on GSD research: 50% is where quality starts degrading.
291
+ *
292
+ * @param {number} percent - Context usage percentage
293
+ * @returns {string} Warning box or empty string
294
+ */
295
+ function generateContextWarning(percent) {
296
+ if (percent < 50) {
297
+ return ''; // No warning needed
298
+ }
299
+
300
+ const width = 60;
301
+ const topLine = `┏${'━'.repeat(width - 2)}┓`;
302
+ const bottomLine = `┗${'━'.repeat(width - 2)}┛`;
303
+
304
+ let color, icon, message, suggestion;
305
+
306
+ if (percent >= 70) {
307
+ // Critical: Dumb Zone
308
+ color = C.coral;
309
+ icon = '🔴';
310
+ message = `Context usage: ${percent}% (in degradation zone)`;
311
+ suggestion = 'Strongly recommend: compact conversation or delegate to sub-agent';
312
+ } else {
313
+ // Warning: Approaching limit (50-69%)
314
+ color = C.amber;
315
+ icon = '⚠️';
316
+ message = `Context usage: ${percent}% (approaching 50% threshold)`;
317
+ suggestion = 'Consider: delegate to sub-agent or compact conversation';
318
+ }
319
+
320
+ // Pad messages to fit width
321
+ const msgPadded = ` ${icon} ${message}`.padEnd(width - 3) + '┃';
322
+ const sugPadded = ` → ${suggestion}`.padEnd(width - 3) + '┃';
323
+
324
+ return [
325
+ `${color}${C.bold}${topLine}${C.reset}`,
326
+ `${color}${C.bold}┃${msgPadded}${C.reset}`,
327
+ `${color}${C.bold}┃${sugPadded}${C.reset}`,
328
+ `${color}${C.bold}${bottomLine}${C.reset}`,
329
+ '',
330
+ ].join('\n');
331
+ }
332
+
168
333
  // =============================================================================
169
334
  // Lazy Evaluation Configuration (US-0093)
170
335
  // =============================================================================
@@ -679,6 +844,13 @@ function generateFullContent(prefetched = null) {
679
844
  content += `${C.dim}Balance: Use at natural pause points. Don't ask permission for routine work.${C.reset}\n\n`;
680
845
  }
681
846
 
847
+ // 0.6 CONTEXT BUDGET WARNING (GSD Integration)
848
+ // Show warning when context usage approaches 50% threshold
849
+ const contextUsage = getContextPercentage();
850
+ if (contextUsage && contextUsage.percent >= 50) {
851
+ content += generateContextWarning(contextUsage.percent);
852
+ }
853
+
682
854
  // 0. PROGRESSIVE DISCLOSURE (section activation)
683
855
  if (activeSections.length > 0) {
684
856
  content += `\n${C.cyan}${C.bold}═══ 📖 Progressive Disclosure: Active Sections ═══${C.reset}\n`;
@@ -1081,6 +1253,54 @@ function generateFullContent(prefetched = null) {
1081
1253
  return content;
1082
1254
  }
1083
1255
 
1256
+ // ============================================
1257
+ // QUERY MODE: Targeted codebase search (US-0127)
1258
+ // ============================================
1259
+
1260
+ /**
1261
+ * Execute query mode using codebase index for targeted search.
1262
+ * Falls back to full context if query returns no results.
1263
+ *
1264
+ * @param {string} query - Natural language or pattern query
1265
+ * @returns {Object|null} Query results or null to fall back to full context
1266
+ */
1267
+ function executeQueryMode(query) {
1268
+ const queryScript = path.join(__dirname, 'query-codebase.js');
1269
+
1270
+ // Check if query script exists
1271
+ if (!fs.existsSync(queryScript)) {
1272
+ console.error('Query mode unavailable: query-codebase.js not found');
1273
+ return null; // Fall back to full context
1274
+ }
1275
+
1276
+ try {
1277
+ // Execute query and capture output
1278
+ const result = execSync(`node "${queryScript}" --query="${query}" --budget=15000`, {
1279
+ encoding: 'utf8',
1280
+ maxBuffer: 50 * 1024 * 1024, // 50MB buffer
1281
+ });
1282
+
1283
+ // Check if we got results
1284
+ if (result.includes('No files found') || result.trim() === '') {
1285
+ return null; // Fall back to full context
1286
+ }
1287
+
1288
+ return {
1289
+ mode: 'query',
1290
+ query: query,
1291
+ results: result.trim(),
1292
+ };
1293
+ } catch (err) {
1294
+ // Exit code 2 = no results, fall back to full context
1295
+ if (err.status === 2) {
1296
+ return null;
1297
+ }
1298
+ // Exit code 1 = error, report but fall back
1299
+ console.error(`Query error: ${err.message}`);
1300
+ return null;
1301
+ }
1302
+ }
1303
+
1084
1304
  // ============================================
1085
1305
  // MAIN: Output with smart summary positioning
1086
1306
  // ============================================
@@ -1089,6 +1309,24 @@ function generateFullContent(prefetched = null) {
1089
1309
  * Main execution function using parallel pre-fetching for optimal performance.
1090
1310
  */
1091
1311
  async function main() {
1312
+ // Check for query mode first (US-0127)
1313
+ if (activeSections.includes('query-mode') && commandParams.QUERY) {
1314
+ const queryResult = executeQueryMode(commandParams.QUERY);
1315
+
1316
+ if (queryResult) {
1317
+ // Output query results instead of full context
1318
+ console.log(`=== QUERY MODE ===`);
1319
+ console.log(`Query: "${queryResult.query}"`);
1320
+ console.log(`---`);
1321
+ console.log(queryResult.results);
1322
+ console.log(`---`);
1323
+ console.log(`[Query mode: targeted search. Run without QUERY= for full context]`);
1324
+ return;
1325
+ }
1326
+ // Fall through to full context if query returned no results
1327
+ console.log(`[Query "${commandParams.QUERY}" returned no results, loading full context...]`);
1328
+ }
1329
+
1092
1330
  // Check for multi-session environment before prefetching
1093
1331
  const registryPath = '.agileflow/sessions/registry.json';
1094
1332
  let isMultiSession = false;
@@ -42,11 +42,23 @@ if [ -d "docs/05-epics" ]; then
42
42
  fi
43
43
 
44
44
  # Detect active commands and extract their Compact Summaries
45
+ # IMPORTANT: Skip "output-only" commands like research:ask, research:list, research:view
46
+ # These commands generate output for the user to copy - they're not ongoing tasks.
47
+ # If we include them in PreCompact, Claude will try to re-execute them after compact.
45
48
  COMMAND_SUMMARIES=""
46
49
  if [ -f "docs/09-agents/session-state.json" ]; then
50
+ # Output-only commands that should NOT be preserved during compact
51
+ # These commands generate output once and are done - no ongoing state
52
+ # Note: Commands with type: output-only in frontmatter are also filtered
53
+ OUTPUT_ONLY_COMMANDS="research/ask research/list research/view help metrics board"
54
+
47
55
  ACTIVE_COMMANDS=$(node -p "
48
56
  const s = require('./docs/09-agents/session-state.json');
49
- (s.active_commands || []).map(c => c.name).join(' ');
57
+ const outputOnly = '$OUTPUT_ONLY_COMMANDS'.split(' ');
58
+ (s.active_commands || [])
59
+ .filter(c => !outputOnly.includes(c.name) && c.type !== 'output-only')
60
+ .map(c => c.name)
61
+ .join(' ');
50
62
  " 2>/dev/null || echo "")
51
63
 
52
64
  for ACTIVE_COMMAND in $ACTIVE_COMMANDS; do