Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ updates:
- dependency-name: typescript
update-types:
- version-update:semver-major
# Types must track the minimum supported runtime major (engines >=22.13.0,
# Types must track the minimum supported runtime major (engines >=22.22.0,
# CI matrix 22 + 24), never the newest release. Revisit with the Node 22
# end-of-life (2027-04-30), adopting @types/node@24 at that point.
- dependency-name: "@types/node"
update-types:
- version-update:semver-major
# 1.18.x pulls effect@4.0.0-beta.83 → ini@7.0.0, whose engines are
# "^22.22.2 || ^24.15.0 || >=26.0.0". That silently raises our declared
# runtime floor (engines >=22.13.0, enforced by the CI packed-install
# runtime floor (engines >=22.22.0, enforced by the CI packed-install
# smoke test) and ships a beta dependency, so adoption is a deliberate
# floor-raising release. See #392.
- dependency-name: "@opencode-ai/plugin"
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ jobs:
if: matrix.node == '22'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.13.0'
node-version: '22.22.0'

- name: Smoke-test minimum supported runtime
if: matrix.node == '22'
Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- **Node.js runtime floor and Chokidar 6**: Raise both package identities' minimum from Node.js 22.13.0 to 22.22.0 to satisfy Chokidar 6's declared engine requirement. Existing platform, binary, and host contracts remain unchanged.

- **Complete history sync scheduling**: Reconcile up to four entities concurrently through one sync-local request budget of four active calls and eight starts per rolling second, without skipping historical edits, deletions, or conditional page validation. Drain real requests before publication, cancellation settlement, or cleanup; retain resumable checkpoints on transient failures and let late access denials take precedence.

### Fixed

- **Directory watcher permission recovery**: Recover readable, policy-selected directory and project-root registrations rejected with `EPERM` or `EACCES`, including empty directories that later gain source files. Reuse the one-shot polling cutover and catch-up reconciliation without broadening ignored, excluded, restricted, protected-index, unreadable, or external-configuration scope. Exercise actual Chokidar 6 directory resources in the watcher/Indexer acceptance checks.

## [0.38.2] - 2026-10-26

### Fixed
Expand Down
11 changes: 10 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Use this when you just want the shortest path to a good PR:

### Prerequisites

- Development Node.js range: `^22.19.0 || ^24.11.0 || >=26.0.0` (Pi and tsup development dependencies). Node.js 24.11+ LTS is recommended. The published package still supports Node.js 22.13 or newer.
- Development Node.js range: `^22.22.0 || ^24.11.0 || >=26.0.0` (Chokidar 6, Pi, and tsup dependencies). Node.js 24.11+ LTS is recommended. Published consumers require Node.js 22.22 or newer.
- Rust toolchain (for native module)
- npm

Expand Down Expand Up @@ -72,6 +72,15 @@ npm run test
cd native && cargo test
```

Watcher permission-recovery tests use real Chokidar 6 directory registrations,
not per-file native handles. Controlled fault cases explicitly select Chokidar's
per-directory native backend on every platform, matching Linux's default strategy.
Readable project roots and traversable directories (including empty directories)
can trigger the existing one-shot polling fallback; ignored, excluded, restricted,
protected-index, unreadable, and auxiliary external-config targets cannot broaden
source coverage. Keep readiness/fault synchronization bounded and retain real
event-delivery, index-definition replacement, and filtering assertions.

The default-root-build watcher rejection scenarios in
`tests/include-excluded-paths-indexing.test.ts` retain diagnostics **only on
failure**, after stopping watchers, settling handler tails, and closing indexers,
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://img.shields.io/npm/dm/open-codebase-index.svg)](https://www.npmjs.com/package/open-codebase-index)
[![Build Status](https://img.shields.io/github/actions/workflow/status/Helweg/open-codebase-index/ci.yml?branch=main)](https://github.com/Helweg/open-codebase-index/actions)
[![Node.js](https://img.shields.io/badge/node-%3E%3D22.13-brightgreen.svg)](https://nodejs.org/)
[![Node.js](https://img.shields.io/badge/node-%3E%3D22.22-brightgreen.svg)](https://nodejs.org/)

> Search a codebase by meaning, then follow the result into definitions, callers, and dependency paths.

Expand Down Expand Up @@ -34,7 +34,7 @@ ocbi search "retry recovery" --project /path/to/repo

## Quick start with OpenCode

Requires Node.js 22.13 or newer. Node.js 24 LTS is recommended.
Requires Node.js 22.22 or newer. Node.js 24 LTS is recommended.

1. Install the package:

Expand Down Expand Up @@ -280,6 +280,8 @@ npm run lint
npm run test:run
```

- Source installs and published consumers require Node.js 22.22 or newer because Chokidar 6 enforces that engine floor. The development toolchain supports `^22.22.0 || ^24.11.0 || >=26.0.0`; building the native module also requires the Rust toolchain.

Pi integration tests use the development host `@earendil-works/pi-coding-agent`
1.3 or newer and its `emitBeforeAgentStart(trigger, systemPromptOptions)` API.
This test-host floor does not narrow the optional Pi peer contract or change the
Expand Down
6 changes: 4 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Track override decisions in [#379](https://github.com/Helweg/open-codebase-index
- Preserve existing `os`, `cpu`, and `libc` selectors on unchanged optional packages when refreshing lockfiles. Package-path/version equality alone does not prove platform-selection equivalence.
- Before calling an override inert, resolve two disposable lock-only trees from the same manifest with no existing lock, with and without that override. Compare every package path and version; report explicit added, removed, and version-changed lists. All three lists must be empty.
- For active changes, record those resolution deltas and run `npm audit --json` including development dependencies, a fresh `npm ci`, affected real CLI/build paths, and the repository validation gate. Preserve security coverage rather than accepting a newly introduced finding.
- Root overrides govern repository installs, not the transitive dependencies of a published package installed by a consumer. Check both packed identities with `npm_config_engine_strict=true` on the supported Node.js floor, 22.13.0.
- Root overrides govern repository installs, not the transitive dependencies of a published package installed by a consumer. Check both packed identities with `npm_config_engine_strict=true` on the supported Node.js floor, 22.22.0.

### Retained Security Floors

Expand Down Expand Up @@ -94,7 +94,9 @@ The existing AJV range admits fast-uri `3.1.9`, addressing
[GHSA-333x-w4r6-6crp](https://github.com/advisories/GHSA-333x-w4r6-6crp).
These runtime resolutions do not need new root overrides. The scoped development
overrides above do not govern downstream installs. Undici's existing development
Node.js `22.19.0` minimum and the published `22.13.0` minimum remain distinct.
Node.js `22.19.0` was the development minimum and `22.13.0` the published
minimum at that review. Chokidar 6 now requires Node.js `22.22.0` for both
source installs and published package use.

### Removed Overrides and Resolved Pi Exception

Expand Down
4 changes: 2 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Installation and host setup

`open-codebase-index` requires Node.js 22.13 or newer and is the preferred package identity. Node.js 24 LTS is recommended. `opencode-codebase-index` remains a supported legacy package alias, and `open-codebase-index-mcp` is the preferred binary alias.
`open-codebase-index` requires Node.js 22.22 or newer and is the preferred package identity. Node.js 24 LTS is recommended. `opencode-codebase-index` remains a supported legacy package alias, and `open-codebase-index-mcp` is the preferred binary alias.

The published package includes the MCP server dependencies and native binaries for supported platforms.

Expand Down Expand Up @@ -308,7 +308,7 @@ Progress and diagnostics for `index` are written to `stderr`; the final formatte

## Local source checkout

Development requires Node.js 22.19 or newer within 22.x, 24.11 or newer within 24.x, or 26 or newer. This build-tool requirement does not raise the published Node.js 22.13 runtime floor.
Development requires Node.js 22.22 or newer within 22.x, 24.11 or newer within 24.x, or 26 or newer. Chokidar 6's engine requirement applies to source installs as well as published packages.

```bash
npm ci
Expand Down
2 changes: 1 addition & 1 deletion docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ References must be executable native-parsed calls or constructions inside exact

Bounded CommonJS support accepts an immutable top-level `const Name = require('literal')` only when the importer has proved CommonJS origin and `require` is an unshadowed Node builtin. The target must have one statically available root `module.exports = Name`, either unconditional or inside the supported `typeof module !== 'undefined'` availability guard without an alternate branch, and have proved CommonJS format. `.cjs`/`.cts` are explicit CommonJS; ambiguous extensions require bounded nearest in-root package-format evidence. An ownership boundary, missing proof, runtime ESM syntax, shadowed/reassigned builtins, dynamic/conditional/competing exports or export-object escape causes abstention. ESM default import of a proved CommonJS root remains supported; requiring an ESM default is not treated as importing that function. Arbitrary compiler rewrites and CommonJS property-export patterns are not inferred.

Package selection models the default Node 22.13+ conditions: `node`, `node-addons`, `module-sync` and `default`, plus mode-specific `import` for ESM or `require` for CommonJS. Declaration order, exact/wildcard precedence and explicit blocks are retained; custom runtime condition sets and opt-out flags are not inferred from source. Legacy `require` entry points use only `main`, not bundler `module` or declaration `types`. Assignment-form `for-in`/`for-of` headers (including destructuring and `for await`) and parenthesized writes, including leading comments and nested parentheses, invalidate affected binding proofs. Ordinary functions' implicit `arguments` objects block outer bindings in parameters and bodies; arrows inherit lexical ownership. Computed method names execute in the enclosing scope and retain its constructor owner. Potential sloppy-script Annex B hoists also invalidate existing body-level function declarations without opening ambiguous parameter/var bindings. Explicit strict directives, runtime ES module syntax, class strictness and lexical declaration conflicts retain block-function isolation.
Package selection models the default Node 22.22+ conditions: `node`, `node-addons`, `module-sync` and `default`, plus mode-specific `import` for ESM or `require` for CommonJS. Declaration order, exact/wildcard precedence and explicit blocks are retained; custom runtime condition sets and opt-out flags are not inferred from source. Legacy `require` entry points use only `main`, not bundler `module` or declaration `types`. Assignment-form `for-in`/`for-of` headers (including destructuring and `for await`) and parenthesized writes, including leading comments and nested parentheses, invalidate affected binding proofs. Ordinary functions' implicit `arguments` objects block outer bindings in parameters and bodies; arrows inherit lexical ownership. Computed method names execute in the enclosing scope and retain its constructor owner. Potential sloppy-script Annex B hoists also invalidate existing body-level function declarations without opening ambiguous parameter/var bindings. Explicit strict directives, runtime ES module syntax, class strictness and lexical declaration conflicts retain block-function isolation.

Ordinary function declarations/expressions can support construction when native syntax proves constructability. Arrow, async and generator functions do not; classes support construction, not bare calls. Same-name foreign imports, comments, strings, substring matches, import-only mentions, registration arguments and arbitrary object/`this` methods are not linked evidence. Syntax errors, unsupported runtime binding patterns or escaped runtime binding spellings, and unverified or unresolved nearer configuration fail linkage closed. Aliases outside the bounded candidate set, namespace calls, dynamic registrations and unsupported container shapes can be missed; this is discovery, not exhaustive coverage analysis. Missing or ambiguous targets return no linked evidence; missing or stale graphs cannot create caller linkage. When linked discovery misses, ordinary scoped conceptual retrieval remains available without direct/related labels.

Expand Down
36 changes: 26 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"pi"
],
"engines": {
"node": ">=22.13.0"
"node": ">=22.22.0"
},
"scripts": {
"build": "npm run build:native && npm run build:ts",
Expand Down Expand Up @@ -105,7 +105,7 @@
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"@opencode-ai/sdk": "1.3.13",
"chokidar": "^5.0.0",
"chokidar": "^6.0.0",
"ignore": "^7.0.5",
"p-queue": "^10.0.0",
"p-retry": "^8.0.1",
Expand Down
44 changes: 26 additions & 18 deletions src/watcher/file-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,32 +306,40 @@ export class FileWatcher {

private canRecoverSourceWatchPermissionError(error: NodeJS.ErrnoException): boolean {
if (error.syscall !== "watch" || typeof error.path !== "string" || !path.isAbsolute(error.path)) return false;
// The auxiliary Chokidar watcher must not take ownership of native source coverage.
if (this.nativeWatcher || this.nativeStarting) return false;

const projectRoot = path.resolve(this.projectRoot);
const filePath = path.resolve(error.path);
const relativePath = path.relative(projectRoot, filePath);
if (!relativePath || this.isOutsideProjectPath(relativePath)) return false;
const watchPath = path.resolve(error.path);
if (this.isOutsideProjectPath(path.relative(projectRoot, watchPath))) return false;

const ignoreFilter = createIgnoreFilter(projectRoot);
const gitIgnoreFilter = createGitIgnoreFilter(projectRoot);
const includeIgnored = this.config.indexing?.includeIgnored ?? [];
const filterOptions = this.getPathFilterOptions();
if (
this.isProjectConfigPath(filePath)
|| this.localModuleConfigTracker.has(filePath)
|| shouldTrackLocalModuleConfigPath(filePath, projectRoot, ignoreFilter, this.getSnapshotConfig())
|| (path.basename(filePath).toLowerCase() === "package.json"
&& this.localModuleConfigTracker.shouldTrackPackagePath(filePath))
|| !shouldIncludeFile(
filePath, projectRoot, [...this.config.include, ...(this.config.additionalInclude ?? [])],
this.config.exclude, ignoreFilter, includeIgnored, gitIgnoreFilter, filterOptions,
)
) return false;

try {
if (!lstatSync(filePath).isFile()) return false;
accessSync(filePath, constants.R_OK);
let directory = path.dirname(filePath);
const state = lstatSync(watchPath);
let directory = watchPath;
if (state.isFile()) {
if (
this.isProjectConfigPath(watchPath)
|| this.localModuleConfigTracker.has(watchPath)
|| shouldTrackLocalModuleConfigPath(watchPath, projectRoot, ignoreFilter, this.getSnapshotConfig())
|| (path.basename(watchPath).toLowerCase() === "package.json"
&& this.localModuleConfigTracker.shouldTrackPackagePath(watchPath))
|| !shouldIncludeFile(
watchPath, projectRoot, [...this.config.include, ...(this.config.additionalInclude ?? [])],
this.config.exclude, ignoreFilter, includeIgnored, gitIgnoreFilter, filterOptions,
)
) return false;
accessSync(watchPath, constants.R_OK);
directory = path.dirname(watchPath);
} else if (!state.isDirectory()) {
return false;
}

// Chokidar 6 registers directories, including empty ones that can gain selected files.
// Reuse watch traversal policy rather than requiring an already-present source file.
for (;;) {
if (!shouldTraverseDirectory(
directory, projectRoot, this.config.exclude, ignoreFilter, includeIgnored, gitIgnoreFilter, filterOptions,
Expand Down
Loading
Loading