Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ release artifact. Current release versions are:

| Package | Version | Release role |
| --- | ---: | --- |
| `@openspec-ui/core` | 0.11.0 | Shared behavior and persistence contract |
| `@openspec-ui/core` | 0.11.1 | Shared behavior and persistence contract |
| `openspec-ui-vscode` | 0.4.3 | VS Code delivery |
| `@openspec-ui/server` | 1.2.2 | Standalone server delivery |
| `@openspec-ui/webui` | 1.2.1 | Shared browser UI |
| `@openspec-ui/server` | 1.2.3 | Standalone server delivery |
| `@openspec-ui/webui` | 1.2.2 | Shared browser UI |

## Delivery Capability Matrix

Expand Down
182 changes: 66 additions & 116 deletions docs/adr/0001-shared-core-two-delivery-targets.md
Original file line number Diff line number Diff line change
@@ -1,116 +1,66 @@
# 0001: общий core/webui-монорепозиторий, два способа поставки

Статус: Принято — 2026-08-03

## Контекст

Инструмент должен существовать в двух формах: standalone (браузер +
локальный сервер) и расширение VS Code — по прямому требованию заказчика.
Обе формы решают одну и ту же задачу (визуализация Changes/Archive/Specs/
Tasks, запуск CLI-агентов) над одними и теми же данными (`openspec/`, git,
CLI-агенты) — писать логику дважды означало бы гарантированное расхождение
поведения между формами со временем.

Решение обсуждалось в два прохода: первый — с автором этого ADR (Claude,
архитектурный набросок), второй — независимая рецензия внешнего эксперта,
скорректировавшая часть решений (см. "Отклонённые альтернативы" ниже —
изначальная рекомендация по режиму запуска extension была пересмотрена по
итогам рецензии).

## Решение

**1. Монорепозиторий с явным разделением слоёв**: `packages/core` (вся
бизнес-логика — execution engine, openspec-парсер, git-обёртка,
security-модель, derived change-state machine), `packages/webui` (общие
React-компоненты, транспорт-агностичные), `packages/server` (тонкий REST/WS
слой над `core`, только для standalone), `packages/extension` (VS Code
расширение). `server` и `extension` не должны содержать бизнес-логику —
только адаптацию `core` к своему транспорту/host API. Единственный источник
правды по поведению (какая задача что делает, как считается статус change'а)
— `core`; при расхождении между тем, что видит standalone и что видит
extension, это баг в `core` или в тонком адаптере, никогда не "у каждого
своя логика".

**2. Extension: прямой импорт `core` + message bridge — основной режим,
локальный сервер — опциональный.** Extension host уже Node-процесс — может
импортировать `core` напрямую, без HTTP. Webview получает данные через
`postMessage`/`acquireVsCodeApi`, а не через `fetch` к localhost. Локальный
REST/WS сервер (тот же пакет `server`, что использует standalone) остаётся
доступен как опциональный режим — для сценариев, где полностью совпадающий
UX между Webview и standalone важнее, чем отсутствие lifecycle-сложности
localhost-порта (см. "Отклонённые альтернативы").

**3. Унифицированный протокол команд/событий, определённый только в
`core`.** Команды: `plan`, `implement`, `review`, `status`, `cancel`.
События (поток, не единичный ответ): `started`, `stdout`, `stderr`,
`progress`, `completed`, `failed`, `cancelled`. И REST/WS-адаптер
(`server`), и message-bridge-адаптер (`extension`) — тонкие сериализаторы
этого протокола под свой транспорт, не отдельные реализации логики запуска
агента.

**4. Security-модель для оркестрации CLI-агентов — обязательная часть
`core`, не опция:**
- Содержимое файлов репозитория (`proposal.md`/`design.md`/issue-описания
и т.п.) — ДАННЫЕ, передаваемые агенту как контекст, никогда не источник
исполняемых инструкций для самого UI/execution engine (защита от prompt
injection через change, пришедший из чужого PR/архива/шаринга).
- cwd каждого запуска агента жёстко ограничен воркспейсом пользователя, без
возможности выйти за его пределы.
- Allowlist разрешённых команд/аргументов на CLI-агента, не произвольная
командная строка.
- Аудит-лог каждого запуска: что запущено, с каким cwd, какой diff получен
на выходе.

**5. Derived state machine для статуса change (draft/in-progress/
implemented/archived) — вычисляется в ОДНОМ месте (`core`), не хранится
как явное поле.** Сам OpenSpec CLI такого поля не имеет (`.openspec.yaml`
содержит только `schema`/`created`) — статус эвристический, выводится из
факта расположения (`openspec/changes/` vs `openspec/changes/archive/`) и
состояния `tasks.md` (доля отмеченных `[x]`). Эвристика специфицируется
явно в `execution-core` (см. `openspec/changes/execution-core/design.md`),
не оставляется на усмотрение реализации в `server`/`extension`.

**6. Максимум VS Code API вместо своего UI**, где нативный аналог есть:
diff-editor вместо своего diff-viewer, `TreeDataProvider` вместо своего
дерева, встроенный Git extension API вместо своего git UI, Tasks/Terminal
API вместо своей панели "Execution", `contributes.configuration` вместо
своего экрана настроек, Chat Participant API (`vscode.chat.
createChatParticipant`) как альтернативный/дополнительный интерфейс к
AI-панели — тот же механизм, на котором работает сам Copilot Chat.

## Отклонённые альтернативы

- **Extension всегда поднимает тот же локальный сервер, что standalone
(единственный транспорт для `webui` — REST/WS всегда)**: первоначальная
рекомендация, чтобы `webui` не знал о двух транспортах. Отклонено по
итогам рецензии — цена НЕ в поддержке двух тонких транспорт-адаптеров
(это несколько методов интерфейса), а в реальной lifecycle-сложности
localhost-порта в extension: динамический выбор порта, коллизии между
несколькими одновременно открытыми окнами VS Code, cleanup процесса при
закрытии окна. Эта сложность возникала бы в САМОМ ЧАСТОМ сценарии
использования (просто открыть VS Code), а не только в редком. Решение:
message bridge как основной путь, сервер — опциональный режим для
случаев, где полная идентичность UX важнее.
- **Одна реализация исполнения агента в каждом из `server`/`extension`
отдельно** (без единого протокола в `core`): отклонено сразу — гарантирует
расхождение поведения потоковой передачи/ошибок между Webview и
standalone, именно риск, который поднят в рецензии.
- **Хранить статус change явным полем в `.openspec.yaml`**: отклонено —
требовало бы форкать/патчить сам формат OpenSpec, а не работать поверх
него; эвристика в `core` даёт то же самое без модификации чужого
формата.

## Последствия

- `core` становится единственным местом, где чинится любой баг поведения —
это обязывает держать его строго свободным от зависимостей на HTTP/VS
Code API (см. "Критерии качества" в будущем `.github`/`CLAUDE.md` этого
репозитория, по аналогии с `docsai-api`'s hexagonal architecture в
соседнем проекте DocsAI).
- `webui` обязан абстрагировать транспорт (интерфейс `Transport` с двумя
реализациями — `FetchTransport`/`MessageBridgeTransport`), что чуть
увеличивает его сложность против "просто fetch везде", но устраняет
дублирование компонентов между standalone и Webview.
- Security-модель CLI-агентов — не post-hoc доработка, а часть первого
change'а (`execution-core`) — если это отложить, риск получить рабочий
прототип, который небезопасно показывать кому-либо кроме автора.
# 0001: Shared Core Monorepo with Two Delivery Targets

Status: Accepted

Date: 2026-08-03

## Context

OpenSpec UI must ship as both a standalone browser application with a local
server and a VS Code extension. Both targets visualize the same OpenSpec and Git
data and execute the same workflows. Duplicating behavior would cause the two
deliveries to diverge over time.

The delivery model was reviewed independently after the initial architecture
proposal. That review changed the extension transport recommendation while
preserving a shared behavioral core.

## Decision

1. **Use a layered monorepo.** `packages/core` owns all business behavior,
including execution, OpenSpec and Git integration, security, persistence,
and derived change state. `packages/webui` owns transport-neutral React
components. `packages/server` and `packages/extension` are thin host adapters.
2. **Use direct core imports and a message bridge as the extension default.**
The extension host already runs Node.js and does not need HTTP for normal
operation. An optional local server remains available when standalone UI
parity is more important than localhost lifecycle simplicity.
3. **Define the command and event protocol only in core.** Commands are `plan`,
`implement`, `review`, `status`, and `cancel`. Events are `started`, `stdout`,
`stderr`, `progress`, `completed`, `failed`, and `cancelled`. Host adapters
serialize this protocol; they do not reimplement execution.
4. **Keep the agent security model in core.** Repository file content is data,
never executable UI instructions. Every run is restricted to the workspace,
commands and arguments are allowlisted, and executions are audited.
5. **Derive change state in core.** Draft, in-progress, implemented, and archived
states are inferred from location and `tasks.md`; they are not stored in a
custom OpenSpec field.
6. **Prefer native VS Code UI.** The extension uses native diff, tree, Git,
terminal/task, configuration, and Chat APIs where they fit the workflow.

## Rejected Alternatives

### Always run the standalone server inside the extension

Rejected because dynamic ports, window collisions, authentication, and process
cleanup would burden the most common extension workflow. The message bridge is
the default; the local server remains optional.

### Implement agent execution independently in each host

Rejected because streaming, cancellation, errors, and security behavior would
diverge. Both hosts must adapt the same core protocol.

### Store change state in `.openspec.yaml`

Rejected because it would extend an external format. A documented core heuristic
provides status without forking OpenSpec.

## Consequences

- Behavioral defects are fixed in core, which remains independent of HTTP and
VS Code APIs.
- Web UI components depend on a transport interface with fetch and message-bridge
implementations.
- Host UX may differ, but shared behavior and protocol contracts remain testable.
- Security is part of the initial execution architecture rather than a later add-on.
28 changes: 28 additions & 0 deletions docs/english-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# English Policy Maintenance

Repository-authored comments, descriptions, and Markdown must be English.
`npm run lint:english` enforces this policy over files returned by
`git ls-files`.

## Legacy Baseline

`scripts/english-policy-baseline.json` records reviewed legacy lines as a
combination of repository path and normalized-content SHA-256. It is migration
debt, not a general allowlist. Moving or editing a baseline line causes the
check to fail.

Cleanup changes should be package-scoped:

1. Translate or remove legacy text without changing runtime behavior.
2. Run package tests and typecheck.
3. Regenerate the baseline with
`node scripts/check-english.mjs --write-baseline`.
4. Review the baseline reduction and run `npm run lint:english`.

New baseline entries are not accepted. The target is an empty baseline, reached
through independently reviewable package and historical-document cleanup changes.

Intentional internationalization fixture lines may use the inline
`english-policy-allow` marker. The marker must be on the same line as the fixture.
Captured external data may be exempted only by an exact path in the scanner and
requires an OpenSpec-reviewed change.
2 changes: 2 additions & 0 deletions openspec/changes/english-policy-enforcement/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-08
3 changes: 3 additions & 0 deletions openspec/changes/english-policy-enforcement/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# English Policy Enforcement

This change translates legacy repository-authored text and prevents new policy regressions.
30 changes: 30 additions & 0 deletions openspec/changes/english-policy-enforcement/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Context

Editor search exclusions and ignored generated directories make filesystem glob
scans unreliable for policy enforcement. The policy applies to committed source,
not build output or dependencies.

## Decisions

- Enumerate files with `git ls-files` so the scanner checks exactly committed content.
- Scan tracked Markdown, TypeScript, JavaScript, JSON, and YAML for Cyrillic code points.
- Ignore generated and third-party paths even if accidentally present locally.
- Allow a violation only when the same line contains `english-policy-allow`.
- Allow an existing legacy line only when its path and normalized content hash
match the reviewed baseline. Moving or editing it makes it a new violation.
- Exempt the captured `openspec-fixtures/show.json` file because it preserves real
CLI output and its repository prose is data, not authored documentation.
- Print file, line, and a bounded preview for every violation.
- Run the scanner from the root `lint` script before workspace ESLint.

## Trade-offs

- Cyrillic detection enforces the repository's current practical language boundary,
not natural-language identification for every writing system.
- Intentional Cyrillic fixtures require a visible per-line marker.
- The temporary baseline is explicit debt; package-scoped changes remove entries
as comments and fixtures are translated.

## Architecture

This enforces existing repository policy and does not change runtime architecture.
21 changes: 21 additions & 0 deletions openspec/changes/english-policy-enforcement/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Why

Repository policy requires English in comments, descriptions, and Markdown,
but legacy Russian text remains across architecture docs, package docs, source
comments, and tests. CI does not currently enforce the policy.

## What Changes

- Translate policy-critical architecture and package documentation to English.
- Record remaining legacy violations as exact path and line-content hashes.
- Reject new or modified unapproved text in the normal lint gate.
- Keep intentional internationalization fixtures behind explicit inline markers.
- Exempt only the captured real CLI JSON fixture whose source content is data.
- Reduce the baseline to zero through separate package-scoped cleanup changes.

## Impact

- New repository-authored text becomes English-only immediately.
- Existing source cleanup becomes measurable and can proceed without risky bulk rewrites.
- Root tooling gains a dependency-free policy check.
- Package versions receive documentation-only patch bumps where package docs change.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## ADDED Requirements

### Requirement: New repository-authored text follows the English policy
The system SHALL fail the normal lint gate when a tracked authored file contains
new or modified unapproved Cyrillic text.

#### Scenario: Tracked source contains Cyrillic text
- **WHEN** a tracked Markdown, source, JSON, or YAML file contains Cyrillic text
- **AND** its path and normalized content do not match the reviewed legacy baseline
- **THEN** the lint gate fails with file and line diagnostics

#### Scenario: Reviewed legacy line remains unchanged
- **WHEN** a tracked line matches its reviewed baseline path and content hash
- **THEN** the scanner reports no new policy violation

#### Scenario: Intentional internationalization fixture is marked
- **WHEN** a fixture line contains Cyrillic text and an explicit policy marker
- **THEN** the scanner accepts that line

#### Scenario: Real CLI output fixture contains repository prose as data
- **WHEN** the scanner encounters the explicitly exempt captured CLI JSON fixture
- **THEN** it leaves the fixture byte-for-byte unchanged

#### Scenario: Generated files exist locally
- **WHEN** ignored build, dependency, or editor-test output contains Cyrillic text
- **THEN** the tracked-file scanner does not inspect that output
17 changes: 17 additions & 0 deletions openspec/changes/english-policy-enforcement/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## 1. Policy Scanner

- [x] 1.1 Add a dependency-free tracked-file Cyrillic scanner with bounded diagnostics and a hashed legacy baseline.
- [x] 1.2 Add scanner tests for new violations, markers, exclusions, baseline matching, and tracked-file filtering.
- [x] 1.3 Run the scanner from the root lint/verify gate.

## 2. Repository Cleanup

- [x] 2.1 Translate ADR 0001 and package READMEs.
- [x] 2.2 Record remaining reviewed source, historical-note, and fixture debt in the hashed baseline.
- [x] 2.3 Document the package-scoped path to a zero baseline.

## 3. Verification

- [x] 3.1 Bump affected package patch versions and update release documentation.
- [x] 3.2 Run the English scanner, workspace verify/build, and strict OpenSpec validation.
- [x] 3.3 Confirm the scanner rejects an injected unmarked violation in a temporary tracked fixture.
Loading
Loading