Skip to content

fix: harden observer hooks and test discovery#513

Merged
affaan-m merged 1 commit into
mainfrom
fix/observer-fixes
Mar 16, 2026
Merged

fix: harden observer hooks and test discovery#513
affaan-m merged 1 commit into
mainfrom
fix/observer-fixes

Conversation

@affaan-m

@affaan-m affaan-m commented Mar 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • make the SessionEnd marker hook cleanup-safe by running it async with a timeout
  • keep observer automated-session guards ahead of project detection and add regression coverage for each guard path
  • switch tests/run-all.js to tests/**/*.test.js discovery and add a direct nested-discovery regression test
  • add scripts/ci/catalog.js to scan agents, commands, and skills and compare repo counts against README.md and AGENTS.md
  • isolate the session-id filename test so it no longer depends on the caller's real home directory

Testing

  • node tests/hooks/hooks.test.js
  • node tests/ci/validators.test.js
  • node tests/run-all.js

Notes

  • scripts/ci/catalog.js is currently reporting real docs drift outside this scope: the repo scans as 18 agents, 48 commands, and 94 skills, while README.md and AGENTS.md still document 16 / 40 / 65-style totals.

Closes #429
Closes #437
Closes #438


Summary by cubic

Hardened observer hooks and test discovery to prevent cleanup blocking and project side effects. Added a CI catalog verifier to catch docs drift in agent/command/skill counts.

  • Bug Fixes

    • Run SessionEnd marker hook async with a 10s timeout to keep cleanup non-blocking.
    • Keep automated-session guards in skills/continuous-learning-v2/hooks/observe.sh ahead of project detection to avoid side effects.
    • Switch test runner to tests/**/*.test.js discovery and add a nested discovery regression test.
  • New Features

    • Add scripts/ci/catalog.js to compare repo catalog counts with README.md and AGENTS.md (--json, --md, --text; exits non-zero on mismatch).

Written for commit 3f68ad8. Summary will update on new commits.

Summary by CodeRabbit

  • Chores
    • Enhanced test coverage with expanded validation helpers and additional test procedures
    • Improved test file discovery mechanism for better test execution
    • SessionEnd hook configured with non-blocking execution and timeout safeguards
    • Refined catalog verification workflow for consistency checks

@coderabbitai

coderabbitai Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This pull request addresses three architectural issues: making the SessionEnd hook non-blocking via async execution with a timeout, implementing glob-based test discovery to automatically detect new tests, and adding a catalog verification script that compares documentation counts against filesystem state. It also moves automated-session guards in observe.sh before project-detection side effects and expands test coverage for these behaviors.

Changes

Cohort / File(s) Summary
SessionEnd Hook Configuration
hooks/hooks.json
Added async: true and timeout: 10 to the SessionEnd hook for the "*" matcher to enable non-blocking execution.
Test Infrastructure & Discovery
tests/run-all.js, tests/hooks/hooks.test.js
Refactored test discovery from hardcoded file list to glob-based pattern matching (**/*.test.js). Expanded hook tests with new helpers for side-effect validation, observe.sh skip behavior, session-end metadata handling, and async marker hook verification.
Catalog Verification
scripts/ci/catalog.js
Replaced agent/command/skill cataloging with a verification workflow that parses README.md and AGENTS.md to extract expected counts, compares against filesystem state (agents/commands as .md files, skills via SKILL.md), and outputs mismatches in JSON, Markdown, or text format.
Guard Documentation
skills/continuous-learning-v2/hooks/observe.sh
Added documentation block explaining that automated-session guards must remain above detect-project.sh sourcing to prevent unwanted project state creation for skipped sessions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

  • PR #31: Modifies SessionEnd hook placement and commands, directly related to the async/timeout configuration being added here.
  • PR #378: Introduces hook schema validation and tests that inspect hooks.json field definitions, including SessionEnd hook properties.
  • PR #427: Updates observe.sh guard logic and project-detection side effects, aligning with the guard placement documentation and test coverage added in this PR.

Poem

🐰 Async hooks now race the clock,
Tests discover without a hardcoded block,
Catalogs count, and guards stand tall,
Before projects bloom, we catch them all! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: harden observer hooks and test discovery' directly summarizes the main changes: hardening observer hooks (SessionEnd hook async/timeout) and improving test discovery (glob-based detection).
Linked Issues check ✅ Passed PR addresses all three linked issues: #429 (SessionEnd hook async/timeout), #437 (observer guards before project detection with regression tests), #438 (glob-based test discovery and catalog script).
Out of Scope Changes check ✅ Passed All changes are scoped to objectives: SessionEnd hook modifications, observer hook reordering, test discovery refactoring, and catalog script implementation align with linked issues.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/observer-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

1 issue found across 5 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="tests/hooks/hooks.test.js">

<violation number="1" location="tests/hooks/hooks.test.js:3393">
P2: Use a platform-agnostic assertion for nested test paths; the current hard-coded `nested/deep.test.js` fails on Windows path separators.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread tests/hooks/hooks.test.js
const result = runPatchedRunAll(testRoot);
assert.strictEqual(result.code, 0, `run-all wrapper should succeed, stderr: ${result.stderr}`);
assert.ok(result.stdout.includes('Running top.test.js'), 'Should run the top-level test');
assert.ok(result.stdout.includes('Running nested/deep.test.js'), 'Should run nested .test.js files');

@cubic-dev-ai cubic-dev-ai Bot Mar 16, 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: Use a platform-agnostic assertion for nested test paths; the current hard-coded nested/deep.test.js fails on Windows path separators.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/hooks/hooks.test.js, line 3393:

<comment>Use a platform-agnostic assertion for nested test paths; the current hard-coded `nested/deep.test.js` fails on Windows path separators.</comment>

<file context>
@@ -3320,6 +3375,32 @@ async function runTests() {
+        const result = runPatchedRunAll(testRoot);
+        assert.strictEqual(result.code, 0, `run-all wrapper should succeed, stderr: ${result.stderr}`);
+        assert.ok(result.stdout.includes('Running top.test.js'), 'Should run the top-level test');
+        assert.ok(result.stdout.includes('Running nested/deep.test.js'), 'Should run nested .test.js files');
+        assert.ok(!result.stdout.includes('ignore.js'), 'Should ignore non-.test.js files');
+        assert.ok(result.stdout.includes('Total Tests:    3'), `Should aggregate nested test totals, got: ${result.stdout}`);
</file context>
Suggested change
assert.ok(result.stdout.includes('Running nested/deep.test.js'), 'Should run nested .test.js files');
assert.ok(/Running nested[\\/]deep\.test\.js/.test(result.stdout), 'Should run nested .test.js files');
Fix with Cubic

@greptile-apps

greptile-apps Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens three independent areas: the SessionEnd hook is made async with a 10-unit timeout to prevent blocking during cleanup; observe.sh's automated-session guards are documented as order-sensitive with respect to detect-project.sh; and tests/run-all.js is upgraded from a flat *.test.js scan to a tests/**/*.test.js recursive glob. A new scripts/ci/catalog.js is added to detect drift between repo counts and documentation.

Key changes and findings:

  • hooks/hooks.json: async: true + timeout: 10 added to the SessionEnd marker hook — the unit of timeout (seconds vs milliseconds) is not documented anywhere in the diff, which could cause silent under-timeout in the wrong runtime.
  • scripts/ci/catalog.js: Rewritten to validate README.md and AGENTS.md counts against actual filesystem counts; will exit with code 1 on the current repo since docs are acknowledged as stale (16/40/65 documented vs 18/48/94 actual). The renderMarkdown output mode sends mismatch lines to console.log (stdout) while renderText correctly uses console.error (stderr), inconsistently mixing output streams.
  • tests/hooks/hooks.test.js: The five observer-guard cases are split into individual async tests for better isolation, and the session-id filename test now uses an isolated temp home directory. The runPatchedRunAll helper patches run-all.js via string replacement, but provides no assertion that the replacement succeeded — a reformatting of run-all.js would cause a silent, opaque test failure.
  • tests/run-all.js: Discovery logic is now correctly scoped to tests/**/*.test.js with a path.matchesGlob polyfill for older Node.js versions. A guard for zero discovered files was also added.

Confidence Score: 3/5

  • Safe to merge with minor follow-ups: one logic fix for the test helper and one style alignment for catalog output streams.
  • The functional changes (async SessionEnd hook, observe.sh comment, run-all.js glob) are low-risk and well-tested. The main concerns are: (1) runPatchedRunAll's fragile string replacement could produce misleading test failures if run-all.js is ever reformatted; (2) renderMarkdown inconsistently routes mismatch output to stdout instead of stderr; and (3) catalog.js will fail CI on current docs drift (acknowledged, but means CI is in a broken state until docs are updated). None are blocking but the test helper fragility warrants a fix before the test suite grows around it.
  • tests/hooks/hooks.test.js (fragile runPatchedRunAll string replacement) and scripts/ci/catalog.js (stdout/stderr inconsistency in renderMarkdown).

Important Files Changed

Filename Overview
hooks/hooks.json Adds async: true and timeout: 10 to the SessionEnd marker hook to make it non-blocking during cleanup; the timeout unit (seconds vs milliseconds) is undocumented.
scripts/ci/catalog.js Major rewrite: now parses README.md and AGENTS.md for documented counts and compares against repo actuals, exiting with code 1 on drift; renderMarkdown inconsistently uses console.log for mismatch output instead of console.error.
skills/continuous-learning-v2/hooks/observe.sh Documentation-only change: adds a comment block explaining that automated-session guards must remain above the detect-project.sh source call to prevent side-effect creation.
tests/hooks/hooks.test.js Refactors the monolithic observer-guard test into individual cases, isolates the session-id filename test to a temp home directory, and adds regression tests for async hook config and nested test discovery; runPatchedRunAll uses a fragile string replacement with no guard against silent failure.
tests/run-all.js Switches test discovery to a tests/**/*.test.js glob with a two-tier path round-trip (absolute → repo-relative for filtering → tests-relative for display), adds a "no files found" guard, and includes a path.matchesGlob polyfill fallback.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[SessionEnd event] --> B[run-with-flags.js\nasync=true, timeout=10]
    B --> C[session-end-marker.js\nnon-blocking cleanup]

    D[PostToolUse event] --> E[observe.sh post]
    E --> F{Automated\nsession guards}
    F -->|non-cli entrypoint| G[exit 0\nbefore detect-project.sh]
    F -->|ECC_HOOK_PROFILE=minimal| G
    F -->|ECC_SKIP_OBSERVE=1| G
    F -->|agent_id present| G
    F -->|cwd in skip paths| G
    F -->|passes all guards| H[source detect-project.sh\ncreates project dirs]
    H --> I[Write observation\nto JSONL]

    J[tests/run-all.js] --> K[walkFiles testsDir]
    K --> L[repo-relative paths]
    L --> M{matchesTestGlob\ntests/**/*.test.js}
    M -->|match| N[testsDir-relative path]
    M -->|no match| O[excluded]
    N --> P[spawnSync test file]

    Q[scripts/ci/catalog.js] --> R[buildCatalog\nagents/commands/skills]
    Q --> S[parseReadmeExpectations]
    Q --> T[parseAgentsDocExpectations]
    R & S & T --> U[evaluateExpectations]
    U --> V{any failures?}
    V -->|yes| W[exit 1]
    V -->|no| X[exit 0]
Loading

Last reviewed commit: 3f68ad8

Comment thread tests/hooks/hooks.test.js
const wrapperPath = path.join(tempRoot, 'run-all-wrapper.js');
const tempTestsDir = path.join(tempRoot, 'tests');
let source = fs.readFileSync(path.join(__dirname, '..', 'run-all.js'), 'utf8');
source = source.replace('const testsDir = __dirname;', `const testsDir = ${JSON.stringify(tempTestsDir)};`);

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.

Fragile source-patching with no guard against replacement failure

source.replace(...) returns the original string unchanged if 'const testsDir = __dirname;' is not found (e.g., after any whitespace/formatting change to run-all.js). In that case the wrapper is written unmodified, __dirname resolves to tempRoot (the wrapper's location), repoRoot becomes the parent of tempRoot, and no files under tests/ match the ^tests\/ regex — so the runner exits 1 with "No test files matched". The test then fails with run-all wrapper should succeed, which is an opaque failure rather than a clear "patching failed" message.

Add an assertion that the replacement actually fired:

Suggested change
source = source.replace('const testsDir = __dirname;', `const testsDir = ${JSON.stringify(tempTestsDir)};`);
let source = fs.readFileSync(path.join(__dirname, '..', 'run-all.js'), 'utf8');
const NEEDLE = 'const testsDir = __dirname;';
assert.ok(source.includes(NEEDLE), `run-all.js no longer contains "${NEEDLE}" — update runPatchedRunAll`);
source = source.replace(NEEDLE, `const testsDir = ${JSON.stringify(tempTestsDir)};`);

Comment thread scripts/ci/catalog.js
Comment on lines +173 to +176
console.log('## Mismatches\n');
for (const mismatch of mismatches) {
console.log(`- ${formatExpectation(mismatch)}`);
}

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.

renderMarkdown sends mismatches to stdout instead of stderr

In renderText, mismatches are written with console.error (stderr), which lets CI pipelines separate error signals from normal output. renderMarkdown writes the same mismatches with console.log (stdout), breaking that separation for --md mode. Given that the script already exits with code 1 on mismatches, keeping stderr consistent is valuable for tooling that tails stderr.

Suggested change
console.log('## Mismatches\n');
for (const mismatch of mismatches) {
console.log(`- ${formatExpectation(mismatch)}`);
}
console.log('## Mismatches\n');
for (const mismatch of mismatches) {
console.error(`- ${formatExpectation(mismatch)}`);
}

Comment thread hooks/hooks.json
Comment on lines +237 to 238
"timeout": 10
}

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.

timeout unit is ambiguous

The timeout: 10 value has no accompanying comment documenting whether the unit is seconds or milliseconds. The existing hook test only asserts > 0, so the contract is unclear from the code alone. If this follows the Claude Code hooks schema (where timeout is in seconds), 10 s is reasonable; if it is milliseconds, session-end-marker.js would almost certainly be killed before finishing. A brief inline comment would remove the ambiguity for future maintainers:

Suggested change
"timeout": 10
}
"async": true,
"timeout": 10

Consider adding a comment in the adjacent description field or in a nearby doc file clarifying the unit (e.g., "description": "Session end lifecycle marker (non-blocking, 10 s timeout)").

@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

🤖 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/ci/catalog.js`:
- Around line 101-115: The parseAgentsDocExpectations function currently sets
skills mode to 'minimum' when the regex captures a '+' (summaryMatch[3]),
allowing "65+" to be treated as a lower bound; change this to always use mode:
'exact' for the skills entry so AGENTS.md counts are enforced exactly. Locate
parseAgentsDocExpectations and the return entry that builds the skills object
(where it uses summaryMatch[2] and summaryMatch[3]) and remove/ignore the
'+'-based branch so the skills entry is created with mode: 'exact' and expected:
Number(summaryMatch[2]) and source: 'AGENTS.md summary'.

In `@tests/hooks/hooks.test.js`:
- Around line 181-188: The test helper currently treats any zero exit as
success; change the assertion after runShellScript(observePath, ...) to also
fail when the observe.sh Python-resolution short-circuits (i.e., when the script
returned 0 because no python/python3 was found). Specifically, update the check
around result.code to assert that result.code === 0 AND that
result.stdout/result.stderr does NOT contain the observe.sh “no python”/“could
not find python” message (or other specific text your observe.sh emits on that
branch), so runShellScript/observePath truly exercised the
interpreter-resolution path before calling assertNoProjectDetectionSideEffects.

In `@tests/run-all.js`:
- Around line 38-43: discoverTestFiles returns OS-specific paths because
path.relative uses platform separators; normalize repoRelativePath to forward
slashes before returning to ensure tests expecting 'nested/deep.test.js' pass on
Windows. In the mapping chain inside discoverTestFiles (after repoRelativePath
is computed or before the final .sort()), replace backslashes with '/' (e.g.,
normalize repoRelativePath by replacing \\ with / or using path.posix-style
join) so matchesTestGlob and downstream code always receive forward-slash paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77ab1845-2761-425d-8806-4f700e657588

📥 Commits

Reviewing files that changed from the base of the PR and between c53bba9 and 3f68ad8.

📒 Files selected for processing (5)
  • hooks/hooks.json
  • scripts/ci/catalog.js
  • skills/continuous-learning-v2/hooks/observe.sh
  • tests/hooks/hooks.test.js
  • tests/run-all.js

Comment thread scripts/ci/catalog.js
Comment on lines +101 to +115
function parseAgentsDocExpectations(agentsContent) {
const summaryMatch = agentsContent.match(/providing\s+(\d+)\s+specialized agents,\s+(\d+)(\+)?\s+skills,\s+(\d+)\s+commands/i);
if (!summaryMatch) {
throw new Error('AGENTS.md is missing the catalog summary line');
}

const catalog = {
agents: { count: agents.length, list: agents },
commands: { count: commands.length, list: commands },
skills: { count: skills.length, list: skills }
};
return [
{ category: 'agents', mode: 'exact', expected: Number(summaryMatch[1]), source: 'AGENTS.md summary' },
{
category: 'skills',
mode: summaryMatch[3] ? 'minimum' : 'exact',
expected: Number(summaryMatch[2]),
source: 'AGENTS.md summary'
},
{ category: 'commands', mode: 'exact', expected: Number(summaryMatch[4]), source: 'AGENTS.md summary' }

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

Use exact AGENTS.md counts here.

Treating 65+ skills as a minimum lets AGENTS.md stay stale indefinitely. Once README.md is corrected to the real value, CI will still go green even if AGENTS.md keeps advertising an outdated lower bound, which defeats the sync check added for #438.

Suggested fix
 function parseAgentsDocExpectations(agentsContent) {
   const summaryMatch = agentsContent.match(/providing\s+(\d+)\s+specialized agents,\s+(\d+)(\+)?\s+skills,\s+(\d+)\s+commands/i);
   if (!summaryMatch) {
     throw new Error('AGENTS.md is missing the catalog summary line');
   }
+  if (summaryMatch[3]) {
+    throw new Error('AGENTS.md must use exact catalog counts; remove the "+" suffix from skills.');
+  }
 
   return [
     { category: 'agents', mode: 'exact', expected: Number(summaryMatch[1]), source: 'AGENTS.md summary' },
     {
       category: 'skills',
-      mode: summaryMatch[3] ? 'minimum' : 'exact',
+      mode: 'exact',
       expected: Number(summaryMatch[2]),
       source: 'AGENTS.md summary'
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/ci/catalog.js` around lines 101 - 115, The parseAgentsDocExpectations
function currently sets skills mode to 'minimum' when the regex captures a '+'
(summaryMatch[3]), allowing "65+" to be treated as a lower bound; change this to
always use mode: 'exact' for the skills entry so AGENTS.md counts are enforced
exactly. Locate parseAgentsDocExpectations and the return entry that builds the
skills object (where it uses summaryMatch[2] and summaryMatch[3]) and
remove/ignore the '+'-based branch so the skills entry is created with mode:
'exact' and expected: Number(summaryMatch[2]) and source: 'AGENTS.md summary'.

Comment thread tests/hooks/hooks.test.js
Comment on lines +181 to +188
const result = await runShellScript(observePath, ['post'], payload, {
HOME: homeDir,
USERPROFILE: homeDir,
...testCase.env
}, projectDir);

assert.strictEqual(result.code, 0, `${testCase.name} should exit successfully, stderr: ${result.stderr}`);
assertNoProjectDetectionSideEffects(homeDir, testCase.name);

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

Make the observe-sh guard helper fail if Python resolution short-circuits first.

This helper currently treats any zero exit as success, but skills/continuous-learning-v2/hooks/observe.sh, Lines 49-53 also return 0 when no python/python3 is available. In that case all five new guard-path tests can pass without exercising the guard ordering you wanted to lock down.

Suggested fix
     const result = await runShellScript(observePath, ['post'], payload, {
       HOME: homeDir,
       USERPROFILE: homeDir,
       ...testCase.env
     }, projectDir);
 
     assert.strictEqual(result.code, 0, `${testCase.name} should exit successfully, stderr: ${result.stderr}`);
+    assert.ok(
+      !result.stderr.includes('No python interpreter found'),
+      `${testCase.name} exited before guard evaluation because observe.sh could not resolve Python`
+    );
     assertNoProjectDetectionSideEffects(homeDir, testCase.name);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hooks/hooks.test.js` around lines 181 - 188, The test helper currently
treats any zero exit as success; change the assertion after
runShellScript(observePath, ...) to also fail when the observe.sh
Python-resolution short-circuits (i.e., when the script returned 0 because no
python/python3 was found). Specifically, update the check around result.code to
assert that result.code === 0 AND that result.stdout/result.stderr does NOT
contain the observe.sh “no python”/“could not find python” message (or other
specific text your observe.sh emits on that branch), so
runShellScript/observePath truly exercised the interpreter-resolution path
before calling assertNoProjectDetectionSideEffects.

Comment thread tests/run-all.js
Comment on lines +38 to +43
function discoverTestFiles() {
return walkFiles(testsDir)
.map(fullPath => path.relative(repoRoot, fullPath))
.filter(matchesTestGlob)
.map(repoRelativePath => path.relative(testsDir, path.join(repoRoot, repoRelativePath)))
.sort();

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:

#!/bin/bash
node <<'NODE'
const path = require('path');

const repoRoot = 'C:\\repo';
const testsDir = 'C:\\repo\\tests';
const fullPath = 'C:\\repo\\tests\\nested\\deep.test.js';

const repoRelativePath = path.win32.relative(repoRoot, fullPath);
const discovered = path.win32.relative(
  testsDir,
  path.win32.join(repoRoot, repoRelativePath)
);

console.log(JSON.stringify({
  repoRelativePath,
  discovered,
  normalized: discovered.split(path.win32.sep).join('/')
}, null, 2));
NODE

rg -n "path\\.relative\\(testsDir, path\\.join\\(repoRoot, repoRelativePath\\)\\)" tests/run-all.js

Repository: affaan-m/everything-claude-code

Length of output: 305


🏁 Script executed:

rg -n "nested.*deep\.test\.js|nested/deep\.test\.js" --type js

Repository: affaan-m/everything-claude-code

Length of output: 351


🏁 Script executed:

cat -n tests/run-all.js | sed -n '35,50p'

Repository: affaan-m/everything-claude-code

Length of output: 597


🏁 Script executed:

rg -n "discoverTestFiles\|nested-discovery" tests/ --type js -A 3

Repository: affaan-m/everything-claude-code

Length of output: 57


Normalize discovered test paths to forward slashes for cross-platform consistency.

Line 42 uses path.relative() which returns OS-specific separators. On Windows, this becomes nested\deep.test.js, but the test at tests/hooks/hooks.test.js:3393 expects 'Running nested/deep.test.js' with forward slashes. The test will fail on Windows with the current implementation.

Suggested fix
 function discoverTestFiles() {
   return walkFiles(testsDir)
     .map(fullPath => path.relative(repoRoot, fullPath))
     .filter(matchesTestGlob)
     .map(repoRelativePath => path.relative(testsDir, path.join(repoRoot, repoRelativePath)))
+    .map(relativePath => relativePath.split(path.sep).join('/'))
     .sort();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function discoverTestFiles() {
return walkFiles(testsDir)
.map(fullPath => path.relative(repoRoot, fullPath))
.filter(matchesTestGlob)
.map(repoRelativePath => path.relative(testsDir, path.join(repoRoot, repoRelativePath)))
.sort();
function discoverTestFiles() {
return walkFiles(testsDir)
.map(fullPath => path.relative(repoRoot, fullPath))
.filter(matchesTestGlob)
.map(repoRelativePath => path.relative(testsDir, path.join(repoRoot, repoRelativePath)))
.map(relativePath => relativePath.split(path.sep).join('/'))
.sort();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/run-all.js` around lines 38 - 43, discoverTestFiles returns OS-specific
paths because path.relative uses platform separators; normalize repoRelativePath
to forward slashes before returning to ensure tests expecting
'nested/deep.test.js' pass on Windows. In the mapping chain inside
discoverTestFiles (after repoRelativePath is computed or before the final
.sort()), replace backslashes with '/' (e.g., normalize repoRelativePath by
replacing \\ with / or using path.posix-style join) so matchesTestGlob and
downstream code always receive forward-slash paths.

@affaan-m affaan-m merged commit 8878c6d into main Mar 16, 2026
30 of 40 checks passed
@affaan-m affaan-m deleted the fix/observer-fixes branch March 20, 2026 07:16
floatingman pushed a commit to floatingman/everything-claude-code that referenced this pull request Mar 22, 2026
FrancescoRosciano pushed a commit to FRosciano-Mambo/everything-claude-code that referenced this pull request Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant