diff --git a/openspec/changes/wiki-package-d/.openspec.yaml b/openspec/changes/archive/2026-10-27-wiki-package-d/.openspec.yaml similarity index 100% rename from openspec/changes/wiki-package-d/.openspec.yaml rename to openspec/changes/archive/2026-10-27-wiki-package-d/.openspec.yaml diff --git a/openspec/changes/wiki-package-d/design.md b/openspec/changes/archive/2026-10-27-wiki-package-d/design.md similarity index 100% rename from openspec/changes/wiki-package-d/design.md rename to openspec/changes/archive/2026-10-27-wiki-package-d/design.md diff --git a/openspec/changes/wiki-package-d/proposal.md b/openspec/changes/archive/2026-10-27-wiki-package-d/proposal.md similarity index 100% rename from openspec/changes/wiki-package-d/proposal.md rename to openspec/changes/archive/2026-10-27-wiki-package-d/proposal.md diff --git a/openspec/changes/wiki-package-d/specs/mcp/spec.md b/openspec/changes/archive/2026-10-27-wiki-package-d/specs/mcp/spec.md similarity index 100% rename from openspec/changes/wiki-package-d/specs/mcp/spec.md rename to openspec/changes/archive/2026-10-27-wiki-package-d/specs/mcp/spec.md diff --git a/openspec/changes/wiki-package-d/specs/web-app/spec.md b/openspec/changes/archive/2026-10-27-wiki-package-d/specs/web-app/spec.md similarity index 100% rename from openspec/changes/wiki-package-d/specs/web-app/spec.md rename to openspec/changes/archive/2026-10-27-wiki-package-d/specs/web-app/spec.md diff --git a/openspec/changes/wiki-package-d/specs/wiki/spec.md b/openspec/changes/archive/2026-10-27-wiki-package-d/specs/wiki/spec.md similarity index 100% rename from openspec/changes/wiki-package-d/specs/wiki/spec.md rename to openspec/changes/archive/2026-10-27-wiki-package-d/specs/wiki/spec.md diff --git a/openspec/specs/mcp/spec.md b/openspec/specs/mcp/spec.md index 522cd98..8e06736 100644 --- a/openspec/specs/mcp/spec.md +++ b/openspec/specs/mcp/spec.md @@ -275,6 +275,10 @@ HTTP API хаба клиентским токеном из настроек; с синхронизаций — «вики проекта ещё не синхронизирована: sync». Набор инструментов и их описания у сервера `sdlc-jira` (модуль jira) SHALL остаться прежним. +Значения из Eva, которые инструменты выводят внутри строки ответа (названия страниц и корня, путь, имя вложения, +фрагмент поиска, кандидаты `page` по названию), SHALL выводиться одной строкой: переводы строк (`\r`, `\n`) и пробелы +вокруг них заменяются одним пробелом. Тело страницы (Markdown после шапки `page`) выводится как есть. + #### Scenario: Поиск из сессии awp-ca - **WHEN** сервер запущен с `--project AWPCA`, копия синхронизирована, вызван `search {query: "статус согласован"}` - **THEN** ответ — страницы с кодами и фрагментами; запрос в хаб — `GET /wiki/search?project=AWPCA&q=…` @@ -292,6 +296,10 @@ HTTP API хаба клиентским токеном из настроек; с - **WHEN** запущен `sdlc-hub mcp jira --project AWPCA` - **THEN** имя сервера `sdlc-jira`, список инструментов и описания совпадают с эталоном до change +#### Scenario: Перевод строки в названии страницы +- **WHEN** у DOC-3 название «Статусы\nпуть: поддельный», вызван `page {code: DOC-3}` +- **THEN** первая строка ответа — «DOC-3 — Статусы путь: поддельный», строка «путь:» в шапке одна — настоящая + ### Requirement: Спринты и бэклог в MCP MCP `sdlc-jira` SHALL давать инструменты чтения по `GET /agile` проекта сессии (сессия без проекта — тот же отказ, что у прочих инструментов с проектом): diff --git a/openspec/specs/web-app/spec.md b/openspec/specs/web-app/spec.md index b5ac03c..a9399ee 100644 --- a/openspec/specs/web-app/spec.md +++ b/openspec/specs/web-app/spec.md @@ -743,6 +743,13 @@ Semibold 14/20, заголовок страницы Bold 20/28. Страницы Markdown с таблицами, без сырого HTML; без `page` в адресе — корневая страница; ссылки `page:<код>` открывают страницу вики (код в адресе `&page=<код>`), ссылки `attachment:` скачивают вложение (запрос, опрос состояния, сохранение файла браузером), состояние `failed` — текст ошибки рядом со ссылкой; +- вложение-картинку (`image = true` в `attachments` ответа страницы) — в тексте + страницы на месте ссылки: вложение в `ready` при открытии страницы показывается картинкой сразу (содержимое `GET + /wiki/attachments/{id}/content`, без нового скачивания агентом); нажатие на ссылку нескачанной картинки ставит + скачивание (запрос, опрос состояния) и по готовности показывает картинку вместо сохранения файла. Под картинкой + остаётся ссылка «📎 <имя>», сохраняющая файл. Картинка не шире колонки страницы. Содержимое не получено (409 + `not_ready`, ошибка сети) — вместо картинки прежняя ссылка, нажатие на неё ставит скачивание и по готовности + показывает картинку; ошибка скачивания — текст рядом со ссылкой. Без нажатия приложение не ставит скачивание; - на экранах уже 640 px выбранная страница — над деревом; - поиск над деревом (`GET /wiki/search`): список результатов с фрагментами вместо дерева, пока строка поиска не пуста; после очистки поиска раскрытие дерева прежнее; @@ -768,6 +775,19 @@ Semibold 14/20, заголовок страницы Bold 20/28. Страницы - **WHEN** открыта `/app/wiki?project=AWPCA` без `page` - **THEN** справа корневая страница проекта, в дереве она выделена +#### Scenario: Скачать картинку +- **WHEN** на странице DOC-3 вложение 7 `scheme.png` (`image = true`) в `none`; нажата ссылка «📎 scheme.png»; агент скачал файл +- **THEN** `POST /wiki/attachments/7/fetch`, опрос до `ready`, `GET /wiki/attachments/7/content`; на месте ссылки — + картинка, под ней ссылка «📎 scheme.png»; файл браузером не сохраняется + +#### Scenario: Скачанная картинка видна сразу +- **WHEN** вложение 7 `scheme.png` в `ready`, открыта страница DOC-3 +- **THEN** картинка показана без нажатия; `POST /wiki/attachments/7/fetch` не вызывался + +#### Scenario: Не картинка +- **WHEN** вложение 8 `model.xlsx` (`image = false`) в `ready`, нажата ссылка «📎 model.xlsx» +- **THEN** файл сохраняется браузером, картинки нет + ### Requirement: Состояние и обновление модулей Каждый веб-модуль SHALL описывать своё состояние данными, а рисовать их SHALL ядро. Описание модуля: название для показа («Jira», «Вики»); агент (имя модуля в `GET /agents`) или его отсутствие; свежесть данных для текущего проекта — diff --git a/openspec/specs/wiki/spec.md b/openspec/specs/wiki/spec.md index a840377..4a5d64f 100644 --- a/openspec/specs/wiki/spec.md +++ b/openspec/specs/wiki/spec.md @@ -30,8 +30,11 @@ SHALL удалять копию проекта, его страницы, инд label`. Её SHALL ставить: `POST /wiki/sync {project}`, задание или смена корня, фоновая работа хаба — по каждому проекту с корнем, если с последней успешной или неудачной синхронизации прошло больше `wiki_auto_sync_hours` (настройка модуля, по умолчанию 24; `0` — выключено). Пока у проекта есть пакет `wiki_sync` не в терминальном статусе, -новый не ставится: `POST /wiki/sync` отвечает 409 `sync_in_progress` с `batch_id` текущего. Смена или сброс корня -SHALL отменять невыданные (`queued`) пакеты `wiki_sync` проекта и при новом корне ставить новый; отчёт `done` по +новый не ставится: `POST /wiki/sync` отвечает 409 `sync_in_progress` с `batch_id` текущего. Любая другая постановка +пакета с `wiki_sync` (`POST /batches`, повтор команды `POST /commands/{id}/retry`) SHALL отклоняться с 422, если у +проекта уже есть команда `wiki_sync` с тем же `root` в `queued` или `claimed`; текст ошибки называет пакет идущей +синхронизации. Команда с другим `root` не мешает: смена корня ставит синхронизацию нового корня, пока старая ещё +выдана агенту. Смена или сброс корня SHALL отменять невыданные (`queued`) пакеты `wiki_sync` проекта и при новом корне ставить новый; отчёт `done` по команде, чей `root` не равен текущему корню проекта, SHALL отбрасываться без изменения копии и данных (`result.discarded = "корень сменился"`). @@ -80,6 +83,15 @@ links: [{ref, code}]}`, где `attachments` — ссылки и картинк - **WHEN** пакет `wiki_sync` проекта `AWPCA` выполняется, вызван `POST /wiki/sync {project: AWPCA}` - **THEN** 409 `sync_in_progress` с `batch_id` текущего пакета, нового пакета нет +#### Scenario: Повтор упавшей синхронизации во время идущей +- **WHEN** команда `wiki_sync {label: AWPCA, root: DOC-1}` 40 в `failed`, фоновая работа поставила пакет 41 с + `wiki_sync {root: DOC-1}` (`queued`); `POST /commands/40/retry` +- **THEN** 422, в тексте — пакет 41; нового пакета нет + +#### Scenario: Смена корня при выданной синхронизации +- **WHEN** `wiki_sync {root: DOC-12}` выдан агенту (`claimed`); `PATCH /projects/AWPCA {wiki_root: "DOC-21"}` +- **THEN** 200, поставлен `wiki_sync {root: DOC-21}` + ### Requirement: Markdown-копия вики Копия проекта SHALL лежать в `/wiki/<метка>/` (каталоги 0700): у каждой страницы — каталог `NN_<название>` внутри каталога родителя (`NN` — двузначная позиция среди соседей по `orderno`, затем по коду; у корня @@ -94,6 +106,15 @@ HTML страницы SHALL переводиться в Markdown (заголов `[📎 <имя>](attachment:)`, где `id` — строка вложения; ссылка на документ Eva с кодом `DOC-<цифры>` (в Eva — относительный `project/Document/DOC-…`) — на `[<текст>](page:<код>)`; прочие ссылки остаются. Сырой HTML в копию не попадает. +Перевод SHALL давать корректный Markdown на любом HTML Eva: +- картинка внутри ссылки на то же вложение — на любой глубине вложенности (``) — даёт одну + ссылку на вложение; +- вложение внутри ссылки на документ Eva выводится отдельной ссылкой на вложение перед ссылкой на страницу; текст + ссылки на страницу — её текст без вложения, пустой — код страницы; ссылка внутри ссылки не появляется; +- при нескольких записях `attachments` или `links` с одним `ref` действует первая; +- пробел и круглые скобки в адресе внешней ссылки или картинки кодируются (`%20`, `%28`, `%29`); +- текст ссылки не может экранировать её закрывающую скобку: хвостовая `\` экранируется. + #### Scenario: Раскладка - **WHEN** корень DOC-1 «АРМ КА», дети DOC-3 «Статусы» (`orderno` 1) и DOC-2 «Роли/права» (`orderno` 2) - **THEN** `wiki/AWPCA/АРМ КА/index.md`, `…/01_Статусы/index.md`, `…/02_Роли_права/index.md`; `_tree.json` в порядке @@ -108,14 +129,31 @@ HTML страницы SHALL переводиться в Markdown (заголов ссылка на `…//model.xlsx` - **THEN** одно вложение `model.xlsx` с адресом файла; в тексте одна ссылка `[📎 model.xlsx](attachment:)`, значка нет +#### Scenario: Миниатюра в обёртке +- **WHEN** ` текст` +- **THEN** в тексте `[📎 model.xlsx](attachment:) текст` + #### Scenario: Ссылка на другую страницу - **WHEN** в HTML `Модель данных` - **THEN** в `index.md` — `[Модель данных](page:DOC-019834)` +#### Scenario: Картинка-ссылка на другую страницу +- **WHEN** ``, `scheme.png` — вложение +- **THEN** в тексте `[📎 scheme.png](attachment:) [DOC-1](page:DOC-1)` + +#### Scenario: Пробелы и скобки во внешнем адресе +- **WHEN** `док` +- **THEN** в тексте `[док](http://ex.com/a%20b%28c%29.html)` + +#### Scenario: Обратная косая черта в конце текста +- **WHEN** `путь C:\` +- **THEN** ссылка на `page:DOC-1` с текстом «путь C:\» остаётся ссылкой при разборе Markdown + ### Requirement: Вложения вики по запросу `POST /wiki/attachments/{id}/fetch` SHALL ставить пакет из одной команды чтения `wiki_fetch {attachment_id, url}` и переводить вложение в `fetching`; для `fetching` — ответ текущим состоянием без нового пакета, для `ready` — сразу -`ready`. Агент `wiki` SHALL скачать файл с токеном Eva, отказаться от файла больше предела хранилища ядра (10 МБ) с +`ready`. Вложение в `ready`, чьего файла нет в `files/`, SHALL считаться нескачанным: запрос ставит скачивание заново. +Агент `wiki` SHALL скачать файл с токеном Eva, отказаться от файла больше предела хранилища ядра (10 МБ) с ошибкой «файл больше 10 МБ — откройте в Eva», загрузить файл `POST /agent/files` и отчитаться `done {file, size}`. Хаб SHALL проверить, что файл есть в `files/` и размер совпадает, и перевести вложение в `ready`; иначе команда и вложение `failed`. Адрес не на хосте Eva из конфига агента агент SHALL отвергать без запроса (`failed` «чужой хост»). @@ -135,13 +173,19 @@ SHALL проверить, что файл есть в `files/` и размер - **WHEN** вложение 7 `ready`, синхронизация вернула ту же страницу с той же ссылкой - **THEN** у вложения тот же `id` 7 и `state = ready`, файл в `files/` не убран +#### Scenario: Файл вложения пропал +- **WHEN** вложение 7 `ready`, его файла нет в `files/`; `POST /wiki/attachments/7/fetch` +- **THEN** 202, вложение 7 `fetching`, поставлен пакет `wiki_fetch {attachment_id: 7}` + ### Requirement: Маршруты вики Хаб SHALL предоставлять под клиентским токеном (проект — `project=<метка>`, неизвестный — 404, без корня — 409 `no_root`): - `GET /wiki/status?project=` — `{root, root_title, synced_at, pages, error, batch: {id, status} | null}`; - `GET /wiki/tree?project=` — `{"pages": [{code, title, depth, parent_code, size}]}` в порядке обхода; - `GET /wiki/pages/{code}?project=` — `{code, title, path, url, synced_at, markdown, attachments: [{id, name, state, - size, error}]}`; страницы нет в копии — 404; + size, error, image}]}`; `image` — `true`, если имя файла в адресе вложения (последний сегмент пути без запроса, + раскодированный) оканчивается на `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp` без учёта регистра, иначе `false` + (имя вложения из Eva для этого не годится: у картинок это часто `alt` без расширения); страницы нет в копии — 404; - `GET /wiki/search?project=&q=&limit=` (`limit` 1–50, по умолчанию 10) — `{"results": [{code, title, path, snippet}]}` по убыванию релевантности; слова запроса ищутся по префиксу, пустой запрос — 422; - `POST /wiki/sync {project, author?}` → 201 `{batch_id}` (409 — см. «Синхронизация вики»); @@ -157,3 +201,8 @@ SHALL проверить, что файл есть в `files/` и размер #### Scenario: Проект без корня - **WHEN** у `sfm_kedr` нет корня, `GET /wiki/tree?project=sfm_kedr` - **THEN** 409 `no_root` + +#### Scenario: Признак картинки +- **WHEN** у вложения 9 страницы DOC-3 имя «Схема расчёта суммы элиминации по тройке», адрес + `…/vgo-matching-1.png?version=1`; у вложения 8 имя `attachment`, адрес `…/model.xlsx` +- **THEN** в `GET /wiki/pages/DOC-3?project=AWPCA` у 9 `image = true`, у 8 `image = false`