agileflow 2.90.7 → 2.92.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 (144) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +6 -6
  3. package/lib/README.md +178 -0
  4. package/lib/codebase-indexer.js +818 -0
  5. package/lib/colors.js +190 -12
  6. package/lib/consent.js +232 -0
  7. package/lib/correlation.js +277 -0
  8. package/lib/error-codes.js +46 -0
  9. package/lib/errors.js +48 -6
  10. package/lib/file-cache.js +182 -0
  11. package/lib/format-error.js +156 -0
  12. package/lib/path-resolver.js +155 -7
  13. package/lib/paths.js +212 -20
  14. package/lib/placeholder-registry.js +205 -0
  15. package/lib/registry-di.js +358 -0
  16. package/lib/result-schema.js +363 -0
  17. package/lib/result.js +210 -0
  18. package/lib/session-registry.js +13 -0
  19. package/lib/session-state-machine.js +465 -0
  20. package/lib/validate-commands.js +308 -0
  21. package/lib/validate-names.js +3 -3
  22. package/lib/validate.js +116 -52
  23. package/package.json +4 -1
  24. package/scripts/af +34 -0
  25. package/scripts/agent-loop.js +63 -9
  26. package/scripts/agileflow-configure.js +2 -2
  27. package/scripts/agileflow-welcome.js +435 -23
  28. package/scripts/archive-completed-stories.sh +57 -11
  29. package/scripts/claude-tmux.sh +102 -0
  30. package/scripts/damage-control-bash.js +3 -70
  31. package/scripts/damage-control-edit.js +3 -20
  32. package/scripts/damage-control-write.js +3 -20
  33. package/scripts/dependency-check.js +310 -0
  34. package/scripts/get-env.js +11 -4
  35. package/scripts/lib/configure-detect.js +23 -1
  36. package/scripts/lib/configure-features.js +43 -2
  37. package/scripts/lib/context-formatter.js +771 -0
  38. package/scripts/lib/context-loader.js +699 -0
  39. package/scripts/lib/damage-control-utils.js +107 -0
  40. package/scripts/lib/json-utils.sh +162 -0
  41. package/scripts/lib/state-migrator.js +353 -0
  42. package/scripts/lib/story-state-machine.js +437 -0
  43. package/scripts/obtain-context.js +118 -1048
  44. package/scripts/pre-push-check.sh +46 -0
  45. package/scripts/precompact-context.sh +36 -11
  46. package/scripts/query-codebase.js +538 -0
  47. package/scripts/ralph-loop.js +5 -5
  48. package/scripts/session-manager.js +220 -42
  49. package/scripts/spawn-parallel.js +651 -0
  50. package/scripts/tui/blessed/data/watcher.js +180 -0
  51. package/scripts/tui/blessed/index.js +244 -0
  52. package/scripts/tui/blessed/panels/output.js +101 -0
  53. package/scripts/tui/blessed/panels/sessions.js +150 -0
  54. package/scripts/tui/blessed/panels/trace.js +97 -0
  55. package/scripts/tui/blessed/ui/help.js +77 -0
  56. package/scripts/tui/blessed/ui/screen.js +52 -0
  57. package/scripts/tui/blessed/ui/statusbar.js +47 -0
  58. package/scripts/tui/blessed/ui/tabbar.js +99 -0
  59. package/scripts/tui/index.js +38 -30
  60. package/scripts/validators/README.md +143 -0
  61. package/scripts/validators/component-validator.js +239 -0
  62. package/scripts/validators/json-schema-validator.js +186 -0
  63. package/scripts/validators/markdown-validator.js +152 -0
  64. package/scripts/validators/migration-validator.js +129 -0
  65. package/scripts/validators/security-validator.js +380 -0
  66. package/scripts/validators/story-format-validator.js +197 -0
  67. package/scripts/validators/test-result-validator.js +114 -0
  68. package/scripts/validators/workflow-validator.js +247 -0
  69. package/src/core/agents/accessibility.md +6 -0
  70. package/src/core/agents/adr-writer.md +6 -0
  71. package/src/core/agents/analytics.md +6 -0
  72. package/src/core/agents/api.md +6 -0
  73. package/src/core/agents/ci.md +6 -0
  74. package/src/core/agents/codebase-query.md +261 -0
  75. package/src/core/agents/compliance.md +6 -0
  76. package/src/core/agents/configuration-damage-control.md +6 -0
  77. package/src/core/agents/configuration-visual-e2e.md +6 -0
  78. package/src/core/agents/database.md +10 -0
  79. package/src/core/agents/datamigration.md +6 -0
  80. package/src/core/agents/design.md +6 -0
  81. package/src/core/agents/devops.md +6 -0
  82. package/src/core/agents/documentation.md +6 -0
  83. package/src/core/agents/epic-planner.md +6 -0
  84. package/src/core/agents/integrations.md +6 -0
  85. package/src/core/agents/mentor.md +6 -0
  86. package/src/core/agents/mobile.md +6 -0
  87. package/src/core/agents/monitoring.md +6 -0
  88. package/src/core/agents/multi-expert.md +6 -0
  89. package/src/core/agents/performance.md +6 -0
  90. package/src/core/agents/product.md +6 -0
  91. package/src/core/agents/qa.md +6 -0
  92. package/src/core/agents/readme-updater.md +6 -0
  93. package/src/core/agents/refactor.md +6 -0
  94. package/src/core/agents/research.md +6 -0
  95. package/src/core/agents/security.md +6 -0
  96. package/src/core/agents/testing.md +10 -0
  97. package/src/core/agents/ui.md +6 -0
  98. package/src/core/commands/adr.md +114 -0
  99. package/src/core/commands/agent.md +120 -0
  100. package/src/core/commands/assign.md +145 -0
  101. package/src/core/commands/audit.md +401 -0
  102. package/src/core/commands/babysit.md +32 -5
  103. package/src/core/commands/board.md +1 -0
  104. package/src/core/commands/changelog.md +118 -0
  105. package/src/core/commands/configure.md +42 -6
  106. package/src/core/commands/diagnose.md +114 -0
  107. package/src/core/commands/epic.md +205 -1
  108. package/src/core/commands/handoff.md +128 -0
  109. package/src/core/commands/help.md +76 -0
  110. package/src/core/commands/metrics.md +1 -0
  111. package/src/core/commands/pr.md +96 -0
  112. package/src/core/commands/research/analyze.md +1 -0
  113. package/src/core/commands/research/ask.md +2 -0
  114. package/src/core/commands/research/import.md +1 -0
  115. package/src/core/commands/research/list.md +2 -0
  116. package/src/core/commands/research/synthesize.md +584 -0
  117. package/src/core/commands/research/view.md +2 -0
  118. package/src/core/commands/roadmap/analyze.md +400 -0
  119. package/src/core/commands/session/new.md +113 -6
  120. package/src/core/commands/session/spawn.md +197 -0
  121. package/src/core/commands/sprint.md +22 -0
  122. package/src/core/commands/status.md +200 -1
  123. package/src/core/commands/story/list.md +9 -9
  124. package/src/core/commands/story/view.md +1 -0
  125. package/src/core/commands/story.md +143 -4
  126. package/src/core/experts/codebase-query/expertise.yaml +190 -0
  127. package/src/core/experts/codebase-query/question.md +73 -0
  128. package/src/core/experts/codebase-query/self-improve.md +105 -0
  129. package/src/core/templates/agileflow-metadata.json +55 -2
  130. package/src/core/templates/plan-template.md +125 -0
  131. package/src/core/templates/story-lifecycle.md +213 -0
  132. package/src/core/templates/story-template.md +4 -0
  133. package/src/core/templates/tdd-test-template.js +241 -0
  134. package/tools/cli/commands/setup.js +86 -0
  135. package/tools/cli/installers/core/installer.js +94 -0
  136. package/tools/cli/installers/ide/_base-ide.js +20 -11
  137. package/tools/cli/installers/ide/codex.js +29 -47
  138. package/tools/cli/lib/config-manager.js +17 -2
  139. package/tools/cli/lib/content-transformer.js +271 -0
  140. package/tools/cli/lib/error-handler.js +14 -22
  141. package/tools/cli/lib/ide-error-factory.js +421 -0
  142. package/tools/cli/lib/ide-health-monitor.js +364 -0
  143. package/tools/cli/lib/ide-registry.js +114 -1
  144. package/tools/cli/lib/ui.js +14 -25
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # Pre-push validation script
3
+ # Run before pushing to catch CI failures locally
4
+ # Usage: npm run check (quick: lint + tests only, ~30s)
5
+ # npm run check:full (full: lint + tests + docs build, ~5m)
6
+
7
+ set -e
8
+
9
+ echo "🔍 Running pre-push checks..."
10
+ echo ""
11
+
12
+ # Track start time
13
+ START_TIME=$(date +%s)
14
+
15
+ # 1. Lint check (fastest, catches most issues)
16
+ echo "📋 Step 1/2: Linting..."
17
+ cd packages/cli
18
+ npm run lint 2>&1 | tail -30 || {
19
+ echo "❌ Lint failed! Fix errors before pushing."
20
+ exit 1
21
+ }
22
+ cd ../..
23
+ echo "✅ Lint passed"
24
+ echo ""
25
+
26
+ # 2. Run tests (catches logic errors)
27
+ echo "🧪 Step 2/2: Running tests..."
28
+ cd packages/cli
29
+ npm test 2>&1 | tail -20 || {
30
+ echo "❌ Tests failed! Fix errors before pushing."
31
+ exit 1
32
+ }
33
+ cd ../..
34
+ echo "✅ Tests passed"
35
+ echo ""
36
+
37
+ # Calculate elapsed time
38
+ END_TIME=$(date +%s)
39
+ ELAPSED=$((END_TIME - START_TIME))
40
+
41
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
42
+ echo "✅ Pre-push checks passed! (${ELAPSED}s)"
43
+ echo " Safe to push."
44
+ echo ""
45
+ echo "💡 Tip: Run 'npm run check:full' to include docs build"
46
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -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
@@ -62,16 +74,29 @@ if [ -f "docs/09-agents/session-state.json" ]; then
62
74
  fi
63
75
 
64
76
  if [ ! -z "$COMMAND_FILE" ]; then
65
- SUMMARY=$(node -e "
66
- const fs = require('fs');
67
- const content = fs.readFileSync('$COMMAND_FILE', 'utf8');
68
- const match = content.match(/<!-- COMPACT_SUMMARY_START[\\s\\S]*?-->([\\s\\S]*?)<!-- COMPACT_SUMMARY_END -->/);
69
- if (match) {
70
- console.log('## ACTIVE COMMAND: /agileflow:${ACTIVE_COMMAND}');
71
- console.log('');
72
- console.log(match[1].trim());
73
- }
74
- " 2>/dev/null || echo "")
77
+ # Security: Validate COMMAND_FILE contains only safe characters (alphanumeric, /, -, _, .)
78
+ # and doesn't contain path traversal sequences
79
+ if [[ "$COMMAND_FILE" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [[ ! "$COMMAND_FILE" =~ \.\. ]]; then
80
+ SUMMARY=$(COMMAND_FILE_PATH="$COMMAND_FILE" ACTIVE_CMD="$ACTIVE_COMMAND" node -e "
81
+ const fs = require('fs');
82
+ const filePath = process.env.COMMAND_FILE_PATH;
83
+ const activeCmd = process.env.ACTIVE_CMD;
84
+ // Double-check: only allow paths within expected directories
85
+ const allowedPrefixes = ['packages/cli/src/core/commands/', '.agileflow/commands/', '.claude/commands/agileflow/'];
86
+ if (!allowedPrefixes.some(p => filePath.startsWith(p))) {
87
+ process.exit(1);
88
+ }
89
+ try {
90
+ const content = fs.readFileSync(filePath, 'utf8');
91
+ const match = content.match(/<!-- COMPACT_SUMMARY_START[\\s\\S]*?-->([\\s\\S]*?)<!-- COMPACT_SUMMARY_END -->/);
92
+ if (match) {
93
+ console.log('## ACTIVE COMMAND: /agileflow:' + activeCmd);
94
+ console.log('');
95
+ console.log(match[1].trim());
96
+ }
97
+ } catch (e) {}
98
+ " 2>/dev/null || echo "")
99
+ fi
75
100
 
76
101
  if [ ! -z "$SUMMARY" ]; then
77
102
  COMMAND_SUMMARIES="${COMMAND_SUMMARIES}
@@ -0,0 +1,538 @@
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
+ * --explain Show equivalent bash workflow (ls/find/grep/cat)
25
+ *
26
+ * Exit codes:
27
+ * 0 = Success
28
+ * 1 = Error
29
+ * 2 = No results
30
+ */
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+ const {
35
+ buildIndex,
36
+ updateIndex,
37
+ getIndex,
38
+ queryFiles,
39
+ queryByTag,
40
+ queryByExport,
41
+ getDependencies,
42
+ } = require('../lib/codebase-indexer');
43
+
44
+ // Default configuration
45
+ const DEFAULT_BUDGET = 15000;
46
+
47
+ // Parse command line arguments
48
+ function parseArgs(argv) {
49
+ const args = {
50
+ buildIndex: false,
51
+ query: null,
52
+ deps: null,
53
+ content: null,
54
+ tag: null,
55
+ export: null,
56
+ project: process.cwd(),
57
+ budget: DEFAULT_BUDGET,
58
+ json: false,
59
+ verbose: false,
60
+ explain: false, // Show equivalent bash workflow
61
+ };
62
+
63
+ for (const arg of argv.slice(2)) {
64
+ if (arg === '--build-index') {
65
+ args.buildIndex = true;
66
+ } else if (arg === '--json') {
67
+ args.json = true;
68
+ } else if (arg === '--verbose') {
69
+ args.verbose = true;
70
+ } else if (arg === '--explain') {
71
+ args.explain = true;
72
+ } else if (arg.startsWith('--query=')) {
73
+ args.query = arg.slice(8);
74
+ } else if (arg.startsWith('--deps=')) {
75
+ args.deps = arg.slice(7);
76
+ } else if (arg.startsWith('--content=')) {
77
+ args.content = arg.slice(10);
78
+ } else if (arg.startsWith('--tag=')) {
79
+ args.tag = arg.slice(6);
80
+ } else if (arg.startsWith('--export=')) {
81
+ args.export = arg.slice(9);
82
+ } else if (arg.startsWith('--project=')) {
83
+ args.project = arg.slice(10);
84
+ } else if (arg.startsWith('--budget=')) {
85
+ args.budget = parseInt(arg.slice(9), 10) || DEFAULT_BUDGET;
86
+ }
87
+ }
88
+
89
+ return args;
90
+ }
91
+
92
+ // Generate bash workflow explanation (research: ls → find → grep → cat pattern)
93
+ function explainWorkflow(queryType, queryValue, projectRoot) {
94
+ const lines = ['', '📖 Equivalent Bash Workflow:', ''];
95
+
96
+ switch (queryType) {
97
+ case 'query':
98
+ lines.push('# Step 1: List available directories (ls)');
99
+ lines.push(`ls -la ${projectRoot}/src/`);
100
+ lines.push('');
101
+ lines.push('# Step 2: Find files matching pattern (find)');
102
+ lines.push(`find ${projectRoot} -name "*${queryValue}*" -type f`);
103
+ lines.push('');
104
+ lines.push('# Step 3: Search content within files (grep)');
105
+ lines.push(`grep -rl "${queryValue}" ${projectRoot}/src/`);
106
+ lines.push('');
107
+ lines.push('# This tool combines all three with indexing for speed.');
108
+ break;
109
+
110
+ case 'content':
111
+ lines.push('# Equivalent to grep with context:');
112
+ lines.push(`grep -rn "${queryValue}" ${projectRoot}/src/ --include="*.{js,ts,tsx}"`);
113
+ lines.push('');
114
+ lines.push('# This tool adds: index awareness, budget truncation, structured output.');
115
+ break;
116
+
117
+ case 'tag':
118
+ const tagPatterns = {
119
+ api: '/api/|/routes/|/controllers/',
120
+ ui: '/components/|/views/|/pages/',
121
+ auth: '/auth/|/login/|/jwt/',
122
+ database: '/db/|/models/|/migrations/',
123
+ test: '/test/|/__tests__/|/spec/',
124
+ };
125
+ lines.push('# Equivalent to find with path patterns:');
126
+ lines.push(`find ${projectRoot} -type f | grep -E "${tagPatterns[queryValue] || queryValue}"`);
127
+ lines.push('');
128
+ lines.push('# This tool uses pre-indexed tags for instant lookup.');
129
+ break;
130
+
131
+ case 'export':
132
+ lines.push('# Equivalent to grep for export statements:');
133
+ lines.push(`grep -rn "export.*${queryValue}" ${projectRoot}/src/ --include="*.{js,ts,tsx}"`);
134
+ lines.push('');
135
+ lines.push('# This tool tracks exports in index for instant symbol lookup.');
136
+ break;
137
+
138
+ case 'deps':
139
+ lines.push('# Equivalent to grep for imports:');
140
+ lines.push(`grep -n "import.*from" ${queryValue}`);
141
+ lines.push('');
142
+ lines.push('# Plus reverse search for files importing this one:');
143
+ lines.push(`grep -rl "${path.basename(queryValue, path.extname(queryValue))}" ${projectRoot}/src/`);
144
+ lines.push('');
145
+ lines.push('# This tool tracks bidirectional dependencies in index.');
146
+ break;
147
+
148
+ default:
149
+ lines.push('# Generic file system exploration:');
150
+ lines.push(`ls -la ${projectRoot}/`);
151
+ lines.push(`find ${projectRoot} -type f -name "*.ts" | head -20`);
152
+ }
153
+
154
+ lines.push('');
155
+ lines.push('─'.repeat(50));
156
+ return lines.join('\n');
157
+ }
158
+
159
+ // Search file content using grep-style regex
160
+ function queryContent(projectRoot, pattern, budget) {
161
+ const results = [];
162
+ let totalChars = 0;
163
+
164
+ // Get list of files to search
165
+ const indexResult = getIndex(projectRoot);
166
+ if (!indexResult.ok) {
167
+ return { ok: false, error: indexResult.error };
168
+ }
169
+
170
+ const regex = new RegExp(pattern, 'gi');
171
+ const files = Object.keys(indexResult.data.files);
172
+
173
+ for (const relativePath of files) {
174
+ const fullPath = path.join(projectRoot, relativePath);
175
+ const fileType = indexResult.data.files[relativePath].type;
176
+
177
+ // Only search code files
178
+ if (!['javascript', 'typescript', 'javascript-react', 'typescript-react'].includes(fileType)) {
179
+ continue;
180
+ }
181
+
182
+ try {
183
+ const content = fs.readFileSync(fullPath, 'utf8');
184
+ const lines = content.split('\n');
185
+ const matches = [];
186
+
187
+ for (let i = 0; i < lines.length; i++) {
188
+ if (regex.test(lines[i])) {
189
+ // Include context (2 lines before/after)
190
+ const start = Math.max(0, i - 2);
191
+ const end = Math.min(lines.length - 1, i + 2);
192
+ const context = lines.slice(start, end + 1).map((line, idx) => ({
193
+ lineNumber: start + idx + 1,
194
+ content: line,
195
+ isMatch: start + idx === i,
196
+ }));
197
+ matches.push({ line: i + 1, context });
198
+ }
199
+ regex.lastIndex = 0; // Reset regex state
200
+ }
201
+
202
+ if (matches.length > 0) {
203
+ const result = { file: relativePath, matches };
204
+ const resultChars = JSON.stringify(result).length;
205
+
206
+ if (totalChars + resultChars > budget) {
207
+ results.push({
208
+ file: '...',
209
+ matches: [
210
+ {
211
+ line: 0,
212
+ context: [
213
+ { lineNumber: 0, content: `[Truncated: budget exceeded]`, isMatch: false },
214
+ ],
215
+ },
216
+ ],
217
+ });
218
+ break;
219
+ }
220
+
221
+ results.push(result);
222
+ totalChars += resultChars;
223
+ }
224
+ } catch (err) {
225
+ // Skip unreadable files
226
+ }
227
+ }
228
+
229
+ return { ok: true, data: results };
230
+ }
231
+
232
+ // Format output for human readability
233
+ function formatResults(results, type) {
234
+ const lines = [];
235
+
236
+ switch (type) {
237
+ case 'files':
238
+ lines.push(`Found ${results.length} file(s):`);
239
+ for (const file of results) {
240
+ lines.push(` ${file}`);
241
+ }
242
+ break;
243
+
244
+ case 'content':
245
+ lines.push(`Found matches in ${results.length} file(s):`);
246
+ for (const result of results) {
247
+ lines.push(`\n${result.file}:`);
248
+ for (const match of result.matches) {
249
+ for (const ctx of match.context) {
250
+ const marker = ctx.isMatch ? '>' : ' ';
251
+ lines.push(`${marker} ${ctx.lineNumber}: ${ctx.content}`);
252
+ }
253
+ lines.push(' ---');
254
+ }
255
+ }
256
+ break;
257
+
258
+ case 'deps':
259
+ lines.push('Dependencies:');
260
+ if (results.imports.length > 0) {
261
+ lines.push('\nImports:');
262
+ for (const imp of results.imports) {
263
+ lines.push(` ${imp}`);
264
+ }
265
+ }
266
+ if (results.importedBy.length > 0) {
267
+ lines.push('\nImported by:');
268
+ for (const dep of results.importedBy) {
269
+ lines.push(` ${dep}`);
270
+ }
271
+ }
272
+ break;
273
+
274
+ case 'index':
275
+ lines.push('Index Statistics:');
276
+ lines.push(` Total files: ${results.stats.total_files}`);
277
+ lines.push(` Indexed files: ${results.stats.indexed_files}`);
278
+ lines.push(` Build time: ${results.stats.build_time_ms}ms`);
279
+ lines.push(` Tags: ${Object.keys(results.tags).length}`);
280
+ lines.push(` Exports tracked: ${Object.keys(results.symbols.exports).length}`);
281
+ break;
282
+
283
+ default:
284
+ lines.push(JSON.stringify(results, null, 2));
285
+ }
286
+
287
+ return lines.join('\n');
288
+ }
289
+
290
+ // Truncate output to budget
291
+ function truncateOutput(output, budget) {
292
+ if (output.length <= budget) {
293
+ return output;
294
+ }
295
+
296
+ const truncated = output.slice(0, budget - 50);
297
+ const lastNewline = truncated.lastIndexOf('\n');
298
+ return truncated.slice(0, lastNewline) + '\n\n... [Truncated: output exceeded budget]';
299
+ }
300
+
301
+ // Main execution
302
+ async function main() {
303
+ const args = parseArgs(process.argv);
304
+
305
+ if (args.verbose) {
306
+ console.error('Args:', JSON.stringify(args, null, 2));
307
+ }
308
+
309
+ // Check project exists
310
+ if (!fs.existsSync(args.project)) {
311
+ console.error(`Error: Project not found: ${args.project}`);
312
+ process.exit(1);
313
+ }
314
+
315
+ let result;
316
+ let outputType;
317
+
318
+ try {
319
+ // Handle --build-index
320
+ if (args.buildIndex) {
321
+ if (args.verbose) console.error('Building index...');
322
+ result = buildIndex(args.project);
323
+ outputType = 'index';
324
+
325
+ if (!result.ok) {
326
+ console.error(`Error: ${result.error}`);
327
+ process.exit(1);
328
+ }
329
+
330
+ const output = args.json
331
+ ? JSON.stringify(result.data, null, 2)
332
+ : formatResults(result.data, outputType);
333
+ console.log(truncateOutput(output, args.budget));
334
+ process.exit(0);
335
+ }
336
+
337
+ // Handle --query (glob pattern search)
338
+ if (args.query) {
339
+ if (args.verbose) {
340
+ console.error(`\n🔍 Step 1: Querying for "${args.query}"`);
341
+ console.error(` Project: ${args.project}`);
342
+ }
343
+
344
+ // First try to get/update index
345
+ if (args.verbose) console.error(`📂 Step 2: Checking/updating index...`);
346
+ const indexResult = updateIndex(args.project);
347
+ if (!indexResult.ok) {
348
+ console.error(`Error: ${indexResult.error}`);
349
+ process.exit(1);
350
+ }
351
+ if (args.verbose) {
352
+ console.error(` Index has ${Object.keys(indexResult.data.files).length} files`);
353
+ }
354
+
355
+ // Interpret query
356
+ const query = args.query.toLowerCase();
357
+ let files = [];
358
+
359
+ // If query looks like a glob, use it directly
360
+ if (query.includes('*') || query.includes('/')) {
361
+ if (args.verbose) console.error(`🔎 Step 3: Glob pattern search: ${args.query}`);
362
+ files = queryFiles(indexResult.data, args.query);
363
+ } else {
364
+ // Otherwise, search multiple ways:
365
+ if (args.verbose) console.error(`🔎 Step 3: Multi-strategy search...`);
366
+
367
+ // 1. Files containing query in name
368
+ if (args.verbose) console.error(` 3a. Filename match: **/*${args.query}*`);
369
+ files = queryFiles(indexResult.data, `**/*${args.query}*`);
370
+ if (args.verbose) console.error(` Found ${files.length} files by name`);
371
+
372
+ // 2. Files with matching tag
373
+ if (args.verbose) console.error(` 3b. Tag search: ${query}`);
374
+ const tagFiles = queryByTag(indexResult.data, query);
375
+ if (args.verbose) console.error(` Found ${tagFiles.length} files by tag`);
376
+ files = [...new Set([...files, ...tagFiles])];
377
+
378
+ // 3. Files exporting symbol
379
+ if (args.verbose) console.error(` 3c. Export search: ${args.query}`);
380
+ const exportFiles = queryByExport(indexResult.data, args.query);
381
+ if (args.verbose) console.error(` Found ${exportFiles.length} files by export`);
382
+ files = [...new Set([...files, ...exportFiles])];
383
+ }
384
+ if (args.verbose) console.error(`✅ Step 4: Total unique results: ${files.length}\n`);
385
+
386
+ if (files.length === 0) {
387
+ console.error(`No files found matching: ${args.query}`);
388
+ process.exit(2);
389
+ }
390
+
391
+ if (args.explain) {
392
+ console.log(explainWorkflow('query', args.query, args.project));
393
+ }
394
+
395
+ const output = args.json ? JSON.stringify(files, null, 2) : formatResults(files, 'files');
396
+ console.log(truncateOutput(output, args.budget));
397
+ process.exit(0);
398
+ }
399
+
400
+ // Handle --content (grep-style search)
401
+ if (args.content) {
402
+ if (args.verbose) console.error(`Searching content: ${args.content}`);
403
+
404
+ result = queryContent(args.project, args.content, args.budget);
405
+
406
+ if (!result.ok) {
407
+ console.error(`Error: ${result.error}`);
408
+ process.exit(1);
409
+ }
410
+
411
+ if (result.data.length === 0) {
412
+ console.error(`No content matches for: ${args.content}`);
413
+ process.exit(2);
414
+ }
415
+
416
+ if (args.explain) {
417
+ console.log(explainWorkflow('content', args.content, args.project));
418
+ }
419
+
420
+ const output = args.json
421
+ ? JSON.stringify(result.data, null, 2)
422
+ : formatResults(result.data, 'content');
423
+ console.log(truncateOutput(output, args.budget));
424
+ process.exit(0);
425
+ }
426
+
427
+ // Handle --tag
428
+ if (args.tag) {
429
+ if (args.verbose) console.error(`Searching tag: ${args.tag}`);
430
+
431
+ const indexResult = getIndex(args.project);
432
+ if (!indexResult.ok) {
433
+ console.error(`Error: ${indexResult.error}`);
434
+ process.exit(1);
435
+ }
436
+
437
+ const files = queryByTag(indexResult.data, args.tag);
438
+
439
+ if (files.length === 0) {
440
+ console.error(`No files with tag: ${args.tag}`);
441
+ process.exit(2);
442
+ }
443
+
444
+ if (args.explain) {
445
+ console.log(explainWorkflow('tag', args.tag, args.project));
446
+ }
447
+
448
+ const output = args.json ? JSON.stringify(files, null, 2) : formatResults(files, 'files');
449
+ console.log(truncateOutput(output, args.budget));
450
+ process.exit(0);
451
+ }
452
+
453
+ // Handle --export
454
+ if (args.export) {
455
+ if (args.verbose) console.error(`Searching export: ${args.export}`);
456
+
457
+ const indexResult = getIndex(args.project);
458
+ if (!indexResult.ok) {
459
+ console.error(`Error: ${indexResult.error}`);
460
+ process.exit(1);
461
+ }
462
+
463
+ const files = queryByExport(indexResult.data, args.export);
464
+
465
+ if (files.length === 0) {
466
+ console.error(`No files export: ${args.export}`);
467
+ process.exit(2);
468
+ }
469
+
470
+ if (args.explain) {
471
+ console.log(explainWorkflow('export', args.export, args.project));
472
+ }
473
+
474
+ const output = args.json ? JSON.stringify(files, null, 2) : formatResults(files, 'files');
475
+ console.log(truncateOutput(output, args.budget));
476
+ process.exit(0);
477
+ }
478
+
479
+ // Handle --deps
480
+ if (args.deps) {
481
+ if (args.verbose) console.error(`Getting dependencies: ${args.deps}`);
482
+
483
+ const indexResult = getIndex(args.project);
484
+ if (!indexResult.ok) {
485
+ console.error(`Error: ${indexResult.error}`);
486
+ process.exit(1);
487
+ }
488
+
489
+ const deps = getDependencies(indexResult.data, args.deps);
490
+
491
+ if (deps.imports.length === 0 && deps.importedBy.length === 0) {
492
+ console.error(`No dependencies found for: ${args.deps}`);
493
+ process.exit(2);
494
+ }
495
+
496
+ if (args.explain) {
497
+ console.log(explainWorkflow('deps', args.deps, args.project));
498
+ }
499
+
500
+ const output = args.json ? JSON.stringify(deps, null, 2) : formatResults(deps, 'deps');
501
+ console.log(truncateOutput(output, args.budget));
502
+ process.exit(0);
503
+ }
504
+
505
+ // No action specified - show help
506
+ console.log(`Usage: node query-codebase.js <command>
507
+
508
+ Commands:
509
+ --build-index Build/rebuild codebase index
510
+ --query="<pattern>" Search files by pattern or keyword
511
+ --content="<regex>" Search file content (grep-style)
512
+ --tag="<tag>" Find files by tag (api, ui, auth, etc.)
513
+ --export="<name>" Find files exporting a symbol
514
+ --deps="<file>" Show file dependencies
515
+
516
+ Options:
517
+ --project=<path> Project root (default: cwd)
518
+ --budget=<chars> Output budget in characters (default: 15000)
519
+ --json Output as JSON
520
+ --verbose Show debug info
521
+ --explain Show equivalent bash workflow (ls/find/grep/cat)
522
+
523
+ Exit codes:
524
+ 0 = Success
525
+ 1 = Error
526
+ 2 = No results
527
+ `);
528
+ process.exit(0);
529
+ } catch (err) {
530
+ console.error(`Error: ${err.message}`);
531
+ if (args.verbose) {
532
+ console.error(err.stack);
533
+ }
534
+ process.exit(1);
535
+ }
536
+ }
537
+
538
+ main();
@@ -34,33 +34,33 @@ const { execSync, spawnSync } = require('child_process');
34
34
 
35
35
  // Shared utilities
36
36
  const { c } = require('../lib/colors');
37
- const { getProjectRoot } = require('../lib/paths');
37
+ const { getProjectRoot, getStatusPath, getSessionStatePath } = require('../lib/paths');
38
38
  const { safeReadJSON, safeWriteJSON } = require('../lib/errors');
39
39
  const { isValidEpicId, parseIntBounded } = require('../lib/validate');
40
40
 
41
41
  // Read session state
42
42
  function getSessionState(rootDir) {
43
- const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
43
+ const statePath = getSessionStatePath(rootDir);
44
44
  const result = safeReadJSON(statePath, { defaultValue: {} });
45
45
  return result.ok ? result.data : {};
46
46
  }
47
47
 
48
48
  // Write session state
49
49
  function saveSessionState(rootDir, state) {
50
- const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
50
+ const statePath = getSessionStatePath(rootDir);
51
51
  safeWriteJSON(statePath, state, { createDir: true });
52
52
  }
53
53
 
54
54
  // Read status.json for stories
55
55
  function getStatus(rootDir) {
56
- const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
56
+ const statusPath = getStatusPath(rootDir);
57
57
  const result = safeReadJSON(statusPath, { defaultValue: { stories: {}, epics: {} } });
58
58
  return result.ok ? result.data : { stories: {}, epics: {} };
59
59
  }
60
60
 
61
61
  // Save status.json
62
62
  function saveStatus(rootDir, status) {
63
- const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
63
+ const statusPath = getStatusPath(rootDir);
64
64
  safeWriteJSON(statusPath, status);
65
65
  }
66
66