Skip to content

fix: v0.2.0 — autoplan ship-blockers, beginner mode, 258/258 tests#9

Merged
ArshyaAI merged 6 commits intomainfrom
arshya/feat/autoplan-v0.2.0
Apr 2, 2026
Merged

fix: v0.2.0 — autoplan ship-blockers, beginner mode, 258/258 tests#9
ArshyaAI merged 6 commits intomainfrom
arshya/feat/autoplan-v0.2.0

Conversation

@ArshyaAI
Copy link
Copy Markdown
Owner

@ArshyaAI ArshyaAI commented Apr 2, 2026

Summary

  • 8 ship-blockers fixed from full /autoplan CEO + Eng review (4 independent AI reviewers, 28 decisions logged)
  • Beginner mode — helpful output when score < 20, expands target user beyond power users
  • Score comparability — shows "N/4 dimensions scored" with skip reasons so badges are honest
  • Competitive positioning — README comparison table vs /refine, cclint
  • Shared utils — DRY fix, readJson/getHome/getSettingsLocations extracted from 4 duplicate sites
  • 258 tests passing (was 242/257, now 258/258 with 1 new integration test)
  • npm package slimmed — .npmignore excludes 15 Phase 2 orchestrator modules

Ship-blockers resolved

  1. Scoring: typed dimension keys replace fragile display-name string matching
  2. Scanner isolation: tryScan() wraps each scanner so one failure doesn't crash everything
  3. CLI: top-level try/catch with exit code 1
  4. Removed hardcoded settings_validation: { valid: true } (false assurance in safety tool)
  5. Efficiency scanner capped to 20 most recent sessions (prevents OOM)
  6. HOME=/tmp fallback removed across 11 call sites (security risk on shared CI)
  7. Generated hooks use node instead of python3 (guaranteed present)
  8. Secret redaction regex in audit log hooks

Test plan

  • npx tsc --noEmit passes
  • 258/258 tests pass (0 failures)
  • New integration test: scan bad setup -> fix -> rescan -> score improves
  • All 6 pre-existing test failures fixed
  • Manual: npx claude-code-xray on a default Claude Code install (beginner mode)
  • Manual: npx claude-code-xray fix --apply with rollback verification

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added dimension scoring visibility (shows which safety/capability/automation/efficiency checks completed).
    • Added getting-started guidance for users with low initial scores.
  • Bug Fixes

    • Fixed array handling in fix application (now replaces instead of concatenates).
    • Improved credential redaction in audit logs.
    • Enhanced environment variable validation with clearer error messages.
  • Documentation

    • Updated product comparison table with additional product benchmarks.
  • Tests

    • Added integration test verifying scan→fix→rescan improvement workflow.
    • Updated archetype detection test expectations.
  • Chores

    • Version bumped to 0.2.0.
    • Added npm package exclusions.

ArshyaAI and others added 6 commits April 2, 2026 21:20
- Scoring: use typed dimension keys instead of fragile display-name
  string matching (fixes 9 previously-failing tests)
- Error handling: wrap each scanner in tryScan() so one failure
  doesn't crash the whole tool
- Error handling: top-level try/catch in CLI with exit code 1
- Remove hardcoded settings_validation: { valid: true } — now
  explicitly marks schema validation as not yet implemented
- Cap efficiency scanner to 20 most recent session files to
  prevent OOM on power users
- Remove /tmp fallback for HOME env var across 11 call sites in
  6 files — now throws instead of silently using shared /tmp
- Replace python3 with node -e in generated hooks (node is
  guaranteed present, python3 is not)
- Add secret redaction regex to audit log hooks before writing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Show "GETTING STARTED" panel when score < 20 or fewer than 2
  dimensions scored, with 3 actionable steps for new users
- Show "Scored: N/4 dimensions" prominently after overall score,
  listing which dimensions were skipped and why (no data vs
  scanner failed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace generic "linters vs X-Ray" table with specific comparison
against /refine, cclint, and generic linters. Shows X-Ray's
differentiators: safety-first weighting, source-grounded checks,
score history, and auto-fix with rollback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tils

DRY fix: readJson was duplicated 4x across scanner files, getHome
and getSettingsLocations were duplicated 2-3x each. Now imported
from src/scan/utils.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add scan-fix-rescan integration test that validates the viral
  thesis: scan a bad setup, apply fixes, rescan, assert improvement
- Fix deepMerge tests: arrays replace (not dedupe), matching impl
- Fix archetype tests: fallback is 'unknown' not 'ts-lib'
- Fix dispatch test: async cost is 1.26 (3 roles x 0.42)
- Fix capability test: scanner now has 7 checks not 4

258 tests, 258 pass, 0 fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bump version to 0.2.0 (ship-blocker fixes, beginner mode,
  score comparability, competitive positioning, shared utils,
  258/258 tests passing)
- .npmignore: exclude 15 orchestrator modules not used by Phase 1
  (keeps detect-archetype + config which scan/ depends on)
- Exclude dist/evaluator/, dist/genotype/, dist/promoter/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

This PR updates the package version to 0.2.0, introduces shared utility functions for settings and home directory resolution across scan modules, adds error handling to XRay scanning via a tryScan wrapper, refactors multiple scan components to use consolidated utilities, removes the /tmp fallback for home directory resolution, switches bash hook script generation from Python to Node, updates test expectations for archetype detection and array merge behavior, and adds a comprehensive integration test validating the scan-and-fix workflow.

Changes

Cohort / File(s) Summary
Package Configuration
.npmignore, package.json
Added .npmignore exclusions for dist/orchestrator/, dist/evaluator/, dist/genotype/, and dist/promoter/ subdirectories; bumped version from 0.1.2 to 0.2.0.
Documentation
README.md
Updated "How It's Different" comparison table to contrast X-Ray with multiple products (/refine, cclint, generic linters), emphasizing safety-first focus, auto-fix capabilities, source grounding, scoring history, and privacy features.
Shared Utilities
src/scan/utils.ts
New file introducing SettingsLocations interface and exported utilities: readJson() for safe JSON parsing, getHome() to resolve user home directory, and getSettingsLocations() to compute Claude settings paths.
Scan Module Refactoring
src/scan/automation.ts, src/scan/capability.ts, src/scan/efficiency.ts, src/scan/safety.ts
Removed local helper functions (readJson, getHome, getSettingsLocations) and migrated to import equivalents from ./utils.js; consolidated path resolution logic for settings file access and home directory derivation.
Environment Variable Handling
src/fix/hook-generator.ts, src/fix/safety-fixer.ts
Removed /tmp fallback for home directory; now requires process.env.HOME or process.env.USERPROFILE, throwing an error if neither is set. Changed bash hook scripts from embedded Python (python3 -c) to embedded Node.js (node -e) for JSON parsing and redaction.
CLI and Error Handling
src/scan/cli.ts
Wrapped command dispatch in try/catch block for centralized error handling; CLI now logs errors to stderr and exits with code 1 on unhandled exceptions. Adjusted fix command to apply history update only after successful non-dry-run execution.
XRay Core Logic
src/scan/index.ts
Introduced tryScan wrapper that catches dimension scanner errors, logs failures, and returns null; updated runXRay to conditionally include only successfully completed dimensions. Changed settings_validation from always-valid to always-invalid with placeholder error. Bumped VERSION constant from 0.1.0 to 0.2.0.
Scoring and Type Updates
src/scan/scoring.ts, src/scan/types.ts
Refactored computeScore to use key-based weight lookups instead of display-name mappings. Added new exported DimensionKey type union (`"safety"
Output Rendering
src/scan/render.ts
Added "Scored: X/4 dimensions" section to show which dimensions contributed data and reasons for skips; added "GETTING STARTED" onboarding block when score is below 20 or fewer than 2 dimensions scored.
Archetype and Array Merge Tests
src/orchestrator/__tests__/config.test.ts, src/orchestrator/__tests__/detect-archetype.test.ts, src/scan/__tests__/capability.test.ts
Updated expected archetype from "ts-lib" to "unknown" for missing factory fixture; updated detection reason from "default" to "unclassified"; increased capability checks count from 4 to 7, asserting presence of "Project-level settings", "Project CLAUDE.md", and "MCP servers configured".
Array Merge Behavior Tests
src/fix/__tests__/index.test.ts
Changed array merge semantics: permissions.deny now replaces (not concatenates) with source value; standalone array merge test updated to expect source replacement (["b","c","d"]) instead of deduped concatenation (["a","b","c","d"]).
Dispatch Cost Aggregation Test
src/orchestrator/__tests__/dispatch.test.ts
Updated runTaskWithCrewAsync expected cost_usd from single-role 0.42 to three-role aggregation 1.26, aligning async cost calculation with synchronous path behavior.
Integration Testing
src/scan/__tests__/integration.test.ts
New comprehensive integration test that creates a temporary directory with minimal unsafe settings, runs XRay to establish baseline score (< 80), generates and applies fixes, rescans to verify score improvement, and validates safety-related checks pass for deny rules, sandbox, and PreToolUse hook.

Sequence Diagram(s)

The changes in this PR do not meet the criteria for sequence diagram visualization. While the PR introduces error handling and modular refactoring, the core interactions remain straightforward: local utility imports, error wrapping around existing scanners, and standard test/fix workflows without significantly altered control flow across multiple distinct components.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • PR #6 — Overlapping edits to README's "How It's Different" comparison table, deepMerge array semantics, archetype fallback changes, and package version bumps across the same files.
  • PR #5 — Modifies the same deepMerge array replacement behavior for sources and updates matching test assertions in src/fix/__tests__/index.test.ts.
  • PR #1 — Refactors scan modules to import shared utilities (readJson, getHome, getSettingsLocations) with identical code-level consolidations.

Poem

🐰 Refactored paths and scores take flight,
Shared utilities shining bright,
Arrays now replace, not blend,
Errors caught from start to end,
Home at last, no /tmp to hide,
XRay hops with unified pride!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: version bump to 0.2.0 with three key feature areas (autoplan ship-blockers, beginner mode, test suite completion).

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch arshya/feat/autoplan-v0.2.0

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
README.md (1)

88-95: Add verifiability guardrails for competitor claims in this table.

This comparison is clear, but several competitor-specific claims are time-sensitive. Please add a short “as of YYYY-MM-DD” note and link/source criteria (or methodology) for /refine and cclint columns so the table doesn’t drift into stale or disputable marketing copy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 88 - 95, Add a verifiability guardrail for the
comparison table by appending a short “as of YYYY-MM-DD” timestamp and a brief
source/methodology note for the /refine and cclint columns; update the table
caption or the final row under the table (referencing the table header labels
"X-Ray", "/refine", and "cclint") to include the date and a parenthetical link
or citation token for each competitor that explains the criteria used (e.g.,
feature check, documentation page, or test method) so readers can trace and
update the claims.
src/scan/cli.ts (1)

141-145: Error handling loses stack trace for debugging.

The catch block extracts only err.message, which loses the stack trace. For production CLI use this is acceptable, but consider adding a --verbose or DEBUG env flag that logs the full error stack to aid debugging:

💡 Optional enhancement
 } catch (err) {
   const msg = err instanceof Error ? err.message : String(err);
   console.error(`\n[xray] Error: ${msg}`);
+  if (process.env.DEBUG || flags.has("--verbose")) {
+    console.error(err);
+  }
   process.exit(1);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scan/cli.ts` around lines 141 - 145, The catch block in src/scan/cli.ts
currently prints only err.message and loses the error stack; update the handler
that catches "err" to print the full stack when verbose/debugging is enabled
(e.g., via a new CLI flag like --verbose or an environment flag DEBUG), falling
back to the concise message when not enabled; specifically, augment the catch
around the CLI main flow so that it checks the flag and logs err.stack (or the
error object) to console.error when set, otherwise keep the existing short
message, and still call process.exit(1).
src/fix/safety-fixer.ts (1)

154-157: LGTM, but note code duplication.

The fail-fast check is necessary here since home is used at line 165 for the allowWrite path. However, resolveSettingsTarget is now duplicated across safety-fixer.ts (line 20) and hook-generator.ts (line 18) with identical logic. Given the PR already extracted readJson, getHome, and getSettingsLocations to src/scan/utils.ts, consider consolidating this function there as well in a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fix/safety-fixer.ts` around lines 154 - 157, There is a duplicated
resolveSettingsTarget implementation in safety-fixer.ts and hook-generator.ts;
extract and consolidate it into the existing utils module that already contains
readJson, getHome and getSettingsLocations, export it (preserving its current
signature), then update safety-fixer.ts and hook-generator.ts to import and use
the shared resolveSettingsTarget from utils; ensure behavior for the failing
HOME/USERPROFILE check stays the same and update any imports/tests that
reference the old local implementations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.npmignore:
- Around line 40-48: The package publish rules are inconsistent: package.json
explicitly lists dist/orchestrator/adopt.js and dist/orchestrator/history.js but
.npmignore contains patterns dist/orchestrator/adopt.* and
dist/orchestrator/history.* which exclude them from the tarball; fix by choosing
one of two actions—either remove the adopt.js and history.js entries from the
"files" array in package.json if they should not be published, or delete/adjust
the dist/orchestrator/adopt.* and dist/orchestrator/history.* lines from
.npmignore so those files are included; after making the change, verify with npm
pack --dry-run to confirm the tarball contains (or excludes) the intended files.

In `@src/fix/hook-generator.ts`:
- Line 64: The shell pipeline that sets CMD in hook-generator.ts (the line
starting with CMD=$(echo "$INPUT" | node -e ...)) silently falls back to an
empty command if node is not installed because of the "|| true" and redirection;
update the hook so it first guards for a Node.js runtime (e.g., check
availability of `node` or its version) and fail non‑zero with an explicit error
if not found, or restore a fallback parser (e.g., `python3`) only after
validating its presence; ensure the guard is adjacent to the CMD assignment so
the PreToolUse check never proceeds with an empty/unknown command when Node is
missing.

In `@src/scan/__tests__/integration.test.ts`:
- Line 15: The test currently reuses a single TMP directory (const TMP) for both
the fake user HOME and the repoRoot which causes getSettingsLocations() to
resolve user and project settings to the same file; change the setup to create
two distinct temp paths (e.g., TMP_HOME and TMP_REPO) and use TMP_HOME as
process.env.HOME and TMP_REPO as the repoRoot used in the tests (and update any
test helpers, cleanup, and assertions that reference TMP to use the appropriate
TMP_HOME or TMP_REPO); ensure references in this file to getSettingsLocations(),
any setup/teardown code, and all places that previously used TMP (lines noted in
the review) are adjusted so user-level files live under TMP_HOME/.claude and
project-level files under TMP_REPO/.claude.

In `@src/scan/efficiency.ts`:
- Around line 92-102: The function listSessionFiles currently fails the whole
mtime pass if any single statSync throws; change it to perform per-file statSync
inside a try/catch (or use a safe helper) so unreadable/missing .jsonl files are
skipped rather than causing an empty result, then sort the successfully-statted
entries by mtime, slice to 20 and return their paths; update the logic around
readdirSync/map/statSync (referencing listSessionFiles, readdirSync, statSync)
to skip errors (optionally logging them) and ensure the 20-most-recent cap is
applied after filtering.

In `@src/scan/index.ts`:
- Around line 78-87: The current code hardcodes settings_validation to { valid:
false, errors: [...] } causing runXRay() to always report a validation error;
change runXRay() (or the code that constructs settings_validation) to avoid
fabricating failures—either omit settings_validation when no validator ran or
set an explicit sentinel (e.g., settings_validation: { implemented: false } or
settings_validation: { status: "not_implemented", errors: [] }) so downstream
consumers can distinguish "not implemented" from a real validation failure;
update any consumers/tests that expect a boolean valid to handle the explicit
not-implemented representation.

In `@src/scan/utils.ts`:
- Around line 11-17: readJson currently returns any JSON value; update it so
after parsing it validates the top-level value is a plain object (non-null,
typeof === "object", and not an Array via Array.isArray) and return that
Record<string, unknown> only when valid, otherwise return null; keep the
try/catch around JSON.parse and treat parse errors the same, and reference the
readJson function to add the type checks and null-returns for non-object JSON
values.
- Around line 20-25: getHome currently reads only HOME/USERPROFILE env vars
which can fail even when the system can resolve a home directory; update getHome
to call Node's os.homedir() (importing os) and return that value, falling back
to throwing the same error if os.homedir() returns falsy. Locate the getHome
function and replace the manual env lookup with a call to os.homedir(), keep the
function signature and error behavior (throw "HOME or USERPROFILE environment
variable is required" if no home is found) so callers (settings resolution,
project detection, automation checks) continue to receive a string or an error.

---

Nitpick comments:
In `@README.md`:
- Around line 88-95: Add a verifiability guardrail for the comparison table by
appending a short “as of YYYY-MM-DD” timestamp and a brief source/methodology
note for the /refine and cclint columns; update the table caption or the final
row under the table (referencing the table header labels "X-Ray", "/refine", and
"cclint") to include the date and a parenthetical link or citation token for
each competitor that explains the criteria used (e.g., feature check,
documentation page, or test method) so readers can trace and update the claims.

In `@src/fix/safety-fixer.ts`:
- Around line 154-157: There is a duplicated resolveSettingsTarget
implementation in safety-fixer.ts and hook-generator.ts; extract and consolidate
it into the existing utils module that already contains readJson, getHome and
getSettingsLocations, export it (preserving its current signature), then update
safety-fixer.ts and hook-generator.ts to import and use the shared
resolveSettingsTarget from utils; ensure behavior for the failing
HOME/USERPROFILE check stays the same and update any imports/tests that
reference the old local implementations.

In `@src/scan/cli.ts`:
- Around line 141-145: The catch block in src/scan/cli.ts currently prints only
err.message and loses the error stack; update the handler that catches "err" to
print the full stack when verbose/debugging is enabled (e.g., via a new CLI flag
like --verbose or an environment flag DEBUG), falling back to the concise
message when not enabled; specifically, augment the catch around the CLI main
flow so that it checks the flag and logs err.stack (or the error object) to
console.error when set, otherwise keep the existing short message, and still
call process.exit(1).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f65dd81-ea7a-4a68-9a0a-a530380980ef

📥 Commits

Reviewing files that changed from the base of the PR and between 986d76b and 35e5446.

📒 Files selected for processing (21)
  • .npmignore
  • README.md
  • package.json
  • src/fix/__tests__/index.test.ts
  • src/fix/hook-generator.ts
  • src/fix/safety-fixer.ts
  • src/orchestrator/__tests__/config.test.ts
  • src/orchestrator/__tests__/detect-archetype.test.ts
  • src/orchestrator/__tests__/dispatch.test.ts
  • src/scan/__tests__/capability.test.ts
  • src/scan/__tests__/integration.test.ts
  • src/scan/automation.ts
  • src/scan/capability.ts
  • src/scan/cli.ts
  • src/scan/efficiency.ts
  • src/scan/index.ts
  • src/scan/render.ts
  • src/scan/safety.ts
  • src/scan/scoring.ts
  • src/scan/types.ts
  • src/scan/utils.ts

Comment thread .npmignore
Comment on lines +40 to +48
dist/orchestrator/adopt.*
dist/orchestrator/cli.*
dist/orchestrator/complexity.*
dist/orchestrator/conventions.*
dist/orchestrator/coverage.*
dist/orchestrator/crew-pipeline.*
dist/orchestrator/dispatch.*
dist/orchestrator/doc-coverage.*
dist/orchestrator/history.*
Copy link
Copy Markdown

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
set -euo pipefail

# Verify what will actually be published
npm pack --dry-run --json > /tmp/pack.json

echo "== Target orchestrator files in packed tarball =="
jq -r '.[0].files[].path' /tmp/pack.json | rg -n '^package/dist/orchestrator/(adopt|history|detect-archetype)\.js$' || true

echo
echo "== Any ignored phase-2 folders still included? =="
jq -r '.[0].files[].path' /tmp/pack.json | rg -n '^package/dist/(evaluator|genotype|promoter)/' || true

Repository: ArshyaAI/claude-code-xray

Length of output: 169


🏁 Script executed:

# Check package.json files list
echo "=== package.json lines 33-43 ==="
sed -n '33,43p' package.json

echo
echo "=== .npmignore lines 40-48 ==="
sed -n '40,48p' .npmignore

echo
echo "=== Check if dist/orchestrator files exist ==="
ls -la dist/orchestrator/ 2>/dev/null | head -20 || echo "dist/orchestrator/ does not exist or is empty"

Repository: ArshyaAI/claude-code-xray

Length of output: 717


Reconcile publish manifest rules—adopted entry points are excluded from tarball.

Your package.json files list (lines 38–40) explicitly includes dist/orchestrator/adopt.js and dist/orchestrator/history.js, but .npmignore (lines 40 and 48) blocks both via dist/orchestrator/adopt.* and dist/orchestrator/history.*. This mismatch prevents those files from being packaged: neither file appears in the tarball contents when using npm pack --dry-run.

Decide: either remove the files from package.json if they should not be published, or remove the corresponding .npmignore rules if they should be included.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.npmignore around lines 40 - 48, The package publish rules are inconsistent:
package.json explicitly lists dist/orchestrator/adopt.js and
dist/orchestrator/history.js but .npmignore contains patterns
dist/orchestrator/adopt.* and dist/orchestrator/history.* which exclude them
from the tarball; fix by choosing one of two actions—either remove the adopt.js
and history.js entries from the "files" array in package.json if they should not
be published, or delete/adjust the dist/orchestrator/adopt.* and
dist/orchestrator/history.* lines from .npmignore so those files are included;
after making the change, verify with npm pack --dry-run to confirm the tarball
contains (or excludes) the intended files.

Comment thread src/fix/hook-generator.ts
set -euo pipefail
INPUT=$(cat)
CMD=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('command',''))" 2>/dev/null || true)
CMD=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log((j.tool_input||{}).command||'')}catch{console.log('')}})" 2>/dev/null || true)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Node dependency assumed; failures are silent.

The switch from python3 to node for JSON parsing assumes Node.js is available in the user's PATH when the hook runs. If node isn't found, the || true fallback silently proceeds with an empty command, which allows destructive commands through the PreToolUse check. Consider adding a guard that exits non-zero when node is unavailable, or documenting this runtime requirement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fix/hook-generator.ts` at line 64, The shell pipeline that sets CMD in
hook-generator.ts (the line starting with CMD=$(echo "$INPUT" | node -e ...))
silently falls back to an empty command if node is not installed because of the
"|| true" and redirection; update the hook so it first guards for a Node.js
runtime (e.g., check availability of `node` or its version) and fail non‑zero
with an explicit error if not found, or restore a fallback parser (e.g.,
`python3`) only after validating its presence; ensure the guard is adjacent to
the CMD assignment so the PreToolUse check never proceeds with an empty/unknown
command when Node is missing.

import { runXRay } from "../index.js";
import { generateFixes, applyFix } from "../../fix/index.js";

const TMP = join(__dirname, ".tmp-integration-test");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Separate the temp HOME from the temp repo.

This test sets HOME and repoRoot to the same path, so getSettingsLocations() resolves the user and project-shared settings files to the same TMP/.claude/settings.json. That can hide scope bugs by letting project-level checks and fixes pass after only mutating the user-level file.

💡 Proposed fix
-const TMP = join(__dirname, ".tmp-integration-test");
+const TMP_ROOT = join(__dirname, ".tmp-integration-test");
+const TMP_HOME = join(TMP_ROOT, "home");
+const TMP_REPO = join(TMP_ROOT, "repo");
 ...
-    process.env.HOME = TMP;
+    process.env.HOME = TMP_HOME;
 ...
-    mkdirSync(join(TMP, ".claude"), { recursive: true });
+    mkdirSync(join(TMP_HOME, ".claude"), { recursive: true });
+    mkdirSync(TMP_REPO, { recursive: true });
 ...
-      join(TMP, ".claude", "settings.json"),
+      join(TMP_HOME, ".claude", "settings.json"),
 ...
-    rmSync(TMP, { recursive: true, force: true });
+    rmSync(TMP_ROOT, { recursive: true, force: true });
 ...
-    const beforeResult = runXRay(TMP);
+    const beforeResult = runXRay(TMP_REPO);
 ...
-    const fixes = generateFixes(beforeResult, TMP);
+    const fixes = generateFixes(beforeResult, TMP_REPO);
 ...
-    const afterResult = runXRay(TMP);
+    const afterResult = runXRay(TMP_REPO);

Also applies to: 19-25, 31-33, 46-55, 58-69, 82-82

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scan/__tests__/integration.test.ts` at line 15, The test currently reuses
a single TMP directory (const TMP) for both the fake user HOME and the repoRoot
which causes getSettingsLocations() to resolve user and project settings to the
same file; change the setup to create two distinct temp paths (e.g., TMP_HOME
and TMP_REPO) and use TMP_HOME as process.env.HOME and TMP_REPO as the repoRoot
used in the tests (and update any test helpers, cleanup, and assertions that
reference TMP to use the appropriate TMP_HOME or TMP_REPO); ensure references in
this file to getSettingsLocations(), any setup/teardown code, and all places
that previously used TMP (lines noted in the review) are adjusted so user-level
files live under TMP_HOME/.claude and project-level files under
TMP_REPO/.claude.

Comment thread src/scan/efficiency.ts
Comment on lines 92 to +102
function listSessionFiles(projectDir: string): string[] {
try {
return readdirSync(projectDir)
const files = readdirSync(projectDir)
.filter((f) => f.endsWith(".jsonl"))
.map((f) => join(projectDir, f));
// Cap at 20 most recent files to avoid OOM on power users
return files
.map((f) => ({ path: f, mtime: statSync(f).mtimeMs }))
.sort((a, b) => b.mtime - a.mtime)
.slice(0, 20)
.map((f) => f.path);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle per-file statSync() failures instead of dropping the whole set.

listSessionFiles() now returns [] if any single .jsonl disappears or becomes unreadable during the mtime pass. That turns one transient file race into "no sessions" and a 0 efficiency score for the repo.

💡 Proposed fix
 function listSessionFiles(projectDir: string): string[] {
   try {
     const files = readdirSync(projectDir)
       .filter((f) => f.endsWith(".jsonl"))
-      .map((f) => join(projectDir, f));
+      .map((f) => join(projectDir, f))
+      .flatMap((f) => {
+        try {
+          return [{ path: f, mtime: statSync(f).mtimeMs }];
+        } catch {
+          return [];
+        }
+      });
     // Cap at 20 most recent files to avoid OOM on power users
     return files
-      .map((f) => ({ path: f, mtime: statSync(f).mtimeMs }))
       .sort((a, b) => b.mtime - a.mtime)
       .slice(0, 20)
       .map((f) => f.path);
   } catch {
     return [];
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scan/efficiency.ts` around lines 92 - 102, The function listSessionFiles
currently fails the whole mtime pass if any single statSync throws; change it to
perform per-file statSync inside a try/catch (or use a safe helper) so
unreadable/missing .jsonl files are skipped rather than causing an empty result,
then sort the successfully-statted entries by mtime, slice to 20 and return
their paths; update the logic around readdirSync/map/statSync (referencing
listSessionFiles, readdirSync, statSync) to skip errors (optionally logging
them) and ensure the 20-most-recent cap is applied after filtering.

Comment thread src/scan/index.ts
Comment on lines +78 to +87
settings_validation: {
valid: false,
errors: [
{
path: "",
message: "Schema validation not yet implemented",
scope: "n/a",
},
],
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't hardcode settings_validation to fail.

Every runXRay() result now reports a validation error even though no schema validator ran. That makes the JSON contract lie to downstream consumers and turns healthy repos into false negatives.

If validation is not implemented yet, represent that state explicitly instead of fabricating an error payload.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scan/index.ts` around lines 78 - 87, The current code hardcodes
settings_validation to { valid: false, errors: [...] } causing runXRay() to
always report a validation error; change runXRay() (or the code that constructs
settings_validation) to avoid fabricating failures—either omit
settings_validation when no validator ran or set an explicit sentinel (e.g.,
settings_validation: { implemented: false } or settings_validation: { status:
"not_implemented", errors: [] }) so downstream consumers can distinguish "not
implemented" from a real validation failure; update any consumers/tests that
expect a boolean valid to handle the explicit not-implemented representation.

Comment thread src/scan/utils.ts
Comment on lines +11 to +17
export function readJson(filePath: string): Record<string, unknown> | null {
if (!existsSync(filePath)) return null;
try {
return JSON.parse(readFileSync(filePath, "utf-8"));
} catch {
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the parsed top-level shape before returning it.

readJson() promises an object but returns any successful JSON value. Arrays and scalars can then flow through every scanner as if they were config objects and produce misleading results.

💡 Proposed fix
 export function readJson(filePath: string): Record<string, unknown> | null {
   if (!existsSync(filePath)) return null;
   try {
-    return JSON.parse(readFileSync(filePath, "utf-8"));
+    const parsed: unknown = JSON.parse(readFileSync(filePath, "utf-8"));
+    return parsed !== null &&
+      typeof parsed === "object" &&
+      !Array.isArray(parsed)
+      ? (parsed as Record<string, unknown>)
+      : null;
   } catch {
     return null;
   }
 }
📝 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
export function readJson(filePath: string): Record<string, unknown> | null {
if (!existsSync(filePath)) return null;
try {
return JSON.parse(readFileSync(filePath, "utf-8"));
} catch {
return null;
}
export function readJson(filePath: string): Record<string, unknown> | null {
if (!existsSync(filePath)) return null;
try {
const parsed: unknown = JSON.parse(readFileSync(filePath, "utf-8"));
return parsed !== null &&
typeof parsed === "object" &&
!Array.isArray(parsed)
? (parsed as Record<string, unknown>)
: null;
} catch {
return null;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scan/utils.ts` around lines 11 - 17, readJson currently returns any JSON
value; update it so after parsing it validates the top-level value is a plain
object (non-null, typeof === "object", and not an Array via Array.isArray) and
return that Record<string, unknown> only when valid, otherwise return null; keep
the try/catch around JSON.parse and treat parse errors the same, and reference
the readJson function to add the type checks and null-returns for non-object
JSON values.

Comment thread src/scan/utils.ts
Comment on lines +20 to +25
export function getHome(): string {
const home = process.env["HOME"] ?? process.env["USERPROFILE"];
if (!home) {
throw new Error("HOME or USERPROFILE environment variable is required");
}
return home;
Copy link
Copy Markdown

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

🌐 Web query:

In current Node.js, how does os.homedir()resolve the home directory on POSIX and Windows, and what fallbacks does it support beyondHOME/USERPROFILE?

💡 Result:

In current Node.js (as of v25.8.2), os.homedir calls libuv's uv_os_homedir to resolve the current user's home directory path. On POSIX systems: - First checks the HOME environment variable using uv_os_getenv("HOME"). If set and valid, returns it. - Otherwise, uses the effective UID (from geteuid) to query the system's password database (via getpwuid_r, typically reading /etc/passwd or equivalent like NSS) to retrieve the pw_dir field. On Windows: - First checks the USERPROFILE environment variable using uv_os_getenv("USERPROFILE"). If set and valid, returns it. - Otherwise, calls GetUserProfileDirectory with the current user's token to get the profile directory path. No further fallbacks beyond these are supported; if both methods fail, uv_os_homedir returns an error (UV_ENOENT or similar), which os.homedir propagates as a thrown Error. This behavior prioritizes environment variables (which can override system defaults, e.g., in sudo scenarios where HOME may not match effective UID) but falls back to authentic system user data. Note that os.userInfo.homedir differs by always using the system lookup without env check.

Citations:


🏁 Script executed:

# Check if the file exists and read the relevant section
head -30 src/scan/utils.ts

Repository: ArshyaAI/claude-code-xray

Length of output: 830


🏁 Script executed:

# Search for all usages of getHome to understand the scope of impact
rg "getHome()" --type ts --type tsx -A 2 -B 2

Repository: ArshyaAI/claude-code-xray

Length of output: 96


🏁 Script executed:

# Search for getHome() usages with correct syntax
rg "getHome\(\)" -A 2 -B 2

Repository: ArshyaAI/claude-code-xray

Length of output: 2525


Replace env-only home directory lookup with os.homedir() for broader fallback support.

The current implementation only checks HOME and USERPROFILE environment variables with no system fallback. Node's os.homedir() first attempts the same env var checks, then falls back to the system password database on POSIX (via getpwuid_r) or GetUserProfileDirectory on Windows, providing more robust resolution. Since getHome() is called throughout the scanner modules (in settings resolution, project detection, and automation checks), a failure here cascades to whole-scan failures even when the runtime can resolve a valid home directory.

Suggested fix
 import { existsSync, readFileSync } from "node:fs";
+import { homedir } from "node:os";
 import { join } from "node:path";
 ...
 export function getHome(): string {
-  const home = process.env["HOME"] ?? process.env["USERPROFILE"];
+  const home = homedir();
   if (!home) {
-    throw new Error("HOME or USERPROFILE environment variable is required");
+    throw new Error("Could not resolve the user's home directory");
   }
   return home;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scan/utils.ts` around lines 20 - 25, getHome currently reads only
HOME/USERPROFILE env vars which can fail even when the system can resolve a home
directory; update getHome to call Node's os.homedir() (importing os) and return
that value, falling back to throwing the same error if os.homedir() returns
falsy. Locate the getHome function and replace the manual env lookup with a call
to os.homedir(), keep the function signature and error behavior (throw "HOME or
USERPROFILE environment variable is required" if no home is found) so callers
(settings resolution, project detection, automation checks) continue to receive
a string or an error.

@ArshyaAI ArshyaAI merged commit 8286509 into main Apr 2, 2026
3 checks passed
@ArshyaAI ArshyaAI deleted the arshya/feat/autoplan-v0.2.0 branch April 2, 2026 21:57
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.

1 participant