feat(create-readme): add clickable steps to Quick Start (#146)

* feat(create-readme): add clickable steps and conditional bash block

* docs(repo-readme): document clickable steps params and example

* docs(handoff): add handoff and ADR for clickable-steps

* docs(handoff): set PR number

* docs(project-map): update create-readme params after PR#146

---------

Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
Sergey 2026-07-30 03:07:09 +03:00 committed by GitHub
parent 0d16b028fa
commit 8308de0981
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 234 additions and 15 deletions

View file

@ -104,7 +104,8 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA.
{git clone строка, если include_clone !== false} {git clone строка, если include_clone !== false}
{quick_start} {quick_start}
\`\`\` \`\`\`
{access_url строка если передан — Access at {url}} {quick_start_steps_en — нумерованный список кликабельных шагов, если передан: 1. ... 2. ...}
{access_url строка если передан — Access at [url](url), кликабельна}
{development_en блок, если передан — ### 🔧 Development + content} {development_en блок, если передан — ### 🔧 Development + content}
--- ---
@ -134,7 +135,8 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA.
{git clone строка, если include_clone !== false} {git clone строка, если include_clone !== false}
{quick_start} {quick_start}
\`\`\` \`\`\`
{access_url строка если передан — Доступ: {url}} {quick_start_steps_ru — нумерованный список кликабельных шагов, если передан: 1. ... 2. ...}
{access_url строка если передан — Доступ: [url](url), кликабельна}
{development_ru блок, если передан — ### 🔧 Разработка + content} {development_ru блок, если передан — ### 🔧 Разработка + content}
--- ---
@ -148,7 +150,8 @@ repos/{owner}/{repo}/contents/README.md` с base64-контентом и SHA.
features), непустой контент между ними, ссылку `slaid098.dev/support`, секции features), непустой контент между ними, ссылку `slaid098.dev/support`, секции
Quick Start (EN) и Быстрый старт (RU), language switcher `[English]` / Quick Start (EN) и Быстрый старт (RU), language switcher `[English]` /
`[Русский]`, заголовок `## 🇷🇺 Русский` (не "Русская версия"), anchor `[Русский]`, заголовок `## 🇷🇺 Русский` (не "Русская версия"), anchor
`[Русский](#-русский)` (не `#-русская-версия`). `[Русский](#-русский)` (не `#-русская-версия`). Шаги `quick_start_steps_*`
не влияют на валидацию — они рендерятся вне delimiter-пар (summary/features).
## 6. Независимость от repo-init ## 6. Независимость от repo-init
@ -171,8 +174,15 @@ Quick Start (EN) и Быстрый старт (RU), language switcher `[English]
`{ title, content }` (optional). `{ title, content }` (optional).
- `access_url` — URL для Access/Доступ строки после Quick Start bash-блока - `access_url` — URL для Access/Доступ строки после Quick Start bash-блока
(optional). EN: `Access at {url}`, RU: `Доступ: {url}`. Omit if no web access. (optional). EN: `Access at {url}`, RU: `Доступ: {url}`. Omit if no web access.
Рендерится как markdown-ссылка `[url](url)` — кликабельна на GitHub.
- `include_clone` — boolean (optional, default true). `false` убирает `git clone` - `include_clone` — boolean (optional, default true). `false` убирает `git clone`
из Quick Start. Для userscript, web-app, npm-package. из Quick Start. Для userscript, web-app, npm-package.
- `quick_start_steps_en` / `quick_start_steps_ru` — массивы raw-markdown строк
(optional). Каждая строка = один шаг, может содержать markdown-ссылки
`[text](url)`. Рендерятся как нумерованный список `1. ... 2. ...` ПОСЛЕ
bash-блока, ДО `access_url`. Кликабельные шаги для setup, где установка — это
не одна команда, а несколько ссылок (установить userscript, получить API-ключ,
настроить). Если массив пуст/не передан — шаги не рендерятся (backward compat).
- `development_en` — raw markdown (optional). `### 🔧 Development` после EN - `development_en` — raw markdown (optional). `### 🔧 Development` после EN
Quick Start, вне delimiter-тегов (не на slaid098.dev). Quick Start, вне delimiter-тегов (не на slaid098.dev).
- `development_ru` — raw markdown (optional). `### 🔧 Разработка` после RU - `development_ru` — raw markdown (optional). `### 🔧 Разработка` после RU
@ -186,8 +196,56 @@ Quick Start (EN) и Быстрый старт (RU), language switcher `[English]
- `include_clone: false` — убирает `git clone` из Quick Start - `include_clone: false` — убирает `git clone` из Quick Start
- `quick_start` — команда установки (npm install, pip install, или ссылка на - `quick_start` — команда установки (npm install, pip install, или ссылка на
установку userscript) установку userscript). Если установка — это несколько ссылок (а не одна
- `access_url` — URL web-доступа (если есть) команда), лучше использовать `quick_start_steps_*` вместо/вместе с bash-блоком.
- `quick_start_steps_en` / `quick_start_steps_ru` — кликабельные шаги setup
(рекомендуется для userscript/multi-step setup): установить userscript,
получить API-ключ, настроить. Каждая строка может содержать `[text](url)`.
Рендерятся как нумерованный список ПОСЛЕ bash-блока.
- `access_url` — URL web-доступа (если есть), рендерится как `[url](url)`.
- `development_en` / `development_ru` — инструкции для разработчиков (как - `development_en` / `development_ru` — инструкции для разработчиков (как
собрать, как контрибьютить), рендерятся после Quick Start, вне собрать, как контрибьютить), рендерятся после Quick Start, вне
delimiter-тегов (не на slaid098.dev) delimiter-тегов (не на slaid098.dev)
### Пример: userscript со шагами-ссылками
```json
{
"mode": "create",
"repo_name": "my-userscript",
"tagline": "One-line tagline.",
"why_en": "Why this exists.",
"what_en": "What it does.",
"why_ru": "Зачем этот проект.",
"what_ru": "Что делает.",
"quick_start": "",
"include_clone": false,
"features_en": [{ "emoji": "⚡", "name": "Fast", "description": "Instant setup" }],
"features_ru": [{ "emoji": "⚡", "name": "Быстрый", "description": "Мгновенный старт" }],
"access_url": "http://localhost:4096",
"quick_start_steps_en": [
"Install the [userscript](https://greasyfork.org/...)",
"Get a [Groq API key](https://console.groq.com/keys)",
"Configure [settings](https://example.com/settings)"
],
"quick_start_steps_ru": [
"Установи [юзерскрипт](https://greasyfork.org/...)",
"Получи [ключ Groq](https://console.groq.com/keys)",
"Настрой [параметры](https://example.com/settings)"
]
}
```
Результат (EN секция):
```markdown
### ⚡ Quick Start
1. Install the [userscript](https://greasyfork.org/...)
2. Get a [Groq API key](https://console.groq.com/keys)
3. Configure [settings](https://example.com/settings)
Access at [http://localhost:4096](http://localhost:4096)
```
`include_clone: false` + `quick_start: ""` → bash-блок не рендерится (только
шаги). Если `quick_start` непустой — bash-блок рендерится перед шагами.

View file

@ -22,6 +22,8 @@ type CreateArgs = {
development_ru?: string development_ru?: string
custom_sections_en?: CustomSection[] custom_sections_en?: CustomSection[]
custom_sections_ru?: CustomSection[] custom_sections_ru?: CustomSection[]
quick_start_steps_en?: string[]
quick_start_steps_ru?: string[]
} }
function extractBetween(text: string, start: string, end: string): string | null { function extractBetween(text: string, start: string, end: string): string | null {
@ -41,6 +43,12 @@ function renderFeaturesTable(features: Feature[], isRu: boolean): string {
return header + rows return header + rows
} }
function renderSteps(steps?: string[]): string {
if (!steps || steps.length === 0) return ""
const items = steps.map((s, i) => `${i + 1}. ${s}`).join("\n")
return `\n${items}\n`
}
function generateReadme(args: CreateArgs): string { function generateReadme(args: CreateArgs): string {
const customEn = (args.custom_sections_en || []) const customEn = (args.custom_sections_en || [])
.map((s) => `\n\n### ${s.title}\n${s.content}`) .map((s) => `\n\n### ${s.title}\n${s.content}`)
@ -48,14 +56,26 @@ function generateReadme(args: CreateArgs): string {
const customRu = (args.custom_sections_ru || []) const customRu = (args.custom_sections_ru || [])
.map((s) => `\n\n### ${s.title}\n${s.content}`) .map((s) => `\n\n### ${s.title}\n${s.content}`)
.join("") .join("")
const accessLineEn = args.access_url ? `\nAccess at ${args.access_url}\n` : "" const accessLineEn = args.access_url ? `\nAccess at [${args.access_url}](${args.access_url})\n` : ""
const accessLineRu = args.access_url ? `\оступ: ${args.access_url}\n` : "" const accessLineRu = args.access_url ? `\оступ: [${args.access_url}](${args.access_url})\n` : ""
const developmentBlockEn = args.development_en const developmentBlockEn = args.development_en
? `\n\n### 🔧 Development\n${args.development_en}\n` ? `\n\n### 🔧 Development\n${args.development_en}\n`
: "" : ""
const developmentBlockRu = args.development_ru const developmentBlockRu = args.development_ru
? `\n\n### 🔧 Разработка\n${args.development_ru}\n` ? `\n\n### 🔧 Разработка\n${args.development_ru}\n`
: "" : ""
const cloneLine = args.include_clone !== false
? `git clone https://github.com/slaid098/${args.repo_name}.git\n`
: ""
const hasBashBlock = args.include_clone !== false || args.quick_start !== ""
const bashBlockEn = hasBashBlock
? `\`\`\`bash\n${cloneLine}${args.quick_start}\n\`\`\``
: ""
const bashBlockRu = hasBashBlock
? `\`\`\`bash\n${cloneLine}${args.quick_start}\n\`\`\``
: ""
const stepsEn = renderSteps(args.quick_start_steps_en)
const stepsRu = renderSteps(args.quick_start_steps_ru)
return `# 🚀 ${args.repo_name} return `# 🚀 ${args.repo_name}
> ${args.tagline} > ${args.tagline}
@ -79,9 +99,7 @@ ${renderFeaturesTable(args.features_en, false)}
<!-- features-en:end -->${customEn} <!-- features-en:end -->${customEn}
### Quick Start ### Quick Start
\`\`\`bash ${bashBlockEn}${stepsEn}${accessLineEn}${developmentBlockEn}
${args.include_clone !== false ? `git clone https://github.com/slaid098/${args.repo_name}.git\n` : ""}${args.quick_start}
\`\`\`${accessLineEn}${developmentBlockEn}
--- ---
## 🇷🇺 Русский ## 🇷🇺 Русский
@ -99,9 +117,7 @@ ${renderFeaturesTable(args.features_ru, true)}
<!-- features-ru:end -->${customRu} <!-- features-ru:end -->${customRu}
### Быстрый старт ### Быстрый старт
\`\`\`bash ${bashBlockRu}${stepsRu}${accessLineRu}${developmentBlockRu}
${args.include_clone !== false ? `git clone https://github.com/slaid098/${args.repo_name}.git\n` : ""}${args.quick_start}
\`\`\`${accessLineRu}${developmentBlockRu}
--- ---
## 💬 Support and contacts / Поддержка и контакты ## 💬 Support and contacts / Поддержка и контакты
@ -195,6 +211,14 @@ export default tool({
.string() .string()
.optional() .optional()
.describe("Install/setup command (e.g. 'pip install -r requirements.txt'). Required for create mode."), .describe("Install/setup command (e.g. 'pip install -r requirements.txt'). Required for create mode."),
quick_start_steps_en: tool.schema
.array(tool.schema.string())
.optional()
.describe("Array of raw-markdown strings rendered as a numbered list of clickable steps after the EN Quick Start bash block. Each string = one step, may contain markdown links [text](url). Omit to render only the bash block."),
quick_start_steps_ru: tool.schema
.array(tool.schema.string())
.optional()
.describe("Array of raw-markdown strings rendered as a numbered list of clickable steps after the RU Быстрый старт bash block. Each string = one step, may contain markdown links [text](url). Omit to render only the bash block."),
features_en: tool.schema features_en: tool.schema
.array( .array(
tool.schema.object({ tool.schema.object({
@ -296,6 +320,8 @@ export default tool({
development_ru: args.development_ru, development_ru: args.development_ru,
custom_sections_en: args.custom_sections_en, custom_sections_en: args.custom_sections_en,
custom_sections_ru: args.custom_sections_ru, custom_sections_ru: args.custom_sections_ru,
quick_start_steps_en: args.quick_start_steps_en,
quick_start_steps_ru: args.quick_start_steps_ru,
}) })
if (args.repo) { if (args.repo) {

View file

@ -0,0 +1,57 @@
# ADR-062: Clickable steps in Quick Start + conditional bash block (PR-146)
## Статус
Accepted (2026-07-29)
## Контекст
Тулза `create-readme` генерировала Quick Start как единый bash code-fence
(``` ```bash ... ``` ```) с опциональной плоской строкой `Access at {url}` /
`Доступ: {url}` после него. Два ограничения:
1. **Markdown-ссылки внутри code-fence не кликабельны** — для multi-step setup
(userscript: установить скрипт → получить API-ключ → настроить) bash-блок
не подходит: нужны кликабельные `[text](url)` шаги.
2. **Пустой bash-блок портил README** — при `quick_start` пустом и
`include_clone=false` рендерился пустой ` ```bash ``` ` (визуальный мусор).
3. **`access_url` был bare URL** — авто-линк GitHub делает его кликабельным,
но явная markdown-ссылка `[url](url)` надёжнее и в raw-просмотре.
## Решение
1. **Новые параметры** `quick_start_steps_en?: string[]` /
`quick_start_steps_ru?: string[]` — массивы raw-markdown строк. Каждый
элемент = один шаг, может содержать `[text](url)`.
2. **Helper `renderSteps(steps?)`** — возвращает `""` для пустого/undefined
массива, иначе нумерованный список `\n1. ...\n2. ...\n` с пустыми строками
вокруг (markdown-разделение от code-fence).
3. **Рендер шагов**`${renderSteps(...)}` вставлен ПОСЛЕ bash-блока, ДО
`accessLine*`, в обеих секциях (EN и RU).
4. **Условный bash-блок** — `hasBashBlock = include_clone !== false ||
quick_start !== ""`. Если оба false → bash-fence не рендерится (только
шаги, если есть). `cloneLine` вынесен в общую переменную. Default behavior
сохранён (`include_clone` не передан → `!== false` → true → git clone).
5. **Кликабельный `access_url`**`[url](url)` вместо bare URL.
6. **Валидатор** — без изменений: шаги вне delimiter-пар, секции "Quick Start" /
"Быстрый старт" присутствуют всегда (заголовок рендерится независимо).
Backward compatibility: все существующие параметры/вызовы работают как раньше.
Новые параметры опциональны — если не переданы, Quick Start выглядит как
раньше (bash-блок + опциональный clickable `access_url`).
## Альтернативы
- **Шаги внутри code-fence** — отвергнуто: markdown-ссылки `[text](url)` НЕ
кликабельны внутри ``` ```bash ``` (рендерятся как текст). Нужен
нумерованный markdown-список вне code-fence.
- **Отдельный параметр `steps_mode: "bash" | "list"`** — отвергнуто: bash-блок
и steps не взаимоисключающи. Можно рендерить bash-команду (установка) И шаги
(получить ключ, настроить) вместе. Условный bash-блок через `hasBashBlock`
покрывает все кейсы.
- **Ослабить required-чек `quick_start` в `execute()`** — отвергнуто: спека
не просит менять required-валидацию. Сценарий "только шаги без bash" через
`execute` требует `quick_start: ""` + `include_clone: false`, но `execute`
отвергает пустой `quick_start`. `generateReadme` обрабатывает `quick_start=""
` корректно (bash не рендерится). Ослабление required — отдельное решение,
вне scope этого PR.
- **Всегда рендерить bash-блок** — отвергнуто: пустой ` ```bash ``` ` портит
README (исходная проблема). Условный рендер через `hasBashBlock` чище.
- **`access_url` как inline `[text](url)` с настраиваемым текстом** — отвергнуто:
спека явно требует `[url](url)` (URL как text). Простой и предсказуемый.

View file

@ -0,0 +1,78 @@
---
pr: 146
title: "feat(create-readme): add clickable steps to Quick Start"
---
## Что сделано
Расширение тулзы `create-readme` (`.opencode/tools/create-readme.ts`) —
кликабельные шаги-ссылки в Quick Start, условный bash-блок, кликабельный
`access_url`. Backward-compatible.
1. **Новые параметры**`quick_start_steps_en?: string[]` и
`quick_start_steps_ru?: string[]` добавлены в тип `CreateArgs` (строки 25-26)
и в `tool.schema` (после `quick_start`, строки 214-220). Каждый элемент =
raw-markdown строка, может содержать `[text](url)`. `.optional()`.
2. **Helper `renderSteps`** (строки 46-50) — если массив пуст/undefined → `""`,
иначе нумерованный список `1. ...\n2. ...\n` с пустыми строками вокруг для
markdown-разделения от code-fence.
3. **Рендер шагов**`${renderSteps(args.quick_start_steps_*)}` вставлен после
bash-блока, до `accessLine*` в обеих секциях (EN: строка 102, RU: строка 120).
4. **Условный bash-блок** — `hasBashBlock = include_clone !== false ||
quick_start !== ""` (строка 70). Если оба условия false → bash-fence НЕ
рендерится (только шаги). `cloneLine` вынесен в общую переменную (строки
67-69). Применено к EN (`bashBlockEn`, строки 71-73) и RU (`bashBlockRu`,
строки 74-76). Default behavior сохранён: `include_clone` не передан →
`!== false` → true → git clone рендерится.
5. **Кликабельный `access_url`** (строки 59-60) — bare URL заменён на
`[url](url)`. EN: `Access at [url](url)`, RU: `Доступ: [url](url)`.
6. **Передача параметров**`quick_start_steps_en/ru` прокинуты в вызов
`generateReadme({...})` в `execute()` (строки 324-325).
7. **Валидатор**`validateReadme` НЕ изменён: шаги рендерятся вне delimiter-пар
(summary/features) и не влияют ни на одну проверку (секции "Quick Start" /
"Быстрый старт" присутствуют). Проверено smoke-тестом: все 28 checks PASS,
`validate` проходит на всех сгенерированных README.
8. **Документация**`.opencode/skills/repo-readme/SKILL.md` обновлён: новые
параметры в секции 7, кликабельный `access_url` отмечен, reference-шаблон
(секция 5) отражает шаги + clickable access, секция 8 (кейс userscript)
расширена, добавлен пример вызова + ожидаемый вывод.
### Smoke-test
Прогнан через `npx tsx` с реальным `@opencode-ai/plugin` из
`.opencode/node_modules/` (mock не понадобился — плагин установлен). 5
сценариев, 28 assertions:
- S1: default (backward compat) — bash + git clone, no steps, no access.
- S2: steps + clickable access (main spec scenario) — вывод точно совпадает с
примером из issue.
- S3: asymmetric (steps only on RU).
- S4: `include_clone=false` + non-empty `quick_start` → bash без git clone.
- S5: empty steps array → no list rendered.
Все `validate` PASS на сгенерированных README.
## Почему
Quick Start умел только bash-блок (markdown-ссылки внутри code-fence НЕ
кликабельны) и плоский `access_url`. Для multi-step setup (userscript:
установить → получить API-ключ → настроить) нужен нумерованный список
кликабельных шагов. Условный bash-блок убирает пустой ` ```bash ``` ` когда
установки нет (только шаги). Кликабельный `access_url` улучшает UX.
## Pending
## Watch out
- **`quick_start` остаётся required** для `create` mode в `execute()` (строки
296-301). Сценарий "пустой quick_start + только steps" через `execute`
невозможен без ослабления required-чеков — спека этого НЕ просит. Чтобы
рендерить только шаги (без bash), передай `quick_start: ""` + `include_clone:
false` — НО `execute` отвергнет пустой `quick_start`. Обход: передать
минимальный `quick_start` (например пробел/коммент) или ослабить required в
отдельном issue. В `generateReadme` напрямую логика `hasBashBlock` корректна
для `quick_start=""`.
- **`access_url` визуально почти идентичен** на GitHub: был bare URL (auto-link)
→ стал `[url](url)` (markdown-ссылка). Оба кликабельны. На raw-text
просмотрах (не GitHub) markdown-синтаксис виден.
- **tsconfig.smoke.json и smoke-harness** созданы в `/tmp/opencode/` (вне
репо) — не коммитятся. Mock `@opencode-ai/plugin` не понадобился (реальный
плагин установлен в `.opencode/node_modules/`, gitignored).
- **`renderSteps` использует `1-based` нумерацию** через `${i + 1}.` — markdown
ordered list, нумерация авто-пересчитывается рендерером, но явные числа
соответствуют порядку массива.

View file

@ -36,7 +36,7 @@ opencode-config/
│ │ ├── python-development/SKILL.md # Python dev patterns │ │ ├── python-development/SKILL.md # Python dev patterns
│ │ ├── release/SKILL.md # Tag + GitHub Release │ │ ├── release/SKILL.md # Tag + GitHub Release
│ │ ├── repo-init/SKILL.md # New repository bootstrap │ │ ├── repo-init/SKILL.md # New repository bootstrap
│ │ ├── repo-readme/SKILL.md # Standardized README generation (create-readme tool: create vs validate, bilingual Why/What + Features table, GitHub metadata) — PR#112, PR#116, PR#118, PR#130 │ │ ├── repo-readme/SKILL.md # Standardized README generation (create-readme tool: create vs validate, bilingual Why/What + Features table, GitHub metadata, quick_start_steps clickable steps + conditional bash block) — PR#112, PR#116, PR#118, PR#130, PR#146
│ │ ├── tunnel/SKILL.md # Cloudflare tunnel toggle (tool `tunnel()`: 1-й вызов start, 2-й stop) — PR#63 (восстановлен, удалён в PR#42) │ │ ├── tunnel/SKILL.md # Cloudflare tunnel toggle (tool `tunnel()`: 1-й вызов start, 2-й stop) — PR#63 (восстановлен, удалён в PR#42)
│ │ ├── run-tests/SKILL.md # Test runner guide │ │ ├── run-tests/SKILL.md # Test runner guide
│ │ └── spec/SKILL.md # 9-phase spec generation │ │ └── spec/SKILL.md # 9-phase spec generation
@ -45,7 +45,7 @@ opencode-config/
│ │ ├── commit.ts # commit tool wrapper (1 arg message, validates format+staged) — PR#38 │ │ ├── commit.ts # commit tool wrapper (1 arg message, validates format+staged) — PR#38
│ │ ├── create-issue.ts # create-issue tool wrapper (3 args, validates format+labels; optional repo?: string) — PR#38, PR#65 │ │ ├── create-issue.ts # create-issue tool wrapper (3 args, validates format+labels; optional repo?: string) — PR#38, PR#65
│ │ ├── create-pr.ts # create-pr tool wrapper (3 args, validates format+Closes #N; optional repo?: string) — PR#38, PR#65 │ │ ├── create-pr.ts # create-pr tool wrapper (3 args, validates format+Closes #N; optional repo?: string) — PR#38, PR#65
│ │ ├── create-readme.ts # create-readme tool (TS plugin, modes: create/validate; standardized bilingual README with features table, include_clone/development_en/ru optional params, RU heading 'Русский' + anchor checks, 4 delimiter pairs for slaid098.dev; local fs + remote gh api) — PR#112, PR#116, PR#118, PR#130 │ │ ├── create-readme.ts # create-readme tool (TS plugin, modes: create/validate; standardized bilingual README with features table, include_clone/development_en/ru/quick_start_steps_en/ru optional params, clickable access_url [url](url), conditional bash block via hasBashBlock, RU heading 'Русский' + anchor checks, 4 delimiter pairs for slaid098.dev; local fs + remote gh api) — PR#112, PR#116, PR#118, PR#130, PR#146
│ │ ├── draw-image.ts # draw-image tool wrapper (opencode plugin, 5 args: template/title/subtitle?/slots?/out?; spawnSync node cli.ts render → sharp PNG) — PR#133 │ │ ├── draw-image.ts # draw-image tool wrapper (opencode plugin, 5 args: template/title/subtitle?/slots?/out?; spawnSync node cli.ts render → sharp PNG) — PR#133
│ │ ├── merge-pr.ts # merge-pr tool wrapper (orchestrator-safe gh pr merge; optional repo?: string) — PR#30, PR#65 │ │ ├── merge-pr.ts # merge-pr tool wrapper (orchestrator-safe gh pr merge; optional repo?: string) — PR#30, PR#65
│ │ ├── memory-access.ts # memory-access tool (bump frontmatter last_accessed/access_count, regex replace, atomic write tmp+rename) — PR#101 │ │ ├── memory-access.ts # memory-access tool (bump frontmatter last_accessed/access_count, regex replace, atomic write tmp+rename) — PR#101