Skip to content

feat: automatic project type and framework detection#314

Merged
affaan-m merged 1 commit into
affaan-m:mainfrom
justtrance-web:feat/project-type-detection
Mar 3, 2026
Merged

feat: automatic project type and framework detection#314
affaan-m merged 1 commit into
affaan-m:mainfrom
justtrance-web:feat/project-type-detection

Conversation

@justtrance-web

@justtrance-web justtrance-web commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the feature requested in #293 — automatic project type detection in the SessionStart hook.

What it does:

  • Detects 12 languages: Python, TypeScript, JavaScript, Go, Rust, Ruby, Java, C#, Swift, Kotlin, Elixir, PHP
  • Detects 25+ frameworks: Next.js, React, Vue, Angular, Svelte, Astro, Remix, Nuxt, NestJS, Express, Electron, Django, FastAPI, Flask, Rails, Spring, Laravel, Symfony, Phoenix, Gin, Echo, Actix, Axum, and more
  • Identifies fullstack projects (both frontend + backend frameworks)
  • Outputs JSON to Claude's context for context-aware recommendations

How it works:

  1. Checks marker files (e.g., tsconfig.json → TypeScript, go.mod → Go, Cargo.toml → Rust)
  2. Parses dependency manifests (package.json, requirements.txt, pyproject.toml, go.mod, Cargo.toml, composer.json, mix.exs)
  3. Matches dependencies against framework detection rules
  4. Injects result into Claude's context via stdout

Example output:

{"languages":["typescript"],"frameworks":["nextjs","react"],"primary":"nextjs","projectDir":"/home/user/my-app"}

Integration: Added to the existing session-start.js hook — no new hook entries needed in hooks.json.

Files changed

File Change
scripts/lib/project-detect.js New: detection library (cross-platform Node.js)
scripts/hooks/session-start.js Modified: integrate detection into SessionStart
tests/lib/project-detect.test.js New: 28 tests covering all languages, frameworks, edge cases
tests/run-all.js Modified: include new test file

Test plan

  • All 28 new tests pass (node tests/lib/project-detect.test.js)
  • All existing 992 tests unaffected (node tests/run-all.js — same 2 pre-existing failures in hooks.test.js)
  • Cross-platform: pure Node.js, no bash/shell dependencies
  • Graceful fallback: unknown projects get primary: "unknown", no errors
  • No new dependencies added

Closes #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.

  • New Features
    • Detects 12 languages and 25+ frameworks via marker files and dependency manifests.
    • Identifies fullstack projects and sets a primary type; falls back to "unknown" gracefully.
    • Logs detection and outputs JSON to context: {languages, frameworks, primary, projectDir}.
    • Pure Node.js, cross-platform, no new dependencies; adds a reusable detection library.
    • Adds 28 tests and integrates them into tests/run-all.js.

Written for commit 6eef75b. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added automatic detection of programming languages and frameworks in your project during session startup
    • System identifies supported language and framework combinations, including fullstack projects
    • Detection results are logged and output for quick project recognition
    • Supports detection across multiple popular programming languages and web frameworks

…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>
@ecc-tools

ecc-tools Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Analyzing 200 commits...

@ecc-tools

ecc-tools Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

⚠️ Analysis Incomplete

Unable to generate skill: Not Found - https://docs.github.com/rest/commits/commits#list-commits

Common Reasons
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


ECC Tools

@coderabbitai

coderabbitai Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces 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

Cohort / File(s) Summary
Session Start Hook
scripts/hooks/session-start.js
Integrates project type detection into session initialization: calls detectProjectType after package manager prompt, logs formatted results, and outputs JSON summary of detected languages, frameworks, and project directory.
Project Detection Library
scripts/lib/project-detect.js
New comprehensive detection module with language rules (12 languages via markers and extensions), framework rules (Python, Node.js, Ruby, Go, Rust, Java, PHP, Elixir), and ecosystem-specific dependency parsers for package.json, requirements.txt, pyproject.toml, go.mod, Cargo.toml, composer.json, and mix.exs; exports main detectProjectType function plus helper utilities and rule constants.
Test Coverage
tests/lib/project-detect.test.js, tests/run-all.js
Adds comprehensive unit test suite validating language/framework detection across all supported ecosystems, edge cases (malformed files, missing directories), dependency readers, and fullstack scenarios; integrates test runner with new test file.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hoppy detection hops through your code,
Languages and frameworks now clearly showed!
Python, TypeScript, Go—the scanner knows,
No more context wasted on flows and flows.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: automatic project type and framework detection' accurately and concisely summarizes the main change: adding automatic detection of project types and frameworks to the SessionStart hook.
Linked Issues check ✅ Passed The PR fully satisfies issue #293's requirements: detects languages (Python, TypeScript, Go, etc.) via marker files and source detection, supports multi-language/fullstack projects, outputs JSON with detected types and project directory, uses cross-platform Node.js implementation, and extends to framework-level detection (25+ frameworks).
Out of Scope Changes check ✅ Passed All changes are in-scope: new project-detect.js library, integration into session-start.js hook, comprehensive test suite, and test runner update. No unrelated modifications to other files or scope creep detected.
Docstring Coverage ✅ Passed Docstring coverage is 86.67% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-ai with guidance or docs links (including llms.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]*?)\]/);

@cubic-dev-ai cubic-dev-ai Bot Mar 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

{ framework: 'axum', language: 'rust', markers: [], packageKeys: ['axum'] },

// Java frameworks
{ framework: 'spring', language: 'java', markers: [], packageKeys: ['spring-boot', 'org.springframework'] },

@cubic-dev-ai cubic-dev-ai Bot Mar 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

})) passed++; else failed++;

// Summary
console.log(`\n=== Results: ${passed} passed, ${failed} failed ===\n`);

@cubic-dev-ai cubic-dev-ai Bot Mar 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with Cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
scripts/lib/project-detect.js (1)

321-339: Use immutable deduplication instead of mutating with splice.

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 only package.json + @angular/core to 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: Use os.tmpdir() instead of a hardcoded /tmp path.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5818e8a and 6eef75b.

📒 Files selected for processing (4)
  • scripts/hooks/session-start.js
  • scripts/lib/project-detect.js
  • tests/lib/project-detect.test.js
  • tests/run-all.js

Comment on lines +75 to +89
// 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');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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).

Comment on lines +145 to +155
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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +224 to +240
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "project-detect.js" -type f

Repository: affaan-m/everything-claude-code

Length of output: 106


🏁 Script executed:

wc -l ./scripts/lib/project-detect.js

Repository: affaan-m/everything-claude-code

Length of output: 110


🏁 Script executed:

sed -n '224,240p' ./scripts/lib/project-detect.js

Repository: affaan-m/everything-claude-code

Length of output: 654


🏁 Script executed:

sed -n '224,244p' ./scripts/lib/project-detect.js

Repository: 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.mod grammar):
    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.mod is line-oriented: each directive is a keyword followed by arguments. Adjacent require lines may also be written as a require ( ... ) block. [2]
  • A trailing comment like // indirect may appear on a require line (commonly added by go 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.js

Repository: affaan-m/everything-claude-code

Length of output: 281


🏁 Script executed:

rg -n "getGoDeps" ./scripts/lib/project-detect.js

Repository: affaan-m/everything-claude-code

Length of output: 172


🏁 Script executed:

sed -n '340,360p' ./scripts/lib/project-detect.js

Repository: affaan-m/everything-claude-code

Length of output: 839


🏁 Script executed:

sed -n '360,380p' ./scripts/lib/project-detect.js

Repository: affaan-m/everything-claude-code

Length of output: 710


🏁 Script executed:

rg -n "FRAMEWORK_RULES" ./scripts/lib/project-detect.js | head -5

Repository: 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.js

Repository: 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/null

Repository: 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 -20

Repository: affaan-m/everything-claude-code

Length of output: 531


🏁 Script executed:

grep -n "getGoDeps\|go.mod\|golang" ./tests/lib/project-detect.test.js

Repository: affaan-m/everything-claude-code

Length of output: 635


🏁 Script executed:

sed -n '329,365p' ./tests/lib/project-detect.test.js

Repository: 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.

@affaan-m affaan-m merged commit 912df24 into affaan-m:main Mar 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: SessionStart Hook for Automatic Project Type Detection

2 participants