feat: automatic project type and framework detection#314
Conversation
…an-m#293) Add SessionStart hook integration that auto-detects project languages and frameworks by inspecting marker files and dependency manifests. Supports 12 languages (Python, TypeScript, Go, Rust, Ruby, Java, C#, Swift, Kotlin, Elixir, PHP, JavaScript) and 25+ frameworks (Next.js, React, Django, FastAPI, Rails, Laravel, Spring, etc.). Detection output is injected into Claude's context as JSON, enabling context-aware recommendations without loading irrelevant rules. - New: scripts/lib/project-detect.js (cross-platform detection library) - Modified: scripts/hooks/session-start.js (integration) - New: tests/lib/project-detect.test.js (28 tests, all passing) Co-Authored-By: Claude <noreply@anthropic.com>
|
|
| Condition | Recommendation |
|---|---|
| < 20 commits | Repository may be too new for pattern detection |
| Unusual structure | Non-standard project layout |
| Documentation-only | Limited code patterns available |
To retry: /ecc-tools analyze
📝 WalkthroughWalkthroughIntroduces automatic project type detection in the session start hook by implementing a comprehensive detection library that identifies 12+ programming languages (Python, TypeScript, JavaScript, Go, Rust, Ruby, Java, C#, Swift, Kotlin, Elixir, PHP) and their frameworks through file markers, extensions, and dependency inspection, with full test coverage. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
3 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/lib/project-detect.js">
<violation number="1" location="scripts/lib/project-detect.js:115">
P2: Bug: Spring framework detection is dead code — it can never be detected. The `spring` rule has `language: 'java'` and `markers: []`, so it relies entirely on `packageKeys`. However, the `switch` statement that maps `rule.language` to a dependency list has no `case 'java'` branch, so `depList` remains `[]` and `hasDep` is always `false`. Either add a Java dependency parser (for `pom.xml`/`build.gradle`) with a corresponding `case 'java'`, or add marker files (e.g., `['src/main/resources/application.properties', 'src/main/resources/application.yml']`) so Spring can be detected without dependency parsing.</violation>
<violation number="2" location="scripts/lib/project-detect.js:205">
P2: Bug: pyproject.toml regex truncates dependency list when any dependency uses PEP 508 extras syntax (e.g., `package[extra]`). The non-greedy `[\s\S]*?` stops at the first `]` it encounters, which could be inside a dependency specifier like `psycopg2[binary]`, causing all subsequent dependencies to be missed. Consider using a greedy match up to `\n]` (a `]` at the start of a line or preceded by a newline) instead.</violation>
</file>
<file name="tests/lib/project-detect.test.js">
<violation number="1" location="tests/lib/project-detect.test.js:414">
P2: Test summary output format doesn't match what `run-all.js` expects. The test runner parses `Passed: N` and `Failed: N` lines, but this file prints `N passed, N failed` instead. These 28 tests will be silently excluded from the aggregate count.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Add one-off context when rerunning by tagging
@cubic-dev-aiwith guidance or docs links (includingllms.txt) - Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const tomlPath = path.join(projectDir, 'pyproject.toml'); | ||
| if (fs.existsSync(tomlPath)) { | ||
| const content = fs.readFileSync(tomlPath, 'utf8'); | ||
| const depMatches = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/); |
There was a problem hiding this comment.
P2: Bug: pyproject.toml regex truncates dependency list when any dependency uses PEP 508 extras syntax (e.g., package[extra]). The non-greedy [\s\S]*? stops at the first ] it encounters, which could be inside a dependency specifier like psycopg2[binary], causing all subsequent dependencies to be missed. Consider using a greedy match up to \n] (a ] at the start of a line or preceded by a newline) instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/lib/project-detect.js, line 205:
<comment>Bug: pyproject.toml regex truncates dependency list when any dependency uses PEP 508 extras syntax (e.g., `package[extra]`). The non-greedy `[\s\S]*?` stops at the first `]` it encounters, which could be inside a dependency specifier like `psycopg2[binary]`, causing all subsequent dependencies to be missed. Consider using a greedy match up to `\n]` (a `]` at the start of a line or preceded by a newline) instead.</comment>
<file context>
@@ -0,0 +1,413 @@
+ const tomlPath = path.join(projectDir, 'pyproject.toml');
+ if (fs.existsSync(tomlPath)) {
+ const content = fs.readFileSync(tomlPath, 'utf8');
+ const depMatches = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
+ if (depMatches) {
+ const block = depMatches[1];
</file context>
| { framework: 'axum', language: 'rust', markers: [], packageKeys: ['axum'] }, | ||
|
|
||
| // Java frameworks | ||
| { framework: 'spring', language: 'java', markers: [], packageKeys: ['spring-boot', 'org.springframework'] }, |
There was a problem hiding this comment.
P2: Bug: Spring framework detection is dead code — it can never be detected. The spring rule has language: 'java' and markers: [], so it relies entirely on packageKeys. However, the switch statement that maps rule.language to a dependency list has no case 'java' branch, so depList remains [] and hasDep is always false. Either add a Java dependency parser (for pom.xml/build.gradle) with a corresponding case 'java', or add marker files (e.g., ['src/main/resources/application.properties', 'src/main/resources/application.yml']) so Spring can be detected without dependency parsing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/lib/project-detect.js, line 115:
<comment>Bug: Spring framework detection is dead code — it can never be detected. The `spring` rule has `language: 'java'` and `markers: []`, so it relies entirely on `packageKeys`. However, the `switch` statement that maps `rule.language` to a dependency list has no `case 'java'` branch, so `depList` remains `[]` and `hasDep` is always `false`. Either add a Java dependency parser (for `pom.xml`/`build.gradle`) with a corresponding `case 'java'`, or add marker files (e.g., `['src/main/resources/application.properties', 'src/main/resources/application.yml']`) so Spring can be detected without dependency parsing.</comment>
<file context>
@@ -0,0 +1,413 @@
+ { framework: 'axum', language: 'rust', markers: [], packageKeys: ['axum'] },
+
+ // Java frameworks
+ { framework: 'spring', language: 'java', markers: [], packageKeys: ['spring-boot', 'org.springframework'] },
+
+ // PHP frameworks
</file context>
| })) passed++; else failed++; | ||
|
|
||
| // Summary | ||
| console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`); |
There was a problem hiding this comment.
P2: Test summary output format doesn't match what run-all.js expects. The test runner parses Passed: N and Failed: N lines, but this file prints N passed, N failed instead. These 28 tests will be silently excluded from the aggregate count.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/lib/project-detect.test.js, line 414:
<comment>Test summary output format doesn't match what `run-all.js` expects. The test runner parses `Passed: N` and `Failed: N` lines, but this file prints `N passed, N failed` instead. These 28 tests will be silently excluded from the aggregate count.</comment>
<file context>
@@ -0,0 +1,418 @@
+ })) passed++; else failed++;
+
+ // Summary
+ console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`);
+ process.exit(failed > 0 ? 1 : 0);
+}
</file context>
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
scripts/lib/project-detect.js (1)
321-339: Use immutable deduplication instead of mutating withsplice.Line [338] mutates
languages. An immutable filter keeps dedupe logic safer and easier to reason about.♻️ Proposed fix
-function detectProjectType(projectDir) { - projectDir = projectDir || process.cwd(); - const languages = []; +function detectProjectType(projectDir) { + projectDir = projectDir || process.cwd(); + let languages = []; const frameworks = []; @@ // Deduplicate: if both typescript and javascript detected, keep typescript if (languages.includes('typescript') && languages.includes('javascript')) { - const idx = languages.indexOf('javascript'); - if (idx !== -1) languages.splice(idx, 1); + languages = languages.filter(language => language !== 'javascript'); }As per coding guidelines: "Always create new objects, never mutate existing ones. Use immutable patterns to prevent hidden side effects and enable safe concurrency."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/lib/project-detect.js` around lines 321 - 339, The deduplication currently mutates the languages array with splice when both 'typescript' and 'javascript' are present; change this to an immutable operation by replacing languages with a new filtered array (e.g., assign languages = languages.filter(...)) that removes 'javascript' if 'typescript' exists. Update the logic around the languages variable (the array built from LANGUAGE_RULES using hasMarker/hasExt) so no in-place mutation occurs and all downstream uses of languages receive the new array reference.tests/lib/project-detect.test.js (2)
210-221: Add a dependency-only Angular regression test.This case uses
angular.json(Line [213]), so it won’t fail if dependency-key matching breaks. Add a test with onlypackage.json+@angular/coreto validate dependency detection independently.Based on learnings: "Write unit tests for individual functions, utilities, and components."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/lib/project-detect.test.js` around lines 210 - 221, Add a new regression test that verifies dependency-only Angular detection by creating a temp dir, writing only package.json with {"dependencies":{"@angular/core":"17.0.0"}}, calling detectProjectType(dir), asserting result.frameworks includes 'angular', and cleaning up; mirror the existing test structure using createTempDir, writeTestFile, detectProjectType, assert.ok and cleanupDir and increment passed/failed counters the same way so dependency-based detection is validated independently of angular.json presence.
385-387: Useos.tmpdir()instead of a hardcoded/tmppath.Line [386] is POSIX-specific. Building the path from
os.tmpdir()keeps this edge-case test consistently cross-platform.♻️ Proposed fix
- if (test('handles non-existent directory gracefully', () => { - const result = detectProjectType('/tmp/nonexistent-dir-' + Date.now()); + if (test('handles non-existent directory gracefully', () => { + const nonexistentDir = path.join(os.tmpdir(), `nonexistent-dir-${Date.now()}`); + const result = detectProjectType(nonexistentDir); assert.strictEqual(result.primary, 'unknown'); assert.deepStrictEqual(result.languages, []); })) passed++; else failed++;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/lib/project-detect.test.js` around lines 385 - 387, The test uses a hardcoded POSIX `/tmp` path; update the test 'handles non-existent directory gracefully' to build the temp path from os.tmpdir() and path.join instead of '/tmp', e.g., require('os') and require('path') at top if needed, then call detectProjectType(path.join(os.tmpdir(), 'nonexistent-dir-' + Date.now())) so the test is cross-platform; keep the assertion on result.primary === 'unknown' unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/hooks/session-start.js`:
- Around line 75-89: The code skips machine-readable output when detection is
empty and prefixes JSON with text; always emit raw JSON for downstream parsing
by calling output(JSON.stringify(projectInfo)) unconditionally (remove the
"Project type: " prefix) after detectProjectType() so the unknown fallback in
projectInfo is preserved; keep the human log via log('[SessionStart] ...') but
ensure output() is only the JSON string of projectInfo (use detectProjectType(),
projectInfo, output(), and log() identifiers to locate the changes).
In `@scripts/lib/project-detect.js`:
- Around line 145-155: The hasFileWithExtension function currently only checks
top-level files and misses common layouts (e.g., src/, app/, backend/); update
hasFileWithExtension to search recursively (or explicitly scan common top-level
source directories) for files with the provided extensions so repositories with
nested source folders are detected; modify the implementation in
hasFileWithExtension to either walk the directory tree (skipping
node_modules/build/dist) or iterate over a whitelist of directories
['src','app','backend','server','lib'] and check their entries, preserving the
try/catch behavior and keeping the same return semantics.
- Around line 224-240: The getGoDeps function only parses require (...) blocks
and misses single-line require statements; update getGoDeps to also scan the
go.mod content for single-line require entries (use a regex that matches
"require <module> <version>" but not "require ("—e.g., a negative lookahead) and
add those module names to the deps list, then deduplicate the combined results
(e.g., via a Set) before returning so both block and single-line requires are
captured without duplicates.
---
Nitpick comments:
In `@scripts/lib/project-detect.js`:
- Around line 321-339: The deduplication currently mutates the languages array
with splice when both 'typescript' and 'javascript' are present; change this to
an immutable operation by replacing languages with a new filtered array (e.g.,
assign languages = languages.filter(...)) that removes 'javascript' if
'typescript' exists. Update the logic around the languages variable (the array
built from LANGUAGE_RULES using hasMarker/hasExt) so no in-place mutation occurs
and all downstream uses of languages receive the new array reference.
In `@tests/lib/project-detect.test.js`:
- Around line 210-221: Add a new regression test that verifies dependency-only
Angular detection by creating a temp dir, writing only package.json with
{"dependencies":{"@angular/core":"17.0.0"}}, calling detectProjectType(dir),
asserting result.frameworks includes 'angular', and cleaning up; mirror the
existing test structure using createTempDir, writeTestFile, detectProjectType,
assert.ok and cleanupDir and increment passed/failed counters the same way so
dependency-based detection is validated independently of angular.json presence.
- Around line 385-387: The test uses a hardcoded POSIX `/tmp` path; update the
test 'handles non-existent directory gracefully' to build the temp path from
os.tmpdir() and path.join instead of '/tmp', e.g., require('os') and
require('path') at top if needed, then call
detectProjectType(path.join(os.tmpdir(), 'nonexistent-dir-' + Date.now())) so
the test is cross-platform; keep the assertion on result.primary === 'unknown'
unchanged.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
scripts/hooks/session-start.jsscripts/lib/project-detect.jstests/lib/project-detect.test.jstests/run-all.js
| // Detect project type and frameworks (#293) | ||
| const projectInfo = detectProjectType(); | ||
| if (projectInfo.languages.length > 0 || projectInfo.frameworks.length > 0) { | ||
| const parts = []; | ||
| if (projectInfo.languages.length > 0) { | ||
| parts.push(`languages: ${projectInfo.languages.join(', ')}`); | ||
| } | ||
| if (projectInfo.frameworks.length > 0) { | ||
| parts.push(`frameworks: ${projectInfo.frameworks.join(', ')}`); | ||
| } | ||
| log(`[SessionStart] Project detected — ${parts.join('; ')}`); | ||
| output(`Project type: ${JSON.stringify(projectInfo)}`); | ||
| } else { | ||
| log('[SessionStart] No specific project type detected'); | ||
| } |
There was a problem hiding this comment.
Always emit machine-readable project JSON (including unknown fallback).
At Line [77], output is skipped when detection is empty, and at Line [86] the JSON is prefixed with text. This makes downstream parsing brittle and drops the "unknown" fallback object from Claude context.
🔧 Proposed fix
const projectInfo = detectProjectType();
- if (projectInfo.languages.length > 0 || projectInfo.frameworks.length > 0) {
+ const hasDetection = projectInfo.languages.length > 0 || projectInfo.frameworks.length > 0;
+ if (hasDetection) {
const parts = [];
if (projectInfo.languages.length > 0) {
parts.push(`languages: ${projectInfo.languages.join(', ')}`);
}
if (projectInfo.frameworks.length > 0) {
parts.push(`frameworks: ${projectInfo.frameworks.join(', ')}`);
}
log(`[SessionStart] Project detected — ${parts.join('; ')}`);
- output(`Project type: ${JSON.stringify(projectInfo)}`);
} else {
log('[SessionStart] No specific project type detected');
}
+ output(JSON.stringify(projectInfo));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/hooks/session-start.js` around lines 75 - 89, The code skips
machine-readable output when detection is empty and prefixes JSON with text;
always emit raw JSON for downstream parsing by calling
output(JSON.stringify(projectInfo)) unconditionally (remove the "Project type: "
prefix) after detectProjectType() so the unknown fallback in projectInfo is
preserved; keep the human log via log('[SessionStart] ...') but ensure output()
is only the JSON string of projectInfo (use detectProjectType(), projectInfo,
output(), and log() identifiers to locate the changes).
| function hasFileWithExtension(projectDir, extensions) { | ||
| try { | ||
| const entries = fs.readdirSync(projectDir, { withFileTypes: true }); | ||
| return entries.some(entry => { | ||
| if (!entry.isFile()) return false; | ||
| const ext = path.extname(entry.name); | ||
| return extensions.includes(ext); | ||
| }); | ||
| } catch { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Root-only extension scanning misses common project layouts.
Line [147] only inspects top-level files. Repos with sources under src/, app/, backend/, etc. can be incorrectly detected as unknown.
🔧 Proposed fix
function hasFileWithExtension(projectDir, extensions) {
try {
- const entries = fs.readdirSync(projectDir, { withFileTypes: true });
- return entries.some(entry => {
- if (!entry.isFile()) return false;
- const ext = path.extname(entry.name);
- return extensions.includes(ext);
- });
+ const stack = [projectDir];
+ while (stack.length > 0) {
+ const currentDir = stack.pop();
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
+ for (const entry of entries) {
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
+ const fullPath = path.join(currentDir, entry.name);
+ if (entry.isDirectory()) {
+ stack.push(fullPath);
+ continue;
+ }
+ if (entry.isFile() && extensions.includes(path.extname(entry.name))) {
+ return true;
+ }
+ }
+ }
+ return false;
} catch {
return false;
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/lib/project-detect.js` around lines 145 - 155, The
hasFileWithExtension function currently only checks top-level files and misses
common layouts (e.g., src/, app/, backend/); update hasFileWithExtension to
search recursively (or explicitly scan common top-level source directories) for
files with the provided extensions so repositories with nested source folders
are detected; modify the implementation in hasFileWithExtension to either walk
the directory tree (skipping node_modules/build/dist) or iterate over a
whitelist of directories ['src','app','backend','server','lib'] and check their
entries, preserving the try/catch behavior and keeping the same return
semantics.
| function getGoDeps(projectDir) { | ||
| try { | ||
| const modPath = path.join(projectDir, 'go.mod'); | ||
| if (!fs.existsSync(modPath)) return []; | ||
| const content = fs.readFileSync(modPath, 'utf8'); | ||
| const deps = []; | ||
| const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/); | ||
| if (requireBlock) { | ||
| requireBlock[1].split('\n').forEach(line => { | ||
| const trimmed = line.trim(); | ||
| if (trimmed && !trimmed.startsWith('//')) { | ||
| const parts = trimmed.split(/\s+/); | ||
| if (parts[0]) deps.push(parts[0]); | ||
| } | ||
| }); | ||
| } | ||
| return deps; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "project-detect.js" -type fRepository: affaan-m/everything-claude-code
Length of output: 106
🏁 Script executed:
wc -l ./scripts/lib/project-detect.jsRepository: affaan-m/everything-claude-code
Length of output: 110
🏁 Script executed:
sed -n '224,240p' ./scripts/lib/project-detect.jsRepository: affaan-m/everything-claude-code
Length of output: 654
🏁 Script executed:
sed -n '224,244p' ./scripts/lib/project-detect.jsRepository: affaan-m/everything-claude-code
Length of output: 687
🌐 Web query:
Go go.mod format single-line require syntax specification
💡 Result:
In a go.mod, a single-line require uses the RequireSpec form:
-
EBNF (official
go.modgrammar):
RequireDirective = "require" ( RequireSpec | "(" newline { RequireSpec } ")" newline ) .
RequireSpec = ModulePath Version newline .[1] -
So the single-line syntax is:
require <module-path> <version>[1] -
Example (from the reference):
require golang.org/x/net v1.2.3[1]
Notes:
go.modis line-oriented: each directive is a keyword followed by arguments. Adjacentrequirelines may also be written as arequire ( ... )block. [2]- A trailing comment like
// indirectmay appear on arequireline (commonly added bygo mod tidy). [1]
Sources:
[1] Go Modules Reference (Go team), require directive + grammar: (tip.golang.org)
[2] Go Modules Reference (Go team), go.mod is line-oriented and shows single-line require examples: (tip.golang.org)
Citations:
🏁 Script executed:
# Test the proposed regex pattern with sample go.mod content
cat > /tmp/test_regex.js << 'EOF'
// Test proposed regex
const sampleContent = `
module example.com/myapp
go 1.21
require (
github.com/some/package v1.0.0
golang.org/x/net v1.2.3
)
require golang.org/x/sync v0.1.0
require github.com/another/lib v2.0.0 // indirect
`;
const blockRegex = /require\s*\(([\s\S]*?)\)/;
const singleLineRegex = /^\s*require\s+([^\s]+)\s+[^\s]+/gm;
console.log("=== Block matches ===");
const blockMatch = sampleContent.match(blockRegex);
if (blockMatch) {
blockMatch[1].split('\n').forEach(line => {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('//')) {
const parts = trimmed.split(/\s+/);
if (parts[0]) console.log("Block dep:", parts[0]);
}
});
}
console.log("=== Single-line matches ===");
for (const match of sampleContent.matchAll(singleLineRegex)) {
console.log("Single-line dep:", match[1]);
}
EOF
node /tmp/test_regex.jsRepository: affaan-m/everything-claude-code
Length of output: 281
🏁 Script executed:
rg -n "getGoDeps" ./scripts/lib/project-detect.jsRepository: affaan-m/everything-claude-code
Length of output: 172
🏁 Script executed:
sed -n '340,360p' ./scripts/lib/project-detect.jsRepository: affaan-m/everything-claude-code
Length of output: 839
🏁 Script executed:
sed -n '360,380p' ./scripts/lib/project-detect.jsRepository: affaan-m/everything-claude-code
Length of output: 710
🏁 Script executed:
rg -n "FRAMEWORK_RULES" ./scripts/lib/project-detect.js | head -5Repository: affaan-m/everything-claude-code
Length of output: 170
🏁 Script executed:
sed -n '84,200p' ./scripts/lib/project-detect.js | grep -A 5 "language: 'golang'"Repository: affaan-m/everything-claude-code
Length of output: 462
🏁 Script executed:
# Test corrected regex patterns
cat > /tmp/test_regex2.js << 'EOF'
const sampleContent = `module example.com/myapp
go 1.21
require (
github.com/some/package v1.0.0
golang.org/x/net v1.2.3
)
require golang.org/x/sync v0.1.0
require github.com/another/lib v2.0.0 // indirect
require github.com/third/pkg v1.5.0`;
console.log("=== Problematic proposed regex ===");
console.log("Pattern: /^\\s*require\\s+([^\\s]+)\\s+[^\\s]+/gm");
for (const match of sampleContent.matchAll(/^\s*require\s+([^\s]+)\s+[^\s]+/gm)) {
console.log(" Match:", match[1]);
}
console.log("\n=== Better regex (exclude 'require (') ===");
console.log("Pattern: /^\\s*require\\s+(?!\\()([^\\s]+)\\s+/gm");
for (const match of sampleContent.matchAll(/^\s*require\s+(?!\()([^\s]+)\s+/gm)) {
console.log(" Match:", match[1]);
}
console.log("\n=== Alternative regex ===");
console.log("Pattern: /^\\s*require\\s+(\\S+)\\s+\\S+/gm");
for (const match of sampleContent.matchAll(/^\s*require\s+(\S+)\s+\S+/gm)) {
console.log(" Match:", match[1]);
}
EOF
node /tmp/test_regex2.jsRepository: affaan-m/everything-claude-code
Length of output: 598
🏁 Script executed:
find ./scripts -name "*.test.js" -o -name "*.spec.js" | xargs grep -l "getGoDeps" 2>/dev/nullRepository: affaan-m/everything-claude-code
Length of output: 59
🏁 Script executed:
find . -type f \( -name "*.test.js" -o -name "*.spec.js" -o -name "*test*.js" \) | head -20Repository: affaan-m/everything-claude-code
Length of output: 531
🏁 Script executed:
grep -n "getGoDeps\|go.mod\|golang" ./tests/lib/project-detect.test.jsRepository: affaan-m/everything-claude-code
Length of output: 635
🏁 Script executed:
sed -n '329,365p' ./tests/lib/project-detect.test.jsRepository: affaan-m/everything-claude-code
Length of output: 1493
Handle both block and single-line require entries in go.mod.
The current implementation only parses require (...) blocks and misses valid single-line require module version entries in Go modules, which can cause framework detection to fail for projects using that syntax.
🔧 Proposed fix
function getGoDeps(projectDir) {
try {
const modPath = path.join(projectDir, 'go.mod');
if (!fs.existsSync(modPath)) return [];
const content = fs.readFileSync(modPath, 'utf8');
const deps = [];
const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
if (requireBlock) {
requireBlock[1].split('\n').forEach(line => {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith('//')) {
const parts = trimmed.split(/\s+/);
if (parts[0]) deps.push(parts[0]);
}
});
}
- return deps;
+ const singleLineRequires = content.matchAll(/^\s*require\s+(?!\()([^\s]+)\s+\S+/gm);
+ for (const match of singleLineRequires) {
+ if (match[1]) deps.push(match[1]);
+ }
+ return [...new Set(deps)];
} catch {
return [];
}
}Note: The proposed regex uses a negative lookahead (?!\() to avoid matching the opening parenthesis from require ( block syntax. Deduplication with Set ensures no duplicate entries across both forms.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/lib/project-detect.js` around lines 224 - 240, The getGoDeps function
only parses require (...) blocks and misses single-line require statements;
update getGoDeps to also scan the go.mod content for single-line require entries
(use a regex that matches "require <module> <version>" but not "require ("—e.g.,
a negative lookahead) and add those module names to the deps list, then
deduplicate the combined results (e.g., via a Set) before returning so both
block and single-line requires are captured without duplicates.
Summary
Implements the feature requested in #293 — automatic project type detection in the SessionStart hook.
What it does:
How it works:
tsconfig.json→ TypeScript,go.mod→ Go,Cargo.toml→ Rust)package.json,requirements.txt,pyproject.toml,go.mod,Cargo.toml,composer.json,mix.exs)Example output:
{"languages":["typescript"],"frameworks":["nextjs","react"],"primary":"nextjs","projectDir":"/home/user/my-app"}Integration: Added to the existing
session-start.jshook — no new hook entries needed inhooks.json.Files changed
scripts/lib/project-detect.jsscripts/hooks/session-start.jstests/lib/project-detect.test.jstests/run-all.jsTest plan
node tests/lib/project-detect.test.js)node tests/run-all.js— same 2 pre-existing failures in hooks.test.js)primary: "unknown", no errorsCloses #293
🤖 Generated with Claude Code
Summary by cubic
Automatically detects the project's languages and frameworks during SessionStart and exposes them as JSON for stack-aware recommendations without manual setup. Implements the detection requested in #293.
Written for commit 6eef75b. Summary will update on new commits.
Summary by CodeRabbit