Skip to content

Build local-first Visual Intelligence OS#7

Draft
frankxai wants to merge 27 commits into
mainfrom
codex/visual-intelligence-os-v02
Draft

Build local-first Visual Intelligence OS#7
frankxai wants to merge 27 commits into
mainfrom
codex/visual-intelligence-os-v02

Conversation

@frankxai

@frankxai frankxai commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • upgrades VIS into a local-first Visual Intelligence OS with SQLite core, CLI, static dashboard, MCP resources/tools, schemas, tests, and product docs
  • adds vis doctor / npm run doctor for cross-machine install health, plus explicit Claude/Codex MCP setup steps
  • adds docs/OPEN_SOURCE_TECH_RADAR.md so future GitHub/project absorption has a license and adapter/core decision record

PR readiness hardening

  • streams media hashing in chunks instead of loading full files into memory
  • reports malformed vis.config.json with a useful path/error
  • hardens MCP allowlist path normalization and relative root resolution
  • makes MCP protocol version configurable via VIS_MCP_PROTOCOL_VERSION
  • fixes SVG viewBox parsing edge cases
  • updates Node engine to match node:sqlite usage (>=22.13.0, Node 24+ recommended)

Verification

  • security intake / repo security scan passed
  • npm run lint
  • npm test
  • node bin\vis.mjs doctor --json
  • estate scan: 3276 logical assets, 13372 locations
  • targeted usage scan: 25497 usage edges
  • dashboard generated locally
  • MCP smoke test passed
  • Claude local MCP install health-check passed on main PC

Main merge recommendation

Keep this PR as draft until Frank's other PC / Claude cross-check runs. Do not direct-push to main.

After the other PC confirms npm run lint, npm test, node bin\vis.mjs doctor, dashboard rendering, MCP install, and no generated data/ files staged, this branch should be marked ready and merged into main.

Cross-check

See README.md and docs/PROGRESS_REPORT_2026-06-26.md for other-PC Claude install and validation steps. Use origin/claude/asset-os-coordination as coordination/reference only; it should not be merged over this implementation branch without file-by-file review.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6a209a8-1747-47b7-91fc-e2f653e78bf7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/visual-intelligence-os-v02

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request transitions the "Visual Intelligence Audit" tool into a comprehensive, local-first "Visual Intelligence OS" (v0.2.0). Key changes include the introduction of a SQLite-based asset graph, a revamped CLI, an updated Model Context Protocol (MCP) server, a static HTML dashboard generator, and extensive documentation and JSON schemas. The code review feedback is highly constructive and identifies several critical improvements: securing path traversal checks in the MCP server allowlist, updating the Node.js engine requirement in package.json to reflect the experimental node:sqlite dependency, preventing Out-Of-Memory crashes by hashing large files in chunks, correcting the MCP protocol version to the stable 2024-11-05 release, adding error handling for malformed configuration files, resolving relative paths in allowedRoots against the project root, and improving SVG viewBox parsing robustness.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread mcp/vis-mcp-server.mjs
Comment on lines +361 to +366
function isAllowedPath(value) {
if (!value || typeof value !== 'string') return true
if (!/^[a-zA-Z]:\\|^\//.test(value)) return true
const normalized = path.resolve(value).toLowerCase()
return ALLOWED_ROOTS.some(root => normalized === root || normalized.startsWith(root + path.sep))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-critical critical

The custom regex /^[a-zA-Z]:\\|^\// used to detect absolute paths fails to match Windows absolute paths using forward slashes (e.g., C:/Windows/...), allowing them to bypass the allowlist check completely. Additionally, relative paths with directory traversal (e.g., ../../etc/passwd) bypass the check because they are not absolute. Using path.resolve with ROOT first and checking the resolved absolute path against ALLOWED_ROOTS secures both absolute and relative paths robustly.

function isAllowedPath(value) {
  if (!value || typeof value !== 'string') return true
  const absolute = path.resolve(ROOT, value).toLowerCase()
  return ALLOWED_ROOTS.some(root => absolute === root || absolute.startsWith(root + path.sep))
}

Comment thread core/vis-core.mjs
import path from 'path'
import crypto from 'crypto'
import { pathToFileURL } from 'url'
import { DatabaseSync } from 'node:sqlite'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The node:sqlite module (DatabaseSync) is an experimental feature introduced in Node.js v22.5.0. However, package.json specifies a Node.js engine requirement of "node": ">=18.0.0". Running this code on Node.js versions prior to v22.5.0 (such as Node 18 or 20) will result in a Cannot find module 'node:sqlite' runtime error. Please update the engines.node field in package.json to require >=22.5.0 to reflect this dependency.

Comment thread core/vis-core.mjs Outdated
Comment on lines +483 to +485
const stats = fs.statSync(filePath)
const bytes = fs.readFileSync(filePath)
const sha256 = crypto.createHash('sha256').update(bytes).digest('hex')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Reading the entire file into memory using fs.readFileSync can cause Out-Of-Memory (OOM) crashes on large media files (such as video or audio assets, which are explicitly supported in videoExtensions and audioExtensions). To prevent this, hash the file in chunks using a stream-like approach, and only read the first 64KB header chunk for dimension detection.

  const stats = fs.statSync(filePath)
  
  const hashFileSync = (p) => {
    const hash = crypto.createHash('sha256')
    const fd = fs.openSync(p, 'r')
    const buffer = Buffer.alloc(64 * 1024)
    let bytesRead = 0
    try {
      do {
        bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null)
        hash.update(buffer.subarray(0, bytesRead))
      } while (bytesRead > 0)
    } finally {
      fs.closeSync(fd)
    }
    return hash.digest('hex')
  }

  const readHeaderSync = (p, len) => {
    const fd = fs.openSync(p, 'r')
    const buffer = Buffer.alloc(len)
    try {
      fs.readSync(fd, buffer, 0, len, 0)
    } finally {
      fs.closeSync(fd)
    }
    return buffer
  }

  const sha256 = hashFileSync(filePath)
  const bytes = stats.size > 64 * 1024
    ? readHeaderSync(filePath, 64 * 1024)
    : fs.readFileSync(filePath)

Comment thread mcp/vis-mcp-server.mjs Outdated
try {
if (method === 'initialize') {
send(id, {
protocolVersion: '2025-06-18',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The Model Context Protocol (MCP) specification defines "2024-11-05" as the stable protocol version. Using an unsupported version like "2025-06-18" will cause standard MCP clients (such as Claude Desktop or Claude Code) to reject the connection or fail to initialize.

Suggested change
protocolVersion: '2025-06-18',
protocolVersion: '2024-11-05',

Comment thread core/vis-core.mjs
Comment on lines +92 to +98
export function loadConfig(root = findProjectRoot()) {
const configPath = path.join(root, 'vis.config.json')
const local = fs.existsSync(configPath)
? JSON.parse(fs.readFileSync(configPath, 'utf-8'))
: {}
return normalizeConfig({ ...DEFAULT_CONFIG, ...local })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If vis.config.json is malformed or contains syntax errors (such as trailing commas), JSON.parse will throw an unhandled exception and crash the CLI. Wrapping the parsing logic in a try-catch block ensures the tool falls back gracefully to default configuration with a warning.

export function loadConfig(root = findProjectRoot()) {
  const configPath = path.join(root, 'vis.config.json')
  let local = {}
  if (fs.existsSync(configPath)) {
    try {
      local = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
    } catch (error) {
      console.warn(`Warning: Failed to parse ${configPath}. Using default configuration.`, error.message)
    }
  }
  return normalizeConfig({ ...DEFAULT_CONFIG, ...local })
}

Comment thread core/vis-core.mjs Outdated
Comment on lines +1455 to +1456
const viewBox = text.match(/\bviewBox=["']([^"']+)["']/)?.[1]?.trim().split(/\s+/).map(Number)
if (viewBox?.length === 4) return { width: viewBox[2], height: viewBox[3], durationSeconds: 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.

medium

The current viewBox parsing logic splits strictly by spaces (\s+) and uses Number, which fails (returns NaN) if the values contain units (e.g., px, em) or are comma-separated. Splitting by /\s*,\s*|\s+/ and using parseFloat makes the parsing robust against standard SVG variations.

  const viewBox = text.match(/\bviewBox=["']([^"']+)["']/)?.[1]?.trim().split(/[\s,]+/).map(parseFloat)
  if (viewBox?.length === 4 && !viewBox.some(isNaN)) {
    return { width: Math.round(viewBox[2]), height: Math.round(viewBox[3]), durationSeconds: null }
  }

Comment thread mcp/vis-mcp-server.mjs Outdated
Comment on lines +34 to +38
const ALLOWED_ROOTS = [
ROOT,
...(process.env.VIS_ALLOWED_ROOTS || '').split(path.delimiter).filter(Boolean),
...(CONFIG.allowedRoots || []),
].map(p => path.resolve(p).toLowerCase())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Relative paths in CONFIG.allowedRoots are currently resolved against process.cwd(). If the CLI or server is started from a different directory, these paths will resolve incorrectly. Resolving them relative to ROOT ensures consistent behavior.

Suggested change
const ALLOWED_ROOTS = [
ROOT,
...(process.env.VIS_ALLOWED_ROOTS || '').split(path.delimiter).filter(Boolean),
...(CONFIG.allowedRoots || []),
].map(p => path.resolve(p).toLowerCase())
const ALLOWED_ROOTS = [
ROOT,
...(process.env.VIS_ALLOWED_ROOTS || '').split(path.delimiter).filter(Boolean),
...(CONFIG.allowedRoots || []).map(p => path.resolve(ROOT, p)),
].map(p => path.resolve(p).toLowerCase())

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