Compare commits

...

27 Commits

Author SHA1 Message Date
4964849397 fix(agents): вернуть opt-in 'session sync: write to mappa' в AGENTS.md\n\nСтроку вырезал коммит 79adaf9 «канон-блок — краткая суть гейтов вместо обрубков» (task:3017)\nпри переработке проекции канон-блока. Без неё pi-расширение (mappa.ts isOptedIn) не поднимает\nlive-ingest → сессии проекта не пишутся в mappa.\n\nВосстановлено на исходную позицию (контекст из вырезавшего коммита). 2026-09-19 16:44:14 +03:00
c5eee95460 docs(AGENTS.md): ре-прогон канон-блока на mappa-setup@0.2.8 (task:3030)
Тело блока — 0 изменений (рендер == живая курированная форма, issue:110);
нормализован стык (контракт mergeCanonBlock, task:3023).
2026-09-19 09:52:30 +03:00
79adaf928d docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 22:12:27 +03:00
b51657bfe4 docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:58:28 +03:00
48fa29e9dc docs(AGENTS.md): канон-блок — краткая суть гейтов вместо обрубков (task:3017) 2026-09-18 21:25:56 +03:00
65a2518a5e docs(canon): канон-блок AGENTS.md из живого shared-среза (task:2882)
Блок перегенерирован писателем кэша, поставленным в mappa-setup 0.2.6
(--gen-canon-block: живой shared → кэш .mappa/share/ → блок из ТОГО ЖЕ среза).

- строка Entity → runbook снова несёт полный каталог типов из runbooks/index
  (issue, intent, requirements, plan, comment, tag, attachment, release,
  brainstorm, agent, repo, project, skill, entity, sched) — до этого в блоке
  оставалась усечённая карта;
- Canon version — версия КАНОНА (canon/*), не максимум по методологии/ранбукам.
2026-09-18 00:28:08 +03:00
84e28c5d1c docs: канон-блок AGENTS.md — Г2 v4 (адресация about/thread) + сняты junk mail-строки (task:2993) 2026-09-17 23:41:40 +03:00
607a475e28 feat(ops-browser): скил своего скрытого браузера (профиль+CDP+хендофф) в каталог; маппинг в Hermes-дерево; строки в README 2026-09-12 12:30:05 +03:00
252e22ec80 fix(browser-operator): PID аренды только настоящий (/proc/$$/winpid), driver.lock руками не трогать 2026-09-11 23:31:37 +03:00
ac0c41feb8 chore: .gitignore — .mappa/share/ (кэш bootstrap, генерируется) 2026-09-11 23:13:32 +03:00
314b15ea25 feat(browser-operator): скил-водитель для браузера оператора (канал по харнессу + аренда)
- skills/browser-operator/SKILL.md: канал по харнессу (Hermes browser_exec / pi тул browser /
  CC chrome-devtools), аренда «один водитель за раз», границы человек/агент, рецепты тяжёлых
  страниц (fetch внутри вкладки, паузы в Python, AX-дерево для кликов), таблица антипаттернов
- основание — RED-прогон без скила (2026-09-11): агент ушёл в curl + browser_cookie3 + ввод
  пароля мимо канала и аренды и не знал адрес кабинета ЧипДипа (/order/list, а не /cabinet)
- browser-cdp 0.1.1: разведены зоны (личные кабинеты оператора -> browser-operator)
- hermes/mapping.yaml: browser-operator (auto/software-development) — сейчас конвертер
  красный из-за 14 ранее незамапленных скилов, это отдельная находка
- README + README.ru: строка провенанса
2026-09-11 22:12:21 +03:00
c310ada38d chore(marker): .mappa/config.yaml — git_host (issue:30, task:2558) 2026-09-06 16:39:41 +03:00
5c726eb5ec docs(project-bootstrap): согласовать doc с mappa-agnostic шаблоном (task:2024 review-minor)
Inline-зеркало шаблона, пояснение об инъекции, строка session sync в
deps-check, README. Строка session handoff (легаси) оставлена — актуальна
для проектов, несущих её в AGENTS.md.
2026-09-02 14:38:35 +03:00
d2059b42bd refactor(project-bootstrap): mappa-agnostic шаблон AGENTS.md — инъекция mappa-триггеров через mappa-bootstrap (task:2024)
Шаблон больше не хардкодит mappa-триггеры (inbox monitor, use project wiki,
use task management system, check across all projects, session handoff убраны).
Kanonicheskiy набор (вкл. session sync) инжектится при создании/апгрейде
через mappa-bootstrap (install.sh --triggers). Step 5.7.2: верификация → инъекция.
2026-09-02 14:34:06 +03:00
2707ba48b5 Revert "chore(project-bootstrap): добавить триггер session sync: write to mappa в шаблон AGENTS.md (task:2015)"
This reverts commit 9c969cefb9.
2026-09-02 13:47:22 +03:00
9c969cefb9 chore(project-bootstrap): добавить триггер session sync: write to mappa в шаблон AGENTS.md (task:2015) 2026-09-02 13:39:06 +03:00
37f617a461 chore(marker): .mappa/config.yaml — маркер mappa-проекта (схема v1) 2026-09-02 09:34:15 +03:00
ccac87200f refactor(1900): mappa-vitya-* → mappa-kzntsv-* в project-bootstrap (бренд kzntsv.dev); ссылка на легаси-репо помечена 2026-09-01 21:28:43 +03:00
b1cc0439a7 chore(dist): rebuild — review-kit-pi-method v0.1.2 (CC-спавн) + синк остальных 2026-09-01 15:25:30 +03:00
3f78c54dd2 feat(review-kit-pi-method): CC-спавн субагент-ревьюера (claude-deepseek) — live-проверен 2026-09-01
v0.1.1→0.1.2: секция CC в Cross-agent — флаги изоляции (--tools '' --disable-slash-commands --no-session-persistence), модель deepseek-v4-flash-vision-exp, обёртка claude-deepseek. Из письма .workshop (task:1849 контекст review-механизмов).
2026-09-01 15:24:41 +03:00
ddcb552601 chore(project-create): relocated to mappa/skills-core (task:1595) — это mappa core-скил, канон в монорепо victor/mappa 2026-08-30 02:04:49 +03:00
74fdbe8070 fix(project-create): уточнить What-NOT-to-do — репо без регистрации = omission, не порядок (review 1594) 2026-08-30 01:58:08 +03:00
973e59b083 feat(project-create): v0.3.0 — путь создания репо = выбор оператора (шаг 2, Hard rule 2: не регистрировать до репо) [agensyn-урок 2026-08-29] 2026-08-30 01:57:45 +03:00
c09901f9a6 feat(project-bootstrap): .mappa маркер при создании проекта — шаг 5.8 + рендер-ассет, project-create шаг 5.5 (wiki:3340, task:1583) [v3.3.0] 2026-08-29 23:55:18 +03:00
e2f2e3a342 chore(1459): убраны mappa-vitya-* из skills-репо — перенесены в victor/mappa-vitya-skills
- удалены skills/mappa-vitya-brainstorming + mappa-vitya-project-discipline
- README: убрана строка provenance mappa-vitya-brainstorming
- project-bootstrap: ссылки на mappa-vitya-project-discipline → victor/mappa-vitya-skills
- правило уведомлений (.admin) уже зафиксировано в целевом репо (3b4d51c)
2026-08-29 23:36:09 +03:00
b529503def docs(1488): переименования после консолидации mappa-расширений — ссылки на mappa.ts
- skills/session-health/SKILL.md: поллер → extensions/mappa.ts (секция session-health, task:1486)
- .wiki/concepts/pi-extension-headless-ritual.md: session-close-ritual → mappa.ts (исторически отдельный файл)
- критерий 6 requirements:1: старые имена в docs/skills/wiki = 0, кроме исторических записей
2026-08-29 09:05:16 +03:00
031268333e fix(1440): review-фикс 1437 — 'review-umbrella' → 'review task (paired or umbrella)' [skip-tdd: visual] 2026-08-28 15:18:45 +03:00
30 changed files with 932 additions and 607 deletions

3
.gitignore vendored
View File

@@ -90,3 +90,6 @@ coverage/
# Missing here made `git status` see `?? .tasks/claims/` → poller skipped every
# claim with "working tree dirty". Mirrors .common/.gitignore.
.tasks/claims/
# mappa bootstrap cache (генерируется, не в репо)
.mappa/share/

9
.mappa/config.yaml Normal file
View File

@@ -0,0 +1,9 @@
# mappa project marker — machine-readable identifier of a mappa project folder
schema_version: 1 # версия схемы файла (bump при изменении структуры)
protocol_version: 1 # версия протокола интерпретации маркера
project: skills
tenant: vitya
url: https://mappa.vds.kzntsv.site
git_provider: gitea
git: OpeItcLoc03/skills
git_host: git.kzntsv.site

View File

@@ -6,7 +6,9 @@ created: 2026-08-12
# pi-extension headless ritual (agent_end, mode guard, loop-guard)
Durable lessons from building `session-close-ritual` (репо `OpeItcLoc03/pi-extensions`, extensions/),
Durable lessons from building `session-close-ritual` (консолидирован в
`extensions/mappa.ts` репо `OpeItcLoc03/pi-extensions`, task:1486; исторически —
отдельный файл `session-close-ritual.ts`),
the headless injector for the session-handoff closing ritual. All three points
were live-verified, not docs-read-only.
@@ -58,6 +60,8 @@ Cache per-cwd; staleness within a long session is accepted (same as
## References
- Source: `~/projects/pi-extensions/extensions/session-close-ritual.ts` (+ `scripts/session-close-ritual.test.mjs`, 12 blocks)
- Source: `~/projects/pi-extensions/extensions/mappa.ts` (секция close-ritual;
консолидация 6 расширений, task:1486 — исторически `session-close-ritual.ts`
+ `scripts/session-close-ritual.test.mjs`, 12 blocks, ныне тесты на mappa.ts)
- Skill: `session-handoff` v0.5.0 — «Headless (pi)» section
- pi docs: `extensions.md` — lifecycle diagram, `sendUserMessage` (deliverAs/triggerTurn), mode table

View File

@@ -8,8 +8,27 @@ check across all projects
pull remote before work
session handoff: read on start, write on end
inbox monitor: raise on start
session sync: write to mappa
follow project discipline
follow tdd-criteria
delegate to interns when allowed
recommend, don't menu
we're on Windows
mappa sync: run on start
<!-- mappa:canon-block (auto; do not edit) -->
## Mappa canon
**Gates (canon/gates — shared):** краткая суть; полный текст — `wiki_get(slug='canon/gates', full=true)`
- Г1. Знание и артефакты → mappa, не файлы — durable-знание и артефакты проекта живут в mappa; файловые каналы (`.brainstorm/`, `.tasks/`, `.wiki/`) и «сохранить рядом с проектом» закрыты.
- Г2. Контракт каналов — письмо (inbox) — носитель вердиктов/указаний/находок, полным телом; комментарии на тасках — короткий след. Адресация `about`/`to`/`thread` (XOR); lifecycle `ack`/`resolve`/`cancel`.
- Г3. Поиск до угадывания — не угадывать slug/роут: сначала `search`/`wiki_search`; `wiki_get` без project = shared; общая память — `search(mode='recall')`.
- Г4. Адресация и слаги — рефы полными именами (`[[task:N]]`/`[[wiki:slug]]`); слаги kebab-case, латиница; номера `task:N` выдаёт сервер.
- Г5. .mappa-гейт — папка участвует в mappa-операциях только с маркером `.mappa`; нет маркера → сказать человеку, мутации — отказ.
- Г6. Секреты — в mappa не пишутся (422); только `secret:<path>`-рефы, значения мимо.
- Г7. Degraded-режим — mappa недоступна: читать кэш `.mappa/` (canon/methodology/runbooks), мутации → `.mappa/pending/`; нет кэша → стоп, не импровизировать.
- Г8. Перед работой с вики/каноном — первым действием прочитать канон-блок AGENTS.md проекта.
- Г9. Живое состояние до заявления — статус заявлять только по свежему чтению mappa, не по памяти/кэшу/ответу create.
**Entity → runbook (runbooks/index — shared):** task → [[runbooks/tasks]] · wiki → [[runbooks/wiki]] · inbox → [[runbooks/inbox]] · **thread** → [[runbooks/threads]] · session → [[runbooks/session]] · search → [[runbooks/search]] · issue → [[runbooks/issue]] · **intent** → [[runbooks/intent]] · requirements → [[runbooks/requirements]] · plan → [[runbooks/plan]] · comment → [[runbooks/comment]] · tag → [[runbooks/tag]] · attachment → [[runbooks/attachment]] · release → [[runbooks/release]] · brainstorm → [[runbooks/brainstorm]] · agent → [[runbooks/agent-operator]] · repo → [[runbooks/repo-commit]] · project → [[runbooks/project]] · skill → [[runbooks/skill]] · entity-слой → [[runbooks/entity]] · sched → [[runbooks/sched-telemetry]]
**Methodology:** `methodology/kzntsv`
**Canon version:** 4
<!-- /mappa:canon-block -->

View File

@@ -114,7 +114,6 @@ an explicit `adapted-from` marker in its frontmatter.
| `find-skills` | `adapted-from: vercel-labs/skills @ c6f69c6` (MIT) — vendored copy |
| `grilling` | `adapted-from: mattpocock/skills @ 84fdeffd` (MIT) — family collapsed to one skill (pi hides `disable-model-invocation` wrappers) |
| `brainstorming` | `adapted-from: obra/superpowers @ 6.2.0` (MIT) — divergent phase, visual-companion dropped |
| `mappa-vitya-brainstorming` | `author: ours` — brainstorm METHODOLOGY for a mappa zone (capture in mappa entity, maturity by criterion, spec → wiki, paired+umbrella review, ask who implements, notify); promotion mechanics delegated to `mappa-brainstorm-promote` |
| `diagnosing-bugs` | `adapted-from: mattpocock/skills @ 84fdeffd` (MIT) + superpowers 6.2.0 concepts (Iron Law, red flags) |
| `loop-me` | `adapted-from: mattpocock/skills @ 84fdeffd` (MIT) — workflow-spec design gate |
| `review-kit-pi-method` | `author: ours` — pi-native spawn for clean-context review subagents |
@@ -123,6 +122,8 @@ an explicit `adapted-from` marker in its frontmatter.
| `code-review` | `adapted-from: mattpocock/skills @ 84fdeffd` (MIT) — two-axis + Fowler baseline; output: caveman-review format |
| `writing-skills` | `adapted-from: obra/superpowers @ 6.2.0` (MIT) — TDD-for-skills core + ideya 8 self-skill-authoring |
| `web-search` | `author: ours` — search_web tool (pi-extension) + policy: when to search, «без поиска» session-off |
| `ops-browser` | `author: ours` — свой **скрытый** браузер агента: отдельный профиль + CDP (`eval`/`fetch` из страницы/скриншоты), `handoff` человеку для пароля/капчи; свой замок `ops.lock` |
| `browser-operator` | `author: ours` — браузер ОПЕРАТОРА (его Chrome/логины): канал по харнессу (Hermes `browser_exec` / pi тул `browser` / CC `chrome-devtools`), аренда «один водитель за раз», границы «человек vs агент», рецепты тяжёлых страниц. Закрывает провал базового прогона 2026-09-11 («куки из Chrome + curl + ввод пароля» мимо канала); анонимные прогоны — `browser-cdp` |
| `review-subagent` | `author: ours` — review_subagent tool (pi-extension): clean-context review by your own model, optional `model` override |
| `report-mappa-issue` | `author: ours` — TEMPORARY stopgap: mappa deviation reporting (mail to `mappa` + `.workshop`) while the service is unstable; retire when stabilized |
| all other `skills/*` | `author: ours` |

View File

@@ -91,6 +91,8 @@ bash scripts/build.sh caveman # один
| `code-search` | `author: ours` — rg-first код-поиск (замер: 15 мин → 0 сек; роутинг: rg / git grep / interns repo_read / grep_audit) |
| `code-review` | `adapted-from: mattpocock/skills @ 84fdeffd` (MIT) — двухосевость + Fowler-база; формат вывода: caveman-review |
| `writing-skills` | `adapted-from: obra/superpowers @ 6.2.0` (MIT) — TDD-for-skills ядро + идея 8 self-skill-authoring |
| `ops-browser` | `author: ours` — свой скрытый браузер агента (профиль + CDP + `handoff` человеку, замок `ops.lock`) |
| `browser-operator` | `author: ours` — браузер ОПЕРАТОРА (его Chrome/логины): канал по харнессу (Hermes `browser_exec` / pi тул `browser` / CC `chrome-devtools`), аренда «один водитель за раз», границы «человек vs агент»; анонимные прогоны — `browser-cdp` |
| остальные `skills/*` | `author: ours` |
Политика адаптации: клон переписывается под наши конвенции (доски `.tasks/`,

BIN
dist/browser-cdp.skill vendored

Binary file not shown.

BIN
dist/browser-operator.skill vendored Normal file

Binary file not shown.

BIN
dist/ops-browser.skill vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -55,6 +55,14 @@ skills:
mode: auto
category: software-development
browser-operator:
mode: auto
category: software-development
ops-browser:
mode: auto
category: software-development
using-markitdown:
mode: auto
category: productivity

View File

@@ -44,7 +44,7 @@ function New-SkillArchive {
[System.IO.Compression.ZipArchiveMode]::Create
)
try {
$files = Get-ChildItem -Path $sourceFull -Recurse -File
$files = Get-ChildItem -Path $sourceFull -Recurse -File | Where-Object { $_.FullName -notmatch '__pycache__' }
foreach ($file in $files) {
$rel = $file.FullName.Substring($sourceFull.Length + 1) -replace '\\','/'
$entryName = "$SkillName/$rel"

View File

@@ -1,13 +1,14 @@
---
name: browser-cdp
author: ours
version: 0.1.0
version: 0.1.1
description: >
Веб-автоматизация через минимальные CDP CLI-тулы в bash — вместо playwright-mcp
/ Chrome-DevTools-MCP (подход «what if you don't need MCP»). Запуск Chrome с remote
debugging, навигация, eval JS, скриншоты. Trigger: «браузер», «скрейпинг», «открой
страницу», «перейди на», «сделай скриншот», «playwright», «веб-автоматизация»,
«web scraping», «browser».
«web scraping», «browser». Для ЛИЧНЫХ КАБИНЕТОВ оператора (его логины, антибот) —
НЕ этот скил, а `browser-operator`.
---
# browser-cdp
@@ -20,6 +21,9 @@ description: >
снять скриншот, собрать данные (скрейпинг). Использовать **вместо** playwright-mcp или
Chrome-DevTools-MCP.
- ⚠️ **Для личных кабинетов оператора этот путь НЕ годится:** здесь свой Chrome и свой
профиль (без его логинов). Нужен браузер оператора — скил `browser-operator`.
## Процесс
1. **Прочитай полную справку** (обязательно, первый шаг):

View File

@@ -0,0 +1,128 @@
---
name: browser-operator
author: ours
version: 0.1.0
description: >
Работа в браузере ОПЕРАТОРА — его Chrome, его профиль, его живые логины: личные
кабинеты поставщиков (ЧипДип, ДКО, Промэлектроника, ТМ), Avito, порталы под
антиботом. Trigger: «зайди в личный кабинет», «открой ЛК», «собери заказы»,
«посмотри в браузере», «нужна его сессия», «ЧипДип/Ozon/Avito», «browser».
НЕ для публичных страниц (там сначала обычный fetch/curl) и НЕ для анонимных
прогонов (там скил browser-cdp).
---
# Browser operator — браузер оператора
**Браузер оператора — разделяемый ресурс с его живыми сессиями.** Работай через
готовый канал своего харнесса и **только под арендой «один водитель за раз»**.
Не вытаскивай его куки, не логинься сам, не поднимай свой Chrome.
## Когда браузер действительно нужен
Сначала спроси себя, нужен ли браузер вообще:
- **Нет** — публичная страница, API, доки: обычный fetch/curl/поиск. Браузер тут лишний.
- **Да** — страница требует взаимодействия (клик/форма/навигация), JS-рендер,
**его логин** (личный кабинет, Avito), либо портал режет не-браузерный трафик.
## Канал по харнессу (не изобретай свой)
| Харнесс | Чем работать | Аренда |
|---|---|---|
| **Hermes** | тул `browser_exec` (демон `browser-use`, тот же профиль оператора) | автоматически: shell-хук `pre_tool_call` берёт аренду и блокирует вызов при чужой |
| **pi** | тул `browser`: `open` / `read` / `js` / `click` / `screenshot` / `lease` | автоматически внутри тула; `lease` покажет, кто держит |
| **Claude Code** | MCP `chrome-devtools` (`list_pages`, `navigate_page`, `take_snapshot`, `evaluate_script`, `click`, `fill`, `take_screenshot`) | автоматически: обёртка берёт аренду перед стартом сервера |
**Никогда:** `curl` с куками из его профиля, `browser_cookie3`-выгрузка куки,
свой `chrome --remote-debugging-port` с пустым профилем (там нет его логинов),
`pip install`-стек ради одного кабинета.
## Аренда: «один водитель в браузере за раз»
Браузер один. Если его держит другой харнесс — **не ломиться**, сказать «занято,
держит X» и вернуться позже (или попросить оператора освободить).
Идёшь в браузер **в обход** тула (например, скриптом или `browser-use` из терминала) —
бери аренду сам:
```bash
BL="$HOME/.config/browser-harness/bin/browser-lease.sh"
HOLD="manual:$$" # метка держателя
MYPID="$(cat /proc/$$/winpid 2>/dev/null || echo 0)" # НАСТОЯЩИЙ windows-PID
bash "$BL" acquire --holder "$HOLD" --ttl 900 --pid "$MYPID" --tool browser # 0 = взял, 3 = занято, 2 = ошибка
# … работа …
bash "$BL" release --holder "$HOLD"
```
**PID — только настоящий.** MSYS `$$` это НЕ windows-PID: по нему живость аренды врёт
(мёртвый держатель выглядит живым, живой — мёртвым). В bash бери `/proc/$$/winpid`,
в pi/Node — `process.pid`; не знаешь — передай `0` («неизвестен», живость решит TTL).
**`driver.lock` руками не трогай** (в том числе пустой или «битый» — это окно чужой
записи): отбор мёртвой аренды делает CLI по `ts`/TTL/мёртвому PID. Чужую аренду не снимай.
Контракт аренды (формат файла, TTL, кого связывать): вики mappa
`concepts/browser-lease-contract`.
## Как работать в страницах (рецепты)
- **Первым делом — своя вкладка.** `ensure_real_tab()` / `new_tab(url)`; не полагайся
на активную вкладку оператора: тяжёлая или аудио-страница подвешивает демон
(все вызовы падают в таймаут, хотя `browser-use --doctor` говорит «alive»).
- **Тяжёлые страницы — читать изнутри, а не обходом.** Если у сайта есть внутренний
JSON-эндпоинт, зови его `fetch(path, {credentials:'include'})` из уже открытой
страницы: это быстрее и надёжнее десятков навигаций.
- **По одной штуке за раз, с паузами.** Паузы — в Python/процессе, **не** в JS `await`
(иначе `Runtime.evaluate timed out`). Ориентир оператора: 1 запрос, пауза 713 с,
перекур каждые ~40, стоп после 3 ошибок подряд.
- **Клики:** сначала дерево доступности (`Accessibility.getFullAXTree`) или
`querySelector` → центр элемента (`getBoundingClientRect`) → `click_at_xy` → **проверь
результат** отдельным `js(...)`/`page_info()`. Не кликай «на глаз» по скриншоту.
- **Прогресс — сразу на диск** (jsonl/atomic), а не в конце прогона: длинные обходы
обрываются.
- **Долгие обходы** — в фоновый процесс, а не в цикл интерактивных вызовов.
## Границы: где человек, а где агент
- **Пароли и второй фактор — никогда.** Не вводим и не просим в аргументах командной
строки. Уже залогиненная сессия оператора — вот твой доступ.
- **Попап «Разрешить удалённую отладку?»** — это человеческое подтверждение: агент его
не жмёт, а просит оператора нажать «Разрешить» и повторяет попытку.
- **Деньги/платежи/отправка форм с персональными данными** — только с явным
подтверждением оператора.
- **Секреты и содержимое залогиненных страниц** не пишем в логи, файлы репозитория и
вики: в mappa — только агрегаты и идентификаторы.
## Проверенные адреса и факты
- **ЧипДип, кабинет заказов:** `https://www.chipdip.ru/order/list`
(⚠️ `/cabinet` и `/orders` отдают 404 — не перебирай наугад, ссылка есть в шапке).
- Залогиненность видна в шапке кабинета (имя оператора); если видишь форму логина —
**сессия потеряна: стоп и скажи оператору**, не логинься сам.
- `about:blank`-вкладка с титулом-лошадкой в `document.title` = страницу ведёт агент.
## Так делать НЕ надо
| Соблазн | Почему нет |
|---|---|
| «Вытащу куки из Chrome и пойду `curl`'ом» | мимо канала и мимо аренды; пароль/2FA оказываются рядом; ломается при смене защиты |
| «Подниму свой Chrome с отладкой» | там **нет** его логинов → выкинет на логин/капчу, плюс это второй водитель |
| «Введу логин/пароль через `read -s`» | пароли и 2FA не вводим никогда |
| «Проверю ещё пяток URL кабинета» | адрес подтверждай по ссылке в интерфейсе, а не перебором |
| «Налью 20 запросов параллельно, быстрее» | антибот + оператор останавливает такие прогоны; один водитель, одна очередь |
Основание: базовый прогон без скила (2026-09-11) ушёл именно в «куки + curl + пароль»
мимо канала; скил закрывает этот путь.
## Красные флаги (стоп и перечитай)
- Собираешься вытащить куки / поднять свой браузер / ввести пароль или код.
- Работаешь с браузером **без** аренды (или ломишься, когда «занято»).
- Полливаешь запросами без паузы или ждёшь паузу в JS.
- Пишешь содержимое залогиненных страниц/секреты в файл, лог или вики.
## Вне скоупа
- Анонимные/антидетект-прогоны, свой профиль, `mode: fresh` — скил `browser-cdp`.
- Облачные браузеры (Browser Use Cloud и прочие) — не берём.
- Свой stdio-MCP-сервер поверх общего демона — отдельная тема.

View File

@@ -1,163 +0,0 @@
---
name: mappa-vitya-brainstorming
author: ours
version: 0.1.0
description: >
The brainstorm METHODOLOGY (the behavior layer) for a mappa knowledge zone —
runs a storm, keeps its running-record buffer, judges MATURITY, and routes
the matured result (spec → wiki concept, tasks, reviews). The mappa promotion
MECHANICS are delegated to mappa-brainstorm-promote. Use when the user opens
a brainstorm or asks whether a storm is mature enough to persist. Triggers:
«забрендшторми», «поштормим», «накидай идеи», «что думаешь про идею»,
«разложи куда что», «когда шторм зрелый», "brainstorm this",
"help me think this through", "run the storm". NOT for: sharpening a finished
plan (→ grilling), a single recommendation (→ recommend-dont-menu), or the
mappa promotion mechanics (→ mappa-brainstorm-promote).
---
# mappa-vitya-brainstorming
**This is the vitya-flavored brainstorm *methodology*.** It tells you how to
run a storm in a mappa zone and where the matured output goes. It does NOT
implement the mappa promotion mechanics — that is `mappa-brainstorm-promote`.
Run a brainstorm **anywhere**, in **any project**, by **any agent**. This is not
boss-zone-only. You (the agent driving the storm) own the method; promotion is
a separate mechanical step you delegate to the promote skill.
A storm produces impl tasks → this skill also covers the review routing. A
storm that produces **zero impl tasks** (a research/decision-only storm) is
mature when the decisions are distilled into a wiki concept and no review
umbrella is created — the criterion holds vacuously, no tasks to review.
**Boundary with the promote skill:** this skill ends at "I have judged
maturity and know the routing shape — which decisions become a spec, which
become tasks, how many reviews". It passes a **routing decision** to
`mappa-brainstorm-promote`, NOT a question of whether to persist.
## When to use
- User opens a brainstorm: «забрендшторми», «накидай идеи», «что думаешь про
идею», «поштормим», "brainstorm this", "run the storm".
- A storm is running and you must decide whether it's mature enough to persist.
- You must route the matured result into mappa (spec → wiki, tasks + reviews).
**NOT for:** sharpening a finished plan (→ grilling), single recommendation
(→ recommend-dont-menu), or the actual mappa promotion mechanics (→
`mappa-brainstorm-promote`).
## Quick reference
| Decision | Answer |
|---|---|
| Where does the buffer live | A mappa `brainstorm` entity (status=buffer). NOT a file. |
| When is a storm mature | The buffer-completion criterion, NOT a gut feel. |
| Where does the spec/decisions go | A mappa **wiki concept**. Before any wiki work — run `mappa-knowledge` first. |
| Multiple impl tasks | **ИЛИ**: единичная таска → парная `<slug>-review`; кластер из одного шторма → зонтичный `<topic>-review` (парные НЕ создаются). Blocked, non-implementer. |
| Who implements | ASK who implements (you are not automatically it). Boss does not implement (Rule 10). |
| Who notifies | Send an `inbox_send` letter to every affected project. |
| Zero-impl storm | Mature when decisions are distilled into a wiki concept; NO review umbrella (criterion holds vacuously). |
## Core method
**1. Capture the buffer in mappa, not in a file.**
Start the storm as a mappa `brainstorm` entity (status=buffer). It is the
running record. Update it after each significant decision, not in batches — a
crash-safe append beats token savings. File-based buffers (`.brainstorm/`,
`notes/*.md`) are legacy; the file channel is closed.
**2. Maturity is a criterion, not a feeling.**
Do not persist on "it feels done". The storm is mature to persist when the
buffer-completion criterion holds: all action-items have been raised as tasks,
and all those tasks are done (including the review-umbrella). Decide this from
the criterion — derived from the graph, not judged by hand.
**3. Distill into decisions, keep the record.**
The buffer records **decisions** (what was decided and why), not just "we
talked". Refs (`[[...]]`) link entities. The value is the decision trail, not
prose volume.
**4. Specs/knowledge → mappa wiki concepts.**
Matured conclusions (the "what we decided and why") become a **mappa wiki
concept** (`wiki_create`). It is a formal enough spec: context → decisions →
rationale → non-goals → open questions, so intent is recoverable and tasks can
reference it. Domain knowledge goes to the target project's wiki; workshop-meta
to the `.workshop` wiki entity. **Before any wiki work — run `mappa-knowledge`
first** (Rule 6), it loads the project's AGENTS entity and the read/ingest
contract.
**5. Review is a separate, non-implementer role.**
Review routing (оператор 2026-08-28): **парные review ИЛИ зонтик** (не BOTH).
- **Единичная импл-таска** из шторма → парная `<slug>-review` (status=blocked, blocker=impl-ref), без зонтика.
- **Кластер тасок** из одного шторма → зонтичный `<topic>-review` на весь кластер (status=blocked, blocker=impl-slugs); парные на каждую таску НЕ создаются (иначе двойное ревью одного и того же).
Reviewer is the next session in the target, **not the implementer** — this
fights the "I just wrote it" bias. You (the brainstorming agent) generate the
review tasks; you do NOT do the code review yourself. The review is done only
when its reviewer confirms — the storm is not mature until the review
(`<slug>-review` ИЛИ `<topic>-review`) is resolved, not merely created.
**6. Ask who implements. Do not assume it's you.**
Brainstorms run anywhere by any agent. The implementer is not automatically the
storming agent. If the project is the boss zone (`.workshop`), the boss does NOT
implement (Rule 10) — it distills and routes via `task_create` to a target
project. In other projects the storming agent may implement, but ASK rather
than assume.
**7. Notify affected projects with a letter.**
A task on a board does not ping a live session. After raising tasks, send an
`inbox_send` letter to every affected project (all except yourself — a
self-copy is not a notification). This is the required ping.
## Where the mappa promotion happens (delegate, don't repeat)
The **mechanics**`wiki_create` / `brainstorm_promote`, `task_create`,
creating review pairs/umbrella, sending the covering letter, adding the final
buffer entry — live in **`mappa-brainstorm-promote`**. This skill does NOT
duplicate those steps; it points to the promote skill for the actual
persistence/promotion.
**Boundary:** you (the brainstorming agent) run the storm and decide maturity
and routing (what becomes a spec, what becomes tasks, how many reviews). The
promote skill executes the mappa write/promotion.
## Common rationalizations (excuse → reality)
| Excuse | Reality |
|---|---|
| "I'll just jot notes in a markdown file" | Buffer must be a mappa brainstorm entity; the file channel is closed. |
| "It feels done, let's write it up" | Maturity is the completion criterion, not a feeling. |
| "The spec can live in docs/ for now" | Spec goes to a mappa wiki concept; before wiki — run `mappa-knowledge`. |
| "I'll review my own tasks, then hand to the user" | Review is a separate non-implementer role; парная ИЛИ зонтик (не BOTH). |
| "I'm the coding agent, I'll just implement it" | Ask who implements; in the boss zone the boss does not implement. |
| "I'll note the impact in the spec, no need to ping" | Notify every affected project with an `inbox_send` letter. |
## Red flags (all = STOP and re-orient)
- You created a `.brainstorm/` or `notes/*.md` file instead of a mappa entity.
- You are persisting because "it feels mature", not by the completion criterion.
- You are about to write the spec to a repo file rather than a mappa wiki concept.
- You are reviewing your own implementation (or the user reviewing it) instead
of a separate non-implementer reviewer.
- You assume you are the implementer for a boss-zone storm.
- You are skipping the `inbox_send` notification to affected projects.
## Out of scope
- Does NOT stress-test a finished plan (→ grilling).
- Does NOT give a single recommendation (→ recommend-dont-menu).
- Does NOT execute the mappa promotion mechanics (→ `mappa-brainstorm-promote`).
- Does NOT cover code-discipline / git / versions / push (→ `project-discipline`
/ `mappa-vitya-project-discipline`).
## Cross-agent applicability
Behavior methodology with a mappa-media dependency. The mappa tool names are
concrete (this zone runs on mappa); the METHOD (capture in a durable record,
maturity by criterion, spec to the durable knowledge store, separate reviewer,
ask who implements, notify affected) transfers to any mappa-based zone. On a
non-mappa setup, substitute the corresponding knowledge channels.
**`mappa-knowledge` is a mandatory precursor before any wiki work** (Rule 6):
it loads the project's AGENTS entity and the read/ingest contract. Never write
a wiki concept without running it first.

View File

@@ -1,59 +0,0 @@
# mappa-vitya-project-discipline
Vitya-flavored cross-project discipline policy skill for a mappa knowledge zone.
Codifies the five work rules (project-canon via mappa, master-only, semver,
push) PLUS four mappa-canon rules (rg-only search, live-verify after reload,
knowledge-first, durable-knowledge-to-wiki).
## When it triggers
- **Session start** — when `AGENTS.md` contains the line
`follow mappa-vitya project discipline` (inserted by `mappa-bootstrap`, which
selects the project's methodology flavor).
- **In-chat** — when the user says "use project discipline", "соблюди
дисциплину", "проектные правила", "follow project discipline", or close
variants.
## The rules
1. **Project canon is a mappa wiki entity, not files.** Read the project's
`AGENTS` entity (`wiki_get(project,'AGENTS')`), NOT `.wiki/CLAUDE.md` /
`.tasks/` (file channel closed). Before any wiki work — run `mappa-knowledge`
first.
2. **Master-only.** All work on `master` (or `main`). No feature branches
without explicit user approval.
3. **Semver discipline.** Bump `version:` in `SKILL.md` / `package.json` /
`pyproject.toml` on every edit per MAJOR / MINOR / PATCH; record in commit
message; rebuild `dist/` artifacts after.
4. **Push freely by default.** No confirmation needed for push; a local push
gate (`push gate: ask` in AGENTS entity, or explicit operator word)
overrides per project. Force / delete / non-ff push always asks.
5. **Transit-zone / brainstorm workspaces (mappa channel).** Brainstorm
artifacts go to a mappa brainstorm entity, not `.brainstorm/*.md`. Only
promote to a wiki concept when the user explicitly directs.
## Mappa-canon rules
- **Rule G — rg-only search.** Never walk node_modules/dist/build with
`grep -r`/`find`/`ag`/`ack`. Use `rg` (gitignore-aware); never `rg --no-ignore`.
- **Rule K — knowledge-first.** Run `mappa-knowledge` before any wiki work.
- **Rule L — live-verify after reload.** Infra changes verified in a live
interactive session, not headless.
- **Rule D — durable knowledge to mappa wiki.** Never `memory/` / local files;
go to a mappa wiki entity.
## Prerequisites
- `mappa` MCP server available (`mcp__mappa__*`) to read the canon.
- `mappa-knowledge` skill installed.
- Methodology flavor selected by `mappa-bootstrap` (the source of the
`follow mappa-vitya project discipline` trigger line).
## Related
- `mappa-bootstrap` — picks the methodology flavor per project and inserts the
trigger line.
- `project-bootstrap` — writes the project scaffold (removed the legacy
`follow project discipline` trigger; the generic skill was replaced by the
per-flavor `mappa-vitya-project-discipline`).
- `mappa-vitya-brainstorming` — the brainstorm METHODOLOGY for a mappa zone.

View File

@@ -1,264 +0,0 @@
---
name: mappa-vitya-project-discipline
author: ours
version: 1.0.0
description: >
The vitya-flavored cross-project discipline for a mappa knowledge zone —
codifies the work rules (project-canon via mappa, master-only, semver, push)
PLUS the four mappa-canon rules (rg-only search, live-verify after reload,
knowledge-first, durable-knowledge-to-wiki). The mappa METHODOLOGY for a
project is selected by mappa-bootstrap, not hardcoded here. Use when the user
says "follow project discipline", "соблюди дисциплину", "проектные правила",
"что у меня по правилам?", "use project discipline", or when AGENTS.md
contains "follow mappa-vitya project discipline". NOT for: the method SELECTION
itself (→ mappa-bootstrap), a project's own AGENTS.md content (→ project-bootstrap).
---
# mappa-vitya-project-discipline
**This is the vitya-flavored cross-project *discipline* skill.** It tells you
the work rules to apply in a project on a mappa-based setup. It is a
policy document — the agent reads it and behaves; it takes no actions and has
no external side-effects.
Run the discipline **anywhere**, in **any project**, by **any agent**. The
methodology flavor is chosen per-project by `mappa-bootstrap` (this is the
`mappa-vitya-*` flavor). The skill itself does NOT select or swap methodology —
that is `mappa-bootstrap`'s job.
**Boundary with `mappa-bootstrap`:** this skill defines WHAT the discipline is
for the vitya flavor. `mappa-bootstrap` decides WHETHER/WHICH methodology a
project uses (by detecting methodology traces in AGENTS.md and installing /
delivering the matching skills). The trigger that activates this skill —
`follow mappa-vitya project discipline` — is inserted by `mappa-bootstrap`, not
baked into a project by hand as canon rules here.
## When to use
- AGENTS.md contains `follow mappa-vitya project discipline` — apply the rules
for the rest of the session.
- User says "use project discipline", "соблюди дисциплину", "проектные
правила", "что у меня по правилам?", "follow project discipline", or asks
about/applying the rules.
**NOT for:** the methodology selection (→ `mappa-bootstrap`), writing a
project's own AGENTS.md content (→ `project-bootstrap`), or the mappa
brainstorm/promotion method (→ `mappa-vitya-brainstorming`).
## The rules
### Rule 1 — Project canon is a mappa wiki entity, not files
Before applying default behavior, read the project's canon from **mappa**, not
from files. On a mappa-based setup the file channel (`.wiki/CLAUDE.md`,
`.tasks/`, `.brainstorm/`) is **closed** — the canonical contract lives as
mappa entities.
Read order:
1. The project's mappa **wiki entity `AGENTS`**`wiki_get(project, 'AGENTS')`
(or for the boss-zone, `wiki_get('.workshop','AGENTS')`). This is the
project's canonical contract.
2. The mappa **handoff** entity (session-start contract) and **inbox** letters.
3. The workspace/zone contract via `.workshop` AGENTS entity where relevant.
Any path, format, or workflow explicitly stated in the project's AGENTS entity
**overrides this skill's default.** No file-based `.wiki/CLAUDE.md` /
`.tasks/` lookups — those are legacy/stubs («не читать, не править»).
**Mandatory precursor:** before ANY wiki work — run `mappa-knowledge` first. It
loads the project's AGENTS entity and the read/ingest contract. Never write a
wiki concept without running it first (this is also mappa-canon rule **K**).
Concrete consequences:
- **Specs / design decisions** → a mappa **wiki concept** (`wiki_create`).
- **Tasks / implementation plans** → mappa **task entities** (`task_create`).
- **Brainstorm buffers** → mappa **brainstorm entities** (`brainstorm_create`),
not `.brainstorm/*.md` files.
- If no convention is stated — fall back to the skill default.
### Rule 2 — Master-only
All work happens on the repo's main integration branch — usually `master`, but
if a project uses `main`, treat `main` as equivalent.
- No `git checkout -b feature/foo` for solo work.
- Sync with remote: `git pull --ff-only` or `git pull --rebase`. **No merge
commits** for solo work.
- If a task genuinely requires isolation (large experiment, risky refactor with
rollback potential, multi-day work with intermediate WIP commits) — **ask**
the user: "this needs its own branch, ok?" — and wait for explicit approval.
Without approval, work continues on master.
- If the agent finds itself on a non-main branch or in detached HEAD — report
it and ask whether to return to master before working.
### Rule 3 — Versioning discipline
When editing any artifact with a semver field, **bump the version before
committing** per:
- **MAJOR** (`X+1.0.0`) — breaks the contract. Renames, removed triggers, layout
changes, removed public functions, breaking API change.
- **MINOR** (`X.Y+1.0`) — adds capability without breaking. New trigger, new
optional step, new public function.
- **PATCH** (`X.Y.Z+1`) — wording / clarity / typo fixes with no behavior change.
The bump is recorded in the commit message: `feat(<artifact>): … [vX.Y.Z]` or
whatever convention the project uses (see Rule 1).
**Applies to:** `skills/<name>/SKILL.md` (`version:` in frontmatter),
`package.json`, `pyproject.toml`, `Cargo.toml`, and any other semver field.
**If the artifact is packaged** as `dist/<name>.skill`**rebuild** the package
in the same or the next commit. Forgotten dist artifacts are a common cause of
deploying stale binaries.
**First edit of an unversioned artifact** that COULD have a semver field — **add**
`version: 0.1.0` before committing; do not bump anything.
### Rule 4 — Push freely, gate only where a local gate exists
**Default: push freely.** An ordinary fast-forward `git push` to the configured
upstream needs no per-push confirmation. No ask-before-push mode by default.
**Push gate** (replaces the free-push default for a specific project). A push
gate makes every push require confirmation — it is set up ONE of two ways:
- **On the record** — the project's AGENTS.md / AGENTS entity declares a push
gate (e.g. `push gate: ask`); it applies persistently for that project.
- **Explicitly, by the operator, in the session** — the operator says "push
gate" / "спрашивай перед пушем"; it applies for the rest of that session OR
until explicitly lifted.
Either way, when a gate is active, EVERY push to that project asks first.
**Always ask:** `git push --force` / `--force-with-lease`; branch deletion;
push to a remote/branch other than the current tracked upstream; push to the
main branch that would require non-fast-forward.
**What counts as "push":** only `git push` family commands. Local commits,
`git stash push`, etc. are not push.
### Rule 5 — Transit-zone / brainstorm workspaces (mappa channel)
Some workspaces are **transit zones** — discussion areas where brainstorm
artifacts are NOT auto-promoted to project wikis. On a mappa setup the default
destination is the mappa brainstorm entity, not a file.
**Default destination for brainstorm artifacts:**
- **In-progress brainstorm outputs** → a mappa **brainstorm entity**
(status=buffer). `.brainstorm/*.md` files are legacy; the file channel is
closed.
- **Mature, cross-cutting outputs** → mappa **wiki concept** (via
`wiki_create`) — **only** when the user explicitly directs this.
**Agent must NOT auto-promote** brainstorm artifacts to wikis by analogy with
Rule 1. The convergence-moment (move from buffer to permanent wiki) is a user
decision, not an automatic action.
**When in doubt:** ask the user "this goes to a mappa brainstorm entity, or
should I promote it to a wiki concept?" rather than assuming.
## Mappa-canon rules
These four rules are the mappa-canon discipline for any mappa-based zone. They
apply in addition to the five above.
### Rule G — rg-only search (never walk node_modules/dist/build)
⛔ Never walk `node_modules/`, `dist/`, `build/`, `.nuxt/`, or any parent tree
containing them with non-gitignore-aware search tools.
- Use **`rg`** (gitignore-aware). NEVER `rg --no-ignore` / `-u` — those bypass
the ignore protection and walk node_modules again.
- Also no `grep -r` / `-R` / `--recursive`, `ag`, `ack`, `find … -exec grep`.
- `grep --include` filters result file names, NOT directory traversal — it still
walks every node_modules entry. `| grep -v node_modules` filters after the
walk, doesn't save you.
Rationale: measured 3701s (grep walk of a projects tree) vs 0s (rg) —
2026-08-26 incident, task:1233.
### Rule K — knowledge-first (mappa-knowledge before any wiki)
Before ANY work with a project's wiki — run the `mappa-knowledge` skill FIRST,
not after the first step. It loads the project's AGENTS entity and the
read/ingest contract. Never write a wiki concept, ingest, or query without
running it first. (Violation noted twice in one session — fixed by rule, not
excuses.)
### Rule L — live-verify after reload
Infrastructure changes (MCP wiring, adapters, configs, bridges) must be
verified in a **live interactive session after reload**, not just by headless
checks. "Fixed, verified headless" is NOT enough — precedent: the
`mappa-http-adapter` (2026-08-25) passed headless but failed real use.
After an infra change, minimally: reload the configuration + make a real
tool call in a live session.
### Rule D — durable knowledge → mappa wiki, never memory/local files
Durable knowledge (domain, ops, methodology) goes to a **mappa wiki entity**,
NOT to local per-project memory (`~/.claude/.../memory/`) or repo files. Memory
is not queryable by peer sessions, not graph-linked, and is lost. If you catch
yourself writing durable knowledge to `memory/` or a local file — stop, redirect
to a mappa wiki entity (domain → target project's wiki; workshop-meta →
`.workshop` wiki entity). User: "все важное писать в вики" (2026-06-18).
## Quick reference
| Decision | Answer |
|---|---|
| Where is the project canon | mappa wiki entity `AGENTS` — NOT `.wiki/CLAUDE.md` / `.tasks/`. |
| Before any wiki work | Run `mappa-knowledge` first (Rule K). |
| Where do specs/design go | mappa wiki concept (`wiki_create`). |
| Where do tasks go | mappa task entities (`task_create`). |
| Where do brainstorm buffers live | mappa brainstorm entity — NOT `.brainstorm/*.md`. |
| Search a codebase | `rg` only — never `grep -r`/`find` over node_modules/dist (Rule G). |
| Infra change verification | Live session after reload, not headless (Rule L). |
| Durable knowledge | mappa wiki, never `memory/` / local files (Rule D). |
| Method flavor / selection | `mappa-bootstrap` selects; this skill only defines the vitya rules. |
## Common rationalizations (excuse → reality)
| Excuse | Reality |
|---|---|
| "Let me just grep -r to be safe" | Use `rg`; `grep -r` walks every node_modules (Rule G). |
| "I'll check CLAUDE.md for conventions" | File channel is closed; read the mappa `AGENTS` entity (Rule 1). |
| "Fixed and typecheck passed — done" | Infra changes need live-verify after reload, not headless (Rule L). |
| "I'll jot this in my project memory" | Durable knowledge goes to a mappa wiki, never `memory/` (Rule D). |
| "I'll write the spec to a repo file" | Spec goes to a mappa wiki concept; run `mappa-knowledge` first (Rule K). |
| "The method is baked into this repo" | Method flavor is per-project, chosen by `mappa-bootstrap`. |
## Red flags (all = STOP and re-orient)
- You are searching with `grep -r` / `find` over a tree that can contain
node_modules/dist/build.
- You are reading `.wiki/CLAUDE.md` / `.tasks/` as the project canon on a
mappa-based setup.
- You are about to write durable knowledge to `memory/` or a local file.
- You are about to write a spec to a repo file rather than a mappa wiki concept.
- You changed an infra/MCP component and only "verified headless".
- You are doing wiki work without running `mappa-knowledge` first.
- You assume this project must use the vitya methodology — let `mappa-bootstrap`
make that selection.
## Out of scope
- Does NOT select or swap the methodology flavor (→ `mappa-bootstrap`).
- Does NOT write a project's AGENTS.md content (→ `project-bootstrap`).
- Does NOT run the brainstorm/promotion method (→ `mappa-vitya-brainstorming`).
- Does NOT install skills (→ `mappa-bootstrap` / `update-skills`).
## Cross-agent applicability
Behavior discipline with a mappa-media dependency. The mappa tool names are
concrete (this zone runs on mappa); the RULES (read the durable canonical
contract, master-only, semver, push-gate, rg-only search, live-verify, durable
knowledge to the wiki) transfer to any mappa-based zone. On a non-mappa setup,
substitute the corresponding knowledge channels.
`mappa-knowledge` is a mandatory precursor before any wiki work (Rule K): it
loads the project's AGENTS entity and the read/ingest contract.

View File

@@ -0,0 +1,90 @@
---
name: ops-browser
author: ours
version: 0.1.0
description: >
Use when a task needs a real browser — личный кабинет, заказы, чеки, yt-digest,
скриншот живого сайта, «дёрни внутренний API». Свой СКРЫТЫЙ браузер агента:
отдельный профиль, CDP, `fetch()` ИЗ страницы, хендофф человеку для пароля/капчи.
---
# ops-browser — свой скрытый браузер агента
**Есть задача, которой нужен браузер? Работай в своём браузере, скрыто, и не подсовывай куки.**
Профиль владеет сессией сам (куки продлеваются браузером, а не руками) — это и есть лечение
боли «куки протухли».
## Когда ЭТОТ инструмент, а когда другой
| Ситуация | Чем работать |
|---|---|
| Публичная страница без логина | `web_search` / `web_extract` / curl — браузер не нужен |
| Личный кабинет, заказы, чеки, внутренний API сайта, антибот | **`ops-browser.sh`** (этот скил) |
| Нужны ЕГО логины и ЕГО Chrome (Avito, кабинеты поставщиков) | канал оператора: Hermes `browser_exec` / pi тул `browser` / CC `chrome-devtools` — всё под арендой `driver.lock` |
| Посмотреть глазами, кликнуть по живому сайту, показать ему | панель предпросмотра (`desktop_preview` + `drive_preview`) — без JS, только текст/клики |
`ops-browser` не заменяет канал оператора: там его сессии, здесь — **мой** профиль.
И то и другое живёт под правилом «один водитель» (у ops свой замок `ops.lock`).
## Команды
```bash
OB="$HOME/.config/browser-harness/bin/ops-browser.sh"
bash "$OB" start # headless + аренда (окон нет)
bash "$OB" open https://site/path # перейти
bash "$OB" read 2000 # видимый текст страницы
bash "$OB" eval 'fetch("/api/x",{credentials:"include"}).then(r=>r.json()).then(j=>JSON.stringify(j))'
bash "$OB" screenshot [path] # PNG, печатает путь
bash "$OB" cookies https://site # имена кук (без значений)
bash "$OB" status # аренда + CDP + текущая страница
bash "$OB" stop # ШТАТНО закрыть и отпустить
bash "$OB" handoff <url> # человеку: пароль/2FA/капча (см. ниже)
```
`eval` выполняет JS **в контексте страницы** — значит `fetch()` идёт с её куками и заголовками:
так дёргают внутренние эндпоинты приложения (то, что недоступно извне и чего не умеет панель).
## Хендофф человеку (пароль, 2FA, капча, согласие)
Останавливаюсь и отдаю экран **сам**, без «скажи готово»:
1. `bash "$OB" handoff <url>` — закрывает headless (профиль нельзя открыть дважды) и поднимает
**видимое** окно ТОГО ЖЕ профиля на нужной странице;
2. человек вводит, что нужно (пароль/2FA/капчу вводит только он);
3. признак успеха ловлю сам: URL ушёл со страницы логина/auth **или** выросли куки домена;
4. штатно закрываю окно и возвращаюсь в headless — логин остался в профиле.
Скриншот делаю **до** шага (что от человека хотят) и **после** (что получилось); **во время ввода
пароля не снимаю**. Если признак успеха не увидел — окно остаётся открытым, а я говорю об этом прямо.
## Границы (не двигаются)
- Пароли, 2FA, капчу — **вводит человек**. Я к ним не подхожу.
- Деньги, оформление заказов, формы с перс.данными — только с явного согласия оператора.
- Куки не выгружаю и не расшифровываю; содержимое залогиненных страниц не уходит в логи/вику.
- Антибот-челленджи не обхожу; если сайт невзлюбил `headless` — это повод для `handoff`, а не для трюков.
## Грабли (стоили времени, проверены живьём)
- **Закрывать только штатно** (`stop``Browser.close`). Жёсткий kill может не сбросить профиль →
потеря логина. Сессионные куки (без срока) перезапуск не переживают по дизайну — настоящие логины живут.
- **MSYS-пути**: нативным программам нужен вид `C:/...` (`cygpath -m`), а `powershell -File`
только `C:\...` (`cygpath -w`). Иначе «модуль не найден» / «файл .ps1 не найден».
- **Экранирование PowerShell внутри bash-строки тихо ломается** (`$_` подменяется) — PS-запросы
держим в отдельных `.ps1`.
- **Профиль нельзя открыть дважды** — перед видимым окном headless обязан остановиться (это делает `handoff`).
- **Профиль**: `~/.config/browser-harness/profiles/ops`; порт CDP: `OPS_CDP_PORT` (9346).
## Аренда
```bash
BL="$HOME/.config/browser-harness/bin/browser-lease.sh"
bash "$BL" --lock ops status # состояние ops-замка (у Chrome оператора — driver.lock)
```
Чужой держатель `ops.lock``start` честно отказывает (rc=1). Мёртвый держатель отпускается по PID/TTL.
Идёшь в браузер **сам** (в обход `ops-browser.sh`)? Возьми `ops.lock` так же, как любой другой харнесс.
Контракт: mappa `concepts/ops-browser-contract` (проект `pi-extensions`),
требования `requirements:42`, план `plan:35`. Исходники: репо `pi-extensions/scripts/browser/`
(установка `just install-browser`).

View File

@@ -39,7 +39,7 @@ Karpathy / `.tasks/` (см. using-wiki/using-tasks, legacy-раздел).
| `README.md` | minimal stub | Skipped if file exists. |
| вики | mappa (`using-wiki`) | сущности `type=wiki` в сервисе; файловый layout — только вне mappa (легаси). |
| таски | mappa (`using-tasks`) | сущности `type=task` в сервисе; файловый `.tasks/` — только вне mappa (легаси). |
| `AGENTS.md` | `assets/AGENTS.md.template` | **Canon**skill triggers (`use project wiki`, `use task management system`, etc.). On non-Windows hosts, swap the `we're on Windows` line for `we're on Linux` / `we're on macOS`. On upgrade, the template is treated as a canonical set and merged idempotently — only missing trigger lines are appended after user confirm. Re-runs are no-ops. |
| `AGENTS.md` | `assets/AGENTS.md.template` | **Canon**mappa-agnostic generic triggers (caveman, pull remote before work, tdd, interns, recommend, platform). Mappa-специфичные триггеры (`inbox monitor: raise on start`, `session sync: write to mappa`, `use project wiki`, `use task management system`, `check across all projects`) инжектятся через mappa-bootstrap (Step 5.7.2). On non-Windows hosts, swap the `we're on Windows` line for `we're on Linux` / `we're on macOS`. On upgrade, the template is treated as a canonical set and merged idempotently — only missing generic trigger lines are appended after user confirm. Re-runs are no-ops. |
| `CLAUDE.md` | generated pointer | `Canon is AGENTS.md. Read AGENTS.md.` — legacy pointer for tooling that looks for the old name. |
| `.wiki/concepts/bootstrap-manifest.md` | generated | Records which skill versions initialized the project, so cross-project layout drift is debuggable. |
@@ -102,9 +102,9 @@ target with `CLAUDE_SKILLS_DIR=/path bash scripts/install.sh …`.
- [`using-wiki`](../using-wiki/) — runtime policy for the mappa wiki (v2).
- [`using-tasks`](../using-tasks/) — runtime policy for the mappa task board (v2).
- [`mappa-vitya-project-discipline`](../mappa-vitya-project-discipline/)
— vitya-flavored cross-project discipline (activated per-project by
`mappa-bootstrap`, which selects the methodology flavor).
- kzntsv-flavored cross-project discipline (activated per-project by
`mappa-bootstrap`, which selects the methodology flavor) — moved to
`victor/mappa-vitya-skills` (легаси, поглощено монорепо) — mappa-kzntsv-project-discipline.
- [`setup-interns`](../setup-interns/), [`using-interns`](../using-interns/) —
pair behind the `delegate to interns when allowed` trigger; cheap-LLM
delegation under a per-session permission grant.

View File

@@ -1,7 +1,7 @@
---
name: project-bootstrap
author: ours
version: 3.2.0
version: 3.3.0
description: >
Initializes or upgrades a project in the current folder: git, .gitignore, README.md,
.wiki/ using Karpathy's method, .tasks/ for task tracking, AGENTS.md (canon) with
@@ -9,6 +9,7 @@ description: >
Creates remote Gitea repo and syncs projects-meta cache for greenfield projects.
Includes the mappa-bootstrap-project module (v3, решение 4 mappa-as-product):
mappa MCP connect + mappa-конвенции + методика-install (версия в манифест).
Creates the `.mappa` marker (wiki:3340) so the folder is a mappa project.
Use this skill when the user says "initialize project", "bootstrap", "setup project",
"upgrade project", "add wiki", "add tasks", "start project", "set everything up",
"create new project", or launches the agent in a new folder and wants a full setup.
@@ -36,6 +37,7 @@ ls -A 2>/dev/null | grep -q . && echo "empty:no" || echo "empty:yes"
[ -d .tasks ] && echo "tasks:yes" || echo "tasks:no"
[ -f CLAUDE.md ] && echo "claude:yes" || echo "claude:no"
[ -f README.md ] && echo "readme:yes" || echo "readme:no"
[ -d .mappa ] && echo "mappa-marker:yes" || echo "mappa-marker:no"
```
Determine mode:
@@ -387,9 +389,6 @@ Template contents (`assets/AGENTS.md.template` — source of truth):
# Agent instructions. Each line is a trigger for an installed skill.
talk like a caveman
use project wiki
use task management system
check across all projects
pull remote before work
follow tdd-criteria
delegate to interns when allowed
@@ -397,10 +396,15 @@ recommend, don't menu
we're on Windows
```
The `check across all projects` trigger activates the **mappa** tooling
(`mcp__mappa__*`) — cross-project boards, shared wiki and the project
registry live in mappa. The file-based `projects-meta-mcp` and its skills
(`using-projects-meta`, `setup-projects-meta`, `meta-host-routing`, `setup-wiki`,
Mappa-специфичные триггеры (`check across all projects`, `inbox monitor:
raise on start`, `use project wiki`, `use task management system`, `session
sync: write to mappa`) **не в шаблоне** — инжектятся через mappa-bootstrap
(Step 5.7.2), project-bootstrap mappa-agnostic. `check across all projects`
активирует **mappa** tooling (`mcp__mappa__*`) — cross-project boards, shared
wiki и реестр проектов живут в mappa.
The file-based `projects-meta-mcp` and its skills (`using-projects-meta`,
`setup-projects-meta`, `meta-host-routing`, `setup-wiki`,
`setup-tasks`) were removed 2026-08-25; the shared `projects-wiki` files are
stubs «не читать, не править» — канон mappa shared-scope.
@@ -426,7 +430,7 @@ which lets Claude offload predictable bulk I/O and summarization tasks
local `interns` MCP server (`mcp__interns__bulk_text_read`,
`mcp__interns__transcript_distill`, etc.) — saves Anthropic quota at ~125× the
per-call cost reduction on bulk reads. Per-session permission grant mirrors the
`mappa-vitya-project-discipline` Rule 4: ask-mode default, conversational grant / revoke,
`mappa-kzntsv-project-discipline` Rule 4 (skill moved from `victor/mappa-vitya-skills`, легаси, поглощено монорепо): ask-mode default, conversational grant / revoke,
always-ask paths for `.env` / secrets / keys / SSH credentials even with an
active grant, session-end reset. The skill is a no-op until the `interns` MCP
server is registered — install via `setup-interns` on a fresh machine if
@@ -514,6 +518,7 @@ Mismatch between template and map → silent gaps in the recommendation.
| `use project wiki` | `mappa-knowledge` | skill | см. mappa-bootstrap (репо mappa) | `cd <mappa-repo> && bash skills/mappa-bootstrap/assets/install.sh` |
| `use task management system` | `mappa-task-work` | skill | см. mappa-bootstrap (репо mappa) | `cd <mappa-repo> && bash skills/mappa-bootstrap/assets/install.sh` |
| `inbox monitor: raise on start` | `mappa-session-orient` | skill | см. mappa-bootstrap (репо mappa) | `cd <mappa-repo> && bash skills/mappa-bootstrap/assets/install.sh` |
| `session sync: write to mappa` | `mappa-session-orient` | skill | см. mappa-bootstrap (репо mappa) | `cd <mappa-repo> && bash skills/mappa-bootstrap/assets/install.sh` |
| `we're on Windows` / `we're on Linux` / `we're on macOS` | `active-platform` | skill | `~/.claude/skills/active-platform/SKILL.md` | `bash scripts/install.sh active-platform` |
### Algorithm
@@ -624,14 +629,22 @@ python -c "import json; d=json.load(open('$HOME/.claude.json')); print('mappa' i
mappa-конвенции в AGENTS.md (5.7.2) можно добавлять и без MCP-регистрации
— триггеры будут ждать установки сервера (как любой absent-скилл).
### 5.7.2 — mappa-конвенции в AGENTS.md (idempotent merge)
### 5.7.2 — mappa-конвенции в AGENTS.md (инъекция через mappa-bootstrap)
mappa-специфичные триггеры уже в каноне шаблона (Step 5) — `inbox monitor:
raise on start`, `use project wiki`, `use task management system`. Это не
отдельный merge: существующая идемпотентная машинерия Step 5 покрывает их.
Модуль только **верифицирует**: после Step 5 убедиться, что строки на месте
(та же substring-проверка что в Step 5 upgrade-merge). Если пользователь
сознательно убрал их из AGENTS.md — не возвращать (уважать выбор).
`project-bootstrap` — mappa-agnostic: mappa-триггеры **не хардкодятся** в
шаблоне (см. `assets/AGENTS.md.template`). Инъекция канонического набора
mappa-триггеров (`inbox monitor: raise on start`, `session sync: write to
mappa`, `use project wiki`, `use task management system`, `check across all
projects`) — через скил `mappa-bootstrap` (репо mappa, единый источник):
```bash
bash <mappa>/skills-core/mappa-bootstrap/assets/install.sh --triggers <project-dir>
```
После инъекции — **верифицировать** наличие строк (substring-проверка, та же
машинерия что Step 5 upgrade-merge). Если пользователь сознательно убрал
mappa-триггеры из AGENTS.md — не возвращать (уважать выбор). Источник истины
набора — mappa-bootstrap, НЕ шаблон project-bootstrap.
### 5.7.3 — методика-install (пакет из репо mappa, версия в манифест)
@@ -682,6 +695,46 @@ install-команда — скил mappa-bootstrap, см. 5.7.3).
---
## Step 5.8 — `.mappa` маркер (контракт wiki:3340)
Машиночитаемый маркер проекта в корне папки (схема v1 — `.mappa/config.yaml`):
гейт mappa-скилов («без маркера папка не участвует в mappa-операциях»,
task:1546) + признак корня проекта для харнессов. Создаётся на bootstrap —
без ручного прогона генератора (task:1583). Детерминированный рендер:
фиксированный порядок полей, без секретов, без timestamp — повторный запуск
no-op (`keep`).
1. **Собрать значения** (реестр mappa → локальное знание):
- `project` — канон папки (`basename "$PWD"`); если проект уже в реестре
(`projects_resolve`) — сверить, не расходится ли;
- `tenant` — `MAPPA_TENANT` (по умолчанию `vitya`);
- `url` — `MAPPA_CORE_URL` (без trailing slash);
- `git_provider` — из реестра `projects.git_provider` (например `gitea`),
иначе из шага 1.5 (создано через Gitea API → gitea); опционально;
- `git` — `projects.qualified` (owner/repo) из реестра, иначе из remote
шага 1.5; опционально (опустить, если неизвестно).
2. **Записать маркер** (скрипт — ассет этого скила, реализует контракт
wiki:3340; в репо: `skills/project-bootstrap/assets/dot_mappa_marker.py`):
```bash
python assets/dot_mappa_marker.py write \
--project "$(basename "$PWD")" --tenant vitya --url "$MAPPA_CORE_URL" \
--git-provider gitea --git "$OWNER/$REPO"
```
Без `--git-provider`/`--git`, если поля неизвестны. Повторный прогон —
no-op (`keep`); отличающийся существующий маркер без `--force` НЕ
перезаписывается — покажи diff и спроси (правило «never overwrite»).
3. **Верифицировать**: `python assets/dot_mappa_marker.py check` → exit 0.
4. **Контракт-тест** (TDD, task:1583): `python assets/test_dot_mappa_marker.py`
— «после bootstrap есть `.mappa/config.yaml`», детерминизм, без секретов,
порядок полей, идемпотентность.
Маркер публичен (без секретов) и попадает в коммит шага 6. Валидный
существующий маркер не трогаем.
---
## Step 6 — Commit
```bash
@@ -706,6 +759,7 @@ Print a final report:
✅ Done! Created:
.wiki/ — project wiki (Karpathy method)
.tasks/ — task tracking system
.mappa/ — mappa project marker (wiki:3340, schema v1)
AGENTS.md — skill triggers (canon)
CLAUDE.md — legacy pointer
.gitignore — standard template

View File

@@ -1,9 +1,12 @@
# AGENTS.md
# Agent instructions. Each line is a trigger for an installed skill.
#
# Inter-session mail channel is Mappa (mcp__mappa__inbox_send/inbox_monitor),
# NOT files. This line opts the project into inbox delivery at session start:
inbox monitor: raise on start
# mappa-специфичные триггеры (inbox monitor: raise on start, session sync:
# write to mappa, use project wiki, use task management system, check across
# all projects) НЕ хардкодятся здесь — project-bootstrap mappa-agnostic. Они
# инжектятся при создании/апгрейде проекта через mappa-bootstrap:
# bash <mappa>/skills-core/mappa-bootstrap/assets/install.sh --triggers <dir>
# (см. Step 5.7.2 — mappa-конвенции в AGENTS.md, делегирование в mappa-bootstrap)
# Search rule
@@ -16,11 +19,7 @@ result file names, NOT directory traversal — it still walks every node_modules
entry; `| grep -v node_modules` filters after the walk, doesn't save you.
talk like a caveman
use project wiki
use task management system
check across all projects
pull remote before work
session handoff: read on start, write on end
follow tdd-criteria
delegate to interns when allowed
recommend, don't menu

View File

@@ -0,0 +1,263 @@
#!/usr/bin/env python3
"""dot_mappa_marker.py — deterministic render + write of the `.mappa` marker.
Contract: mappa wiki:3340 (concepts/dot-mappa-marker), schema v1.
Used by project-create (step 5.5) and project-bootstrap (step 5.8) so a project
folder gets its marker at create time — no manual generator run needed
(task:1583). The batch generator (mappa `server/scripts/gen-dot-mappa-markers.ts`)
remains for registry-wide migration; this is the per-project create path.
Guarantees (the contract):
* `.mappa/config.yaml` — каталог + файл внутри
* fixed field order (schema_version, protocol_version, project, tenant, url,
git_provider?, git?)
* deterministic render — no timestamps, same input → same bytes
* NO secrets — only public registry fields; url with credentials is rejected
* optional fields (`git_provider`, `git`) omitted when absent
* idempotent write: same content → no-op (keep); different content → refuse
without --force
Usage:
python dot_mappa_marker.py render --project NAME --tenant TENANT --url URL \
[--git-provider P] [--git OWNER/REPO] # print content to stdout
python dot_mappa_marker.py write --project NAME --tenant TENANT --url URL \
[--git-provider P] [--git OWNER/REPO] [--dir PATH] [--force] # write marker
python dot_mappa_marker.py check --dir PATH # verify existing marker
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
SCHEMA_VERSION = 1
PROTOCOL_VERSION = 1
# Canonical header comment — same as the contract example (wiki:3340).
HEADER = "# mappa project marker — machine-readable identifier of a mappa project folder"
# YAML: these are indicator characters / reserved tokens — never plain.
_INDICATOR_START = set("!&*{}[],#|>@`\"'%?:~-")
_RESERVED_PLAIN = {"null", "Null", "NULL", "~", "true", "True", "TRUE", "false",
"False", "FALSE", "yes", "Yes", "YES", "no", "No", "NO",
"on", "On", "ON", "off", "Off", "OFF", "-", "?", ":"}
class MarkerConflict(Exception):
"""An existing marker differs from the requested one and --force is absent."""
def yaml_scalar(value: str) -> str:
"""YAML plain-safe scalar: plain without quotes when safe, else double-quoted.
Plain-safe = non-empty, no leading indicator, not a reserved token, no flow
chars, no embedded newlines, no surrounding whitespace. Double-quoting uses
JSON escaping, which is a valid subset of YAML double-quoted style.
"""
s = str(value)
if s == "":
return '""'
if s.strip() != s:
return json.dumps(s)
if s[0] in _INDICATOR_START or s in _RESERVED_PLAIN:
return json.dumps(s)
if s.startswith(("- ", "? ", ": ")):
return json.dumps(s)
# plain scalars stay plain unless they would confuse the parser:
# ": " (mapping indicator), trailing ":", " #" (comment), newlines/tabs
if ": " in s or s.endswith(":") or " #" in s or "\n" in s or "\t" in s:
return json.dumps(s)
return s
def validate_folder_name(name: str) -> str:
"""A folder name (canon/tenant/git_provider) must be a single sane segment."""
if not name or name in (".", ".."):
raise ValueError(f"invalid name {name!r}: must be a non-empty folder name")
if any(sep in name for sep in ("/", "\\", "\x00")):
raise ValueError(f"invalid name {name!r}: must be a single path segment")
if name != name.strip():
raise ValueError(f"invalid name {name!r}: no surrounding whitespace allowed")
return name
def validate_git_ref(git: str) -> str:
"""`git` = projects.qualified (owner/repo) — no credentials, no colon."""
g = str(git)
if not g or "/" not in g:
raise ValueError(f"invalid git ref {g!r}: expected owner/repo")
if any(c in g for c in ("@", ":", " ", "\t", "\n", "\\")):
raise ValueError(f"invalid git ref {g!r}: no credentials / separators allowed")
if g.startswith("/") or g.endswith("/") or ".." in g.split("/"):
raise ValueError(f"invalid git ref {g!r}: must be owner/repo, not a path")
return g
def normalize_url(url: str) -> str:
"""Absolute http(s) URL without credentials and without trailing slash."""
u = str(url).strip()
if not (u.startswith("http://") or u.startswith("https://")):
raise ValueError(f"invalid url {u!r}: must be http(s)://host...")
authority = u.split("://", 1)[1].split("/", 1)[0]
if "@" in authority:
raise ValueError("url must not contain credentials (no secrets in the marker)")
return u.rstrip("/")
def render(
project: str,
tenant: str,
url: str,
git_provider: str | None = None,
git: str | None = None,
) -> str:
"""Deterministic `.mappa/config.yaml` content per wiki:3340 schema v1."""
project = validate_folder_name(project)
tenant = validate_folder_name(tenant)
url = normalize_url(url)
lines = [
HEADER,
f"schema_version: {SCHEMA_VERSION}",
f"protocol_version: {PROTOCOL_VERSION}",
f"project: {yaml_scalar(project)}",
f"tenant: {yaml_scalar(tenant)}",
f"url: {yaml_scalar(url)}",
]
if git_provider:
lines.append(f"git_provider: {yaml_scalar(validate_folder_name(git_provider))}")
if git:
lines.append(f"git: {yaml_scalar(validate_git_ref(git))}")
return "\n".join(lines) + "\n"
def _sane_dir(directory: str | Path) -> Path:
"""Resolve the target directory; reject `..` segments and non-dirs."""
p = Path(directory)
if ".." in p.parts:
raise ValueError(f"invalid directory {str(directory)!r}: '..' segments not allowed")
if p.exists() and not p.is_dir():
raise ValueError(f"invalid directory {str(directory)!r}: not a directory")
return p
def write_marker(directory: str | Path, content: str, force: bool = False) -> tuple[Path, str]:
"""Write `.mappa/config.yaml` under `directory`.
Returns (marker_path, outcome) where outcome is one of
"created" | "keep" (idempotent no-op) | "overwrite" (force).
Raises MarkerConflict when an existing marker differs and force is False.
"""
marker = _sane_dir(directory) / ".mappa" / "config.yaml"
if marker.exists():
existing = marker.read_text(encoding="utf-8")
if existing == content:
return marker, "keep"
if not force:
raise MarkerConflict(
f"{marker} already exists with different content; "
"pass --force to overwrite (contract: no silent overwrite)"
)
marker.write_text(content, encoding="utf-8")
return marker, "overwrite"
marker.parent.mkdir(parents=True, exist_ok=True)
marker.write_text(content, encoding="utf-8")
return marker, "created"
def _parse_marker_lines(body: str) -> list[tuple[str, str]]:
"""(key, value) pairs of data lines — comments skipped, first colon splits."""
pairs = []
for line in body.splitlines():
if not line or line.startswith("#"):
continue
if ": " not in line:
raise ValueError(f"malformed marker line (no 'key: value'): {line!r}")
key, value = line.split(": ", 1)
pairs.append((key, value.strip()))
return pairs
def check_marker(directory: str | Path) -> tuple[bool, str]:
"""Gate check (wiki:3340 / task:1546): is `directory` a mappa project?
Returns (ok, message). ok means `.mappa/config.yaml` exists and its data
lines start with exactly the required fields (schema_version,
protocol_version, project, tenant, url) in canonical order with valid
values; optional `git_provider`/`git` may follow.
"""
marker = _sane_dir(directory) / ".mappa" / "config.yaml"
if not marker.is_file():
return False, f"no marker: {marker} (folder without marker is not a mappa project)"
try:
pairs = _parse_marker_lines(marker.read_text(encoding="utf-8"))
except ValueError as e:
return False, f"marker {marker}: {e}"
if len(pairs) < 5:
return False, f"marker {marker}: fewer than the 5 required fields"
required = ["schema_version", "protocol_version", "project", "tenant", "url"]
if [k for k, _ in pairs[:5]] != required:
return False, f"marker {marker}: field order mismatch ({[k for k, _ in pairs[:5]]})"
values = dict(pairs)
if values["schema_version"] != str(SCHEMA_VERSION):
return False, f"marker {marker}: schema_version must be {SCHEMA_VERSION}"
if values["protocol_version"] != str(PROTOCOL_VERSION):
return False, f"marker {marker}: protocol_version must be {PROTOCOL_VERSION}"
try:
validate_folder_name(values["project"])
validate_folder_name(values["tenant"])
normalize_url(values["url"])
if "git_provider" in values:
validate_folder_name(values["git_provider"])
if "git" in values:
validate_git_ref(values["git"])
except ValueError as e:
return False, f"marker {marker}: {e}"
return True, f"marker ok: {marker}"
def _add_common(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--project", required=True, help="канон папки = реестр projects.name (slug)")
parser.add_argument("--tenant", required=True, help="тенант, где живёт проект (MAPPA_TENANT)")
parser.add_argument("--url", required=True, help="MAPPA_CORE_URL (без trailing slash)")
parser.add_argument("--git-provider", default=None, help="projects.git_provider (gitea/...) — опционально")
parser.add_argument("--git", default=None, help="projects.qualified (owner/repo) — опционально")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=".mappa marker per wiki:3340 (schema v1)")
sub = parser.add_subparsers(dest="cmd", required=True)
p_render = sub.add_parser("render", help="print deterministic marker content")
_add_common(p_render)
p_write = sub.add_parser("write", help="write .mappa/config.yaml into a folder")
_add_common(p_write)
p_write.add_argument("--dir", default=".", help="project folder (default: cwd)")
p_write.add_argument("--force", action="store_true", help="overwrite a differing marker")
p_check = sub.add_parser("check", help="gate check: is the folder a mappa project?")
p_check.add_argument("--dir", default=".", help="project folder (default: cwd)")
args = parser.parse_args(argv)
if args.cmd in ("render", "write"):
content = render(args.project, args.tenant, args.url, args.git_provider, args.git)
if args.cmd == "render":
sys.stdout.write(content)
return 0
marker, outcome = write_marker(args.dir, content, force=args.force)
print(f"{outcome}: {marker}")
return 0
if args.cmd == "check":
ok, msg = check_marker(args.dir)
print(msg)
return 0 if ok else 1
return 2 # unreachable
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,290 @@
#!/usr/bin/env python3
"""Contract test for the `.mappa` marker — mappa wiki:3340 (concepts/dot-mappa-marker).
The contract under test (task:1583): after the project-create/bootstrap marker
step, the project folder contains `.mappa/config.yaml` matching schema v1:
fixed field order, deterministic render (no timestamps), NO secrets, optional
fields (`git_provider`, `git`) omitted when absent, idempotent write.
Run: python test_dot_mappa_marker.py (or: python -m unittest test_dot_mappa_marker)
"""
from __future__ import annotations
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import dot_mappa_marker as dmm # noqa: E402
CANON = "skills" # registry projects.name — канон папки (slug)
TENANT = "vitya"
URL = "https://mappa.vds.kzntsv.site"
GIT_PROVIDER = "gitea"
GIT = "OpeItcLoc03/skills"
FIELD_ORDER = [
"schema_version",
"protocol_version",
"project",
"tenant",
"url",
"git_provider",
"git",
]
def field_keys(body: str) -> list[str]:
return [
line.split(":", 1)[0]
for line in body.splitlines()
if line and not line.startswith("#") and ": " in line
]
def write_contract_marker(tmp: str) -> Path:
"""Helper: create a valid marker as the bootstrap step would."""
marker, outcome = dmm.write_marker(tmp, dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT))
assert outcome == "created"
return marker
class ContractTests(unittest.TestCase):
"""Contract: after bootstrap there is `.mappa/config.yaml` (task:1583)."""
def setUp(self) -> None:
self.tmp = tempfile.mkdtemp(prefix="mappa-marker-test-")
def tearDown(self) -> None:
shutil.rmtree(self.tmp, ignore_errors=True)
# --- presence / shape -------------------------------------------------
def test_bootstrap_marker_step_creates_config_yaml(self) -> None:
"""The bootstrap marker step leaves `.mappa/config.yaml` in the folder."""
content = dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT)
marker, outcome = dmm.write_marker(self.tmp, content)
self.assertEqual(outcome, "created")
self.assertTrue(marker.is_file())
self.assertEqual(marker.name, "config.yaml")
self.assertEqual(marker.parent.name, ".mappa")
def test_fixed_field_order(self) -> None:
body = dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT)
self.assertEqual(field_keys(body), FIELD_ORDER)
def test_deterministic_render_no_timestamp(self) -> None:
a = dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT)
b = dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT)
self.assertEqual(a, b)
# no ISO-date-like content
self.assertNotRegex(a, r"\d{4}-\d{2}-\d{2}")
def test_optional_fields_omitted_when_absent(self) -> None:
body = dmm.render(CANON, TENANT, URL)
self.assertEqual(field_keys(body), FIELD_ORDER[:5])
self.assertNotIn("git_provider", body)
self.assertNotIn("\ngit:", body)
def test_no_secrets_in_marker(self) -> None:
body = dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT)
lowered = body.lower()
# credentials in the url authority are rejected separately
for secret in ("token", "password", "secret", "api_key", "key:", "@"):
self.assertNotIn(secret, lowered)
# --- idempotent write --------------------------------------------------
def test_idempotent_write_keeps_same_content(self) -> None:
content = dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT)
marker, first = dmm.write_marker(self.tmp, content)
marker, second = dmm.write_marker(self.tmp, content)
self.assertEqual(first, "created")
self.assertEqual(second, "keep")
self.assertEqual(marker.read_text(encoding="utf-8"), content)
def test_refuses_overwrite_of_different_marker_without_force(self) -> None:
dmm.write_marker(self.tmp, dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT))
with self.assertRaises(dmm.MarkerConflict):
dmm.write_marker(self.tmp, dmm.render(CANON, TENANT, URL, "github", GIT))
def test_force_overwrites_different_marker(self) -> None:
dmm.write_marker(self.tmp, dmm.render(CANON, TENANT, URL, GIT_PROVIDER, GIT))
marker, outcome = dmm.write_marker(
self.tmp, dmm.render(CANON, TENANT, URL, "github", GIT), force=True
)
self.assertEqual(outcome, "overwrite")
self.assertIn("git_provider: github", marker.read_text(encoding="utf-8"))
# --- input validation ---------------------------------------------------
def test_folder_name_path_segments_rejected(self) -> None:
for bad in ("../evil", "a/b", "a\\b", ".", "..", ""):
with self.assertRaises(ValueError, msg=f"name {bad!r} must be rejected"):
dmm.render(bad, TENANT, URL)
def test_url_trailing_slash_stripped_but_path_kept(self) -> None:
body = dmm.render(CANON, TENANT, URL + "//")
self.assertIn(f"url: {URL}", body)
# a trailing slash after a path must be stripped, the path kept
body2 = dmm.render(CANON, TENANT, "https://example.com/mappa/")
self.assertIn("url: https://example.com/mappa", body2)
def test_url_with_credentials_rejected(self) -> None:
with self.assertRaises(ValueError):
dmm.render(CANON, TENANT, "https://user:pass@mappa.vds.kzntsv.site")
def test_url_scheme_restricted_to_http_https(self) -> None:
for bad in ("ftp://mappa.example", "javascript://x", "mappa.vds.kzntsv.site", "://x"):
with self.assertRaises(ValueError, msg=f"url {bad!r} must be rejected"):
dmm.render(CANON, TENANT, bad)
def test_git_ref_with_credentials_rejected(self) -> None:
for bad in ("user:pass@host/repo", "victor/repo@token", "../config", "/owner/repo", "owner/repo/", "owner repo", "norepo"):
with self.assertRaises(ValueError, msg=f"git {bad!r} must be rejected"):
dmm.render(CANON, TENANT, URL, GIT_PROVIDER, bad)
def test_directory_with_parent_segments_rejected(self) -> None:
with self.assertRaises(ValueError):
dmm.write_marker("some/../elsewhere", dmm.render(CANON, TENANT, URL))
with self.assertRaises(ValueError):
dmm.check_marker("../etc")
# --- YAML scalar edge cases -------------------------------------------
def test_yaml_scalar_quoting_edge_cases(self) -> None:
# reserved tokens / indicators must be double-quoted (never plain)
for special in ("~", "@host", "-", "?", ":", "null", "yes", "on", "true",
"a: b", "ends:", " #lead", "has tab\tinside"):
self.assertTrue(dmm.yaml_scalar(special).startswith('"'),
f"{special!r} must be double-quoted, got {dmm.yaml_scalar(special)!r}")
# plain-safe values stay plain
for plain in ("vitya", "OpeItcLoc03/skills", "https://mappa.vds.kzntsv.site",
"a:b", "x#y", "lead#ing", "my-proj"):
self.assertEqual(dmm.yaml_scalar(plain), plain)
self.assertEqual(dmm.yaml_scalar(""), '""')
# --- check_marker (gate) ----------------------------------------------
def test_check_ok_on_valid_marker(self) -> None:
write_contract_marker(self.tmp)
ok, msg = dmm.check_marker(self.tmp)
self.assertTrue(ok, msg)
def test_check_fails_on_missing_marker(self) -> None:
ok, _ = dmm.check_marker(self.tmp)
self.assertFalse(ok)
def test_check_fails_on_wrong_field_order(self) -> None:
(Path(self.tmp) / ".mappa").mkdir()
(Path(self.tmp) / ".mappa" / "config.yaml").write_text(
"# c\nproject: skills\nschema_version: 1\nprotocol_version: 1\n"
"tenant: vitya\nurl: https://mappa.vds.kzntsv.site\n",
encoding="utf-8",
)
ok, _ = dmm.check_marker(self.tmp)
self.assertFalse(ok)
def test_check_fails_on_extra_field_before_required(self) -> None:
(Path(self.tmp) / ".mappa").mkdir()
(Path(self.tmp) / ".mappa" / "config.yaml").write_text(
"extra: sneaky\nschema_version: 1\nprotocol_version: 1\n"
"project: skills\ntenant: vitya\nurl: https://mappa.vds.kzntsv.site\n",
encoding="utf-8",
)
ok, _ = dmm.check_marker(self.tmp)
self.assertFalse(ok)
def test_check_fails_on_wrong_versions(self) -> None:
(Path(self.tmp) / ".mappa").mkdir()
(Path(self.tmp) / ".mappa" / "config.yaml").write_text(
"schema_version: 2\nprotocol_version: 1\nproject: skills\n"
"tenant: vitya\nurl: https://mappa.vds.kzntsv.site\n",
encoding="utf-8",
)
ok, _ = dmm.check_marker(self.tmp)
self.assertFalse(ok)
def test_check_fails_on_malicious_project_value(self) -> None:
(Path(self.tmp) / ".mappa").mkdir()
(Path(self.tmp) / ".mappa" / "config.yaml").write_text(
"schema_version: 1\nprotocol_version: 1\nproject: ../../evil\n"
"tenant: vitya\nurl: https://mappa.vds.kzntsv.site\n",
encoding="utf-8",
)
ok, _ = dmm.check_marker(self.tmp)
self.assertFalse(ok)
def test_check_fails_on_malformed_line(self) -> None:
(Path(self.tmp) / ".mappa").mkdir()
(Path(self.tmp) / ".mappa" / "config.yaml").write_text(
"schema_version: 1\nprotocol_version: 1\nproject skills\n"
"tenant: vitya\nurl: https://mappa.vds.kzntsv.site\n",
encoding="utf-8",
)
ok, _ = dmm.check_marker(self.tmp)
self.assertFalse(ok)
def test_check_accepts_url_with_port(self) -> None:
(Path(self.tmp) / ".mappa").mkdir()
(Path(self.tmp) / ".mappa" / "config.yaml").write_text(
"schema_version: 1\nprotocol_version: 1\nproject: skills\n"
"tenant: vitya\nurl: https://mappa.example:8443\n",
encoding="utf-8",
)
ok, _ = dmm.check_marker(self.tmp)
self.assertTrue(ok)
# --- CLI end-to-end ----------------------------------------------------
def test_cli_write_creates_marker(self) -> None:
"""End-to-end: the documented CLI command produces the marker."""
proc = subprocess.run(
[
sys.executable,
str(Path(__file__).resolve().parent / "dot_mappa_marker.py"),
"write",
"--project", CANON,
"--tenant", TENANT,
"--url", URL,
"--git-provider", GIT_PROVIDER,
"--git", GIT,
"--dir", self.tmp,
],
capture_output=True,
text=True,
)
self.assertEqual(proc.returncode, 0, proc.stderr)
marker = Path(self.tmp) / ".mappa" / "config.yaml"
self.assertTrue(marker.is_file())
self.assertEqual(field_keys(marker.read_text(encoding="utf-8")), FIELD_ORDER)
def test_cli_check_verifies_marker(self) -> None:
write_contract_marker(self.tmp)
script = Path(__file__).resolve().parent / "dot_mappa_marker.py"
ok = subprocess.run(
[sys.executable, str(script), "check", "--dir", self.tmp],
capture_output=True,
text=True,
)
self.assertEqual(ok.returncode, 0, ok.stderr)
# check on an empty dir fails (gate semantics: no marker → not a mappa project)
empty = tempfile.mkdtemp(prefix="mappa-marker-empty-")
try:
missing = subprocess.run(
[sys.executable, str(script), "check", "--dir", empty],
capture_output=True,
text=True,
)
self.assertNotEqual(missing.returncode, 0)
finally:
shutil.rmtree(empty, ignore_errors=True)
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -1,84 +0,0 @@
---
name: project-create
author: ours
version: 0.1.1
description: >
Mappa-side cycle of creating a new project: ask the operator for the hosting
address (platform + user/org) FIRST — never derive it from neighbouring
projects — then pre-flight checks (free in mappa AND in gitea), then create
mappa registration and the gitea repo SIMULTANEOUSLY (repo via .admin: task
+ covering letter, priority P0, no paired review for ops), then ask where on
disk the project folder goes and what it's named, create the folder, and
hand over to project-bootstrap (general skill). Triggers (bilingual):
«создай проект», «заведи проект», «новый проект», «создать проект на
гите», "create a project", "start a new project", "set up a project",
«куда разместить проект». NOT repo content/bootstrap (→ project-bootstrap),
NOT renaming/re-hosting an existing project.
---
# Project Create (mappa-side)
The cycle of creating a **new project** from the mappa side. Built live on the
tg-digest case (2026-08-27, brainstorm:129): the operator ran the pattern
through a real creation, the skill records it. Guarantees that the hosting
address is the operator's decision (not the agent's guess), that mappa and
gitea stay in sync (created together, not one before the other), and that the
operator picks the on-disk location.
## When to use
Before any `projects_register` / gitea repo creation for a **new** project.
**Activates:** «создай проект», «заведи проект», «новый проект», «создать проект на гите», "create a project", "start a new project", "set up a project".
**Not applicable:**
- Repo content / bootstrap (git init, README, AGENTS.md, .wiki, .tasks, remote push) → `project-bootstrap` (general skill; project-create hands over to it).
- Re-hosting / renaming an existing project → read what's there first, no creation.
- A project that already exists in mappa registry or gitea → verify with the operator, don't create a duplicate.
## Hard rule (the original lesson)
**The hosting address (platform + user/org) is the operator's decision. ASK, never derive.** Do not copy the owner from a neighbouring project (the live failure: `victor/tg-digest` was assumed by analogy with `victor/yt-digest` — wrong, the operator had to correct it). Options to offer: platform (Gitea git.kzntsv.site / GitHub), and for Gitea — the known users/orgs from the registry (e.g. `victor` personal apps, `OpeItcLoc03` infra/meta, `cancel_music` music modules). The registry `mcp__mappa__projects_list` shows the existing `qualified` names — use it to propose options, but the choice is the operator's.
## Steps (the pattern)
1. **Ask the hosting address.** Platform (Gitea/GitHub) + user/org. One question, offer the registry-derived options, wait for the answer. Do NOT register anything yet.
2. **Pre-flight checks** — can we create in BOTH places:
- mappa: the project name is not in the registry (`projects_list` / `projects_resolve`).
- gitea: repo name is free, the user/org exists, we have rights (the .admin owns the tokens — it checks on its side).
3. **Create SIMULTANEOUSLY** (not one before the other):
- `mcp__mappa__projects_register` — mappa registration (id returned).
- repo via `.admin``task_create` in `.admin` (**priority P0** — it blocks the project chain) + covering letter via `inbox_send` (a task on the board doesn't ping a live session). The repo task is an **ops task → NO paired review** (mappa-delegation: skip review for ops). Acceptance: repo created private, answer letter with clone URLs + which token is needed for push.
4. **Ask the operator: where on disk the project folder goes and what it's named** (location AND folder name — both are the operator's call) — do not guess the path or the name.
5. **Create the folder** on the agent's local filesystem (the operator's workstation, not a remote host).
6. **Hand over to bootstrap**`project-bootstrap` (general skill) does git init, .gitignore, README, AGENTS.md, remote connect, push. Separate step, after the repo exists.
## Why the repo goes through `.admin`
Gitea tokens are secrets — they live only with `.admin` (zone rule: secrets stay out of other agents). Precedent: yt-digest (inbox:2305) and tg-digest (inbox:2463) — `.admin` creates the repo via `POST /user/repos` → transfer to the target owner (the admin endpoint refuses without write:admin), then answers with the clone URLs and the push token (`pass show gitea/admin-token` for OpeItcLoc03, or a user token with repo:write for the target owner).
## Failure modes
- The operator hasn't chosen the address → abort, don't create. The address is the operator's call.
- mappa name taken → suggest a rename, don't overwrite.
- gitea name taken / user missing → report, suggest a rename.
- `.admin` repo task failed → report, don't register the project alone (mappa and gitea are created as a pair).
- `task_create` in `.admin` → 422 secret-detected on the token scope string (`write:repository` is flagged) → rephrase ("токен с правами на запись в репозиторий"), don't include raw token strings.
- The operator answers the on-disk path only after the repo is created → that's the normal order, fine.
## What NOT to do
- Don't assume the hosting owner from a neighbouring project — ask (the hard rule).
- Don't create mappa registration alone without the repo (they're a pair).
- Don't create the repo alone without the mappa registration.
- Don't create the on-disk folder in a guessed location — ask.
- Don't do the bootstrap inside this skill — that's `project-bootstrap`.
- Don't create a paired review for the `.admin` repo task (ops task).
- Don't skip the covering letter to `.admin` (task + letter, not just task).
## Reference
- Mappa registry: `mcp__mappa__projects_list` / `projects_register` / `projects_resolve`.
- Repo creation: `.admin` (task + letter, mappa-delegation canon; ops → no review).
- Bootstrap: `project-bootstrap` (general skill).
- Live case: brainstorm:129 (tg-digest, .workshop) — the whole pattern was built there.

View File

@@ -1,7 +1,7 @@
---
name: review-kit-pi-method
author: ours
version: 0.1.1
version: 0.1.2
description: >
Spawn clean-context non-implementer subagents for review, trigger-testing,
and spec validation under pi — the pi-native port of the review-kit method.
@@ -124,6 +124,26 @@ non-implementer subagents, anti-priming checklist, negative controls — is
agent-agnostic and transfers to any runtime that can spawn a fresh-context
subprocess (claude `-p`, codex exec, hermes headless).
### CC-спавн (claude-code через deepseek) — live-проверен 2026-09-01 (session:974)
CC (`claude -p`) умеет субагент-ревью БЕЗ скилов: тот же чистый контекст
(флаги изоляции) через обёртку `claude-deepseek`
(`.common/scripts/claude-switch.ps1`; env `ANTHROPIC_BASE_URL=
https://api.deepseek.com/anthropic` + ключ из `~/.deepseek_api_key`):
```bash
claude -p "<question>" \
--output-format stream-json --verbose \
--model deepseek-v4-flash-vision-exp \
--tools "" --disable-slash-commands --no-session-persistence
```
Флаги изоляции (аналог `-nc -ns -nt` pi): `--tools ""` (нет тулов —
не читает файлы, не самопраймится), `--disable-slash-commands` (нет
команд-интерпретатора), `--no-session-persistence` (эфемерно, без
сессионного блода). Анти-прайминг-чеклист и правила промпта — те же, что
выше (ask the behavior, one question per run, negative controls).
## Out of scope
- Does NOT define the review criteria themselves (skill-specific acceptance —

View File

@@ -16,9 +16,10 @@ description: >
# session-health
Что делать, когда поллер pi (`extensions/session-health.ts`) прислал
предупреждение о размере контекста — или когда сам агент подозревает, что
сессия раздулась. Поллер — единственный источник точных цифр: footer-статус
Что делать, когда поллер pi (`extensions/mappa.ts`, секция session-health —
консолидация 6 расширений, task:1486, wiki:3325) прислал предупреждение о
размере контекста — или когда сам агент подозревает, что сессия раздулась.
Поллер — единственный источник точных цифр: footer-статус
(`14.6%/1.0M`) и `/session` агент (LLM) **не видит** — это TUI для человека.
## When to use