opencode-config/docs/handoff/pr-175-overwrite-existing-local-readme.md
Sergey 4258621def
fix(repo-readme): create mode does not overwrite existing local README (#175)
* fix(create-readme): resolve file_path against context.worktree

* test(loader): support fs imports and extended TS stripping

* test(create-readme): add regression tests for local overwrite

* docs(repo-readme): note file_path resolves against worktree

* docs(handoff): set PR number

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-31 21:10:29 +03:00

108 lines
No EOL
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 (зафиксировано в
комментарии теста).