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.
- package/CHANGELOG.md +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +818 -0
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate-names.js +3 -3
- package/lib/validate.js +116 -52
- package/package.json +4 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +435 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +43 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +118 -1048
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +36 -11
- package/scripts/query-codebase.js +538 -0
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +220 -42
- package/scripts/spawn-parallel.js +651 -0
- package/scripts/tui/blessed/data/watcher.js +180 -0
- package/scripts/tui/blessed/index.js +244 -0
- package/scripts/tui/blessed/panels/output.js +101 -0
- package/scripts/tui/blessed/panels/sessions.js +150 -0
- package/scripts/tui/blessed/panels/trace.js +97 -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 +47 -0
- package/scripts/tui/blessed/ui/tabbar.js +99 -0
- package/scripts/tui/index.js +38 -30
- package/scripts/validators/README.md +143 -0
- package/scripts/validators/component-validator.js +239 -0
- package/scripts/validators/json-schema-validator.js +186 -0
- package/scripts/validators/markdown-validator.js +152 -0
- package/scripts/validators/migration-validator.js +129 -0
- package/scripts/validators/security-validator.js +380 -0
- package/scripts/validators/story-format-validator.js +197 -0
- package/scripts/validators/test-result-validator.js +114 -0
- package/scripts/validators/workflow-validator.js +247 -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 +261 -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/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/audit.md +401 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/board.md +1 -0
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +205 -1
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +76 -0
- package/src/core/commands/metrics.md +1 -0
- package/src/core/commands/pr.md +96 -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/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +113 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +200 -1
- package/src/core/commands/story/list.md +9 -9
- package/src/core/commands/story/view.md +1 -0
- package/src/core/commands/story.md +143 -4
- 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/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +86 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +114 -1
- 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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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();
|
package/scripts/ralph-loop.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
63
|
+
const statusPath = getStatusPath(rootDir);
|
|
64
64
|
safeWriteJSON(statusPath, status);
|
|
65
65
|
}
|
|
66
66
|
|