diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..8bf3376 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "claudeMdExcludes": [ + "**/reference/mathtrail-standalone/**/CLAUDE.md", + "**/reference/mathtrail-standalone/**/CLAUDE.local.md" + ] +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c0476a9..5ee0e3e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -52,6 +52,9 @@ // gopls and dlv come pinned from the image; the Go extension must not fetch its own. "go.toolsManagement.autoUpdate": false, "go.toolsManagement.checkForUpdates": "off", + // The sample under reference/ is a read-only submodule: Source Control does + // not offer it as a second repository to commit in. + "git.detectSubmodules": false, "files.eol": "\n", "files.insertFinalNewline": true, // SonarQube for IDE sends nothing about how it is used. diff --git a/.devcontainer/post-start.sh b/.devcontainer/post-start.sh index a1cf239..6cb59d4 100644 --- a/.devcontainer/post-start.sh +++ b/.devcontainer/post-start.sh @@ -7,6 +7,22 @@ if [[ -d .githooks ]]; then git config core.hooksPath .githooks fi +# A pull or a checkout that moves the sample's pin moves the sample with it. Otherwise it +# stays at the old commit, reads as the pinned one, and the next "commit all" takes the +# pin back. +git config submodule.recurse true + +# The sample this repository follows is a submodule, and a fresh clone leaves it empty. +# Checked out only when it is missing: once it is there, it may sit at a commit somebody +# has just raised it to and not committed yet, and a restart must not take that back. +# Without the network the container still starts; the next start tries again. A clone +# that hangs, or a remote that asks for credentials, is cut short rather than holding +# the start. +if [[ ! -e reference/mathtrail-standalone/.git ]]; then + GIT_TERMINAL_PROMPT=0 timeout 120 git submodule update --init reference/mathtrail-standalone \ + || echo "reference/mathtrail-standalone: not checked out; run git submodule update --init" >&2 +fi + # The docker inside this container hands its networks an MTU of 1500. When this container's # own interface is smaller — a tunnel on the host is the usual reason — everything that # crosses both stalls: a packet that fits here is too large for the tunnel, and nothing diff --git a/.dockerignore b/.dockerignore index 0f73cb0..98b52ea 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,6 +14,8 @@ **/.env.* .devcontainer reference +.gitmodules +.ignore docs draft web diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8f984bc..38fc636 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -5,7 +5,7 @@ # not become a wait. set -euo pipefail -# The repository's own Go files: the copy of the sample under reference/ is +# The repository's own Go files: the sample under reference/ is # read-only and not this repository's to answer for, and installed packages and # providers are not its own either. The same list as GO_FILES in the justfile, # which this hook does not read, since a commit may be made where there is no diff --git a/.gitignore b/.gitignore index c485b44..dd7f7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -60,8 +60,10 @@ firebase-debug.*.log* # permission of a live run, which is deleted once the run is over. .claude/settings.local.json -# Reference copies of other repositories: read-only material, never committed. -/reference/ +# Other repositories' material for reading. The sample is a submodule and is tracked as +# one; anything else put here stays out of this public repository. +/reference/* +!/reference/mathtrail-standalone # The one deployment this repository delivers to is described in the repository, # not in somebody's console: these two are committed on purpose. diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..23d540f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "reference/mathtrail-standalone"] + path = reference/mathtrail-standalone + url = https://github.com/MathTrail/mathtrail-standalone.git + branch = main diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..b10dfb4 --- /dev/null +++ b/.ignore @@ -0,0 +1,5 @@ +# Search tools that read this file — ripgrep, and the editors and agents built on it — +# skip the sample under reference/: git tracks it as a submodule, but its files are +# another repository's, and a search here is about this one. An explicit path still +# reads them. +/reference/ diff --git a/CLAUDE.md b/CLAUDE.md index 895a6eb..ec00d3e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,7 +67,7 @@ Project context lives in these files, not in chat history. - Before starting, read the RUN.md task, the product, spec and privacy sections it cites, and `docs/decisions.md`; a DevX task also reads the files of the sample it names under `reference/mathtrail-standalone/`. If the task contradicts them or something is missing, stop and ask. Do not silently fill gaps; record them as open questions under the next free number, which the section's opening names — О- in product 15 (the open ones are in 15.2), Т- in spec 20, П- in privacy 12 — or in the «Замечания» section of the document being written. - Before calling any change done — code, config, docs or content — run `/code-review high` on it. Skip it only when the author asks, for that change alone, and say in the report that it did not run. - Run it once the change is written and its checks — `just ci-lint`, `just ci-test` and `just ci-mocks-check` (from T04), `just ci-licenses` and `just ci-vuln` (from T05), `just ci-secrets`, `just ci-image-smoke` and `just ci-image-scan` (from T06), `just release-version` and `just release-artifacts` (from T08), `just tf-check` (from T10), `just ci-workflows` (from T11), the others RUN.md rule 7 names for what the change touches, whatever else the task names — are green. - - The checks and the review run in a clean copy of the tree that holds this change and nothing else, so that another session's uncommitted work gets into neither. A tree that holds nothing but the change is that copy already. Otherwise the copy is a `git worktree` of `HEAD`, outside the repository, given this change's own edits, new files included, and what its checks need that git does not keep — a link to `reference/`, `web/node_modules` installed from the lockfile — and the checks and the review run from inside it. Fixes are made in the tree and carried into the copy, and `git worktree remove --force` takes the copy away after the review: it holds nothing the tree does not. + - The checks and the review run in a clean copy of the tree that holds this change and nothing else, so that another session's uncommitted work gets into neither. A tree that holds nothing but the change is that copy already. Otherwise the copy is a `git worktree` of `HEAD`, outside the repository, given this change's own edits, new files included, and what its checks need that a worktree does not bring — `web/node_modules` installed from the lockfile; the checks do not read the sample, as CI, which checks out no submodule, shows, so a review that needs it reads it in the tree — and the checks and the review run from inside it. Fixes are made in the tree and carried into the copy, and `git worktree remove --force` takes the copy away after the review: it holds nothing the tree does not. - It reads the working tree plus the branch's commits — those not yet pushed, or all since `main` without an upstream — but not untracked files. Mark new files with `git add -N` for the review and unmark them with `git reset -- ` afterwards: the mark makes `git stash` fail. Pass no path — a path is reviewed instead of the diff, not beside it. - Verify each finding against the repository before acting on it; do not use `--fix`. Fix a real defect your uncommitted work introduced, wherever it shows up, with a test that fails without the fix when the defect is in code. A defect your work did not introduce — in an earlier commit, in code your work does not touch, or in another session's uncommitted work in the same tree — goes to the report as open. - Drop a wrong finding with a one-line reason, and one that re-proposes a rejected alternative with no new evidence by naming the decision. A finding that questions product, spec, privacy or a decision for a reason that holds goes to the author as a question. @@ -87,7 +87,7 @@ Project context lives in these files, not in chat history. ## Hard rules -- **Devcontainer only.** All work happens inside the devcontainer (`.devcontainer/`, from T03): building, tests, linters, mocks, running the server and the SPA, Terraform, gcloud and live runs. Nothing but Docker and VS Code is installed or run on the host. If you are not inside the container (`MATHTRAIL_DEVCONTAINER` is not `1`), stop and ask the author to "Reopen in Container". "The change is only documents" is no reason to go on: stop before the first edit. The one exception is a cloud session of Claude Code that the author started for the task: it runs there without the container, runs the checks it can reach, and the report names every check that did not run. The exceptions run on the host: T00–T03, the only host-side tasks (T03 creates the container), and the author's refresh of the reference copy (see "Reference copies"). +- **Devcontainer only.** All work happens inside the devcontainer (`.devcontainer/`, from T03): building, tests, linters, mocks, running the server and the SPA, Terraform, gcloud and live runs. Nothing but Docker and VS Code is installed or run on the host. If you are not inside the container (`MATHTRAIL_DEVCONTAINER` is not `1`), stop and ask the author to "Reopen in Container". "The change is only documents" is no reason to go on: stop before the first edit. The one exception is a cloud session of Claude Code that the author started for the task: it runs there without the container, runs the checks it can reach, and the report names every check that did not run. The exceptions run on the host: T00–T03, the only host-side tasks (T03 creates the container). - **Exact versions only.** No `latest`, no floating tags or ranges (spec 1). This covers: - Docker images and devcontainer features: exact tag plus digest. An image whose tags name no version, such as distroless, is pinned by digest alone; - Go and its modules: exact versions in `go.mod`; @@ -282,24 +282,14 @@ Implementations of the same interface must be interchangeable, and that is worth ## Reference copies -`reference/mathtrail-standalone/` is a local, gitignored snapshot of the working tree of `mathtrail-standalone`, the sibling repository these rules and the DevX come from. The devcontainer cannot see a sibling directory of the host, so a task reads the snapshot instead. - -- Read-only: never edit it, and never commit anything from it. -- It is what R11 ports from: the skeleton (`app`, `config`, `logger`, `telemetry`, `apierror`, `version`), the devcontainer, the justfile, CI/CD, Terraform and the quality settings — each changed only where canvas-api needs it. "As in standalone" in RUN.md means: take the named file from the snapshot and change only what the task names. -- Its rules file is renamed to `CLAUDE.standalone.md`. Under its own name, Claude Code would load it whenever a file of the snapshot is read, and its rules do not apply here: it is the sample this file follows, section by section. -- Left out of it: the sample's `.git`, its installed packages, binaries, coverage profile and Terraform providers (`node_modules`, `bin/`, `dist/`, `coverage.out`, `.terraform/`), its own reference material (`prototype/`, `reference/`, `research/`, `draft/`) — so mentor-api, which the sample keeps under `reference/`, is not here — and what is local to the sample's machine: a `.env` with its secrets and a `CLAUDE.local.md`, which Claude Code would load just like a `CLAUDE.md`. -- Search tools that respect `.gitignore` (ripgrep) skip it. Use explicit paths (`Read`, `ls`, `grep -r reference/mathtrail-standalone/...`). -- The author refreshes it on the host, from the root of this repository: - - ```sh - mkdir -p reference && rsync -a --delete \ - --exclude=.git --exclude=node_modules \ - --exclude=/prototype/ --exclude=/reference/ --exclude=/research/ --exclude=/draft/ \ - --exclude=bin/ --exclude=dist/ --exclude=coverage.out --exclude=.terraform/ \ - --exclude=.env --exclude=CLAUDE.local.md \ - ../mathtrail-standalone/ reference/mathtrail-standalone/ \ - && mv reference/mathtrail-standalone/CLAUDE.md reference/mathtrail-standalone/CLAUDE.standalone.md - ``` +`reference/mathtrail-standalone/` is a git submodule of [mathtrail-standalone](https://github.com/MathTrail/mathtrail-standalone), the sibling repository these rules and the DevX come from, pinned to one commit of it. `post-start` checks it out when a fresh clone left it empty, and sets `submodule.recurse`, so a pull or a checkout that moves the pin moves the sample too; elsewhere, `git submodule update --init` does both. + +- **Its rules do not apply here.** Every `CLAUDE.md` under `reference/` — the sample's own, and any a later commit of it brings — holds another repository's rules, and binds nothing in this repository. `claudeMdExcludes` in `.claude/settings.json` keeps Claude Code from loading them; should one reach you anyway, ignore it. This file is the only one that does, and the sample is what it follows, section by section. +- Read-only: never edit it, and never commit inside it. +- It is what R11 ports from: the skeleton (`app`, `config`, `logger`, `telemetry`, `apierror`, `version`), the devcontainer, the justfile, CI/CD, Terraform and the quality settings — each changed only where canvas-api needs it. "As in standalone" in RUN.md means: take the named file from the sample and change only what the task names. +- It holds what the sample's git tracks, and so none of what that repository keeps out of git: its installed packages, binaries, a `.env` with its secrets, a `CLAUDE.local.md`. mentor-api, which the sample keeps under a `reference/` of its own outside git, is not here. +- `.ignore` keeps search tools built on ripgrep out of it, and `sonar.exclusions` keeps the analysis out, so a search or a finding here is about this repository. Use explicit paths (`Read`, `ls`, `grep -r reference/mathtrail-standalone/...`). +- To raise the pin, run `git submodule update --remote reference/mathtrail-standalone` in the container; it moves the sample to the head of its `main`, the branch `.gitmodules` names. The author commits the new pin like any other change. ## Environment @@ -324,8 +314,8 @@ Inside the container: - The everyday ones are `just fmt`, `just test`, `just lint`, `just build` and `just run`, and for the SPA `just web-dev`, which serves it on port 5173, `just web-fmt` and `just web-test`, and for the cloud `just tf-fmt` and `just usage`, which sets what the deployed service used of Cloud Run against a month's free tier. `just web-build` builds the SPA into `web/dist`; `just build` and the image leave it out (R10). - The ones that must be green before a task is done are **`just ci-lint`** (formatting and lint), **`just ci-test`** (the race detector and coverage), **`just ci-mocks-check`**, **`just ci-licenses`**, **`just ci-vuln`**, **`just ci-secrets`** (gitleaks over the whole history and over what is not committed yet), **`just ci-image-smoke`** (the runtime image starts, and its `/health` names this build), **`just ci-image-scan`** (Trivy over that image, HIGH and CRITICAL with a fix out), **`just release-version`** (the version the next release takes, from `RELEASE_LINE` in `release.yml` and the tags), **`just release-artifacts`** (what a release holds: the server for amd64 and arm64, the SPA's archive and `SHA256SUMS`, in `dist/`), **`just tf-check`** (the Terraform files formatted, the configuration valid against the providers its lock file names, and its tests on mock providers, with no credential) and **`just ci-workflows`** (actionlint, with shellcheck over the scripts of the workflows' steps but not of the composite actions': what GitHub would refuse or misread in the workflows, the call from `release.yml` to `deploy.yml` among it), and they are what CI runs (`.github/workflows/ci.yml`). `ci-licenses` holds every Go module and npm package to the license list and `THIRD_PARTY_LICENSES` to the dependency graph — `just licenses` rewrites it. `ci-vuln` runs govulncheck and `npm audit` (R48). A false alarm of the secret scan is waived in `.gitleaks.toml`, by a rule, a path and the shape of a value together, a vulnerability only in `.trivyignore`, and a finding of actionlint only in `.github/actionlint.yaml`; each with its reason. - A change to `web/` must also pass **`just ci-web`**: Biome, the types, the tests with their coverage, and a build. - - gofmt never walks into `reference/`: the copy is the sample's, read-only. + - gofmt never walks into `reference/`: the sample there is another repository's, read-only. - **Git hooks.** `.githooks/pre-commit` is enabled by `post-start` through `core.hooksPath`. It runs formatting, a build and the tests — enough to catch what is embarrassing, while the linter, the race detector, `govulncheck` and `gitleaks` wait for CI. Where no Go toolchain is reachable — a Git client outside the container, for instance — it says so and skips those checks instead of blocking the commit: CI runs them again and is what gates a merge. - **The runtime image.** `just docker-build` builds it and `just docker-run` starts it on port 8080. Both images in the root `Dockerfile` are pinned: a Go builder by tag and digest, at the version `go.mod` names, and a distroless static runtime by digest, since distroless names no versions in its tags; the runtime has no shell and runs as a non-root user. The SPA is no part of it: Firebase Hosting serves the build (R10). -- **Build context.** Both images are built with the repository root as context: the devcontainer copies nothing from it, the runtime image the Go module. `.dockerignore` keeps out of it the history, every `.env` and its variants such as `.env.local`, `.github/`, `.vscode/`, `.sonarlint/`, `.claude/`, `.githooks/`, `.devcontainer/`, `reference/`, `docs/`, `draft/`, `web/`, `infra/`, every `testdata/`, `tools/`, `bin/`, `dist/`, test and profile output, `.scannerwork/`, a local `go.work`, `node_modules`, the Markdown files at the root, `firebase.json` and what the Firebase CLI leaves behind, and the credentials file Google's sign-in action writes into a workflow's checkout. +- **Build context.** Both images are built with the repository root as context: the devcontainer copies nothing from it, the runtime image the Go module. `.dockerignore` keeps out of it the history, every `.env` and its variants such as `.env.local`, `.github/`, `.vscode/`, `.sonarlint/`, `.claude/`, `.githooks/`, `.devcontainer/`, `reference/` with `.gitmodules` and `.ignore`, `docs/`, `draft/`, `web/`, `infra/`, every `testdata/`, `tools/`, `bin/`, `dist/`, test and profile output, `.scannerwork/`, a local `go.work`, `node_modules`, the Markdown files at the root, `firebase.json` and what the Firebase CLI leaves behind, and the credentials file Google's sign-in action writes into a workflow's checkout. - **The release image.** A delivery does not build the server again: the `release` stage of the same `Dockerfile` puts the server a release signed into the same runtime, from a build context of its own (`just ci-image-push`, R58). diff --git a/RUN.md b/RUN.md index 2ef79e4..50591be 100644 --- a/RUN.md +++ b/RUN.md @@ -4,7 +4,7 @@ > > Этот файл задаёт порядок работы: маленькие задачи, которые запускаются по одной. После каждой задачи я проверяю результат и только потом запускаю следующую. Задачи собраны в эпики. Подробно расписан только текущий эпик, остальные расписываются, когда до них доходит очередь (R44). > -> Практики, инструменты и настройки качества — те же, что в соседнем репозитории mathtrail-standalone. Его локальная копия — `reference/mathtrail-standalone/` (T01). «Как в standalone» значит: взять названный файл из копии и поменять в нём только то, что названо в задаче. +> Практики, инструменты и настройки качества — те же, что в соседнем репозитории mathtrail-standalone. Он подключён подмодулем git `reference/mathtrail-standalone/`, закреплённым на коммите образца (T01, R70). «Как в standalone» значит: взять названный файл из образца и поменять в нём только то, что названо в задаче. ## Как пользоваться diff --git a/docs/decisions.md b/docs/decisions.md index b409e1d..d43271c 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -8,7 +8,7 @@ > > Записи R37–R43 внесены 2026-10-11 при повороте к LLM-first. Записи, которые они заменили, остались на месте с пометкой «Заменено»: их текст не правится, а их «Отвергнуто» больше не действует. Записи, которые поворот только задел, поправлены на месте, и в конце каждой стоит «Поправка 2026-10-11». Запись R44 о плане эпиками внесена тогда же, по решению автора. Запись R45 о выкладке после релиза — тоже, при сверке с mathtrail-standalone. Запись R46 о намеренных отличиях devcontainer от образца внесена 2026-10-12 при сверке с ним (T60). Записи R47 о тулчейне SPA и R48 о лицензиях npm внесены тогда же, в T05. Запись R49 о CI и его отличиях от образца внесена 2026-10-13 в T06. Записи R50 о том, что держит мерж, и R51 о находках, которые остаются открытыми, внесены тогда же, в T07. > -> Записи R60–R67 внесены 2026-10-16 (T61) по решениям автора 2026-10-15, когда E2 стал минимальным срезом с замером Gemini, а закрытая бета — эпиком E15. Запись R40 они заменили, и она осталась на месте с пометкой «Заменено». Записи R22–R24, R35, R38, R39, R42, R44, R56 и R59 поправлены на месте, и в конце каждой стоит «Поправка 2026-10-15». Запись R68 внесена 2026-10-16, в T16, по решению автора того же дня. Запись R69 о втором агенте, Codex, внесена 2026-10-16 по решению автора, вне задач плана; R46 поправлена на месте. +> Записи R60–R67 внесены 2026-10-16 (T61) по решениям автора 2026-10-15, когда E2 стал минимальным срезом с замером Gemini, а закрытая бета — эпиком E15. Запись R40 они заменили, и она осталась на месте с пометкой «Заменено». Записи R22–R24, R35, R38, R39, R42, R44, R56 и R59 поправлены на месте, и в конце каждой стоит «Поправка 2026-10-15». Запись R68 внесена 2026-10-16, в T16, по решению автора того же дня. Запись R69 о втором агенте, Codex, внесена 2026-10-16 по решению автора, вне задач плана; R46 поправлена на месте. Запись R70 об образце как подмодуле git внесена тогда же и так же, и R46 поправлена ещё раз. ## Рамки и продукт @@ -204,6 +204,7 @@ Centrifugo сохраняет роль из R23. Событие `verdict` ухо - Python и uv образца не ставятся, расширений Python нет: в canvas-api Python нет (T03). Поэтому gcloud на arm, как и прежде, идёт из образа Google (R28 standalone). - Firebase CLI, которого в образце нет, ставится автономным бинарником и, как gcloud, только на x86_64: другой сборки нет (R58). - Расширение Codex, том `canvas-api-codex` и папка `~/.codex` под него, которых в образце нет (R69). +- `post-start.sh` достаёт подмодуль образца после свежего клона, а `git.detectSubmodules: false` убирает его из Source Control (R70). Остальные отличия — имена проекта, сети и томов, порт 5173 для Vite (T03) и комментарии, которые описывают canvas-api. Временных отличий не осталось. Их было три, и все закрыты: - `.dockerignore` был короче образца — его список дополнил T04; @@ -212,7 +213,7 @@ Centrifugo сохраняет роль из R23. Событие `verdict` ухо Версии инструментов, features, расширения VS Code, кроме пакета Python и Codex, и digest базового образа совпадают с образцом: сверено 2026-10-11 и ещё раз в T60. -— *Почему:* сверка с образцом берёт его файлы и меняет в них только то, что названо в задаче. Без этой записи следующая сверка вернула бы то, что здесь исправлено или решено иначе. — *Отвергнуто:* вернуть в этих местах файлы образца как есть — их недостатки названы выше. — `.devcontainer/`, `.gitattributes`, `.dockerignore`, `justfile`; R11; standalone: R28, R36. — *Поправка 2026-10-13:* временные отличия закрыты, список выше поправлен на месте (T07). — *Поправка 2026-10-14:* добавлен Firebase CLI (T11). — *Поправка 2026-10-16:* добавлено расширение Codex (R69). +— *Почему:* сверка с образцом берёт его файлы и меняет в них только то, что названо в задаче. Без этой записи следующая сверка вернула бы то, что здесь исправлено или решено иначе. — *Отвергнуто:* вернуть в этих местах файлы образца как есть — их недостатки названы выше. — `.devcontainer/`, `.gitattributes`, `.dockerignore`, `justfile`; R11; standalone: R28, R36. — *Поправка 2026-10-13:* временные отличия закрыты, список выше поправлен на месте (T07). — *Поправка 2026-10-14:* добавлен Firebase CLI (T11). — *Поправка 2026-10-16:* добавлено расширение Codex (R69). — *Поправка 2026-10-16:* `post-start.sh` достаёт подмодуль образца (R70). **R47. SPA собирает Vite 8 на React 19 с `@vitejs/plugin-react`; TypeScript только проверяет типы, Biome форматирует и линтует, Vitest тестирует в happy-dom.** Тулчейн — тот же, что у виджета standalone (R84 standalone), с тремя отличиями: два — ниже, третье — в абзаце о типах. - **JSX собирает `@vitejs/plugin-react`.** В dev-сервере правка компонента обновляет его на месте, без перезагрузки страницы (Fast Refresh). Образец собирает Preact по настройкам tsconfig, без плагина. @@ -678,6 +679,26 @@ Centrifugo сохраняет роль из R23. Событие `verdict` ухо — `.devcontainer/devcontainer.json`, `.devcontainer/Dockerfile`, `AGENTS.md`, `CLAUDE.md`; privacy 12 (П-11); R46. Расширение и CLI проверены по пакету из Marketplace 2026-10-16: `openai.chatgpt` 26.1007.21434 — стабильная версия от 2026-10-09, `engines.vscode ^1.96.2`, `CODEX_HOME` по умолчанию `~/.codex`. +**R70. Образец mathtrail-standalone — подмодуль git, а не снимок; правила его `CLAUDE.md` здесь не действуют.** Решение автора 2026-10-16. +- `reference/mathtrail-standalone/` — подмодуль git репозитория `MathTrail/mathtrail-standalone`, закреплённый на коммите. Первый закреп — `c6cbeb2` (v0.6.8, 2026-10-15), голова его `main` на 2026-10-16. Поднимает закреп `git submodule update --remote` в контейнере, а коммитит новый закреп автор, как любую правку. +- `CLAUDE.md` образца остаётся под своим именем: переименовать файл внутри подмодуля нельзя, а Claude Code загружает `CLAUDE.md` рядом с файлом, который читает. Его не пускает `claudeMdExcludes` в `.claude/settings.json`: шаблоны `**/reference/mathtrail-standalone/**/CLAUDE.md` и `**/reference/mathtrail-standalone/**/CLAUDE.local.md`. Они привязаны к пути образца, а не к любой папке `reference`: иначе клон, лежащий внутри такой папки, потерял бы наш собственный CLAUDE.md. Настройка и её разбор проверены по Claude Code 2.1.280, закреплённому в образе, 2026-10-16: шаблоны picomatch сверяются с абсолютным путём и действуют для памяти проекта. Сами шаблоны проверены на picomatch: путь образца исключают на любой глубине, а наш `CLAUDE.md` не исключают — даже у клона в `/home/u/reference/canvas-api`. Кроме того, наш CLAUDE.md говорит прямо: любой `CLAUDE.md` под `reference/` — правила другого репозитория, и здесь они не действуют. `AGENTS.md` у образца нет, а наш `AGENTS.md` отправляет Codex к нашему CLAUDE.md. +- `.gitignore` держит вне git всё в `reference/`, кроме подмодуля: другой материал для чтения туда по-прежнему кладут, и в публичный репозиторий он не попадёт. +- VS Code не показывает подмодуль вторым репозиторием в Source Control (`git.detectSubmodules: false`), чтобы в образце не закоммитить по ошибке. +- Чистой копии для ревью подмодуль не нужен: проверки его не читают, как показывает CI, а ревью читает образец в основном дереве. +- В подмодуле — то, что отслеживает git образца. Исключения rsync совпадали с этим и так, кроме `research/`: теперь он здесь. +- `.ignore` в корне держит ripgrep — а с ним поиск Claude и VS Code — вне образца, как держал `.gitignore`. Явный путь читает его по-прежнему. +- `post-start` достаёт подмодуль, только когда его нет, — после свежего клона. Уже извлечённый не трогает: иначе перезапуск откатил бы закреп, поднятый, но ещё не закоммиченный. Без сети контейнер всё равно стартует: клон ограничен 120 секундами и не ждёт ввода учётных данных. Ещё `post-start` ставит `submodule.recurse`: `pull` или `checkout`, сдвинувший закреп, сдвигает и образец. Иначе тот остался бы на старом коммите, и следующий «commit all» вернул бы старый закреп. `.gitmodules` называет ветку `main`, за которой идёт `--remote`. `sonar.exclusions` не пускает образец в анализ: SonarQube for IDE в connected mode иначе мерил бы чужой код как наш. +- CI подмодуль не нужен и не скачивается: `actions/checkout` не берёт подмодули без `submodules:`. Go-инструменты останавливаются на его `go.mod`, gofmt и pre-commit обходят `reference/`, в контекст образов его не пускает `.dockerignore`, а `ci-secrets` видит его одной ссылкой на коммит. + +— *Почему:* снимок был рабочим деревом образца без даты и коммита, обновлялся rsync на хосте, вне контейнера, а на свежем клоне его не было вовсе. Подмодуль называет точный коммит — то же правило точных версий, — обновляется git в контейнере и приходит с клоном. + +— *Отвергнуто:* +- оставить снимок rsync — по причинам выше; +- обновления подмодуля Dependabot (`gitsubmodule`) — закреп поднимают, когда задача сверяется с образцом, а не по каждому его мержу; +- удалять или переименовывать `CLAUDE.md` образца после извлечения — это правка внутри подмодуля, и она висела бы в `git status` как изменение образца. + +— `.gitmodules`, `.gitignore`, `.ignore`, `.dockerignore`, `sonar-project.properties`, `.claude/settings.json`, `.devcontainer/post-start.sh`, `.devcontainer/devcontainer.json`, `CLAUDE.md` (Reference copies, Devcontainer only, чистая копия для ревью, Build context), RUN.md; R11, R46. + ## Идентификация и доступ **R14. Для Firebase используем Admin SDK (`firebase.google.com/go/v4`).** Через него проверяем ID token и App Check и удаляем пользователей. `golang-jwt/jwt/v5` нужен только для подписи токенов Centrifugo. `keyfunc` не используется. diff --git a/docs/stack.html b/docs/stack.html index 3eed827..d41c41f 100644 --- a/docs/stack.html +++ b/docs/stack.html @@ -411,7 +411,7 @@

После MVP: платформа MathTrai diff --git a/justfile b/justfile index 6ae89ca..81ffb3f 100644 --- a/justfile +++ b/justfile @@ -76,8 +76,8 @@ DATE := `date -u +%Y-%m-%dT%H:%M:%SZ` SYMBOLS := MODULE + "/internal/version" LDFLAGS := "-X " + SYMBOLS + ".Version=" + VERSION + " -X " + SYMBOLS + ".Commit=" + COMMIT + " -X " + SYMBOLS + ".Date=" + DATE -# The repository's own Go files, as a NUL-separated list. The copy of the -# sample under reference/ is read-only and not this repository's to format, so +# The repository's own Go files, as a NUL-separated list. The sample under +# reference/ is read-only and not this repository's to format, so # gofmt never walks into it, nor into installed packages and providers. The # pre-commit hook keeps the same list in go_files. GO_FILES := "find . -path ./reference -prune -o -path ./.git -prune -o -name node_modules -prune -o -name .terraform -prune -o -type f -name '*.go' -print0" diff --git a/reference/mathtrail-standalone b/reference/mathtrail-standalone new file mode 160000 index 0000000..c6cbeb2 --- /dev/null +++ b/reference/mathtrail-standalone @@ -0,0 +1 @@ +Subproject commit c6cbeb2fcf4effb8f19b9e224ff710dc4d05e02a diff --git a/sonar-project.properties b/sonar-project.properties index 697d2df..da2ba7f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -12,13 +12,14 @@ sonar.tests=. # measured. sonar.test.inclusions=**/*_test.go,internal/version/testdata/**,web/**/*.test.ts,web/**/*.test.tsx -# The mockup and the design's prototypes, bundles a design tool exported, each +# The sample this repository follows, a submodule of another repository; the +# mockup and the design's prototypes, bundles a design tool exported, each # with its own copy of React; the summary of the stack, a document whose script # only draws its tables; the mocks mockery generates, and the code buf generates # from proto/; and the tools that have checks of their own. None of it is code # of the product that anybody here writes, and all of it would be measured as # if it were. -sonar.exclusions=draft/**,docs/design/**,docs/stack.html,**/mocks/**,gen/**,web/src/gen/**,tools/** +sonar.exclusions=reference/**,draft/**,docs/design/**,docs/stack.html,**/mocks/**,gen/**,web/src/gen/**,tools/** sonar.test.exclusions=tools/** # The SPA's entry point only finds the page's root and starts what the tests # cover, as the tests' own configuration says. The build's configurations are