opencode-config/docs/project-map
Sergey 66f0afa58b
refactor(draw-image): decouple tests from production cover.svg (#197)
* refactor(draw-image): add tests/fixtures/cover.svg and loadFixture helper

* feat(draw-image): add --template-dir flag to cli for test isolation

* refactor(draw-image): switch tests from templates/cover to fixtures

* chore(draw-image): simplify production cover.svg now decoupled from tests

* docs(handoff): add handoff + ADR for issue #196

* docs(handoff): set PR number

* docs(project-map): update after structural changes

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-08-01 01:22:15 +03:00
..
README.md refactor(draw-image): decouple tests from production cover.svg (#197) 2026-08-01 01:22:15 +03:00

Project Map

opencode-config — Docker-based AI coding assistant with persistent memory (opencode configuration). Runs in Docker via docker-compose.yml (dind + opencode services).

Root AGENTS.md — orchestrator directive (chat = plan only, all via subagents) + global rules (pipeline, code style, language RU) + ## Tool Usage Policy (таблица tools: commit/create-pr/create-issue/merge-pr/post-review/post-docs-review/pipeline-status/spec-status/memory-doctor/memory-save/memory-search/memory-list/memory-access/tunnel; raw bash заблокирован deny, при сбое tool — STOP, НЕ fallback; memory-setup REMOVED PR#103) + ## Bug Discovery Protocol (агенты создают issues через create-issue tool для найденных багов вне scope) + ## Linear Execution (строго линейный pipeline, параллельные research OK, параллельное исполнение ЗАПРЕЩЕНО) — PR#113. Auto-loaded for project + bind-mounted globally in container — PR#31, PR#63.

Structure

opencode-config/
├── .github/
│   ├── workflows/
│   │   ├── ci.yml                  # Lint, test, typecheck, complexity (bootstrap + output-based skip); apt-get install ripgrep + npm ci (.opencode) для keyword search — PR#124
│   │   ├── permissions-check.yml  # .opencode/scripts/check-permissions.py validator (step-level skip)
│   │   └── adr-check.yml           # ADR cross-reference validator (.opencode/scripts/check-adr-refs.py)
│   └── dependabot.yml              # pip + github-actions ecosystem updates
├── .opencode/                      # Project-local opencode config (auto-discovery, zero env var) — PR#23
│   ├── agents/
│   │   ├── docs-reviewer.md        # Docs validation subagent (project map + handoff + ADR, uses `commit`+`post-docs-review`+`create_issue` tools; `gh issue list*` allow) — PR#40, PR#46, PR#69, PR#113
│   │   ├── memory-syncer.md        # Distills gotchas from handoffs into opencode-memory (`create_issue` tool; `gh issue list*` allow) — PR#69, PR#113
│   │   └── reviewer.md             # Code review subagent (verdict via `post-review` tool: APPROVE|REQUEST_CHANGES|NEEDS_DISCUSSION) — PR#46, PR#69
│   ├── commands/
│   │   ├── configure-opencode.md   # /configure-opencode — edit opencode.json
│   │   ├── feature-spec.md          # /feature-spec — SDD-style Q&A for feature planning (loads feature-spec skill) — PR#172
│   │   ├── repo-readme.md           # /repo-readme — standardized README generation (frontmatter agent: build, loads repo-readme skill) — PR#158
│   │   ├── run-pipeline.md          # /run-pipeline — 7-phase PR pipeline
│   │   └── spec.md                  # /spec — 9-phase spec generation
│   ├── skills/
│   │   ├── add-skill/SKILL.md      # Create new opencode skill
│   │   ├── bug-discovery/SKILL.md   # Bug Discovery Protocol — create issue for out-of-scope bugs (create-issue tool, 7 SDD sections, duplicate check) — PR#120, PR#169
│   │   ├── branch/SKILL.md         # Branch naming conventions
│   │   ├── code-standards/SKILL.md # Universal code style rules
│   │   ├── configure-opencode/SKILL.md # Canonical rule: write to .opencode/
│   │   ├── feature-spec/SKILL.md    # SDD-style Q&A for feature planning (7 SDD sections, decision criteria, handoff to issue skill) — PR#172
│   │   ├── get-project-map/SKILL.md # Maintain docs/project-map/
│   │   ├── issue/SKILL.md          # GitHub issue creation (7 SDD sections template) — PR#169
│   │   ├── memory/SKILL.md         # opencode-memory usage guide
│   │   ├── run-pipeline/SKILL.md # 7-phase pipeline orchestration
│   │   ├── python-development/SKILL.md # Python dev patterns
│   │   ├── release/SKILL.md        # Tag + GitHub Release
│   │   ├── 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, quick_start_steps clickable steps + conditional bash block; tagline_en/tagline_ru required + delimiter-теги, breaking change note; workflow: create → draw-image (template "cover", slots icon/sub-icon/badge, brand-logos vs Lucide) → validate → fix-цикл) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#151, PR#158 (cover pipeline в workflow)
│   │   ├── tunnel/SKILL.md         # Cloudflare tunnel toggle (tool `tunnel()`: 1-й вызов start, 2-й stop) — PR#63 (восстановлен, удалён в PR#42)
│   │   ├── run-tests/SKILL.md      # Test runner guide
│   │   └── spec/SKILL.md           # 9-phase spec generation (issue body template: 7 SDD sections) — PR#169
│   ├── tools/
│   │   ├── _shared.ts              # Shared module for GitHub tools: parseRepo(repo?), runGh(args, repo?, opts?), formatResult(r, toolName) — PR#65
│   │   ├── commit.ts               # commit tool wrapper (1 arg message, validates format+staged) — PR#38
│   │   ├── create-issue.ts         # create-issue tool wrapper (3 args, validates 7 SDD headings+format+labels; optional repo?: string) — PR#38, PR#65, PR#169
│   │   ├── 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/quick_start_steps_en/ru optional params, clickable access_url [url](url), conditional bash block via hasBashBlock, RU heading 'Русский' + anchor checks, 6 delimiter pairs for slaid098.dev (summary-en/ru, features-en/ru, tagline-en/ru); tagline_en+tagline_ru required (BREAKING: tagline removed PR#151), content-валидация repo_name (lowercase kebab-case) + tagline_en (no Cyrillic) + tagline_ru (require Cyrillic), validateReadme H1 prefix check `# 🚀 `; generateReadme рендерит `![Cover](assets/cover.png)` после H1 перед tagline-разделителями; validateReadme substring-чек `assets/cover.png` (без file existence — для remote gh api режима); local fs path resolved via path.resolve(context.worktree, file_path) — overwrite existing README; remote gh api) — PR#112, PR#116, PR#118, PR#130, PR#146, PR#149 (quick_start optional + guard), PR#151 (bilingual tagline + repo_name validation, breaking), PR#158 (cover image reference в template + validation), PR#175 (local file_path resolved against context.worktree — overwrite existing README)
│   │   ├── 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
│   │   ├── memory-access.ts        # memory-access tool (bump frontmatter last_accessed/access_count, regex replace, atomic write tmp+rename) — PR#101
│   │   ├── memory-doctor.ts        # memory-doctor tool (read-only diagnostics: rg {lines, rgWorks} G9, allowSystemFallback:true G5, arch-mismatch detection G2; npm-missing hint G3; markdown ✅/❌ report) — PR#101, PR#124
│   │   ├── memory-list.ts          # memory-list tool (pure TS, categories count .md or files in category with frontmatter) — PR#101
│   │   ├── memory-save.ts          # memory-save tool (auto-setup mkdir+git init/clone+hook+7 categories, git commit, async reindex via spawn detached+unref) — PR#101
│   │   ├── memory-search.ts        # memory-search tool (ripgrep keyword + Python semantic via spawnSync + scoring port + cross-category fallback; stderr warning при rgBin===null) — PR#101, PR#124
│   │   ├── pipeline-status.ts      # pipeline-status tool wrapper
│   │   ├── post-docs-review.ts     # post-docs-review tool wrapper (3 args: pr_number, verdict enum, body; deterministic ## Docs Review Summary heading; optional repo?: string) — PR#46, PR#65
│   │   ├── post-review.ts          # post-review tool wrapper (3 args: pr_number, verdict enum, body; deterministic ## Code Review Summary heading; optional repo?: string) — PR#46, PR#65
│   │   ├── spec-status.ts          # spec-status tool wrapper
│   │   ├── telegram-send.ts        # telegram-send plugin-tool wrapper (spawnSync CLI .opencode/telegram/cli.ts; action: text|document|photo; MarkdownV2 default; error → ⚠️ telegram-send failed) — PR#174
│   │   └── tunnel.ts               # Cloudflare tunnel toggle tool (start/stop без args) — PR#34
│   ├── draw-image/                 # SVG template renderer for on-brand covers (sharp + lucide-static) — PR#133
│   │   ├── package.json            # deps: sharp, lucide-static; devDeps: vitest, typescript; postinstall sync-lucide
│   │   ├── tsconfig.json           # ES2022 Bundler, strict, noEmit
│   │   ├── vitest.config.ts        # node env, tests/**/*.test.ts
│   │   ├── render.mjs              # SVG string → PNG via sharp (fontFiles: Geist TTF bundle)
│   │   ├── cli.ts                  # CLI entry: render <template> --title --slots --out [--template-dir dir]; temp SVG в os.tmpdir()/draw-image-${pid}.svg + finally rmSync (race-safe для параллельных рендеров) — PR#178, PR#197 (--template-dir)
│   │   ├── brand.json              # default palette slaid098 (base/surface/fg/muted/accent/line)
│   │   ├── templates/cover.svg     # 1024×1024 cover template (упрощён: только icon-слот 520×520 + {{title}} y=830; sub-icon/badge/subtitle удалены — PR#197, тесты на fixture)
│   │   ├── fonts/                  # Geist Sans TTF (Regular + Bold) bundled for sharp fontFiles
│   │   ├── icons/lucide/           # 2007 Lucide SVG icons (synced from npm lucide-static via postinstall)
│   │   ├── brand-logos/            # brand SVG logos (opencode.svg — адаптированный логотип OpenCode 512×512 fill #ccff00, резолвится через draw-image slot icon=opencode) — PR#158
│   │   ├── scripts/sync-lucide.mjs # postinstall: copy icons from node_modules/lucide-static → icons/lucide/
│   │   ├── src/
│   │   │   ├── config.ts           # loadBrand + validateBrand (zod-style hex validation)
│   │   │   ├── slot-parser.ts      # parseSlots: HTML comments → SlotSpec objects
│   │   │   ├── fit.ts              # computeFit (contain/cover/stretch) + parseViewBox
│   │   │   ├── recolor.ts          # recolorSvg (stroke/fill → target) + stripSvgWrapper + extractRootAttrs
│   │   │   ├── resolve.ts          # resolveSlotContent (lucide/brand-logo/file lookup) + renderSlotSvg
│   │   │   └── render.ts           # buildSvg (assemble final SVG) + computeHash (sha256 idempotency) + loadTemplate(drawImageDir, name, templateDir?) — опц. templateDir (дефолт "templates", относит./абсолют.) — PR#197
│   │   └── tests/                  # vitest: 58 tests (unit + integration + e2e) — PR#178 (+3 cleanup), PR#197 (9 тестов на fixture, отвязка от продакшн cover.svg)
│   │       ├── fixtures/cover.svg  # каноничный тестовый шаблон (все типы слотов: icon/sub-icon/badge/subtitle) — стабильный контракт для тестов, не зависит от продакшн templates/cover.svg — PR#197
│   │       ├── helpers/fixtures.ts # loadFixture(name): читает из tests/fixtures/, экспортирует FIXTURES_DIR_ABS — PR#197
│   │       ├── config.test.ts
│   │       ├── slot-parser.test.ts
│   │       ├── fit.test.ts
│   │       ├── recolor.test.ts
│   │       ├── cli.test.ts
│   │       ├── render.integration.test.ts      # integration: PNG 1024x1024 + recursive out dir; renderToPng → per-process mkdtempSync tmp dir + finally cleanup (PR#177); на fixture (PR#197)
│   │       ├── idempotency.test.ts             # на fixture (PR#197)
│   │       ├── slot.test.ts                    # на fixture (PR#197)
│   │       ├── e2e.test.ts                     # e2e: --template-dir tests/fixtures (PR#197)
│   │       ├── e2e.no-icon.test.ts             # e2e: --template-dir tests/fixtures (PR#197)
│   │       ├── e2e.bad-input.test.ts
│   │       ├── render.optional.test.ts        # unit: empty badge → no rect, empty subtitle → no text (PR#144); на fixture (PR#197)
│   │       ├── render.optional.integration.test.ts # integration: PNG valid with/without subtitle+badge (PR#144); renderToPng → per-process mkdtempSync tmp dir + finally cleanup (PR#177); на fixture (PR#197)
│   │       ├── e2e.optional.test.ts           # e2e: title-only CLI → exit 0, clean SVG (PR#144); хелпер переписан — buildSvg напрямую вместо чтения leftover .tmp-render.svg (PR#178); --template-dir tests/fixtures (PR#197)
│   │       └── cleanup.test.ts                 # unit: temp SVG cleanup — no .tmp-render.svg leftover, PID-based path under os.tmpdir, no stray .svg (PR#178); на fixture (PR#197)
│   ├── telegram/                    # Telegram Bot API CLI-проект (по паттерну draw-image: plugin-tool + CLI, без runtime-deps) — PR#174
│   │   ├── package.json            # "type": "module", без runtime-deps; devDeps: @types/node, typescript, vitest; script test: vitest run
│   │   ├── tsconfig.json           # ES2022 + DOM (для типов fetch/Blob/FormData/File), moduleResolution: Bundler, allowImportingTsExtensions: true
│   │   ├── vitest.config.ts        # environment: node, include: tests/**/*.test.ts (e2e.manual.ts исключён — нет .test. в имени)
│   │   ├── cli.ts                  # argv-парсер, диспетчер по action (text/document/photo), parse_mode=MarkdownV2 по умолчанию; успех → JSON в stdout, провал → stderr + exit(1)
│   │   ├── src/
│   │   │   ├── config.ts           # loadConfig(overrides?): читает TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID из env, валидирует, override chatId приоритетнее env
│   │   │   ├── markdown.ts         # escapeMarkdownV2(text): экранирует спецсимволы MarkdownV2 (_*[]()~`>#+-=|{}.!), кириллицу не трогает, backslash НЕ в наборе
│   │   │   └── api.ts              # sendMessage/sendDocument/sendPhoto через встроенный fetch Node 22+; telegramFetch парсит result → {ok, message_id, chat_id}; ok:false → throw с description
│   │   └── tests/                  # vitest: 39 unit-тестов (markdown 25, config 5, api 9), все проходят
│   │       ├── markdown.test.ts    # 25 кейсов: пустая строка, без спецсимволов, кириллица, it.each по каждому спецсимволу, микс, backtick, `\` не в наборе
│   │       ├── config.test.ts      # 5 кейсов: валидный env, отсутствие token/chatId, override приоритет, override без env
│   │       ├── api.test.ts         # 9 кейсов: URL/method/body/headers sendMessage, parse_mode omit/plain/HTML, return value, Telegram API error, network error, sendDocument/sendPhoto FormData
│   │       └── e2e.manual.ts       # ручной скрипт (НЕ в npm test — нет .test. в имени + vitest include)
│   ├── scripts/
│   │   ├── check-adr-refs.py       # ADR cross-reference validator (adr-check.yml)
│   │   ├── check-permissions.py    # Permissions validator (permissions-check.yml)
│   │   ├── observability.py        # OTel spans for tools
│   │   ├── pipeline-status.py      # 7-phase oracle (gh PR + CI polling, NEXT_ACTIONS with subagent_type+template) — PR#42, PR#67 (check_project_map guard)
│   │   ├── scaffold-handoff.sh     # Scaffold handoff + ADR stubs
│   │   ├── spec-status.py          # 9-phase spec oracle
│   │   └── tunnel.sh               # Cloudflare tunnel toggle bash (named mode via CLOUDFLARE_TUNNEL_TOKEN) — PR#34
│   ├── opencode.json               # MCP servers, providers, permissions, agents (role-based tools; `create_issue` enabled for reviewer/docs-reviewer/memory-syncer) — PR#40, PR#69, PR#113 (plugin block REMOVED PR#103, deprecated `new_api` provider REMOVED PR#106)
│   ├── package.json                # npm deps for tools/*.ts (deps: @vscode/ripgrep; devDeps: @types/node, typescript) — PR#101
│   └── .gitignore                  # Ignores node_modules, etc.
├── docs/
│   ├── handoff/                   # PR handoffs (pr-<N>-<slug>.md)
│   ├── decisions/                 # ADRs (NNN-pr-<N>-<slug>.md)
│   └── project-map/               # This file — structure snapshot
├── assets/                        # Generated cover images for README (cover.png + cover.meta.json idempotency hash; default draw-image out path, referenced by create-readme `![Cover](assets/cover.png)`) — PR#158
├── src/                           # Python RAG CLI (memory) — PR#17
│   └── memory/
│       ├── __init__.py
│       ├── __main__.py            # Entry point for `python -m memory`
│       ├── cli.py                 # CLI commands (prog="memory")
│       ├── embedder.py            # Embedding via OPENAI_BASE_URL (env-only, OpenAI-compatible); rate-limit-aware: Retry-After header parsing, retry=5, backoff max=30 — PR#83
│       ├── index.py               # Indexing with chunking (MEMORY_CHUNK_SIZE/OVERLAP env); SHA256 incremental index (.rag/meta.json), atomic writes (.tmp+os.replace), versioning (model:chunk_size:overlap), fcntl.flock(LOCK_EX) — PR#83
│       └── search.py              # Search with dedup by source in top-K
├── tests/                         # pytest + TS/MJS test suite — PR#17
│   ├── _ts_loader.mjs             # TS test loader (load/exec_stub/exec_stub_json/exec_real modes; relative import inlining via inlineShared(); fs import support + multi-line type/var/non-null assertion stripping for create-readme.ts) — PR#38, PR#65, PR#175
│   ├── test_agent_frontmatter.py  # Agent frontmatter validators (no top-level doom_loop, permission.doom_loop present, steps:150) — PR#49, PR#69
│   ├── test_check_adr_refs.py     # adr-check.yml validator
│   ├── test_check_permissions.py  # permissions-check.yml validator
│   ├── test_cli.py                # src/memory/cli.py
│   ├── test_docker_compose.py    # docker-compose.yml port exposure (0.0.0.0:4096, no 127.0.0.1) + opencode limits/init/healthcheck assertions (8 raw-text: 8G/4cpu/2048pids, init:true, healthcheck block+timing, restart-not-duplicated, dind-unchanged) — PR#51, PR#132
│   ├── test_dockerfile.py        # Dockerfile npm install (opencode-ai + repomix; @mathew-cf/opencode-memory REMOVED PR#103, assertions inverted) — PR#57, PR#103
│   ├── test_commit_tool.py        # .opencode/tools/commit.ts (via _ts_loader.mjs exec_stub_json) — PR#38
│   ├── test_commit_tool.ts        # TS wrapper test (mjs loader) — PR#38
│   ├── test_create_issue_tool.py  # .opencode/tools/create-issue.ts (via _ts_loader.mjs exec_stub_json; 7 SDD headings, 4 new missing-heading tests, +repo) — PR#38, PR#65, PR#169
│   ├── test_create_issue_tool.ts  # TS wrapper test (mjs loader; +repo cases, 7 SDD headings) — PR#38, PR#65, PR#169
│   ├── test_create_pr_tool.py     # .opencode/tools/create-pr.ts (via _ts_loader.mjs exec_stub_json; +repo explicit/omitted/invalid) — PR#38, PR#65
│   ├── test_create_pr_tool.ts     # TS wrapper test (mjs loader; +repo cases) — PR#38, PR#65
│   ├── test_create_readme_tool.py # .opencode/tools/create-readme.ts (via _ts_loader.mjs exec_stub_json; 14 tests: local create overwrite/new/subdir, remote 2-call, local validate, validation errors; regression #148 file_path resolved via context.worktree) — PR#175
│   ├── test_draw_image_tool.py    # .opencode/tools/draw-image.ts (via _ts_loader.mjs exec_stub_json; 5 tests: load/valid/failure/cwd/all-args) — PR#133
│   ├── test_draw_image_tool.ts    # TS wrapper test (mjs loader; 3 tests: valid/failure/cwd) — PR#133
│   ├── test_embedder.py           # src/memory/embedder.py (mocks OPENAI_BASE_URL)
│   ├── test_embedder_live.py      # Live embed tests (skip without RUN_LIVE=1)
│   ├── test_index.py              # src/memory/index.py (chunking + env override + .rag skip)
│   ├── test_chunking.py           # _chunk_text edge cases (empty, unicode, size<overlap)
│   ├── test_memory_doctor.ts      # TS unit-тесты для memory-doctor (allGreen false при битом бинарнике G9, allGreen true regression guard, arch-mismatch detection G2, npm-missing hint G3) — PR#124
│   ├── test_memory_keyword_search.ts # TS unit-тесты для resolveRgBinary (npm-package, system-fallback, null-nothing, null-no-fallback) + memory-search stderr warning при rgBin===null — PR#124
│   ├── test_memory_tools_e2e.py   # E2E hybrid memory (4 scenarios: zero-config keyword-only [БЕЗ RUN_LIVE], full hybrid, OpenRouter fallback, deletion [individual skipif(not RUN_LIVE)]; subprocess-based: ripgrep + python3 -m src.memory) — PR#102, PR#124
│   ├── test_merge_pr_tool.py      # .opencode/tools/merge-pr.ts (via _ts_loader.mjs; 8 tests: base + repo explicit/omitted/invalid) — PR#65
│   ├── test_merge_pr_tool.ts      # TS wrapper test (mjs loader; 5 tests: base + repo) — PR#65
│   ├── test_observability.py      # .opencode/scripts/observability.py
│   ├── test_permissions.py        # Global deny rules + agent.<name>.tools role-based access (8 tests) — PR#40, PR#69
│   ├── test_pipeline_status.py    # .opencode/scripts/pipeline-status.py (REVIEW verdict branching)
│   ├── test_pipeline_status_adr.py
│   ├── test_pipeline_status_ci.py
│   ├── test_pipeline_status_next_actions.py # NEXT_ACTIONS subagent_type+template per phase (25 tests) — PR#42
│   ├── test_pipeline_status_project_map.py  # check_project_map guard (README.md mandatory, ADR-028) — PR#67
│   ├── test_pipeline_status_tool.py
│   ├── test_pipeline_status_tool.ts  # TS wrapper test (mjs loader)
│   ├── test_post_docs_review_tool.py # .opencode/tools/post-docs-review.ts (via _ts_loader.mjs exec_stub_json; +repo cases) — PR#46, PR#65
│   ├── test_post_docs_review_tool.ts # TS wrapper test (mjs loader; +repo cases) — PR#46, PR#65
│   ├── test_post_review_tool.py    # .opencode/tools/post-review.ts (via _ts_loader.mjs exec_stub_json; +repo cases) — PR#46, PR#65
│   ├── test_post_review_tool.ts    # TS wrapper test (mjs loader; +repo cases) — PR#46, PR#65
│   ├── test_search.py             # src/memory/search.py
│   ├── test_spec_status.py        # .opencode/scripts/spec-status.py
│   ├── test_spec_status_tool.py
│   ├── test_tunnel_tool.py        # .opencode/scripts/tunnel.sh (6 pytest: start/stop/stale PID/toggle, isolated tmp copy + fake cloudflared) — PR#65
│   └── test_tunnel_tool.ts        # .opencode/tools/tunnel.ts (4 TS: spawnSync wiring, success/error/cwd) — PR#65
├── pyproject.toml                 # Python project (uv, ruff, pytest config)
├── uv.lock                        # Locked deps for Python project
├── .pre-commit-config.yaml        # ruff + UV hooks
├── docker-compose.yml             # 2 services (dind + opencode), opencode_network, 4 bind mounts, port 4096 on 0.0.0.0; opencode: init: true (tini reaper), healthcheck (curl :4096, 200|401 healthy), limits 8G/4cpu/2048pids; command passed through entrypoint shim via "$@" — PR#24, PR#51, PR#132, PR#144
├── Dockerfile                     # node:22-trixie-slim (Debian 13 trixie, glibc 2.41) + uv + gh + chromium + docker.io + opencode-ai + repomix + cloudflared + ripgrep (apt fallback для keyword search); ENTRYPOINT=/usr/local/bin/docker-entrypoint.sh (self-healing: npm ci if sharp missing) (@mathew-cf/opencode-memory REMOVED PR#103) — PR#24, PR#34, PR#57, PR#71, PR#103, PR#124, PR#144
├── docker-entrypoint.sh           # Self-healing entrypoint shim: checks node_modules/sharp, runs npm ci if missing (continue-on-error), exec opencode "$@" — PR#144
├── .dockerignore                  # Excludes app_data/, .git, **/node_modules from docker build context — PR#144
│   # Memory deps install layers (PR#107): COPY .opencode/package.json → npm install --omit=dev (runtime: @vscode/ripgrep for memory-search.ts); COPY pyproject.toml uv.lock → uv sync --no-dev --frozen (runtime: httpx, numpy, tenacity for python -m src.memory)
├── .env.example                   # Placeholder-only env template (user copies to .env) — PR#24, PR#34 (TUNNEL_DOMAIN), PR#36 (OPENCODE_MEMORY_REMOTE/DIR), PR#106 (AI_PROVIDER_* removed, OPENCODE_MEMORY_REMOTE now optional), PR#118 (OPENCODE_SERVER_USERNAME), PR#174 (TELEGRAM_CHAT_ID)
├── app_data/
│   ├── opencode-memory/           # Persistent memory (separate git repo, gitignored) — PR#36
│   ├── workspaces/                # Agent working directory (.gitkeep)
│   └── ssh/                       # SSH keys, not in git (.gitkeep)
├── .editorconfig
├── .gitignore
├── .python-version
├── AGENTS.md                       # Orchestrator directive (CRITICAL — overrides built-in prompt: FORBIDDEN delegation map explore/general/reviewer/docs-reviewer/memory-syncer + ALLOWED read-only oracle tools; global rules, Bug Discovery Protocol, Linear Execution; bind-mounted globally) — PR#31, PR#113, PR#189
├── LICENSE
└── README.md

Pending (future PRs)

  • .opencode/scripts/pipeline-status.py fix 610452f — PR#7 (config/scripts/ migration)
  • .opencode/tools/ TS wrappers — PR#7 (config/tools/ migration)

Update Protocol

Updated by docs-reviewer subagent on each PR. Reflects tracked files only (git ls-files).

Guarded by pipeline-status.py:check_project_map — README.md existence is mandatory.