---
pr: 175
title: fix(repo-readme): create mode does not overwrite existing local README
---

## Что сделано

Фикс бага #148 в тулзе `create-readme` (`.opencode/tools/create-readme.ts`):
локальный режим (`file_path`, без `repo`) вызывал `writeFileSync(file_path, ...)`
и `readFileSync(file_path, ...)` с относительным `file_path` (default
`README.md`), который резолвился относительно CWD процесса плагина Bun, а НЕ
относительно `context.worktree` (рабочей директории сессии пользователя).
Результат: отчёт `README.md created at README.md` — ложный success, файл на
диске не менялся.

Root cause: `spawnSync` вызовы в удалённом режиме корректно передавали
`cwd: context.worktree`, но `fs.*Sync` в локальном режиме — нет, пути
резолвились не там.

Изменения:

- `.opencode/tools/create-readme.ts`:
  - Добавлен `import path from "path"`.
  - `absPath = path.resolve(context.worktree, file_path)` — путь резолвится
    относительно рабочей директории сессии.
  - `writeFileSync(absPath, ...)` и `readFileSync(absPath, ...)` — запись/чтение
    в/из корректной директории. Существующий README гарантированно
    перезаписывается (создаётся новый если не существует).
  - Удалённый режим (`args.repo`) не тронут — уже использовал
    `cwd: context.worktree` для `spawnSync`.

- `tests/_ts_loader.mjs` — расширение лоадера для тестирования `create-readme.ts`
  (локальный режим использует `fs`):
  - `stripTs`: обработка `import { readFileSync, writeFileSync } from "fs"` →
    `const { ... } = require("fs")`.
  - `stripTs`: `g`-флаг для стрипа `type X = ...` (несколько алиасов в одном
    файле: `Feature`, `CustomSection`, `CreateArgs`).
  - `stripTs`: стрип многострочных `type X = { ... }` object-типов.
  - `stripTs`: обобщение стрипа return type (объектный литерал как return type
    `validateReadme(...): { ok: boolean; issues: string[] }`).
  - `stripTs`: стрип type annotations в `const/let/var x: Type = ...`.
  - `stripTs`: стрип TS non-null assertions `x!` → `x` (не трогает `!=`, `!==`,
    унарный `!foo`).
  - `loadTool(spawnSyncImpl, fsImpl)`: `fs` передаётся в sandbox через `new
    Function(..., "fs", ...)` и `require("fs")` shim возвращает модуль
    (настоящий `node:fs` по умолчанию или mock).

- `tests/test_create_readme_tool.py` — новый файл, 14 регрессионных тестов:
  - `test_loader_can_load_tool` — sanity (args declared).
  - `test_local_create_overwrites_existing_readme` — регрессия #148: существующий
    README перезаписывается (mtime обновляется, delimiter-теги присутствуют,
    старый контент отсутствует).
  - `test_local_create_creates_new_readme` — создаёт новый файл.
  - `test_local_create_custom_subdir_file_path` — пишет в поддиректорию.
  - `test_remote_create_makes_two_spawnsync_calls` — удалённый режим: 2 вызова
    (GET sha + PUT), не сломан фиксом.
  - `test_remote_create_passes_worktree_cwd` — `cwd=context.worktree` в opts.
  - `test_local_validate_reads_file_path` — локальный validate читает из
    `file_path`.
  - `test_local_validate_reports_missing_delimiters` — malformed README → issues.
  - 6 валидационных ошибок: missing repo_name, missing tagline_ru, uppercase
    repo_name, latin tagline_ru, cyrillic tagline_en, empty features_en.

- `.opencode/skills/repo-readme/SKILL.md` — раздел 3: уточнение, что `file_path`
  резолвится относительно `context.worktree` и существующий файл
  перезаписывается.

- `docs/project-map/README.md` — обновлены описания `create-readme.ts`,
  `_ts_loader.mjs`, добавлен `test_create_readme_tool.py`.

## Почему

Issue #148: `create-readme` (mode `create`, локальный режим, без `repo`)
возвращал `README.md created at README.md`, но файл на диске не изменялся —
`stat` показывал прежний mtime, delimiter-теги отсутствовали. Скилл
`repo-readme` (раздел 6) обещает «перезапишет README.md (локально) через
`fs.writeFileSync`» — фактически ложный success. Тот же root cause затрагивал
`validate` (читал из неверного пути, не находил файл → ошибка вместо
валидации реального README).

Фикс `path.resolve(context.worktree, file_path)` гарантирует, что путь
резолвится относительно рабочей директории сессии (как и `spawnSync` в
удалённом режиме), а не относительно CWD процесса плагина. Перешёл от
«relative path против CWD процесса» к «absolute path относительно worktree» —
симметрично с тем, как `spawnSync` получает `cwd`.

## Pending

—

## Watch out

Лоадер `tests/_ts_loader.mjs` расширен для поддержки `fs` и расширенного
TS-стриппинга (многострочные type-алиасы, non-null assertions, type
annotations в переменных). Эти расширения безопасны для существующих тулз
(проверено: `commit.ts`, `draw-image.ts`, `create-pr.ts`,
`create-issue.ts` — все загружаются и 459 тестов проходят). Но если будущая
тулза использует более сложный TS-синтаксис (generics в вызовах, conditional
types, template literal types), лоадер может потребовать дальнейшего
расширения `stripTs`.

Тесты локального режима используют абсолютный `file_path` во временной
директории (`tempfile.TemporaryDirectory`), чтобы избежать перезаписи
README репозитория (лоадер хардкодит `worktree: REPO_ROOT`, относительный
путь `README.md` резолвился бы в `REPO_ROOT/README.md`). Для полноценного
теста `path.resolve(context.worktree, relative)` потребовалось бы параметризовать
`worktree` в лоадере — оставлено как future improvement (зафиксировано в
комментарии теста).