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

7.2 KiB
Raw Blame History


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