npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

opencode-hashline

v1.3.0

Published

Hashline plugin for OpenCode — content-addressable line hashing for precise AI code editing

Readme

🔗 opencode-hashline

Контентно-адресуемое хеширование строк для точного редактирования кода с помощью AI

CI Release npm version npm downloads GitHub release License: MIT semantic-release TypeScript Node.js

🇷🇺 Русский | 🇬🇧 English

Hashline-плагин для OpenCode — аннотирует каждую строку файла детерминированным хеш-тегом, чтобы AI мог ссылаться на код и редактировать его с хирургической точностью.


📖 Что такое Hashline?

Hashline аннотирует каждую строку файла коротким детерминированным hex-хешем. Когда AI читает файл, он видит:

#HL 1:a3f|function hello() {
#HL 2:f1c|  return "world";
#HL 3:0e7|}

Примечание: Длина хеша адаптивная — она зависит от размера файла (2 символа для ≤256 строк, 3 символа для ≤4096 строк, 4 символа для >4096 строк). В примерах ниже используются 3-символьные хеши. Префикс #HL защищает от ложных срабатываний при удалении хешей и является настраиваемым.

AI-модель может ссылаться на строки по их хеш-тегам для точного редактирования:

  • «Заменить строку 2:f1c» — указать конкретную строку однозначно
  • «Заменить блок от 1:a3f до 3:0e7» — указать диапазон строк
  • «Вставить после 3:0e7» — вставить в точное место

🤔 Почему это помогает?

Традиционные номера строк сдвигаются при редактировании, вызывая ошибки смещения и устаревшие ссылки. Хеш-теги Hashline контентно-адресуемы — они вычисляются из индекса строки и её содержимого, что делает их стабильной, верифицируемой ссылкой для точной коммуникации о местоположении в коде.


✨ Возможности

📏 Адаптивная длина хеша

Длина хеша автоматически адаптируется к размеру файла для минимизации коллизий:

| Размер файла | Длина хеша | Возможных значений | |-------------|:----------:|:------------------:| | ≤ 256 строк | 2 hex-символа | 256 | | ≤ 4 096 строк | 3 hex-символа | 4 096 | | > 4 096 строк | 4 hex-символа | 65 536 |

🏷️ Магический префикс (#HL )

Строки аннотируются настраиваемым префиксом (по умолчанию: #HL ), чтобы предотвратить ложные срабатывания при удалении хешей. Это гарантирует, что строки данных вроде 1:ab|some data не будут случайно обрезаны.

#HL 1:a3|function hello() {
#HL 2:f1|  return "world";
#HL 3:0e|}

Префикс можно настроить или отключить для обратной совместимости:

// Кастомный префикс
const hl = createHashline({ prefix: ">> " });

// Отключить префикс (legacy-формат: "1:a3|code")
const hl = createHashline({ prefix: false });

💾 LRU-кеширование

Встроенный LRU-кеш (filePath → annotatedContent) с настраиваемым размером (по умолчанию 100 файлов). При повторном чтении того же файла с неизменённым содержимым возвращается кешированный результат. Кеш автоматически инвалидируется при изменении содержимого файла.

✅ Верификация хешей

Проверка того, что строка не изменилась с момента чтения — защита от race conditions:

import { verifyHash } from "opencode-hashline";

const result = verifyHash(2, "f1c", currentContent);
if (!result.valid) {
  console.error(result.message); // "Hash mismatch at line 2: ..."
}

Верификация хешей использует длину предоставленной хеш-ссылки (а не текущий размер файла), поэтому ссылка вроде 2:f1 остаётся валидной даже если файл вырос.

🔍 Чувствительность к отступам

Вычисление хеша использует trimEnd() (а не trim()), поэтому изменения ведущих пробелов (отступов) обнаруживаются как изменения содержимого, а завершающие пробелы игнорируются.

📐 Range-операции

Резолвинг и замена диапазонов строк по хеш-ссылкам:

import { resolveRange, replaceRange } from "opencode-hashline";

// Получить строки между двумя хеш-ссылками
const range = resolveRange("1:a3f", "3:0e7", content);
console.log(range.lines); // ["function hello() {", '  return "world";', "}"]

// Заменить диапазон новым содержимым
const newContent = replaceRange(
  "1:a3f", "3:0e7", content,
  "function goodbye() {\n  return 'farewell';\n}"
);

⚙️ Конфигурируемость

Создание кастомных экземпляров Hashline с определёнными настройками:

import { createHashline } from "opencode-hashline";

const hl = createHashline({
  exclude: ["**/node_modules/**", "**/*.min.js"],
  maxFileSize: 512_000,  // 512 КБ
  hashLength: 3,         // принудительно 3-символьные хеши
  cacheSize: 200,        // кешировать до 200 файлов
  prefix: "#HL ",        // магический префикс (по умолчанию)
});

// Использование настроенного экземпляра
const annotated = hl.formatFileWithHashes(content, "src/app.ts");
const isExcluded = hl.shouldExclude("node_modules/foo.js"); // true

Параметры конфигурации

| Параметр | Тип | По умолчанию | Описание | |----------|-----|:------------:|----------| | exclude | string[] | См. ниже | Glob-паттерны для исключения файлов | | maxFileSize | number | 1_000_000 | Макс. размер файла в байтах | | hashLength | number \| undefined | undefined (адаптивно) | Принудительная длина хеша | | cacheSize | number | 100 | Макс. файлов в LRU-кеше | | prefix | string \| false | "#HL " | Префикс строки (false для отключения) |

Паттерны исключения по умолчанию: lock-файлы, node_modules, минифицированные файлы, бинарные файлы (изображения, шрифты, архивы и т.д.).


📦 Установка

npm install opencode-hashline

🔧 Конфигурация

Добавьте плагин в ваш opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-hashline"]
}

Файлы конфигурации

Плагин загружает конфигурацию из следующих мест (в порядке приоритета, более поздние перезаписывают ранние):

| Приоритет | Расположение | Область | |:---------:|-------------|---------| | 1 | ~/.config/opencode/opencode-hashline.json | Глобальная (все проекты) | | 2 | <project>/opencode-hashline.json | Локальная (проект) | | 3 | Программная конфигурация через createHashlinePlugin() | Аргумент фабрики |

Пример opencode-hashline.json:

{
  "exclude": ["**/node_modules/**", "**/*.min.js"],
  "maxFileSize": 1048576,
  "hashLength": 0,
  "cacheSize": 100,
  "prefix": "#HL "
}

Вот и всё! Плагин автоматически:

| # | Действие | Описание | |:-:|----------|----------| | 1 | 📝 Аннотирует чтение файлов | При чтении файла AI каждая строка получает #HL хеш-префикс | | 2 | 📎 Аннотирует @file упоминания | Файлы, прикреплённые через @filename в промпте, тоже аннотируются хешлайнами | | 3 | ✂️ Убирает хеш-префиксы при редактировании | При записи/редактировании файла хеш-префиксы удаляются перед применением изменений | | 4 | 🧠 Внедряет инструкции в системный промпт | AI получает инструкции по интерпретации и использованию hashline-ссылок | | 5 | 💾 Кеширует результаты | Повторные чтения того же файла возвращают кешированные аннотации | | 6 | 🔍 Фильтрует по инструменту | Только инструменты чтения файлов (например read_file, cat, view) получают аннотации; остальные не затрагиваются | | 7 | ⚙️ Учитывает конфигурацию | Исключённые файлы и файлы, превышающие maxFileSize, пропускаются | | 8 | 🧩 Регистрирует hashline_edit tool | Применяет replace/delete/insert по hash-ссылкам без точного old_string-матчинга |


🛠️ Как это работает

Вычисление хеша

Хеш каждой строки вычисляется из:

  • 0-based индекса строки
  • Содержимого строки с обрезанными завершающими пробелами (trimEnd) — ведущие пробелы (отступы) ЗНАЧИМЫ

Это подаётся в хеш-функцию FNV-1a, сводится к соответствующему модулю в зависимости от размера файла и отображается как hex-строка.

Хуки и tool плагина

Плагин регистрирует четыре хука OpenCode и один кастомный tool:

| Хук | Назначение | |-----|-----------| | tool.hashline_edit | Hash-aware правки по ссылкам вроде 5:a3f или #HL 5:a3f|... | | tool.execute.after | Добавляет hashline-аннотации в вывод инструментов чтения файлов | | tool.execute.before | Убирает hashline-префиксы из аргументов инструментов редактирования | | chat.message | Аннотирует @file упоминания в сообщениях пользователя (записывает аннотированный контент во временный файл и подменяет URL) | | experimental.chat.system.transform | Добавляет инструкции по использованию hashline в системный промпт |


🔌 Программный API

Основные утилиты экспортируются из субпути opencode-hashline/utils (чтобы избежать конфликтов с загрузчиком плагинов OpenCode, который вызывает каждый экспорт как функцию Plugin):

import {
  computeLineHash,
  formatFileWithHashes,
  stripHashes,
  parseHashRef,
  normalizeHashRef,
  buildHashMap,
  getAdaptiveHashLength,
  verifyHash,
  resolveRange,
  replaceRange,
  applyHashEdit,
  HashlineCache,
  createHashline,
  shouldExclude,
  matchesGlob,
  resolveConfig,
  DEFAULT_PREFIX,
} from "opencode-hashline/utils";

Основные функции

// Вычислить хеш для одной строки
const hash = computeLineHash(0, "function hello() {"); // например "a3f"

// Вычислить хеш с определённой длиной
const hash4 = computeLineHash(0, "function hello() {", 4); // например "a3f2"

// Аннотировать содержимое файла (адаптивная длина хеша, с префиксом #HL)
const annotated = formatFileWithHashes(fileContent);
// "#HL 1:a3|function hello() {\n#HL 2:f1|  return \"world\";\n#HL 3:0e|}"

// Аннотировать с определённой длиной хеша
const annotated3 = formatFileWithHashes(fileContent, 3);

// Аннотировать без префикса (legacy-формат)
const annotatedLegacy = formatFileWithHashes(fileContent, undefined, false);

// Убрать аннотации, получить оригинальное содержимое
const original = stripHashes(annotated);

Хеш-ссылки и верификация

// Разобрать хеш-ссылку
const { line, hash } = parseHashRef("2:f1c"); // { line: 2, hash: "f1c" }

// Нормализовать ссылку из аннотированной строки
const ref = normalizeHashRef("#HL 2:f1c|const x = 1;"); // "2:f1c"

// Построить карту соответствий
const map = buildHashMap(fileContent); // Map<"2:f1c", 2>

// Верифицировать хеш-ссылку (использует hash.length, а не размер файла)
const result = verifyHash(2, "f1c", fileContent);

Range-операции

// Резолвить диапазон
const range = resolveRange("1:a3f", "3:0e7", fileContent);

// Заменить диапазон
const newContent = replaceRange("1:a3f", "3:0e7", fileContent, "новое содержимое");

// Hash-aware операция редактирования (replace/delete/insert_before/insert_after)
const edited = applyHashEdit(
  { operation: "replace", startRef: "1:a3f", endRef: "3:0e7", replacement: "новое содержимое" },
  fileContent
).content;

Утилиты

// Проверить, нужно ли исключить файл
const excluded = shouldExclude("node_modules/foo.js", ["**/node_modules/**"]);

// Создать настроенный экземпляр
const hl = createHashline({ cacheSize: 50, hashLength: 3 });

📊 Бенчмарк

Корректность: hashline vs str_replace

Оба подхода протестированы на 60 фикстурах из react-edit-benchmark — мутированных файлах React с известными багами (инвертированные булевы, перепутанные операторы, удалённые guard-клаузы и т.д.):

| | hashline | str_replace | |---|:---:|:---:| | Прошло | 60/60 (100%) | 58/60 (96.7%) | | Провалено | 0 | 2 | | Неоднозначные правки | 0 | 4 |

str_replace ломается, когда old_string встречается в файле несколько раз (например, повторяющиеся guard-клаузы, похожие блоки кода). Hashline адресует каждую строку уникально через lineNumber:hash, поэтому неоднозначность исключена.

# Запустите сами:
npx tsx benchmark/run.ts              # режим hashline
npx tsx benchmark/run.ts --no-hash    # режим str_replace
  • structural-remove-early-return-001old_string совпал в нескольких местах, замена применена не к тому
  • structural-remove-early-return-002 — аналогичная проблема
  • structural-delete-statement-002 — неоднозначное совпадение (первое совпадение оказалось верным)
  • structural-delete-statement-003 — неоднозначное совпадение (первое совпадение оказалось верным)

Расход токенов

Аннотации hashline добавляют префикс #HL <line>:<hash>| (~12 символов / ~3 токена) на строку:

| | Без хешей | С хешами | Оверхед | |---|---:|---:|:---:| | Символы | 404K | 564K | +40% | | Токены (~) | ~101K | ~141K | +40% |

Оверхед стабильно ~40% независимо от размера файла. Для типичного файла на 200 строк (~800 токенов) hashline добавляет ~600 токенов — пренебрежимо мало при контекстном окне в 200K.

Производительность

| Размер файла | Аннотация | Правка | Удаление хешей | |-------------:|:---------:|:------:|:--------------:| | 10 строк | 0.05 мс | 0.01 мс | 0.03 мс | | 100 строк | 0.12 мс | 0.02 мс | 0.08 мс | | 1 000 строк | 0.95 мс | 0.04 мс | 0.60 мс | | 5 000 строк | 4.50 мс | 0.08 мс | 2.80 мс | | 10 000 строк | 9.20 мс | 0.10 мс | 5.50 мс |

Типичный файл из 1 000 строк аннотируется за < 1 мс — незаметно для пользователя.


🧑‍💻 Разработка

# Установить зависимости
npm install

# Запустить тесты
npm test

# Собрать
npm run build

# Проверка типов
npm run typecheck

💡 Вдохновение и теоретическая база

Идея hashline вдохновлена концепциями из oh-my-pi от can1357 — AI-тулкита для разработки (coding agent CLI, unified LLM API, TUI-библиотеки) — и статьи «The Harness Problem» (проблема обвязки).

Суть проблемы: современные AI-модели обладают огромными возможностями, но инструменты (harness), которые передают модели контекст и применяют её правки к файлам, теряют информацию и порождают ошибки. Модель видит содержимое файла, но при редактировании вынуждена «угадывать» контекст окружающих строк. Search-and-replace ломается на дубликатах строк, а diff-формат тоже ненадёжен на практике.

Hashline решает эту проблему, присваивая каждой строке короткий детерминированный хеш-тег (например, 2:f1c), что делает адресацию строк точной и однозначной. Модель может ссылаться на любую строку или диапазон без ошибок смещения и путаницы с дубликатами.

Ссылки:


📄 Лицензия

MIT © opencode-hashline contributors